From b6486821ada03f7c16ef9e646e5a084cc12e76e2 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 10 Apr 2025 22:11:41 +0200 Subject: [PATCH 01/40] shorten release notes --- app/src/main/assets/whats-new.txt | 2 +- fastlane/metadata/android/en-US/changelogs/95.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/assets/whats-new.txt b/app/src/main/assets/whats-new.txt index e7303018d8..a1fef4418e 100644 --- a/app/src/main/assets/whats-new.txt +++ b/app/src/main/assets/whats-new.txt @@ -8,6 +8,6 @@ Key Mapper 3.0 is here! 🎉 🛜 Send HTTP requests with a new action. -❤️ There are also tonnes of improvements to make your key mapping experience more enjoyable. +❤️ There are tonnes of improvements to make your key mapping experience more enjoyable. See all the changes at http://changelog.keymapper.club. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/95.txt b/fastlane/metadata/android/en-US/changelogs/95.txt index e7303018d8..a1fef4418e 100644 --- a/fastlane/metadata/android/en-US/changelogs/95.txt +++ b/fastlane/metadata/android/en-US/changelogs/95.txt @@ -8,6 +8,6 @@ Key Mapper 3.0 is here! 🎉 🛜 Send HTTP requests with a new action. -❤️ There are also tonnes of improvements to make your key mapping experience more enjoyable. +❤️ There are tonnes of improvements to make your key mapping experience more enjoyable. See all the changes at http://changelog.keymapper.club. \ No newline at end of file From 1f10931185073e839b62d9160a997c345a579b9e Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 10 Apr 2025 22:15:28 +0200 Subject: [PATCH 02/40] shorten release notes even more --- app/src/main/assets/whats-new.txt | 6 +++--- fastlane/metadata/android/en-US/changelogs/95.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/assets/whats-new.txt b/app/src/main/assets/whats-new.txt index a1fef4418e..7c29bd65d8 100644 --- a/app/src/main/assets/whats-new.txt +++ b/app/src/main/assets/whats-new.txt @@ -1,13 +1,13 @@ Key Mapper 3.0 is here! 🎉 -🫧 This release introduces Floating Buttons: you can create custom on-screen buttons to trigger key maps. +🫧 Floating Buttons: you can create custom on-screen buttons to trigger key maps. 🗂️ Grouping key maps into folders with shared constraints. -🔦 You can now change the flashlight brightness. Tip: use the constraint for when the flashlight is showing to remap your volume buttons to change the brightness. +🔦 Change the flashlight brightness. Tip: use the constraint for when the flashlight is showing to remap your volume buttons to change the brightness. 🛜 Send HTTP requests with a new action. -❤️ There are tonnes of improvements to make your key mapping experience more enjoyable. +❤️ Many improvements to make your key mapping experience more enjoyable. See all the changes at http://changelog.keymapper.club. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/95.txt b/fastlane/metadata/android/en-US/changelogs/95.txt index a1fef4418e..7c29bd65d8 100644 --- a/fastlane/metadata/android/en-US/changelogs/95.txt +++ b/fastlane/metadata/android/en-US/changelogs/95.txt @@ -1,13 +1,13 @@ Key Mapper 3.0 is here! 🎉 -🫧 This release introduces Floating Buttons: you can create custom on-screen buttons to trigger key maps. +🫧 Floating Buttons: you can create custom on-screen buttons to trigger key maps. 🗂️ Grouping key maps into folders with shared constraints. -🔦 You can now change the flashlight brightness. Tip: use the constraint for when the flashlight is showing to remap your volume buttons to change the brightness. +🔦 Change the flashlight brightness. Tip: use the constraint for when the flashlight is showing to remap your volume buttons to change the brightness. 🛜 Send HTTP requests with a new action. -❤️ There are tonnes of improvements to make your key mapping experience more enjoyable. +❤️ Many improvements to make your key mapping experience more enjoyable. See all the changes at http://changelog.keymapper.club. \ No newline at end of file From bd87fbe03b495a57f81aaf86dcc878e2611f21c3 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 10 Apr 2025 22:58:31 +0200 Subject: [PATCH 03/40] bump version to 3.0.1 --- app/version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/version.properties b/app/version.properties index b2195383b2..2cffa7f2ac 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ -VERSION_NAME=3.0.0 -VERSION_CODE=95 +VERSION_NAME=3.0.1 +VERSION_CODE=96 VERSION_NUM=0 \ No newline at end of file From b4703a79db06e09e5309031b3e669af23644dba4 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 13 Apr 2025 20:55:48 +0200 Subject: [PATCH 04/40] delete redundant untranslated fastlane files --- .../metadata/android/ar/full_description.txt | 43 ------------------- .../metadata/android/ar/short_description.txt | 1 - fastlane/metadata/android/ar/title.txt | 1 - .../android/cs_CZ/full_description.txt | 43 ------------------- .../android/cs_CZ/short_description.txt | 1 - fastlane/metadata/android/cs_CZ/title.txt | 1 - .../android/de_DE/full_description.txt | 43 ------------------- .../android/de_DE/short_description.txt | 1 - fastlane/metadata/android/de_DE/title.txt | 1 - .../android/es_ES/full_description.txt | 43 ------------------- .../android/es_ES/short_description.txt | 1 - fastlane/metadata/android/es_ES/title.txt | 1 - .../android/fr_FR/full_description.txt | 43 ------------------- .../android/fr_FR/short_description.txt | 1 - fastlane/metadata/android/fr_FR/title.txt | 1 - .../android/hu_HU/full_description.txt | 43 ------------------- .../android/hu_HU/short_description.txt | 1 - fastlane/metadata/android/hu_HU/title.txt | 1 - .../android/ka_GE/full_description.txt | 43 ------------------- .../android/ka_GE/short_description.txt | 1 - fastlane/metadata/android/ka_GE/title.txt | 1 - .../android/ko_KR/full_description.txt | 43 ------------------- .../android/ko_KR/short_description.txt | 1 - fastlane/metadata/android/ko_KR/title.txt | 1 - .../android/pl_PL/full_description.txt | 43 ------------------- .../android/pl_PL/short_description.txt | 1 - fastlane/metadata/android/pl_PL/title.txt | 1 - .../android/pt_BR/full_description.txt | 43 ------------------- .../android/pt_BR/short_description.txt | 1 - fastlane/metadata/android/pt_BR/title.txt | 1 - .../android/ru_RU/full_description.txt | 43 ------------------- .../android/ru_RU/short_description.txt | 1 - fastlane/metadata/android/ru_RU/title.txt | 1 - .../metadata/android/sk/full_description.txt | 43 ------------------- .../metadata/android/sk/short_description.txt | 1 - fastlane/metadata/android/sk/title.txt | 1 - .../android/tr_TR/full_description.txt | 43 ------------------- .../android/tr_TR/short_description.txt | 1 - fastlane/metadata/android/tr_TR/title.txt | 1 - .../metadata/android/uk/full_description.txt | 43 ------------------- .../metadata/android/uk/short_description.txt | 1 - fastlane/metadata/android/uk/title.txt | 1 - .../metadata/android/vi/full_description.txt | 43 ------------------- .../metadata/android/vi/short_description.txt | 1 - fastlane/metadata/android/vi/title.txt | 1 - .../android/zh_CN/full_description.txt | 43 ------------------- .../android/zh_CN/short_description.txt | 1 - fastlane/metadata/android/zh_CN/title.txt | 1 - .../android/zh_TW/full_description.txt | 43 ------------------- .../android/zh_TW/short_description.txt | 1 - fastlane/metadata/android/zh_TW/title.txt | 1 - 51 files changed, 765 deletions(-) delete mode 100644 fastlane/metadata/android/ar/full_description.txt delete mode 100644 fastlane/metadata/android/ar/short_description.txt delete mode 100644 fastlane/metadata/android/ar/title.txt delete mode 100644 fastlane/metadata/android/cs_CZ/full_description.txt delete mode 100644 fastlane/metadata/android/cs_CZ/short_description.txt delete mode 100644 fastlane/metadata/android/cs_CZ/title.txt delete mode 100644 fastlane/metadata/android/de_DE/full_description.txt delete mode 100644 fastlane/metadata/android/de_DE/short_description.txt delete mode 100644 fastlane/metadata/android/de_DE/title.txt delete mode 100644 fastlane/metadata/android/es_ES/full_description.txt delete mode 100644 fastlane/metadata/android/es_ES/short_description.txt delete mode 100644 fastlane/metadata/android/es_ES/title.txt delete mode 100644 fastlane/metadata/android/fr_FR/full_description.txt delete mode 100644 fastlane/metadata/android/fr_FR/short_description.txt delete mode 100644 fastlane/metadata/android/fr_FR/title.txt delete mode 100644 fastlane/metadata/android/hu_HU/full_description.txt delete mode 100644 fastlane/metadata/android/hu_HU/short_description.txt delete mode 100644 fastlane/metadata/android/hu_HU/title.txt delete mode 100644 fastlane/metadata/android/ka_GE/full_description.txt delete mode 100644 fastlane/metadata/android/ka_GE/short_description.txt delete mode 100644 fastlane/metadata/android/ka_GE/title.txt delete mode 100644 fastlane/metadata/android/ko_KR/full_description.txt delete mode 100644 fastlane/metadata/android/ko_KR/short_description.txt delete mode 100644 fastlane/metadata/android/ko_KR/title.txt delete mode 100644 fastlane/metadata/android/pl_PL/full_description.txt delete mode 100644 fastlane/metadata/android/pl_PL/short_description.txt delete mode 100644 fastlane/metadata/android/pl_PL/title.txt delete mode 100644 fastlane/metadata/android/pt_BR/full_description.txt delete mode 100644 fastlane/metadata/android/pt_BR/short_description.txt delete mode 100644 fastlane/metadata/android/pt_BR/title.txt delete mode 100644 fastlane/metadata/android/ru_RU/full_description.txt delete mode 100644 fastlane/metadata/android/ru_RU/short_description.txt delete mode 100644 fastlane/metadata/android/ru_RU/title.txt delete mode 100644 fastlane/metadata/android/sk/full_description.txt delete mode 100644 fastlane/metadata/android/sk/short_description.txt delete mode 100644 fastlane/metadata/android/sk/title.txt delete mode 100644 fastlane/metadata/android/tr_TR/full_description.txt delete mode 100644 fastlane/metadata/android/tr_TR/short_description.txt delete mode 100644 fastlane/metadata/android/tr_TR/title.txt delete mode 100644 fastlane/metadata/android/uk/full_description.txt delete mode 100644 fastlane/metadata/android/uk/short_description.txt delete mode 100644 fastlane/metadata/android/uk/title.txt delete mode 100644 fastlane/metadata/android/vi/full_description.txt delete mode 100644 fastlane/metadata/android/vi/short_description.txt delete mode 100644 fastlane/metadata/android/vi/title.txt delete mode 100644 fastlane/metadata/android/zh_CN/full_description.txt delete mode 100644 fastlane/metadata/android/zh_CN/short_description.txt delete mode 100644 fastlane/metadata/android/zh_CN/title.txt delete mode 100644 fastlane/metadata/android/zh_TW/full_description.txt delete mode 100644 fastlane/metadata/android/zh_TW/short_description.txt delete mode 100644 fastlane/metadata/android/zh_TW/title.txt diff --git a/fastlane/metadata/android/ar/full_description.txt b/fastlane/metadata/android/ar/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/ar/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ar/short_description.txt b/fastlane/metadata/android/ar/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ar/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ar/title.txt b/fastlane/metadata/android/ar/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/ar/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/full_description.txt b/fastlane/metadata/android/cs_CZ/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/cs_CZ/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/short_description.txt b/fastlane/metadata/android/cs_CZ/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/cs_CZ/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/title.txt b/fastlane/metadata/android/cs_CZ/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/cs_CZ/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/full_description.txt b/fastlane/metadata/android/de_DE/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/de_DE/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/short_description.txt b/fastlane/metadata/android/de_DE/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/de_DE/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/title.txt b/fastlane/metadata/android/de_DE/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/de_DE/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/full_description.txt b/fastlane/metadata/android/es_ES/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/es_ES/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/short_description.txt b/fastlane/metadata/android/es_ES/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/es_ES/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/title.txt b/fastlane/metadata/android/es_ES/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/es_ES/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/full_description.txt b/fastlane/metadata/android/fr_FR/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/fr_FR/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/short_description.txt b/fastlane/metadata/android/fr_FR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/fr_FR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/title.txt b/fastlane/metadata/android/fr_FR/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/fr_FR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/full_description.txt b/fastlane/metadata/android/hu_HU/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/hu_HU/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/short_description.txt b/fastlane/metadata/android/hu_HU/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/hu_HU/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/title.txt b/fastlane/metadata/android/hu_HU/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/hu_HU/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/full_description.txt b/fastlane/metadata/android/ka_GE/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/ka_GE/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/short_description.txt b/fastlane/metadata/android/ka_GE/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ka_GE/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/title.txt b/fastlane/metadata/android/ka_GE/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/ka_GE/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/full_description.txt b/fastlane/metadata/android/ko_KR/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/ko_KR/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/short_description.txt b/fastlane/metadata/android/ko_KR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ko_KR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/title.txt b/fastlane/metadata/android/ko_KR/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/ko_KR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/full_description.txt b/fastlane/metadata/android/pl_PL/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/pl_PL/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/short_description.txt b/fastlane/metadata/android/pl_PL/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/pl_PL/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/title.txt b/fastlane/metadata/android/pl_PL/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/pl_PL/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/full_description.txt b/fastlane/metadata/android/pt_BR/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/pt_BR/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/short_description.txt b/fastlane/metadata/android/pt_BR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/pt_BR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/title.txt b/fastlane/metadata/android/pt_BR/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/pt_BR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/full_description.txt b/fastlane/metadata/android/ru_RU/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/ru_RU/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/short_description.txt b/fastlane/metadata/android/ru_RU/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ru_RU/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/title.txt b/fastlane/metadata/android/ru_RU/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/ru_RU/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/sk/full_description.txt b/fastlane/metadata/android/sk/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/sk/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/sk/short_description.txt b/fastlane/metadata/android/sk/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/sk/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/sk/title.txt b/fastlane/metadata/android/sk/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/sk/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/tr_TR/full_description.txt b/fastlane/metadata/android/tr_TR/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/tr_TR/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/tr_TR/short_description.txt b/fastlane/metadata/android/tr_TR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/tr_TR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/tr_TR/title.txt b/fastlane/metadata/android/tr_TR/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/tr_TR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/uk/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/uk/short_description.txt b/fastlane/metadata/android/uk/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/uk/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/uk/title.txt b/fastlane/metadata/android/uk/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/uk/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/vi/full_description.txt b/fastlane/metadata/android/vi/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/vi/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/vi/short_description.txt b/fastlane/metadata/android/vi/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/vi/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/vi/title.txt b/fastlane/metadata/android/vi/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/vi/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/full_description.txt b/fastlane/metadata/android/zh_CN/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/zh_CN/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/short_description.txt b/fastlane/metadata/android/zh_CN/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/zh_CN/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/title.txt b/fastlane/metadata/android/zh_CN/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/zh_CN/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/full_description.txt b/fastlane/metadata/android/zh_TW/full_description.txt deleted file mode 100644 index c15c478bd3..0000000000 --- a/fastlane/metadata/android/zh_TW/full_description.txt +++ /dev/null @@ -1,43 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/short_description.txt b/fastlane/metadata/android/zh_TW/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/zh_TW/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/title.txt b/fastlane/metadata/android/zh_TW/title.txt deleted file mode 100644 index 6eb7f19549..0000000000 --- a/fastlane/metadata/android/zh_TW/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper: Unleash your keys! \ No newline at end of file From 3e7b01326f1422e5de2d9281f132af7cf9a4a70f Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 14 Apr 2025 13:38:06 +0200 Subject: [PATCH 05/40] update play store description to include accessibility service disclosure --- fastlane/metadata/android/en-US/full_description.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index c15c478bd3..6863b48341 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -33,6 +33,18 @@ Not currently supported: - Mouse buttons - Joysticks and triggers (LT,RT) on gamepads + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + Come say hi in our Discord community! www.keymapper.club From 362f63d5a3db8faf7a9a054a0b3424421ae7e4ca Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 17 Apr 2025 11:46:30 +0100 Subject: [PATCH 06/40] fix: check that indexes are in range for moving list elements --- .../main/java/io/github/sds100/keymapper/util/ListUtils.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ListUtils.kt b/app/src/main/java/io/github/sds100/keymapper/util/ListUtils.kt index 4eb60c35ee..429f0b7a48 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/ListUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ListUtils.kt @@ -7,6 +7,10 @@ import java.util.Collections */ fun MutableList<*>.moveElement(fromIndex: Int, toIndex: Int) { + if (toIndex >= size || fromIndex >= size) { + return + } + if (fromIndex < toIndex) { for (i in fromIndex until toIndex) { Collections.swap(this, i, i + 1) From bab9c86cfab18b2affe2f35eee890de37bf6bdc0 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 17 Apr 2025 15:33:33 +0100 Subject: [PATCH 07/40] fix: check that a URL can be opened and show a toast if not --- .../keymapper/actions/ActionOptionsBottomSheet.kt | 5 ++++- .../sds100/keymapper/home/HomeKeyMapListScreen.kt | 4 +++- .../mappings/keymaps/ConfigKeyMapScreen.kt | 8 ++++++-- .../mappings/keymaps/KeyMapOptionsScreen.kt | 5 ++++- .../trigger/SetupGuiKeyboardBottomSheet.kt | 5 ++++- .../trigger/TriggerKeyOptionsBottomSheet.kt | 5 ++++- .../keymapper/util/ui/compose/UriHandlerUtils.kt | 15 +++++++++++++++ 7 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/io/github/sds100/keymapper/util/ui/compose/UriHandlerUtils.kt diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt index bfc4bea515..5292cbd8bc 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt @@ -30,6 +30,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource @@ -44,6 +45,7 @@ import io.github.sds100.keymapper.util.ui.SliderStepSizes import io.github.sds100.keymapper.util.ui.compose.CheckBoxText import io.github.sds100.keymapper.util.ui.compose.RadioButtonText import io.github.sds100.keymapper.util.ui.compose.SliderOptionText +import io.github.sds100.keymapper.util.ui.compose.openUriSafe import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -63,6 +65,7 @@ fun ActionOptionsBottomSheet( dragHandle = {}, ) { val uriHandler = LocalUriHandler.current + val ctx = LocalContext.current val helpUrl = stringResource(R.string.url_keymap_action_options_guide) val scope = rememberCoroutineScope() @@ -80,7 +83,7 @@ fun ActionOptionsBottomSheet( modifier = Modifier .align(Alignment.TopEnd) .padding(horizontal = 8.dp), - onClick = { uriHandler.openUri(helpUrl) }, + onClick = { uriHandler.openUriSafe(ctx, helpUrl) }, ) { Icon( imageVector = Icons.AutoMirrored.Rounded.HelpOutline, diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt index 0f955052e4..6b7663bfc7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt @@ -68,6 +68,7 @@ import io.github.sds100.keymapper.util.drawable import io.github.sds100.keymapper.util.ui.compose.CollapsableFloatingActionButton import io.github.sds100.keymapper.util.ui.compose.ComposeChipModel import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo +import io.github.sds100.keymapper.util.ui.compose.openUriSafe @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -140,6 +141,7 @@ fun HomeKeyMapListScreen( val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() val uriHandler = LocalUriHandler.current + val ctx = LocalContext.current val helpUrl = stringResource(R.string.url_quick_start_guide) var keyMapListBottomPadding by remember { mutableStateOf(100.dp) } @@ -183,7 +185,7 @@ fun HomeKeyMapListScreen( onSettingsClick = onSettingsClick, onAboutClick = onAboutClick, onSortClick = { viewModel.showSortBottomSheet = true }, - onHelpClick = { uriHandler.openUri(helpUrl) }, + onHelpClick = { uriHandler.openUriSafe(ctx, helpUrl) }, onExportClick = viewModel::onExportClick, onImportClick = { importFileLauncher.launch(FileUtils.MIME_TYPE_ALL) }, onTogglePausedClick = viewModel::onTogglePausedClick, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt index 9c5b0c9390..3814f879f5 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt @@ -46,6 +46,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Devices @@ -58,6 +59,7 @@ import io.github.sds100.keymapper.actions.ActionsScreen import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.constraints.ConstraintsScreen import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerScreen +import io.github.sds100.keymapper.util.ui.compose.openUriSafe import kotlinx.coroutines.launch @Composable @@ -141,6 +143,7 @@ private fun ConfigKeyMapScreen( var currentTab: ConfigKeyMapTab? by remember { mutableStateOf(null) } val uriHandler = LocalUriHandler.current + val ctx = LocalContext.current BackHandler(onBack = onBackClick) @@ -167,7 +170,7 @@ private fun ConfigKeyMapScreen( } if (url.isNotEmpty()) { - uriHandler.openUri(url) + uriHandler.openUriSafe(ctx, url) } }, ) @@ -505,6 +508,7 @@ private fun ScreenCard( screen: @Composable () -> Unit, ) { val uriHandler = LocalUriHandler.current + val ctx = LocalContext.current OutlinedCard(modifier = modifier) { Column { @@ -520,7 +524,7 @@ private fun ScreenCard( color = MaterialTheme.colorScheme.primary, ) - IconButton(onClick = { uriHandler.openUri(helpUrl) }) { + IconButton(onClick = { uriHandler.openUriSafe(ctx, helpUrl) }) { Icon( Icons.AutoMirrored.Rounded.HelpOutline, contentDescription = stringResource(R.string.button_help), diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapOptionsScreen.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapOptionsScreen.kt index 5f3b5b3f17..03216b2bf0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapOptionsScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapOptionsScreen.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.LocalClipboard +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -48,6 +49,7 @@ import io.github.sds100.keymapper.util.ui.SliderMinimums import io.github.sds100.keymapper.util.ui.SliderStepSizes import io.github.sds100.keymapper.util.ui.compose.CheckBoxText import io.github.sds100.keymapper.util.ui.compose.SliderOptionText +import io.github.sds100.keymapper.util.ui.compose.openUriSafe import kotlinx.coroutines.launch @Composable @@ -323,6 +325,7 @@ private fun TriggerFromOtherAppsSection( } val uriHandler = LocalUriHandler.current + val ctx = LocalContext.current val intentGuideUrl = stringResource(R.string.url_trigger_by_intent_guide) FilledTonalButton( @@ -330,7 +333,7 @@ private fun TriggerFromOtherAppsSection( .fillMaxWidth() .padding(horizontal = 16.dp), onClick = { - uriHandler.openUri(intentGuideUrl) + uriHandler.openUriSafe(ctx, intentGuideUrl) }, ) { Text(text = stringResource(R.string.button_open_trigger_keymap_from_intent_guide)) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardBottomSheet.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardBottomSheet.kt index 767c250ffd..51c21bd786 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardBottomSheet.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardBottomSheet.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource @@ -34,6 +35,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme +import io.github.sds100.keymapper.util.ui.compose.openUriSafe import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -101,6 +103,7 @@ private fun SetupGuiKeyboardBottomSheet( ) { val scope = rememberCoroutineScope() val uriHandler = LocalUriHandler.current + val ctx = LocalContext.current val scrollState = rememberScrollState() ModalBottomSheet( @@ -145,7 +148,7 @@ private fun SetupGuiKeyboardBottomSheet( buttonTextEnabled = stringResource(R.string.setup_gui_keyboard_install_keyboard_button), buttonTextDisabled = stringResource(R.string.setup_gui_keyboard_install_keyboard_button_disabled), onButtonClick = { - uriHandler.openUri(guiKeyboardUrl) + uriHandler.openUriSafe(ctx, guiKeyboardUrl) }, ) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt index 81197a6383..b84c05d57e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt @@ -27,6 +27,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource @@ -40,6 +41,7 @@ import io.github.sds100.keymapper.mappings.FingerprintGestureType import io.github.sds100.keymapper.util.ui.CheckBoxListItem import io.github.sds100.keymapper.util.ui.compose.CheckBoxText import io.github.sds100.keymapper.util.ui.compose.RadioButtonText +import io.github.sds100.keymapper.util.ui.compose.openUriSafe import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @@ -65,6 +67,7 @@ fun TriggerKeyOptionsBottomSheet( dragHandle = {}, ) { val uriHandler = LocalUriHandler.current + val ctx = LocalContext.current val helpUrl = stringResource(R.string.url_trigger_key_options_guide) val scope = rememberCoroutineScope() @@ -82,7 +85,7 @@ fun TriggerKeyOptionsBottomSheet( modifier = Modifier .align(Alignment.TopEnd) .padding(horizontal = 8.dp), - onClick = { uriHandler.openUri(helpUrl) }, + onClick = { uriHandler.openUriSafe(ctx, helpUrl) }, ) { Icon( imageVector = Icons.AutoMirrored.Rounded.HelpOutline, diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/UriHandlerUtils.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/UriHandlerUtils.kt new file mode 100644 index 0000000000..59b70ff257 --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/UriHandlerUtils.kt @@ -0,0 +1,15 @@ +package io.github.sds100.keymapper.util.ui.compose + +import android.content.Context +import android.widget.Toast +import androidx.compose.ui.platform.UriHandler +import io.github.sds100.keymapper.R +import io.github.sds100.keymapper.util.str + +fun UriHandler.openUriSafe(ctx: Context, uri: String) { + try { + openUri(uri) + } catch (e: IllegalArgumentException) { + Toast.makeText(ctx, ctx.str(R.string.error_no_app_to_open_url), Toast.LENGTH_SHORT).show() + } +} From bf09b42858ecf01e58172d57d105268966ace757 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 17 Apr 2025 15:45:07 +0100 Subject: [PATCH 08/40] fix: wait for input events from Shizuku to processed by the application before sending the next one --- .../keymapper/shizuku/ShizukuInputEventInjector.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt b/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt index 6fabff179c..cd890bac34 100644 --- a/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt +++ b/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt @@ -16,7 +16,9 @@ import timber.log.Timber class ShizukuInputEventInjector : InputEventInjector { companion object { - private const val INJECT_INPUT_EVENT_MODE_ASYNC = 0 + // private const val INJECT_INPUT_EVENT_MODE_ASYNC = 0 + + private const val INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2 } private val iInputManager: IInputManager by lazy { @@ -46,12 +48,15 @@ class ShizukuInputEventInjector : InputEventInjector { model.scanCode, ) - iInputManager.injectInputEvent(keyEvent, INJECT_INPUT_EVENT_MODE_ASYNC) + // MUST wait for the application to finish processing the event before sending the next one. + // Otherwise, rapidly repeating input events will go in a big queue and all inputs + // into the application will be delayed or overloaded. + iInputManager.injectInputEvent(keyEvent, INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH) if (model.inputType == InputEventType.DOWN_UP) { val upEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_UP) - iInputManager.injectInputEvent(upEvent, INJECT_INPUT_EVENT_MODE_ASYNC) + iInputManager.injectInputEvent(upEvent, INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH) } } } From 90d2af86bc69904aeaf83188a902b1e156758ac2 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 17 Apr 2025 16:05:52 +0100 Subject: [PATCH 09/40] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 2cffa7f2ac..5dd3378be1 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.0.1 -VERSION_CODE=96 +VERSION_CODE=97 VERSION_NUM=0 \ No newline at end of file From 18afd17c2f8c3e468352eb902c9beb685cf5cbf0 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 17 Apr 2025 19:01:24 +0100 Subject: [PATCH 10/40] show version code in about page --- .../main/java/io/github/sds100/keymapper/about/AboutFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/about/AboutFragment.kt b/app/src/main/java/io/github/sds100/keymapper/about/AboutFragment.kt index deb0c6fa00..7c994503ae 100644 --- a/app/src/main/java/io/github/sds100/keymapper/about/AboutFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/about/AboutFragment.kt @@ -57,7 +57,7 @@ class AboutFragment : Fragment() { onBackPressed() } - version = Constants.VERSION + version = "${Constants.VERSION} ${Constants.VERSION_CODE}" } } From 2e9db962c334661dc0366da1803c457d25b2db31 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 13:17:21 +0100 Subject: [PATCH 11/40] fix: improve how key events are inputted with Shizuku --- CHANGELOG.md | 6 ++ .../io/github/sds100/keymapper/UseCases.kt | 5 +- .../actions/PerformActionsUseCase.kt | 85 +++++++------------ .../mappings/SimpleMappingController.kt | 14 +-- .../keymaps/detection/DetectKeyMapsUseCase.kt | 37 ++++---- .../ParallelTriggerActionPerformer.kt | 44 ++++++---- .../SequenceTriggerActionPerformer.kt | 4 +- .../RerouteKeyEventsUseCase.kt | 6 +- .../shizuku/ShizukuInputEventInjector.kt | 23 +++-- .../BaseAccessibilityServiceController.kt | 6 +- .../system/inputevents/InputEventInjector.kt | 6 +- .../inputmethod/ImeInputEventInjector.kt | 2 +- .../system/navigation/OpenMenuHelper.kt | 7 +- 13 files changed, 127 insertions(+), 118 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 080df16191..9ab41a0909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) + +## Bug fixes + +- Inputting key events with Shizuku does not crash the app if a Key Mapper keyboard is being used at the same time. And latency when inputting key events has been improved in some apps. + ## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0) _See the changes from previous 3.0 Beta releases._ diff --git a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt index 22e395491a..caee6e48b0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt +++ b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt @@ -142,7 +142,7 @@ object UseCases { ServiceLocator.intentAdapter(ctx), getActionError(ctx), keyMapperImeMessenger(ctx, keyEventRelayService), - ShizukuInputEventInjector(), + ShizukuInputEventInjector(coroutineScope = ServiceLocator.appCoroutineScope(ctx)), ServiceLocator.packageManagerAdapter(ctx), ServiceLocator.appShortcutAdapter(ctx), ServiceLocator.popupMessageAdapter(ctx), @@ -179,11 +179,12 @@ object UseCases { ServiceLocator.audioAdapter(ctx), keyMapperImeMessenger(ctx, keyEventRelayService), service, - ShizukuInputEventInjector(), + ShizukuInputEventInjector(ServiceLocator.appCoroutineScope(ctx)), ServiceLocator.popupMessageAdapter(ctx), ServiceLocator.permissionAdapter(ctx), ServiceLocator.resourceProvider(ctx), ServiceLocator.vibratorAdapter(ctx), + ServiceLocator.appCoroutineScope(ctx), ) fun rerouteKeyEvents(ctx: Context, keyEventRelayService: KeyEventRelayServiceWrapper) = RerouteKeyEventsUseCaseImpl( diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt index 53c7efe637..f465c2f691 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt @@ -67,7 +67,6 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch import splitties.bitflags.withFlag import timber.log.Timber @@ -114,6 +113,7 @@ class PerformActionsUseCaseImpl( accessibilityService, shizukuInputEventInjector, permissionAdapter, + coroutineScope, ) } @@ -124,7 +124,7 @@ class PerformActionsUseCaseImpl( permissionAdapter.isGrantedFlow(Permission.SHIZUKU) .stateIn(coroutineScope, SharingStarted.Eagerly, false) - override fun perform( + override suspend fun perform( action: ActionData, inputEventType: InputEventType, keyMetaState: Int, @@ -132,7 +132,7 @@ class PerformActionsUseCaseImpl( /** * Is null if the action is being performed asynchronously */ - val result: Result<*>? + val result: Result<*> when (action) { is ActionData.App -> { @@ -254,20 +254,15 @@ class PerformActionsUseCaseImpl( } is ActionData.SwitchKeyboard -> { - coroutineScope.launch { - inputMethodAdapter - .chooseImeWithoutUserInput(action.imeId) - .onSuccess { - val message = resourceProvider.getString( - R.string.toast_chose_keyboard, - it.label, - ) - popupMessageAdapter.showPopupMessage(message) - } - .showErrorMessageOnFail() - } - - result = null + result = inputMethodAdapter + .chooseImeWithoutUserInput(action.imeId) + .onSuccess { + val message = resourceProvider.getString( + R.string.toast_chose_keyboard, + it.label, + ) + popupMessageAdapter.showPopupMessage(message) + } } is ActionData.Volume.Down -> { @@ -578,14 +573,10 @@ class PerformActionsUseCaseImpl( } is ActionData.GoLastApp -> { - coroutineScope.launch { - accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS) - delay(100) + accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS) + delay(100) + result = accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS) - .showErrorMessageOnFail() - } - - result = null } is ActionData.OpenMenu -> { @@ -711,11 +702,11 @@ class PerformActionsUseCaseImpl( is ActionData.Screenshot -> { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - coroutineScope.launch { - val picturesFolder = fileAdapter.getPicturesFolder() - val screenshotsFolder = "$picturesFolder/Screenshots" - val fileDate = FileUtils.createFileDate() + val picturesFolder = fileAdapter.getPicturesFolder() + val screenshotsFolder = "$picturesFolder/Screenshots" + val fileDate = FileUtils.createFileDate() + result = suAdapter.execute("mkdir -p $screenshotsFolder; screencap -p $screenshotsFolder/Screenshot_$fileDate.png") .onSuccess { // Wait 3 seconds so the message isn't shown in the screenshot. @@ -726,9 +717,7 @@ class PerformActionsUseCaseImpl( R.string.toast_screenshot_taken, ), ) - }.showErrorMessageOnFail() - } - result = null + } } else { result = accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT) @@ -781,19 +770,11 @@ class PerformActionsUseCaseImpl( } ActionData.DismissAllNotifications -> { - coroutineScope.launch { - notificationReceiverAdapter.send(ServiceEvent.DismissAllNotifications) - } - - result = null + result = notificationReceiverAdapter.send(ServiceEvent.DismissAllNotifications) } ActionData.DismissLastNotification -> { - coroutineScope.launch { - notificationReceiverAdapter.send(ServiceEvent.DismissLastNotification) - } - - result = null + result = notificationReceiverAdapter.send(ServiceEvent.DismissLastNotification) } ActionData.AnswerCall -> { @@ -815,27 +796,23 @@ class PerformActionsUseCaseImpl( } is ActionData.HttpRequest -> { - coroutineScope.launch { - networkAdapter.sendHttpRequest( - method = action.method, - url = action.url, - body = action.body, - authorizationHeader = action.authorizationHeader, - ).showErrorMessageOnFail() - } - - result = null + result = networkAdapter.sendHttpRequest( + method = action.method, + url = action.url, + body = action.body, + authorizationHeader = action.authorizationHeader, + ) } } when (result) { - null, is Success -> Timber.d("Performed action $action, input event type: $inputEventType, key meta state: $keyMetaState") + is Success -> Timber.d("Performed action $action, input event type: $inputEventType, key meta state: $keyMetaState") is Error -> Timber.d( "Failed to perform action $action, reason: ${result.getFullMessage(resourceProvider)}, action: $action, input event type: $inputEventType, key meta state: $keyMetaState", ) } - result?.showErrorMessageOnFail() + result.showErrorMessageOnFail() } override fun getErrorSnapshot(): ActionErrorSnapshot { @@ -925,7 +902,7 @@ interface PerformActionsUseCase { val defaultRepeatDelay: Flow val defaultRepeatRate: Flow - fun perform( + suspend fun perform( action: ActionData, inputEventType: InputEventType = InputEventType.DOWN_UP, keyMetaState: Int = 0, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/SimpleMappingController.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/SimpleMappingController.kt index 10bb5b2747..dc3d19e77c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/SimpleMappingController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/SimpleMappingController.kt @@ -127,7 +127,7 @@ abstract class SimpleMappingController( } } - private fun performAction( + private suspend fun performAction( action: Action, inputEventType: InputEventType = InputEventType.DOWN_UP, ) { @@ -172,20 +172,22 @@ abstract class SimpleMappingController( } fun reset() { - repeatJobs.values.forEach { jobs -> + for (jobs in repeatJobs.values) { jobs.forEach { it.cancel() } } repeatJobs.clear() - performActionJobs.values.forEach { - it.cancel() + for (job in performActionJobs.values) { + job.cancel() } performActionJobs.clear() - actionsBeingHeldDown.forEach { - performAction(it, InputEventType.UP) + coroutineScope.launch { + for (it in actionsBeingHeldDown) { + performAction(it, InputEventType.UP) + } } actionsBeingHeldDown.clear() diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt index 1ae73e4351..fafed46f55 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt @@ -32,11 +32,14 @@ import io.github.sds100.keymapper.util.InputEventType import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.dataOrNull import io.github.sds100.keymapper.util.ui.ResourceProvider +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import timber.log.Timber /** @@ -58,6 +61,7 @@ class DetectKeyMapsUseCaseImpl( private val permissionAdapter: PermissionAdapter, private val resourceProvider: ResourceProvider, private val vibrator: VibratorAdapter, + private val coroutineScope: CoroutineScope, ) : DetectKeyMapsUseCase { companion object { @@ -164,6 +168,7 @@ class DetectKeyMapsUseCaseImpl( accessibilityService, shizukuInputEventInjector, permissionAdapter, + coroutineScope, ) override val forceVibrate: Flow = @@ -189,18 +194,20 @@ class DetectKeyMapsUseCaseImpl( inputEventType: InputEventType, scanCode: Int, ) { + val model = InputKeyModel( + keyCode, + inputEventType, + metaState, + deviceId, + scanCode, + ) + if (permissionAdapter.isGranted(Permission.SHIZUKU)) { Timber.d("Imitate button press ${KeyEvent.keyCodeToString(keyCode)} with Shizuku, key code: $keyCode, device id: $deviceId, meta state: $metaState, scan code: $scanCode") - shizukuInputEventInjector.inputKeyEvent( - InputKeyModel( - keyCode, - inputEventType, - metaState, - deviceId, - scanCode, - ), - ) + coroutineScope.launch { + shizukuInputEventInjector.inputKeyEvent(model) + } } else { Timber.d("Imitate button press ${KeyEvent.keyCodeToString(keyCode)}, key code: $keyCode, device id: $deviceId, meta state: $metaState, scan code: $scanCode") @@ -217,15 +224,9 @@ class DetectKeyMapsUseCaseImpl( KeyEvent.KEYCODE_MENU -> openMenuHelper.openMenu() - else -> imeInputEventInjector.inputKeyEvent( - InputKeyModel( - keyCode, - inputEventType, - metaState, - deviceId, - scanCode, - ), - ) + else -> runBlocking { + imeInputEventInjector.inputKeyEvent(model) + } } } } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/ParallelTriggerActionPerformer.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/ParallelTriggerActionPerformer.kt index 8205770d4e..1a8a3ef36d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/ParallelTriggerActionPerformer.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/ParallelTriggerActionPerformer.kt @@ -57,7 +57,7 @@ class ParallelTriggerActionPerformer( once before repeating (if configured). */ performActionsJob = coroutineScope.launch { - actionList.forEachIndexed { actionIndex, action -> + for ((actionIndex, action) in actionList.withIndex()) { var performUpAction = false if (action.holdDown && action.repeat && action.repeatMode == RepeatMode.TRIGGER_PRESSED_AGAIN) { @@ -94,15 +94,17 @@ class ParallelTriggerActionPerformer( } } - repeatJobs.forEach { it?.cancel() } + for (job in repeatJobs) { + job?.cancel() + } - actionList.forEachIndexed { actionIndex, action -> + for ((actionIndex, action) in actionList.withIndex()) { if (!action.repeat) { - return@forEachIndexed + continue } if (calledOnTriggerRelease && action.repeatMode == RepeatMode.TRIGGER_RELEASED) { - return@forEachIndexed + continue } // don't start repeating if it is already repeating @@ -110,11 +112,11 @@ class ParallelTriggerActionPerformer( repeatJobs[actionIndex]?.cancel() repeatJobs[actionIndex] = null - return@forEachIndexed + continue } if (action.data is ActionData.InputKeyEvent && InputEventUtils.isModifierKey(action.data.keyCode)) { - return@forEachIndexed + continue } repeatJobs[actionIndex] = coroutineScope.launch { @@ -124,9 +126,11 @@ class ParallelTriggerActionPerformer( delay(action.repeatDelay?.toLong() ?: defaultRepeatDelay.value) while (isActive && continueRepeating) { - if (action.holdDown && action.repeat) { + if (action.holdDown) { performAction(action, InputEventType.DOWN, metaState) - delay(action.holdDownDuration?.toLong() ?: defaultHoldDownDuration.value) + delay( + action.holdDownDuration?.toLong() ?: defaultHoldDownDuration.value, + ) performAction(action, InputEventType.UP, metaState) } else { performAction(action, InputEventType.DOWN_UP, metaState) @@ -152,12 +156,14 @@ class ParallelTriggerActionPerformer( } } - actionList.forEachIndexed { actionIndex, action -> - if (action.holdDown && !action.stopHoldDownWhenTriggerPressedAgain) { - if (actionIsHeldDown[actionIndex]) { - actionIsHeldDown[actionIndex] = false + coroutineScope.launch { + for ((actionIndex, action) in actionList.withIndex()) { + if (action.holdDown && !action.stopHoldDownWhenTriggerPressedAgain) { + if (actionIsHeldDown[actionIndex]) { + actionIsHeldDown[actionIndex] = false - performAction(action, InputEventType.UP, metaState) + performAction(action, InputEventType.UP, metaState) + } } } } @@ -167,9 +173,11 @@ class ParallelTriggerActionPerformer( performActionsJob?.cancel() performActionsJob = null - actionIsHeldDown.forEachIndexed { index, isHeldDown -> - if (isHeldDown) { - performAction(actionList[index], inputEventType = InputEventType.UP, 0) + coroutineScope.launch { + for ((index, isHeldDown) in actionIsHeldDown.withIndex()) { + if (isHeldDown) { + performAction(actionList[index], inputEventType = InputEventType.UP, 0) + } } } @@ -183,7 +191,7 @@ class ParallelTriggerActionPerformer( } } - private fun performAction( + private suspend fun performAction( action: Action, inputEventType: InputEventType, metaState: Int, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/SequenceTriggerActionPerformer.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/SequenceTriggerActionPerformer.kt index b776b709b7..a5ef9e2b0d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/SequenceTriggerActionPerformer.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/SequenceTriggerActionPerformer.kt @@ -25,7 +25,7 @@ class SequenceTriggerActionPerformer( */ job?.cancel() job = coroutineScope.launch { - actionList.forEach { action -> + for (action in actionList) { performAction(action, metaState) delay(action.delayBeforeNextAction?.toLong() ?: 0L) @@ -38,7 +38,7 @@ class SequenceTriggerActionPerformer( job = null } - private fun performAction(action: Action, metaState: Int) { + private suspend fun performAction(action: Action, metaState: Int) { repeat(action.multiplier ?: 1) { useCase.perform(action.data, InputEventType.DOWN_UP, metaState) } diff --git a/app/src/main/java/io/github/sds100/keymapper/reroutekeyevents/RerouteKeyEventsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/reroutekeyevents/RerouteKeyEventsUseCase.kt index 39f8257540..2d50fd4b7c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/reroutekeyevents/RerouteKeyEventsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/reroutekeyevents/RerouteKeyEventsUseCase.kt @@ -9,6 +9,7 @@ import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter import io.github.sds100.keymapper.system.inputmethod.KeyMapperImeHelper import io.github.sds100.keymapper.util.firstBlocking import kotlinx.coroutines.flow.map +import kotlinx.coroutines.runBlocking /** * Created by sds100 on 27/04/2021. @@ -48,7 +49,10 @@ class RerouteKeyEventsUseCaseImpl( } override fun inputKeyEvent(keyModel: InputKeyModel) { - imeInputEventInjector.inputKeyEvent(keyModel) + // It is safe to run the ime injector on the main thread. + runBlocking { + imeInputEventInjector.inputKeyEvent(keyModel) + } } } diff --git a/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt b/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt index cd890bac34..9608b58f1c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt +++ b/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt @@ -8,12 +8,15 @@ import android.view.KeyEvent import io.github.sds100.keymapper.system.inputevents.InputEventInjector import io.github.sds100.keymapper.system.inputmethod.InputKeyModel import io.github.sds100.keymapper.util.InputEventType +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import rikka.shizuku.ShizukuBinderWrapper import rikka.shizuku.SystemServiceHelper import timber.log.Timber @SuppressLint("PrivateApi") -class ShizukuInputEventInjector : InputEventInjector { +class ShizukuInputEventInjector(private val coroutineScope: CoroutineScope) : InputEventInjector { companion object { // private const val INJECT_INPUT_EVENT_MODE_ASYNC = 0 @@ -27,7 +30,7 @@ class ShizukuInputEventInjector : InputEventInjector { IInputManager.Stub.asInterface(binder) } - override fun inputKeyEvent(model: InputKeyModel) { + override suspend fun inputKeyEvent(model: InputKeyModel) { Timber.d("Inject input event with Shizuku ${KeyEvent.keyCodeToString(model.keyCode)}, $model") val action = when (model.inputType) { @@ -48,15 +51,17 @@ class ShizukuInputEventInjector : InputEventInjector { model.scanCode, ) - // MUST wait for the application to finish processing the event before sending the next one. - // Otherwise, rapidly repeating input events will go in a big queue and all inputs - // into the application will be delayed or overloaded. - iInputManager.injectInputEvent(keyEvent, INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH) + withContext(Dispatchers.IO) { + // MUST wait for the application to finish processing the event before sending the next one. + // Otherwise, rapidly repeating input events will go in a big queue and all inputs + // into the application will be delayed or overloaded. + iInputManager.injectInputEvent(keyEvent, INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH) - if (model.inputType == InputEventType.DOWN_UP) { - val upEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_UP) + if (model.inputType == InputEventType.DOWN_UP) { + val upEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_UP) - iInputManager.injectInputEvent(upEvent, INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH) + iInputManager.injectInputEvent(upEvent, INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH) + } } } } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt index 51252b41d7..c95b86cc26 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt @@ -495,7 +495,11 @@ abstract class BaseAccessibilityServiceController( } } - is ServiceEvent.TestAction -> performActionsUseCase.perform(event.action) + is ServiceEvent.TestAction -> coroutineScope.launch { + performActionsUseCase.perform( + event.action, + ) + } is ServiceEvent.Ping -> coroutineScope.launch { outputEvents.emit(ServiceEvent.Pong(event.key)) diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt index 97bbe5764a..a126bac316 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt @@ -2,10 +2,6 @@ package io.github.sds100.keymapper.system.inputevents import io.github.sds100.keymapper.system.inputmethod.InputKeyModel -/** - * Created by sds100 on 21/04/2021. - */ - interface InputEventInjector { - fun inputKeyEvent(model: InputKeyModel) + suspend fun inputKeyEvent(model: InputKeyModel) } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ImeInputEventInjector.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ImeInputEventInjector.kt index 298dfc076d..756acde8df 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ImeInputEventInjector.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ImeInputEventInjector.kt @@ -45,7 +45,7 @@ class ImeInputEventInjectorImpl( private val ctx = context.applicationContext - override fun inputKeyEvent(model: InputKeyModel) { + override suspend fun inputKeyEvent(model: InputKeyModel) { Timber.d("Inject key event with input method ${KeyEvent.keyCodeToString(model.keyCode)}, $model") val imePackageName = inputMethodAdapter.chosenIme.value?.packageName diff --git a/app/src/main/java/io/github/sds100/keymapper/system/navigation/OpenMenuHelper.kt b/app/src/main/java/io/github/sds100/keymapper/system/navigation/OpenMenuHelper.kt index aa24829d97..0864806c2d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/navigation/OpenMenuHelper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/navigation/OpenMenuHelper.kt @@ -13,6 +13,8 @@ import io.github.sds100.keymapper.util.InputEventType import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.firstBlocking import io.github.sds100.keymapper.util.success +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** * Created by sds100 on 21/04/2021. @@ -22,6 +24,7 @@ class OpenMenuHelper( private val accessibilityService: IAccessibilityService, private val shizukuInputEventInjector: InputEventInjector, private val permissionAdapter: PermissionAdapter, + private val coroutineScope: CoroutineScope, ) { companion object { @@ -36,7 +39,9 @@ class OpenMenuHelper( inputType = InputEventType.DOWN_UP, ) - shizukuInputEventInjector.inputKeyEvent(inputKeyModel) + coroutineScope.launch { + shizukuInputEventInjector.inputKeyEvent(inputKeyModel) + } return success() } From 0bf3bf63a8fb1b7ed7200fcd8d1c7c0b602e3cac Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 13:20:16 +0100 Subject: [PATCH 12/40] Revert "#1618 The Key Mapper keyboard is no longer required for Text actions" This reverts commit 14f2216eaadd9e206ffce55a87e8f1860295bbc3. --- .../java/io/github/sds100/keymapper/actions/ActionUtils.kt | 3 +-- .../sds100/keymapper/actions/PerformActionsUseCase.kt | 6 +----- .../accessibility/BaseAccessibilityServiceController.kt | 4 ---- .../keymapper/system/accessibility/IAccessibilityService.kt | 3 --- .../system/accessibility/MyAccessibilityService.kt | 6 ------ 5 files changed, 2 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt index f1f755074e..e3b696fc8c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt @@ -781,8 +781,7 @@ fun ActionData.canBeHeldDown(): Boolean = when (this) { fun ActionData.canUseImeToPerform(): Boolean = when (this) { is ActionData.InputKeyEvent -> !useShell - // Android 13+ can use the accessibility service to input text. - is ActionData.Text -> Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU + is ActionData.Text -> true is ActionData.MoveCursorToEnd -> true else -> false } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt index f465c2f691..9734435728 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt @@ -328,11 +328,7 @@ class PerformActionsUseCaseImpl( } is ActionData.Text -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - accessibilityService.inputText(action.text) - } else { - imeInputEventInjector.inputText(action.text) - } + imeInputEventInjector.inputText(action.text) result = Success(Unit) } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt index c95b86cc26..f29c038ee8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt @@ -150,10 +150,6 @@ abstract class BaseAccessibilityServiceController( flags = flags.withFlag(AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME) } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - flags = flags.withFlag(AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR) - } - return@lazy flags } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt index 449f83c0fa..59d9ac5e42 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt @@ -61,7 +61,4 @@ interface IAccessibilityService { fun disableSelf() fun findFocussedNode(focus: Int): AccessibilityNodeModel? - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - fun inputText(text: String) } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt index 54e3538019..2fdae08f4f 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt @@ -562,12 +562,6 @@ class MyAccessibilityService : override fun findFocussedNode(focus: Int): AccessibilityNodeModel? = findFocus(focus)?.toModel() - override fun inputText(text: String) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - inputMethod?.currentInputConnection?.commitText(text, 1, null) - } - } - override fun setInputMethodEnabled(imeId: String, enabled: Boolean) { @SuppressLint("CheckResult") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { From 550c49b4c4ca4567aa149368dc7e96aa11bc86e8 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 14:51:03 +0100 Subject: [PATCH 13/40] chore: update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ab41a0909..a651a968cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) + +## Changed + +- #1654 The Key Mapper keyboard is now required again for Text actions because the accessibility service API does not work in all situations. + ## Bug fixes - Inputting key events with Shizuku does not crash the app if a Key Mapper keyboard is being used at the same time. And latency when inputting key events has been improved in some apps. From b0adf0de9b47f1fc1eab8e3869d3cb8746f963a3 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 15:05:01 +0100 Subject: [PATCH 14/40] #1653 Hide the export/import menu buttons in groups --- CHANGELOG.md | 2 +- .../sds100/keymapper/home/KeyMapListAppBar.kt | 169 ++++++++++-------- 2 files changed, 95 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a651a968cb..e4ae32186b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,9 @@ ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) - ## Changed - #1654 The Key Mapper keyboard is now required again for Text actions because the accessibility service API does not work in all situations. +- #1653 Hide the export/import menu buttons in groups ## Bug fixes diff --git a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt index 8ea94d7eee..290aca0f05 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt @@ -158,13 +158,37 @@ fun KeyMapListAppBar( } }, actions = { + var expandedDropdown by rememberSaveable { mutableStateOf(false) } + AppBarActions( onHelpClick, - onSortClick, - onSettingsClick, - onAboutClick, - onExportClick, - onImportClick, + onMenuClick = { expandedDropdown = true }, + dropdownMenuContent = { + RootGroupDropdownMenu( + expanded = expandedDropdown, + onSortClick = { + expandedDropdown = false + onSortClick() + }, + onSettingsClick = { + expandedDropdown = false + onSettingsClick() + }, + onAboutClick = { + expandedDropdown = false + onAboutClick() + }, + onExportClick = { + expandedDropdown = false + onExportClick() + }, + onImportClick = { + expandedDropdown = false + onImportClick() + }, + onDismissRequest = { expandedDropdown = false }, + ) + }, ) }, ) @@ -252,17 +276,32 @@ fun KeyMapListAppBar( onFixConstraintClick = onFixConstraintClick, actions = { AnimatedVisibility(!state.isEditingGroupName) { + var expandedDropdown by rememberSaveable { mutableStateOf(false) } + AppBarActions( onHelpClick, - onSortClick, - onSettingsClick, - onAboutClick, - onExportClick, - onImportClick, - showDeleteGroup = true, - showSort = true, - onDeleteGroupClick = { - showDeleteGroupDialog = true + onMenuClick = { expandedDropdown = true }, + dropdownMenuContent = { + ChildGroupDropdownMenu( + expanded = expandedDropdown, + onSortClick = { + expandedDropdown = false + onSortClick() + }, + onSettingsClick = { + expandedDropdown = false + onSettingsClick() + }, + onAboutClick = { + expandedDropdown = false + onAboutClick() + }, + onDismissRequest = { expandedDropdown = false }, + onDeleteGroupClick = { + expandedDropdown = false + showDeleteGroupDialog = true + }, + ) }, ) } @@ -532,17 +571,9 @@ private fun SelectingAppBar( @Composable private fun AppBarActions( onHelpClick: () -> Unit, - onSortClick: () -> Unit, - onSettingsClick: () -> Unit, - onAboutClick: () -> Unit, - onExportClick: () -> Unit, - onImportClick: () -> Unit, - showDeleteGroup: Boolean = false, - showSort: Boolean = false, - onDeleteGroupClick: () -> Unit = {}, + onMenuClick: () -> Unit = {}, + dropdownMenuContent: @Composable () -> Unit, ) { - var expandedDropdown by rememberSaveable { mutableStateOf(false) } - Row { IconButton(onClick = onHelpClick) { Icon( @@ -551,43 +582,14 @@ private fun AppBarActions( ) } - IconButton(onClick = { expandedDropdown = true }) { + IconButton(onClick = onMenuClick) { Icon( Icons.Rounded.MoreVert, contentDescription = stringResource(R.string.home_app_bar_more), ) } - AppBarDropdownMenu( - expanded = expandedDropdown, - onSortClick = { - expandedDropdown = false - onSortClick() - }, - onSettingsClick = { - expandedDropdown = false - onSettingsClick() - }, - onAboutClick = { - expandedDropdown = false - onAboutClick() - }, - onExportClick = { - expandedDropdown = false - onExportClick() - }, - onImportClick = { - expandedDropdown = false - onImportClick() - }, - onDismissRequest = { expandedDropdown = false }, - showDeleteGroup = showDeleteGroup, - showSort = showSort, - onDeleteGroupClick = { - expandedDropdown = false - onDeleteGroupClick() - }, - ) + dropdownMenuContent() } } @@ -818,7 +820,7 @@ private fun selectedTextTransition( } @Composable -private fun AppBarDropdownMenu( +private fun RootGroupDropdownMenu( expanded: Boolean, onSortClick: () -> Unit = {}, onSettingsClick: () -> Unit = {}, @@ -826,30 +828,11 @@ private fun AppBarDropdownMenu( onExportClick: () -> Unit = {}, onImportClick: () -> Unit = {}, onDismissRequest: () -> Unit = {}, - showDeleteGroup: Boolean = false, - showSort: Boolean = false, - onDeleteGroupClick: () -> Unit = {}, ) { DropdownMenu( expanded = expanded, onDismissRequest = onDismissRequest, ) { - if (showDeleteGroup) { - DropdownMenuItem( - leadingIcon = { Icon(Icons.Rounded.Delete, contentDescription = null) }, - text = { Text(stringResource(R.string.home_menu_delete_group)) }, - onClick = onDeleteGroupClick, - ) - } - - if (showSort) { - DropdownMenuItem( - leadingIcon = { Icon(Icons.AutoMirrored.Rounded.Sort, contentDescription = null) }, - text = { Text(stringResource(R.string.home_app_bar_sort)) }, - onClick = onSortClick, - ) - } - DropdownMenuItem( leadingIcon = { Icon(Icons.Rounded.Settings, contentDescription = null) }, text = { Text(stringResource(R.string.home_menu_settings)) }, @@ -873,6 +856,42 @@ private fun AppBarDropdownMenu( } } +@Composable +private fun ChildGroupDropdownMenu( + expanded: Boolean, + onSortClick: () -> Unit = {}, + onSettingsClick: () -> Unit = {}, + onAboutClick: () -> Unit = {}, + onDismissRequest: () -> Unit = {}, + onDeleteGroupClick: () -> Unit = {}, +) { + DropdownMenu( + expanded = expanded, + onDismissRequest = onDismissRequest, + ) { + DropdownMenuItem( + leadingIcon = { Icon(Icons.Rounded.Delete, contentDescription = null) }, + text = { Text(stringResource(R.string.home_menu_delete_group)) }, + onClick = onDeleteGroupClick, + ) + DropdownMenuItem( + leadingIcon = { Icon(Icons.AutoMirrored.Rounded.Sort, contentDescription = null) }, + text = { Text(stringResource(R.string.home_app_bar_sort)) }, + onClick = onSortClick, + ) + DropdownMenuItem( + leadingIcon = { Icon(Icons.Rounded.Settings, contentDescription = null) }, + text = { Text(stringResource(R.string.home_menu_settings)) }, + onClick = onSettingsClick, + ) + DropdownMenuItem( + leadingIcon = { Icon(Icons.Rounded.Info, contentDescription = null) }, + text = { Text(stringResource(R.string.home_menu_about)) }, + onClick = onAboutClick, + ) + } +} + @Composable private fun constraintsSampleList(): List { val ctx = LocalContext.current From dd6267420e1fbb2ee32e8e23d62e9f982a82b7e5 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 15:14:26 +0100 Subject: [PATCH 15/40] #1652 feat: Bring back the menu button to show input method picker --- CHANGELOG.md | 6 ++++++ .../sds100/keymapper/home/HomeKeyMapListScreen.kt | 1 + .../io/github/sds100/keymapper/home/HomeViewModel.kt | 5 +++++ .../github/sds100/keymapper/home/KeyMapListAppBar.kt | 12 ++++++++++++ .../mappings/keymaps/KeyMapListViewModel.kt | 6 ++++++ .../java/io/github/sds100/keymapper/util/Inject.kt | 2 ++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4ae32186b..44a97fc1ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) +#### TO BE RELEASED + +## Added + +- #1652 Bring back the menu button to show input method picker + ## Changed - #1654 The Key Mapper keyboard is now required again for Text actions because the accessibility service API does not work in all situations. diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt index 6b7663bfc7..c5bbc7a833 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt @@ -188,6 +188,7 @@ fun HomeKeyMapListScreen( onHelpClick = { uriHandler.openUriSafe(ctx, helpUrl) }, onExportClick = viewModel::onExportClick, onImportClick = { importFileLauncher.launch(FileUtils.MIME_TYPE_ALL) }, + onInputMethodPickerClick = viewModel::showInputMethodPicker, onTogglePausedClick = viewModel::onTogglePausedClick, onFixWarningClick = viewModel::onFixWarningClick, onBackClick = { diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt index 9b5212b89a..636a9dea5a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt @@ -19,6 +19,7 @@ import io.github.sds100.keymapper.mappings.keymaps.ListKeyMapsUseCase import io.github.sds100.keymapper.mappings.keymaps.trigger.SetupGuiKeyboardUseCase import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.sorting.SortKeyMapsUseCase +import io.github.sds100.keymapper.system.inputmethod.ShowInputMethodPickerUseCase import io.github.sds100.keymapper.util.ui.DialogResponse import io.github.sds100.keymapper.util.ui.NavigationViewModel import io.github.sds100.keymapper.util.ui.NavigationViewModelImpl @@ -48,6 +49,7 @@ class HomeViewModel( private val setupGuiKeyboard: SetupGuiKeyboardUseCase, private val sortKeyMaps: SortKeyMapsUseCase, private val listFloatingLayouts: ListFloatingLayoutsUseCase, + private val showInputMethodPickerUseCase: ShowInputMethodPickerUseCase, ) : ViewModel(), ResourceProvider by resourceProvider, PopupViewModel by PopupViewModelImpl(), @@ -78,6 +80,7 @@ class HomeViewModel( showAlertsUseCase, pauseKeyMaps, backupRestore, + showInputMethodPickerUseCase, ) } @@ -186,6 +189,7 @@ class HomeViewModel( private val setupGuiKeyboard: SetupGuiKeyboardUseCase, private val sortKeyMaps: SortKeyMapsUseCase, private val listFloatingLayouts: ListFloatingLayoutsUseCase, + private val showInputMethodPickerUseCase: ShowInputMethodPickerUseCase, ) : ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T = HomeViewModel( @@ -198,6 +202,7 @@ class HomeViewModel( setupGuiKeyboard, sortKeyMaps, listFloatingLayouts, + showInputMethodPickerUseCase, ) as T } } diff --git a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt index 290aca0f05..2990b29b39 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt @@ -42,6 +42,7 @@ import androidx.compose.material.icons.rounded.Edit import androidx.compose.material.icons.rounded.ErrorOutline import androidx.compose.material.icons.rounded.Info import androidx.compose.material.icons.rounded.IosShare +import androidx.compose.material.icons.rounded.Keyboard import androidx.compose.material.icons.rounded.MoreVert import androidx.compose.material.icons.rounded.PauseCircleOutline import androidx.compose.material.icons.rounded.PlayCircleOutline @@ -122,6 +123,7 @@ fun KeyMapListAppBar( onFixWarningClick: (String) -> Unit = {}, onExportClick: () -> Unit = {}, onImportClick: () -> Unit = {}, + onInputMethodPickerClick: () -> Unit = {}, onBackClick: () -> Unit = {}, onSelectAllClick: () -> Unit = {}, scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), @@ -186,6 +188,10 @@ fun KeyMapListAppBar( expandedDropdown = false onImportClick() }, + onInputMethodPickerClick = { + expandedDropdown = false + onInputMethodPickerClick() + }, onDismissRequest = { expandedDropdown = false }, ) }, @@ -827,6 +833,7 @@ private fun RootGroupDropdownMenu( onAboutClick: () -> Unit = {}, onExportClick: () -> Unit = {}, onImportClick: () -> Unit = {}, + onInputMethodPickerClick: () -> Unit = {}, onDismissRequest: () -> Unit = {}, ) { DropdownMenu( @@ -848,6 +855,11 @@ private fun RootGroupDropdownMenu( text = { Text(stringResource(R.string.home_menu_import)) }, onClick = onImportClick, ) + DropdownMenuItem( + leadingIcon = { Icon(Icons.Rounded.Keyboard, contentDescription = null) }, + text = { Text(stringResource(R.string.home_menu_input_method_picker)) }, + onClick = onInputMethodPickerClick, + ) DropdownMenuItem( leadingIcon = { Icon(Icons.Rounded.Info, contentDescription = null) }, text = { Text(stringResource(R.string.home_menu_about)) }, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt index 4352967f2b..5d4d4e4b7c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt @@ -26,6 +26,7 @@ import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerErrorSnapshot import io.github.sds100.keymapper.sorting.SortKeyMapsUseCase import io.github.sds100.keymapper.sorting.SortViewModel import io.github.sds100.keymapper.system.accessibility.ServiceState +import io.github.sds100.keymapper.system.inputmethod.ShowInputMethodPickerUseCase import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.Result @@ -82,6 +83,7 @@ class KeyMapListViewModel( private val showAlertsUseCase: ShowHomeScreenAlertsUseCase, private val pauseKeyMaps: PauseKeyMapsUseCase, private val backupRestore: BackupRestoreMappingsUseCase, + private val showInputMethodPickerUseCase: ShowInputMethodPickerUseCase, ) : PopupViewModel by PopupViewModelImpl(), ResourceProvider by resourceProvider, @@ -845,6 +847,10 @@ class KeyMapListViewModel( } } + fun showInputMethodPicker() { + showInputMethodPickerUseCase.show(fromForeground = true) + } + private suspend fun onAutomaticBackupResult(result: Result<*>) { when (result) { is Success -> {} diff --git a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt index f24c2b27b3..f3d48fcdf1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt @@ -39,6 +39,7 @@ import io.github.sds100.keymapper.system.apps.ChooseAppViewModel import io.github.sds100.keymapper.system.apps.DisplayAppShortcutsUseCaseImpl import io.github.sds100.keymapper.system.bluetooth.ChooseBluetoothDeviceUseCaseImpl import io.github.sds100.keymapper.system.bluetooth.ChooseBluetoothDeviceViewModel +import io.github.sds100.keymapper.system.inputmethod.ShowInputMethodPickerUseCaseImpl import io.github.sds100.keymapper.system.intents.ConfigIntentViewModel /** @@ -182,6 +183,7 @@ object Inject { UseCases.displayKeyMap(ctx), ), UseCases.listFloatingLayouts(ctx), + ShowInputMethodPickerUseCaseImpl(ServiceLocator.inputMethodAdapter(ctx)) ) fun settingsViewModel(context: Context): SettingsViewModel.Factory = SettingsViewModel.Factory( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dfd8e9db02..dde186e79d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1355,6 +1355,7 @@ About Export all Import + Choose keyboard Importing… Importing successful! Exporting… From da42d87a297273f574ce0ae725490444116d9693 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 15:29:05 +0100 Subject: [PATCH 16/40] #1646 fix: disabling Bluetooth clears the list of connected devices --- CHANGELOG.md | 1 + .../keymapper/system/devices/AndroidDevicesAdapter.kt | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a97fc1ec..f89489386f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ## Bug fixes - Inputting key events with Shizuku does not crash the app if a Key Mapper keyboard is being used at the same time. And latency when inputting key events has been improved in some apps. +- #1646 disabling Bluetooth clears the list of connected devices ## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0) diff --git a/app/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt index ebed7f99f2..daa1799a9a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import splitties.mainthread.mainLooper @@ -109,6 +110,12 @@ class AndroidDevicesAdapter( connectedBluetoothDevices.value = currentValue.minus(device) }.launchIn(coroutineScope) + + bluetoothAdapter.isBluetoothEnabled.onEach { isEnabled -> + if (!isEnabled) { + connectedBluetoothDevices.update { emptySet() } + } + }.launchIn(coroutineScope) } override fun deviceHasKey(id: Int, keyCode: Int): Boolean { From b69bdd633351efb99400cf990077f1cf092992e9 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 21:27:56 +0100 Subject: [PATCH 17/40] update app title --- fastlane/metadata/android/en-US/title.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt index 6eb7f19549..4efb85f341 100644 --- a/fastlane/metadata/android/en-US/title.txt +++ b/fastlane/metadata/android/en-US/title.txt @@ -1 +1 @@ -Key Mapper: Unleash your keys! \ No newline at end of file +Key Mapper & Floating buttons \ No newline at end of file From 24872b58c43b0b9d2e1dace9b86dccb38581d947 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 21:57:24 +0100 Subject: [PATCH 18/40] fix: try to fix CorruptionException in datastore by updating to 1.2.0-alpha01. See https://issuetracker.google.com/issues/346197747?hl=ar --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8d92d65056..00b2fdc6af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -221,7 +221,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.room:room-runtime:$room_version" implementation "androidx.viewpager2:viewpager2:1.1.0" - implementation "androidx.datastore:datastore-preferences:1.1.4" + implementation "androidx.datastore:datastore-preferences:1.2.0-alpha01" implementation "androidx.core:core-splashscreen:1.0.1" implementation "androidx.activity:activity-compose:1.10.1" implementation "androidx.navigation:navigation-compose:2.8.9" From 3a09324d28847977f370bf726577753e5f015c1a Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 21:58:23 +0100 Subject: [PATCH 19/40] #1655 fix: do not crash when restoring key map groups --- CHANGELOG.md | 1 + .../sds100/keymapper/backup/BackupManager.kt | 38 +++++++++-- .../sds100/keymapper/BackupManagerTest.kt | 68 ++++++++++++++++++- 3 files changed, 98 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f89489386f..d602d6e9d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Inputting key events with Shizuku does not crash the app if a Key Mapper keyboard is being used at the same time. And latency when inputting key events has been improved in some apps. - #1646 disabling Bluetooth clears the list of connected devices +- #1655 do not crash when restoring key map groups ## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0) diff --git a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt index 7b9abecc16..659c66b79e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt +++ b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt @@ -70,6 +70,7 @@ import kotlinx.coroutines.withContext import timber.log.Timber import java.io.IOException import java.io.InputStream +import java.util.LinkedList import java.util.UUID /** @@ -410,7 +411,12 @@ class BackupManagerImpl( val soundFiles = soundDir.listFiles() ?: emptyList() // null if dir doesn't exist return@then parseBackupContent(inputStream).then { backupContent -> - restore(restoreType, backupContent, soundFiles) + restore( + restoreType, + backupContent, + soundFiles, + currentTime = System.currentTimeMillis(), + ) } } } @@ -441,25 +447,45 @@ class BackupManagerImpl( } } - private suspend fun restore( + suspend fun restore( restoreType: RestoreType, backupContent: BackupContent, soundFiles: List, + currentTime: Long, ): Result<*> { try { // MUST come before restoring key maps so it is possible to // validate that each key map's group exists in the repository. if (backupContent.groups != null) { - val groupUids = backupContent.groups.map { it.uid }.toMutableSet() - val existingGroupUids = groupRepository.getAllGroups().first() .map { it.uid } .toSet() - .also { groupUids.addAll(it) } - val currentTime = System.currentTimeMillis() + val groupUids = backupContent.groups.map { it.uid }.toMutableSet() + + groupUids.addAll(existingGroupUids) + + // Group parents must be restored first so an SqliteConstraintException + // is not thrown when restoring a child group. + val groupsToRestoreMap = backupContent.groups.associateBy { it.uid }.toMutableMap() + val groupRestoreQueue = LinkedList() + // Order the groups into a queue such that a parent is always before a child. for (group in backupContent.groups) { + if (groupsToRestoreMap.containsKey(group.uid)) { + groupRestoreQueue.addFirst(group) + } + + var parent = groupsToRestoreMap[group.parentUid] + + while (parent != null) { + groupRestoreQueue.addFirst(parent) + groupsToRestoreMap.remove(parent.uid) + parent = groupsToRestoreMap[parent.parentUid] + } + } + + for (group in groupRestoreQueue) { // Set the last opened date to now so that the imported group // shows as the most recent. var modifiedGroup = group.copy(lastOpenedDate = currentTime) diff --git a/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt b/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt index 2bfa190c60..a76df90b1a 100644 --- a/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt @@ -5,6 +5,7 @@ import com.google.gson.Gson import com.google.gson.JsonParser import io.github.sds100.keymapper.actions.sound.SoundFileInfo import io.github.sds100.keymapper.actions.sound.SoundsManager +import io.github.sds100.keymapper.backup.BackupContent import io.github.sds100.keymapper.backup.BackupManagerImpl import io.github.sds100.keymapper.backup.RestoreType import io.github.sds100.keymapper.data.db.AppDatabase @@ -46,10 +47,12 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers import org.mockito.junit.MockitoJUnitRunner import org.mockito.kotlin.any import org.mockito.kotlin.anyVararg import org.mockito.kotlin.doReturn +import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times @@ -79,6 +82,7 @@ class BackupManagerTest { private lateinit var fakeFileAdapter: FakeFileAdapter private lateinit var fakePreferenceRepository: PreferenceRepository private lateinit var mockKeyMapRepository: KeyMapRepository + private lateinit var mockGroupRepository: GroupRepository private lateinit var mockSoundsManager: SoundsManager private lateinit var mockUuidGenerator: UuidGenerator @@ -94,6 +98,10 @@ class BackupManagerTest { fakePreferenceRepository = FakePreferenceRepository() mockKeyMapRepository = mock() + mockGroupRepository = mock { + on { getAllGroups() } doReturn MutableStateFlow(emptyList()) + on { getGroupsByParent(ArgumentMatchers.any()) }.thenReturn(MutableStateFlow(emptyList())) + } fakeFileAdapter = FakeFileAdapter(temporaryFolder) @@ -116,9 +124,7 @@ class BackupManagerTest { floatingLayoutRepository = mock { on { layouts } doReturn MutableStateFlow(State.Data(emptyList())) }, - groupRepository = mock { - on { getAllGroups() } doReturn MutableStateFlow(emptyList()) - }, + groupRepository = mockGroupRepository, ) parser = JsonParser() @@ -130,6 +136,62 @@ class BackupManagerTest { Dispatchers.resetMain() } + /** + * Issue #1655. If the list of groups in the backup has a child before the parent then the + * parent must be restored first. Otherwise the SqliteConstraintException will be thrown. + */ + @Test + fun `restore groups breadth first so parents exist before children are restored`() = runTest(testDispatcher) { + val parentGroup1 = GroupEntity( + uid = "parent_group_1_uid", + name = "parent_group_1_name", + parentUid = null, + lastOpenedDate = 0L, + ) + + val parentGroup2 = GroupEntity( + uid = "parent_group_2_uid", + name = "parent_group_2_name", + parentUid = null, + lastOpenedDate = 0L, + ) + + val childGroup = GroupEntity( + uid = "child_group_uid", + name = "child_group_name", + parentUid = parentGroup1.uid, + lastOpenedDate = 0L, + ) + + val grandChildGroup = GroupEntity( + uid = "grand_child_group_uid", + name = "grand_child_group_name", + parentUid = childGroup.uid, + lastOpenedDate = 0L, + ) + + val backupContent = BackupContent( + appVersion = Constants.VERSION_CODE, + dbVersion = AppDatabase.DATABASE_VERSION, + groups = listOf(parentGroup2, grandChildGroup, childGroup, parentGroup1), + ) + + inOrder(mockGroupRepository) { + backupManager.restore( + RestoreType.REPLACE, + backupContent, + emptyList(), + currentTime = 0L, + ) + + verify(mockGroupRepository).insert(parentGroup1) + verify(mockGroupRepository).insert(childGroup) + verify(mockGroupRepository).insert(grandChildGroup) + verify(mockGroupRepository).insert(parentGroup2) + verify(mockGroupRepository, never()).update(any()) + } + } + @Test fun `when backing up everything include layouts that are not in the list of key maps`() = runTest(testDispatcher) { val layoutWithButtons = FloatingLayoutEntityWithButtons( From 4a697b62685d37ed9cac1bc99bf26b7d5f40d68f Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 22:02:58 +0100 Subject: [PATCH 20/40] #1657 feat: turn on repeat by default for volume actions --- CHANGELOG.md | 9 +++++---- .../keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d602d6e9d6..833ba15594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,18 +4,19 @@ ## Added -- #1652 Bring back the menu button to show input method picker +- #1652 Bring back the menu button to show input method picker. +- #1657 Turn on repeat by default for volume actions. ## Changed - #1654 The Key Mapper keyboard is now required again for Text actions because the accessibility service API does not work in all situations. -- #1653 Hide the export/import menu buttons in groups +- #1653 Hide the export/import menu buttons in groups. ## Bug fixes - Inputting key events with Shizuku does not crash the app if a Key Mapper keyboard is being used at the same time. And latency when inputting key events has been improved in some apps. -- #1646 disabling Bluetooth clears the list of connected devices -- #1655 do not crash when restoring key map groups +- #1646 disabling Bluetooth clears the list of connected devices. +- #1655 do not crash when restoring key map groups. ## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt index f5b0dcdeae..a27ea970cb 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt @@ -52,7 +52,6 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.util.LinkedList @@ -802,6 +801,10 @@ class ConfigKeyMapUseCaseController( } } + if (data is ActionData.Volume.Down || data is ActionData.Volume.Up || data is ActionData.Volume.Stream) { + repeat = true + } + if (data is ActionData.AnswerCall) { addConstraint(Constraint.PhoneRinging()) } From ef92321e3555d5b39c2e555667a5c4b5f2eade83 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 20 Apr 2025 22:38:28 +0100 Subject: [PATCH 21/40] bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 5dd3378be1..6ff7a1b266 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.0.1 -VERSION_CODE=97 +VERSION_CODE=98 VERSION_NUM=0 \ No newline at end of file From ef8cf7fbd2316840495adfe4ff0f01b73465e513 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 10:04:46 +0200 Subject: [PATCH 22/40] bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 6ff7a1b266..348fa4e31b 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.0.1 -VERSION_CODE=98 +VERSION_CODE=100 VERSION_NUM=0 \ No newline at end of file From a5ac8108f664be5efcd7bb54530659e7d8a8a7be Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 10:05:34 +0200 Subject: [PATCH 23/40] style: reformat --- app/src/main/java/io/github/sds100/keymapper/util/Inject.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt index f3d48fcdf1..41a4f69d47 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt @@ -183,7 +183,7 @@ object Inject { UseCases.displayKeyMap(ctx), ), UseCases.listFloatingLayouts(ctx), - ShowInputMethodPickerUseCaseImpl(ServiceLocator.inputMethodAdapter(ctx)) + ShowInputMethodPickerUseCaseImpl(ServiceLocator.inputMethodAdapter(ctx)), ) fun settingsViewModel(context: Context): SettingsViewModel.Factory = SettingsViewModel.Factory( From 1af0c08aa5c3adb7bd7cfbfb5083356a82c07ef3 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 12:55:31 +0200 Subject: [PATCH 24/40] #1648 fix: revenuecat caching works An ancient shared preference migration was causing the RevenueCat cache to be deleted every time it was migrated on an app launch. --- .../repositories/SettingsPreferenceRepository.kt | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/data/repositories/SettingsPreferenceRepository.kt b/app/src/main/java/io/github/sds100/keymapper/data/repositories/SettingsPreferenceRepository.kt index 9c51de7b05..b4fba3ba06 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/repositories/SettingsPreferenceRepository.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/repositories/SettingsPreferenceRepository.kt @@ -1,11 +1,9 @@ package io.github.sds100.keymapper.data.repositories import android.content.Context -import androidx.datastore.preferences.SharedPreferencesMigration import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.preferencesDataStore -import io.github.sds100.keymapper.Constants import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged @@ -17,21 +15,9 @@ class SettingsPreferenceRepository( private val coroutineScope: CoroutineScope, ) : PreferenceRepository { - companion object { - private const val DEFAULT_SHARED_PREFS_NAME = "${Constants.PACKAGE_NAME}_preferences" - } - private val ctx = context.applicationContext - private val sharedPreferencesMigration = SharedPreferencesMigration( - ctx, - DEFAULT_SHARED_PREFS_NAME, - ) - - private val Context.dataStore by preferencesDataStore( - name = "preferences", - produceMigrations = { listOf(sharedPreferencesMigration) }, - ) + private val Context.dataStore by preferencesDataStore(name = "preferences") private val dataStore = ctx.dataStore From 542045fa6388d54b411c92e8b301d6dfd4d5dab2 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 13:22:59 +0200 Subject: [PATCH 25/40] #1649 fix: show verification error if there is a network error when fetching purchases --- .../mappings/keymaps/DisplayKeyMapUseCase.kt | 9 ++-- .../mappings/keymaps/KeyMapListScreen.kt | 5 ++- .../trigger/BaseConfigTriggerViewModel.kt | 42 ++++++++++--------- .../mappings/keymaps/trigger/TriggerError.kt | 2 + .../keymaps/trigger/TriggerErrorSnapshot.kt | 24 ++++++++--- .../keymaps/trigger/TriggerKeyListItem.kt | 1 + .../keymapper/purchasing/PurchasingManager.kt | 3 +- app/src/main/res/values/strings.xml | 1 + 8 files changed, 57 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt index 1a54f071b0..8e87d9d122 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt @@ -76,10 +76,11 @@ class DisplayKeyMapUseCaseImpl( * This waits for the purchases to be processed with a timeout so the UI doesn't * say there are no purchases while it is loading. */ - private val purchasesFlow: Flow>> = callbackFlow { + private val purchasesFlow: Flow>>> = callbackFlow { try { val value = withTimeout(5000L) { - purchasingManager.purchases.filterIsInstance>>().first() + purchasingManager.purchases.filterIsInstance>>>() + .first() } send(value) @@ -103,7 +104,7 @@ class DisplayKeyMapUseCaseImpl( isKeyMapperImeChosen = keyMapperImeHelper.isCompatibleImeChosen(), isDndAccessGranted = permissionAdapter.isGranted(Permission.ACCESS_NOTIFICATION_POLICY), isRootGranted = permissionAdapter.isGranted(Permission.ROOT), - purchases = purchases.dataOrNull() ?: emptySet(), + purchases = purchases.dataOrNull() ?: Success(emptySet()), showDpadImeSetupError = showDpadImeSetupError, ) } @@ -150,6 +151,8 @@ class DisplayKeyMapUseCaseImpl( ProductId.FLOATING_BUTTONS, ), ) + + TriggerError.PURCHASE_VERIFICATION_FAILED -> purchasingManager.refresh() } } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt index 71d155010d..5cf30faa3c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt @@ -91,7 +91,9 @@ fun KeyMapList( Surface(modifier = modifier) { if (listItems.data.isEmpty()) { EmptyKeyMapList( - modifier = Modifier.fillMaxSize().padding(bottom = bottomListPadding), + modifier = Modifier + .fillMaxSize() + .padding(bottom = bottomListPadding), ) } else { LoadedKeyMapList( @@ -487,6 +489,7 @@ private fun getTriggerErrorMessage(error: TriggerError): String { TriggerError.DPAD_IME_NOT_SELECTED -> stringResource(R.string.trigger_error_dpad_ime_not_selected) TriggerError.FLOATING_BUTTON_DELETED -> stringResource(R.string.trigger_error_floating_button_deleted) TriggerError.FLOATING_BUTTONS_NOT_PURCHASED -> stringResource(R.string.trigger_error_floating_buttons_not_purchased) + TriggerError.PURCHASE_VERIFICATION_FAILED -> stringResource(R.string.trigger_error_product_verification_failed) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt index 847da51c4b..e4310a4da6 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt @@ -27,7 +27,9 @@ import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.dataOrNull +import io.github.sds100.keymapper.util.ifIsData import io.github.sds100.keymapper.util.mapData +import io.github.sds100.keymapper.util.onSuccess import io.github.sds100.keymapper.util.ui.CheckBoxListItem import io.github.sds100.keymapper.util.ui.DialogResponse import io.github.sds100.keymapper.util.ui.LinkType @@ -93,7 +95,7 @@ abstract class BaseConfigTriggerViewModel( private val triggerKeyShortcuts = combine( fingerprintGesturesSupported.isSupported, purchasingManager.purchases, - ) { isFingerprintGesturesSupported, purchases -> + ) { isFingerprintGesturesSupported, purchasesState -> val newShortcuts = mutableSetOf>() if (isFingerprintGesturesSupported == true) { @@ -106,25 +108,27 @@ abstract class BaseConfigTriggerViewModel( ) } - if (purchases is State.Data) { - if (purchases.data.contains(ProductId.ASSISTANT_TRIGGER)) { - newShortcuts.add( - ShortcutModel( - icon = ComposeIconInfo.Vector(Icons.Rounded.Assistant), - text = getString(R.string.trigger_key_shortcut_add_assistant), - data = TriggerKeyShortcut.ASSISTANT, - ), - ) - } + purchasesState.ifIsData { result -> + result.onSuccess { purchases -> + if (purchases.contains(ProductId.ASSISTANT_TRIGGER)) { + newShortcuts.add( + ShortcutModel( + icon = ComposeIconInfo.Vector(Icons.Rounded.Assistant), + text = getString(R.string.trigger_key_shortcut_add_assistant), + data = TriggerKeyShortcut.ASSISTANT, + ), + ) + } - if (purchases.data.contains(ProductId.FLOATING_BUTTONS)) { - newShortcuts.add( - ShortcutModel( - icon = ComposeIconInfo.Vector(Icons.Rounded.BubbleChart), - text = getString(R.string.trigger_key_shortcut_add_floating_button), - data = TriggerKeyShortcut.FLOATING_BUTTON, - ), - ) + if (purchases.contains(ProductId.FLOATING_BUTTONS)) { + newShortcuts.add( + ShortcutModel( + icon = ComposeIconInfo.Vector(Icons.Rounded.BubbleChart), + text = getString(R.string.trigger_key_shortcut_add_floating_button), + data = TriggerKeyShortcut.FLOATING_BUTTON, + ), + ) + } } } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerError.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerError.kt index c622594308..a3fa5cc5e1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerError.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerError.kt @@ -20,4 +20,6 @@ enum class TriggerError(val isFixable: Boolean) { FLOATING_BUTTON_DELETED(isFixable = false), FLOATING_BUTTONS_NOT_PURCHASED(isFixable = true), + + PURCHASE_VERIFICATION_FAILED(isFixable = true), } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerErrorSnapshot.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerErrorSnapshot.kt index 9b4f53365a..7b4fe3910d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerErrorSnapshot.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerErrorSnapshot.kt @@ -6,6 +6,10 @@ import io.github.sds100.keymapper.mappings.keymaps.KeyMap import io.github.sds100.keymapper.mappings.keymaps.requiresImeKeyEventForwardingInPhoneCall import io.github.sds100.keymapper.purchasing.ProductId import io.github.sds100.keymapper.system.inputevents.InputEventUtils +import io.github.sds100.keymapper.util.Error +import io.github.sds100.keymapper.util.Result +import io.github.sds100.keymapper.util.onFailure +import io.github.sds100.keymapper.util.onSuccess /** * Store the data required for determining trigger errors to reduce the number of calls with @@ -15,7 +19,7 @@ data class TriggerErrorSnapshot( val isKeyMapperImeChosen: Boolean, val isDndAccessGranted: Boolean, val isRootGranted: Boolean, - val purchases: Set, + val purchases: Result>, val showDpadImeSetupError: Boolean, ) { companion object { @@ -26,13 +30,21 @@ data class TriggerErrorSnapshot( } fun getTriggerError(keyMap: KeyMap, key: TriggerKey): TriggerError? { - if (key is AssistantTriggerKey && !purchases.contains(ProductId.ASSISTANT_TRIGGER)) { - return TriggerError.ASSISTANT_TRIGGER_NOT_PURCHASED + purchases.onSuccess { purchases -> + if (key is AssistantTriggerKey && !purchases.contains(ProductId.ASSISTANT_TRIGGER)) { + return TriggerError.ASSISTANT_TRIGGER_NOT_PURCHASED + } + + if (key is FloatingButtonKey && !purchases.contains(ProductId.FLOATING_BUTTONS)) { + return TriggerError.FLOATING_BUTTONS_NOT_PURCHASED + } + }.onFailure { error -> + if ((key is AssistantTriggerKey || key is FloatingButtonKey) && error == Error.PurchasingError.NetworkError) { + return TriggerError.PURCHASE_VERIFICATION_FAILED + } } - if (key is FloatingButtonKey && !purchases.contains(ProductId.FLOATING_BUTTONS)) { - return TriggerError.FLOATING_BUTTONS_NOT_PURCHASED - } else if (key is FloatingButtonKey && key.button == null) { + if (key is FloatingButtonKey && key.button == null) { return TriggerError.FLOATING_BUTTON_DELETED } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt index 6c95e170a1..46f27f7661 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt @@ -259,6 +259,7 @@ private fun getErrorMessage(error: TriggerError): String { TriggerError.DPAD_IME_NOT_SELECTED -> stringResource(R.string.trigger_error_dpad_ime_not_selected) TriggerError.FLOATING_BUTTON_DELETED -> stringResource(R.string.trigger_error_floating_button_deleted) TriggerError.FLOATING_BUTTONS_NOT_PURCHASED -> stringResource(R.string.trigger_error_floating_buttons_not_purchased) + TriggerError.PURCHASE_VERIFICATION_FAILED -> stringResource(R.string.trigger_error_product_verification_failed) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/purchasing/PurchasingManager.kt b/app/src/main/java/io/github/sds100/keymapper/purchasing/PurchasingManager.kt index 22ba146008..7ad6b4c003 100644 --- a/app/src/main/java/io/github/sds100/keymapper/purchasing/PurchasingManager.kt +++ b/app/src/main/java/io/github/sds100/keymapper/purchasing/PurchasingManager.kt @@ -7,8 +7,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow interface PurchasingManager { val onCompleteProductPurchase: MutableSharedFlow - val purchases: Flow>> + val purchases: Flow>>> suspend fun launchPurchasingFlow(product: ProductId): Result suspend fun getProductPrice(product: ProductId): Result suspend fun isPurchased(product: ProductId): Result + fun refresh() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dde186e79d..7f1b636069 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1212,6 +1212,7 @@ You must read the instructions on our website that describe how to set up this trigger. Key Mapper will not guide you. Read instructions Select trigger type + Purchase can not be verified. Do you have an internet connection? Unlock (%s) From 6ddaa534f61fce700c7d579dcf19bd7cc00fc98a Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 13:35:43 +0200 Subject: [PATCH 26/40] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 833ba15594..9dc1f4135c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - Inputting key events with Shizuku does not crash the app if a Key Mapper keyboard is being used at the same time. And latency when inputting key events has been improved in some apps. - #1646 disabling Bluetooth clears the list of connected devices. - #1655 do not crash when restoring key map groups. +- #1649 show purchase verification failed error if no network connection. +- #1648 caching purchases works so you can use floating buttons and assistant trigger without an internet connection. ## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0) From 5aa5d22dc590d80c7838c169198a6b609c9fba6e Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 13:57:29 +0200 Subject: [PATCH 27/40] #1658 fix: floating buttons appear in the wrong place in portrait if saved in landscape --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dc1f4135c..3032d6d0c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - #1655 do not crash when restoring key map groups. - #1649 show purchase verification failed error if no network connection. - #1648 caching purchases works so you can use floating buttons and assistant trigger without an internet connection. +- #1658 floating buttons appear in the wrong place in portrait if saved in landscape. ## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0) From 3bd799d9705e15454d03a057c4071adb73f36256 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 14:12:47 +0200 Subject: [PATCH 28/40] #1659 fix: Use trigger does not work if the screen orientation changes when re-entering the app --- CHANGELOG.md | 1 + .../keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3032d6d0c8..328837be57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - #1649 show purchase verification failed error if no network connection. - #1648 caching purchases works so you can use floating buttons and assistant trigger without an internet connection. - #1658 floating buttons appear in the wrong place in portrait if saved in landscape. +- #1659 Use trigger does not work if the screen orientation changes when re-entering the app. ## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt index a27ea970cb..62e1d6984c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt @@ -145,10 +145,6 @@ class ConfigKeyMapUseCaseController( } } - override fun useFloatingButtonTrigger(buttonUid: String) { - floatingButtonToUse.update { buttonUid } - } - override fun addConstraint(constraint: Constraint): Boolean { var containsConstraint = false @@ -1028,7 +1024,6 @@ interface ConfigKeyMapUseCase : GetDefaultKeyMapOptionsUseCase { fun getAvailableTriggerKeyDevices(): List - val floatingButtonToUse: StateFlow - fun useFloatingButtonTrigger(buttonUid: String) + val floatingButtonToUse: MutableStateFlow suspend fun getFloatingLayoutCount(): Int } From 123f98e4ae204363b0ab721b505e3a23dc2dfb42 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 14:16:30 +0200 Subject: [PATCH 29/40] fix missing import --- .../sds100/keymapper/purchasing/PurchasingManagerImpl.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/free/java/io/github/sds100/keymapper/purchasing/PurchasingManagerImpl.kt b/app/src/free/java/io/github/sds100/keymapper/purchasing/PurchasingManagerImpl.kt index a5dfd67817..88a2d9a5ee 100644 --- a/app/src/free/java/io/github/sds100/keymapper/purchasing/PurchasingManagerImpl.kt +++ b/app/src/free/java/io/github/sds100/keymapper/purchasing/PurchasingManagerImpl.kt @@ -14,7 +14,8 @@ class PurchasingManagerImpl( private val coroutineScope: CoroutineScope, ) : PurchasingManager { override val onCompleteProductPurchase: MutableSharedFlow = MutableSharedFlow() - override val purchases: Flow>> = MutableStateFlow(State.Data(emptySet())) + override val purchases: Flow>>> = + MutableStateFlow(State.Data(Error.PurchasingNotImplemented)) override suspend fun launchPurchasingFlow(product: ProductId): Result { return Error.PurchasingNotImplemented @@ -27,4 +28,6 @@ class PurchasingManagerImpl( override suspend fun isPurchased(product: ProductId): Result { return Error.PurchasingNotImplemented } + + override fun refresh() {} } From 9f67f7c55aa74be3c389e7e076b243fabb0789dd Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 14:25:53 +0200 Subject: [PATCH 30/40] #1668 fix: Crashes when floating menu does not fit in the display height. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 328837be57..16f9ad355f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - #1648 caching purchases works so you can use floating buttons and assistant trigger without an internet connection. - #1658 floating buttons appear in the wrong place in portrait if saved in landscape. - #1659 Use trigger does not work if the screen orientation changes when re-entering the app. +- #1668 Crashes when floating menu does not fit in the display height. ## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0) From edb7b38ddb137b5f24734bc80b730f6589992d73 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 14:37:36 +0200 Subject: [PATCH 31/40] #1667 fix: hold down mode UI is missing from 2.8. --- CHANGELOG.md | 1 + .../actions/ActionOptionsBottomSheet.kt | 32 ++++++++++++++++++- .../actions/ConfigActionsViewModel.kt | 21 ++++++++++-- .../keymapper/mappings/keymaps/ShortcutRow.kt | 2 +- 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16f9ad355f..81051a6414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - #1658 floating buttons appear in the wrong place in portrait if saved in landscape. - #1659 Use trigger does not work if the screen orientation changes when re-entering the app. - #1668 Crashes when floating menu does not fit in the display height. +- #1667 Hold down mode UI is missing from 2.8. ## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt index 5292cbd8bc..4a8de35bf8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt @@ -264,7 +264,36 @@ fun ActionOptionsBottomSheet( ) } - Spacer(Modifier.height(8.dp)) + if (state.showHoldDownMode) { + Spacer(Modifier.height(8.dp)) + + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(R.string.hold_down_until_trigger_is_dot_dot_dot), + style = MaterialTheme.typography.titleSmall, + ) + + FlowRow( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + RadioButtonText( + isSelected = state.holdDownMode == HoldDownMode.TRIGGER_RELEASED, + text = stringResource(R.string.stop_holding_down_when_trigger_released), + onSelected = { callback.onSelectHoldDownMode(HoldDownMode.TRIGGER_RELEASED) }, + ) + + RadioButtonText( + isSelected = state.holdDownMode == HoldDownMode.TRIGGER_PRESSED_AGAIN, + text = stringResource(R.string.stop_holding_down_trigger_pressed_again), + onSelected = { callback.onSelectHoldDownMode(HoldDownMode.TRIGGER_PRESSED_AGAIN) }, + ) + + Spacer(Modifier.width(8.dp)) + } + } if (state.showHoldDown) { Spacer(Modifier.height(8.dp)) @@ -355,6 +384,7 @@ interface ActionOptionsBottomSheetCallback { fun onRepeatDelayChanged(delay: Int) = run { } fun onHoldDownCheckedChange(checked: Boolean) = run { } fun onHoldDownDurationChanged(duration: Int) = run { } + fun onSelectHoldDownMode(holdDownMode: HoldDownMode) = run { } fun onDelayBeforeNextActionChanged(delay: Int) = run { } fun onMultiplierChanged(multiplier: Int) = run { } } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt index 2d5ec2105a..f501900c70 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt @@ -219,6 +219,15 @@ class ConfigActionsViewModel( actionOptionsUid.value?.let { uid -> config.setActionHoldDownDuration(uid, duration) } } + override fun onSelectHoldDownMode(holdDownMode: HoldDownMode) { + actionOptionsUid.value?.let { uid -> + config.setActionStopHoldingDownWhenTriggerPressedAgain( + uid, + holdDownMode == HoldDownMode.TRIGGER_PRESSED_AGAIN, + ) + } + } + override fun onDelayBeforeNextActionChanged(delay: Int) { actionOptionsUid.value?.let { uid -> config.setDelayBeforeNextAction(uid, delay) } } @@ -230,7 +239,10 @@ class ConfigActionsViewModel( override fun onSelectRepeatMode(repeatMode: RepeatMode) { actionOptionsUid.value?.let { uid -> when (repeatMode) { - RepeatMode.TRIGGER_RELEASED -> config.setActionStopRepeatingWhenTriggerReleased(uid) + RepeatMode.TRIGGER_RELEASED -> config.setActionStopRepeatingWhenTriggerReleased( + uid, + ) + RepeatMode.LIMIT_REACHED -> config.setActionStopRepeatingWhenLimitReached(uid) RepeatMode.TRIGGER_PRESSED_AGAIN -> config.setActionStopRepeatingWhenTriggerPressedAgain( uid, @@ -416,7 +428,8 @@ class ConfigActionsViewModel( return ConfigActionsState.Empty(shortcuts = shortcuts) } - val actions = createListItems(keyMap, showDeviceDescriptors, errorSnapshot, shortcuts.size) + val actions = + createListItems(keyMap, showDeviceDescriptors, errorSnapshot, shortcuts.size) return ConfigActionsState.Loaded( actions = actions, @@ -542,7 +555,9 @@ class ConfigActionsViewModel( holdDownDuration = action.holdDownDuration ?: defaultHoldDownDuration, defaultHoldDownDuration = defaultHoldDownDuration, - showHoldDownMode = keyMap.isStopHoldingDownActionWhenTriggerPressedAgainAllowed(action), + showHoldDownMode = keyMap.isStopHoldingDownActionWhenTriggerPressedAgainAllowed( + action, + ), holdDownMode = if (action.stopHoldDownWhenTriggerPressedAgain) { HoldDownMode.TRIGGER_PRESSED_AGAIN } else { diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt index ce2b62b4ad..d8a147549a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt @@ -38,7 +38,7 @@ fun ShortcutRow( FlowRow( modifier = modifier, horizontalArrangement = Arrangement.spacedBy( - 16.dp, + 8.dp, alignment = Alignment.CenterHorizontally, ), verticalArrangement = Arrangement.Center, From a39b793a783e3f247d2f3a1d0a4de008eb0e3b7e Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 14:47:26 +0200 Subject: [PATCH 32/40] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 348fa4e31b..cf110f8e05 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.0.1 -VERSION_CODE=100 +VERSION_CODE=101 VERSION_NUM=0 \ No newline at end of file From fa6c77d4e6590c33ec3aeee89fec918e4f4b5fa9 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 15:02:28 +0200 Subject: [PATCH 33/40] color behind the gesture bar on the home screen --- .../github/sds100/keymapper/home/HomeKeyMapListScreen.kt | 9 ++++++++- .../java/io/github/sds100/keymapper/home/HomeScreen.kt | 4 +--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt index c5bbc7a833..dc46702f69 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt @@ -12,8 +12,13 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowForward @@ -156,7 +161,9 @@ fun HomeKeyMapListScreen( exit = fadeOut() + slideOutHorizontally(targetOffsetX = { it }), ) { CollapsableFloatingActionButton( - modifier = Modifier.padding(bottom = fabBottomPadding), + modifier = Modifier + .padding(bottom = fabBottomPadding) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.End)), onClick = viewModel::onNewKeyMapClick, showText = viewModel.showFabText, text = stringResource(R.string.home_fab_new_key_map), diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeScreen.kt index d1b08873db..0381317498 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding @@ -106,8 +105,7 @@ private fun HomeScreen( Column( modifier // Only take the horizontal because the status bar is the same color as the app bar - .windowInsetsPadding(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)) - .navigationBarsPadding(), + .windowInsetsPadding(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)), ) { Box(contentAlignment = Alignment.BottomCenter) { NavHost( From 7f9bd5dcea2d7fb6d22e5c83cdccc9db82745725 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 15:59:35 +0200 Subject: [PATCH 34/40] #1553 fix: hide double press option for side key and fingerprint gesture triggers because it is misleading. Double activations can be done with sequence triggers instead. --- CHANGELOG.md | 1 + .../mappings/keymaps/trigger/AssistantTriggerKey.kt | 1 + .../keymaps/trigger/BaseConfigTriggerViewModel.kt | 8 +++----- .../mappings/keymaps/trigger/FingerprintTriggerKey.kt | 1 + .../mappings/keymaps/trigger/FloatingButtonKey.kt | 1 + .../mappings/keymaps/trigger/KeyCodeTriggerKey.kt | 1 + .../keymapper/mappings/keymaps/trigger/TriggerKey.kt | 1 + .../keymaps/trigger/TriggerKeyOptionsBottomSheet.kt | 1 - 8 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81051a6414..91fcae1471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - #1654 The Key Mapper keyboard is now required again for Text actions because the accessibility service API does not work in all situations. - #1653 Hide the export/import menu buttons in groups. +- #1553 Hide double press option for side key and fingerprint gesture triggers because it is misleading. Double activations can be done with sequence triggers instead. ## Bug fixes diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerKey.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerKey.kt index a4e05c3299..45e37e94e4 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerKey.kt @@ -23,6 +23,7 @@ data class AssistantTriggerKey( override val consumeEvent: Boolean = true override val allowedLongPress: Boolean = false + override val allowedDoublePress: Boolean = false override fun compareTo(other: TriggerKey) = when (other) { is AssistantTriggerKey -> compareValuesBy( diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt index e4310a4da6..34658e5890 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt @@ -291,7 +291,7 @@ abstract class BaseConfigTriggerViewModel( * or there are only key code keys in the trigger. It is not possible to do a long press of * non-key code keys in a parallel trigger. */ - if (trigger.keys.size == 1) { + if (trigger.keys.size == 1 && trigger.keys.all { it.allowedDoublePress }) { clickTypeButtons.add(ClickType.SHORT_PRESS) clickTypeButtons.add(ClickType.DOUBLE_PRESS) } @@ -363,7 +363,6 @@ abstract class BaseConfigTriggerViewModel( return TriggerKeyOptionsState.Assistant( assistantType = key.type, clickType = key.clickType, - showClickTypes = showClickTypes, ) } @@ -379,7 +378,6 @@ abstract class BaseConfigTriggerViewModel( return TriggerKeyOptionsState.FingerprintGesture( gestureType = key.type, clickType = key.clickType, - showClickTypes = showClickTypes, ) } } @@ -864,16 +862,16 @@ sealed class TriggerKeyOptionsState { data class Assistant( val assistantType: AssistantTriggerType, override val clickType: ClickType, - override val showClickTypes: Boolean, ) : TriggerKeyOptionsState() { + override val showClickTypes: Boolean = false override val showLongPressClickType: Boolean = false } data class FingerprintGesture( val gestureType: FingerprintGestureType, override val clickType: ClickType, - override val showClickTypes: Boolean, ) : TriggerKeyOptionsState() { + override val showClickTypes: Boolean = false override val showLongPressClickType: Boolean = false } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FingerprintTriggerKey.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FingerprintTriggerKey.kt index 88204c82e0..1ec7fae6f4 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FingerprintTriggerKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FingerprintTriggerKey.kt @@ -19,6 +19,7 @@ data class FingerprintTriggerKey( ) : TriggerKey() { override val consumeEvent: Boolean = true override val allowedLongPress: Boolean = false + override val allowedDoublePress: Boolean = false override fun compareTo(other: TriggerKey) = when (other) { is FingerprintTriggerKey -> compareValuesBy( diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FloatingButtonKey.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FloatingButtonKey.kt index 2999fc2cb6..ca23629e92 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FloatingButtonKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FloatingButtonKey.kt @@ -19,6 +19,7 @@ data class FloatingButtonKey( override val consumeEvent: Boolean = true override val allowedLongPress: Boolean = true + override val allowedDoublePress: Boolean = true override fun compareTo(other: TriggerKey) = when (other) { is FloatingButtonKey -> compareValuesBy( diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyCodeTriggerKey.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyCodeTriggerKey.kt index b8f28e9a53..94b8c0f579 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyCodeTriggerKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyCodeTriggerKey.kt @@ -19,6 +19,7 @@ data class KeyCodeTriggerKey( ) : TriggerKey() { override val allowedLongPress: Boolean = true + override val allowedDoublePress: Boolean = true override fun toString(): String { val deviceString = when (device) { diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKey.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKey.kt index cc29746511..a94d0db410 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKey.kt @@ -15,6 +15,7 @@ sealed class TriggerKey : Comparable { abstract val consumeEvent: Boolean abstract val uid: String abstract val allowedLongPress: Boolean + abstract val allowedDoublePress: Boolean fun setClickType(clickType: ClickType): TriggerKey = when (this) { is AssistantTriggerKey -> copy(clickType = clickType) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt index b84c05d57e..f1fb647ca7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt @@ -333,7 +333,6 @@ private fun AssistantPreview() { state = TriggerKeyOptionsState.Assistant( assistantType = AssistantTriggerType.VOICE, clickType = ClickType.DOUBLE_PRESS, - showClickTypes = true, ), ) } From 7a47797d9ed5366cdbc0595ca2cb3f84d5f81eaf Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 17:55:54 +0200 Subject: [PATCH 35/40] fix bottom padding on key map selection bottom sheet --- .../io/github/sds100/keymapper/home/SelectionBottomSheet.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/home/SelectionBottomSheet.kt b/app/src/main/java/io/github/sds100/keymapper/home/SelectionBottomSheet.kt index c78a40dac2..9add3884a7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/SelectionBottomSheet.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/SelectionBottomSheet.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn @@ -67,8 +66,7 @@ fun SelectionBottomSheet( Surface( modifier = modifier .widthIn(max = BottomSheetDefaults.SheetMaxWidth) - .fillMaxWidth() - .navigationBarsPadding(), + .fillMaxWidth(), shadowElevation = 5.dp, shape = BottomSheetDefaults.ExpandedShape, tonalElevation = BottomSheetDefaults.Elevation, From ebdf15965cb85f7de9d27bc4259a2efe207fb703 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sun, 27 Apr 2025 18:00:10 +0200 Subject: [PATCH 36/40] fix: keys with do not remap option would show dot before in key map list --- .../mappings/keymaps/KeyMapListItemCreator.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListItemCreator.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListItemCreator.kt index 2c46bb79d7..b8f4d46016 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListItemCreator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListItemCreator.kt @@ -267,21 +267,25 @@ class KeyMapListItemCreator( } } - if (deviceName != null || key.detectionSource == KeyEventDetectionSource.INPUT_METHOD || !key.consumeEvent) { - append(" (") + val parts = mutableListOf() + if (deviceName != null || key.detectionSource == KeyEventDetectionSource.INPUT_METHOD || !key.consumeEvent) { if (key.detectionSource == KeyEventDetectionSource.INPUT_METHOD) { - append("${getString(R.string.flag_detect_from_input_method)} $midDot ") + parts.add(getString(R.string.flag_detect_from_input_method)) } if (deviceName != null) { - append(deviceName) + parts.add(deviceName) } if (!key.consumeEvent) { - append(" $midDot ${getString(R.string.flag_dont_override_default_action)}") + parts.add(getString(R.string.flag_dont_override_default_action)) } + } + if (parts.isNotEmpty()) { + append(" (") + append(parts.joinToString(separator = " $midDot ")) append(")") } } From b3da0128f7227ad992e37a96de1e7413b81f8a9f Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 28 Apr 2025 16:44:07 +0200 Subject: [PATCH 37/40] fix: do not allow mixing click types in a parallel trigger --- .../mappings/keymaps/ConfigKeyMapUseCase.kt | 10 +- .../keymapper/ConfigKeyMapUseCaseTest.kt | 145 ++++++++++++++++++ app/version.properties | 2 +- 3 files changed, 153 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt index 62e1d6984c..5f69f2bff7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt @@ -304,7 +304,7 @@ class ConfigKeyMapUseCaseController( val triggerKey = AssistantTriggerKey(type = type, clickType = clickType) - val newKeys = trigger.keys.plus(triggerKey) + val newKeys = trigger.keys.plus(triggerKey).map { it.setClickType(ClickType.SHORT_PRESS) } val newMode = when { trigger.mode != TriggerMode.Sequence && containsAssistantKey -> TriggerMode.Sequence @@ -335,7 +335,7 @@ class ConfigKeyMapUseCaseController( val triggerKey = FingerprintTriggerKey(type = type, clickType = clickType) - val newKeys = trigger.keys.plus(triggerKey) + val newKeys = trigger.keys.plus(triggerKey).map { it.setClickType(ClickType.SHORT_PRESS) } val newMode = when { trigger.mode != TriggerMode.Sequence && containsFingerprintGesture -> TriggerMode.Sequence @@ -513,7 +513,7 @@ class ConfigKeyMapUseCaseController( // You can't set the trigger to a long press if it contains a key // that isn't detected with key codes. This is because there aren't // separate key events for the up and down press that can be timed. - if (trigger.keys.any { it is AssistantTriggerKey }) { + if (trigger.keys.any { !it.allowedLongPress }) { return@editTrigger trigger } @@ -534,6 +534,10 @@ class ConfigKeyMapUseCaseController( return@editTrigger trigger } + if (trigger.keys.any { !it.allowedDoublePress }) { + return@editTrigger trigger + } + val newKeys = trigger.keys.map { it.setClickType(clickType = ClickType.DOUBLE_PRESS) } val newMode = TriggerMode.Undefined diff --git a/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt index 6d2300f589..fa87b9068a 100644 --- a/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt @@ -5,6 +5,7 @@ import io.github.sds100.keymapper.actions.Action import io.github.sds100.keymapper.actions.ActionData import io.github.sds100.keymapper.constraints.Constraint import io.github.sds100.keymapper.mappings.ClickType +import io.github.sds100.keymapper.mappings.FingerprintGestureType import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCaseController import io.github.sds100.keymapper.mappings.keymaps.KeyMap import io.github.sds100.keymapper.mappings.keymaps.trigger.AssistantTriggerKey @@ -57,6 +58,150 @@ class ConfigKeyMapUseCaseTest { ) } + @Test + fun `Do not allow setting double press for parallel trigger with side key`() = runTest(testDispatcher) { + useCase.keyMap.value = State.Data(KeyMap()) + + useCase.addKeyCodeTriggerKey( + KeyEvent.KEYCODE_VOLUME_DOWN, + TriggerKeyDevice.Any, + detectionSource = KeyEventDetectionSource.ACCESSIBILITY_SERVICE, + ) + useCase.addAssistantTriggerKey(AssistantTriggerType.ANY) + + useCase.setTriggerDoublePress() + + val trigger = useCase.keyMap.value.dataOrNull()!!.trigger + assertThat(trigger.mode, `is`(TriggerMode.Parallel(clickType = ClickType.SHORT_PRESS))) + assertThat(trigger.keys[0].clickType, `is`(ClickType.SHORT_PRESS)) + assertThat(trigger.keys[1].clickType, `is`(ClickType.SHORT_PRESS)) + } + + @Test + fun `Do not allow setting long press for parallel trigger with side key`() = runTest(testDispatcher) { + useCase.keyMap.value = State.Data(KeyMap()) + + useCase.addKeyCodeTriggerKey( + KeyEvent.KEYCODE_VOLUME_DOWN, + TriggerKeyDevice.Any, + detectionSource = KeyEventDetectionSource.ACCESSIBILITY_SERVICE, + ) + useCase.addAssistantTriggerKey(AssistantTriggerType.ANY) + + useCase.setTriggerLongPress() + + val trigger = useCase.keyMap.value.dataOrNull()!!.trigger + assertThat(trigger.mode, `is`(TriggerMode.Parallel(clickType = ClickType.SHORT_PRESS))) + assertThat(trigger.keys[0].clickType, `is`(ClickType.SHORT_PRESS)) + assertThat(trigger.keys[1].clickType, `is`(ClickType.SHORT_PRESS)) + } + + @Test + fun `Do not allow setting double press for side key`() = runTest(testDispatcher) { + useCase.keyMap.value = State.Data(KeyMap()) + + useCase.addAssistantTriggerKey(AssistantTriggerType.ANY) + + useCase.setTriggerDoublePress() + + val trigger = useCase.keyMap.value.dataOrNull()!!.trigger + assertThat(trigger.mode, `is`(TriggerMode.Undefined)) + assertThat(trigger.keys[0].clickType, `is`(ClickType.SHORT_PRESS)) + } + + @Test + fun `Do not allow setting long press for side key`() = runTest(testDispatcher) { + useCase.keyMap.value = State.Data(KeyMap()) + + useCase.addAssistantTriggerKey(AssistantTriggerType.ANY) + + useCase.setTriggerLongPress() + + val trigger = useCase.keyMap.value.dataOrNull()!!.trigger + assertThat(trigger.mode, `is`(TriggerMode.Undefined)) + assertThat(trigger.keys[0].clickType, `is`(ClickType.SHORT_PRESS)) + } + + @Test + fun `Set click type to short press if side key added to double press volume button`() = runTest(testDispatcher) { + useCase.keyMap.value = State.Data(KeyMap()) + + useCase.addKeyCodeTriggerKey( + KeyEvent.KEYCODE_VOLUME_DOWN, + TriggerKeyDevice.Any, + detectionSource = KeyEventDetectionSource.ACCESSIBILITY_SERVICE, + ) + + useCase.setTriggerDoublePress() + + useCase.addAssistantTriggerKey(AssistantTriggerType.ANY) + + val trigger = useCase.keyMap.value.dataOrNull()!!.trigger + assertThat(trigger.mode, `is`(TriggerMode.Parallel(clickType = ClickType.SHORT_PRESS))) + assertThat(trigger.keys[0].clickType, `is`(ClickType.SHORT_PRESS)) + assertThat(trigger.keys[1].clickType, `is`(ClickType.SHORT_PRESS)) + } + + @Test + fun `Set click type to short press if fingerprint gestures added to double press volume button`() = runTest(testDispatcher) { + useCase.keyMap.value = State.Data(KeyMap()) + + useCase.addKeyCodeTriggerKey( + KeyEvent.KEYCODE_VOLUME_DOWN, + TriggerKeyDevice.Any, + detectionSource = KeyEventDetectionSource.ACCESSIBILITY_SERVICE, + ) + + useCase.setTriggerDoublePress() + + useCase.addFingerprintGesture(FingerprintGestureType.SWIPE_UP) + + val trigger = useCase.keyMap.value.dataOrNull()!!.trigger + assertThat(trigger.mode, `is`(TriggerMode.Parallel(clickType = ClickType.SHORT_PRESS))) + assertThat(trigger.keys[0].clickType, `is`(ClickType.SHORT_PRESS)) + assertThat(trigger.keys[1].clickType, `is`(ClickType.SHORT_PRESS)) + } + + @Test + fun `Set click type to short press if side key added to long press volume button`() = runTest(testDispatcher) { + useCase.keyMap.value = State.Data(KeyMap()) + + useCase.addKeyCodeTriggerKey( + KeyEvent.KEYCODE_VOLUME_DOWN, + TriggerKeyDevice.Any, + detectionSource = KeyEventDetectionSource.ACCESSIBILITY_SERVICE, + ) + + useCase.setTriggerLongPress() + + useCase.addAssistantTriggerKey(AssistantTriggerType.ANY) + + val trigger = useCase.keyMap.value.dataOrNull()!!.trigger + assertThat(trigger.mode, `is`(TriggerMode.Parallel(clickType = ClickType.SHORT_PRESS))) + assertThat(trigger.keys[0].clickType, `is`(ClickType.SHORT_PRESS)) + assertThat(trigger.keys[1].clickType, `is`(ClickType.SHORT_PRESS)) + } + + @Test + fun `Set click type to short press if fingerprint gestures added to long press volume button`() = runTest(testDispatcher) { + useCase.keyMap.value = State.Data(KeyMap()) + + useCase.addKeyCodeTriggerKey( + KeyEvent.KEYCODE_VOLUME_DOWN, + TriggerKeyDevice.Any, + detectionSource = KeyEventDetectionSource.ACCESSIBILITY_SERVICE, + ) + + useCase.setTriggerLongPress() + + useCase.addFingerprintGesture(FingerprintGestureType.SWIPE_UP) + + val trigger = useCase.keyMap.value.dataOrNull()!!.trigger + assertThat(trigger.mode, `is`(TriggerMode.Parallel(clickType = ClickType.SHORT_PRESS))) + assertThat(trigger.keys[0].clickType, `is`(ClickType.SHORT_PRESS)) + assertThat(trigger.keys[1].clickType, `is`(ClickType.SHORT_PRESS)) + } + @Test fun `Enable hold down option for key event actions when the trigger is a DPAD button`() = runTest(testDispatcher) { useCase.keyMap.value = State.Data(KeyMap()) diff --git a/app/version.properties b/app/version.properties index cf110f8e05..c55a9cc370 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.0.1 -VERSION_CODE=101 +VERSION_CODE=102 VERSION_NUM=0 \ No newline at end of file From ae43028669dc7519ac0f709668319f6ee7ed8f50 Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 28 Apr 2025 20:04:40 +0200 Subject: [PATCH 38/40] #1669 change tile text --- CHANGELOG.md | 1 + app/src/main/AndroidManifest.xml | 2 +- .../system/tiles/ToggleMappingsTile.kt | 96 ++++++++++++++----- app/src/main/res/values/strings.xml | 6 +- 4 files changed, 78 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91fcae1471..931a784687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - #1654 The Key Mapper keyboard is now required again for Text actions because the accessibility service API does not work in all situations. - #1653 Hide the export/import menu buttons in groups. - #1553 Hide double press option for side key and fingerprint gesture triggers because it is misleading. Double activations can be done with sequence triggers instead. +- #1669 Change quick settings tile text. ## Bug fixes diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 78e07f48d0..c19777221d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -254,7 +254,7 @@ android:name=".system.tiles.ToggleMappingsTile" android:exported="true" android:icon="@drawable/ic_tile_pause" - android:label="@string/tile_pause" + android:label="@string/tile_pause_title" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" tools:targetApi="24"> diff --git a/app/src/main/java/io/github/sds100/keymapper/system/tiles/ToggleMappingsTile.kt b/app/src/main/java/io/github/sds100/keymapper/system/tiles/ToggleMappingsTile.kt index e838c840e5..7763983b09 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/tiles/ToggleMappingsTile.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/tiles/ToggleMappingsTile.kt @@ -45,35 +45,83 @@ class ToggleMappingsTile : val ctx = this@ToggleMappingsTile - when { - serviceState == ServiceState.DISABLED -> { - qsTile.label = str(R.string.tile_service_disabled) - qsTile.contentDescription = - str(R.string.tile_accessibility_service_disabled_content_description) - qsTile.icon = Icon.createWithResource(ctx, R.drawable.ic_tile_error) - qsTile.state = Tile.STATE_UNAVAILABLE - } - - isPaused -> { - qsTile.label = str(R.string.tile_resume) - qsTile.contentDescription = str(R.string.tile_resume) - qsTile.icon = Icon.createWithResource(ctx, R.drawable.ic_tile_resume) - qsTile.state = Tile.STATE_INACTIVE - } - - !isPaused -> { - qsTile.label = str(R.string.tile_pause) - qsTile.contentDescription = str(R.string.tile_pause) - qsTile.icon = Icon.createWithResource(ctx, R.drawable.ic_tile_pause) - qsTile.state = Tile.STATE_ACTIVE - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + updateQsTile(serviceState, ctx, isPaused) + } else { + updateQsTilePreSdk29(serviceState, ctx, isPaused) } - - qsTile.updateTile() }.collect() } } + private fun updateQsTilePreSdk29( + serviceState: ServiceState, + ctx: ToggleMappingsTile, + isPaused: Boolean, + ) { + when { + serviceState == ServiceState.DISABLED -> { + qsTile.label = str(R.string.tile_service_disabled) + qsTile.contentDescription = + str(R.string.tile_accessibility_service_disabled_content_description) + qsTile.icon = Icon.createWithResource(ctx, R.drawable.ic_tile_error) + qsTile.state = Tile.STATE_UNAVAILABLE + } + + isPaused -> { + qsTile.label = str(R.string.tile_resume_title) + qsTile.contentDescription = str(R.string.tile_resume_title) + qsTile.icon = Icon.createWithResource(ctx, R.drawable.ic_tile_pause) + qsTile.state = Tile.STATE_INACTIVE + } + + !isPaused -> { + qsTile.label = str(R.string.tile_pause_title) + qsTile.contentDescription = str(R.string.tile_pause_title) + qsTile.icon = Icon.createWithResource(ctx, R.drawable.ic_tile_resume) + qsTile.state = Tile.STATE_ACTIVE + } + } + + qsTile.updateTile() + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun updateQsTile( + serviceState: ServiceState, + ctx: ToggleMappingsTile, + isPaused: Boolean, + ) { + when { + serviceState == ServiceState.DISABLED -> { + qsTile.label = str(R.string.app_name) + qsTile.subtitle = str(R.string.tile_service_disabled) + qsTile.contentDescription = + str(R.string.tile_accessibility_service_disabled_content_description) + qsTile.icon = Icon.createWithResource(ctx, R.drawable.ic_tile_error) + qsTile.state = Tile.STATE_UNAVAILABLE + } + + isPaused -> { + qsTile.label = str(R.string.app_name) + qsTile.subtitle = str(R.string.tile_paused_subtitle) + qsTile.contentDescription = str(R.string.tile_resume_title) + qsTile.icon = Icon.createWithResource(ctx, R.drawable.ic_tile_pause) + qsTile.state = Tile.STATE_INACTIVE + } + + !isPaused -> { + qsTile.label = str(R.string.app_name) + qsTile.subtitle = str(R.string.tile_running_subtitle) + qsTile.contentDescription = str(R.string.tile_pause_title) + qsTile.icon = Icon.createWithResource(ctx, R.drawable.ic_tile_resume) + qsTile.state = Tile.STATE_ACTIVE + } + } + + qsTile.updateTile() + } + override fun onStartListening() { lifecycleRegistry.currentState = Lifecycle.State.STARTED diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f1b636069..a95ad25d6f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -761,8 +761,10 @@ - Pause key maps - Resume key maps + Pause key maps + Resume key maps + Paused + Running Service Disabled Key Mapper accessibility service is disabled Toggle Key Mapper keyboard From b1173013278f2d6a217af2bf7317ccf54bfd5319 Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 28 Apr 2025 20:23:56 +0200 Subject: [PATCH 39/40] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index c55a9cc370..aa81264386 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.0.1 -VERSION_CODE=102 +VERSION_CODE=103 VERSION_NUM=0 \ No newline at end of file From 5d95314ddc46850febc522a2173ccfcded626702 Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 28 Apr 2025 20:24:14 +0200 Subject: [PATCH 40/40] complete changelog for 3.0.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 931a784687..9fb8048a0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) -#### TO BE RELEASED +#### 28 April 2025 ## Added