diff --git a/.editorconfig b/.editorconfig
index 45db7af9a4..a55befd7a9 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -4,6 +4,6 @@ ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_ignore_back_ticked_identifier = true
ktlint_code_style = intellij_idea # Use IntelliJ style because it has trailing commas
-[app/src/main/java/io/github/sds100/keymapper/util/ui/compose/icons/*.{kt,kts}]
+[base/src/main/java/io/github/sds100/keymapper/base/utils/ui/compose/icons/*.{kt,kts}]
ktlint_standard_property-naming = disabled
ktlint_standard_backing-property-naming = disabled
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 25b2b65b93..0000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ''
-labels: bug, needs triage
-assignees: sds100
-
----
-
-**Developer TODO (don't remove)**
-- [ ] update documentation
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**Attach bug report**
-https://docs.keymapper.club/report-issues
-
-**To Reproduce**
-Steps to reproduce the behavior:
-
-**Key maps**
-Upload a backup of your keymaps. [Follow the guide here](https://docs.keymapper.club/user-guide/backup-restore)
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
-
-**Device and app info**
- - Device:
- - Android version (include HyperOS, OneUI version as well):
- - Key Mapper version:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000000..b08635993b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,79 @@
+name: Bug report
+description: Use this template if there is a technical fault in the app
+labels: ["bug", "needs triage"]
+assignees:
+ - sds100
+
+body:
+ - type: checkboxes
+ id: screening
+ attributes:
+ label: Have you checked if an issue already exists for this bug?
+ description: Please search to see if someone has already reported this bug. If they have, you can speed up fixing the bug by commenting on that issue.
+ options:
+ - label: Nobody else has reported this bug before
+ required: true
+
+ - type: checkboxes
+ id: restart
+ attributes:
+ label: Have you tried restarting your device?
+ description: This can occasionally be necessary.
+ options:
+ - label: I have tried restarting my device and that did not help
+ required: false
+
+ - type: textarea
+ id: unexpected_behaviour
+ attributes:
+ label: What happened that you did not expect?
+ description: |
+ What behaviour were you experiencing that you think is a bug (not intended)
+ **It will help if you upload your key maps or logs**
+ placeholder: I was doing X, and then Y happened that I did not expect.
+
+ - type: textarea
+ id: expected_behaviour
+ attributes:
+ label: What did you expect to happen?
+ description: If the app was working properly, what would have happened instead?
+ placeholder: When I pressed X, I should have seen Y
+
+ - type: textarea
+ id: how_to_reproduce
+ attributes:
+ label: What would the developer need to do to reproduce the bug?
+ description: Imagine the developer has the same device as you, and has just installed the app. What steps should they follow to see the same unexpected behaviour?
+ placeholder: |
+ 1. Make a key map with volume up trigger
+ 2. Add the flashlight action
+ 3. Save the key map and close the app
+ 4. Press the volume up trigger
+ 5. See error message
+
+ - type: input
+ id: version
+ attributes:
+ label: App version
+ description: You can find the app version on the About page
+ placeholder: 3.2.1 999
+ validations:
+ required: true
+
+ - type: input
+ id: device
+ attributes:
+ label: Device model and manufacturer
+ description: What model is your device? Be as specific as you can
+ placeholder: Xiaomi, Samsung Galaxy, etc.
+ validations:
+ required: true
+
+ - type: checkboxes
+ id: extras
+ attributes:
+ label: Extra info
+ description: It would help us even more if you could check the boxes that apply to you.
+ options:
+ - label: My device is rooted
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000..165131bc55
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Need help using the app?
+ url: http://keymapper.club?utm_source=issue_template_chooser
+ about: Join the Discord server and let us help you
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000000..e6a029cffb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,51 @@
+name: Feature request
+description: Use this template if you have an idea for a new feature to be added to the app
+labels: ["enhancement", "needs triage"]
+assignees:
+ - sds100
+
+body:
+ - type: checkboxes
+ id: screening
+ attributes:
+ label: Have you checked if an issue already exists for this feature request?
+ description: Please search to see if someone has already requested this feature. If they have, you can speed up implementing the feature by commenting on that issue.
+ options:
+ - label: Nobody else has requested this feature before
+ required: true
+
+ - type: textarea
+ id: enhancement
+ attributes:
+ label: What feature would you like added?
+ description: |
+ Please explain the feature in as much detail as you can to help us imagine what you are looking for. Imagine how someone might use the feature, who would it benefit?
+ validations:
+ required: true
+
+ - type: input
+ id: version
+ attributes:
+ label: App version
+ description: You can find the app version on the About page
+ placeholder: 3.2.1 999
+ validations:
+ required: false
+
+ - type: input
+ id: device
+ attributes:
+ label: Device model and manufacturer
+ description: What model is your device? Be as specific as you can
+ placeholder: Xiaomi, Samsung Galaxy, etc.
+ validations:
+ required: false
+
+ - type: checkboxes
+ id: extras
+ attributes:
+ label: Extra info
+ description: It would help us even more if you could check the boxes that apply to you.
+ options:
+ - label: My device is rooted
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/new-feature.md b/.github/ISSUE_TEMPLATE/new-feature.md
deleted file mode 100644
index 677a2e1159..0000000000
--- a/.github/ISSUE_TEMPLATE/new-feature.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-name: New Feature
-about: Add a new feature or enhancement to the app.
-title: ''
-labels: enhancement, needs triage
-assignees: sds100
-
----
-
-**Developer TODO (don't remove)**
-- [ ] update documentation
diff --git a/.github/ISSUE_TEMPLATE/ux_issue.yml b/.github/ISSUE_TEMPLATE/ux_issue.yml
new file mode 100644
index 0000000000..63a0ec8d7b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/ux_issue.yml
@@ -0,0 +1,54 @@
+name: User experience complaint
+description: Use this template if you found the app hard to use or confusing
+labels: ["user experience", "needs triage"]
+assignees:
+ - jambl3r
+
+body:
+ - type: checkboxes
+ id: screening
+ attributes:
+ label: Have you checked if an issue already exists for this problem?
+ description: Please search to see if someone has already reported this problem. If they have, you can speed up improving the app by commenting on that issue.
+ options:
+ - label: Nobody else has reported this problem before
+ required: true
+
+ - type: textarea
+ id: complaint
+ attributes:
+ label: What problem did you face using the app?
+ description: If something was confusing or difficult, please explain in as much detail as you can what you found difficult.
+
+ - type: textarea
+ id: improvements
+ attributes:
+ label: What do you think would make the experience easier for you?
+ description: Feel free to share your ideas, you can use examples of good experiences elsewhere in the app or in other apps or your every day life.
+
+ - type: input
+ id: version
+ attributes:
+ label: App version
+ description: You can find the app version on the About page
+ placeholder: 3.2.1 999
+ validations:
+ required: true
+
+ - type: input
+ id: device
+ attributes:
+ label: Device model and manufacturer
+ description: What model is your device? Be as specific as you can
+ placeholder: Xiaomi, Samsung Galaxy, etc.
+ validations:
+ required: true
+
+ - type: checkboxes
+ id: extras
+ attributes:
+ label: Extra info
+ description: It would help us even more if you could check the boxes that apply to you.
+ options:
+ - label: My device is rooted
+ required: false
diff --git a/.github/workflows/approve-translations.yml b/.github/workflows/approve-translations.yml
new file mode 100644
index 0000000000..027ece94d8
--- /dev/null
+++ b/.github/workflows/approve-translations.yml
@@ -0,0 +1,69 @@
+name: Auto Approve Crowdin Translations
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+
+jobs:
+ approve-translations:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '18'
+
+ - name: Install Crowdin CLI
+ run: |
+ npm install -g @crowdin/cli@3
+
+ - name: Approve top-voted translations for all languages
+ env:
+ CROWDIN_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
+ CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
+ run: |
+ set -e
+
+ if [ -z "$CROWDIN_TOKEN" ] || [ -z "$CROWDIN_PROJECT_ID" ]; then
+ echo "Crowdin token or project id is missing!"
+ exit 1
+ fi
+
+ echo "Start automatic approval Crowdin Translation..."
+
+ language_ids=$(crowdin api list-languages --project-id=$CROWDIN_PROJECT_ID --plain | jq -r '.data[].data.id' || true)
+ if [ -z "$language_ids" ]; then
+ echo "No language ids found, check Crowdin project or CLI output."
+ exit 1
+ fi
+
+ string_ids=$(crowdin api list-strings --project-id=$CROWDIN_PROJECT_ID --plain | jq -r '.data[].data.id' || true)
+ if [ -z "$string_ids" ]; then
+ echo "No string ids found, check Crowdin project or CLI output."
+ exit 1
+ fi
+
+ for lang in $language_ids; do
+ echo "Processing language $lang"
+
+ translations=$(crowdin api list-string-translations --project-id=$CROWDIN_PROJECT_ID --language-id=$lang --plain)
+
+ for sid in $string_ids; do
+ best=$(echo "$translations" | jq -r "
+ .data[]
+ | .data
+ | select(.stringId == \"$sid\" and .votes > 0)
+ | {\"id\": .id, \"votes\": .votes}
+ " | jq -s 'sort_by(-.votes) | .[0] | .id')
+
+ if [ -n "$best" ] && [ "$best" != "null" ]; then
+ echo " → Approved translation $best for string $sid"
+ crowdin api edit-translation \
+ --project-id=$CROWDIN_PROJECT_ID \
+ --translation-id=$best \
+ --approved=true
+ fi
+ done
+ done
\ No newline at end of file
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index b0a1c1f356..a2027703b1 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -21,7 +21,7 @@ jobs:
uses: android-actions/setup-android@v2
- name: Unit tests
- run: bash ./gradlew testFreeDebugUnitTest
+ run: bash ./gradlew testDebugUnitTest
style:
name: Code style check
@@ -96,10 +96,10 @@ jobs:
run: bundle exec fastlane testing
- name: set apk name env
- run: echo "APK_NAME=$(basename app/build/outputs/apk/free/ci/*.apk .apk)" >> $GITHUB_ENV
+ run: echo "APK_NAME=$(basename app/build/outputs/apk/ci/*.apk .apk)" >> $GITHUB_ENV
- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: ${{ env.APK_NAME }}
- path: app/build/outputs/apk/free/ci/${{ env.APK_NAME }}.apk
+ path: app/build/outputs/apk/ci/${{ env.APK_NAME }}.apk
diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 7bef735539..b86ad1b439 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -27,7 +27,7 @@ jobs:
uses: android-actions/setup-android@v2
- name: Unit tests
- run: bash ./gradlew testFreeDebugUnitTest
+ run: bash ./gradlew testDebugUnitTest
style:
name: Code style check
@@ -114,13 +114,13 @@ jobs:
run: bundle exec fastlane testing
- name: set apk name env
- run: echo "APK_NAME=$(basename app/build/outputs/apk/free/ci/*.apk .apk)" >> $GITHUB_ENV
+ run: echo "APK_NAME=$(basename app/build/outputs/apk/ci/*.apk .apk)" >> $GITHUB_ENV
- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: ${{ env.APK_NAME }}
- path: app/build/outputs/apk/free/ci/${{ env.APK_NAME }}.apk
+ path: app/build/outputs/apk/ci/${{ env.APK_NAME }}.apk
- name: Upload to Discord
uses: sinshutu/upload-to-discord@v2.0.0
@@ -128,7 +128,7 @@ jobs:
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
with:
- args: app/build/outputs/apk/free/ci/${{ env.APK_NAME }}.apk
+ args: app/build/outputs/apk/ci/${{ env.APK_NAME }}.apk
- name: Report build status to Discord
uses: sarisia/actions-status-discord@v1
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000000..74b4435ffe
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+KeyMapperFoss
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000000..fd844d9451
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml
new file mode 100644
index 0000000000..7dbf723192
--- /dev/null
+++ b/.idea/navEditor.xml
@@ -0,0 +1,257 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/instrumentation-tests.run.xml b/.run/instrumentation-tests.run.xml
deleted file mode 100644
index 57995ce1b4..0000000000
--- a/.run/instrumentation-tests.run.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.run/unit-tests.run.xml b/.run/unit-tests.run.xml
deleted file mode 100644
index 880d3c46f6..0000000000
--- a/.run/unit-tests.run.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b57ad351a1..e7a03af9e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,38 @@
+## [3.2.0](https://github.com/sds100/KeyMapper/releases/tag/v3.2.0)
+
+#### 02 Sept 2025
+
+## Added
+
+- #1466 show onboarding when creating a key map for the first time
+- #1729 target Android 16.
+- #1725 action to move cursor to previous/next character, word, line, paragraph, or page.
+- Names for new key codes introduced in recent Android versions
+
+## Changed
+
+- #1711 major refactoring of the entire codebase into separate Gradle modules.
+- #1701 improve the order of the actions and categories
+- Key Mapper keyboard or Shizuku are no longer required for the action to move the cursor to the end
+
+## Bug fixes
+
+- #1686 (more fixes) do not show some screens behind system bars on the left/right side of the
+ device.
+- #1701 optimize the trigger screen for smaller screens so elements are less cut off.
+- #1699 Do not highlight a floating button as if it is pressed after triggering a key event action
+ from it.
+- Button to copy the key map UID to the clipboard is invisible on small screens.
+- #1709 Quick settings tiles were causing crashes on Android 15
+- #1714 Editing "interact with app element" actions works.
+- #1707 do not back up sound files if no key maps are using them
+- #797 #1719 execute key maps that can fix themselves. E.g having an action to select the Key Mapper
+ Keyboard before a key code action.
+- #1735 Floating buttons no longer flash on screen when the accessibility service restarts if they
+ are not supposed to be visible.
+- #1717 do not show floating buttons if quick settings is expanded on the lockscreen.
+- Correctly show error that Airplane mode actions require root
+
## [3.1.1](https://github.com/sds100/KeyMapper/releases/tag/v3.1.1)
#### 12 May 2025
@@ -32,7 +67,8 @@
- #1683 key event actions work in Minecraft and other apps again.
- Export log files as .txt instead of .zip files.
-- #1684 Removed the redundant and broken refresh devices button when configuring a key event action because they are automatically refreshed anyway.
+- #1684 Removed the redundant and broken refresh devices button when configuring a key event action
+ because they are automatically refreshed anyway.
- #1687 restoring key map groups would sometimes fail.
## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1)
@@ -46,18 +82,22 @@
## Changed
-- #1654 The Key Mapper keyboard is now required again for Text actions because the accessibility service API does not work in all situations.
+- #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.
+- #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
-- 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.
+- 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.
+- #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.
@@ -92,7 +132,8 @@ _See the changes from previous 3.0 Beta releases as well._
## Added
- #1620 enable Key Mapper Basic Input Method without user interaction on Android 13+.
-- #1619 Automatically select the non key mapper keyboard when the device is locked and wanting to type.
+- #1619 Automatically select the non key mapper keyboard when the device is locked and wanting to
+ type.
## Changed
@@ -112,7 +153,9 @@ _See the changes from previous 3.0 Beta releases as well._
This is not an April Fool's joke ;)
## Added
-- #320 🗂️ Key map groups! You can now sort key maps into groups and share constraints across all the key maps in the group.
+
+- #320 🗂️ Key map groups! You can now sort key maps into groups and share constraints across all the
+ key maps in the group.
- #1586 🎨 Customise floating button border and background opacity.
- #1276 Use key event scan code as fallback if the key code is unrecognized.
- Make it clearer that the instructions need to be read for the assistant trigger.
@@ -124,12 +167,14 @@ This is not an April Fool's joke ;)
## Bug fixes
-- Do not hide floating button when the quick settings are showing if the key map action can collapse the status bar.
+- Do not hide floating button when the quick settings are showing if the key map action can collapse
+ the status bar.
- Do not show floating buttons on the always-on display or when the display is "off".
- Prompt to unlock device when tapping "Go back" on the floating menu.
- #1596 Do not show the option for front flashlight if the device does not have one.
- #1598 Do not allow changing flashlight brightness on devices that do not support it.
-- Omit "Back" from Back flashlight actions and constraints since most devices only have a back flashlight anyway.
+- Omit "Back" from Back flashlight actions and constraints since most devices only have a back
+ flashlight anyway.
- Do not ask for which flashlight to use in constraints if the device only has one
## [3.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0-beta.2)
@@ -138,17 +183,22 @@ This is not an April Fool's joke ;)
## Added
-- #1560 Action to change flashlight brightness and also set a custom brightness when enabling the flashlight.
+- #1560 Action to change flashlight brightness and also set a custom brightness when enabling the
+ flashlight.
- Prompt to unlock device when using a floating button as a trigger from the lock screen
## Changed
-- #1577 Move unsupported actions to the bottom of the list and do not allow selecting root actions if root permission is not granted.
-- #1593 Deprecate the 'Open menu' action by not letting new key maps use it. It is a relic of the past when most apps had a 3-dot menu with a consistent content description making it somewhat easy to identify.
+- #1577 Move unsupported actions to the bottom of the list and do not allow selecting root actions
+ if root permission is not granted.
+- #1593 Deprecate the 'Open menu' action by not letting new key maps use it. It is a relic of the
+ past when most apps had a 3-dot menu with a consistent content description making it somewhat easy
+ to identify.
## Bug fixes
-- #1585 Track changes when editing key maps and only prompt to discard changes if there were indeed changes.
+- #1585 Track changes when editing key maps and only prompt to discard changes if there were indeed
+ changes.
## [3.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0-beta.1)
@@ -163,14 +213,18 @@ in Jetpack Compose, resulting in many improvements to the user experience.
- Key maps are much more dense on the home screen.
- Button to pause/resume key maps at the top of the home screen.
- #1502 Constraint for lockscreen is (not) showing.
-- #1203 Show a share sheet after exporting key maps rather than asking where to store it. This solves the problem when no apps are installed to select where to back it up. You can still find the file in the Downloads file.
+- #1203 Show a share sheet after exporting key maps rather than asking where to store it. This
+ solves the problem when no apps are installed to select where to back it up. You can still find
+ the file in the Downloads file.
- #1531 Show shortcuts to quickly add recently used actions and constraints.
-- #1487 Add confirmation dialog when importing key maps and offer the option to replace all the key maps or append to the list.
+- #1487 Add confirmation dialog when importing key maps and offer the option to replace all the key
+ maps or append to the list.
- #1546 Add short explanation of what constraints mean on top of the list.
- #1548 Dynamically change key map enabled switch label.
- #1562 Import key maps by opening .json and .zip files from other apps and file managers.
## Bug fixes
+
- #1518 detect more apps that are playing media (fix to previous fix).
- #1545 support phone call constraints in more apps.
- #1536 'Edit action' sometimes does not appear.
@@ -196,9 +250,11 @@ in Jetpack Compose, resulting in many improvements to the user experience.
## Changes
-- #1514, #1454 Improving naming of assistant trigger to also refer to the side key and do not force the user to select Key Mapper as the assistant.
+- #1514, #1454 Improving naming of assistant trigger to also refer to the side key and do not force
+ the user to select Key Mapper as the assistant.
## Bug fixes
+
- #1461 fix: crash on startup due to getting MotionEvent device
- #1518 fix: detect apps playing media without a notification for media constraints
@@ -212,7 +268,8 @@ in Jetpack Compose, resulting in many improvements to the user experience.
- #1386 wait for sequence trigger timeout before triggering other overlapping triggers.
- #1449 improve the key mapper crashed dialog.
- #1415 make the discard changes dialog less confusing.
-- #1440 do not show the "Button not detected?" bottom sheet every time you open the config key map screen in some cases.
+- #1440 do not show the "Button not detected?" bottom sheet every time you open the config key map
+ screen in some cases.
- #1447 the app bar when configuring an Intent action would extend to the top of the screen.
- #1444 use the correct icon for screen on/off constraints.
@@ -234,7 +291,8 @@ in Jetpack Compose, resulting in many improvements to the user experience.
## Removed
-- #1411 remove the app intro screen for remapping fingerprint gestures because almost all new phones do not support them anyway.
+- #1411 remove the app intro screen for remapping fingerprint gestures because almost all new phones
+ do not support them anyway.
## Bug fixes
@@ -258,7 +316,8 @@ in Jetpack Compose, resulting in many improvements to the user experience.
- #1342 add Meta modifier keys to the key event action.
- #1101 deprecate the toggle split screen action on Android 12L and newer.
-- #1370 warn the user that extra permissions are required for the Launch app action on Xiaomi devices
+- #1370 warn the user that extra permissions are required for the Launch app action on Xiaomi
+ devices
- #1371 try to fix the app not opening on people's devices
## [2.7.1](https://github.com/sds100/KeyMapper/releases/tag/v2.7.1)
@@ -277,12 +336,14 @@ in Jetpack Compose, resulting in many improvements to the user experience.
## Added
-- #1274 New trigger! You can now trigger your key maps from any of the ways your phone launches the assistant! This could be the Bixby button, Power button, or a button on your headset.
+- #1274 New trigger! You can now trigger your key maps from any of the ways your phone launches the
+ assistant! This could be the Bixby button, Power button, or a button on your headset.
- #1304 Vietnamese translations.
## Bug fixes
-- #1222 #1307 Key Mapper doesn't execute the correct app shortcut action if you created multiple from the same app.
+- #1222 #1307 Key Mapper doesn't execute the correct app shortcut action if you created multiple
+ from the same app.
- #1328 Single-character non-ASCII TEXT_BLOCK input crashes the service
## [2.6.2](https://github.com/sds100/KeyMapper/releases/tag/v2.6.2)
@@ -304,7 +365,8 @@ This release adds support for Android 14 and fixes some bugs associated with it.
### Bug fixes
-- #1218, #1251 Key event actions and triggering key maps from an intent were delayed by 1 second on Android 14 due to new broadcast receiver restrictions.
+- #1218, #1251 Key event actions and triggering key maps from an intent were delayed by 1 second on
+ Android 14 due to new broadcast receiver restrictions.
- #1175 Bypass the do not disturb permission requirement for volume button triggers.
- #1234 Granting permissions with Shizuku crashes on Android 14.
- #1249 Crash when opening help page from the home page if no browser app for custom tabs was found.
@@ -313,35 +375,43 @@ This release adds support for Android 14 and fixes some bugs associated with it.
- #1252 Add another Camera key code as supported for screen off triggers.
- #1219 Key Mapper notifications could not be enabled on Android 14.
- #1194 Deprecate closing the status bar on Android 14 due to new restrictions.
-- #1190 Add a 3 second delay after the screenshot action before showing the on-screen message confirming it happened.
+- #1190 Add a 3 second delay after the screenshot action before showing the on-screen message
+ confirming it happened.
## [2.6.0](https://github.com/sds100/KeyMapper/releases/tag/v2.6.0)
#### 7 October 2023
-- #550 Action for doing pinches and swipes on the screen with 2 or more fingers. Many thanks to Tino (@pixel-shock) for working on this feature. 😊
+- #550 Action for doing pinches and swipes on the screen with 2 or more fingers. Many thanks to
+ Tino (@pixel-shock) for working on this feature. 😊
## [2.5.0](https://github.com/sds100/KeyMapper/releases/tag/v2.5.0)
#### 9 September 2023
### Added
-- [#1157](https://github.com/keymapperorg/KeyMapper/pull/1157) Action for doing swipe gestures on the screen with 1 or more fingers. Many thanks to Tino (@pixel-shock) for working on this feature. 😊
+- [#1157](https://github.com/keymapperorg/KeyMapper/pull/1157) Action for doing swipe gestures on
+ the screen with 1 or more fingers. Many thanks to Tino (@pixel-shock) for working on this feature.
+ 😊
## [2.4.6](https://github.com/sds100/KeyMapper/releases/tag/v2.4.6)
#### 16 August 2023
### Changed
-- [#1148](https://github.com/keymapperorg/KeyMapper/issues/1148) Fix crash when accessibility service started.
+
+- [#1148](https://github.com/keymapperorg/KeyMapper/issues/1148) Fix crash when accessibility
+ service started.
## [2.4.5](https://github.com/sds100/KeyMapper/releases/tag/v2.4.5)
#### 5 August 2023
### Changed
-- [#1120](https://github.com/keymapperorg/KeyMapper/issues/1120) Do not change the UIDs of key maps when importing them.
+
+- [#1120](https://github.com/keymapperorg/KeyMapper/issues/1120) Do not change the UIDs of key maps
+ when importing them.
## [2.4.4](https://github.com/sds100/KeyMapper/releases/tag/v2.4.4)
@@ -349,18 +419,23 @@ This release adds support for Android 14 and fixes some bugs associated with it.
### Bug fixes
-- [#1073](https://github.com/keymapperorg/KeyMapper/issues/1073) The toggle key maps quick settings tile now looks
+- [#1073](https://github.com/keymapperorg/KeyMapper/issues/1073) The toggle key maps quick settings
+ tile now looks
enabled when the key maps are resumed.
-- [#1062](https://github.com/keymapperorg/KeyMapper/issues/1062) The select word at cursor action would select the whole
+- [#1062](https://github.com/keymapperorg/KeyMapper/issues/1062) The select word at cursor action
+ would select the whole
line if the cursor is at the end of the line.
-- [#999](https://github.com/keymapperorg/KeyMapper/issues/999) Remove delay when launching app shortcut actions when at
+- [#999](https://github.com/keymapperorg/KeyMapper/issues/999) Remove delay when launching app
+ shortcut actions when at
the launcher.
### Added
-- [#1054](https://github.com/keymapperorg/KeyMapper/issues/1054) Make it clearer why key map launcher shortcut can't be
+- [#1054](https://github.com/keymapperorg/KeyMapper/issues/1054) Make it clearer why key map
+ launcher shortcut can't be
created automatically.
-- [#1068](https://github.com/keymapperorg/KeyMapper/issues/1068) Make the confirmation dialog when leaving without
+- [#1068](https://github.com/keymapperorg/KeyMapper/issues/1068) Make the confirmation dialog when
+ leaving without
saving clearer.
## [2.4.3](https://github.com/sds100/KeyMapper/releases/tag/v2.4.3)
@@ -369,25 +444,33 @@ This release adds support for Android 14 and fixes some bugs associated with it.
### Bug fixes
-- [#1052](https://github.com/keymapperorg/KeyMapper/issues/1052) Crash when disabling accessibility service through the
+- [#1052](https://github.com/keymapperorg/KeyMapper/issues/1052) Crash when disabling accessibility
+ service through the
Key Mapper notification on Android Lollipop and Marshmallow.
-- [#1049](https://github.com/keymapperorg/KeyMapper/issues/1049) Enabling the accessibility service automatically with
+- [#1049](https://github.com/keymapperorg/KeyMapper/issues/1049) Enabling the accessibility service
+ automatically with
WRITE_SECURE_SETTINGS wouldn't work if no other accessibility features were enabled.
-- [#1044](https://github.com/keymapperorg/KeyMapper/issues/1044) On some devices there was a random crash in the
+- [#1044](https://github.com/keymapperorg/KeyMapper/issues/1044) On some devices there was a random
+ crash in the
notification listener service.
-- [#999](https://github.com/keymapperorg/KeyMapper/issues/999) Action to launch app has a 5-10 second delay when you're
+- [#999](https://github.com/keymapperorg/KeyMapper/issues/999) Action to launch app has a 5-10
+ second delay when you're
on your device's home screen.
-- [#1047](https://github.com/keymapperorg/KeyMapper/issues/1047) App NOT playing media constraint would be saved as
+- [#1047](https://github.com/keymapperorg/KeyMapper/issues/1047) App NOT playing media constraint
+ would be saved as
wrong constraint.
-- [#1043](https://github.com/keymapperorg/KeyMapper/issues/1043) Inputting key events with Shizuku didn't actually work
+- [#1043](https://github.com/keymapperorg/KeyMapper/issues/1043) Inputting key events with Shizuku
+ didn't actually work
on release builds. OOPS. 🤦
### Added
- [#1025](https://github.com/keymapperorg/KeyMapper/issues/1025) Support for Android 12L.
-- [#1016](https://github.com/keymapperorg/KeyMapper/issues/1016) Ability to never show the denied Do Not Disturb
+- [#1016](https://github.com/keymapperorg/KeyMapper/issues/1016) Ability to never show the denied Do
+ Not Disturb
permission errors if the device does not support those settings.
-- [#1042](https://github.com/keymapperorg/KeyMapper/issues/1042) Put date in the timestamp in the log.
+- [#1042](https://github.com/keymapperorg/KeyMapper/issues/1042) Put date in the timestamp in the
+ log.
## [2.4.2](https://github.com/sds100/KeyMapper/releases/tag/v2.4.2)
@@ -395,7 +478,8 @@ This release adds support for Android 14 and fixes some bugs associated with it.
### Bug fixes
-- [#1017](https://github.com/keymapperorg/KeyMapper/issues/1017) The app would go in an infinite loop saying "Using root
+- [#1017](https://github.com/keymapperorg/KeyMapper/issues/1017) The app would go in an infinite
+ loop saying "Using root
to grant WRITE_SECURE_SETTINGS permission" on screen and then eventually crashing.
## [2.4.1](https://github.com/sds100/KeyMapper/releases/tag/v2.4.1)
@@ -410,7 +494,8 @@ This release adds support for Android 14 and fixes some bugs associated with it.
- Crash if trying to grant permission with Shizuku but Key Mapper doesn't have Shizuku permission.
- [#1009](https://github.com/keymapperorg/KeyMapper/issues/1009) Crash when trying to edit actions.
-- [#1001](https://github.com/keymapperorg/KeyMapper/issues/1001) Crash when granting permission with Shizuku on Android
+- [#1001](https://github.com/keymapperorg/KeyMapper/issues/1001) Crash when granting permission with
+ Shizuku on Android
12+.
## [2.4.0](https://github.com/sds100/KeyMapper/releases/tag/v2.4.0)
@@ -423,42 +508,59 @@ See beta releases for bug fixes.
#### Important
-- [#748](https://github.com/keymapperorg/KeyMapper/issues/748) Android 12 and Material You support 🎨!
-- [#746](https://github.com/keymapperorg/KeyMapper/issues/746) Shizuku support for some features! You can use this
+- [#748](https://github.com/keymapperorg/KeyMapper/issues/748) Android 12 and Material You support
+ 🎨!
+- [#746](https://github.com/keymapperorg/KeyMapper/issues/746) Shizuku support for some features!
+ You can use this
instead of using a Key Mapper keyboard!
#### Actions
-- [#603](https://github.com/keymapperorg/KeyMapper/issues/603) You can now edit actions! You don't have to delete an
+- [#603](https://github.com/keymapperorg/KeyMapper/issues/603) You can now edit actions! You don't
+ have to delete an
action and completely reconfigure it.
- [#851](https://github.com/keymapperorg/KeyMapper/issues/850) Action to answer/end a phone call.
- [#704](https://github.com/keymapperorg/KeyMapper/issues/704) Action to dismiss notifications.
#### Constraints
-- [#851](https://github.com/keymapperorg/KeyMapper/issues/851) Constraints for when the device is ringing and in a phone
+- [#851](https://github.com/keymapperorg/KeyMapper/issues/851) Constraints for when the device is
+ ringing and in a phone
call.
-- [#811](https://github.com/keymapperorg/KeyMapper/issues/811) Constraint for when the device is locked.
-- [#776](https://github.com/keymapperorg/KeyMapper/issues/776) Constraint for when an input method is chosen.
-- [#598](https://github.com/keymapperorg/KeyMapper/issues/598) Constraint for any app (not) playing or a specific app
+- [#811](https://github.com/keymapperorg/KeyMapper/issues/811) Constraint for when the device is
+ locked.
+- [#776](https://github.com/keymapperorg/KeyMapper/issues/776) Constraint for when an input method
+ is chosen.
+- [#598](https://github.com/keymapperorg/KeyMapper/issues/598) Constraint for any app (not) playing
+ or a specific app
not playing media.
-- [#702](https://github.com/keymapperorg/KeyMapper/issues/702) WiFi on/off/connected/disconnected constraints.
+- [#702](https://github.com/keymapperorg/KeyMapper/issues/702) WiFi on/off/connected/disconnected
+ constraints.
- [#722](https://github.com/keymapperorg/KeyMapper/issues/722) Flashlight on/off constraint.
#### Other
-- [#911](https://github.com/keymapperorg/KeyMapper/issues/911) Detect camera button when screen is off.
-- [#780](https://github.com/keymapperorg/KeyMapper/issues/780) If the accessibility settings can't be found prompt the
+- [#911](https://github.com/keymapperorg/KeyMapper/issues/911) Detect camera button when screen is
+ off.
+- [#780](https://github.com/keymapperorg/KeyMapper/issues/780) If the accessibility settings can't
+ be found prompt the
user to follow an online guide to do it with ADB.
-- [#773](https://github.com/keymapperorg/KeyMapper/issues/773) Prompt for a message when the user reports a bug.
-- [#686](https://github.com/keymapperorg/KeyMapper/issues/686) Setting to switch to a different input method on input
+- [#773](https://github.com/keymapperorg/KeyMapper/issues/773) Prompt for a message when the user
+ reports a bug.
+- [#686](https://github.com/keymapperorg/KeyMapper/issues/686) Setting to switch to a different
+ input method on input
focus.
-- [#715](https://github.com/keymapperorg/KeyMapper/issues/715) Show a share button after a successful backup.
-- [#716](https://github.com/keymapperorg/KeyMapper/issues/716) Support for a Key Mapper compatible version of Hacker's
- Keyboard. Releases can be found here: https://github.com/keymapperorg/KeyMapperHackersKeyboard/releases
-- [#955](https://github.com/keymapperorg/KeyMapper/issues/955) You can now detect the Menu and Search button when the
+- [#715](https://github.com/keymapperorg/KeyMapper/issues/715) Show a share button after a
+ successful backup.
+- [#716](https://github.com/keymapperorg/KeyMapper/issues/716) Support for a Key Mapper compatible
+ version of Hacker's
+ Keyboard. Releases can be found
+ here: https://github.com/keymapperorg/KeyMapperHackersKeyboard/releases
+- [#955](https://github.com/keymapperorg/KeyMapper/issues/955) You can now detect the Menu and
+ Search button when the
screen is off. On some devices the Bixby button is detected by Key Mapper as the Menu button.
-- [#928](https://github.com/keymapperorg/KeyMapper/issues/928) You can now call Termux RUN_COMMAND intents because the
+- [#928](https://github.com/keymapperorg/KeyMapper/issues/928) You can now call Termux RUN_COMMAND
+ intents because the
necessary permission has been added to Key Mapper.
## [2.4.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v2.4.0-beta.02)
@@ -467,19 +569,25 @@ See beta releases for bug fixes.
### Bug fixes
-- [#946](https://github.com/keymapperorg/KeyMapper/issues/946) Choosing a Bluetooth device disconnected constraint would
+- [#946](https://github.com/keymapperorg/KeyMapper/issues/946) Choosing a Bluetooth device
+ disconnected constraint would
save it as a connected constraint.
-- [#981](https://github.com/keymapperorg/KeyMapper/issues/981) Trying to backup/restore would crash the app if no
+- [#981](https://github.com/keymapperorg/KeyMapper/issues/981) Trying to backup/restore would crash
+ the app if no
file-picker was installed.
-- [#965](https://github.com/keymapperorg/KeyMapper/issues/965) The app would crash if you had disabled key maps.
-- [#957](https://github.com/keymapperorg/KeyMapper/issues/957) The dialog to choose flags for an Intent action wouldn't
+- [#965](https://github.com/keymapperorg/KeyMapper/issues/965) The app would crash if you had
+ disabled key maps.
+- [#957](https://github.com/keymapperorg/KeyMapper/issues/957) The dialog to choose flags for an
+ Intent action wouldn't
show the flags that had already been picked.
### Added
-- [#955](https://github.com/keymapperorg/KeyMapper/issues/955) You can now detect the Menu and Search button when the
+- [#955](https://github.com/keymapperorg/KeyMapper/issues/955) You can now detect the Menu and
+ Search button when the
screen is off. On some devices the Bixby button is detected by Key Mapper as the Menu button.
-- [#928](https://github.com/keymapperorg/KeyMapper/issues/928) You can now call Termux RUN_COMMAND intents because the
+- [#928](https://github.com/keymapperorg/KeyMapper/issues/928) You can now call Termux RUN_COMMAND
+ intents because the
necessary permission has been added to Key Mapper.
## [2.4.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v2.4.0-beta.01)
@@ -488,58 +596,80 @@ See beta releases for bug fixes.
### Changes
-- [#815](https://github.com/keymapperorg/KeyMapper/issues/815) Always show the button to pick a package when configuring
+- [#815](https://github.com/keymapperorg/KeyMapper/issues/815) Always show the button to pick a
+ package when configuring
an intent action.
-- [#749](https://github.com/keymapperorg/KeyMapper/issues/749) Remove do not disturb app intro slide.
+- [#749](https://github.com/keymapperorg/KeyMapper/issues/749) Remove do not disturb app intro
+ slide.
- [#750](https://github.com/keymapperorg/KeyMapper/issues/750) Redesign the About screen.
-- [#747](https://github.com/keymapperorg/KeyMapper/issues/747) Reorganise the Settings screen so it is less cluttered.
+- [#747](https://github.com/keymapperorg/KeyMapper/issues/747) Reorganise the Settings screen so it
+ is less cluttered.
### Added
#### Important
-- [#748](https://github.com/keymapperorg/KeyMapper/issues/748) Android 12 and Material You support 🎨!
-- [#746](https://github.com/keymapperorg/KeyMapper/issues/746) Shizuku support for some features! You can use this
+- [#748](https://github.com/keymapperorg/KeyMapper/issues/748) Android 12 and Material You support
+ 🎨!
+- [#746](https://github.com/keymapperorg/KeyMapper/issues/746) Shizuku support for some features!
+ You can use this
instead of using a Key Mapper keyboard!
#### Actions
-- [#603](https://github.com/keymapperorg/KeyMapper/issues/603) You can now edit actions! You don't have to delete an
+- [#603](https://github.com/keymapperorg/KeyMapper/issues/603) You can now edit actions! You don't
+ have to delete an
action and completely reconfigure it.
- [#851](https://github.com/keymapperorg/KeyMapper/issues/850) Action to answer/end a phone call.
- [#704](https://github.com/keymapperorg/KeyMapper/issues/704) Action to dismiss notifications.
#### Constraints
-- [#851](https://github.com/keymapperorg/KeyMapper/issues/851) Constraints for when the device is ringing and in a phone
+- [#851](https://github.com/keymapperorg/KeyMapper/issues/851) Constraints for when the device is
+ ringing and in a phone
call.
-- [#811](https://github.com/keymapperorg/KeyMapper/issues/811) Constraint for when the device is locked.
-- [#776](https://github.com/keymapperorg/KeyMapper/issues/776) Constraint for when an input method is chosen.
-- [#598](https://github.com/keymapperorg/KeyMapper/issues/598) Constraint for any app (not) playing or a specific app
+- [#811](https://github.com/keymapperorg/KeyMapper/issues/811) Constraint for when the device is
+ locked.
+- [#776](https://github.com/keymapperorg/KeyMapper/issues/776) Constraint for when an input method
+ is chosen.
+- [#598](https://github.com/keymapperorg/KeyMapper/issues/598) Constraint for any app (not) playing
+ or a specific app
not playing media.
-- [#702](https://github.com/keymapperorg/KeyMapper/issues/702) WiFi on/off/connected/disconnected constraints.
+- [#702](https://github.com/keymapperorg/KeyMapper/issues/702) WiFi on/off/connected/disconnected
+ constraints.
- [#722](https://github.com/keymapperorg/KeyMapper/issues/722) Flashlight on/off constraint.
#### Other
-- [#911](https://github.com/keymapperorg/KeyMapper/issues/911) Detect camera button when screen is off.
-- [#780](https://github.com/keymapperorg/KeyMapper/issues/780) If the accessibility settings can't be found prompt the
+- [#911](https://github.com/keymapperorg/KeyMapper/issues/911) Detect camera button when screen is
+ off.
+- [#780](https://github.com/keymapperorg/KeyMapper/issues/780) If the accessibility settings can't
+ be found prompt the
user to follow an online guide to do it with ADB.
-- [#773](https://github.com/keymapperorg/KeyMapper/issues/773) Prompt for a message when the user reports a bug.
-- [#686](https://github.com/keymapperorg/KeyMapper/issues/686) Setting to switch to a different input method on input
+- [#773](https://github.com/keymapperorg/KeyMapper/issues/773) Prompt for a message when the user
+ reports a bug.
+- [#686](https://github.com/keymapperorg/KeyMapper/issues/686) Setting to switch to a different
+ input method on input
focus.
-- [#715](https://github.com/keymapperorg/KeyMapper/issues/715) Show a share button after a successful backup.
-- [#716](https://github.com/keymapperorg/KeyMapper/issues/716) Support for a Key Mapper compatible version of Hacker's
- Keyboard. Releases can be found here: https://github.com/keymapperorg/KeyMapperHackersKeyboard/releases
+- [#715](https://github.com/keymapperorg/KeyMapper/issues/715) Show a share button after a
+ successful backup.
+- [#716](https://github.com/keymapperorg/KeyMapper/issues/716) Support for a Key Mapper compatible
+ version of Hacker's
+ Keyboard. Releases can be found
+ here: https://github.com/keymapperorg/KeyMapperHackersKeyboard/releases
### Bug Fixes
-- [#794](https://github.com/keymapperorg/KeyMapper/issues/794) Only list apps that can be launched when creating an open
+- [#794](https://github.com/keymapperorg/KeyMapper/issues/794) Only list apps that can be launched
+ when creating an open
app action.
-- [#823](https://github.com/keymapperorg/KeyMapper/issues/823) Can't choose an app when creating media action.
-- [#756](https://github.com/keymapperorg/KeyMapper/issues/756) On slighter smaller screens show split layout when
+- [#823](https://github.com/keymapperorg/KeyMapper/issues/823) Can't choose an app when creating
+ media action.
+- [#756](https://github.com/keymapperorg/KeyMapper/issues/756) On slighter smaller screens show
+ split layout when
configuring a mapping.
-- [#739](https://github.com/keymapperorg/KeyMapper/issues/739) Long press triggers ignore constraints.
+- [#739](https://github.com/keymapperorg/KeyMapper/issues/739) Long press triggers ignore
+ constraints.
## [2.3.3](https://github.com/sds100/KeyMapper/releases/tag/v2.3.3)
@@ -549,7 +679,8 @@ See beta releases for bug fixes.
### Bug fixes
-- [#893](https://github.com/sds100/KeyMapper/issues/893) Creating intent actions with a boolean extra didn't work.
+- [#893](https://github.com/sds100/KeyMapper/issues/893) Creating intent actions with a boolean
+ extra didn't work.
- [#885](https://github.com/keymapperorg/KeyMapper/issues/885) F-Droid build failed.
- [#894](https://github.com/keymapperorg/KeyMapper/issues/894) Links to documentation website broke.
- [#904](https://github.com/keymapperorg/KeyMapper/issues/904) Fix string.
@@ -560,42 +691,59 @@ See beta releases for bug fixes.
### Changes
-- [#828](https://github.com/sds100/KeyMapper/issues/828) Rename the "Android 11 workaround" setting to be more clear
+- [#828](https://github.com/sds100/KeyMapper/issues/828) Rename the "Android 11 workaround" setting
+ to be more clear
what it does.
-- [#859](https://github.com/sds100/KeyMapper/issues/859) Rename the "trigger from other apps" trigger option to be more
+- [#859](https://github.com/sds100/KeyMapper/issues/859) Rename the "trigger from other apps"
+ trigger option to be more
clear what it does.
-- [#753](https://github.com/sds100/KeyMapper/issues/753) Automatically add the "do not remap" trigger key option when
- remapping a modifier key. This will make sure the modifier key can still behave like a normal modifier key.
+- [#753](https://github.com/sds100/KeyMapper/issues/753) Automatically add the "do not remap"
+ trigger key option when
+ remapping a modifier key. This will make sure the modifier key can still behave like a normal
+ modifier key.
### Added
-- [#814](https://github.com/sds100/KeyMapper/issues/814) Show system dialog to remove Key Mapper from battery
+- [#814](https://github.com/sds100/KeyMapper/issues/814) Show system dialog to remove Key Mapper
+ from battery
optimisation so the user doesn't have to dig through their device sittings.
### Bug Fixes
- [#810](https://github.com/sds100/KeyMapper/issues/810) Intent actions didn't work
-- [#789](https://github.com/sds100/KeyMapper/issues/789) Try to fix Key Mapper saying that the accessibility service has
+- [#789](https://github.com/sds100/KeyMapper/issues/789) Try to fix Key Mapper saying that the
+ accessibility service has
crashed even if it hasn't.
-- [#829](https://github.com/sds100/KeyMapper/issues/829) Try to fix Key Mapper being listed as incompatible on Google
+- [#829](https://github.com/sds100/KeyMapper/issues/829) Try to fix Key Mapper being listed as
+ incompatible on Google
Play for some devices.
-- [#854](https://github.com/sds100/KeyMapper/issues/854) The toggle airplane mode action didn't work.
+- [#854](https://github.com/sds100/KeyMapper/issues/854) The toggle airplane mode action didn't
+ work.
## [2.3.1](https://github.com/sds100/KeyMapper/releases/tag/v2.3.1)
#### 02 October 2021
### Changes
-- [#772](https://github.com/sds100/KeyMapper/issues/772) Remapping game controllers should work automatically in games now. You no longer have to manually set the device of a key event action to be the game controller.
+
+- [#772](https://github.com/sds100/KeyMapper/issues/772) Remapping game controllers should work
+ automatically in games now. You no longer have to manually set the device of a key event action to
+ be the game controller.
### Bug Fixes
+
- Try to fix a lot of random crashes on some devices.
-- [#771](https://github.com/sds100/KeyMapper/issues/771) Don't show a "failed to find accessibility node" toast message when the open menu action fails.
--
-- [#775](https://github.com/sds100/KeyMapper/issues/775) The options for key maps would sometimes sporadically change when navigating the configuration screen.
--
+- [#771](https://github.com/sds100/KeyMapper/issues/771) Don't show a "failed to find accessibility
+ node" toast message when the open menu action fails.
+-
+- [#775](https://github.com/sds100/KeyMapper/issues/775) The options for key maps would sometimes
+ sporadically change when navigating the configuration screen.
+-
+
### Removed
-- Option to give feedback by emailing the developer. The number of emails was overwhelming and most of them were not constructive at all.
+
+- Option to give feedback by emailing the developer. The number of emails was overwhelming and most
+ of them were not constructive at all.
## [2.3.0](https://github.com/sds100/KeyMapper/releases/tag/v2.3.0)
@@ -604,6 +752,7 @@ These are all the changes from 2.2.0.
#### 27 August 2021
### Added
+
- 🎉 A new website with a tutorial! 🎉 [docs.keymapper.club](https://docs.keymapper.club)
- Action to broadcast intent, start activity and start service. #112
@@ -612,9 +761,12 @@ These are all the changes from 2.2.0.
- Action to call a phone number. #516
- Action to play a sound.
-- A workaround for the Android 11 bug that sets the language of external keyboards to English-US when an accessibility service is enabled. #618 Read the guide here https://docs.keymapper.club/redirects/android-11-device-id-bug-work-around
+- A workaround for the Android 11 bug that sets the language of external keyboards to English-US
+ when an accessibility service is enabled. #618 Read the guide
+ here https://docs.keymapper.club/redirects/android-11-device-id-bug-work-around
-- Prompt the user to read the quick start guide on the website the first time the app is opened. #544
+- Prompt the user to read the quick start guide on the website the first time the app is opened.
+ #544
- Links to a relevant online guide in each screen in the app. #539
- Option in key event action to input the key event through the shell. #559
- Splash screen #561
@@ -623,7 +775,7 @@ These are all the changes from 2.2.0.
- Ability to change the input method with the accessibility service on Android 11+. #619
- Make it clearer that selecting a screenshot to set up a tap coordinate action is optional. #632
- Show a prompt to install the Key Mapper GUI Keyboard when a key event action is created. #645
-- Back up default key map settings in back ups. #659
+- Back up default key map settings in back ups. #659
- Warnings when the accessibility service is turned on but isn't actually running. #643
- Show a message at the top of the home screen when mappings are paused. #642
- A caution message to avoid locking the user when using screen pinning mode. #602
@@ -634,15 +786,19 @@ These are all the changes from 2.2.0.
- A new Key Mapper keyboard that is designed for Android TV. #493
- An Intent API to pause/resume key maps. #668
- Allow Key Mapper to be launched from the Android TV launcher. #695
-- Make it much easier to report bugs and turn off aggressive app killing. #728 There is now a button in the home screen menu to send a bug report and the user is now prompted to read dontkillmyapp.com when the accessibility service crashes.
+- Make it much easier to report bugs and turn off aggressive app killing. #728 There is now a button
+ in the home screen menu to send a bug report and the user is now prompted to read
+ dontkillmyapp.com when the accessibility service crashes.
- Support for repeat until limit reached action option in fingerprint gesture maps. #710
- Polish translations.
-- Czech translations.
+- Czech translations.
### Changed
+
- Move action option to show a toast message to the same place as the vibrate option. #565
-- Replace setting to choose Bluetooth device in settings with setting to choose any input device. #620
+- Replace setting to choose Bluetooth device in settings with setting to choose any input device.
+ #620
- Rename 'action count' option to 'how many times'. #611
- Move option to show the volume ui for an action to when the action is created. #639
- Tapping the pause/resume key maps notification now opens Key Mapper. #665
@@ -650,11 +806,14 @@ These are all the changes from 2.2.0.
- Alerts at the top of the home screen have been simplified.
### Removed
+
- Dex slide in the app intro because it didn't work. #646
- Buttons to enable all and disable all key maps in the home screen menu. #647
- Support for Android KitKat 4.4 and older. #627
-- Ability to view changelog, license and privacy policy in an in-app dialog. They now open a link in the browser. #648
-- Alerts at the top of the home screen to enable a Key Mapper keyboard, grant WRITE_SECURE_SETTINGS and grant Do not Disturb mode.
+- Ability to view changelog, license and privacy policy in an in-app dialog. They now open a link in
+ the browser. #648
+- Alerts at the top of the home screen to enable a Key Mapper keyboard, grant WRITE_SECURE_SETTINGS
+ and grant Do not Disturb mode.
### Bug Fixes
@@ -665,13 +824,16 @@ See the 2.3.0 Beta releases below.
#### 15 August 2021
### Changes
+
- Never show the "key mapper has crashed" dialog automatically since this causes a lot of confusion.
- Prompt the user to restart the accessibility service rather than report a bug. #736
### Added
+
- Polish translations
### Bug Fixes
+
- Just opening any options dialog might modify the options.
- Various NPEs
- Crash when showing on back pressed dialog
@@ -682,24 +844,31 @@ See the 2.3.0 Beta releases below.
#### 19 July 2021
### Changes
-- Don't show "key mapper has crashed" dialog the first time the app detects it has been crashed after being opened.
+
+- Don't show "key mapper has crashed" dialog the first time the app detects it has been crashed
+ after being opened.
### Bug Fixes
- Write Secure Settings section in settings is enabled even if permission is revoked. #732
- Many random crashes. #744, #743, #742, #741, #740, #738, #737
- Don't crash when restoring back ups without a sounds folder in it.
-- Don't restore a back up from a newer version of key mapper to prevent the app crashing when reading the restored data.
+- Don't restore a back up from a newer version of key mapper to prevent the app crashing when
+ reading the restored data.
## [2.3.0 Beta 3](https://github.com/sds100/KeyMapper/releases/tag/v2.3.0-beta.03)
#### 06 July 2021
### Added
-- Make it much easier to report bugs and turn off aggressive app killing. #728 There is now a button in the home screen menu to send a bug report and the user is now prompted to read dontkillmyapp.com when the accessibility service crashes.
+
+- Make it much easier to report bugs and turn off aggressive app killing. #728 There is now a button
+ in the home screen menu to send a bug report and the user is now prompted to read
+ dontkillmyapp.com when the accessibility service crashes.
- Action to play a sound
### Bug Fixes
+
- Close notification drawer after the notification has been pressed. #719
- Crash if couldn't find input device. #730
- Crash if couldn't find chosen input method. #731
@@ -714,29 +883,37 @@ See the 2.3.0 Beta releases below.
#### 25 June 2021
### Added
+
- Support for repeat until limit reached action option in fingerprint gesture maps. #710
### Bug Fixes
+
- Crash on start up on some devices. #706
- Notification advertising fingerprint gesture maps is shown on every update #709
-- Key map launcher shortcut repeats indefinitely when triggered if repeat until released is chosen. #707
+- Key map launcher shortcut repeats indefinitely when triggered if repeat until released is chosen.
+ #707
-## [2.3.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v2.3.0-beta.01)
+## [2.3.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v2.3.0-beta.01)
#### 22 June 2021
-- A huge rewrite of the code which should make the app more stable and easier to add features in the future.
+- A huge rewrite of the code which should make the app more stable and easier to add features in the
+ future.
### Added
+
- 🎉 A new website with a tutorial! 🎉 [docs.keymapper.club](https://docs.keymapper.club)
- Action to broadcast intent, start activity and start service. #112
- Action to show the input method picker by using the Key Mapper keyboard. #531
- Action to toggle the notification drawer and the quick settings drawer. #242
- Action to call a phone number. #516
-- A workaround for the Android 11 bug that sets the language of external keyboards to English-US when an accessibility service is enabled. #618 Read the guide here https://docs.keymapper.club/redirects/android-11-device-id-bug-work-around
+- A workaround for the Android 11 bug that sets the language of external keyboards to English-US
+ when an accessibility service is enabled. #618 Read the guide
+ here https://docs.keymapper.club/redirects/android-11-device-id-bug-work-around
-- Prompt the user to read the quick start guide on the website the first time the app is opened. #544
+- Prompt the user to read the quick start guide on the website the first time the app is opened.
+ #544
- Links to a relevant online guide in each screen in the app. #539
- Option in key event action to input the key event through the shell. #559
- Splash screen #561
@@ -745,7 +922,7 @@ See the 2.3.0 Beta releases below.
- Ability to change the input method with the accessibility service on Android 11+. #619
- Make it clearer that selecting a screenshot to set up a tap coordinate action is optional. #632
- Show a prompt to install the Key Mapper GUI Keyboard when a key event action is created. #645
-- Back up default key map settings in back ups. #659
+- Back up default key map settings in back ups. #659
- Warnings when the accessibility service is turned on but isn't actually running. #643
- Show a message at the top of the home screen when mappings are paused. #642
- A caution message to avoid locking the user when using screen pinning mode. #602
@@ -758,8 +935,10 @@ See the 2.3.0 Beta releases below.
- Allow Key Mapper to be launched from the Android TV launcher. #695
### Changed
+
- Move action option to show a toast message to the same place as the vibrate option. #565
-- Replace setting to choose Bluetooth device in settings with setting to choose any input device. #620
+- Replace setting to choose Bluetooth device in settings with setting to choose any input device.
+ #620
- Rename 'action count' option to 'how many times'. #611
- Move option to show the volume ui for an action to when the action is created. #639
- Tapping the pause/resume key maps notification now opens Key Mapper. #665
@@ -767,69 +946,89 @@ See the 2.3.0 Beta releases below.
- Alerts at the top of the home screen have been simplified.
### Removed
+
- Dex slide in the app intro because it didn't work. #646
- Buttons to enable all and disable all key maps in the home screen menu. #647
- Support for Android KitKat 4.4 and older. #627
-- Ability to view changelog, license and privacy policy in an in-app dialog. They now open a link in the browser. #648
-- Alerts at the top of the home screen to enable a Key Mapper keyboard, grant WRITE_SECURE_SETTINGS and grant Do not Disturb mode.
+- Ability to view changelog, license and privacy policy in an in-app dialog. They now open a link in
+ the browser. #648
+- Alerts at the top of the home screen to enable a Key Mapper keyboard, grant WRITE_SECURE_SETTINGS
+ and grant Do not Disturb mode.
### Bug fixes
+
- Fix jank #549
- Fix text consistency #543
-- A parallel trigger which contains another parallel trigger after the first key should cancel the other. #571
+- A parallel trigger which contains another parallel trigger after the first key should cancel the
+ other. #571
- Actions go off screen for key maps on the home screen. #613
-- Remove uses of Android framework strings for dialog buttons. #650
+- Remove uses of Android framework strings for dialog buttons. #650
- Trigger key click type sometimes resets to short press. #615
-- Wrong device id is used when performing key event actions and there are multiple devices with the same descriptor. #637
+- Wrong device id is used when performing key event actions and there are multiple devices with the
+ same descriptor. #637
- Trigger key isn't imitated after a failed double press. #606
-- Actions don't start repeating on a failed long press or failed double press. #626
+- Actions don't start repeating on a failed long press or failed double press. #626
- Crash when modifying a huge number of key maps. #641
- Home menu is chopped off on screens with small height. #582
- Crash when double pressing button to open action or trigger key options. #600
- Some action options disappear when adding a new trigger key. #594
-- An action can continue to repeat even when the trigger is released if delay until next action is not 0. #662
+- An action can continue to repeat even when the trigger is released if delay until next action is
+ not 0. #662
- A lot of input latency when using a lot of constraints. #599
-- Trigger button isn't imitated when a short press trigger with multiple keys fails to be triggered. #664
+- Trigger button isn't imitated when a short press trigger with multiple keys fails to be triggered.
+ #664
- Overlapping triggers. #653
-## [2.2.0](https://github.com/sds100/KeyMapper/releases/tag/v2.2.0)
+## [2.2.0](https://github.com/sds100/KeyMapper/releases/tag/v2.2.0)
#### 07 March 2021
This sums up all the changes for 2.2
### Added
-- Remap fingerprint gestures! #378 Android 8.0+ and only on devices which support them. Even devices with the setting to swipe down for notifications might not support this! The dev can't do anything about this.
+
+- Remap fingerprint gestures! #378 Android 8.0+ and only on devices which support them. Even devices
+ with the setting to swipe down for notifications might not support this! The dev can't do anything
+ about this.
- Widget/shortcut to launch actions. #459
-- Setting to show the first 5 digits of input devices so devices with the same name can be differentiated in Key Mapper lists. #470
-- Show a warning at the top of the homescreen if the user hasn't disabled battery optimisation for Key Mapper. #496
+- Setting to show the first 5 digits of input devices so devices with the same name can be
+ differentiated in Key Mapper lists. #470
+- Show a warning at the top of the homescreen if the user hasn't disabled battery optimisation for
+ Key Mapper. #496
- Action option to hold down until the trigger is pressed again. #479
- Action option to change the delay before the next action in the list. #476
- Orientation constraint. #505
- Key Event action option to pretend that the Key Event came from a particular device. #509
- Use duplicates of the same key in a sequence trigger. #513
- Show the fingerprint gesture intro slide when updating to 2.2 #545
-- Show a silent notification, which advertises the remapping fingerprint gesture feature, when the user updates to 2.2 #546
+- Show a silent notification, which advertises the remapping fingerprint gesture feature, when the
+ user updates to 2.2 #546
- Trigger key maps from an Intent #490
- Prompt the user to go to https://dontkillmyapp.com when they first setup the app.
- Add Fdroid link to the Key Mapper GUI Keyboard ad. #524
### BREAKING CHANGES
-- Key Mapper action shortcuts work completely differently. See https://docs.keymapper.club/user-guide/triggers/#trigger-from-other-apps-230
+
+- Key Mapper action shortcuts work completely differently.
+ See https://docs.keymapper.club/user-guide/triggers/#trigger-from-other-apps-230
### Changes
+
- No max limit for sliders (except in settings). #458
- The app intro slides will show feedback if the steps have been done correctly.
### Removed
+
- XDA Labs links because it has been shut down.
### Bug Fixes
+
- Save and restore state for all view models. #519
- Use View Binding in fragments properly. This should stop random crashes for some users. #518
- Hold Down action option doesn't work for long press triggers. #504
-- A trigger for a specific device can still be detected if the same buttons on another device are pressed. #523
+- A trigger for a specific device can still be detected if the same buttons on another device are
+ pressed. #523
- Fix layout of the trigger fragment on some screen sizes so that some things aren't cut off. #522
- Remapping modifier keys to the same key didn't work as expected. #563
- Parallel triggers which contained another parallel trigger didn't cancel the other. #571
@@ -843,38 +1042,49 @@ This sums up all the changes for 2.2
- The error message for an app being disabled was the wrong one.
- Actions to open Android TV apps didn't work #503
- The app list didn't show Android TV-only apps. #487
-- Settings for repeat rate and delay until repeat didn't match their names when configuring an action.
+- Settings for repeat rate and delay until repeat didn't match their names when configuring an
+ action.
- Text would move up/down when sliding between slides in the app intro. #540
- Icon for "specific app playing media" constraint had the wrong tint. #535
- Limit Media actions to Android 4.4 KitKat+ because they don't work on older versions.
-- Up Key Event was sent from all keymaps with the "hold down" action option regardless of whether the trigger was released. #533
+- Up Key Event was sent from all keymaps with the "hold down" action option regardless of whether
+ the trigger was released. #533
- Testing actions didn't work.
- Scroll position was lost when reloading the key map list.
- Try to fix random crashes when navigating.
- Duplicating key maps didn't work.
-## [2.2.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v2.2.0-beta.2)
+## [2.2.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v2.2.0-beta.2)
#### 29 Jan 2021
### Added
-- Remap fingerprint gestures! #378 Android 8.0+ and only on devices which support them. Even devices with the setting to swipe down for notifications might not support this! The dev can't do anything about this.
+
+- Remap fingerprint gestures! #378 Android 8.0+ and only on devices which support them. Even devices
+ with the setting to swipe down for notifications might not support this! The dev can't do anything
+ about this.
- Show the fingerprint gesture intro slide when updating to 2.2 #545
-- Show a silent notification, which advertises the remapping fingerprint gesture feature, when the user updates to 2.2 #546
+- Show a silent notification, which advertises the remapping fingerprint gesture feature, when the
+ user updates to 2.2 #546
- Trigger key maps from an Intent #490
- Prompt the user to go to https://dontkillmyapp.com when they first setup the app.
- Add Fdroid link to the Key Mapper GUI Keyboard ad. #524
### BREAKING CHANGES
-- Key Mapper action shortcuts work completely differently. See https://docs.keymapper.club/user-guide/triggers/#trigger-from-other-apps-230
+
+- Key Mapper action shortcuts work completely differently.
+ See https://docs.keymapper.club/user-guide/triggers/#trigger-from-other-apps-230
### Changes
+
- The app intro slides will show feedback if the steps have been done correctly.
### Removed
+
- XDA Labs links because it has been shut down.
### Bug Fixes
+
- Remapping modifier keys to the same key didn't work as expected. #563
- Parallel triggers which contained another parallel trigger didn't cancel the other. #571
- Don't allow screen on/off constraints for fingerprint gestures #570
@@ -887,26 +1097,33 @@ This sums up all the changes for 2.2
- The error message for an app being disabled was the wrong one.
- Actions to open Android TV apps didn't work #503
- The app list didn't show Android TV-only apps. #487
-- Settings for repeat rate and delay until repeat didn't match their names when configuring an action.
+- Settings for repeat rate and delay until repeat didn't match their names when configuring an
+ action.
- Text would move up/down when sliding between slides in the app intro. #540
- Icon for "specific app playing media" constraint had the wrong tint. #535
- Limit Media actions to Android 4.4 KitKat+ because they don't work on older versions.
-- Up Key Event was sent from all keymaps with the "hold down" action option regardless of whether the trigger was released. #533
+- Up Key Event was sent from all keymaps with the "hold down" action option regardless of whether
+ the trigger was released. #533
- Testing actions didn't work.
- Scroll position was lost when reloading the key map list.
- Try to fix random crashes when navigating.
- Duplicating key maps didn't work.
-## [2.2.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v2.2.0-beta.1)
+## [2.2.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v2.2.0-beta.1)
#### 30 Dec 2020
### Added
-- Remap fingerprint gestures! #378 Android 8.0+ and only on devices which support them. Even devices with the setting to swipe down for notifications might not support this! The dev can't do anything about this.
+
+- Remap fingerprint gestures! #378 Android 8.0+ and only on devices which support them. Even devices
+ with the setting to swipe down for notifications might not support this! The dev can't do anything
+ about this.
- Widget/shortcut to launch actions. #459
-- Setting to show the first 5 digits of input devices so devices with the same name can be differentiated in Key Mapper lists. #470
-- Show a warning at the top of the homescreen if the user hasn't disabled battery optimisation for Key Mapper. #496
+- Setting to show the first 5 digits of input devices so devices with the same name can be
+ differentiated in Key Mapper lists. #470
+- Show a warning at the top of the homescreen if the user hasn't disabled battery optimisation for
+ Key Mapper. #496
- Action option to hold down until the trigger is pressed again. #479
- Action option to change the delay before the next action in the list. #476
- Orientation constraint. #505
@@ -916,16 +1133,19 @@ This sums up all the changes for 2.2
- Hold down repeatedly if repeat and hold down are enabled. #500
### Changes
+
- No max limit for sliders (except in settings). #458
### Bug Fixes
+
- Save and restore state for all view models. #519
- Use View Binding in fragments properly. This should stop random crashes for some users. #518
- Hold Down action option doesn't work for long press triggers. #504
-- A trigger for a specific device can still be detected if the same buttons on another device are pressed. #523
+- A trigger for a specific device can still be detected if the same buttons on another device are
+ pressed. #523
- Fix layout of the trigger fragment on some screen sizes so that some things aren't cut off. #522
-## [2.1.0](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0)
+## [2.1.0](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0)
#### 23 Nov 2020
@@ -940,21 +1160,26 @@ This summarises the changes since 2.0.2.
- Action to create Key Event with optional modifiers.
- Action to select word at cursor.
- Action to toggle the screen on and off.
-- Action to tap a coordinate on the screen. The user and the app can NOT touch the screen at the same time. This is a
+- Action to tap a coordinate on the screen. The user and the app can NOT touch the screen at the
+ same time. This is a
limitation in Android.
- Action to double press recents to go to last app.
-- Dismiss button to the notification that pauses/resumes keymaps. It will be shown again when the app is opened.
+- Dismiss button to the notification that pauses/resumes keymaps. It will be shown again when the
+ app is opened.
- Show a warning dialog when leaving the screen to configure a keymap without saving.
-- Keymaps can have multiple of the same action. There is now a slider in the action options called "Action Count".
+- Keymaps can have multiple of the same action. There is now a slider in the action options called "
+ Action Count".
- Can detect the headset button when the screen is off.
- Prompt the user to reboot their device if they fail to record a trigger 2 times in a row.
- Show a toast after using the Screenshot (ROOT) action.
- Consuming the key event is optional for each key.
### Changed
+
- Don't hide the Repeat option if there is no trigger.
### Fixes
+
- Caps Lock key still caps lock when remapped.
- When making a parallel trigger, the keys don't all have the same click type.
- Dragging trigger keys by the remove button would cause a crash
@@ -962,7 +1187,8 @@ This summarises the changes since 2.0.2.
- The Menu (ROOT) action was slow
- show a toast if there is an IOException when detecting buttons when the screen is off.
- Remapping modifier keys to modifier keys doesn't work as expected.
-- the Screenshot (ROOT) action didn't create the Pictures and Screenshots directories. Therefore, it didn't save the screenshot.
+- the Screenshot (ROOT) action didn't create the Pictures and Screenshots directories. Therefore, it
+ didn't save the screenshot.
- Hold Down action option didn't work for long-press triggers.
- Opening a keymap with a long-press parallel trigger would set it to short press.
- Crash if a modifier key trigger is not mapped to a Key Event action.
@@ -970,9 +1196,10 @@ This summarises the changes since 2.0.2.
- Attempt to fix the problem of the accessibility service being enabled but broken on some devices.
- Typo in the dialog message prompting the user to reboot.
- The dialog prompting the user to reboot would show at the wrong time.
-- Switch to a new App Intro library. Hopefully it is more stable because the old library was crashing for many users.
+- Switch to a new App Intro library. Hopefully it is more stable because the old library was
+ crashing for many users.
-## [2.1.0 Beta 4](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0-beta.4)
+## [2.1.0 Beta 4](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0-beta.4)
#### 14 Nov 2020
@@ -982,7 +1209,7 @@ This summarises the changes since 2.0.2.
- Consuming the key event is optional for each key.
- There was no search button under the "Other" tab when choosing an action.
-## [2.1.0 Beta 3](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0-beta.3)
+## [2.1.0 Beta 3](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0-beta.3)
#### 23 Oct 2020
@@ -993,35 +1220,42 @@ This summarises the changes since 2.0.2.
- Attempt to fix the problem of the accessibility service being enabled but broken on some devices.
- Typo in the dialog message prompting the user to reboot.
- The dialog prompting the user to reboot would show at the wrong time.
-- Switch to a new App Intro library. Hopefully it is more stable because the old library was crashing for many users.
+- Switch to a new App Intro library. Hopefully it is more stable because the old library was
+ crashing for many users.
-## [2.1.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0-beta.2)
+## [2.1.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0-beta.2)
#### 21 Oct 2020
### Added
+
- Prompt the user to reboot their device if they fail to record a trigger 2 times in a row.
- Show a toast after using the Screenshot (ROOT) action.
### Bug Fixes
+
- Dragging trigger keys by the remove button would cause a crash
- stop recording if the user leaves the Trigger fragment
- The Menu (ROOT) action was slow
-- Entering an invalid integer into the keycode box when creating a Key Event action would cause a crash.
+- Entering an invalid integer into the keycode box when creating a Key Event action would cause a
+ crash.
- show a toast if there is an IOException when detecting buttons when the screen is off.
- Remapping modifier keys to modifier keys doesn't work as expected.
-- the Screenshot (ROOT) action didn't create the Pictures and Screenshots directories. Therefore, it didn't save the screenshot.
+- the Screenshot (ROOT) action didn't create the Pictures and Screenshots directories. Therefore, it
+ didn't save the screenshot.
- Hold Down action option didn't work for long-press triggers.
- Opening a keymap with a long-press parallel trigger would set it to short press.
-- JSON files are sometimes greyed out when picking a file to restore. All file types are now shown because Android doens't have a mimetype for JSON files.
+- JSON files are sometimes greyed out when picking a file to restore. All file types are now shown
+ because Android doens't have a mimetype for JSON files.
- Crash if a modifier key trigger is not mapped to a Key Event action.
- Potential crash when showing keymaps on the homescreen.
-## [2.1.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0-beta.1)
+## [2.1.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v2.1.0-beta.1)
#### 29 Sept 2020
### Added
+
- Support for a proper keyboard. Install the Key Mapper GUI Keyboard.
- Support for Android 11.
- Backup/Restore keymaps.
@@ -1030,39 +1264,49 @@ This summarises the changes since 2.0.2.
- Action to create Key Event with optional modifiers.
- Action to select word at cursor.
- Action to toggle the screen on and off.
-- Action to tap a coordinate on the screen. The user and the app can NOT touch the screen at the same time. This is a
+- Action to tap a coordinate on the screen. The user and the app can NOT touch the screen at the
+ same time. This is a
limitation in Android.
- Action to double press recents to go to last app.
-- Dismiss button to the notification that pauses/resumes keymaps. It will be shown again when the app is opened.
+- Dismiss button to the notification that pauses/resumes keymaps. It will be shown again when the
+ app is opened.
- Show a warning dialog when leaving the screen to configure a keymap without saving.
-- Keymaps can have multiple of the same action. There is now a slider in the action options called "Action Count".
+- Keymaps can have multiple of the same action. There is now a slider in the action options called "
+ Action Count".
- Can detect the headset button when the screen is off.
- Option to not override the default behavior of the trigger.
### Changed
+
- Don't hide the Repeat option if there is no trigger.
### Fixes
+
- Caps Lock key still caps lock when remapped.
- When making a parallel trigger, the keys don't all have the same click type.
-## [2.0.2](https://github.com/sds100/KeyMapper/releases/tag/v2.0.2)
+## [2.0.2](https://github.com/sds100/KeyMapper/releases/tag/v2.0.2)
#### 31 Aug 2020
### Bug Fixes
+
- Fixed many crashes throughout the app. See the commit history for more detail.
### Changes
-- Make the functionality to fix actions by pressing on them more discoverable. The top of the keymap on the homescreen will show "Tap actions to fix!" and the broken actions have a red tint.
-## [2.0.1](https://github.com/sds100/KeyMapper/releases/tag/v2.0.1)
+- Make the functionality to fix actions by pressing on them more discoverable. The top of the keymap
+ on the homescreen will show "Tap actions to fix!" and the broken actions have a red tint.
+
+## [2.0.1](https://github.com/sds100/KeyMapper/releases/tag/v2.0.1)
#### 16 Aug 2020
### Bug Fixes
+
- Choosing app shortcut actions didn't work
-- Remapping the Home and Recents buttons wouldn't stop them from doing their default Home/Recents actions.
+- Remapping the Home and Recents buttons wouldn't stop them from doing their default Home/Recents
+ actions.
- All titles for flashlight actions are the same.
- Actions didn't work on Android 11.
- Screen off triggers didn't pause.
@@ -1071,18 +1315,21 @@ This summarises the changes since 2.0.2.
- Toggle Keyboard tile: Crash when switching keyboard without WRITE_SECURE_SETTINGS permission.
- A few more random crashes.
-## [2.0.0](https://github.com/sds100/KeyMapper/releases/tag/v2.0.0)
+## [2.0.0](https://github.com/sds100/KeyMapper/releases/tag/v2.0.0)
#### 22 July 2020
### Added
+
- Dark mode! 🕶
- A keymap can have multiple actions.
- Triggers
- 2 modes. The keys can all be pressed at the same time or one after another in a sequence.
- - Keys can be limited to a specific external device, any device or the device the app is installed on.
+ - Keys can be limited to a specific external device, any device or the device the app is
+ installed on.
- Double press support.
-- Constraints. Keymaps can be restricted to only work in certain situations. Constraints can be mixed in OR mode or AND mode.
+- Constraints. Keymaps can be restricted to only work in certain situations. Constraints can be
+ mixed in OR mode or AND mode.
- App in foreground
- App not in foreground
- Bluetooth device connected
@@ -1101,7 +1348,8 @@ This summarises the changes since 2.0.2.
- Renamed "Repeat Delay" to "Repeat Rate".
- Renamed "Hold Down Delay" to "Repeat Delay"
- Modifier keys now affect Key and Keycode actions.
-- Option to vibrate twice for long press actions. Once when initially pressing the keys and again when the action is performed.
+- Option to vibrate twice for long press actions. Once when initially pressing the keys and again
+ when the action is performed.
- Option for keymaps with volume key triggers to be detected when the screen is off (ROOT only).
- Option to stop repeating an action when the trigger is pressed again.
- Button in the homescreen menu to resume/pause keymaps and enable the accessibility service.
@@ -1112,42 +1360,50 @@ This summarises the changes since 2.0.2.
- Screen to configure keymaps is more optimised for very large screens.
- Preference to switch to and from the Key Mapper keyboard when pausing/resuming keymaps.
- The option to show the "performing action" toast has been moved to a toggle in each keymap.
-- The long press delay, double press timeout, sequence trigger timeout, action repeat delay, hold-down delay until actions are repeated and vibrate delay can be changed per keymap.
+- The long press delay, double press timeout, sequence trigger timeout, action repeat delay,
+ hold-down delay until actions are repeated and vibrate delay can be changed per keymap.
- Keymaps which have modifier key actions now affect other keymaps and keys which aren't mapped.
- Link to the Discord server in About.
### Bug Fixes
+
- App Shortcut actions now work properly!
- The code base has completely changed so some bugs in 1.1.7 could have been fixed.
### Changes
-- Keymaps can only have one trigger. Any keymaps with multiple triggers will be split up into multiple keymaps.
+
+- Keymaps can only have one trigger. Any keymaps with multiple triggers will be split up into
+ multiple keymaps.
### Removed
+
- The in-app logger. Send Android bug reports instead.
- Showing the Input Method picker on Android 10 and newer because Android dropped support.
-## [2.0.0 Beta 4](https://github.com/sds100/KeyMapper/releases/tag/v2.0.0-beta.4)
+## [2.0.0 Beta 4](https://github.com/sds100/KeyMapper/releases/tag/v2.0.0-beta.4)
#### 17 July 2020
Only bug fixes.
### Changes
+
- Renamed "Repeat Delay" to "Repeat Rate".
- Renamed "Hold Down Delay" to "Repeat Delay"
### Bug Fixes
+
- Crash when leaving app the menu to tweak an action showing.
- Double press triggers aren't detected.
-## [2.0.0 Beta 3](https://github.com/sds100/KeyMapper/releases/tag/v2.0.0-beta.3)
+## [2.0.0 Beta 3](https://github.com/sds100/KeyMapper/releases/tag/v2.0.0-beta.3)
#### 02 July 2020
Significantly improved the input latency.
### Added
+
- Actions can now have unique repeat options and any action is allowed to be repeated now.
- Screen on/off constraints (ROOT only).
- Option for keymaps with volume key triggers to be detected when the screen is off (ROOT only).
@@ -1157,6 +1413,7 @@ Significantly improved the input latency.
- Action to take screenshots on rooted devices older than Pie.
### Bug Fixes
+
- Triggers with the Recents and Home button would sometimes open Recents and go Home.
- Increase the screen width threshold to put all the cards in one tab to 1000dp.
- Don't crash when sometimes changing a slider.
@@ -1164,13 +1421,17 @@ Significantly improved the input latency.
- Increase the min repeat delay to 5ms because 0ms caused crashes.
### Changes
+
- Persist whether keymaps are paused.
-- The "Switch Keyboard" action now works when the app has WRITE_SECURE_SETTINGS permission rather than just rooted devices.
+- The "Switch Keyboard" action now works when the app has WRITE_SECURE_SETTINGS permission rather
+ than just rooted devices.
### Removed
-- Setting to show a toast message when an action fails. Removing this made improving the input latency much easier.
-## [2.0.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v2.0.0-beta.2)
+- Setting to show a toast message when an action fails. Removing this made improving the input
+ latency much easier.
+
+## [2.0.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v2.0.0-beta.2)
#### 16 Jun 2020
@@ -1182,7 +1443,8 @@ Significantly improved the input latency.
- Action to launch the device assistant rather than the voice assistant.
- Notification to toggle the Key Mapper keyboard.
- Quick Settings to toggle the Key Mapper keyboard and pause/resume keymaps.
-- Keymap option to vibrate twice for long press actions. Once when initially pressing the keys and again when the action is performed.
+- Keymap option to vibrate twice for long press actions. Once when initially pressing the keys and
+ again when the action is performed.
- Duplicate keymaps.
- Screen to configure keymaps is more optimised for very large screens.
- Preference to switch to and from the Key Mapper keyboard when pausing/resuming keymaps.
@@ -1196,7 +1458,8 @@ Significantly improved the input latency.
- Don't consume keyevents when actions for parallel triggers fail.
- Short press and long press triggers don't cross over.
- Short press and double press triggers don't cross over.
-- Wifi actions didn't work on Android Pie. Android doesn't allow apps to control WiFi anymore so these actions have been restricted to rooted devices on Android 9.0+ .
+- Wifi actions didn't work on Android Pie. Android doesn't allow apps to control WiFi anymore so
+ these actions have been restricted to rooted devices on Android 9.0+ .
- Crash when sometimes changing keymap options with a slider.
- Sequence trigger timeout option was shown for a single key double press trigger.
- Crash when launching the app for the first time in landscape.
@@ -1204,6 +1467,7 @@ Significantly improved the input latency.
## [2.0.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v2.0.0-beta.1) 🎉
#### 08 Jun 2020
+
### Added
- Dark mode! 🕶
@@ -1211,15 +1475,18 @@ Significantly improved the input latency.
- A keymap can have multiple actions.
- Triggers
- 2 modes. The keys can all be pressed at the same time or one after another in a sequence.
- - Keys can be limited to a specific external device, any device or the device the app is installed on.
+ - Keys can be limited to a specific external device, any device or the device the app is
+ installed on.
- Double press support.
-- Constraints. Keymaps can be restricted to only work in certain situations. Constraints can be mixed in OR mode or AND mode.
+- Constraints. Keymaps can be restricted to only work in certain situations. Constraints can be
+ mixed in OR mode or AND mode.
- App in foreground
- App not in foreground
- Bluetooth device connected
- Bluetooth device not connected
- The option to show the "performing action" toast has been moved to a toggle in each keymap.
-- The long press delay, double press timeout, sequence trigger timeout, action repeat delay, hold-down delay until actions are repeated and vibrate delay can be changed per keymap.
+- The long press delay, double press timeout, sequence trigger timeout, action repeat delay,
+ hold-down delay until actions are repeated and vibrate delay can be changed per keymap.
- Modifier keys now affect Key and Keycode actions.
- Keymaps which have modifier key actions now affect other keymaps and keys which aren't mapped.
- Show the keycode number when picking a Keycode action.
@@ -1233,7 +1500,8 @@ Significantly improved the input latency.
### Changes
-- Keymaps can only have one trigger. Any keymaps with multiple triggers will be split up into multiple keymaps.
+- Keymaps can only have one trigger. Any keymaps with multiple triggers will be split up into
+ multiple keymaps.
### Removed
@@ -1241,17 +1509,22 @@ Significantly improved the input latency.
- Showing the Input Method picker on Android 10 and newer because Android dropped support.
## [1.1.7](https://github.com/sds100/KeyMapper/releases/tag/v1.1.7)
+
#### 07 Jan 2020
### Bug Fixes
+
- KEYCODE_BACK appeared twice in the keycode list.
- crashed when the battery optimisation settings couldn't be found.
- some trigger keys have no name.
- unable to uncheck the "show volume dialog" flag.
-- on some devices (e.g Oxygen OS 10), the volume buttons up keyevents need to be consumed to stop them from changing the volume when performing an action.
-- couldn't necessarily press the back button to get back to Key Mapper when opening the accessibility settings.
+- on some devices (e.g Oxygen OS 10), the volume buttons up keyevents need to be consumed to stop
+ them from changing the volume when performing an action.
+- couldn't necessarily press the back button to get back to Key Mapper when opening the
+ accessibility settings.
### Added
+
- support for Jelly Bean 4.2 and 4.3.
- setting to show the toast message when an action fails to perform.
- action to open the device settings.
@@ -1262,77 +1535,107 @@ Significantly improved the input latency.
- action to toggle split screen (Android 7.0+)
### Changes
+
- Removed Firebase.
## [1.1.6](https://github.com/sds100/KeyMapper/releases/tag/v1.1.6)
+
#### 03 Nov 2019
F-Droid can now build.
## [1.1.5](https://github.com/sds100/KeyMapper/releases/tag/v1.1.5)
+
#### 03 Nov 2019
+
This is the first release to be released on F-Droid.
### Removed
+
- Firebase library.
### Bug Fix
-- KEYCODE_BACK appeared twice in the keycode action list. #247
+
+- KEYCODE_BACK appeared twice in the keycode action list. #247
## [1.1.4](https://github.com/sds100/KeyMapper/releases/tag/v1.1.4)
+
#### 22 Aug 2019
+
### Bug Fixes
+
- App crashed when opening the choose action activity on KitKat devices.
## [1.1.3](https://github.com/sds100/KeyMapper/releases/tag/v1.1.3)
+
#### 20 Aug 2019
+
### Bug Fixes
+
- App crashed after updating.
## [1.1.2](https://github.com/sds100/KeyMapper/releases/tag/v1.1.2)
+
#### 19 Aug 2019
+
### Bug Fixes
-- Make all slides in the intro activity scrollable so the content can be displayed on smaller devices
+
+- Make all slides in the intro activity scrollable so the content can be displayed on smaller
+ devices
- Remapping the recents button would still open recents
- Crash when the app was rotated in the "choose action" activity
- Triggers are ignored when another trigger is being detected.
### Added
+
- Action to show the keyboard picker
-- Guide the user to grant WRITE_SECURE_SETTINGS for the app so features previously restricted to rooted devices can be used on all devices.
+- Guide the user to grant WRITE_SECURE_SETTINGS for the app so features previously restricted to
+ rooted devices can be used on all devices.
- Slide to enable Do Not Disturb in the intro activity.
### Changed
+
- Rename strings for the keyboard picker notification
- Use unique keyboard names for CI and debug builds.
## [1.1.1](https://github.com/sds100/KeyMapper/releases/tag/v1.1.1)
+
#### 27 July 2019
-Exact same as 1.1.0 besides the version code and name. I messed up the versioning on Google play so had to increment the version code.
+
+Exact same as 1.1.0 besides the version code and name. I messed up the versioning on Google play so
+had to increment the version code.
## [1.1.0](https://github.com/sds100/KeyMapper/releases/tag/v1.1.0)
+
#### 27 July 2019
The initial release for Key Mapper.
Changes from 1.1.0 Beta 8:
+
- Moved the dialog to opt in to analytics to a slide in the intro activity.
## [1.1.0 Beta 8](https://github.com/sds100/KeyMapper/releases/tag/v1.1.0-beta.8)
+
#### 20 July 2019
+
### Bug Fixes
+
- Changes to a keymap wouldn't persist after a configuration change (e.g rotation)
- The keyboard service status layout at the top of the homescreen wouldn't update.
- fix minor inconsistencies in the app icons
### Changed
+
- The action to simulate the menu button no longer requires root
- Use a countdown timer when recording a trigger
- Support Android Q
### Added
+
- Show an error on the homescreen and if an action needs the Key Mapper keyboard to be enabled.
-- Show an error when trying to use an action which requires the Key Mapper keyboard and it is disabled.
+- Show an error when trying to use an action which requires the Key Mapper keyboard and it is
+ disabled.
- Action to move the cursor to the end of a file
- Actions to toggle, show and hide the keyboard
- Button to change the keyboard in the homescreen menu
@@ -1342,33 +1645,42 @@ Changes from 1.1.0 Beta 8:
- labels for the KEYCODE_BUTTON_START and KEYCODE_BUTTON_SELECT keycodes
- An introduction activity the first time the app is opened
- Logger: log when recording a trigger has started and stopped
-- Show a dialog the first time the Key Mapper keyboard is chosen explaining why another keyboard can't be used.
+- Show a dialog the first time the Key Mapper keyboard is chosen explaining why another keyboard
+ can't be used.
- ChooseActionActivity: A tab to which lists all the actions which aren't supported and why.
- Show a "requires root" message for actions which need it
## [1.1.0 Beta 7](https://github.com/sds100/KeyMapper/releases/tag/v1.1.0-beta.7)
+
#### 27 May 2019
+
### Bug Fixes
+
- App would crash when trying to read a system setting which doesn't exist
- App would crash if couldn't find the Do Not Disturb settings page
-- Logger: send icon was grey but should be white
+- Logger: send icon was grey but should be white
### Added
+
- The status card at the top of the homescreen can now be expanded and collapsed
- Show a toast message when a foreseen error is encountered
- Logger: log whenever the accessibility service is started/stopped
## [1.1.0 Beta 6](https://github.com/sds100/KeyMapper/releases/tag/v1.1.0-beta.6)
+
#### 19 May 2019
+
### Bug Fixes
- Don't show NFC actions on devices without NFC
- Couldn't change volume when short pressing a volume button remapped to a long press action
## [1.1.0 Beta 5](https://github.com/sds100/KeyMapper/releases/tag/v1.1.0-beta.5)
+
#### 19 May 2019
- Updated libraries
+
### Added
- Action to enable, disable and toggle NFC.
@@ -1381,7 +1693,8 @@ Changes from 1.1.0 Beta 8:
### Changes
-- Add the trigger after the 5 seconds rather than having to press the button so the app can work with devices which only have remotes as input.
+- Add the trigger after the 5 seconds rather than having to press the button so the app can work
+ with devices which only have remotes as input.
- Cleanup Settings strings.
- Use slightly darker homescreen background.
- Don't show the "Key mapper is performing an action" toast message by default.
@@ -1389,6 +1702,7 @@ Changes from 1.1.0 Beta 8:
- Minimum vibration duration is 1ms rather than 100ms
### Bug Fixes
+
- The landscape mode action wouldn't work.
- Would potentially crash when trying to open the write-settings permission page.
- Don't show a toast message when enabling/disabling the device admin.
@@ -1396,102 +1710,144 @@ Changes from 1.1.0 Beta 8:
- Device would go to the homescreen when using a trigger with the home button in it.
## [1.1.0 Beta 4](https://github.com/sds100/KeyMapper/releases/tag/v1.1.0-beta.4)
+
#### 10 Apr 2019
+
### Added
+
- Option to choose which flash to use for flashlight actions
- Optimised the New and Edit Keymap activities for various screen sizes
- Slightly optimised the homescreen for wide screens
### Bug Fixes
+
- Could potentially crash when trying to switch to the Key Mapper input method
- Could potentially crash when removing a trigger from the list
- Would crash if it couldn't find the input method settings page
-- Would crash when trying to change a specific volume stream while the device is in a Do Not Disturb state
+- Would crash when trying to change a specific volume stream while the device is in a Do Not Disturb
+ state
- Would crash when using an app shortcut without the correct permissions.
## [1.1.0 Beta 3](https://github.com/sds100/KeyMapper/releases/tag/v1.1.0-beta.3)
+
#### 4 Apr 2019
+
- Reduced the repeat delay to 5ms
- Force expand the menu on the homescreen
- Made the cards on the homescreen slightly more compact
### Added
+
- Flag to vibrate and an option to force vibrate for all actions
- Action which just consumes the keyevent and does nothing
-- Action to lock the device (ROOT only for now) and an option to lock the device securely (without root).
+- Action to lock the device (ROOT only for now) and an option to lock the device securely (without
+ root).
### Bug fixes
+
- The bottom app bar on the homescreen would overlap the list items
-- The app would potentially crash when trying to perform a flashlight action whilst the camera is in use in another app.
-- Short press actions with the same trigger as a long press action would be performed with the long press action
-- A keymap would still have the "Show volume dialog" flag if the action is changed to a non volume related action
+- The app would potentially crash when trying to perform a flashlight action whilst the camera is in
+ use in another app.
+- Short press actions with the same trigger as a long press action would be performed with the long
+ press action
+- A keymap would still have the "Show volume dialog" flag if the action is changed to a non volume
+ related action
- The app would crash if trying to show the menu on the homescreen if it is already showing.
-- The accessibility service status on the homescreen wouldn't change when the service is started/stopped.
+- The accessibility service status on the homescreen wouldn't change when the service is
+ started/stopped.
## [1.1.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v1.1.0-beta.2)
+
#### 31 Mar 2019
+
- Won't immediately crash on KitKat anymore! :)
### Bug fixes
+
- Persistent IME notification wouldn't automatically show when it is enabled.
- App would crash if it couldn't find the device's accessibility settings page.
## [1.1.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v1.1.0-beta.1)
+
#### 27 Mar 2019
+
### Added
+
- Setting to change the long-press delay.
-- Persistent notification which can pause/resume your remaps. It can also open the accessibility settings on the device to enable/disable the service. Rooted devices can start/stop the accessibility service without going into settings and just tap the notification.
+- Persistent notification which can pause/resume your remaps. It can also open the accessibility
+ settings on the device to enable/disable the service. Rooted devices can start/stop the
+ accessibility service without going into settings and just tap the notification.
- Use Material Design 2 for homescreen.
### Bug fixes
+
- Persistent notifications wouldn't show on boot
-- The app would crash if using the "open google assistant" action if the Google app wasn't installed.
-- Prevent the accessibility service from stopping if there is a fatal exception and show a toast when it happens.
+- The app would crash if using the "open google assistant" action if the Google app wasn't
+ installed.
+- Prevent the accessibility service from stopping if there is a fatal exception and show a toast
+ when it happens.
## [1.0.0 Beta 6](https://github.com/sds100/KeyMapper/releases/tag/v1.0.0-beta.6)
+
#### 22 Mar 2019
+
- Changed developer email.
- Added link to the XDA Thread in the About activity.
## [1.0.0 Beta 5](https://github.com/sds100/KeyMapper/releases/tag/v1.0.0-beta.5)
+
#### 22 Mar 2019
+
- Updated build-tools to 28.0.4
- Updated Room library to 2.1.0-alpha05
- Updated Firebase core library to 16.0.8
#### Bug fixes
+
- App would crash when using brightness actions because it needed write system settings permission.
## [1.0.0 Beta 4](https://github.com/sds100/KeyMapper/releases/tag/v1.0.0-beta.4)
+
#### 9 Mar 2019
+
- Added more labels for keys.
- Added a link to the app in the device's Accessibility settings.
- Updated the Gradle version to 3.3.2
-- When the long-press flag is chosen, show a warning saying it will only work properly for volume and navigation buttons.
+- When the long-press flag is chosen, show a warning saying it will only work properly for volume
+ and navigation buttons.
- Enable the show-volume-ui flag by default.
#### Bug fixes
+
- App would crash when choosing flags for a keymap without an action.
- Buttons being repeatedly pressed.
-- Enabling the long-press flag would stop the button from working when it is pressed without a long press.
+- Enabling the long-press flag would stop the button from working when it is pressed without a long
+ press.
## Accidentally skipped Beta 3 release. Oops.
## [1.0.0 Beta 2](https://github.com/sds100/KeyMapper/releases/tag/v1.0.0-beta.2)
+
#### 2 Mar 2019
+
- added option to email developer in the About activity.
- created privacy policy and ability to opt in/out of Firebase analytics
## [1.0.0 Beta 1](https://github.com/sds100/KeyMapper/releases/tag/v1.0.0-beta.1)
+
#### 2 Mar 2019
+
- Initial release!
-- Option to automatically change the input method and/or show the input method picker when a chosen Bluetooth device is connected and switch back to the old one when disconnected
-- Option to show a notification, which when clicked on, will show the input method picker. Android 8.1+ needs root.
+- Option to automatically change the input method and/or show the input method picker when a chosen
+ Bluetooth device is connected and switch back to the old one when disconnected
+- Option to show a notification, which when clicked on, will show the input method picker. Android
+ 8.1+ needs root.
- Option to show a toast message whenever an action is performed.
- A Help activity
- An About activity
- No limit on the amount of triggers for a keymap and how many keys can be used to create a trigger.
-- Optional flags for each keymap so it can only be triggered on a long press and whether to show the volume dialog for volume related actions.
+- Optional flags for each keymap so it can only be triggered on a long press and whether to show the
+ volume dialog for volume related actions.
- Ability to enable/disable specific/all keymaps.
#### Added these actions
diff --git a/CREDITS.md b/CREDITS.md
index 2327ac943f..8d7b22f638 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -8,11 +8,11 @@ Many thanks to...
- [App Mockup] for their screenshot utility.
- Airbnb for their [Epoxy](https://github.com/airbnb/epoxy) RecyclerView. They made having multiple itemview types and dragging and dropping super easy!
- @[Jake Wharton](https://github.com/JakeWharton) for his [Timber](https://github.com/JakeWharton/timber) logging library.
- - @[AppIntro](https://github.com/AppIntro) for their [AppIntro](https://github.com/AppIntro/AppIntro) library.
- @[srikanth-lingala](https://github.com/srikanth-lingala) for their Java zip file [library](https://github.com/srikanth-lingala/zip4j).
- @[anggrayudi](https://github.com/anggrayudi) for their [Simple Storage](https://github.com/anggrayudi/SimpleStorage) library that makes working with Scoped Storage and the Storage Access Framework on Android much easier.
- @[MFlisar](https://github.com/MFlisar) for their [drag and select](https://github.com/MFlisar/DragSelectRecyclerView) library.
- @[RikkaApps](https://github.com/RikkaApps) for Shizuku! It is amazing.
+ - @[canopas](https://github.com/canopas) for their Jetpack Compose Tap Target library https://github.com/canopas/compose-intro-showcase.
[salomonbrys]: https://github.com/salomonbrys
[Kotson]: https://github.com/salomonbrys/Kotson
diff --git a/api/.gitignore b/api/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/api/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/api/build.gradle.kts b/api/build.gradle.kts
new file mode 100644
index 0000000000..e2ce37ba42
--- /dev/null
+++ b/api/build.gradle.kts
@@ -0,0 +1,49 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.google.devtools.ksp)
+ alias(libs.plugins.jlleitschuh.gradle.ktlint)
+ alias(libs.plugins.dagger.hilt.android)
+}
+
+android {
+ namespace = "io.github.sds100.keymapper.api"
+ compileSdk = libs.versions.compile.sdk.get().toInt()
+
+ defaultConfig {
+ minSdk = libs.versions.min.sdk.get().toInt()
+
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+
+ buildFeatures {
+ aidl = true
+ }
+}
+
+dependencies {
+ implementation(project(":common"))
+ implementation(project(":base"))
+ implementation(project(":system"))
+
+ implementation(libs.jakewharton.timber)
+
+ implementation(libs.dagger.hilt.android)
+ ksp(libs.dagger.hilt.android.compiler)
+}
diff --git a/app/src/test/resources/backup-manager-test/empty.json b/api/consumer-rules.pro
similarity index 100%
rename from app/src/test/resources/backup-manager-test/empty.json
rename to api/consumer-rules.pro
diff --git a/api/proguard-rules.pro b/api/proguard-rules.pro
new file mode 100644
index 0000000000..481bb43481
--- /dev/null
+++ b/api/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/api/src/main/AndroidManifest.xml b/api/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..b3e73b7e65
--- /dev/null
+++ b/api/src/main/AndroidManifest.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/io/github/sds100/keymapper/api/Api.kt b/api/src/main/java/io/github/sds100/keymapper/api/Api.kt
similarity index 92%
rename from app/src/main/java/io/github/sds100/keymapper/api/Api.kt
rename to api/src/main/java/io/github/sds100/keymapper/api/Api.kt
index e14b0c974b..3c51a8126f 100644
--- a/app/src/main/java/io/github/sds100/keymapper/api/Api.kt
+++ b/api/src/main/java/io/github/sds100/keymapper/api/Api.kt
@@ -1,8 +1,5 @@
package io.github.sds100.keymapper.api
-/**
- * Created by sds100 on 17/06/2021.
- */
object Api {
// Do not use the package name for debug/ci builds
const val ACTION_TRIGGER_KEYMAP_BY_UID =
diff --git a/api/src/main/java/io/github/sds100/keymapper/api/ApiHiltModule.kt b/api/src/main/java/io/github/sds100/keymapper/api/ApiHiltModule.kt
new file mode 100644
index 0000000000..aaf5e6ce4b
--- /dev/null
+++ b/api/src/main/java/io/github/sds100/keymapper/api/ApiHiltModule.kt
@@ -0,0 +1,16 @@
+package io.github.sds100.keymapper.api
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import io.github.sds100.keymapper.system.apps.KeyMapShortcutActivityIntentBuilder
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class ApiHiltModule {
+ @Binds
+ abstract fun bindKeyMapShortcutActivityIntentBuilder(
+ impl: KeyMapShortcutActivityIntentBuilderImpl,
+ ): KeyMapShortcutActivityIntentBuilder
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/api/KeyEventRelayService.kt b/api/src/main/java/io/github/sds100/keymapper/api/KeyEventRelayService.kt
similarity index 92%
rename from app/src/main/java/io/github/sds100/keymapper/api/KeyEventRelayService.kt
rename to api/src/main/java/io/github/sds100/keymapper/api/KeyEventRelayService.kt
index 333f14932d..58c49939e5 100644
--- a/app/src/main/java/io/github/sds100/keymapper/api/KeyEventRelayService.kt
+++ b/api/src/main/java/io/github/sds100/keymapper/api/KeyEventRelayService.kt
@@ -8,7 +8,6 @@ import android.os.IBinder
import android.os.IBinder.DeathRecipient
import android.view.KeyEvent
import android.view.MotionEvent
-import io.github.sds100.keymapper.system.inputmethod.KeyMapperImeHelper
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
@@ -31,16 +30,29 @@ class KeyEventRelayService : Service() {
const val ACTION_REBIND_RELAY_SERVICE =
"io.github.sds100.keymapper.ACTION_REBIND_RELAY_SERVICE"
- const val CALLBACK_ID_ACCESSIBILITY_SERVICE = "accessibility_service"
- const val CALLBACK_ID_INPUT_METHOD = "input_method"
-
/**
* Used when a client registers a callback without specifying an ID.
*/
private const val CALLBACK_ID_DEFAULT = "default"
+
+ const val KEY_MAPPER_GUI_IME_PACKAGE =
+ "io.github.sds100.keymapper.inputmethod.latin"
+
+ private const val KEY_MAPPER_LEANBACK_IME_PACKAGE =
+ "io.github.sds100.keymapper.inputmethod.leanback"
+
+ private const val KEY_MAPPER_HACKERS_KEYBOARD_PACKAGE =
+ "io.github.sds100.keymapper.inputmethod.hackers"
}
- val permittedPackages = KeyMapperImeHelper.KEY_MAPPER_IME_PACKAGE_LIST
+ val permittedPackages by lazy {
+ arrayOf(
+ packageName,
+ KEY_MAPPER_GUI_IME_PACKAGE,
+ KEY_MAPPER_LEANBACK_IME_PACKAGE,
+ KEY_MAPPER_HACKERS_KEYBOARD_PACKAGE,
+ )
+ }
private val binderInterface: IKeyEventRelayService = object : IKeyEventRelayService.Stub() {
override fun sendKeyEvent(
diff --git a/api/src/main/java/io/github/sds100/keymapper/api/KeyMapShortcutActivityIntentBuilderImpl.kt b/api/src/main/java/io/github/sds100/keymapper/api/KeyMapShortcutActivityIntentBuilderImpl.kt
new file mode 100644
index 0000000000..f6d8faae4f
--- /dev/null
+++ b/api/src/main/java/io/github/sds100/keymapper/api/KeyMapShortcutActivityIntentBuilderImpl.kt
@@ -0,0 +1,22 @@
+package io.github.sds100.keymapper.api
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import dagger.hilt.android.qualifiers.ApplicationContext
+import io.github.sds100.keymapper.system.apps.KeyMapShortcutActivityIntentBuilder
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class KeyMapShortcutActivityIntentBuilderImpl @Inject constructor(
+ @ApplicationContext private val ctx: Context,
+) : KeyMapShortcutActivityIntentBuilder {
+ override fun build(intentAction: String, intentExtras: Bundle): Intent {
+ return Intent(ctx, LaunchKeyMapShortcutActivity::class.java).apply {
+ action = intentAction
+
+ putExtras(intentExtras)
+ }
+ }
+}
diff --git a/api/src/main/java/io/github/sds100/keymapper/api/LaunchKeyMapShortcutActivity.kt b/api/src/main/java/io/github/sds100/keymapper/api/LaunchKeyMapShortcutActivity.kt
new file mode 100644
index 0000000000..32e269e42b
--- /dev/null
+++ b/api/src/main/java/io/github/sds100/keymapper/api/LaunchKeyMapShortcutActivity.kt
@@ -0,0 +1,58 @@
+package io.github.sds100.keymapper.api
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter
+import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceState
+import javax.inject.Inject
+
+// DON'T MOVE THIS CLASS TO A DIFFERENT PACKAGE BECAUSE IT BREAKS THE API
+/**
+ * Use basic Activity, NOT AppCompatActivity so the NoDisplay theme works. Otherwise an
+ * exception may be thrown because the theme doesn't extend AppCompat.
+ */
+
+@AndroidEntryPoint
+class LaunchKeyMapShortcutActivity : ComponentActivity() {
+
+ @Inject
+ lateinit var accessibilityServiceAdapter: AccessibilityServiceAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val accessibilityServiceState = accessibilityServiceAdapter.state.value
+
+ when (accessibilityServiceState) {
+ AccessibilityServiceState.ENABLED ->
+ if (intent.action == Api.ACTION_TRIGGER_KEYMAP_BY_UID) {
+ Intent(Api.ACTION_TRIGGER_KEYMAP_BY_UID).apply {
+ setPackage(packageName)
+
+ val uuid = intent.getStringExtra(Api.EXTRA_KEYMAP_UID)
+ putExtra(Api.EXTRA_KEYMAP_UID, uuid)
+
+ sendBroadcast(this)
+ }
+ }
+
+ AccessibilityServiceState.CRASHED -> Toast.makeText(
+ this,
+ R.string.error_accessibility_service_crashed,
+ Toast.LENGTH_SHORT,
+ ).show()
+
+ AccessibilityServiceState.DISABLED -> Toast.makeText(
+ this,
+ R.string.error_accessibility_service_disabled,
+ Toast.LENGTH_SHORT,
+ ).show()
+ }
+
+ finish()
+ }
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/api/PauseMappingsBroadcastReceiver.kt b/api/src/main/java/io/github/sds100/keymapper/api/PauseMappingsBroadcastReceiver.kt
similarity index 73%
rename from app/src/main/java/io/github/sds100/keymapper/api/PauseMappingsBroadcastReceiver.kt
rename to api/src/main/java/io/github/sds100/keymapper/api/PauseMappingsBroadcastReceiver.kt
index 662687d589..8cabcafc4c 100644
--- a/app/src/main/java/io/github/sds100/keymapper/api/PauseMappingsBroadcastReceiver.kt
+++ b/api/src/main/java/io/github/sds100/keymapper/api/PauseMappingsBroadcastReceiver.kt
@@ -3,21 +3,21 @@ package io.github.sds100.keymapper.api
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import io.github.sds100.keymapper.UseCases
-import io.github.sds100.keymapper.util.firstBlocking
-
-/**
- * Created by sds100 on 17/06/2021.
- */
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.keymaps.PauseKeyMapsUseCase
+import io.github.sds100.keymapper.common.utils.firstBlocking
+import javax.inject.Inject
// DON'T MOVE THIS CLASS TO A DIFFERENT PACKAGE BECAUSE IT BREAKS THE API
+@AndroidEntryPoint
class PauseMappingsBroadcastReceiver : BroadcastReceiver() {
+ @Inject
+ lateinit var useCase: PauseKeyMapsUseCase
+
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
- val useCase = UseCases.pauseKeyMaps(context)
-
when (intent?.action) {
Api.ACTION_PAUSE_MAPPINGS -> useCase.pause()
Api.ACTION_RESUME_MAPPINGS -> useCase.resume()
diff --git a/app/src/main/java/io/github/sds100/keymapper/api/TriggerKeyMapsBroadcastReceiver.kt b/api/src/main/java/io/github/sds100/keymapper/api/TriggerKeyMapsBroadcastReceiver.kt
similarity index 55%
rename from app/src/main/java/io/github/sds100/keymapper/api/TriggerKeyMapsBroadcastReceiver.kt
rename to api/src/main/java/io/github/sds100/keymapper/api/TriggerKeyMapsBroadcastReceiver.kt
index ffb5e42468..12b0acdf8e 100644
--- a/app/src/main/java/io/github/sds100/keymapper/api/TriggerKeyMapsBroadcastReceiver.kt
+++ b/api/src/main/java/io/github/sds100/keymapper/api/TriggerKeyMapsBroadcastReceiver.kt
@@ -3,25 +3,32 @@ package io.github.sds100.keymapper.api
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import io.github.sds100.keymapper.ServiceLocator
-import io.github.sds100.keymapper.util.ServiceEvent
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.keymaps.TriggerKeyMapEvent
+import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import javax.inject.Inject
// DON'T MOVE THIS CLASS TO A DIFFERENT PACKAGE BECAUSE IT BREAKS THE API
+@AndroidEntryPoint
class TriggerKeyMapsBroadcastReceiver : BroadcastReceiver() {
+ @Inject
+ lateinit var serviceAdapter: AccessibilityServiceAdapter
+
+ @Inject
+ lateinit var coroutineScope: CoroutineScope
+
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
intent ?: return
- val serviceAdapter = ServiceLocator.accessibilityServiceAdapter(context)
- val scope = ServiceLocator.appCoroutineScope(context)
-
when (intent.action) {
Api.ACTION_TRIGGER_KEYMAP_BY_UID -> {
intent.getStringExtra(Api.EXTRA_KEYMAP_UID)?.let { uid ->
- scope.launch {
- serviceAdapter.send(ServiceEvent.TriggerKeyMap(uid))
+ coroutineScope.launch {
+ serviceAdapter.send(TriggerKeyMapEvent(uid))
}
}
}
diff --git a/app/build.gradle b/app/build.gradle
deleted file mode 100644
index 13f120a407..0000000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,280 +0,0 @@
-apply plugin: "com.android.application"
-apply plugin: "kotlin-android"
-apply plugin: "kotlin-kapt"
-apply plugin: "com.google.devtools.ksp"
-apply plugin: "androidx.navigation.safeargs.kotlin"
-apply plugin: "kotlinx-serialization"
-apply plugin: "org.jetbrains.kotlin.plugin.parcelize"
-apply plugin: "org.jlleitschuh.gradle.ktlint"
-apply plugin: "org.jetbrains.kotlin.plugin.compose"
-apply plugin: "androidx.room"
-
-android {
-
- namespace "io.github.sds100.keymapper"
- compileSdk 35
- buildToolsVersion = "35.0.0"
-
- def versionProperties = new Properties()
- file("version.properties").withInputStream { versionProperties.load(it) }
-
- defaultConfig {
- applicationId "io.github.sds100.keymapper"
- minSdkVersion 21
- targetSdkVersion 35
- versionCode versionProperties.getProperty("VERSION_CODE").toInteger()
- versionName versionProperties.getProperty("VERSION_NAME")
- multiDexEnabled true
-
- vectorDrawables.useSupportLibrary = true
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-
- javaCompileOptions {
- annotationProcessorOptions {
- arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
- }
- }
- }
-
- signingConfigs {
- release {
- storeFile file("keystore.jks")
- storePassword System.getenv("KEYSTORE_PASSWORD")
- keyAlias "keymapper"
- keyPassword System.getenv("KEY_PASSWORD")
- }
- }
-
- buildTypes {
-
- release {
- minifyEnabled true
- proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
- signingConfig signingConfigs.release
- }
-
- debug {
- applicationIdSuffix ".debug"
- versionNameSuffix "-debug"
- }
-
- debug_release {
- // Extend from debug build type so compose Live Edit and rapid building works
- initWith debug
-
- // Do not alter the package name so can test revenuecat and billing while developing.
- applicationIdSuffix ""
-
- /*
- This is required because the splitties library does not have a debug_release build type.
- */
- matchingFallbacks = ["debug"]
- }
-
- ci {
- minifyEnabled true
- shrinkResources true
-
- /*
- This is required because the splitties library does not have a ci build type.
- */
- matchingFallbacks = ["debug"]
-
- applicationIdSuffix ".ci"
- versionNameSuffix "-ci." + versionProperties.getProperty("VERSION_NUM")
-
- proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
-
- signingConfig signingConfigs.debug
- }
- }
-
- flavorDimensions = ["pro"]
- productFlavors {
- free {
- dimension "pro"
- }
- pro {
- dimension "pro"
-
- File file = rootProject.file("local.properties")
- String keyName = "REVENUECAT_API_KEY"
-
- if (file.exists()) {
- def localProperties = new Properties()
- localProperties.load(new FileInputStream(file))
- if (localProperties.containsKey(keyName)) {
- buildConfigField("String", keyName, localProperties[keyName])
- }
- }
- }
- }
-
- buildFeatures {
- dataBinding true
- viewBinding true
- aidl true
- buildConfig true
- compose true
- }
-
- compileOptions {
- // Required for desugaring new Java time API on lower than API 26
- coreLibraryDesugaringEnabled = true
-
- sourceCompatibility JavaVersion.VERSION_17
- targetCompatibility JavaVersion.VERSION_17
- }
-
- kotlinOptions {
- jvmTarget = "17"
- }
-
- kapt {
- correctErrorTypes = true
- }
-
- composeOptions {
- kotlinCompilerExtensionVersion "1.5.10"
- }
-
- sourceSets {
- androidTest {
- assets.srcDirs += files("$projectDir/schemas".toString())
- resources.srcDirs += ["src/test/resources"]
- }
-
- test {
- java.srcDirs += ["src/pro/test/java"]
- }
- }
-
- applicationVariants.configureEach { variant ->
- variant.outputs.configureEach {
- outputFileName = "keymapper-${variant.versionName}.apk"
- }
- }
-
- room {
- schemaDirectory "$projectDir/schemas"
- }
-}
-
-dependencies {
- implementation fileTree(include: ["*.jar"], dir: "libs")
-
- compileOnly project(":systemstubs")
-
- def room_version = "2.7.1"
- def coroutinesVersion = "1.9.0"
- def nav_version = '2.8.9'
- def epoxy_version = "4.6.2"
- def splitties_version = "3.0.0"
- def multidex_version = "2.0.1"
- def shizuku_version = "13.1.5"
-
- // kotlin stuff
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
- implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0"
-
- // random stuff
- implementation "com.google.android.material:material:1.13.0-alpha13"
- implementation "com.github.salomonbrys.kotson:kotson:2.5.0"
- implementation "com.airbnb.android:epoxy:$epoxy_version"
- implementation "com.airbnb.android:epoxy-databinding:$epoxy_version"
- kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
- implementation "com.jakewharton.timber:timber:5.0.1"
- implementation "net.lingala.zip4j:zip4j:2.8.0"
- implementation "com.anggrayudi:storage:0.8.1"
- implementation "com.github.MFlisar:DragSelectRecyclerView:0.3"
- implementation "com.google.android.flexbox:flexbox:3.0.0"
- implementation "dev.rikka.shizuku:api:$shizuku_version"
- implementation "dev.rikka.shizuku:provider:$shizuku_version"
- implementation "org.lsposed.hiddenapibypass:hiddenapibypass:4.3"
- proImplementation 'com.revenuecat.purchases:purchases:8.17.0'
- proImplementation "com.airbnb.android:lottie-compose:6.6.3"
- implementation("com.squareup.okhttp3:okhttp:4.12.0")
- coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
-
- // splitties
- implementation "com.louiscad.splitties:splitties-bitflags:$splitties_version"
- implementation "com.louiscad.splitties:splitties-alertdialog-appcompat-coroutines:$splitties_version"
- implementation("com.louiscad.splitties:splitties-alertdialog-material:$splitties_version")
- implementation "com.louiscad.splitties:splitties-snackbar:$splitties_version"
- implementation "com.louiscad.splitties:splitties-toast:$splitties_version"
- implementation "com.louiscad.splitties:splitties-mainthread:$splitties_version"
-
- // androidx
- implementation "androidx.legacy:legacy-support-core-ui:1.0.0"
- implementation "androidx.core:core-ktx:1.16.0"
-
- implementation "androidx.activity:activity-ktx:1.10.1"
- implementation "androidx.fragment:fragment-ktx:1.8.6"
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7"
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.7"
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.7"
- implementation "androidx.room:room-ktx:$room_version"
- implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
- implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
- implementation "androidx.multidex:multidex:$multidex_version"
- implementation "androidx.appcompat:appcompat:1.7.0"
- implementation "androidx.recyclerview:recyclerview:1.4.0"
- implementation "androidx.preference:preference-ktx:1.2.1"
- implementation "androidx.constraintlayout:constraintlayout:2.2.1"
- 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.2.0-alpha02"
- implementation "androidx.core:core-splashscreen:1.0.1"
- implementation "androidx.activity:activity-compose:1.10.1"
- implementation "androidx.navigation:navigation-compose:2.8.9"
- implementation "androidx.navigation:navigation-fragment-compose:2.8.9"
- ksp "androidx.room:room-compiler:$room_version"
-
- // Compose
- Dependency composeBom = platform('androidx.compose:compose-bom-beta:2025.04.01')
- implementation composeBom
- implementation 'androidx.compose.foundation:foundation'
- implementation "androidx.compose.ui:ui-android"
- implementation "androidx.compose.material3:material3-android"
- implementation "androidx.compose.ui:ui-tooling-preview-android"
- implementation "androidx.compose.material:material-icons-extended-android"
- implementation 'androidx.compose.material3.adaptive:adaptive-android'
- implementation "androidx.compose.material3.adaptive:adaptive-navigation"
- implementation "com.google.accompanist:accompanist-drawablepainter:0.35.0-alpha"
- implementation "androidx.activity:activity-compose:1.10.1"
- debugImplementation "androidx.compose.ui:ui-tooling"
- debug_releaseImplementation "androidx.compose.ui:ui-tooling"
-
-// debugImplementation "com.squareup.leakcanary:leakcanary-android:2.6"
-
- def junitVersion = "4.13.2"
- def androidXTestExtKotlinRunnerVersion = "1.2.1"
- def espressoVersion = "3.6.1"
- def androidXTestCoreVersion = "1.6.1"
-
- // Dependencies for local unit tests
- testImplementation "junit:junit:$junitVersion"
- testImplementation "org.hamcrest:hamcrest-all:1.3"
- testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"
- testImplementation "androidx.test:core-ktx:1.6.1"
- testImplementation "org.robolectric:robolectric:4.14.1"
- testImplementation "androidx.arch.core:core-testing:2.2.0"
- testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
- testImplementation "pl.pragmatists:JUnitParams:1.1.1"
- testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
- testImplementation "org.mockito:mockito-core:5.15.2"
- testImplementation "org.mockito:mockito-inline:5.2.0"
-
- androidTestImplementation "androidx.test.ext:junit:$androidXTestExtKotlinRunnerVersion"
- androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
- androidTestImplementation "androidx.arch.core:core-testing:2.2.0"
- androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
- androidTestImplementation "junit:junit:$junitVersion"
- androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
- androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
- androidTestImplementation "org.mockito:mockito-android:4.6.1"
- debugImplementation "androidx.fragment:fragment-testing:1.8.6"
- implementation "androidx.test:core:$androidXTestCoreVersion"
-}
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000000..e5df922030
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,181 @@
+@file:Suppress("UnstableApiUsage")
+
+import java.util.Properties
+
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.compose)
+ alias(libs.plugins.kotlin.kapt)
+ alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.kotlin.parcelize)
+ alias(libs.plugins.androidx.navigation.safeargs.kotlin)
+ alias(libs.plugins.google.devtools.ksp)
+ alias(libs.plugins.jlleitschuh.gradle.ktlint)
+ alias(libs.plugins.dagger.hilt.android)
+}
+
+android {
+ namespace = "io.github.sds100.keymapper"
+ compileSdk = libs.versions.compile.sdk.get().toInt()
+ buildToolsVersion = libs.versions.build.tools.get()
+
+ val versionProperties = Properties().apply {
+ project.file("version.properties").inputStream().use { load(it) }
+ }
+
+ defaultConfig {
+ applicationId = "io.github.sds100.keymapper"
+ minSdk = libs.versions.min.sdk.get().toInt()
+ targetSdk = libs.versions.target.sdk.get().toInt()
+
+ versionCode = versionProperties.getProperty("VERSION_CODE").toInt()
+ versionName = versionProperties.getProperty("VERSION_NAME")
+ multiDexEnabled = true
+
+ vectorDrawables.useSupportLibrary = true
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ signingConfigs {
+ create("release") {
+ storeFile = file("keystore.jks")
+ storePassword = System.getenv("KEYSTORE_PASSWORD")
+ keyAlias = "keymapper"
+ keyPassword = System.getenv("KEY_PASSWORD")
+ }
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ isShrinkResources = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ signingConfig = signingConfigs.getByName("release")
+ versionNameSuffix = "-foss"
+ }
+
+ debug {
+ applicationIdSuffix = ".debug"
+ versionNameSuffix = "-foss-debug"
+ }
+
+ create("debug_release") {
+ initWith(getByName("debug"))
+ applicationIdSuffix = "" // Reset from debug
+ matchingFallbacks.add("debug")
+ }
+
+ create("ci") {
+ isMinifyEnabled = true
+ // shrinkResources is now part of isMinifyEnabled in newer AGP,
+ // but let's keep explicit if an older AGP interpretation is in mind.
+ // If build fails, this might need adjustment.
+ // For AGP 8.x, shrinkResources is controlled by isMinifyEnabled.
+ // Explicitly setting it might be deprecated or have no effect.
+ // I'll assume it implies full R8 shrinkage.
+ // shrinkResources = true // This property might not exist directly here in KTS for AGP 8+
+ // Instead, you rely on isMinifyEnabled and Proguard rules.
+
+ matchingFallbacks.add("debug")
+ applicationIdSuffix = ".ci"
+ versionNameSuffix = "-foss-ci.${versionProperties.getProperty("VERSION_NUM")}"
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ signingConfig = signingConfigs.getByName("debug") // Assuming debug signing for CI
+ }
+ }
+
+ buildFeatures {
+ dataBinding = true
+ aidl = true
+ buildConfig = true
+ compose = true
+ }
+
+ compileOptions {
+ isCoreLibraryDesugaringEnabled = true
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+
+ kapt {
+ correctErrorTypes = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
+ }
+
+ sourceSets {
+ getByName("androidTest") {
+ assets.srcDirs(files("$projectDir/schemas"))
+ resources.srcDirs("src/test/resources")
+ }
+ getByName("test") {
+ java.srcDirs("src/pro/test/java")
+ }
+ }
+
+ applicationVariants.all {
+ outputs.all {
+ val output = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
+ output.outputFileName = "keymapper-$versionName.apk"
+ }
+ }
+}
+
+dependencies {
+ implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
+
+ implementation(project(":common"))
+ implementation(project(":base"))
+ implementation(project(":api"))
+ implementation(project(":data"))
+ implementation(project(":system"))
+ compileOnly(project(":systemstubs"))
+
+ coreLibraryDesugaring(libs.desugar.jdk.libs)
+
+ // Other
+ implementation(libs.jakewharton.timber)
+
+ // Androidx
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.multidex)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.bundles.androidx.navigation)
+
+ // Compose
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.compose.foundation)
+ implementation(libs.androidx.compose.ui.android)
+ implementation(libs.androidx.compose.material3)
+ implementation(libs.androidx.compose.ui.tooling.preview)
+ implementation(libs.androidx.compose.material.icons.extended)
+ implementation(libs.androidx.compose.material3.adaptive)
+ implementation(libs.androidx.compose.material3.adaptive.navigation)
+ implementation(libs.androidx.hilt.navigation.compose)
+ implementation(libs.google.accompanist.drawablepainter)
+ implementation(libs.androidx.compose.ui.tooling)
+
+ // Dagger
+ implementation(libs.dagger.hilt.android)
+ ksp(libs.dagger.hilt.android.compiler)
+
+ debugImplementation(libs.androidx.ui.tooling)
+
+ testImplementation(libs.junit)
+}
diff --git a/app/src/androidTest/java/io/github/sds100/keymapper/AppDatabaseMigrationTest.kt b/app/src/androidTest/java/io/github/sds100/keymapper/AppDatabaseMigrationTest.kt
deleted file mode 100644
index 83dc9f3ce9..0000000000
--- a/app/src/androidTest/java/io/github/sds100/keymapper/AppDatabaseMigrationTest.kt
+++ /dev/null
@@ -1,858 +0,0 @@
-package io.github.sds100.keymapper
-
-import androidx.datastore.preferences.core.PreferenceDataStoreFactory
-import androidx.datastore.preferences.core.edit
-import androidx.datastore.preferences.core.stringPreferencesKey
-import androidx.room.migration.Migration
-import androidx.room.testing.MigrationTestHelper
-import androidx.sqlite.db.SupportSQLiteDatabase
-import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
-import androidx.test.espresso.matcher.ViewMatchers
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.github.salomonbrys.kotson.get
-import com.google.gson.Gson
-import com.google.gson.JsonArray
-import com.google.gson.JsonElement
-import com.google.gson.JsonParseException
-import com.google.gson.JsonParser
-import io.github.sds100.keymapper.data.db.AppDatabase
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.runBlocking
-import org.hamcrest.Matchers
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import java.io.File
-import java.io.IOException
-
-/**
- * Created by sds100 on 05/06/20.
- */
-@ExperimentalCoroutinesApi
-@RunWith(AndroidJUnit4::class)
-class AppDatabaseMigrationTest {
- companion object {
- private const val TEST_DB_NAME = "migration_test"
-
- private val MIGRATION_1_2_TEST_DATA = arrayOf(
- arrayOf(1, "[]", 0, 1, "NULL", "NULL", "NULL"),
- arrayOf(2, "[{\"keys\":[25]}]", 4, 1, "APP", "com.android.chrome", "[]"),
- arrayOf(3, "[{\"keys\":[25,24]}]", 0, 1, "KEY", "24", "[]"),
- arrayOf(4, "[{\"keys\":[25,24]}]", 0, 1, "KEYCODE", "24", "[]"),
- arrayOf(
- 5,
- "[{\"keys\":[25,24]},{\"keys\":[25]}]",
- 0,
- 1,
- "SYSTEM_ACTION",
- "toggle_flashlight",
- "[{\"data\":\"option_lens_back\",\"id\":\"extra_flash\"}]",
- ),
- arrayOf(6, "[{\"keys\":[4]}]", 3, 1, "SYSTEM_ACTION", "volume_mute", "[]"),
- )
-
- private val MIGRATION_1_2_EXPECTED_DATA = arrayOf(
- arrayOf(1, "{\"extras\":[],\"keys\":[],\"mode\":1}", "[]", "[]", 1, 0, "NULL", 1),
- arrayOf(
- 2,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.ANY_DEVICE\",\"keyCode\":25}],\"mode\":1}",
- "[{\"data\":\"com.android.chrome\",\"extras\":[],\"flags\":0,\"type\":\"APP\"}]",
- "[]",
- 1,
- 1,
- "NULL",
- 1,
- ),
- arrayOf(
- 3,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.ANY_DEVICE\",\"keyCode\":25},{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.ANY_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"24\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- arrayOf(
- 4,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.ANY_DEVICE\",\"keyCode\":25},{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.ANY_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"24\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- arrayOf(
- 5,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.ANY_DEVICE\",\"keyCode\":25},{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.ANY_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"toggle_flashlight\",\"extras\":[{\"data\":\"option_lens_back\",\"id\":\"extra_flash\"}],\"flags\":0,\"type\":\"SYSTEM_ACTION\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- arrayOf(
- 6,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.ANY_DEVICE\",\"keyCode\":25}],\"mode\":1}",
- "[{\"data\":\"toggle_flashlight\",\"extras\":[{\"data\":\"option_lens_back\",\"id\":\"extra_flash\"}],\"flags\":0,\"type\":\"SYSTEM_ACTION\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- arrayOf(
- 7,
- "{\"extras\":[],\"keys\":[{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.ANY_DEVICE\",\"keyCode\":4}],\"mode\":1}",
- "[{\"data\":\"volume_mute\",\"extras\":[],\"flags\":1,\"type\":\"SYSTEM_ACTION\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- )
-
- private val MIGRATION_2_3_TEST_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[{\"data\":\"610\",\"id\":\"extra_repeat_delay\"}],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25}],\"mode\":1}",
- "[{\"data\":\"10\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 1,
- "NULL",
- 1,
- ),
- arrayOf(2, "{\"extras\":[],\"keys\":[],\"mode\":1}", "[]", "[]", 1, 0, "NULL", 1),
- arrayOf(
- 3,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"enable_mobile_data\",\"extras\":[],\"flags\":0,\"type\":\"SYSTEM_ACTION\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- arrayOf(
- 4,
- "{\"extras\":[],\"keys\":[{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"14\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- )
-
- private val MIGRATION_2_3_EXPECTED_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[{\"data\":\"610\",\"id\":\"extra_repeat_delay\"}],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25}],\"mode\":1}",
- "[{\"data\":\"10\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 17,
- "NULL",
- 1,
- ),
- arrayOf(2, "{\"extras\":[],\"keys\":[],\"mode\":1}", "[]", "[]", 1, 0, "NULL", 1),
- arrayOf(
- 3,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"enable_mobile_data\",\"extras\":[],\"flags\":0,\"type\":\"SYSTEM_ACTION\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- arrayOf(
- 4,
- "{\"extras\":[],\"keys\":[{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"14\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 16,
- "NULL",
- 1,
- ),
- )
-
- private val MIGRATION_3_4_TEST_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[{\"data\":\"610\",\"id\":\"extra_repeat_delay\"}],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25}],\"mode\":1}",
- "[{\"data\":\"10\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 17,
- "NULL",
- 1,
- ),
- arrayOf(2, "{\"extras\":[],\"keys\":[],\"mode\":1}", "[]", "[]", 1, 0, "NULL", 1),
- arrayOf(
- 3,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"enable_mobile_data\",\"extras\":[],\"flags\":0,\"type\":\"SYSTEM_ACTION\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- arrayOf(
- 4,
- "{\"extras\":[],\"keys\":[{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"14\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 16,
- "NULL",
- 1,
- ),
- )
-
- private val MIGRATION_3_4_EXPECTED_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[{\"data\":\"610\",\"id\":\"extra_repeat_delay\"}],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25}],\"mode\":2}",
- "[{\"data\":\"10\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 17,
- "NULL",
- 1,
- ),
- arrayOf(2, "{\"extras\":[],\"keys\":[],\"mode\":2}", "[]", "[]", 1, 0, "NULL", 1),
- arrayOf(
- 3,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"enable_mobile_data\",\"extras\":[],\"flags\":0,\"type\":\"SYSTEM_ACTION\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- arrayOf(
- 4,
- "{\"extras\":[],\"keys\":[{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"14\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 16,
- "NULL",
- 1,
- ),
- )
-
- private val MIGRATION_4_5_TEST_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"volume_up\",\"extras\":[],\"flags\":1,\"type\":\"SYSTEM_ACTION\"},{\"data\":\"com.android.settings\",\"extras\":[],\"flags\":0,\"type\":\"APP\"}]",
- "[]",
- 1,
- 16,
- "NULL",
- 1,
- ),
- arrayOf(
- 2,
- "{\"extras\":[{\"data\":\"5000\",\"id\":\"extra_hold_down_until_repeat_delay\"},{\"data\":\"575\",\"id\":\"extra_repeat_delay\"},{\"data\":\"365\",\"id\":\"extra_vibration_duration\"}],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25}],\"mode\":2}",
- "[{\"data\":\"7\",\"extras\":[],\"flags\":0,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 19,
- "NULL",
- 1,
- ),
- )
-
- private val MIGRATION_4_5_EXPECTED_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":0}",
- "[{\"data\":\"volume_up\",\"extras\":[],\"flags\":5,\"type\":\"SYSTEM_ACTION\"},{\"data\":\"com.android.settings\",\"extras\":[],\"flags\":4,\"type\":\"APP\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- arrayOf(
- 2,
- "{\"extras\":[{\"data\":\"365\",\"id\":\"extra_vibration_duration\"}],\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25}],\"mode\":2}",
- "[{\"data\":\"7\",\"extras\":[{\"data\":\"5000\",\"id\":\"extra_hold_down_until_repeat_delay\"},{\"data\":\"575\",\"id\":\"extra_repeat_delay\"}],\"flags\":6,\"type\":\"KEY_EVENT\"}]",
- "[]",
- 1,
- 1,
- "NULL",
- 1,
- ),
- )
-
- private val MIGRATION_5_6_TEST_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[{\"data\":\"2930\",\"id\":\"extra_sequence_trigger_timeout\"},{\"data\":\"1840\",\"id\":\"extra_long_press_delay\"},{\"data\":\"3580\",\"id\":\"extra_double_press_timeout\"},{\"data\":\"390\",\"id\":\"extra_vibration_duration\"}],\"keys\":[{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":2,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":1}",
- "[{\"data\":\"volume_up\",\"extras\":[],\"flags\":1,\"type\":\"SYSTEM_ACTION\"}]",
- "[]",
- 1,
- 5,
- "NULL",
- 1,
- ),
- )
-
- private val MIGRATION_5_6_EXPECTED_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[{\"data\":\"2930\",\"id\":\"extra_sequence_trigger_timeout\"},{\"data\":\"1840\",\"id\":\"extra_long_press_delay\"},{\"data\":\"3580\",\"id\":\"extra_double_press_timeout\"},{\"data\":\"390\",\"id\":\"extra_vibration_duration\"}],\"flags\":5,\"keys\":[{\"clickType\":1,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":25},{\"clickType\":2,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"keyCode\":24}],\"mode\":1}",
- "[{\"data\":\"volume_up\",\"extras\":[],\"flags\":1,\"type\":\"SYSTEM_ACTION\"}]",
- "[]",
- 1,
- 0,
- "NULL",
- 1,
- ),
- )
-
- private val MIGRATION_9_10_TEST_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[],\"flags\":0,\"keys\":[],\"mode\":2}",
- "[{\"data\":\"com.google.android.contacts\",\"extras\":[],\"flags\":2,\"type\":\"APP\",\"uid\":\"dc2d8c69-aaa5-4471-b981-17cba7677c1a\"}]",
- "[]",
- 1,
- 0,
- "",
- 1,
- "d314e9e8-fac9-43e7-b540-0b9c0bfb4238",
- ),
- arrayOf(
- 2,
- "{\"extras\":[],\"flags\":1,\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"flags\":0,\"keyCode\":25,\"uid\":\"9d5d6f0b-1b9a-44ba-9406-1caaacea05de\"}],\"mode\":2}",
- "[{\"data\":\"com.discord\",\"extras\":[],\"flags\":2,\"type\":\"APP\",\"uid\":\"86c78374-59ce-4050-94a4-299f4778658f\"}]",
- "[]",
- 1,
- 0,
- "",
- 1,
- "b854ece7-2f0e-45c4-9cf3-bb5aa4fad288",
- ),
- arrayOf(
- 3,
- "{\"extras\":[],\"flags\":0,\"keys\":[],\"mode\":2}",
- "[{\"data\":\"com.google.android.vr.home\",\"extras\":[],\"flags\":2,\"type\":\"APP\",\"uid\":\"ca5b18ee-2673-4443-b2a7-cedef2c455b2\"},{\"data\":\"com.google.android.calendar\",\"extras\":[],\"flags\":0,\"type\":\"APP\",\"uid\":\"d274a5a8-f48e-4fbf-acbd-ed3c4b4ff377\"},{\"data\":\"enable_mobile_data\",\"extras\":[],\"flags\":2,\"type\":\"SYSTEM_ACTION\",\"uid\":\"f6b2afc5-4265-403d-8a0f-4eacf245286f\"}]",
- "[]",
- 1,
- 0,
- "",
- 1,
- "75ab7552-c175-4df4-9f50-1a9b86e717cc",
- ),
- )
-
- private val MIGRATION_9_10_EXPECTED_DATA = arrayOf(
- arrayOf(
- 1,
- "{\"extras\":[],\"flags\":16,\"keys\":[],\"mode\":2}",
- "[{\"data\":\"com.google.android.contacts\",\"extras\":[],\"flags\":0,\"type\":\"APP\",\"uid\":\"dc2d8c69-aaa5-4471-b981-17cba7677c1a\"}]",
- "[]",
- 1,
- 0,
- "",
- 1,
- "d314e9e8-fac9-43e7-b540-0b9c0bfb4238",
- ),
- arrayOf(
- 2,
- "{\"extras\":[],\"flags\":17,\"keys\":[{\"clickType\":0,\"deviceId\":\"io.github.sds100.keymapper.THIS_DEVICE\",\"flags\":0,\"keyCode\":25,\"uid\":\"9d5d6f0b-1b9a-44ba-9406-1caaacea05de\"}],\"mode\":2}",
- "[{\"data\":\"com.discord\",\"extras\":[],\"flags\":0,\"type\":\"APP\",\"uid\":\"86c78374-59ce-4050-94a4-299f4778658f\"}]",
- "[]",
- 1,
- 0,
- "",
- 1,
- "b854ece7-2f0e-45c4-9cf3-bb5aa4fad288",
- ),
- arrayOf(
- 3,
- "{\"extras\":[],\"flags\":16,\"keys\":[],\"mode\":2}",
- "[{\"data\":\"com.google.android.vr.home\",\"extras\":[],\"flags\":0,\"type\":\"APP\",\"uid\":\"ca5b18ee-2673-4443-b2a7-cedef2c455b2\"},{\"data\":\"com.google.android.calendar\",\"extras\":[],\"flags\":0,\"type\":\"APP\",\"uid\":\"d274a5a8-f48e-4fbf-acbd-ed3c4b4ff377\"},{\"data\":\"enable_mobile_data\",\"extras\":[],\"flags\":0,\"type\":\"SYSTEM_ACTION\",\"uid\":\"f6b2afc5-4265-403d-8a0f-4eacf245286f\"}]",
- "[]",
- 1,
- 0,
- "",
- 1,
- "75ab7552-c175-4df4-9f50-1a9b86e717cc",
- ),
- )
- }
-
- @get:Rule
- val helper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- AppDatabase::class.java.canonicalName,
- FrameworkSQLiteOpenHelperFactory(),
- )
-
- private val coroutineScope = MainScope()
-
- private lateinit var jsonParser: JsonParser
- private lateinit var gson: Gson
-
- @Before
- fun init() {
- jsonParser = JsonParser()
- gson = Gson()
- }
-
- /**
- * issue #612
- */
- @Test
- @Throws(IOException::class)
- fun migrate11to12() {
- val legacyFingerprintMapsDataStore = PreferenceDataStoreFactory.create(
- corruptionHandler = null,
- migrations = emptyList(),
- scope = coroutineScope,
- produceFile = { File.createTempFile("test", ".preferences_pb") },
- )
-
- val testDataFileName = "migration-11-12-test-data.json"
- val expectedDataFileName = "migration-11-12-expected-data.json"
- val testDataJson = getJsonFileText(testDataFileName)
-
- runBlocking {
- val rootElement = jsonParser.parse(testDataJson)
-
- legacyFingerprintMapsDataStore.edit { preferences ->
- preferences.putAll(
- stringPreferencesKey("swipe_down") to gson.toJson(rootElement["fingerprint_swipe_down"]),
- stringPreferencesKey("swipe_up") to gson.toJson(rootElement["fingerprint_swipe_up"]),
- stringPreferencesKey("swipe_left") to gson.toJson(rootElement["fingerprint_swipe_left"]),
- stringPreferencesKey("swipe_right") to gson.toJson(rootElement["fingerprint_swipe_right"]),
- )
- }
- }
-
- val fromVersion = 11
- val toVersion = 12
- val migration = AppDatabase.RoomMigration11To12(legacyFingerprintMapsDataStore)
- val keyMapColumnNameToJsonNameMap = mapOf(
- "id" to "id",
- "trigger" to "trigger",
- "action_list" to "actionList",
- "constraint_list" to "constraintList",
- "constraint_mode" to "constraintMode",
- "flags" to "flags",
- "folder_name" to "folderName",
- "is_enabled" to "isEnabled",
- "uid" to "uid",
- )
- val fingerprintMapColumnNameToJsonNameMap = mapOf(
- "id" to "id",
- "action_list" to "action_list",
- "constraint_list" to "constraints",
- "constraint_mode" to "constraint_mode",
- "extras" to "extras",
- "flags" to "flags",
- "is_enabled" to "enabled",
- )
-
- // do this without using the test() method because fingerprint maps are stored differently in test file
-
- helper.createDatabase(TEST_DB_NAME, fromVersion).apply {
- val keyMapJsonArray = getKeyMapListJsonFromFile(testDataFileName)
- insertMappingListJsonIntoDatabase(
- this,
- keyMapJsonArray,
- keyMapColumnNameToJsonNameMap,
- "keymaps",
- )
-
- val deviceInfoJsonArray = jsonParser.parse(testDataJson)["device_info"].asJsonArray
-
- deviceInfoJsonArray.forEach { element ->
-
- val descriptor = element["descriptor"].asString
- val name = element["name"].asString
-
- this.execSQL(
- """
- INSERT INTO deviceinfo (descriptor, name) VALUES ('$descriptor', '$name')
- """,
- )
- }
- // dont insert test data fingerprintmaps into database because they weren't in version 11
- }
-
- val db = helper.runMigrationsAndValidate(TEST_DB_NAME, toVersion, true, migration)
-
- val expectedKeyMapJsonList =
- getKeyMapListJsonFromFile(expectedDataFileName).map { element ->
-
- val values = keyMapColumnNameToJsonNameMap.values.map { key ->
- element.convertValueToJson(key).convertJsonValueToSqlValue()
- }
-
- values.toTypedArray()
- }
-
- testColumnsMatch(db, tableName = "keymaps", expectedKeyMapJsonList.toTypedArray())
-
- val expectedFingerprintMapJsonList =
- getFingerprintMapListJsonFromFile(expectedDataFileName).map { element ->
- val values = fingerprintMapColumnNameToJsonNameMap.values.map { key ->
- element.convertValueToJson(key).convertJsonValueToSqlValue()
- }
-
- values.toTypedArray()
- }
-
- testColumnsMatch(
- db,
- tableName = "fingerprintmaps",
- expectedFingerprintMapJsonList.toTypedArray(),
- )
- }
-
- /**
- * issue #621
- */
- @Test
- @Throws(IOException::class)
- fun migrate10to11() {
- test(
- fromVersion = 10,
- toVersion = 11,
- migration = AppDatabase.MIGRATION_10_11,
- testDataFileName = "migration-10-11-test-data.json",
- expectedDataFileName = "migration-10-11-expected-data.json",
- keyMapColumnNameToJsonNameMap = mapOf(
- "id" to "id",
- "trigger" to "trigger",
- "action_list" to "actionList",
- "constraint_list" to "constraintList",
- "constraint_mode" to "constraintMode",
- "flags" to "flags",
- "folder_name" to "folderName",
- "is_enabled" to "isEnabled",
- "uid" to "uid",
- ),
- )
- }
-
- @Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
- @Test
- @Throws(IOException::class)
- fun migrate9to10() {
- var db = helper.createDatabase(TEST_DB_NAME, 9).apply {
- MIGRATION_9_10_TEST_DATA.forEach { row ->
-
- execSQL(
- """
- INSERT INTO keymaps (id, trigger, action_list, constraint_list, constraint_mode, flags, folder_name, is_enabled, uid)
- VALUES (${row.joinToString { "'$it'" }})
- """,
- )
- }
- close()
- }
-
- db = helper.runMigrationsAndValidate(TEST_DB_NAME, 10, true, AppDatabase.MIGRATION_9_10)
-
- testColumnsMatch(db, tableName = "keymaps", MIGRATION_9_10_EXPECTED_DATA)
- }
-
- @Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
- @Test
- @Throws(IOException::class)
- fun migrate5to6() {
- var db = helper.createDatabase(TEST_DB_NAME, 5).apply {
- MIGRATION_5_6_TEST_DATA.forEach { row ->
-
- execSQL(
- """
- INSERT INTO keymaps (id, trigger, action_list, constraint_list, constraint_mode, flags, folder_name, is_enabled)
- VALUES (${row.joinToString { "'$it'" }})
- """,
- )
- }
- close()
- }
-
- db = helper.runMigrationsAndValidate(TEST_DB_NAME, 6, true, AppDatabase.MIGRATION_5_6)
-
- testColumnsMatch(db, tableName = "keymaps", MIGRATION_5_6_EXPECTED_DATA)
- }
-
- @Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
- @Test
- @Throws(IOException::class)
- fun migrate4to5() {
- var db = helper.createDatabase(TEST_DB_NAME, 4).apply {
- MIGRATION_4_5_TEST_DATA.forEach { row ->
-
- execSQL(
- """
- INSERT INTO keymaps (id, trigger, action_list, constraint_list, constraint_mode, flags, folder_name, is_enabled)
- VALUES (${row.joinToString { "'$it'" }})
- """,
- )
- }
- close()
- }
-
- db = helper.runMigrationsAndValidate(TEST_DB_NAME, 5, true, AppDatabase.MIGRATION_4_5)
-
- testColumnsMatch(db, tableName = "keymaps", MIGRATION_4_5_EXPECTED_DATA)
- }
-
- @Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
- @Test
- @Throws(IOException::class)
- fun migrate3to4() {
- var db = helper.createDatabase(TEST_DB_NAME, 3).apply {
- MIGRATION_3_4_TEST_DATA.forEach { row ->
-
- execSQL(
- """
- INSERT INTO keymaps (id, trigger, action_list, constraint_list, constraint_mode, flags, folder_name, is_enabled)
- VALUES (${row.joinToString { "'$it'" }})
- """,
- )
- }
- close()
- }
-
- db = helper.runMigrationsAndValidate(TEST_DB_NAME, 4, true, AppDatabase.MIGRATION_3_4)
-
- testColumnsMatch(db, tableName = "keymaps", MIGRATION_3_4_EXPECTED_DATA)
- }
-
- @Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
- @Test
- @Throws(IOException::class)
- fun migrate2to3() {
- var db = helper.createDatabase(TEST_DB_NAME, 2).apply {
- MIGRATION_2_3_TEST_DATA.forEach { row ->
-
- execSQL(
- """
- INSERT INTO keymaps (id, trigger, action_list, constraint_list, constraint_mode, flags, folder_name, is_enabled)
- VALUES (${row.joinToString { "'$it'" }})
- """,
- )
- }
- close()
- }
-
- db = helper.runMigrationsAndValidate(TEST_DB_NAME, 3, true, AppDatabase.MIGRATION_2_3)
-
- testColumnsMatch(db, tableName = "keymaps", MIGRATION_2_3_EXPECTED_DATA)
- }
-
- @Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
- @Test
- @Throws(IOException::class)
- fun migrate1to2() {
- var db = helper.createDatabase(TEST_DB_NAME, 1).apply {
- MIGRATION_1_2_TEST_DATA.forEach { row ->
-
- execSQL(
- """
- INSERT INTO keymaps (id, trigger_list, flags, is_enabled, action_type, action_data, action_extras)
- VALUES (${row.joinToString { "'$it'" }})
- """,
- )
- }
-
- close()
- }
-
- db = helper.runMigrationsAndValidate(TEST_DB_NAME, 2, true, AppDatabase.MIGRATION_1_2)
- testColumnsMatch(db, tableName = "keymaps", MIGRATION_1_2_EXPECTED_DATA)
- }
-
- private fun test(
- fromVersion: Int,
- toVersion: Int,
- migration: Migration,
- testDataFileName: String,
- expectedDataFileName: String,
- keyMapColumnNameToJsonNameMap: Map,
- fingerprintMapColumnNameToJsonNameMap: Map? = null,
- ) {
- helper.createDatabase(TEST_DB_NAME, fromVersion).apply {
- val keyMapJsonArray = getKeyMapListJsonFromFile(testDataFileName)
- insertMappingListJsonIntoDatabase(
- this,
- keyMapJsonArray,
- keyMapColumnNameToJsonNameMap,
- "keymaps",
- )
-
- if (fingerprintMapColumnNameToJsonNameMap != null) {
- val fingerprintMapJsonArray = getFingerprintMapListJsonFromFile(testDataFileName)
- insertMappingListJsonIntoDatabase(
- this,
- fingerprintMapJsonArray,
- fingerprintMapColumnNameToJsonNameMap,
- "fingerprintmaps",
- )
- }
- }
-
- val db = helper.runMigrationsAndValidate(TEST_DB_NAME, toVersion, true, migration)
-
- val expectedKeyMapJsonList =
- getKeyMapListJsonFromFile(expectedDataFileName).map { element ->
-
- val values = keyMapColumnNameToJsonNameMap.values.map { key ->
- element.convertValueToJson(key).convertJsonValueToSqlValue()
- }
-
- values.toTypedArray()
- }
-
- testColumnsMatch(db, tableName = "keymaps", expectedKeyMapJsonList.toTypedArray())
-
- if (fingerprintMapColumnNameToJsonNameMap != null) {
- val expectedFingerprintMapJsonList =
- getFingerprintMapListJsonFromFile(expectedDataFileName).map { element ->
- val values = fingerprintMapColumnNameToJsonNameMap.values.map { key ->
- element.convertValueToJson(key).convertJsonValueToSqlValue()
- }
-
- values.toTypedArray()
- }
-
- testColumnsMatch(
- db,
- tableName = "fingerprintmaps",
- expectedFingerprintMapJsonList.toTypedArray(),
- )
- }
- }
-
- private fun insertMappingListJsonIntoDatabase(
- database: SupportSQLiteDatabase,
- mappingListJson: JsonArray,
- columnNameToJsonMap: Map,
- tableName: String,
- ) {
- mappingListJson.forEach { element ->
- val jsonElementNames = columnNameToJsonMap.values
-
- val values = jsonElementNames.map { key ->
- element.convertValueToJson(key).convertJsonValueToSqlValue()
- }
-
- database.execSQL(
- """
- INSERT INTO $tableName (${columnNameToJsonMap.keys.joinToString()})
- VALUES (${values.joinToString { "'$it'" }})
- """,
- )
- }
- }
-
- private fun String?.convertJsonValueToSqlValue() =
- when {
- this == null -> "NULL"
- this == "true" -> 1
- this == "false" -> 0
- this.toIntOrNull() != null -> this.toInt()
- else -> this
- }
-
- private fun JsonElement.convertValueToJson(key: String) = try {
- gson.toJson(this[key])
- } catch (e: NoSuchElementException) {
- null
- }
-
- private fun getKeyMapListJsonFromFile(fileName: String): JsonArray {
- val json = getJsonFileText(fileName)
-
- val rootElement = jsonParser.parse(json)
-
- return rootElement["keymap_list"].asJsonArray
- }
-
- private fun getFingerprintMapListJsonFromFile(fileName: String): JsonArray {
- val json = getJsonFileText(fileName)
-
- val rootElement = jsonParser.parse(json)
-
- return rootElement["fingerprint_map_list"].asJsonArray
- }
-
- private fun getJsonFileText(fileName: String): String {
- val inputStream =
- this.javaClass.classLoader!!.getResourceAsStream("json-migration-test/$fileName")
- return inputStream.bufferedReader().use { it.readText() }
- }
-
- private fun testColumnsMatch(
- db: SupportSQLiteDatabase,
- tableName: String,
- expectedData: Array>,
- ) {
- val cursor = db.query("SELECT * FROM $tableName")
-
- ViewMatchers.assertThat("Check the logcat", cursor.count, Matchers.`is`(expectedData.size))
-
- while (cursor.moveToNext()) {
- val row = cursor.position
- val expectedColumnValues: Array = expectedData[row]
-
- cursor.columnNames.forEachIndexed { columnIndex, columnName ->
-
- val expectedColumnValue = expectedColumnValues[columnIndex]
-
- val columnValue: Any = when (expectedColumnValue) {
- is Int -> cursor.getInt(columnIndex)
- is String -> cursor.getString(columnIndex)
- else -> throw Exception("Don't know how to get this type ${expectedColumnValue::class.simpleName} from cursor")
- }
-
- when (expectedColumnValue) {
- is Int -> ViewMatchers.assertThat(
- "$columnName at row $row doesn't match",
- columnValue,
- Matchers.`is`(expectedColumnValue),
- )
- is String -> {
- try {
- JsonTestUtils.compareBothWays(
- element = jsonParser.parse(expectedColumnValue),
- elementName = "expected $columnName at row $row",
- other = jsonParser.parse(columnValue as String),
- otherName = "migrated $columnName at row $row",
- )
- } catch (e: JsonParseException) {
- ViewMatchers.assertThat(
- "$columnName at row $row doesn't match",
- columnValue,
- Matchers.`is`(expectedColumnValue),
- )
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml
index 0e97b14435..bc42450e26 100644
--- a/app/src/debug/res/values/strings.xml
+++ b/app/src/debug/res/values/strings.xml
@@ -1,5 +1,5 @@
- Key Mapper Debug
+ Key Mapper DebugKey Mapper Debug Basic Input Method
\ No newline at end of file
diff --git a/app/src/free/java/io/github/sds100/keymapper/MainActivity.kt b/app/src/free/java/io/github/sds100/keymapper/MainActivity.kt
deleted file mode 100644
index 51dddd9b2e..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/MainActivity.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package io.github.sds100.keymapper
-
-class MainActivity : BaseMainActivity()
diff --git a/app/src/free/java/io/github/sds100/keymapper/floating/FloatingLayoutsScreen.kt b/app/src/free/java/io/github/sds100/keymapper/floating/FloatingLayoutsScreen.kt
deleted file mode 100644
index ed0f903e9e..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/floating/FloatingLayoutsScreen.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package io.github.sds100.keymapper.floating
-
-import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.navigation.NavHostController
-
-@Composable
-fun FloatingLayoutsScreen(
- modifier: Modifier = Modifier,
- viewModel: ListFloatingLayoutsViewModel,
- navController: NavHostController,
- lazyListState: LazyListState,
-) {
-}
diff --git a/app/src/free/java/io/github/sds100/keymapper/floating/ListFloatingLayoutsUseCase.kt b/app/src/free/java/io/github/sds100/keymapper/floating/ListFloatingLayoutsUseCase.kt
deleted file mode 100644
index 5a1a8786de..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/floating/ListFloatingLayoutsUseCase.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package io.github.sds100.keymapper.floating
-
-import io.github.sds100.keymapper.data.repositories.FloatingLayoutRepository
-import io.github.sds100.keymapper.data.repositories.PreferenceRepository
-import io.github.sds100.keymapper.purchasing.PurchasingManager
-import io.github.sds100.keymapper.system.accessibility.ServiceAdapter
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
-
-interface ListFloatingLayoutsUseCase {
- val showFloatingLayouts: Flow
-}
-
-class ListFloatingLayoutsUseCaseImpl(
- private val repository: FloatingLayoutRepository,
- private val purchasingManager: PurchasingManager,
- private val serviceAdapter: ServiceAdapter,
- private val preferences: PreferenceRepository,
-) : ListFloatingLayoutsUseCase,
- PurchasingManager by purchasingManager {
-
- override val showFloatingLayouts: Flow = flowOf(false)
-}
diff --git a/app/src/free/java/io/github/sds100/keymapper/floating/ListFloatingLayoutsViewModel.kt b/app/src/free/java/io/github/sds100/keymapper/floating/ListFloatingLayoutsViewModel.kt
deleted file mode 100644
index f3e8a9dd9a..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/floating/ListFloatingLayoutsViewModel.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package io.github.sds100.keymapper.floating
-
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.PopupViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-
-class ListFloatingLayoutsViewModel(
- val coroutineScope: CoroutineScope,
- val useCase: ListFloatingLayoutsUseCase,
- resourceProvider: ResourceProvider,
-) : PopupViewModel by PopupViewModelImpl() {
- val state: StateFlow = MutableStateFlow(FloatingLayoutsState.NotPurchased)
- val showFabText: Boolean = false
-
- fun onNewLayoutClick() {
- }
-}
-
-sealed class FloatingLayoutsState {
- data object Loading : FloatingLayoutsState()
- data object NotPurchased : FloatingLayoutsState()
- data object Purchased : FloatingLayoutsState()
-}
diff --git a/app/src/free/java/io/github/sds100/keymapper/home/HomeFloatingLayoutsScreen.kt b/app/src/free/java/io/github/sds100/keymapper/home/HomeFloatingLayoutsScreen.kt
deleted file mode 100644
index aca43b669c..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/home/HomeFloatingLayoutsScreen.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package io.github.sds100.keymapper.home
-
-import androidx.compose.material3.SnackbarHostState
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
-import androidx.navigation.NavHostController
-import io.github.sds100.keymapper.floating.ListFloatingLayoutsViewModel
-
-@Composable
-fun HomeFloatingLayoutsScreen(
- modifier: Modifier = Modifier,
- viewModel: ListFloatingLayoutsViewModel,
- navController: NavHostController,
- snackbarState: SnackbarHostState,
- fabBottomPadding: Dp,
-) {
-}
diff --git a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AdvancedTriggersBottomSheet.kt b/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AdvancedTriggersBottomSheet.kt
deleted file mode 100644
index 3d6731ccdf..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AdvancedTriggersBottomSheet.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps.trigger
-
-import androidx.compose.foundation.layout.Arrangement
-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.padding
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.FilledTonalButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.ModalBottomSheet
-import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.SheetState
-import androidx.compose.material3.SheetValue.Expanded
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalUriHandler
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import kotlinx.coroutines.launch
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun AdvancedTriggersBottomSheet(
- modifier: Modifier = Modifier,
- onDismissRequest: () -> Unit,
- viewModel: ConfigTriggerViewModel,
- sheetState: SheetState,
-) {
- AdvancedTriggersBottomSheet(
- modifier,
- onDismissRequest,
- sheetState,
- )
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun AdvancedTriggersBottomSheet(
- modifier: Modifier = Modifier,
- onDismissRequest: () -> Unit,
- sheetState: SheetState,
-) {
- val scope = rememberCoroutineScope()
-
- ModalBottomSheet(
- modifier = modifier,
- onDismissRequest = onDismissRequest,
- sheetState = sheetState,
- // Hide drag handle because other bottom sheets don't have it
- dragHandle = {},
- ) {
- Spacer(modifier = Modifier.height(8.dp))
-
- Text(
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center,
- text = stringResource(R.string.advanced_triggers_sheet_title),
- style = MaterialTheme.typography.headlineMedium,
- )
-
- Spacer(modifier = Modifier.height(8.dp))
-
- Text(
- modifier = Modifier
- .padding(horizontal = 16.dp)
- .fillMaxWidth(),
- text = stringResource(R.string.advanced_triggers_sheet_text),
- )
-
- Spacer(modifier = Modifier.height(8.dp))
-
- Text(
- modifier = Modifier
- .padding(horizontal = 16.dp)
- .fillMaxWidth(),
- text = stringResource(R.string.purchasing_not_implemented_bottom_sheet_text),
- fontStyle = FontStyle.Italic,
- )
-
- Spacer(modifier = Modifier.height(8.dp))
-
- val uriHandler = LocalUriHandler.current
- val googlePlayUrl = stringResource(R.string.url_play_store_listing)
-
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = 16.dp),
- horizontalArrangement = Arrangement.SpaceBetween,
- ) {
- OutlinedButton(
- modifier = Modifier,
- onClick = {
- scope.launch {
- sheetState.hide()
- onDismissRequest()
- }
- },
- ) {
- Text(stringResource(R.string.neg_cancel))
- }
-
- FilledTonalButton(
- modifier = Modifier,
- onClick = {
- scope.launch {
- uriHandler.openUri(googlePlayUrl)
- }
- },
- ) {
- Text(stringResource(R.string.purchasing_download_key_mapper_from_google_play))
- }
- }
-
- Spacer(Modifier.height(16.dp))
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Preview
-@Composable
-private fun Preview() {
- KeyMapperTheme {
- val sheetState = SheetState(
- skipPartiallyExpanded = true,
- density = LocalDensity.current,
- initialValue = Expanded,
- )
-
- AdvancedTriggersBottomSheet(
- onDismissRequest = {},
- sheetState = sheetState,
- )
- }
-}
diff --git a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerSetupBottomSheet.kt b/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerSetupBottomSheet.kt
deleted file mode 100644
index 3730a02096..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerSetupBottomSheet.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps.trigger
-
-import androidx.compose.runtime.Composable
-
-@Composable
-fun HandleAssistantTriggerSetupBottomSheet(
- viewModel: ConfigTriggerViewModel,
-) {
-}
diff --git a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/ConfigTriggerViewModel.kt b/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/ConfigTriggerViewModel.kt
deleted file mode 100644
index cb30491821..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/ConfigTriggerViewModel.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps.trigger
-
-import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCase
-import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCase
-import io.github.sds100.keymapper.mappings.keymaps.CreateKeyMapShortcutUseCase
-import io.github.sds100.keymapper.mappings.keymaps.DisplayKeyMapUseCase
-import io.github.sds100.keymapper.onboarding.OnboardingUseCase
-import io.github.sds100.keymapper.purchasing.PurchasingManager
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import kotlinx.coroutines.CoroutineScope
-
-class ConfigTriggerViewModel(
- coroutineScope: CoroutineScope,
- onboarding: OnboardingUseCase,
- config: ConfigKeyMapUseCase,
- recordTrigger: RecordTriggerUseCase,
- createKeyMapShortcut: CreateKeyMapShortcutUseCase,
- displayKeyMap: DisplayKeyMapUseCase,
- resourceProvider: ResourceProvider,
- purchasingManager: PurchasingManager,
- setupGuiKeyboardUseCase: SetupGuiKeyboardUseCase,
- fingerprintGesturesSupported: FingerprintGesturesSupportedUseCase,
-) : BaseConfigTriggerViewModel(
- coroutineScope,
- onboarding,
- config,
- recordTrigger,
- createKeyMapShortcut,
- displayKeyMap,
- purchasingManager,
- setupGuiKeyboardUseCase,
- fingerprintGesturesSupported,
- resourceProvider,
-) {
- fun onEditFloatingButtonClick() {}
- fun onEditFloatingLayoutClick() {}
-}
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
deleted file mode 100644
index 88a2d9a5ee..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/purchasing/PurchasingManagerImpl.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package io.github.sds100.keymapper.purchasing
-
-import android.content.Context
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.State
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class PurchasingManagerImpl(
- context: Context,
- private val coroutineScope: CoroutineScope,
-) : PurchasingManager {
- override val onCompleteProductPurchase: MutableSharedFlow = MutableSharedFlow()
- override val purchases: Flow>>> =
- MutableStateFlow(State.Data(Error.PurchasingNotImplemented))
-
- override suspend fun launchPurchasingFlow(product: ProductId): Result {
- return Error.PurchasingNotImplemented
- }
-
- override suspend fun getProductPrice(product: ProductId): Result {
- return Error.PurchasingNotImplemented
- }
-
- override suspend fun isPurchased(product: ProductId): Result {
- return Error.PurchasingNotImplemented
- }
-
- override fun refresh() {}
-}
diff --git a/app/src/free/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt b/app/src/free/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt
deleted file mode 100644
index 8e0fb9dda5..0000000000
--- a/app/src/free/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-package io.github.sds100.keymapper.system.accessibility
-
-import io.github.sds100.keymapper.actions.PerformActionsUseCase
-import io.github.sds100.keymapper.constraints.DetectConstraintsUseCase
-import io.github.sds100.keymapper.data.repositories.AccessibilityNodeRepository
-import io.github.sds100.keymapper.data.repositories.PreferenceRepository
-import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCase
-import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCase
-import io.github.sds100.keymapper.mappings.keymaps.detection.DetectKeyMapsUseCase
-import io.github.sds100.keymapper.reroutekeyevents.RerouteKeyEventsUseCase
-import io.github.sds100.keymapper.system.devices.DevicesAdapter
-import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter
-import io.github.sds100.keymapper.system.root.SuAdapter
-import io.github.sds100.keymapper.util.ServiceEvent
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-
-class AccessibilityServiceController(
- coroutineScope: CoroutineScope,
- accessibilityService: MyAccessibilityService,
- inputEvents: SharedFlow,
- outputEvents: MutableSharedFlow,
- detectConstraintsUseCase: DetectConstraintsUseCase,
- performActionsUseCase: PerformActionsUseCase,
- detectKeyMapsUseCase: DetectKeyMapsUseCase,
- fingerprintGesturesSupportedUseCase: FingerprintGesturesSupportedUseCase,
- rerouteKeyEventsUseCase: RerouteKeyEventsUseCase,
- pauseKeyMapsUseCase: PauseKeyMapsUseCase,
- devicesAdapter: DevicesAdapter,
- suAdapter: SuAdapter,
- inputMethodAdapter: InputMethodAdapter,
- settingsRepository: PreferenceRepository,
- nodeRepository: AccessibilityNodeRepository,
-) : BaseAccessibilityServiceController(
- coroutineScope,
- accessibilityService,
- inputEvents,
- outputEvents,
- detectConstraintsUseCase,
- performActionsUseCase,
- detectKeyMapsUseCase,
- fingerprintGesturesSupportedUseCase,
- rerouteKeyEventsUseCase,
- pauseKeyMapsUseCase,
- devicesAdapter,
- suAdapter,
- inputMethodAdapter,
- settingsRepository,
- nodeRepository,
-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c19777221d..fd3a99ee47 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,78 +2,13 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ tools:ignore="GoogleAppIndexingWarning,MissingTvBanner">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/assets/whats-new.txt b/app/src/main/assets/whats-new.txt
deleted file mode 100644
index aa0d3355cb..0000000000
--- a/app/src/main/assets/whats-new.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-Fix for Minecraft 1.21.80!
-
-⏰ Time constraints.
-
-🔎 Action to interact with app elements.
-
-Other bug fixes.
-
-== 3.0 features ==
-
-🫧 Floating Buttons: you can create custom on-screen buttons to trigger key maps.
-
-🗂️ Grouping key maps into folders with shared constraints.
-
-🔦 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.
-
-❤️ 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/app/src/main/java/io/github/sds100/keymapper/ActivityViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/ActivityViewModel.kt
deleted file mode 100644
index 8fefdddbe4..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/ActivityViewModel.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package io.github.sds100.keymapper
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewModelScope
-import io.github.sds100.keymapper.util.ui.NavigationViewModel
-import io.github.sds100.keymapper.util.ui.NavigationViewModelImpl
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.PopupViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.ViewModelHelper
-import kotlinx.coroutines.launch
-
-/**
- * Created by sds100 on 23/07/2021.
- */
-class ActivityViewModel(
- resourceProvider: ResourceProvider,
-) : ViewModel(),
- ResourceProvider by resourceProvider,
- PopupViewModel by PopupViewModelImpl(),
- NavigationViewModel by NavigationViewModelImpl() {
-
- var handledActivityLaunchIntent: Boolean = false
- var previousNightMode: Int? = null
-
- fun onCantFindAccessibilitySettings() {
- viewModelScope.launch {
- ViewModelHelper.handleCantFindAccessibilitySettings(
- resourceProvider = this@ActivityViewModel,
- popupViewModel = this@ActivityViewModel,
- )
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- class Factory(
- private val resourceProvider: ResourceProvider,
- ) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T =
- ActivityViewModel(resourceProvider) as T
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/AppHiltModule.kt b/app/src/main/java/io/github/sds100/keymapper/AppHiltModule.kt
new file mode 100644
index 0000000000..286adf22ad
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/AppHiltModule.kt
@@ -0,0 +1,60 @@
+package io.github.sds100.keymapper
+
+import android.os.Build
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import io.github.sds100.keymapper.base.purchasing.PurchasingManager
+import io.github.sds100.keymapper.common.BuildConfigProvider
+import io.github.sds100.keymapper.common.KeyMapperClassProvider
+import io.github.sds100.keymapper.common.utils.DefaultDispatcherProvider
+import io.github.sds100.keymapper.common.utils.DispatcherProvider
+import io.github.sds100.keymapper.purchasing.PurchasingManagerImpl
+import io.github.sds100.keymapper.system.accessibility.MyAccessibilityService
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.MainScope
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class AppHiltModule {
+ @Singleton
+ @Provides
+ fun provideCoroutineScope(): CoroutineScope = MainScope()
+
+ @Provides
+ @Singleton
+ fun provideDispatchers(): DispatcherProvider = DefaultDispatcherProvider()
+
+ @Singleton
+ @Provides
+ fun provideBuildConfigProvider(): BuildConfigProvider = object : BuildConfigProvider {
+ override val minApi: Int
+ get() = Build.VERSION_CODES.LOLLIPOP
+ override val maxApi: Int
+ get() = 1000
+ override val packageName: String
+ get() = BuildConfig.APPLICATION_ID
+ override val version: String
+ get() = BuildConfig.VERSION_NAME
+ override val versionCode: Int
+ get() = BuildConfig.VERSION_CODE
+ }
+
+ @Singleton
+ @Provides
+ fun provideClassProvider(): KeyMapperClassProvider = object : KeyMapperClassProvider {
+ override fun getMainActivity(): Class<*> {
+ return MainActivity::class.java
+ }
+
+ override fun getAccessibilityService(): Class<*> {
+ return MyAccessibilityService::class.java
+ }
+ }
+
+ @Provides
+ @Singleton
+ fun providePurchasingManager(): PurchasingManager = PurchasingManagerImpl()
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/Constants.kt b/app/src/main/java/io/github/sds100/keymapper/Constants.kt
deleted file mode 100644
index 7ba1f736d8..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/Constants.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package io.github.sds100.keymapper
-
-import android.os.Build
-
-/**
- * Created by sds100 on 22/11/2018.
- */
-object Constants {
- const val MIN_API = Build.VERSION_CODES.LOLLIPOP
- const val MAX_API = 1000
- const val PACKAGE_NAME = BuildConfig.APPLICATION_ID
- const val VERSION = BuildConfig.VERSION_NAME
- const val VERSION_CODE = BuildConfig.VERSION_CODE
- const val MIN_API_FLOATING_BUTTONS = Build.VERSION_CODES.R
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt
index 4019a372cc..eb29f31ee0 100644
--- a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt
+++ b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt
@@ -1,320 +1,13 @@
package io.github.sds100.keymapper
import android.annotation.SuppressLint
-import android.content.Intent
-import android.os.Build
-import android.os.UserManager
-import android.util.Log
-import androidx.appcompat.app.AppCompatDelegate
-import androidx.core.content.getSystemService
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.OnLifecycleEvent
-import androidx.lifecycle.ProcessLifecycleOwner
-import androidx.multidex.MultiDexApplication
-import io.github.sds100.keymapper.actions.uielement.InteractUiElementController
-import io.github.sds100.keymapper.data.Keys
-import io.github.sds100.keymapper.data.entities.LogEntryEntity
-import io.github.sds100.keymapper.logging.KeyMapperLoggingTree
-import io.github.sds100.keymapper.mappings.keymaps.trigger.RecordTriggerController
-import io.github.sds100.keymapper.purchasing.PurchasingManagerImpl
-import io.github.sds100.keymapper.settings.ThemeUtils
-import io.github.sds100.keymapper.shizuku.ShizukuAdapterImpl
-import io.github.sds100.keymapper.system.AndroidSystemFeatureAdapter
-import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter
-import io.github.sds100.keymapper.system.airplanemode.AndroidAirplaneModeAdapter
-import io.github.sds100.keymapper.system.apps.AndroidAppShortcutAdapter
-import io.github.sds100.keymapper.system.apps.AndroidPackageManagerAdapter
-import io.github.sds100.keymapper.system.bluetooth.AndroidBluetoothAdapter
-import io.github.sds100.keymapper.system.camera.AndroidCameraAdapter
-import io.github.sds100.keymapper.system.clipboard.AndroidClipboardAdapter
-import io.github.sds100.keymapper.system.devices.AndroidDevicesAdapter
-import io.github.sds100.keymapper.system.display.AndroidDisplayAdapter
-import io.github.sds100.keymapper.system.files.AndroidFileAdapter
-import io.github.sds100.keymapper.system.inputmethod.AndroidInputMethodAdapter
-import io.github.sds100.keymapper.system.inputmethod.AutoSwitchImeController
-import io.github.sds100.keymapper.system.inputmethod.ShowHideInputMethodUseCaseImpl
-import io.github.sds100.keymapper.system.intents.IntentAdapterImpl
-import io.github.sds100.keymapper.system.leanback.LeanbackAdapterImpl
-import io.github.sds100.keymapper.system.lock.AndroidLockScreenAdapter
-import io.github.sds100.keymapper.system.media.AndroidMediaAdapter
-import io.github.sds100.keymapper.system.network.AndroidNetworkAdapter
-import io.github.sds100.keymapper.system.nfc.AndroidNfcAdapter
-import io.github.sds100.keymapper.system.notifications.AndroidNotificationAdapter
-import io.github.sds100.keymapper.system.notifications.ManageNotificationsUseCaseImpl
-import io.github.sds100.keymapper.system.notifications.NotificationController
-import io.github.sds100.keymapper.system.notifications.NotificationReceiverAdapter
-import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter
-import io.github.sds100.keymapper.system.permissions.AutoGrantPermissionController
-import io.github.sds100.keymapper.system.permissions.Permission
-import io.github.sds100.keymapper.system.phone.AndroidPhoneAdapter
-import io.github.sds100.keymapper.system.popup.AndroidToastAdapter
-import io.github.sds100.keymapper.system.power.AndroidPowerAdapter
-import io.github.sds100.keymapper.system.ringtones.AndroidRingtoneAdapter
-import io.github.sds100.keymapper.system.root.SuAdapterImpl
-import io.github.sds100.keymapper.system.url.AndroidOpenUrlAdapter
-import io.github.sds100.keymapper.system.vibrator.AndroidVibratorAdapter
-import io.github.sds100.keymapper.system.volume.AndroidVolumeAdapter
-import io.github.sds100.keymapper.util.ui.ResourceProviderImpl
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import splitties.toast.toast
-import timber.log.Timber
-import java.util.Calendar
+import dagger.hilt.android.HiltAndroidApp
+import io.github.sds100.keymapper.base.BaseKeyMapperApp
-/**
- * Created by sds100 on 19/05/2020.
- */
@SuppressLint("LogNotTimber")
-class KeyMapperApp : MultiDexApplication() {
- private val tag = KeyMapperApp::class.simpleName
-
- val appCoroutineScope = MainScope()
-
- val notificationAdapter by lazy { AndroidNotificationAdapter(this, appCoroutineScope) }
-
- lateinit var notificationController: NotificationController
- lateinit var autoSwitchImeController: AutoSwitchImeController
-
- val resourceProvider by lazy { ResourceProviderImpl(this, appCoroutineScope) }
-
- val bluetoothMonitor by lazy { AndroidBluetoothAdapter(this, appCoroutineScope) }
-
- val packageManagerAdapter by lazy {
- AndroidPackageManagerAdapter(
- this,
- appCoroutineScope,
- )
- }
-
- val inputMethodAdapter by lazy {
- AndroidInputMethodAdapter(
- this,
- appCoroutineScope,
- accessibilityServiceAdapter,
- permissionAdapter,
- suAdapter,
- )
- }
- val devicesAdapter by lazy {
- AndroidDevicesAdapter(
- this,
- bluetoothMonitor,
- permissionAdapter,
- appCoroutineScope,
- )
- }
- val cameraAdapter by lazy { AndroidCameraAdapter(this) }
- val permissionAdapter by lazy {
- AndroidPermissionAdapter(
- this,
- appCoroutineScope,
- suAdapter,
- notificationReceiverAdapter,
- ServiceLocator.settingsRepository(this),
- packageManagerAdapter,
- )
- }
-
- val systemFeatureAdapter by lazy { AndroidSystemFeatureAdapter(this) }
- val accessibilityServiceAdapter by lazy { AccessibilityServiceAdapter(this, appCoroutineScope) }
- val notificationReceiverAdapter by lazy { NotificationReceiverAdapter(this, appCoroutineScope) }
- val appShortcutAdapter by lazy { AndroidAppShortcutAdapter(this) }
- val fileAdapter by lazy { AndroidFileAdapter(this) }
- val popupMessageAdapter by lazy { AndroidToastAdapter(this) }
- val vibratorAdapter by lazy { AndroidVibratorAdapter(this) }
- val displayAdapter by lazy { AndroidDisplayAdapter(this, coroutineScope = appCoroutineScope) }
- val audioAdapter by lazy { AndroidVolumeAdapter(this) }
- val suAdapter by lazy {
- SuAdapterImpl(
- appCoroutineScope,
- ServiceLocator.settingsRepository(this),
- )
- }
- val phoneAdapter by lazy { AndroidPhoneAdapter(this, appCoroutineScope) }
- val intentAdapter by lazy { IntentAdapterImpl(this) }
- val mediaAdapter by lazy { AndroidMediaAdapter(this, appCoroutineScope) }
- val lockScreenAdapter by lazy { AndroidLockScreenAdapter(this) }
- val airplaneModeAdapter by lazy { AndroidAirplaneModeAdapter(this, suAdapter) }
- val networkAdapter by lazy { AndroidNetworkAdapter(this, suAdapter) }
- val nfcAdapter by lazy { AndroidNfcAdapter(this, suAdapter) }
- val openUrlAdapter by lazy { AndroidOpenUrlAdapter(this) }
- val clipboardAdapter by lazy { AndroidClipboardAdapter(this) }
- val shizukuAdapter by lazy { ShizukuAdapterImpl(appCoroutineScope, packageManagerAdapter) }
- val leanbackAdapter by lazy { LeanbackAdapterImpl(this) }
- val powerAdapter by lazy { AndroidPowerAdapter(this) }
-
- val recordTriggerController by lazy {
- RecordTriggerController(appCoroutineScope, accessibilityServiceAdapter)
- }
-
- val interactUiElementController by lazy {
- InteractUiElementController(
- appCoroutineScope,
- accessibilityServiceAdapter,
- ServiceLocator.accessibilityNodeRepository(this),
- packageManagerAdapter,
- )
- }
-
- val autoGrantPermissionController by lazy {
- AutoGrantPermissionController(
- appCoroutineScope,
- permissionAdapter,
- popupMessageAdapter,
- resourceProvider,
- )
- }
-
- val purchasingManager: PurchasingManagerImpl by lazy {
- PurchasingManagerImpl(this.applicationContext, appCoroutineScope)
- }
-
- val ringtoneManagerAdapter: AndroidRingtoneAdapter by lazy {
- AndroidRingtoneAdapter(this)
- }
-
- private val loggingTree by lazy {
- KeyMapperLoggingTree(
- appCoroutineScope,
- ServiceLocator.settingsRepository(this),
- ServiceLocator.logRepository(this),
- )
- }
-
- private val processLifecycleOwner by lazy { ProcessLifecycleOwner.get() }
-
- private val userManager: UserManager? by lazy { getSystemService() }
-
- private val initLock: Any = Any()
- private var initialized = false
-
- override fun onCreate() {
- val priorExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
-
- Log.i(tag, "KeyMapperApp: OnCreate")
-
- Thread.setDefaultUncaughtExceptionHandler { thread, exception ->
- // log in a blocking manner and always log regardless of whether the setting is turned on
- val entry = LogEntryEntity(
- id = 0,
- time = Calendar.getInstance().timeInMillis,
- severity = LogEntryEntity.SEVERITY_ERROR,
- message = exception.stackTraceToString(),
- )
-
- runBlocking {
- ServiceLocator.logRepository(this@KeyMapperApp).insertSuspend(entry)
- }
-
- priorExceptionHandler?.uncaughtException(thread, exception)
- }
-
- super.onCreate()
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && userManager?.isUserUnlocked == false) {
- Log.i(tag, "KeyMapperApp: Delay init because locked.")
- // If the device is still encrypted and locked do not initialize anything that
- // may potentially need the encrypted app storage like databases.
- return
- }
-
- synchronized(initLock) {
- init()
- initialized = true
- }
- }
-
- fun onBootUnlocked() {
- synchronized(initLock) {
- if (!initialized) {
- init()
- }
- initialized = true
- }
- }
-
- private fun init() {
- Log.i(tag, "KeyMapperApp: Init")
-
- ServiceLocator.settingsRepository(this).get(Keys.darkTheme)
- .map { it?.toIntOrNull() }
- .map {
- when (it) {
- ThemeUtils.DARK -> AppCompatDelegate.MODE_NIGHT_YES
- ThemeUtils.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
- else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
- }
- }
- .onEach { mode -> AppCompatDelegate.setDefaultNightMode(mode) }
- .launchIn(appCoroutineScope)
-
- if (BuildConfig.BUILD_TYPE == "debug" || BuildConfig.BUILD_TYPE == "debug_release") {
- Timber.plant(Timber.DebugTree())
- }
-
- Timber.plant(loggingTree)
-
- notificationController = NotificationController(
- appCoroutineScope,
- ManageNotificationsUseCaseImpl(
- ServiceLocator.settingsRepository(this),
- notificationAdapter,
- suAdapter,
- permissionAdapter,
- ),
- UseCases.pauseKeyMaps(this),
- UseCases.showImePicker(this),
- UseCases.controlAccessibilityService(this),
- UseCases.toggleCompatibleIme(this),
- ShowHideInputMethodUseCaseImpl(ServiceLocator.accessibilityServiceAdapter(this)),
- UseCases.onboarding(this),
- ServiceLocator.resourceProvider(this),
- )
-
- autoSwitchImeController = AutoSwitchImeController(
- appCoroutineScope,
- ServiceLocator.settingsRepository(this),
- ServiceLocator.inputMethodAdapter(this),
- UseCases.pauseKeyMaps(this),
- devicesAdapter,
- popupMessageAdapter,
- resourceProvider,
- ServiceLocator.accessibilityServiceAdapter(this),
- )
-
- processLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
- @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
- fun onResume() {
- // when the user returns to the app let everything know that the permissions could have changed
- notificationController.onOpenApp()
-
- if (BuildConfig.DEBUG && permissionAdapter.isGranted(Permission.WRITE_SECURE_SETTINGS)) {
- accessibilityServiceAdapter.start()
- }
- }
- })
-
- appCoroutineScope.launch {
- notificationController.openApp.collectLatest { intentAction ->
- Intent(this@KeyMapperApp, MainActivity::class.java).apply {
- action = intentAction
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
-
- startActivity(this)
- }
- }
- }
-
- notificationController.showToast.onEach {
- toast(it)
- }.launchIn(appCoroutineScope)
-
- autoGrantPermissionController.start()
+@HiltAndroidApp
+class KeyMapperApp : BaseKeyMapperApp() {
+ override fun getMainActivityClass(): Class<*> {
+ return MainActivity::class.java
}
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/MainActivity.kt b/app/src/main/java/io/github/sds100/keymapper/MainActivity.kt
new file mode 100644
index 0000000000..d48551a3dc
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/MainActivity.kt
@@ -0,0 +1,43 @@
+package io.github.sds100.keymapper
+
+import android.os.Bundle
+import androidx.databinding.DataBindingUtil
+import androidx.navigation.fragment.FragmentNavigator
+import androidx.navigation.fragment.NavHostFragment
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.BaseMainActivity
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.databinding.ActivityMainBinding
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.showDialogs
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class MainActivity : BaseMainActivity() {
+
+ @Inject
+ lateinit var dialogProvider: DialogProvider
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val binding =
+ DataBindingUtil.setContentView(this, R.layout.activity_main)
+
+ val navController = binding.container.getFragment().navController
+ val fragmentNavigator =
+ navController.navigatorProvider.getNavigator(FragmentNavigator::class.java)
+
+ val homeDest = fragmentNavigator.createDestination().apply {
+ id = R.id.home_fragment
+ setClassName(MainFragment::class.java.name)
+ }
+
+ navController.graph = navController.navInflater.inflate(R.navigation.nav_base_app).apply {
+ addDestination(homeDest)
+ setStartDestination(R.id.home_fragment)
+ }
+
+ dialogProvider.showDialogs(this, binding.coordinatorLayout)
+ }
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/MainFragment.kt b/app/src/main/java/io/github/sds100/keymapper/MainFragment.kt
new file mode 100644
index 0000000000..ccd1e61eee
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/MainFragment.kt
@@ -0,0 +1,152 @@
+package io.github.sds100.keymapper
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.add
+import androidx.compose.foundation.layout.displayCutout
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.only
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.compose.ui.unit.dp
+import androidx.fragment.app.Fragment
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.BaseMainNavHost
+import io.github.sds100.keymapper.base.actions.ChooseActionScreen
+import io.github.sds100.keymapper.base.actions.ChooseActionViewModel
+import io.github.sds100.keymapper.base.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.databinding.FragmentComposeBinding
+import io.github.sds100.keymapper.base.home.HomeKeyMapListScreen
+import io.github.sds100.keymapper.base.utils.navigation.NavDestination
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProviderImpl
+import io.github.sds100.keymapper.base.utils.navigation.SetupNavigation
+import io.github.sds100.keymapper.base.utils.navigation.handleRouteArgs
+import io.github.sds100.keymapper.base.utils.navigation.setupFragmentNavigation
+import io.github.sds100.keymapper.base.utils.ui.DialogProviderImpl
+import io.github.sds100.keymapper.home.HomeViewModel
+import io.github.sds100.keymapper.keymaps.ConfigKeyMapScreen
+import io.github.sds100.keymapper.keymaps.ConfigKeyMapViewModel
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class MainFragment : Fragment() {
+
+ @Inject
+ lateinit var navigationProvider: NavigationProviderImpl
+
+ @Inject
+ lateinit var dialogProvider: DialogProviderImpl
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ navigationProvider.setupFragmentNavigation(this)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
+ FragmentComposeBinding.inflate(inflater, container, false).apply {
+ composeView.apply {
+ // Dispose of the Composition when the view's LifecycleOwner
+ // is destroyed
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent {
+ val navController = rememberNavController()
+ SetupNavigation(navigationProvider, navController)
+
+ KeyMapperTheme {
+ BaseMainNavHost(
+ modifier = Modifier
+ .windowInsetsPadding(
+ WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal)
+ .add(WindowInsets.displayCutout.only(sides = WindowInsetsSides.Horizontal)),
+ ),
+ navController = navController,
+ composableDestinations = {
+ composableDestinations()
+ },
+ )
+ }
+ }
+ }
+ return this.root
+ }
+ }
+
+ private fun NavGraphBuilder.composableDestinations() {
+ composable {
+ val snackbarState = remember { SnackbarHostState() }
+ val viewModel: HomeViewModel = hiltViewModel()
+
+ HomeKeyMapListScreen(
+ modifier = Modifier.fillMaxSize(),
+ viewModel = viewModel.keyMapListViewModel,
+ snackbarState = snackbarState,
+ onSettingsClick = viewModel::launchSettings,
+ onAboutClick = viewModel::launchAbout,
+ finishActivity = {
+ requireActivity().finish()
+ },
+ fabBottomPadding = 0.dp,
+ )
+ }
+
+ composable { backStackEntry ->
+ val viewModel: ConfigKeyMapViewModel = hiltViewModel()
+
+ backStackEntry.handleRouteArgs { args ->
+ viewModel.loadNewKeyMap(groupUid = args.groupUid)
+
+ if (args.showAdvancedTriggers) {
+ viewModel.configTriggerViewModel.showAdvancedTriggersBottomSheet = true
+ }
+ }
+
+ ConfigKeyMapScreen(
+ modifier = Modifier.fillMaxSize(),
+ viewModel = viewModel,
+ )
+ }
+
+ composable { backStackEntry ->
+ val viewModel: ConfigKeyMapViewModel = hiltViewModel()
+
+ backStackEntry.handleRouteArgs { args ->
+ viewModel.loadKeyMap(uid = args.keyMapUid)
+
+ if (args.showAdvancedTriggers) {
+ viewModel.configTriggerViewModel.showAdvancedTriggersBottomSheet = true
+ }
+ }
+
+ ConfigKeyMapScreen(
+ modifier = Modifier.fillMaxSize(),
+ viewModel = viewModel,
+ )
+ }
+
+ composable {
+ val viewModel: ChooseActionViewModel = hiltViewModel()
+
+ ChooseActionScreen(
+ modifier = Modifier.fillMaxSize(),
+ viewModel = viewModel,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt b/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt
deleted file mode 100755
index 4151e715e2..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt
+++ /dev/null
@@ -1,324 +0,0 @@
-package io.github.sds100.keymapper
-
-import android.content.Context
-import androidx.datastore.preferences.preferencesDataStore
-import androidx.room.Room
-import io.github.sds100.keymapper.actions.sound.SoundsManager
-import io.github.sds100.keymapper.actions.sound.SoundsManagerImpl
-import io.github.sds100.keymapper.backup.BackupManager
-import io.github.sds100.keymapper.backup.BackupManagerImpl
-import io.github.sds100.keymapper.data.db.AppDatabase
-import io.github.sds100.keymapper.data.repositories.AccessibilityNodeRepository
-import io.github.sds100.keymapper.data.repositories.AccessibilityNodeRepositoryImpl
-import io.github.sds100.keymapper.data.repositories.FloatingButtonRepository
-import io.github.sds100.keymapper.data.repositories.FloatingLayoutRepository
-import io.github.sds100.keymapper.data.repositories.GroupRepository
-import io.github.sds100.keymapper.data.repositories.PreferenceRepository
-import io.github.sds100.keymapper.data.repositories.RoomFloatingButtonRepository
-import io.github.sds100.keymapper.data.repositories.RoomFloatingLayoutRepository
-import io.github.sds100.keymapper.data.repositories.RoomGroupRepository
-import io.github.sds100.keymapper.data.repositories.RoomKeyMapRepository
-import io.github.sds100.keymapper.data.repositories.RoomLogRepository
-import io.github.sds100.keymapper.data.repositories.SettingsPreferenceRepository
-import io.github.sds100.keymapper.logging.LogRepository
-import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCaseController
-import io.github.sds100.keymapper.purchasing.PurchasingManagerImpl
-import io.github.sds100.keymapper.shizuku.ShizukuAdapter
-import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter
-import io.github.sds100.keymapper.system.airplanemode.AirplaneModeAdapter
-import io.github.sds100.keymapper.system.apps.AppShortcutAdapter
-import io.github.sds100.keymapper.system.apps.PackageManagerAdapter
-import io.github.sds100.keymapper.system.bluetooth.BluetoothAdapter
-import io.github.sds100.keymapper.system.camera.CameraAdapter
-import io.github.sds100.keymapper.system.clipboard.ClipboardAdapter
-import io.github.sds100.keymapper.system.devices.DevicesAdapter
-import io.github.sds100.keymapper.system.display.AndroidDisplayAdapter
-import io.github.sds100.keymapper.system.files.FileAdapter
-import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter
-import io.github.sds100.keymapper.system.intents.IntentAdapter
-import io.github.sds100.keymapper.system.leanback.LeanbackAdapter
-import io.github.sds100.keymapper.system.lock.LockScreenAdapter
-import io.github.sds100.keymapper.system.media.AndroidMediaAdapter
-import io.github.sds100.keymapper.system.network.NetworkAdapter
-import io.github.sds100.keymapper.system.nfc.NfcAdapter
-import io.github.sds100.keymapper.system.notifications.AndroidNotificationAdapter
-import io.github.sds100.keymapper.system.notifications.NotificationController
-import io.github.sds100.keymapper.system.notifications.NotificationReceiverAdapter
-import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter
-import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter
-import io.github.sds100.keymapper.system.phone.PhoneAdapter
-import io.github.sds100.keymapper.system.popup.PopupMessageAdapter
-import io.github.sds100.keymapper.system.power.PowerAdapter
-import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter
-import io.github.sds100.keymapper.system.root.SuAdapter
-import io.github.sds100.keymapper.system.url.OpenUrlAdapter
-import io.github.sds100.keymapper.system.vibrator.VibratorAdapter
-import io.github.sds100.keymapper.system.volume.VolumeAdapter
-import io.github.sds100.keymapper.util.ui.ResourceProviderImpl
-import kotlinx.coroutines.CoroutineScope
-
-/**
- * Created by sds100 on 17/05/2020.
- */
-object ServiceLocator {
-
- private var database: AppDatabase? = null
-
- private fun database(context: Context): AppDatabase {
- synchronized(this) {
- return database ?: createDatabase(context.applicationContext).also {
- this.database = it
- }
- }
- }
-
- @Volatile
- private var roomKeymapRepository: RoomKeyMapRepository? = null
-
- fun roomKeyMapRepository(context: Context): RoomKeyMapRepository {
- synchronized(this) {
- return roomKeymapRepository ?: RoomKeyMapRepository(
- database(context).keyMapDao(),
- database(context).fingerprintMapDao(),
- (context.applicationContext as KeyMapperApp).appCoroutineScope,
- ).also {
- this.roomKeymapRepository = it
- }
- }
- }
-
- @Volatile
- private var settingsRepository: PreferenceRepository? = null
-
- fun settingsRepository(context: Context): PreferenceRepository {
- synchronized(this) {
- return settingsRepository ?: SettingsPreferenceRepository(
- context.applicationContext,
- (context.applicationContext as KeyMapperApp).appCoroutineScope,
- ).also {
- this.settingsRepository = it
- }
- }
- }
-
- @Volatile
- private var logRepository: LogRepository? = null
-
- fun logRepository(context: Context): LogRepository {
- synchronized(this) {
- return logRepository ?: RoomLogRepository(
- (context.applicationContext as KeyMapperApp).appCoroutineScope,
- database(context).logEntryDao(),
- ).also {
- this.logRepository = it
- }
- }
- }
-
- @Volatile
- private var floatingLayoutRepository: FloatingLayoutRepository? = null
-
- fun floatingLayoutRepository(context: Context): FloatingLayoutRepository {
- synchronized(this) {
- return floatingLayoutRepository ?: RoomFloatingLayoutRepository(
- database(context).floatingLayoutDao(),
- (context.applicationContext as KeyMapperApp).appCoroutineScope,
- ).also {
- this.floatingLayoutRepository = it
- }
- }
- }
-
- @Volatile
- private var floatingButtonRepository: FloatingButtonRepository? = null
-
- fun floatingButtonRepository(context: Context): FloatingButtonRepository {
- synchronized(this) {
- return floatingButtonRepository ?: RoomFloatingButtonRepository(
- database(context).floatingButtonDao(),
- (context.applicationContext as KeyMapperApp).appCoroutineScope,
- ).also {
- this.floatingButtonRepository = it
- }
- }
- }
-
- @Volatile
- private var groupRepository: GroupRepository? = null
-
- fun groupRepository(context: Context): GroupRepository {
- synchronized(this) {
- return groupRepository ?: RoomGroupRepository(
- database(context).groupDao(),
- (context.applicationContext as KeyMapperApp).appCoroutineScope,
- ).also {
- this.groupRepository = it
- }
- }
- }
-
- @Volatile
- private var backupManager: BackupManager? = null
-
- fun backupManager(context: Context): BackupManager {
- synchronized(this) {
- return backupManager ?: createBackupManager(context).also {
- this.backupManager = it
- }
- }
- }
-
- private fun createBackupManager(context: Context): BackupManager = backupManager ?: BackupManagerImpl(
- (context.applicationContext as KeyMapperApp).appCoroutineScope,
- fileAdapter(context),
- roomKeyMapRepository(context),
- settingsRepository(context),
- floatingLayoutRepository(context),
- floatingButtonRepository(context),
- groupRepository(context),
- soundsManager(context),
- )
-
- @Volatile
- private var soundsManager: SoundsManager? = null
-
- fun soundsManager(context: Context): SoundsManager {
- synchronized(this) {
- return soundsManager ?: SoundsManagerImpl(
- (context.applicationContext as KeyMapperApp).appCoroutineScope,
- fileAdapter(context),
- ).also {
- this.soundsManager = it
- }
- }
- }
-
- @Volatile
- private var configKeyMapsController: ConfigKeyMapUseCaseController? = null
-
- fun configKeyMapsController(ctx: Context): ConfigKeyMapUseCaseController {
- synchronized(this) {
- return configKeyMapsController
- ?: createConfigKeyMapsController(ctx).also {
- configKeyMapsController = it
- }
- }
- }
-
- private fun createConfigKeyMapsController(ctx: Context): ConfigKeyMapUseCaseController {
- return ConfigKeyMapUseCaseController(
- appCoroutineScope(ctx),
- roomKeyMapRepository(ctx),
- devicesAdapter(ctx),
- settingsRepository(ctx),
- floatingLayoutRepository(ctx),
- floatingButtonRepository(ctx),
- accessibilityServiceAdapter(ctx),
- )
- }
-
- @Volatile
- private var accessibilityNodeRepository: AccessibilityNodeRepository? = null
-
- fun accessibilityNodeRepository(context: Context): AccessibilityNodeRepository {
- synchronized(this) {
- return accessibilityNodeRepository ?: AccessibilityNodeRepositoryImpl(
- (context.applicationContext as KeyMapperApp).appCoroutineScope,
- database(context).accessibilityNodeDao(),
- ).also {
- this.accessibilityNodeRepository = it
- }
- }
- }
-
- fun fileAdapter(context: Context): FileAdapter = (context.applicationContext as KeyMapperApp).fileAdapter
-
- fun inputMethodAdapter(context: Context): InputMethodAdapter = (context.applicationContext as KeyMapperApp).inputMethodAdapter
-
- fun devicesAdapter(context: Context): DevicesAdapter = (context.applicationContext as KeyMapperApp).devicesAdapter
-
- fun bluetoothAdapter(context: Context): BluetoothAdapter = (context.applicationContext as KeyMapperApp).bluetoothMonitor
-
- fun notificationController(context: Context): NotificationController = (context.applicationContext as KeyMapperApp).notificationController
-
- fun resourceProvider(context: Context): ResourceProviderImpl = (context.applicationContext as KeyMapperApp).resourceProvider
-
- fun packageManagerAdapter(context: Context): PackageManagerAdapter = (context.applicationContext as KeyMapperApp).packageManagerAdapter
-
- fun cameraAdapter(context: Context): CameraAdapter = (context.applicationContext as KeyMapperApp).cameraAdapter
-
- fun permissionAdapter(context: Context): AndroidPermissionAdapter = (context.applicationContext as KeyMapperApp).permissionAdapter
-
- fun systemFeatureAdapter(context: Context): SystemFeatureAdapter = (context.applicationContext as KeyMapperApp).systemFeatureAdapter
-
- fun accessibilityServiceAdapter(context: Context): AccessibilityServiceAdapter = (context.applicationContext as KeyMapperApp).accessibilityServiceAdapter
-
- fun notificationReceiverAdapter(context: Context): NotificationReceiverAdapter = (context.applicationContext as KeyMapperApp).notificationReceiverAdapter
-
- fun appShortcutAdapter(context: Context): AppShortcutAdapter = (context.applicationContext as KeyMapperApp).appShortcutAdapter
-
- fun notificationAdapter(context: Context): AndroidNotificationAdapter = (context.applicationContext as KeyMapperApp).notificationAdapter
-
- fun popupMessageAdapter(context: Context): PopupMessageAdapter = (context.applicationContext as KeyMapperApp).popupMessageAdapter
-
- fun vibratorAdapter(context: Context): VibratorAdapter = (context.applicationContext as KeyMapperApp).vibratorAdapter
-
- fun displayAdapter(context: Context): AndroidDisplayAdapter = (context.applicationContext as KeyMapperApp).displayAdapter
-
- fun audioAdapter(context: Context): VolumeAdapter = (context.applicationContext as KeyMapperApp).audioAdapter
-
- fun suAdapter(context: Context): SuAdapter = (context.applicationContext as KeyMapperApp).suAdapter
-
- fun intentAdapter(context: Context): IntentAdapter = (context.applicationContext as KeyMapperApp).intentAdapter
-
- fun phoneAdapter(context: Context): PhoneAdapter = (context.applicationContext as KeyMapperApp).phoneAdapter
-
- fun mediaAdapter(context: Context): AndroidMediaAdapter = (context.applicationContext as KeyMapperApp).mediaAdapter
-
- fun lockScreenAdapter(context: Context): LockScreenAdapter = (context.applicationContext as KeyMapperApp).lockScreenAdapter
-
- fun airplaneModeAdapter(context: Context): AirplaneModeAdapter = (context.applicationContext as KeyMapperApp).airplaneModeAdapter
-
- fun networkAdapter(context: Context): NetworkAdapter = (context.applicationContext as KeyMapperApp).networkAdapter
-
- fun nfcAdapter(context: Context): NfcAdapter = (context.applicationContext as KeyMapperApp).nfcAdapter
-
- fun openUrlAdapter(context: Context): OpenUrlAdapter = (context.applicationContext as KeyMapperApp).openUrlAdapter
-
- fun clipboardAdapter(context: Context): ClipboardAdapter = (context.applicationContext as KeyMapperApp).clipboardAdapter
-
- fun shizukuAdapter(context: Context): ShizukuAdapter = (context.applicationContext as KeyMapperApp).shizukuAdapter
-
- fun leanbackAdapter(context: Context): LeanbackAdapter = (context.applicationContext as KeyMapperApp).leanbackAdapter
-
- fun powerAdapter(context: Context): PowerAdapter = (context.applicationContext as KeyMapperApp).powerAdapter
-
- fun appCoroutineScope(context: Context): CoroutineScope = (context.applicationContext as KeyMapperApp).appCoroutineScope
-
- fun purchasingManager(context: Context): PurchasingManagerImpl = (context.applicationContext as KeyMapperApp).purchasingManager
-
- fun ringtoneAdapter(context: Context): RingtoneAdapter = (context.applicationContext as KeyMapperApp).ringtoneManagerAdapter
-
- private fun createDatabase(context: Context): AppDatabase = Room.databaseBuilder(
- context.applicationContext,
- AppDatabase::class.java,
- AppDatabase.DATABASE_NAME,
- ).addMigrations(
- AppDatabase.MIGRATION_1_2,
- AppDatabase.MIGRATION_2_3,
- AppDatabase.MIGRATION_3_4,
- AppDatabase.MIGRATION_4_5,
- AppDatabase.MIGRATION_5_6,
- AppDatabase.MIGRATION_6_7,
- AppDatabase.MIGRATION_7_8,
- AppDatabase.MIGRATION_8_9,
- AppDatabase.MIGRATION_9_10,
- AppDatabase.MIGRATION_10_11,
- AppDatabase.RoomMigration11To12(context.applicationContext.legacyFingerprintMapDataStore),
- AppDatabase.MIGRATION_12_13,
- AppDatabase.MIGRATION_13_14,
- AppDatabase.MIGRATION_17_18,
- ).build()
-
- private val Context.legacyFingerprintMapDataStore by preferencesDataStore("fingerprint_gestures")
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt
deleted file mode 100644
index 3950710c45..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt
+++ /dev/null
@@ -1,220 +0,0 @@
-package io.github.sds100.keymapper
-
-import android.content.Context
-import io.github.sds100.keymapper.actions.CreateActionUseCaseImpl
-import io.github.sds100.keymapper.actions.GetActionErrorUseCaseImpl
-import io.github.sds100.keymapper.actions.PerformActionsUseCaseImpl
-import io.github.sds100.keymapper.api.KeyEventRelayServiceWrapper
-import io.github.sds100.keymapper.constraints.DetectConstraintsUseCaseImpl
-import io.github.sds100.keymapper.constraints.GetConstraintErrorUseCaseImpl
-import io.github.sds100.keymapper.floating.ListFloatingLayoutsUseCase
-import io.github.sds100.keymapper.floating.ListFloatingLayoutsUseCaseImpl
-import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCaseImpl
-import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCaseImpl
-import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCase
-import io.github.sds100.keymapper.mappings.keymaps.CreateKeyMapShortcutUseCaseImpl
-import io.github.sds100.keymapper.mappings.keymaps.DisplayKeyMapUseCase
-import io.github.sds100.keymapper.mappings.keymaps.DisplayKeyMapUseCaseImpl
-import io.github.sds100.keymapper.mappings.keymaps.detection.DetectKeyMapsUseCaseImpl
-import io.github.sds100.keymapper.onboarding.OnboardingUseCaseImpl
-import io.github.sds100.keymapper.reroutekeyevents.RerouteKeyEventsUseCaseImpl
-import io.github.sds100.keymapper.shizuku.ShizukuInputEventInjector
-import io.github.sds100.keymapper.sorting.SortKeyMapsUseCase
-import io.github.sds100.keymapper.sorting.SortKeyMapsUseCaseImpl
-import io.github.sds100.keymapper.system.Shell
-import io.github.sds100.keymapper.system.accessibility.ControlAccessibilityServiceUseCase
-import io.github.sds100.keymapper.system.accessibility.ControlAccessibilityServiceUseCaseImpl
-import io.github.sds100.keymapper.system.accessibility.IAccessibilityService
-import io.github.sds100.keymapper.system.accessibility.MyAccessibilityService
-import io.github.sds100.keymapper.system.apps.DisplayAppsUseCase
-import io.github.sds100.keymapper.system.apps.DisplayAppsUseCaseImpl
-import io.github.sds100.keymapper.system.inputmethod.ImeInputEventInjectorImpl
-import io.github.sds100.keymapper.system.inputmethod.ShowInputMethodPickerUseCase
-import io.github.sds100.keymapper.system.inputmethod.ShowInputMethodPickerUseCaseImpl
-import io.github.sds100.keymapper.system.inputmethod.ToggleCompatibleImeUseCaseImpl
-
-/**
- * Created by sds100 on 03/03/2021.
- */
-object UseCases {
-
- fun listFloatingLayouts(ctx: Context): ListFloatingLayoutsUseCase = ListFloatingLayoutsUseCaseImpl(
- ServiceLocator.floatingLayoutRepository(ctx),
- ServiceLocator.purchasingManager(ctx),
- ServiceLocator.accessibilityServiceAdapter(ctx),
- ServiceLocator.settingsRepository(ctx),
- )
-
- fun displayPackages(ctx: Context): DisplayAppsUseCase = DisplayAppsUseCaseImpl(
- ServiceLocator.packageManagerAdapter(ctx),
- )
-
- fun displayKeyMap(ctx: Context): DisplayKeyMapUseCase = DisplayKeyMapUseCaseImpl(
- ServiceLocator.permissionAdapter(ctx),
- ServiceLocator.inputMethodAdapter(ctx),
- ServiceLocator.packageManagerAdapter(ctx),
- ServiceLocator.settingsRepository(ctx),
- ServiceLocator.accessibilityServiceAdapter(ctx),
- ServiceLocator.settingsRepository(ctx),
- ServiceLocator.purchasingManager(ctx),
- ServiceLocator.ringtoneAdapter(ctx),
- getActionError(ctx),
- getConstraintError(ctx),
- )
-
- fun configKeyMap(ctx: Context): ConfigKeyMapUseCase = ServiceLocator.configKeyMapsController(ctx)
-
- fun getActionError(ctx: Context) = GetActionErrorUseCaseImpl(
- ServiceLocator.packageManagerAdapter(ctx),
- ServiceLocator.inputMethodAdapter(ctx),
- ServiceLocator.permissionAdapter(ctx),
- ServiceLocator.systemFeatureAdapter(ctx),
- ServiceLocator.cameraAdapter(ctx),
- ServiceLocator.soundsManager(ctx),
- ServiceLocator.shizukuAdapter(ctx),
- ServiceLocator.ringtoneAdapter(ctx),
- )
-
- fun getConstraintError(ctx: Context) = GetConstraintErrorUseCaseImpl(
- ServiceLocator.packageManagerAdapter(ctx),
- ServiceLocator.permissionAdapter(ctx),
- ServiceLocator.systemFeatureAdapter(ctx),
- ServiceLocator.inputMethodAdapter(ctx),
- ServiceLocator.cameraAdapter(ctx),
- )
-
- fun onboarding(ctx: Context) = OnboardingUseCaseImpl(
- ServiceLocator.settingsRepository(ctx),
- ServiceLocator.fileAdapter(ctx),
- ServiceLocator.leanbackAdapter(ctx),
- ServiceLocator.shizukuAdapter(ctx),
- ServiceLocator.permissionAdapter(ctx),
- ServiceLocator.packageManagerAdapter(ctx),
- )
-
- fun createKeymapShortcut(ctx: Context) = CreateKeyMapShortcutUseCaseImpl(
- ServiceLocator.appShortcutAdapter(ctx),
- ServiceLocator.resourceProvider(ctx),
- )
-
- fun fingerprintGesturesSupported(ctx: Context) = FingerprintGesturesSupportedUseCaseImpl(ServiceLocator.settingsRepository(ctx))
-
- fun pauseKeyMaps(ctx: Context) = PauseKeyMapsUseCaseImpl(
- ServiceLocator.settingsRepository(ctx),
- ServiceLocator.mediaAdapter(ctx),
- ServiceLocator.ringtoneAdapter(ctx),
- )
-
- fun showImePicker(ctx: Context): ShowInputMethodPickerUseCase = ShowInputMethodPickerUseCaseImpl(
- ServiceLocator.inputMethodAdapter(ctx),
- )
-
- fun controlAccessibilityService(ctx: Context): ControlAccessibilityServiceUseCase = ControlAccessibilityServiceUseCaseImpl(
- ServiceLocator.accessibilityServiceAdapter(ctx),
- ServiceLocator.permissionAdapter(ctx),
- )
-
- fun toggleCompatibleIme(ctx: Context) = ToggleCompatibleImeUseCaseImpl(
- ServiceLocator.inputMethodAdapter(ctx),
- )
-
- fun detectConstraints(service: MyAccessibilityService) = DetectConstraintsUseCaseImpl(
- service,
- ServiceLocator.mediaAdapter(service),
- ServiceLocator.devicesAdapter(service),
- ServiceLocator.displayAdapter(service),
- ServiceLocator.cameraAdapter(service),
- ServiceLocator.networkAdapter(service),
- ServiceLocator.inputMethodAdapter(service),
- ServiceLocator.lockScreenAdapter(service),
- ServiceLocator.phoneAdapter(service),
- ServiceLocator.powerAdapter(service),
- )
-
- fun performActions(
- ctx: Context,
- service: IAccessibilityService,
- keyEventRelayService: KeyEventRelayServiceWrapper,
- ) = PerformActionsUseCaseImpl(
- (ctx.applicationContext as KeyMapperApp).appCoroutineScope,
- service,
- ServiceLocator.inputMethodAdapter(ctx),
- ServiceLocator.fileAdapter(ctx),
- ServiceLocator.suAdapter(ctx),
- Shell,
- ServiceLocator.intentAdapter(ctx),
- getActionError(ctx),
- keyMapperImeMessenger(ctx, keyEventRelayService),
- ShizukuInputEventInjector(),
- ServiceLocator.packageManagerAdapter(ctx),
- ServiceLocator.appShortcutAdapter(ctx),
- ServiceLocator.popupMessageAdapter(ctx),
- ServiceLocator.devicesAdapter(ctx),
- ServiceLocator.phoneAdapter(ctx),
- ServiceLocator.audioAdapter(ctx),
- ServiceLocator.cameraAdapter(ctx),
- ServiceLocator.displayAdapter(ctx),
- ServiceLocator.lockScreenAdapter(ctx),
- ServiceLocator.mediaAdapter(ctx),
- ServiceLocator.airplaneModeAdapter(ctx),
- ServiceLocator.networkAdapter(ctx),
- ServiceLocator.bluetoothAdapter(ctx),
- ServiceLocator.nfcAdapter(ctx),
- ServiceLocator.openUrlAdapter(ctx),
- ServiceLocator.resourceProvider(ctx),
- ServiceLocator.settingsRepository(ctx),
- ServiceLocator.soundsManager(ctx),
- ServiceLocator.permissionAdapter(ctx),
- ServiceLocator.notificationReceiverAdapter(ctx),
- ServiceLocator.ringtoneAdapter(ctx),
- )
-
- fun detectKeyMaps(
- ctx: Context,
- service: IAccessibilityService,
- keyEventRelayService: KeyEventRelayServiceWrapper,
- ) = DetectKeyMapsUseCaseImpl(
- ServiceLocator.roomKeyMapRepository(ctx),
- ServiceLocator.floatingButtonRepository(ctx),
- ServiceLocator.groupRepository(ctx),
- ServiceLocator.settingsRepository(ctx),
- ServiceLocator.suAdapter(ctx),
- ServiceLocator.displayAdapter(ctx),
- ServiceLocator.audioAdapter(ctx),
- keyMapperImeMessenger(ctx, keyEventRelayService),
- service,
- ShizukuInputEventInjector(),
- ServiceLocator.popupMessageAdapter(ctx),
- ServiceLocator.permissionAdapter(ctx),
- ServiceLocator.resourceProvider(ctx),
- ServiceLocator.vibratorAdapter(ctx),
- ServiceLocator.appCoroutineScope(ctx),
- )
-
- fun rerouteKeyEvents(ctx: Context, keyEventRelayService: KeyEventRelayServiceWrapper) = RerouteKeyEventsUseCaseImpl(
- ServiceLocator.inputMethodAdapter(ctx),
- keyMapperImeMessenger(ctx, keyEventRelayService),
- ServiceLocator.settingsRepository(ctx),
- )
-
- fun createAction(ctx: Context) = CreateActionUseCaseImpl(
- ServiceLocator.inputMethodAdapter(ctx),
- ServiceLocator.systemFeatureAdapter(ctx),
- ServiceLocator.cameraAdapter(ctx),
- ServiceLocator.permissionAdapter(ctx),
- )
-
- private fun keyMapperImeMessenger(
- ctx: Context,
- keyEventRelayService: KeyEventRelayServiceWrapper,
- ) = ImeInputEventInjectorImpl(
- ctx,
- keyEventRelayService,
- ServiceLocator.inputMethodAdapter(ctx),
- )
-
- fun sortKeyMapsUseCase(ctx: Context): SortKeyMapsUseCase = SortKeyMapsUseCaseImpl(
- ServiceLocator.settingsRepository(ctx),
- displayKeyMap(ctx),
- )
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionFragment.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionFragment.kt
deleted file mode 100644
index 7abaeb4976..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionFragment.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package io.github.sds100.keymapper.actions
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ViewCompositionStrategy
-import androidx.core.os.bundleOf
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.setFragmentResult
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.withStateAtLeast
-import androidx.navigation.findNavController
-import androidx.navigation.fragment.findNavController
-import androidx.navigation.fragment.navArgs
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.databinding.FragmentComposeBinding
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.ui.setupNavigation
-import io.github.sds100.keymapper.util.ui.showPopups
-import io.github.sds100.keymapper.util.viewLifecycleScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
-
-class ChooseActionFragment : Fragment() {
-
- companion object {
- const val EXTRA_ACTION = "extra_action"
- }
-
- private val args: ChooseActionFragmentArgs by navArgs()
-
- private val viewModel by viewModels {
- Inject.chooseActionViewModel(requireContext())
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- viewModel.setupNavigation(this)
-
- launchRepeatOnLifecycle(Lifecycle.State.CREATED) {
- viewModel.returnAction.collectLatest { action ->
- viewLifecycleScope.launch {
- withStateAtLeast(Lifecycle.State.RESUMED) {
- setFragmentResult(
- args.requestKey,
- bundleOf(EXTRA_ACTION to Json.encodeToString(action)),
- )
- findNavController().navigateUp()
- }
- }
- }
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View {
- FragmentComposeBinding.inflate(inflater, container, false).apply {
- composeView.apply {
- // Dispose of the Composition when the view's LifecycleOwner
- // is destroyed
- setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
- setContent {
- KeyMapperTheme {
- ChooseActionScreen(
- modifier = Modifier.fillMaxSize(),
- viewModel = viewModel,
- onNavigateBack = findNavController()::navigateUp,
- )
- }
- }
- }
- return this.root
- }
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- viewModel.showPopups(this, view)
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/DisplayActionUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/DisplayActionUseCase.kt
deleted file mode 100644
index 4166825086..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/actions/DisplayActionUseCase.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package io.github.sds100.keymapper.actions
-
-import android.graphics.drawable.Drawable
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.Result
-import kotlinx.coroutines.flow.Flow
-
-interface DisplayActionUseCase : GetActionErrorUseCase {
- val showDeviceDescriptors: Flow
- fun getAppName(packageName: String): Result
- fun getAppIcon(packageName: String): Result
- fun getInputMethodLabel(imeId: String): Result
- fun getRingtoneLabel(uri: String): Result
- suspend fun fixError(error: Error)
- fun neverShowDndTriggerError()
- fun startAccessibilityService(): Boolean
- fun restartAccessibilityService(): Boolean
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/TestActionUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/TestActionUseCase.kt
deleted file mode 100644
index 6d4dd00ca1..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/actions/TestActionUseCase.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package io.github.sds100.keymapper.actions
-
-import io.github.sds100.keymapper.system.accessibility.ServiceAdapter
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.ServiceEvent
-
-/**
- * Created by sds100 on 20/02/2021.
- */
-
-class TestActionUseCaseImpl(
- private val serviceAdapter: ServiceAdapter,
-) : TestActionUseCase {
- override suspend fun invoke(action: ActionData): Result<*> =
- serviceAdapter.send(ServiceEvent.TestAction(action))
-}
-
-interface TestActionUseCase {
- suspend operator fun invoke(action: ActionData): Result<*>
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileUseCase.kt
deleted file mode 100644
index 2e365bf23b..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileUseCase.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package io.github.sds100.keymapper.actions.sound
-
-import io.github.sds100.keymapper.system.files.FileAdapter
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.Success
-import kotlinx.coroutines.flow.StateFlow
-
-/**
- * Created by sds100 on 25/06/2021.
- */
-
-class ChooseSoundFileUseCaseImpl(
- private val fileAdapter: FileAdapter,
- private val soundsManager: SoundsManager,
-) : ChooseSoundFileUseCase {
- override val soundFiles = soundsManager.soundFiles
-
- override suspend fun saveSound(uri: String): Result = soundsManager.saveNewSound(uri)
-
- override fun getSoundFileName(uri: String): Result {
- val name = fileAdapter.getFileFromUri(uri).name
-
- return if (name == null) {
- Error.NoFileName
- } else {
- Success(name)
- }
- }
-}
-
-interface ChooseSoundFileUseCase {
-
- /**
- * @return the sound file uid
- */
- suspend fun saveSound(uri: String): Result
- val soundFiles: StateFlow>
- fun getSoundFileName(uri: String): Result
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/sound/SoundFileInfo.kt b/app/src/main/java/io/github/sds100/keymapper/actions/sound/SoundFileInfo.kt
deleted file mode 100644
index 5178df7542..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/actions/sound/SoundFileInfo.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.github.sds100.keymapper.actions.sound
-
-/**
- * Created by sds100 on 25/06/2021.
- */
-data class SoundFileInfo(val uid: String, val name: String)
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementFragment.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementFragment.kt
deleted file mode 100644
index ddde228691..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementFragment.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-package io.github.sds100.keymapper.actions.uielement
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ViewCompositionStrategy
-import androidx.core.os.bundleOf
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.setFragmentResult
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.withStateAtLeast
-import androidx.navigation.findNavController
-import androidx.navigation.fragment.findNavController
-import androidx.navigation.fragment.navArgs
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.databinding.FragmentComposeBinding
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.ui.showPopups
-import io.github.sds100.keymapper.util.viewLifecycleScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import kotlinx.serialization.json.Json
-
-class InteractUiElementFragment : Fragment() {
-
- companion object {
- const val EXTRA_ACTION = "extra_action"
- }
-
- private val args: InteractUiElementFragmentArgs by navArgs()
-
- private val viewModel by viewModels {
- Inject.interactUiElementViewModel(requireContext())
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- args.action?.let { argsAction -> viewModel.loadAction(Json.decodeFromString(argsAction)) }
-
- launchRepeatOnLifecycle(Lifecycle.State.CREATED) {
- viewModel.returnAction.collectLatest { action ->
- viewLifecycleScope.launch {
- withStateAtLeast(Lifecycle.State.RESUMED) {
- setFragmentResult(
- args.requestKey,
- bundleOf(EXTRA_ACTION to Json.encodeToString(action)),
- )
- findNavController().navigateUp()
- }
- }
- }
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View {
- FragmentComposeBinding.inflate(inflater, container, false).apply {
- composeView.apply {
- // Dispose of the Composition when the view's LifecycleOwner
- // is destroyed
- setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
- setContent {
- KeyMapperTheme {
- InteractUiElementScreen(
- modifier = Modifier.fillMaxSize(),
- viewModel = viewModel,
- navigateBack = findNavController()::navigateUp,
- )
- }
- }
- }
- return this.root
- }
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- viewModel.showPopups(this, view)
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/api/LaunchKeyMapShortcutActivity.kt b/app/src/main/java/io/github/sds100/keymapper/api/LaunchKeyMapShortcutActivity.kt
deleted file mode 100644
index b83fec2085..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/api/LaunchKeyMapShortcutActivity.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package io.github.sds100.keymapper.api
-
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import io.github.sds100.keymapper.Constants
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.ServiceLocator
-import io.github.sds100.keymapper.system.accessibility.ServiceState
-import splitties.toast.toast
-
-/**
- * Created by sds100 on 08/09/20.
- */
-
-// DON'T MOVE THIS CLASS TO A DIFFERENT PACKAGE BECAUSE IT BREAKS THE API
-/**
- * Use basic Activity, NOT AppCompatActivity so the NoDisplay theme works. Otherwise an
- * exception may be thrown because the theme doesn't extend AppCompat.
- */
-class LaunchKeyMapShortcutActivity : Activity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- val accessibilityServiceState = ServiceLocator.accessibilityServiceAdapter(this).state.value
-
- when (accessibilityServiceState) {
- ServiceState.ENABLED ->
- if (intent.action == Api.ACTION_TRIGGER_KEYMAP_BY_UID) {
- Intent(Api.ACTION_TRIGGER_KEYMAP_BY_UID).apply {
- setPackage(Constants.PACKAGE_NAME)
-
- val uuid = intent.getStringExtra(Api.EXTRA_KEYMAP_UID)
- putExtra(Api.EXTRA_KEYMAP_UID, uuid)
-
- sendBroadcast(this)
- }
- }
-
- ServiceState.CRASHED -> toast(R.string.error_accessibility_service_crashed)
- ServiceState.DISABLED -> toast(R.string.error_accessibility_service_disabled)
- }
-
- finish()
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintFragment.kt b/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintFragment.kt
deleted file mode 100644
index 4d694b4b6b..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintFragment.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package io.github.sds100.keymapper.constraints
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ViewCompositionStrategy
-import androidx.core.os.bundleOf
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.setFragmentResult
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.withStateAtLeast
-import androidx.navigation.findNavController
-import androidx.navigation.fragment.findNavController
-import androidx.navigation.fragment.navArgs
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.databinding.FragmentComposeBinding
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.ui.setupNavigation
-import io.github.sds100.keymapper.util.ui.showPopups
-import io.github.sds100.keymapper.util.viewLifecycleScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
-
-class ChooseConstraintFragment : Fragment() {
-
- companion object {
- const val EXTRA_CONSTRAINT = "extra_constraint"
- }
-
- private val navArgs by navArgs()
-
- private val viewModel: ChooseConstraintViewModel by viewModels {
- Inject.chooseConstraintListViewModel(requireContext())
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- viewModel.setupNavigation(this)
-
- launchRepeatOnLifecycle(Lifecycle.State.CREATED) {
- viewModel.returnResult.collectLatest { constraint ->
- viewLifecycleScope.launch {
- withStateAtLeast(Lifecycle.State.RESUMED) {
- setFragmentResult(
- navArgs.requestKey,
- bundleOf(EXTRA_CONSTRAINT to Json.encodeToString(constraint)),
- )
- findNavController().navigateUp()
- }
- }
- }
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View {
- FragmentComposeBinding.inflate(inflater, container, false).apply {
- composeView.apply {
- // Dispose of the Composition when the view's LifecycleOwner
- // is destroyed
- setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
- setContent {
- KeyMapperTheme {
- ChooseConstraintScreen(
- modifier = Modifier.fillMaxSize(),
- viewModel = viewModel,
- onNavigateBack = findNavController()::navigateUp,
- )
- }
- }
- }
- return this.root
- }
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- viewModel.showPopups(this, view)
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/constraints/ConstraintMode.kt b/app/src/main/java/io/github/sds100/keymapper/constraints/ConstraintMode.kt
deleted file mode 100644
index ddc7a85d6c..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/constraints/ConstraintMode.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.github.sds100.keymapper.constraints
-
-/**
- * Created by sds100 on 03/03/2021.
- */
-enum class ConstraintMode {
- AND,
- OR,
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/constraints/DisplayConstraintUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/constraints/DisplayConstraintUseCase.kt
deleted file mode 100644
index 6b5bec73ee..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/constraints/DisplayConstraintUseCase.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package io.github.sds100.keymapper.constraints
-
-import android.graphics.drawable.Drawable
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.Result
-
-interface DisplayConstraintUseCase : GetConstraintErrorUseCase {
- fun getAppName(packageName: String): Result
- fun getAppIcon(packageName: String): Result
- fun getInputMethodLabel(imeId: String): Result
- fun neverShowDndTriggerError()
- suspend fun fixError(error: Error)
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/groups/GroupListItemModel.kt b/app/src/main/java/io/github/sds100/keymapper/groups/GroupListItemModel.kt
deleted file mode 100644
index 9f53a1da74..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/groups/GroupListItemModel.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package io.github.sds100.keymapper.groups
-
-import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
-
-data class GroupListItemModel(val uid: String, val name: String, val icon: ComposeIconInfo? = null)
diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeDestination.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeDestination.kt
deleted file mode 100644
index 33d985c64f..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/home/HomeDestination.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.github.sds100.keymapper.home
-
-sealed class HomeDestination(val route: String) {
- data object KeyMaps : HomeDestination("key_maps")
- data object FloatingButtons : HomeDestination("floating_buttons")
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeFragment.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeFragment.kt
deleted file mode 100644
index 52b2a6f59d..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/home/HomeFragment.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package io.github.sds100.keymapper.home
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.ui.platform.ViewCompositionStrategy
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.activityViewModels
-import androidx.navigation.findNavController
-import io.github.sds100.keymapper.ActivityViewModel
-import io.github.sds100.keymapper.BaseMainActivity
-import io.github.sds100.keymapper.NavAppDirections
-import io.github.sds100.keymapper.ServiceLocator
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.databinding.FragmentComposeBinding
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.ui.setupNavigation
-import io.github.sds100.keymapper.util.ui.showPopups
-
-class HomeFragment : Fragment() {
-
- private val homeViewModel: HomeViewModel by activityViewModels {
- Inject.homeViewModel(requireContext())
- }
-
- val activityViewModel: ActivityViewModel by activityViewModels {
- ActivityViewModel.Factory(ServiceLocator.resourceProvider(requireContext()))
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- homeViewModel.setupNavigation(this)
- homeViewModel.keyMapListViewModel.setupNavigation(this)
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View {
- val startDestination =
- if (!activityViewModel.handledActivityLaunchIntent &&
- requireActivity().intent?.action == BaseMainActivity.ACTION_USE_FLOATING_BUTTONS
- ) {
- activityViewModel.handledActivityLaunchIntent = true
- HomeDestination.FloatingButtons
- } else {
- HomeDestination.KeyMaps
- }
-
- FragmentComposeBinding.inflate(inflater, container, false).apply {
- composeView.apply {
- // Dispose of the Composition when the view's LifecycleOwner
- // is destroyed
- setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
- setContent {
- KeyMapperTheme {
- HomeScreen(
- viewModel = homeViewModel,
- onSettingsClick = {
- findNavController().navigate(NavAppDirections.toSettingsFragment())
- },
- onAboutClick = {
- findNavController().navigate(NavAppDirections.actionGlobalAboutFragment())
- },
- finishActivity = requireActivity()::finish,
- startDestination = startDestination,
- )
- }
- }
- }
- return this.root
- }
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- homeViewModel.showPopups(this, view)
- homeViewModel.keyMapListViewModel.showPopups(this, view)
- homeViewModel.listFloatingLayoutsViewModel.showPopups(this, view)
- }
-}
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
deleted file mode 100644
index 0381317498..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/home/HomeScreen.kt
+++ /dev/null
@@ -1,197 +0,0 @@
-package io.github.sds100.keymapper.home
-
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.slideInVertically
-import androidx.compose.animation.slideOutVertically
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.WindowInsets
-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.only
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.windowInsetsPadding
-import androidx.compose.material3.Badge
-import androidx.compose.material3.BadgedBox
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.NavigationBar
-import androidx.compose.material3.NavigationBarItem
-import androidx.compose.material3.SnackbarHostState
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavDestination.Companion.hierarchy
-import androidx.navigation.NavGraph.Companion.findStartDestination
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.currentBackStackEntryAsState
-import androidx.navigation.compose.rememberNavController
-import io.github.sds100.keymapper.util.ui.SelectionState
-
-@Composable
-fun HomeScreen(
- modifier: Modifier = Modifier,
- viewModel: HomeViewModel,
- onSettingsClick: () -> Unit,
- onAboutClick: () -> Unit,
- finishActivity: () -> Unit,
- startDestination: HomeDestination = HomeDestination.KeyMaps,
-) {
- val navController = rememberNavController()
- val navBarItems by viewModel.navBarItems.collectAsStateWithLifecycle()
-
- val snackbarState = remember { SnackbarHostState() }
- val selectionState by viewModel.keyMapListViewModel.multiSelectProvider.state.collectAsStateWithLifecycle()
-
- HomeScreen(
- modifier = modifier,
- isSelectingKeyMaps = selectionState is SelectionState.Selecting,
- startDestination = startDestination,
- navController = navController,
- navBarItems = navBarItems,
- keyMapsContent = {
- HomeKeyMapListScreen(
- viewModel = viewModel.keyMapListViewModel,
- snackbarState = snackbarState,
- onSettingsClick = onSettingsClick,
- onAboutClick = onAboutClick,
- finishActivity = finishActivity,
- fabBottomPadding = if (navBarItems.size == 1) {
- 0.dp
- } else {
- 80.dp
- },
- )
- },
- floatingButtonsContent = {
- HomeFloatingLayoutsScreen(
- viewModel = viewModel.listFloatingLayoutsViewModel,
- navController = navController,
- snackbarState = snackbarState,
- fabBottomPadding = if (navBarItems.size == 1) {
- 0.dp
- } else {
- 80.dp
- },
- )
- },
- )
-}
-
-@Composable
-private fun HomeScreen(
- modifier: Modifier = Modifier,
- isSelectingKeyMaps: Boolean,
- startDestination: HomeDestination = HomeDestination.KeyMaps,
- navController: NavHostController,
- navBarItems: List,
- keyMapsContent: @Composable () -> Unit,
- floatingButtonsContent: @Composable () -> Unit,
-) {
- val navBackStackEntry by navController.currentBackStackEntryAsState()
- val currentDestination = navBackStackEntry?.destination
-
- Column(
- modifier // Only take the horizontal because the status bar is the same color as the app bar
- .windowInsetsPadding(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)),
- ) {
- Box(contentAlignment = Alignment.BottomCenter) {
- NavHost(
- modifier = Modifier.fillMaxSize(),
- contentAlignment = Alignment.TopCenter,
- navController = navController,
- startDestination = startDestination.route,
- // use no animations because otherwise the transition freezes
- // when quickly navigating to another page while the transition is still happening.
- enterTransition = { EnterTransition.None },
- exitTransition = { ExitTransition.None },
- ) {
- composable(HomeDestination.KeyMaps.route) {
- keyMapsContent()
- }
- composable(HomeDestination.FloatingButtons.route) {
- floatingButtonsContent()
- }
- }
-
- this@Column.AnimatedVisibility(
- visible = !isSelectingKeyMaps && navBarItems.size > 1,
- enter = slideInVertically { it },
- exit = slideOutVertically { it },
- ) {
- NavigationBar {
- navBarItems.forEach { item ->
- NavigationBarItem(
- icon = {
- if (item.badge == null) {
- Icon(item.icon, contentDescription = null)
- } else {
- BadgedBox(
- badge = {
- Badge(
- modifier = Modifier
- .height(22.dp)
- .padding(start = 10.dp),
- containerColor = MaterialTheme.colorScheme.primary,
- contentColor = MaterialTheme.colorScheme.onPrimary,
- ) {
- Text(
- modifier = Modifier.padding(horizontal = 2.dp),
- text = item.badge,
- style = MaterialTheme.typography.labelLarge,
- )
- }
- },
- ) {
- Icon(item.icon, contentDescription = null)
- }
- }
- },
- label = {
- Text(
- item.label,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- )
- },
- selected = currentDestination?.hierarchy?.any { it.route == item.destination.route } == true,
- onClick = {
- // don't do anything if clicking on the current
- // destination because this results in some ugly animations.
- if (currentDestination?.route == item.destination.route) {
- return@NavigationBarItem
- }
-
- navController.navigate(item.destination.route) {
- // Pop up to the start destination of the graph to
- // avoid building up a large stack of destinations
- // on the back stack as users select items
- popUpTo(navController.graph.findStartDestination().id) {
- saveState = true
- }
- // Avoid multiple copies of the same destination when
- // reselecting the same item
- launchSingleTop = true
- // Restore state when re-selecting a previously selected item
- restoreState = true
- }
- },
- )
- }
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeTab.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeTab.kt
deleted file mode 100644
index 264083953c..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/home/HomeTab.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.github.sds100.keymapper.home
-
-/**
- * Created by sds100 on 02/04/2021.
- */
-enum class HomeTab {
- KEY_EVENTS,
- FINGERPRINT_MAPS,
-}
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 636a9dea5a..0c2f3dc906 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
@@ -1,45 +1,22 @@
package io.github.sds100.keymapper.home
-import android.os.Build
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.BubbleChart
-import androidx.compose.material.icons.outlined.Keyboard
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewModelScope
-import io.github.sds100.keymapper.Constants
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.backup.BackupRestoreMappingsUseCase
-import io.github.sds100.keymapper.floating.ListFloatingLayoutsUseCase
-import io.github.sds100.keymapper.floating.ListFloatingLayoutsViewModel
-import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCase
-import io.github.sds100.keymapper.mappings.keymaps.KeyMapListViewModel
-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
-import io.github.sds100.keymapper.util.ui.PopupUi
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.PopupViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.showPopup
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-
-/**
- * Created by sds100 on 18/01/21.
- */
-class HomeViewModel(
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.backup.BackupRestoreMappingsUseCase
+import io.github.sds100.keymapper.base.home.BaseHomeViewModel
+import io.github.sds100.keymapper.base.home.ShowHomeScreenAlertsUseCase
+import io.github.sds100.keymapper.base.keymaps.ListKeyMapsUseCase
+import io.github.sds100.keymapper.base.keymaps.PauseKeyMapsUseCase
+import io.github.sds100.keymapper.base.onboarding.OnboardingUseCase
+import io.github.sds100.keymapper.base.sorting.SortKeyMapsUseCase
+import io.github.sds100.keymapper.base.system.inputmethod.ShowInputMethodPickerUseCase
+import io.github.sds100.keymapper.base.trigger.SetupGuiKeyboardUseCase
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import javax.inject.Inject
+
+@HiltViewModel
+class HomeViewModel @Inject constructor(
private val listKeyMaps: ListKeyMapsUseCase,
private val pauseKeyMaps: PauseKeyMapsUseCase,
private val backupRestore: BackupRestoreMappingsUseCase,
@@ -48,179 +25,19 @@ class HomeViewModel(
resourceProvider: ResourceProvider,
private val setupGuiKeyboard: SetupGuiKeyboardUseCase,
private val sortKeyMaps: SortKeyMapsUseCase,
- private val listFloatingLayouts: ListFloatingLayoutsUseCase,
private val showInputMethodPickerUseCase: ShowInputMethodPickerUseCase,
-) : ViewModel(),
- ResourceProvider by resourceProvider,
- PopupViewModel by PopupViewModelImpl(),
- NavigationViewModel by NavigationViewModelImpl() {
-
- val navBarItems: StateFlow> =
- combine(
- listFloatingLayouts.showFloatingLayouts,
- onboarding.hasViewedAdvancedTriggers,
- transform = ::buildNavBarItems,
- )
- .stateIn(
- viewModelScope,
- SharingStarted.Eagerly,
- buildNavBarItems(
- showFloatingLayouts = false,
- viewedAdvancedTriggers = false,
- ),
- )
-
- val keyMapListViewModel by lazy {
- KeyMapListViewModel(
- viewModelScope,
- listKeyMaps,
- resourceProvider,
- setupGuiKeyboard,
- sortKeyMaps,
- showAlertsUseCase,
- pauseKeyMaps,
- backupRestore,
- showInputMethodPickerUseCase,
- )
- }
-
- val listFloatingLayoutsViewModel by lazy {
- ListFloatingLayoutsViewModel(
- viewModelScope,
- listFloatingLayouts,
- resourceProvider,
- )
- }
-
- init {
-
- combine(
- onboarding.showWhatsNew,
- onboarding.showQuickStartGuideHint,
- ) { showWhatsNew, showQuickStartGuideHint ->
- if (showWhatsNew) {
- showWhatsNewDialog()
- }
- }.launchIn(viewModelScope)
-
- viewModelScope.launch {
- if (setupGuiKeyboard.isInstalled.first() && !setupGuiKeyboard.isCompatibleVersion.first()) {
- showUpgradeGuiKeyboardDialog()
- }
- }
- }
-
- private fun buildNavBarItems(
- showFloatingLayouts: Boolean,
- viewedAdvancedTriggers: Boolean,
- ): List {
- val items = mutableListOf()
- items.add(
- HomeNavBarItem(
- HomeDestination.KeyMaps,
- getString(R.string.home_nav_bar_key_maps),
- icon = Icons.Outlined.Keyboard,
- badge = null,
- ),
- )
-
- if (showFloatingLayouts && Build.VERSION.SDK_INT >= Constants.MIN_API_FLOATING_BUTTONS) {
- items.add(
- HomeNavBarItem(
- HomeDestination.FloatingButtons,
- getString(R.string.home_nav_bar_floating_buttons),
- icon = Icons.Outlined.BubbleChart,
- badge = if (viewedAdvancedTriggers) {
- null
- } else {
- getString(R.string.button_advanced_triggers_badge)
- },
- ),
- )
- }
-
- return items
- }
-
- private suspend fun showWhatsNewDialog() {
- val dialog = PopupUi.Dialog(
- title = getString(R.string.whats_new),
- message = onboarding.getWhatsNewText(),
- positiveButtonText = getString(R.string.pos_ok),
- neutralButtonText = getString(R.string.neutral_changelog),
- )
-
- // don't return if they dismiss the dialog because this is common behaviour.
- val response = showPopup("whats-new", dialog)
-
- if (response == DialogResponse.NEUTRAL) {
- showPopup("url_changelog", PopupUi.OpenUrl(getString(R.string.url_changelog)))
- }
-
- onboarding.showedWhatsNew()
- }
-
- private suspend fun showUpgradeGuiKeyboardDialog() {
- val dialog = PopupUi.Dialog(
- title = getString(R.string.dialog_upgrade_gui_keyboard_title),
- message = getString(R.string.dialog_upgrade_gui_keyboard_message),
- positiveButtonText = getString(R.string.dialog_upgrade_gui_keyboard_positive),
- negativeButtonText = getString(R.string.dialog_upgrade_gui_keyboard_neutral),
- )
-
- val response = showPopup("upgrade_gui_keyboard", dialog)
-
- if (response == DialogResponse.POSITIVE) {
- showPopup(
- "gui_keyboard_play_store",
- PopupUi.OpenUrl(getString(R.string.url_play_store_keymapper_gui_keyboard)),
- )
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- class Factory(
- private val listKeyMaps: ListKeyMapsUseCase,
- private val pauseMappings: PauseKeyMapsUseCase,
- private val backupRestore: BackupRestoreMappingsUseCase,
- private val showAlertsUseCase: ShowHomeScreenAlertsUseCase,
- private val onboarding: OnboardingUseCase,
- private val resourceProvider: ResourceProvider,
- 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(
- listKeyMaps,
- pauseMappings,
- backupRestore,
- showAlertsUseCase,
- onboarding,
- resourceProvider,
- setupGuiKeyboard,
- sortKeyMaps,
- listFloatingLayouts,
- showInputMethodPickerUseCase,
- ) as T
- }
-}
-
-enum class SelectedKeyMapsEnabled {
- ALL,
- NONE,
- MIXED,
-}
-
-data class HomeWarningListItem(
- val id: String,
- val text: String,
-)
-
-data class HomeNavBarItem(
- val destination: HomeDestination,
- val label: String,
- val icon: ImageVector,
- val badge: String? = null,
+ navigationProvider: NavigationProvider,
+ dialogProvider: DialogProvider,
+) : BaseHomeViewModel(
+ listKeyMaps,
+ pauseKeyMaps,
+ backupRestore,
+ showAlertsUseCase,
+ onboarding,
+ resourceProvider,
+ setupGuiKeyboard,
+ sortKeyMaps,
+ showInputMethodPickerUseCase,
+ navigationProvider,
+ dialogProvider,
)
diff --git a/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt
new file mode 100644
index 0000000000..61e36a3624
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt
@@ -0,0 +1,81 @@
+package io.github.sds100.keymapper.keymaps
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import io.github.sds100.keymapper.base.actions.ActionsScreen
+import io.github.sds100.keymapper.base.constraints.ConstraintsScreen
+import io.github.sds100.keymapper.base.keymaps.BaseConfigKeyMapScreen
+import io.github.sds100.keymapper.base.keymaps.KeyMapOptionsScreen
+import io.github.sds100.keymapper.base.utils.ui.UnsavedChangesDialog
+import io.github.sds100.keymapper.trigger.TriggerScreen
+
+@Composable
+fun ConfigKeyMapScreen(
+ modifier: Modifier = Modifier,
+ viewModel: ConfigKeyMapViewModel,
+) {
+ val isKeyMapEnabled by viewModel.isEnabled.collectAsStateWithLifecycle()
+ val showActionTapTarget by viewModel.showActionsTapTarget.collectAsStateWithLifecycle()
+ val showConstraintTapTarget by viewModel.showConstraintsTapTarget.collectAsStateWithLifecycle()
+
+ val snackbarHostState = remember { SnackbarHostState() }
+
+ var showBackDialog by rememberSaveable { mutableStateOf(false) }
+
+ if (showBackDialog) {
+ UnsavedChangesDialog(
+ onDismiss = { showBackDialog = false },
+ onDiscardClick = {
+ showBackDialog = false
+ viewModel.onBackClick()
+ },
+ )
+ }
+
+ BaseConfigKeyMapScreen(
+ modifier = modifier,
+ isKeyMapEnabled = isKeyMapEnabled,
+ onKeyMapEnabledChange = viewModel::onEnabledChanged,
+ triggerScreen = {
+ TriggerScreen(Modifier.fillMaxSize(), viewModel.configTriggerViewModel)
+ },
+ actionScreen = {
+ ActionsScreen(Modifier.fillMaxSize(), viewModel.configActionsViewModel)
+ },
+ constraintsScreen = {
+ ConstraintsScreen(
+ Modifier.fillMaxSize(),
+ viewModel.configConstraintsViewModel,
+ snackbarHostState,
+ )
+ },
+ optionsScreen = {
+ KeyMapOptionsScreen(
+ Modifier.fillMaxSize(),
+ viewModel.configTriggerViewModel.optionsViewModel,
+ )
+ },
+ onBackClick = {
+ if (viewModel.isKeyMapEdited) {
+ showBackDialog = true
+ } else {
+ viewModel.onBackClick()
+ }
+ },
+ onDoneClick = viewModel::onDoneClick,
+ snackbarHostState = snackbarHostState,
+ showActionTapTarget = showActionTapTarget,
+ onActionTapTargetCompleted = viewModel::onActionTapTargetCompleted,
+ showConstraintTapTarget = showConstraintTapTarget,
+ onConstraintTapTargetCompleted = viewModel::onConstraintTapTargetCompleted,
+ onSkipTutorialClick = viewModel::onSkipTutorialClick,
+ )
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt
new file mode 100644
index 0000000000..046f5cee1c
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt
@@ -0,0 +1,81 @@
+package io.github.sds100.keymapper.keymaps
+
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.actions.ConfigActionsViewModel
+import io.github.sds100.keymapper.base.actions.CreateActionUseCase
+import io.github.sds100.keymapper.base.actions.TestActionUseCase
+import io.github.sds100.keymapper.base.constraints.ConfigConstraintsViewModel
+import io.github.sds100.keymapper.base.keymaps.BaseConfigKeyMapViewModel
+import io.github.sds100.keymapper.base.keymaps.ConfigKeyMapUseCase
+import io.github.sds100.keymapper.base.keymaps.CreateKeyMapShortcutUseCase
+import io.github.sds100.keymapper.base.keymaps.DisplayKeyMapUseCase
+import io.github.sds100.keymapper.base.keymaps.FingerprintGesturesSupportedUseCase
+import io.github.sds100.keymapper.base.onboarding.OnboardingUseCase
+import io.github.sds100.keymapper.base.purchasing.PurchasingManager
+import io.github.sds100.keymapper.base.trigger.RecordTriggerUseCase
+import io.github.sds100.keymapper.base.trigger.SetupGuiKeyboardUseCase
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.trigger.ConfigTriggerViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class ConfigKeyMapViewModel @Inject constructor(
+ display: DisplayKeyMapUseCase,
+ config: ConfigKeyMapUseCase,
+ onboarding: OnboardingUseCase,
+ createActionUseCase: CreateActionUseCase,
+ testActionUseCase: TestActionUseCase,
+ recordTriggerUseCase: RecordTriggerUseCase,
+ createKeyMapShortcutUseCase: CreateKeyMapShortcutUseCase,
+ purchasingManager: PurchasingManager,
+ setupGuiKeyboardUseCase: SetupGuiKeyboardUseCase,
+ fingerprintGesturesSupportedUseCase: FingerprintGesturesSupportedUseCase,
+ resourceProvider: ResourceProvider,
+ navigationProvider: NavigationProvider,
+ dialogProvider: DialogProvider,
+) : BaseConfigKeyMapViewModel(
+ config = config,
+ onboarding = onboarding,
+ navigationProvider = navigationProvider,
+ dialogProvider = dialogProvider,
+) {
+ override val configActionsViewModel: ConfigActionsViewModel = ConfigActionsViewModel(
+ coroutineScope = viewModelScope,
+ displayAction = display,
+ createAction = createActionUseCase,
+ testAction = testActionUseCase,
+ config = config,
+ onboarding = onboarding,
+ resourceProvider = resourceProvider,
+ navigationProvider = navigationProvider,
+ dialogProvider = dialogProvider,
+ )
+
+ override val configTriggerViewModel: ConfigTriggerViewModel = ConfigTriggerViewModel(
+ coroutineScope = viewModelScope,
+ onboarding = onboarding,
+ config = config,
+ recordTrigger = recordTriggerUseCase,
+ createKeyMapShortcut = createKeyMapShortcutUseCase,
+ displayKeyMap = display,
+ purchasingManager = purchasingManager,
+ setupGuiKeyboard = setupGuiKeyboardUseCase,
+ fingerprintGesturesSupported = fingerprintGesturesSupportedUseCase,
+ resourceProvider = resourceProvider,
+ navigationProvider = navigationProvider,
+ dialogProvider = dialogProvider,
+ )
+
+ override val configConstraintsViewModel: ConfigConstraintsViewModel =
+ ConfigConstraintsViewModel(
+ coroutineScope = viewModelScope,
+ config = config,
+ displayConstraint = display,
+ resourceProvider = resourceProvider,
+ navigationProvider = navigationProvider,
+ dialogProvider = dialogProvider,
+ )
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/logging/LogSeverity.kt b/app/src/main/java/io/github/sds100/keymapper/logging/LogSeverity.kt
deleted file mode 100644
index 225fcf2e00..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/logging/LogSeverity.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.github.sds100.keymapper.logging
-
-/**
- * Created by sds100 on 13/05/2021.
- */
-enum class LogSeverity {
- ERROR,
- INFO,
- DEBUG,
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/ClickType.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/ClickType.kt
deleted file mode 100644
index 4b0a201ca6..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/ClickType.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.github.sds100.keymapper.mappings
-
-/**
- * Created by sds100 on 21/02/2021.
- */
-enum class ClickType {
- SHORT_PRESS,
- LONG_PRESS,
- DOUBLE_PRESS,
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt
deleted file mode 100644
index 24d56ea5f8..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.WindowInsetsSides
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.only
-import androidx.compose.foundation.layout.systemBars
-import androidx.compose.foundation.layout.windowInsetsPadding
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.ViewCompositionStrategy
-import androidx.fragment.app.Fragment
-import androidx.navigation.findNavController
-import androidx.navigation.fragment.navArgs
-import androidx.navigation.navGraphViewModels
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.databinding.FragmentComposeBinding
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.ui.setupNavigation
-import io.github.sds100.keymapper.util.ui.showPopups
-
-class ConfigKeyMapFragment : Fragment() {
-
- private val args by navArgs()
-
- private val viewModel: ConfigKeyMapViewModel by navGraphViewModels(R.id.nav_config_keymap) {
- Inject.configKeyMapViewModel(requireContext())
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- // only load the keymap if opening this fragment for the first time
- if (savedInstanceState == null) {
- args.keyMapUid.also { keyMapUid ->
- if (keyMapUid == null) {
- viewModel.loadNewKeymap(
- args.newFloatingButtonTriggerKey,
- groupUid = args.groupUid,
- )
- } else {
- viewModel.loadKeyMap(keyMapUid)
- }
- }
-
- if (args.showAdvancedTriggers) {
- viewModel.configTriggerViewModel.showAdvancedTriggersBottomSheet = true
- }
- }
-
- viewModel.configTriggerViewModel.setupNavigation(this)
- viewModel.configActionsViewModel.setupNavigation(this)
- viewModel.configConstraintsViewModel.setupNavigation(this)
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
- ): View {
- FragmentComposeBinding.inflate(inflater, container, false).apply {
- composeView.apply {
- // Dispose of the Composition when the view's LifecycleOwner
- // is destroyed
- setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
- setContent {
- KeyMapperTheme {
- ConfigKeyMapScreen(
- modifier = Modifier
- .windowInsetsPadding(WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal))
- .fillMaxSize(),
- viewModel = viewModel,
- navigateBack = findNavController()::navigateUp,
- )
- }
- }
- }
- return this.root
- }
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- viewModel.configTriggerViewModel.showPopups(this, view)
- viewModel.configTriggerViewModel.optionsViewModel.showPopups(this, view)
- viewModel.configActionsViewModel.showPopups(this, view)
- viewModel.configConstraintsViewModel.showPopups(this, view)
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- viewModel.saveState(outState)
-
- super.onSaveInstanceState(outState)
- }
-
- override fun onViewStateRestored(savedInstanceState: Bundle?) {
- super.onViewStateRestored(savedInstanceState)
-
- savedInstanceState ?: return
-
- viewModel.restoreState(savedInstanceState)
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapViewModel.kt
deleted file mode 100644
index 010ba017b0..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapViewModel.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps
-
-import android.os.Bundle
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.viewModelScope
-import io.github.sds100.keymapper.actions.ConfigActionsViewModel
-import io.github.sds100.keymapper.actions.CreateActionUseCase
-import io.github.sds100.keymapper.actions.TestActionUseCase
-import io.github.sds100.keymapper.constraints.ConfigConstraintsViewModel
-import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCase
-import io.github.sds100.keymapper.mappings.keymaps.trigger.ConfigTriggerViewModel
-import io.github.sds100.keymapper.mappings.keymaps.trigger.RecordTriggerUseCase
-import io.github.sds100.keymapper.mappings.keymaps.trigger.SetupGuiKeyboardUseCase
-import io.github.sds100.keymapper.onboarding.OnboardingUseCase
-import io.github.sds100.keymapper.purchasing.PurchasingManager
-import io.github.sds100.keymapper.ui.utils.getJsonSerializable
-import io.github.sds100.keymapper.ui.utils.putJsonSerializable
-import io.github.sds100.keymapper.util.dataOrNull
-import io.github.sds100.keymapper.util.firstBlocking
-import io.github.sds100.keymapper.util.ifIsData
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-
-/**
- * Created by sds100 on 22/11/20.
- */
-
-class ConfigKeyMapViewModel(
- private val config: ConfigKeyMapUseCase,
- private val testAction: TestActionUseCase,
- private val onboarding: OnboardingUseCase,
- private val recordTrigger: RecordTriggerUseCase,
- private val createKeyMapShortcut: CreateKeyMapShortcutUseCase,
- private val displayMapping: DisplayKeyMapUseCase,
- createActionUseCase: CreateActionUseCase,
- resourceProvider: ResourceProvider,
- purchasingManager: PurchasingManager,
- setupGuiKeyboardUseCase: SetupGuiKeyboardUseCase,
- fingerprintGesturesSupported: FingerprintGesturesSupportedUseCase,
-) : ViewModel(),
- ResourceProvider by resourceProvider {
-
- companion object {
- private const val STATE_KEY = "config_keymap"
- }
-
- val configActionsViewModel = ConfigActionsViewModel(
- viewModelScope,
- displayMapping,
- createActionUseCase,
- testAction,
- config,
- onboarding,
- resourceProvider,
- )
-
- val configTriggerViewModel = ConfigTriggerViewModel(
- viewModelScope,
- onboarding,
- config,
- recordTrigger,
- createKeyMapShortcut,
- displayMapping,
- resourceProvider,
- purchasingManager,
- setupGuiKeyboardUseCase,
- fingerprintGesturesSupported,
- )
-
- val configConstraintsViewModel = ConfigConstraintsViewModel(
- viewModelScope,
- config,
- displayMapping,
- resourceProvider,
- )
-
- val isEnabled: StateFlow = config.keyMap
- .map { state -> state.dataOrNull()?.isEnabled ?: true }
- .stateIn(viewModelScope, SharingStarted.Eagerly, true)
-
- val isKeyMapEdited: Boolean
- get() = config.isEdited
-
- fun save() = config.save()
-
- fun saveState(outState: Bundle) {
- config.keyMap.firstBlocking().ifIsData {
- outState.putJsonSerializable(STATE_KEY, it)
- }
- }
-
- fun restoreState(state: Bundle) {
- val keyMap = state.getJsonSerializable(STATE_KEY) ?: KeyMap()
- config.restoreState(keyMap)
- }
-
- fun loadNewKeymap(floatingButtonUid: String? = null, groupUid: String?) {
- config.loadNewKeyMap(groupUid)
- if (floatingButtonUid != null) {
- viewModelScope.launch {
- config.addFloatingButtonTriggerKey(floatingButtonUid)
- }
- }
- }
-
- fun loadKeyMap(uid: String) {
- viewModelScope.launch {
- config.loadKeyMap(uid)
- }
- }
-
- fun onEnabledChanged(enabled: Boolean) {
- config.setEnabled(enabled)
- }
-
- class Factory(
- private val config: ConfigKeyMapUseCase,
- private val testAction: TestActionUseCase,
- private val onboard: OnboardingUseCase,
- private val recordTrigger: RecordTriggerUseCase,
- private val createKeyMapShortcut: CreateKeyMapShortcutUseCase,
- private val displayMapping: DisplayKeyMapUseCase,
- private val createActionUseCase: CreateActionUseCase,
- private val resourceProvider: ResourceProvider,
- private val purchasingManager: PurchasingManager,
- private val setupGuiKeyboardUseCase: SetupGuiKeyboardUseCase,
- private val fingerprintGesturesSupported: FingerprintGesturesSupportedUseCase,
- ) : ViewModelProvider.Factory {
-
- @Suppress("UNCHECKED_CAST")
- override fun create(modelClass: Class) = ConfigKeyMapViewModel(
- config,
- testAction,
- onboard,
- recordTrigger,
- createKeyMapShortcut,
- displayMapping,
- createActionUseCase,
- resourceProvider,
- purchasingManager,
- setupGuiKeyboardUseCase,
- fingerprintGesturesSupported,
- ) as T
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutActivity.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutActivity.kt
deleted file mode 100644
index 14a3006dad..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutActivity.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps
-
-import android.os.Bundle
-import androidx.activity.SystemBarStyle
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.activity.viewModels
-import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.ui.graphics.toArgb
-import androidx.lifecycle.Lifecycle
-import androidx.navigation.findNavController
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.ServiceLocator
-import io.github.sds100.keymapper.compose.ComposeColors
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.system.permissions.RequestPermissionDelegate
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import kotlinx.coroutines.flow.collectLatest
-
-/**
- * Created by sds100 on 08/09/20.
- */
-
-class CreateKeyMapShortcutActivity : AppCompatActivity() {
-
- private val viewModel by viewModels {
- Inject.createActionShortcutViewModel(this)
- }
-
- private lateinit var requestPermissionDelegate: RequestPermissionDelegate
-
- override fun onCreate(savedInstanceState: Bundle?) {
- enableEdgeToEdge(
- statusBarStyle = SystemBarStyle.auto(
- ComposeColors.surfaceContainerLight.toArgb(),
- ComposeColors.surfaceContainerDark.toArgb(),
- ),
- navigationBarStyle = SystemBarStyle.auto(
- ComposeColors.surfaceContainerLight.toArgb(),
- ComposeColors.surfaceContainerDark.toArgb(),
- ),
- )
- super.onCreate(savedInstanceState)
-
- setContent {
- KeyMapperTheme {
- CreateKeyMapShortcutScreen(
- viewModel = viewModel,
- finishActivity = { finish() },
- )
- }
- }
-
- requestPermissionDelegate = RequestPermissionDelegate(this, showDialogs = true)
-
- launchRepeatOnLifecycle(Lifecycle.State.STARTED) {
- ServiceLocator.permissionAdapter(this@CreateKeyMapShortcutActivity).request
- .collectLatest { permission ->
- requestPermissionDelegate.requestPermission(
- permission,
- findNavController(R.id.container),
- )
- }
- }
-
- launchRepeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.returnIntentResult.collectLatest { intent ->
- setResult(RESULT_OK, intent)
- finish()
- }
- }
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutUseCase.kt
deleted file mode 100644
index e910d77733..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutUseCase.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps
-
-import android.content.Intent
-import android.graphics.drawable.Drawable
-import androidx.core.os.bundleOf
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.api.Api
-import io.github.sds100.keymapper.system.apps.AppShortcutAdapter
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-
-/**
- * Created by sds100 on 23/03/2021.
- */
-
-class CreateKeyMapShortcutUseCaseImpl(
- private val adapter: AppShortcutAdapter,
- resourceProvider: ResourceProvider,
-) : CreateKeyMapShortcutUseCase,
- ResourceProvider by resourceProvider {
-
- override val isSupported: Boolean
- get() = adapter.areLauncherShortcutsSupported
-
- override fun pinShortcut(
- keyMapUid: String,
- shortcutLabel: String,
- icon: Drawable?,
- ): Result<*> {
- val shortcut = if (icon == null) {
- adapter.createLauncherShortcut(
- iconResId = R.mipmap.ic_launcher_round,
- label = shortcutLabel,
- intentAction = Api.ACTION_TRIGGER_KEYMAP_BY_UID,
- bundleOf(Api.EXTRA_KEYMAP_UID to keyMapUid),
- )
- } else {
- adapter.createLauncherShortcut(
- icon = icon,
- label = shortcutLabel,
- intentAction = Api.ACTION_TRIGGER_KEYMAP_BY_UID,
- bundleOf(Api.EXTRA_KEYMAP_UID to keyMapUid),
- )
- }
- return adapter.pinShortcut(shortcut)
- }
-
- override fun createIntent(
- keyMapUid: String,
- shortcutLabel: String,
- icon: Drawable?,
- ): Intent {
- val shortcut = if (icon == null) {
- adapter.createLauncherShortcut(
- iconResId = R.mipmap.ic_launcher_round,
- label = shortcutLabel,
- intentAction = Api.ACTION_TRIGGER_KEYMAP_BY_UID,
- bundleOf(Api.EXTRA_KEYMAP_UID to keyMapUid),
- )
- } else {
- adapter.createLauncherShortcut(
- icon = icon,
- label = shortcutLabel,
- intentAction = Api.ACTION_TRIGGER_KEYMAP_BY_UID,
- bundleOf(Api.EXTRA_KEYMAP_UID to keyMapUid),
- )
- }
- return adapter.createShortcutResultIntent(shortcut)
- }
-}
-
-interface CreateKeyMapShortcutUseCase {
- val isSupported: Boolean
-
- fun pinShortcut(
- keyMapUid: String,
- shortcutLabel: String,
- icon: Drawable?,
- ): Result<*>
-
- fun createIntent(
- keyMapUid: String,
- shortcutLabel: String,
- icon: Drawable?,
- ): Intent
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapGroup.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapGroup.kt
deleted file mode 100644
index de2910ec78..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapGroup.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps
-
-import io.github.sds100.keymapper.groups.Group
-import io.github.sds100.keymapper.util.State
-
-data class KeyMapGroup(
- val group: Group?,
- val subGroups: List,
- val parents: List,
- val keyMaps: State>,
-)
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListState.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListState.kt
deleted file mode 100644
index 9f79721767..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListState.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps
-
-import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyMapListItemModel
-import io.github.sds100.keymapper.util.State
-
-data class KeyMapListState(
- val appBarState: KeyMapAppBarState,
- val listItems: State>,
-)
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutModel.kt
deleted file mode 100644
index ea54953873..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutModel.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps
-
-import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
-
-data class ShortcutModel(
- val icon: ComposeIconInfo,
- val text: String,
- val data: T,
-)
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapModel.kt
deleted file mode 100644
index 6242e555bd..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapModel.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps.detection
-
-import io.github.sds100.keymapper.constraints.ConstraintState
-import io.github.sds100.keymapper.mappings.keymaps.KeyMap
-
-data class DetectKeyMapModel(
- val keyMap: KeyMap,
- val groupConstraintStates: List = emptyList(),
-)
diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerButtonRow.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerButtonRow.kt
deleted file mode 100644
index 5f97f455ad..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerButtonRow.kt
+++ /dev/null
@@ -1,154 +0,0 @@
-package io.github.sds100.keymapper.mappings.keymaps.trigger
-
-import androidx.compose.foundation.layout.Box
-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.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.Badge
-import androidx.compose.material3.ButtonDefaults
-import androidx.compose.material3.FilledTonalButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.compose.LocalCustomColorsPalette
-
-@Composable
-fun RecordTriggerButtonRow(
- modifier: Modifier = Modifier,
- onRecordTriggerClick: () -> Unit = {},
- recordTriggerState: RecordTriggerState,
- onAdvancedTriggersClick: () -> Unit = {},
- showNewBadge: Boolean,
-) {
- Row(modifier) {
- RecordTriggerButton(
- modifier = Modifier
- .weight(1f)
- .align(Alignment.Bottom),
- recordTriggerState,
- onClick = onRecordTriggerClick,
- )
-
- Spacer(modifier = Modifier.width(8.dp))
-
- AdvancedTriggersButton(
- modifier = Modifier.weight(1f),
- isEnabled = recordTriggerState !is RecordTriggerState.CountingDown,
- onClick = onAdvancedTriggersClick,
- showNewBadge = showNewBadge,
- )
- }
-}
-
-@Composable
-private fun RecordTriggerButton(
- modifier: Modifier,
- state: RecordTriggerState,
- onClick: () -> Unit,
-) {
- val colors = ButtonDefaults.filledTonalButtonColors().copy(
- containerColor = LocalCustomColorsPalette.current.red,
- contentColor = LocalCustomColorsPalette.current.onRed,
- )
-
- val text: String = when (state) {
- is RecordTriggerState.CountingDown ->
- stringResource(R.string.button_recording_trigger_countdown, state.timeLeft)
-
- else ->
- stringResource(R.string.button_record_trigger)
- }
-
- FilledTonalButton(
- modifier = modifier,
- onClick = onClick,
- colors = colors,
- ) {
- Text(
- text = text,
- maxLines = 2,
- overflow = TextOverflow.Ellipsis,
- )
- }
-}
-
-@Composable
-private fun AdvancedTriggersButton(
- modifier: Modifier,
- isEnabled: Boolean,
- showNewBadge: Boolean,
- onClick: () -> Unit,
-) {
- Box(modifier = modifier) {
- OutlinedButton(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 20.dp),
- enabled = isEnabled,
- onClick = onClick,
- ) {
- Text(
- text = stringResource(R.string.button_advanced_triggers),
- maxLines = 2,
- overflow = TextOverflow.Ellipsis,
- )
- }
-
- if (showNewBadge) {
- Badge(
- modifier = Modifier
- .align(Alignment.TopEnd)
- .height(36.dp),
- containerColor = MaterialTheme.colorScheme.primaryContainer,
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
- ) {
- Text(
- modifier = Modifier.padding(horizontal = 8.dp),
- text = stringResource(R.string.button_advanced_triggers_badge),
- style = MaterialTheme.typography.labelLarge,
- )
- }
- }
- }
-}
-
-@Preview(widthDp = 400)
-@Composable
-private fun PreviewCountingDown() {
- KeyMapperTheme {
- Surface {
- RecordTriggerButtonRow(
- modifier = Modifier.fillMaxWidth(),
- recordTriggerState = RecordTriggerState.CountingDown(3),
- showNewBadge = true,
- )
- }
- }
-}
-
-@Preview(widthDp = 400)
-@Composable
-private fun PreviewStopped() {
- KeyMapperTheme {
- Surface {
- RecordTriggerButtonRow(
- modifier = Modifier.fillMaxWidth(),
- recordTriggerState = RecordTriggerState.Idle,
- showNewBadge = false,
- )
- }
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt
deleted file mode 100644
index 73b90c0118..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt
+++ /dev/null
@@ -1,196 +0,0 @@
-package io.github.sds100.keymapper.onboarding
-
-import io.github.sds100.keymapper.Constants
-import io.github.sds100.keymapper.actions.ActionData
-import io.github.sds100.keymapper.actions.canUseImeToPerform
-import io.github.sds100.keymapper.actions.canUseShizukuToPerform
-import io.github.sds100.keymapper.data.Keys
-import io.github.sds100.keymapper.data.repositories.PreferenceRepository
-import io.github.sds100.keymapper.shizuku.ShizukuAdapter
-import io.github.sds100.keymapper.shizuku.ShizukuUtils
-import io.github.sds100.keymapper.system.apps.PackageManagerAdapter
-import io.github.sds100.keymapper.system.files.FileAdapter
-import io.github.sds100.keymapper.system.inputmethod.KeyMapperImeHelper
-import io.github.sds100.keymapper.system.leanback.LeanbackAdapter
-import io.github.sds100.keymapper.system.permissions.Permission
-import io.github.sds100.keymapper.system.permissions.PermissionAdapter
-import io.github.sds100.keymapper.util.PrefDelegate
-import io.github.sds100.keymapper.util.VersionHelper
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.map
-
-/**
- * Created by sds100 on 14/02/21.
- */
-class OnboardingUseCaseImpl(
- private val preferences: PreferenceRepository,
- private val fileAdapter: FileAdapter,
- private val leanbackAdapter: LeanbackAdapter,
- private val shizukuAdapter: ShizukuAdapter,
- private val permissionAdapter: PermissionAdapter,
- private val packageManagerAdapter: PackageManagerAdapter,
-) : PreferenceRepository by preferences,
- OnboardingUseCase {
-
- override var shownAppIntro by PrefDelegate(Keys.shownAppIntro, false)
-
- override suspend fun showInstallGuiKeyboardPrompt(action: ActionData): Boolean {
- val acknowledged = preferences.get(Keys.acknowledgedGuiKeyboard).first()
- val isGuiKeyboardInstalled =
- packageManagerAdapter.isAppInstalled(KeyMapperImeHelper.KEY_MAPPER_GUI_IME_PACKAGE)
-
- val isShizukuInstalled = shizukuAdapter.isInstalled.value
-
- return (acknowledged == null || !acknowledged) &&
- !isGuiKeyboardInstalled &&
- !isShizukuInstalled &&
- action.canUseImeToPerform()
- }
-
- override suspend fun showInstallShizukuPrompt(action: ActionData): Boolean = !shizukuAdapter.isInstalled.value &&
- ShizukuUtils.isRecommendedForSdkVersion() &&
- action.canUseShizukuToPerform()
-
- override fun neverShowGuiKeyboardPromptsAgain() {
- preferences.set(Keys.acknowledgedGuiKeyboard, true)
- }
-
- override var shownParallelTriggerOrderExplanation by PrefDelegate(
- Keys.shownParallelTriggerOrderExplanation,
- false,
- )
- override var shownSequenceTriggerExplanation by PrefDelegate(
- Keys.shownSequenceTriggerExplanation,
- false,
- )
- override var shownKeyCodeToScanCodeTriggerExplanation by PrefDelegate(
- Keys.shownKeyCodeToScanCodeTriggerExplanation,
- false,
- )
-
- override val showWhatsNew = get(Keys.lastInstalledVersionCodeHomeScreen)
- .map { (it ?: -1) < Constants.VERSION_CODE }
-
- override fun showedWhatsNew() {
- set(Keys.lastInstalledVersionCodeHomeScreen, Constants.VERSION_CODE)
- }
-
- override fun getWhatsNewText(): String = with(fileAdapter.openAsset("whats-new.txt").bufferedReader()) {
- readText()
- }
-
- override var approvedFloatingButtonFeaturePrompt by PrefDelegate(
- Keys.approvedFloatingButtonFeaturePrompt,
- false,
- )
-
- /**
- * Show only when they *upgrade* to the new version and after they've
- * completed the app intro, which asks them whether they want to receive notifications.
- */
- override val showFloatingButtonFeatureNotification: Flow = combine(
- get(Keys.lastInstalledVersionCodeBackground).map { it ?: -1 },
- get(Keys.approvedFloatingButtonFeaturePrompt).map { it ?: false },
- ) { oldVersionCode, approvedPrompt ->
- oldVersionCode < VersionHelper.FLOATING_BUTTON_MIN_VERSION && !approvedPrompt
- }
-
- override fun showedFloatingButtonFeatureNotification() {
- set(Keys.lastInstalledVersionCodeBackground, Constants.VERSION_CODE)
- }
-
- override val showQuickStartGuideHint: Flow = get(Keys.shownQuickStartGuideHint).map {
- if (it == null) {
- true
- } else {
- !it
- }
- }
-
- override fun shownQuickStartGuideHint() {
- preferences.set(Keys.shownQuickStartGuideHint, true)
- }
-
- override fun isTvDevice(): Boolean = leanbackAdapter.isTvDevice()
-
- override val promptForShizukuPermission: Flow = combine(
- preferences.get(Keys.shownShizukuPermissionPrompt),
- shizukuAdapter.isInstalled,
- permissionAdapter.isGrantedFlow(Permission.SHIZUKU),
- ) {
- shownPromptBefore,
- isShizkuInstalled,
- isShizukuPermissionGranted,
- ->
- shownPromptBefore != true && isShizkuInstalled && !isShizukuPermissionGranted
- }
-
- override val showShizukuAppIntroSlide: Boolean
- get() = shizukuAdapter.isInstalled.value
-
- override val showNoKeysDetectedBottomSheet: Flow =
- preferences.get(Keys.neverShowNoKeysRecordedError).map { neverShow ->
- if (neverShow == null) {
- true
- } else {
- !neverShow
- }
- }
-
- override fun neverShowNoKeysRecordedBottomSheet() {
- preferences.set(Keys.neverShowNoKeysRecordedError, true)
- }
-
- override val hasViewedAdvancedTriggers: Flow =
- get(Keys.viewedAdvancedTriggers).map { it ?: false }
-
- override fun viewedAdvancedTriggers() {
- set(Keys.viewedAdvancedTriggers, true)
- }
-}
-
-interface OnboardingUseCase {
- var shownAppIntro: Boolean
-
- /**
- * @return whether to prompt the user to install the Key Mapper GUI Keyboard after adding
- * this action
- */
- suspend fun showInstallGuiKeyboardPrompt(action: ActionData): Boolean
-
- /**
- * @return whether to prompt the user to install Shizuku after adding
- * this action
- */
- suspend fun showInstallShizukuPrompt(action: ActionData): Boolean
-
- fun isTvDevice(): Boolean
- fun neverShowGuiKeyboardPromptsAgain()
-
- var shownParallelTriggerOrderExplanation: Boolean
- var shownSequenceTriggerExplanation: Boolean
- var shownKeyCodeToScanCodeTriggerExplanation: Boolean
-
- val showFloatingButtonFeatureNotification: Flow
- fun showedFloatingButtonFeatureNotification()
- var approvedFloatingButtonFeaturePrompt: Boolean
-
- val showWhatsNew: Flow
- fun showedWhatsNew()
- fun getWhatsNewText(): String
-
- val showQuickStartGuideHint: Flow
- fun shownQuickStartGuideHint()
-
- val promptForShizukuPermission: Flow
-
- val showShizukuAppIntroSlide: Boolean
-
- val showNoKeysDetectedBottomSheet: Flow
- fun neverShowNoKeysRecordedBottomSheet()
-
- val hasViewedAdvancedTriggers: Flow
- fun viewedAdvancedTriggers()
-}
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
deleted file mode 100644
index 7ad6b4c003..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/purchasing/PurchasingManager.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package io.github.sds100.keymapper.purchasing
-
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.State
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-
-interface PurchasingManager {
- val onCompleteProductPurchase: MutableSharedFlow
- 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/java/io/github/sds100/keymapper/purchasing/PurchasingManagerImpl.kt b/app/src/main/java/io/github/sds100/keymapper/purchasing/PurchasingManagerImpl.kt
new file mode 100644
index 0000000000..0011982ba9
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/purchasing/PurchasingManagerImpl.kt
@@ -0,0 +1,30 @@
+package io.github.sds100.keymapper.purchasing
+
+import io.github.sds100.keymapper.base.purchasing.ProductId
+import io.github.sds100.keymapper.base.purchasing.PurchasingError
+import io.github.sds100.keymapper.base.purchasing.PurchasingManager
+import io.github.sds100.keymapper.common.utils.KMResult
+import io.github.sds100.keymapper.common.utils.State
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class PurchasingManagerImpl : PurchasingManager {
+ override val onCompleteProductPurchase: MutableSharedFlow = MutableSharedFlow()
+ override val purchases: Flow>>> =
+ MutableStateFlow(State.Data(PurchasingError.PurchasingNotImplemented))
+
+ override suspend fun launchPurchasingFlow(product: ProductId): KMResult {
+ return PurchasingError.PurchasingNotImplemented
+ }
+
+ override suspend fun getProductPrice(product: ProductId): KMResult {
+ return PurchasingError.PurchasingNotImplemented
+ }
+
+ override suspend fun isPurchased(product: ProductId): KMResult {
+ return PurchasingError.PurchasingNotImplemented
+ }
+
+ override fun refresh() {}
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/BuildUtils.kt b/app/src/main/java/io/github/sds100/keymapper/system/BuildUtils.kt
deleted file mode 100644
index 6ab2e09f90..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/BuildUtils.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-package io.github.sds100.keymapper.system
-
-import android.os.Build
-import android.os.Build.VERSION_CODES.JELLY_BEAN
-import android.os.Build.VERSION_CODES.JELLY_BEAN_MR1
-import android.os.Build.VERSION_CODES.JELLY_BEAN_MR2
-import android.os.Build.VERSION_CODES.KITKAT
-import android.os.Build.VERSION_CODES.LOLLIPOP
-import android.os.Build.VERSION_CODES.LOLLIPOP_MR1
-import android.os.Build.VERSION_CODES.M
-import android.os.Build.VERSION_CODES.N
-import android.os.Build.VERSION_CODES.N_MR1
-import android.os.Build.VERSION_CODES.O
-import android.os.Build.VERSION_CODES.O_MR1
-import android.os.Build.VERSION_CODES.P
-import android.os.Build.VERSION_CODES.Q
-import android.os.Build.VERSION_CODES.R
-import android.os.Build.VERSION_CODES.S
-import android.os.Build.VERSION_CODES.S_V2
-import android.os.Build.VERSION_CODES.TIRAMISU
-import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
-
-/**
- * Created by sds100 on 12/01/2019.
- */
-
-object BuildUtils {
- /**
- * @return the name of the android version for each [Build.VERSION_CODES] after the minimum api for this app.
- *
- * E.g 28 = "Pie 9.0"
- */
- fun getSdkVersionName(version: Int): String = when (version) {
- JELLY_BEAN -> "Jelly Bean 4.1"
- JELLY_BEAN_MR1 -> "Jelly Bean 4.2"
- JELLY_BEAN_MR2 -> "Jelly Bean 4.3"
- KITKAT -> "KitKat 4.4"
- LOLLIPOP -> "Lollipop 5.0"
- LOLLIPOP_MR1 -> "Lollipop 5.1"
- M -> "Marshmallow 6.0"
- N -> "Nougat 7.0"
- N_MR1 -> "Nougat 7.1"
- O -> "Oreo 8.0"
- O_MR1 -> "Oreo 8.1"
- P -> "Pie 9.0"
- Q -> "10"
- R -> "11"
- S -> "12"
- S_V2 -> "12L"
- TIRAMISU -> "13"
- UPSIDE_DOWN_CAKE -> "14"
- else -> "API $version"
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityNodeAction.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityNodeAction.kt
deleted file mode 100644
index 12af5f515c..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityNodeAction.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.github.sds100.keymapper.system.accessibility
-
-/**
- * Created by sds100 on 24/04/2021.
- */
-data class AccessibilityNodeAction(val action: Int, val extras: Map = emptyMap())
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt
new file mode 100644
index 0000000000..6bc72f0bbe
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt
@@ -0,0 +1,48 @@
+package io.github.sds100.keymapper.system.accessibility
+
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import io.github.sds100.keymapper.base.actions.PerformActionsUseCaseImpl
+import io.github.sds100.keymapper.base.constraints.DetectConstraintsUseCaseImpl
+import io.github.sds100.keymapper.base.keymaps.FingerprintGesturesSupportedUseCase
+import io.github.sds100.keymapper.base.keymaps.PauseKeyMapsUseCase
+import io.github.sds100.keymapper.base.keymaps.detection.DetectKeyMapsUseCaseImpl
+import io.github.sds100.keymapper.base.reroutekeyevents.RerouteKeyEventsController
+import io.github.sds100.keymapper.base.system.accessibility.AccessibilityNodeRecorder
+import io.github.sds100.keymapper.base.system.accessibility.BaseAccessibilityServiceController
+import io.github.sds100.keymapper.data.repositories.PreferenceRepository
+import io.github.sds100.keymapper.system.devices.DevicesAdapter
+import io.github.sds100.keymapper.system.root.SuAdapter
+
+class AccessibilityServiceController @AssistedInject constructor(
+ @Assisted
+ private val service: MyAccessibilityService,
+ rerouteKeyEventsControllerFactory: RerouteKeyEventsController.Factory,
+ accessibilityNodeRecorderFactory: AccessibilityNodeRecorder.Factory,
+ performActionsUseCaseFactory: PerformActionsUseCaseImpl.Factory,
+ detectKeyMapsUseCaseFactory: DetectKeyMapsUseCaseImpl.Factory,
+ detectConstraintsUseCaseFactory: DetectConstraintsUseCaseImpl.Factory,
+ fingerprintGesturesSupported: FingerprintGesturesSupportedUseCase,
+ pauseKeyMapsUseCase: PauseKeyMapsUseCase,
+ devicesAdapter: DevicesAdapter,
+ suAdapter: SuAdapter,
+ settingsRepository: PreferenceRepository,
+) : BaseAccessibilityServiceController(
+ service = service,
+ rerouteKeyEventsControllerFactory = rerouteKeyEventsControllerFactory,
+ accessibilityNodeRecorderFactory = accessibilityNodeRecorderFactory,
+ performActionsUseCaseFactory = performActionsUseCaseFactory,
+ detectKeyMapsUseCaseFactory = detectKeyMapsUseCaseFactory,
+ detectConstraintsUseCaseFactory = detectConstraintsUseCaseFactory,
+ fingerprintGesturesSupported = fingerprintGesturesSupported,
+ pauseKeyMapsUseCase = pauseKeyMapsUseCase,
+ devicesAdapter = devicesAdapter,
+ suAdapter = suAdapter,
+ settingsRepository = settingsRepository,
+) {
+ @AssistedFactory
+ interface Factory {
+ fun create(service: MyAccessibilityService): AccessibilityServiceController
+ }
+}
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 c640bcf18f..6d470eb0a7 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
@@ -1,247 +1,33 @@
package io.github.sds100.keymapper.system.accessibility
-import android.accessibilityservice.AccessibilityService
-import android.accessibilityservice.FingerprintGestureController
-import android.accessibilityservice.GestureDescription
-import android.accessibilityservice.GestureDescription.StrokeDescription
-import android.annotation.SuppressLint
-import android.app.ActivityManager
import android.content.Intent
-import android.content.res.Configuration
-import android.graphics.Path
-import android.graphics.Point
-import android.os.Build
-import android.view.KeyEvent
-import android.view.MotionEvent
-import android.view.accessibility.AccessibilityEvent
-import androidx.core.content.getSystemService
-import androidx.core.os.bundleOf
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import androidx.savedstate.SavedStateRegistry
-import androidx.savedstate.SavedStateRegistryController
-import androidx.savedstate.SavedStateRegistryOwner
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.actions.pinchscreen.PinchScreenType
-import io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback
-import io.github.sds100.keymapper.api.KeyEventRelayService
-import io.github.sds100.keymapper.api.KeyEventRelayServiceWrapperImpl
-import io.github.sds100.keymapper.mappings.FingerprintGestureType
-import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyEventDetectionSource
-import io.github.sds100.keymapper.system.devices.InputDeviceUtils
-import io.github.sds100.keymapper.system.inputevents.MyKeyEvent
-import io.github.sds100.keymapper.system.inputevents.MyMotionEvent
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.InputEventType
-import io.github.sds100.keymapper.util.MathUtils
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.Success
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.update
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.system.accessibility.BaseAccessibilityService
+import io.github.sds100.keymapper.base.system.accessibility.BaseAccessibilityServiceController
import timber.log.Timber
+import javax.inject.Inject
-/**
- * Created by sds100 on 05/04/2020.
- */
+@AndroidEntryPoint
+class MyAccessibilityService : BaseAccessibilityService() {
-class MyAccessibilityService :
- AccessibilityService(),
- LifecycleOwner,
- IAccessibilityService,
- SavedStateRegistryOwner {
+ @Inject
+ lateinit var controllerFactory: AccessibilityServiceController.Factory
- // virtual distance between fingers on multitouch gestures
- private val fingerGestureDistance = 10L
+ private var controller: AccessibilityServiceController? = null
- private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
- private var savedStateRegistryController: SavedStateRegistryController? =
- SavedStateRegistryController.create(this)
- override val savedStateRegistry: SavedStateRegistry
- get() = savedStateRegistryController!!.savedStateRegistry
-
- private var fingerprintGestureCallback: FingerprintGestureController.FingerprintGestureCallback? =
- null
-
- override val rootNode: AccessibilityNodeModel?
- get() {
- return rootInActiveWindow?.toModel()
- }
-
- private val _activeWindowPackage: MutableStateFlow = MutableStateFlow(null)
- override val activeWindowPackage: Flow = _activeWindowPackage
-
- override val isFingerprintGestureDetectionAvailable: Boolean
- get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- fingerprintGestureController.isGestureDetectionAvailable
- } else {
- false
- }
-
- private val _isKeyboardHidden by lazy {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- MutableStateFlow(softKeyboardController.showMode == SHOW_MODE_HIDDEN)
- } else {
- MutableStateFlow(false)
- }
- }
-
- override val isKeyboardHidden: Flow
- get() = _isKeyboardHidden
-
- override var serviceFlags: Int?
- get() = serviceInfo?.flags
- set(value) {
- if (serviceInfo != null && value != null) {
- serviceInfo = serviceInfo.apply {
- flags = value
- }
- }
- }
-
- override var serviceFeedbackType: Int?
- get() = serviceInfo?.feedbackType
- set(value) {
- if (serviceInfo != null && value != null) {
- serviceInfo = serviceInfo.apply {
- feedbackType = value
- }
- }
- }
-
- override var serviceEventTypes: Int?
- get() = serviceInfo?.eventTypes
- set(value) {
- if (serviceInfo != null && value != null) {
- serviceInfo = serviceInfo.apply {
- eventTypes = value
- }
- }
- }
-
- override var notificationTimeout: Long?
- get() = serviceInfo?.notificationTimeout
- set(value) {
- if (serviceInfo != null && value != null) {
- serviceInfo = serviceInfo.apply {
- notificationTimeout = value
- }
- }
- }
-
- private val relayServiceCallback: IKeyEventRelayServiceCallback =
- object : IKeyEventRelayServiceCallback.Stub() {
- override fun onKeyEvent(event: KeyEvent?): Boolean {
- event ?: return false
-
- val device = event.device?.let { InputDeviceUtils.createInputDeviceInfo(it) }
-
- if (controller != null) {
- return controller!!.onKeyEventFromIme(
- MyKeyEvent(
- keyCode = event.keyCode,
- action = event.action,
- metaState = event.metaState,
- scanCode = event.scanCode,
- device = device,
- repeatCount = event.repeatCount,
- source = event.source,
- ),
- )
- }
-
- return false
- }
-
- override fun onMotionEvent(event: MotionEvent?): Boolean {
- event ?: return false
-
- if (controller != null) {
- return controller!!.onMotionEventFromIme(MyMotionEvent.fromMotionEvent(event))
- }
-
- return false
- }
- }
-
- private val keyEventRelayServiceWrapper: KeyEventRelayServiceWrapperImpl by lazy {
- KeyEventRelayServiceWrapperImpl(
- ctx = this,
- id = KeyEventRelayService.CALLBACK_ID_ACCESSIBILITY_SERVICE,
- callback = relayServiceCallback,
- )
- }
-
- var controller: AccessibilityServiceController? = null
-
- override fun onCreate() {
- super.onCreate()
- Timber.i("Accessibility service: onCreate")
-
- savedStateRegistryController?.performAttach()
- savedStateRegistryController?.performRestore(null)
-
- lifecycleRegistry.currentState = Lifecycle.State.CREATED
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- softKeyboardController.addOnShowModeChangedListener { _, showMode ->
- when (showMode) {
- SHOW_MODE_AUTO -> _isKeyboardHidden.value = false
- SHOW_MODE_HIDDEN -> _isKeyboardHidden.value = true
- }
- }
- }
-
- keyEventRelayServiceWrapper.onCreate()
+ override fun getController(): BaseAccessibilityServiceController? {
+ return controller
}
override fun onServiceConnected() {
super.onServiceConnected()
- Timber.i("Accessibility service: onServiceConnected")
- lifecycleRegistry.currentState = Lifecycle.State.STARTED
-
- setTheme(R.style.AppTheme)
-
- _activeWindowPackage.update { rootInActiveWindow?.packageName?.toString() }
/*
I would put this in onCreate but for some reason on some devices getting the application
context would return null
*/
if (controller == null) {
- controller = Inject.accessibilityServiceController(this, keyEventRelayServiceWrapper)
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- fingerprintGestureCallback =
- object : FingerprintGestureController.FingerprintGestureCallback() {
- override fun onGestureDetected(gesture: Int) {
- super.onGestureDetected(gesture)
-
- val id: FingerprintGestureType = when (gesture) {
- FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN ->
- FingerprintGestureType.SWIPE_DOWN
-
- FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP ->
- FingerprintGestureType.SWIPE_UP
-
- FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_LEFT ->
- FingerprintGestureType.SWIPE_LEFT
-
- FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_RIGHT ->
- FingerprintGestureType.SWIPE_RIGHT
-
- else -> return
- }
- controller?.onFingerprintGesture(id)
- }
- }
-
- fingerprintGestureCallback?.let {
- fingerprintGestureController.registerFingerprintGestureCallback(it, null)
- }
+ controller = controllerFactory.create(this)
}
controller?.onServiceConnected()
@@ -258,327 +44,6 @@ class MyAccessibilityService :
controller?.onDestroy()
controller = null
- lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- fingerprintGestureController
- .unregisterFingerprintGestureCallback(fingerprintGestureCallback)
- }
-
- keyEventRelayServiceWrapper.onDestroy()
-
- Timber.i("Accessibility service: onDestroy")
-
super.onDestroy()
}
-
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
-
- controller?.onConfigurationChanged(newConfig)
- }
-
- override fun onTrimMemory(level: Int) {
- val memoryInfo = ActivityManager.MemoryInfo()
- getSystemService()?.getMemoryInfo(memoryInfo)
-
- Timber.i("Accessibility service: onLowMemory, total: ${memoryInfo.totalMem}, available: ${memoryInfo.availMem}, is low memory: ${memoryInfo.lowMemory}, threshold: ${memoryInfo.threshold}")
-
- super.onTrimMemory(level)
- }
-
- override fun onAccessibilityEvent(event: AccessibilityEvent?) {
- event ?: return
-
- if (event.eventType == AccessibilityEvent.TYPE_WINDOWS_CHANGED) {
- _activeWindowPackage.update { rootInActiveWindow?.packageName?.toString() }
- }
-
- controller?.onAccessibilityEvent(event)
- }
-
- override fun onKeyEvent(event: KeyEvent?): Boolean {
- event ?: return super.onKeyEvent(event)
-
- val device = if (event.device == null) {
- null
- } else {
- InputDeviceUtils.createInputDeviceInfo(event.device)
- }
-
- if (controller != null) {
- return controller!!.onKeyEvent(
- MyKeyEvent(
- keyCode = event.keyCode,
- action = event.action,
- metaState = event.metaState,
- scanCode = event.scanCode,
- device = device,
- repeatCount = event.repeatCount,
- source = event.source,
- ),
- KeyEventDetectionSource.ACCESSIBILITY_SERVICE,
- )
- }
-
- return false
- }
-
- override val lifecycle: Lifecycle
- get() = lifecycleRegistry
-
- override fun hideKeyboard() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- softKeyboardController.showMode = SHOW_MODE_HIDDEN
- }
- }
-
- override fun showKeyboard() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- softKeyboardController.showMode = SHOW_MODE_AUTO
- }
- }
-
- override fun switchIme(imeId: String) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- softKeyboardController.switchToInputMethod(imeId)
- }
- }
-
- override fun performActionOnNode(
- findNode: (node: AccessibilityNodeModel) -> Boolean,
- performAction: (node: AccessibilityNodeModel) -> AccessibilityNodeAction?,
- ): Result<*> {
- val node = rootInActiveWindow.findNodeRecursively {
- findNode(it.toModel())
- }
-
- if (node == null) {
- return Error.FailedToFindAccessibilityNode
- }
-
- val (action, extras) = performAction(node.toModel()) ?: return Success(Unit)
-
- node.performAction(action, bundleOf(*extras.toList().toTypedArray()))
- node.recycle()
-
- return Success(Unit)
- }
-
- override fun doGlobalAction(action: Int): Result<*> {
- val success = performGlobalAction(action)
-
- if (success) {
- return Success(Unit)
- } else {
- return Error.FailedToPerformAccessibilityGlobalAction(action)
- }
- }
-
- override fun tapScreen(x: Int, y: Int, inputEventType: InputEventType): Result<*> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- val duration = 1L // ms
-
- val path = Path().apply {
- moveTo(x.toFloat(), y.toFloat())
- }
-
- val strokeDescription =
- when {
- inputEventType == InputEventType.DOWN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ->
- StrokeDescription(
- path,
- 0,
- duration,
- true,
- )
-
- inputEventType == InputEventType.UP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ->
- StrokeDescription(
- path,
- 59999,
- duration,
- false,
- )
-
- else -> StrokeDescription(path, 0, duration)
- }
-
- strokeDescription.let {
- val gestureDescription = GestureDescription.Builder().apply {
- addStroke(it)
- }.build()
-
- val success = dispatchGesture(gestureDescription, null, null)
-
- return if (success) {
- Success(Unit)
- } else {
- Error.FailedToDispatchGesture
- }
- }
- }
-
- return Error.SdkVersionTooLow(Build.VERSION_CODES.N)
- }
-
- override fun swipeScreen(
- xStart: Int,
- yStart: Int,
- xEnd: Int,
- yEnd: Int,
- fingerCount: Int,
- duration: Int,
- inputEventType: InputEventType,
- ): Result<*> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- if (fingerCount >= GestureDescription.getMaxStrokeCount()) {
- return Error.GestureStrokeCountTooHigh
- }
- if (duration >= GestureDescription.getMaxGestureDuration()) {
- return Error.GestureDurationTooHigh
- }
-
- val pStart = Point(xStart, yStart)
- val pEnd = Point(xEnd, yEnd)
-
- val gestureBuilder = GestureDescription.Builder()
-
- if (fingerCount == 1) {
- val p = Path()
- p.moveTo(pStart.x.toFloat(), pStart.y.toFloat())
- p.lineTo(pEnd.x.toFloat(), pEnd.y.toFloat())
- gestureBuilder.addStroke(StrokeDescription(p, 0, duration.toLong()))
- } else {
- // segments between fingers
- val segmentCount = fingerCount - 1
- // the line of the perpendicular line which will be created to place the virtual fingers on it
- val perpendicularLineLength = (fingerGestureDistance * fingerCount).toInt()
-
- // the length of each segment between fingers
- val segmentLength = perpendicularLineLength / segmentCount
- // perpendicular line of the start swipe point
- val perpendicularLineStart = MathUtils.getPerpendicularOfLine(
- pStart,
- pEnd,
- perpendicularLineLength,
- )
- // perpendicular line of the end swipe point
- val perpendicularLineEnd = MathUtils.getPerpendicularOfLine(
- pEnd,
- pStart,
- perpendicularLineLength,
- true,
- )
-
- // this is the angle between start and end point to rotate all virtual fingers on the perpendicular lines in the same direction
- val angle =
- MathUtils.angleBetweenPoints(Point(xStart, yStart), Point(xEnd, yEnd)) - 90
-
- // create the virtual fingers
- for (index in 0..segmentCount) {
- // offset of each finger
- val fingerOffsetLength = index * segmentLength * 2
- // move the coordinates of the current virtual finger on the perpendicular line for the start coordinates
- val startFingerCoordinateWithOffset =
- MathUtils.movePointByDistanceAndAngle(
- perpendicularLineStart.start,
- fingerOffsetLength,
- angle,
- )
- // move the coordinates of the current virtual finger on the perpendicular line for the end coordinates
- val endFingerCoordinateWithOffset =
- MathUtils.movePointByDistanceAndAngle(
- perpendicularLineEnd.start,
- fingerOffsetLength,
- angle,
- )
-
- // create a path for each finger, move the the coordinates on the perpendicular line and draw it to the end coordinates of the perpendicular line of the end swipe point
- val p = Path()
- p.moveTo(
- startFingerCoordinateWithOffset.x.toFloat(),
- startFingerCoordinateWithOffset.y.toFloat(),
- )
- p.lineTo(
- endFingerCoordinateWithOffset.x.toFloat(),
- endFingerCoordinateWithOffset.y.toFloat(),
- )
-
- gestureBuilder.addStroke(StrokeDescription(p, 0, duration.toLong()))
- }
- }
-
- val success = dispatchGesture(gestureBuilder.build(), null, null)
-
- return if (success) {
- Success(Unit)
- } else {
- Error.FailedToDispatchGesture
- }
- }
-
- return Error.SdkVersionTooLow(Build.VERSION_CODES.N)
- }
-
- override fun pinchScreen(
- x: Int,
- y: Int,
- distance: Int,
- pinchType: PinchScreenType,
- fingerCount: Int,
- duration: Int,
- inputEventType: InputEventType,
- ): Result<*> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- if (fingerCount >= GestureDescription.getMaxStrokeCount()) {
- return Error.GestureStrokeCountTooHigh
- }
- if (duration >= GestureDescription.getMaxGestureDuration()) {
- return Error.GestureDurationTooHigh
- }
-
- val gestureBuilder = GestureDescription.Builder()
- val distributedPoints: List =
- MathUtils.distributePointsOnCircle(Point(x, y), distance.toFloat() / 2, fingerCount)
-
- for (index in distributedPoints.indices) {
- val p = Path()
- if (pinchType == PinchScreenType.PINCH_IN) {
- p.moveTo(x.toFloat(), y.toFloat())
- p.lineTo(
- distributedPoints[index].x.toFloat(),
- distributedPoints[index].y.toFloat(),
- )
- } else {
- p.moveTo(
- distributedPoints[index].x.toFloat(),
- distributedPoints[index].y.toFloat(),
- )
- p.lineTo(x.toFloat(), y.toFloat())
- }
-
- gestureBuilder.addStroke(StrokeDescription(p, 0, duration.toLong()))
- }
-
- val success = dispatchGesture(gestureBuilder.build(), null, null)
-
- return if (success) {
- Success(Unit)
- } else {
- Error.FailedToDispatchGesture
- }
- }
-
- return Error.SdkVersionTooLow(Build.VERSION_CODES.N)
- }
-
- override fun findFocussedNode(focus: Int): AccessibilityNodeModel? = findFocus(focus)?.toModel()
-
- override fun setInputMethodEnabled(imeId: String, enabled: Boolean) {
- @SuppressLint("CheckResult")
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- softKeyboardController.setInputMethodEnabled(imeId, enabled)
- }
- }
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/airplanemode/AirplaneModeAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/airplanemode/AirplaneModeAdapter.kt
deleted file mode 100644
index f4ce8d93b6..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/airplanemode/AirplaneModeAdapter.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package io.github.sds100.keymapper.system.airplanemode
-
-import io.github.sds100.keymapper.util.Result
-
-/**
- * Created by sds100 on 24/04/2021.
- */
-interface AirplaneModeAdapter {
- fun isEnabled(): Boolean
- fun enable(): Result<*>
- fun disable(): Result<*>
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/apps/DisplayAppsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/system/apps/DisplayAppsUseCase.kt
deleted file mode 100644
index 22abf49a84..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/apps/DisplayAppsUseCase.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package io.github.sds100.keymapper.system.apps
-
-import android.graphics.drawable.Drawable
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.State
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Created by sds100 on 04/04/2021.
- */
-
-class DisplayAppsUseCaseImpl(
- private val adapter: PackageManagerAdapter,
-) : DisplayAppsUseCase {
- override val installedPackages: Flow>> = adapter.installedPackages
-
- override fun getAppName(packageName: String): Result = adapter.getAppName(packageName)
-
- override fun getAppIcon(packageName: String): Result = adapter.getAppIcon(packageName)
-
- override fun getActivityLabel(packageName: String, activityClass: String): Result =
- adapter.getActivityLabel(packageName, activityClass)
-
- override fun getActivityIcon(packageName: String, activityClass: String): Result =
- adapter.getActivityIcon(packageName, activityClass)
-}
-
-interface DisplayAppsUseCase {
- val installedPackages: Flow>>
-
- fun getActivityLabel(packageName: String, activityClass: String): Result
- fun getActivityIcon(packageName: String, activityClass: String): Result
- fun getAppName(packageName: String): Result
- fun getAppIcon(packageName: String): Result
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/camera/CameraLensUtils.kt b/app/src/main/java/io/github/sds100/keymapper/system/camera/CameraLensUtils.kt
deleted file mode 100644
index 80f7090f2d..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/camera/CameraLensUtils.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package io.github.sds100.keymapper.system.camera
-
-import io.github.sds100.keymapper.R
-
-/**
- * Created by sds100 on 23/03/2021.
- */
-object CameraLensUtils {
- fun getLabel(lens: CameraLens) = when (lens) {
- CameraLens.FRONT -> R.string.lens_front
- CameraLens.BACK -> R.string.lens_back
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/display/OrientationUtils.kt b/app/src/main/java/io/github/sds100/keymapper/system/display/OrientationUtils.kt
deleted file mode 100644
index fc3607627e..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/display/OrientationUtils.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package io.github.sds100.keymapper.system.display
-
-import io.github.sds100.keymapper.R
-
-/**
- * Created by sds100 on 23/03/2021.
- */
-object OrientationUtils {
- fun getLabel(orientation: Orientation) = when (orientation) {
- Orientation.ORIENTATION_0 -> R.string.orientation_0
- Orientation.ORIENTATION_90 -> R.string.orientation_90
- Orientation.ORIENTATION_180 -> R.string.orientation_180
- Orientation.ORIENTATION_270 -> R.string.orientation_270
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventUtils.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventUtils.kt
deleted file mode 100644
index a42c1949fa..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventUtils.kt
+++ /dev/null
@@ -1,872 +0,0 @@
-package io.github.sds100.keymapper.system.inputevents
-
-import android.os.Build
-import android.view.InputDevice
-import android.view.InputEvent
-import android.view.KeyEvent
-import io.github.sds100.keymapper.R
-import splitties.bitflags.withFlag
-
-/**
- * Created by sds100 on 17/07/2018.
- */
-object InputEventUtils {
- /**
- * Maps keys which aren't single characters like the Control keys to a string representation
- */
- private val NON_CHARACTER_KEY_LABELS: Map
- get() = sequence {
- yieldAll(
- listOf(
- KeyEvent.KEYCODE_VOLUME_DOWN to "Volume down",
- KeyEvent.KEYCODE_VOLUME_UP to "Volume up",
-
- KeyEvent.KEYCODE_CTRL_LEFT to "Ctrl Left",
- KeyEvent.KEYCODE_CTRL_RIGHT to "Ctrl Right",
-
- KeyEvent.KEYCODE_SHIFT_LEFT to "Shift Left",
- KeyEvent.KEYCODE_SHIFT_RIGHT to "Shift Right",
-
- KeyEvent.KEYCODE_ALT_LEFT to "Alt Left",
- KeyEvent.KEYCODE_ALT_RIGHT to "Alt Right",
-
- KeyEvent.KEYCODE_DPAD_LEFT to "Left",
- KeyEvent.KEYCODE_DPAD_RIGHT to "Right",
- KeyEvent.KEYCODE_DPAD_DOWN to "Down",
- KeyEvent.KEYCODE_DPAD_UP to "Up",
-
- KeyEvent.KEYCODE_ENTER to "Enter",
- KeyEvent.KEYCODE_HOME to "Home",
- KeyEvent.KEYCODE_BACK to "Back",
- KeyEvent.KEYCODE_MENU to "Menu",
- KeyEvent.KEYCODE_APP_SWITCH to "Recents",
- KeyEvent.KEYCODE_DEL to "Delete",
- KeyEvent.KEYCODE_TAB to "Tab",
- KeyEvent.KEYCODE_SPACE to "Space",
- KeyEvent.KEYCODE_SEARCH to "Search",
- KeyEvent.KEYCODE_CAPS_LOCK to "Caps Lock",
- KeyEvent.KEYCODE_HEADSETHOOK to "Headset Button",
- KeyEvent.KEYCODE_NUM_LOCK to "Num Lock",
-
- KeyEvent.KEYCODE_BUTTON_1 to "Button 1",
- KeyEvent.KEYCODE_BUTTON_2 to "Button 2",
- KeyEvent.KEYCODE_BUTTON_3 to "Button 3",
- KeyEvent.KEYCODE_BUTTON_4 to "Button 4",
- KeyEvent.KEYCODE_BUTTON_5 to "Button 5",
- KeyEvent.KEYCODE_BUTTON_6 to "Button 6",
- KeyEvent.KEYCODE_BUTTON_7 to "Button 7",
- KeyEvent.KEYCODE_BUTTON_8 to "Button 8",
- KeyEvent.KEYCODE_BUTTON_9 to "Button 9",
- KeyEvent.KEYCODE_BUTTON_10 to "Button 10",
- KeyEvent.KEYCODE_BUTTON_11 to "Button 11",
- KeyEvent.KEYCODE_BUTTON_12 to "Button 12",
- KeyEvent.KEYCODE_BUTTON_13 to "Button 13",
- KeyEvent.KEYCODE_BUTTON_14 to "Button 14",
- KeyEvent.KEYCODE_BUTTON_15 to "Button 15",
- KeyEvent.KEYCODE_BUTTON_16 to "Button 16",
-
- KeyEvent.KEYCODE_BUTTON_L1 to "Button L1",
- KeyEvent.KEYCODE_BUTTON_L2 to "Button L2",
- KeyEvent.KEYCODE_BUTTON_R1 to "Button R1",
- KeyEvent.KEYCODE_BUTTON_R2 to "Button R2",
-
- KeyEvent.KEYCODE_BUTTON_A to "Button A",
- KeyEvent.KEYCODE_BUTTON_B to "Button B",
- KeyEvent.KEYCODE_BUTTON_C to "Button C",
-
- KeyEvent.KEYCODE_BUTTON_X to "Button X",
- KeyEvent.KEYCODE_BUTTON_Y to "Button Y",
- KeyEvent.KEYCODE_BUTTON_Z to "Button Z",
-
- KeyEvent.KEYCODE_BUTTON_THUMBL to "Thumb Left",
- KeyEvent.KEYCODE_BUTTON_THUMBR to "Thumb Right",
-
- KeyEvent.KEYCODE_BUTTON_START to "Start",
- KeyEvent.KEYCODE_BUTTON_SELECT to "Select",
-
- KeyEvent.KEYCODE_SOFT_LEFT to "SOFT_LEFT",
- KeyEvent.KEYCODE_SOFT_RIGHT to "SOFT_RIGHT",
- KeyEvent.KEYCODE_CALL to "Call",
- KeyEvent.KEYCODE_ENDCALL to "End Call",
- KeyEvent.KEYCODE_0 to "0",
- KeyEvent.KEYCODE_1 to "1",
- KeyEvent.KEYCODE_2 to "2",
- KeyEvent.KEYCODE_3 to "3",
- KeyEvent.KEYCODE_4 to "4",
- KeyEvent.KEYCODE_5 to "5",
- KeyEvent.KEYCODE_6 to "6",
- KeyEvent.KEYCODE_7 to "7",
- KeyEvent.KEYCODE_8 to "8",
- KeyEvent.KEYCODE_9 to "9",
- KeyEvent.KEYCODE_STAR to "*",
- KeyEvent.KEYCODE_POUND to "#",
- KeyEvent.KEYCODE_DPAD_CENTER to "DPAD Center",
- KeyEvent.KEYCODE_POWER to "Power",
- KeyEvent.KEYCODE_CAMERA to "Camera",
- KeyEvent.KEYCODE_CLEAR to "Clear",
- KeyEvent.KEYCODE_A to "A",
- KeyEvent.KEYCODE_B to "B",
- KeyEvent.KEYCODE_C to "C",
- KeyEvent.KEYCODE_D to "D",
- KeyEvent.KEYCODE_E to "E",
- KeyEvent.KEYCODE_F to "F",
- KeyEvent.KEYCODE_G to "G",
- KeyEvent.KEYCODE_H to "H",
- KeyEvent.KEYCODE_I to "I",
- KeyEvent.KEYCODE_J to "J",
- KeyEvent.KEYCODE_K to "K",
- KeyEvent.KEYCODE_L to "L",
- KeyEvent.KEYCODE_M to "M",
- KeyEvent.KEYCODE_N to "N",
- KeyEvent.KEYCODE_O to "O",
- KeyEvent.KEYCODE_P to "P",
- KeyEvent.KEYCODE_Q to "Q",
- KeyEvent.KEYCODE_R to "R",
- KeyEvent.KEYCODE_S to "S",
- KeyEvent.KEYCODE_T to "T",
- KeyEvent.KEYCODE_U to "U",
- KeyEvent.KEYCODE_V to "V",
- KeyEvent.KEYCODE_W to "W",
- KeyEvent.KEYCODE_X to "X",
- KeyEvent.KEYCODE_Y to "Y",
- KeyEvent.KEYCODE_Z to "Z",
- KeyEvent.KEYCODE_COMMA to ",",
- KeyEvent.KEYCODE_PERIOD to ".",
- KeyEvent.KEYCODE_SYM to "Symbol",
- KeyEvent.KEYCODE_EXPLORER to "Explorer",
- KeyEvent.KEYCODE_ENVELOPE to "Mail",
- KeyEvent.KEYCODE_GRAVE to "`",
- KeyEvent.KEYCODE_MINUS to "-",
- KeyEvent.KEYCODE_EQUALS to "=",
- KeyEvent.KEYCODE_LEFT_BRACKET to "(",
- KeyEvent.KEYCODE_RIGHT_BRACKET to ")",
- KeyEvent.KEYCODE_BACKSLASH to "\\",
- KeyEvent.KEYCODE_SEMICOLON to ";",
- KeyEvent.KEYCODE_APOSTROPHE to "'",
- KeyEvent.KEYCODE_SLASH to "/",
- KeyEvent.KEYCODE_AT to "@",
- KeyEvent.KEYCODE_NUM to "Num",
- KeyEvent.KEYCODE_FOCUS to "Focus",
- KeyEvent.KEYCODE_PLUS to "+",
- KeyEvent.KEYCODE_NOTIFICATION to "Notification",
- KeyEvent.KEYCODE_SEARCH to "Search",
- KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE to "Play/Pause",
- KeyEvent.KEYCODE_MEDIA_STOP to "Stop Media",
- KeyEvent.KEYCODE_MEDIA_NEXT to "Play Next",
- KeyEvent.KEYCODE_MEDIA_PREVIOUS to "Play Previous",
- KeyEvent.KEYCODE_MEDIA_REWIND to "Rewind",
- KeyEvent.KEYCODE_MEDIA_FAST_FORWARD to "Fast Forward",
- KeyEvent.KEYCODE_MUTE to "Mute Microphone",
- KeyEvent.KEYCODE_PAGE_UP to "Page Up",
- KeyEvent.KEYCODE_PAGE_DOWN to "Page Down",
- KeyEvent.KEYCODE_PICTSYMBOLS to "Picture Symbols",
- KeyEvent.KEYCODE_SWITCH_CHARSET to "Switch Charset",
- KeyEvent.KEYCODE_BUTTON_MODE to "Button Mode",
- KeyEvent.KEYCODE_ESCAPE to "Esc",
- KeyEvent.KEYCODE_FORWARD_DEL to "Forward Del",
- KeyEvent.KEYCODE_SCROLL_LOCK to "Scroll Lock",
- KeyEvent.KEYCODE_META_LEFT to "Meta Left",
- KeyEvent.KEYCODE_META_RIGHT to "Meta Right",
- KeyEvent.KEYCODE_FUNCTION to "Function",
- KeyEvent.KEYCODE_SYSRQ to "SYSRQ",
- KeyEvent.KEYCODE_BREAK to "Break",
- KeyEvent.KEYCODE_MOVE_HOME to "Home",
- KeyEvent.KEYCODE_MOVE_END to "End",
- KeyEvent.KEYCODE_INSERT to "Insert",
- KeyEvent.KEYCODE_FORWARD to "Forward",
- KeyEvent.KEYCODE_MEDIA_PLAY to "Play",
- KeyEvent.KEYCODE_MEDIA_PAUSE to "Pause",
- KeyEvent.KEYCODE_MEDIA_CLOSE to "Media Close",
- KeyEvent.KEYCODE_MEDIA_EJECT to "Eject",
- KeyEvent.KEYCODE_MEDIA_RECORD to "Media Record",
- KeyEvent.KEYCODE_F1 to "F1",
- KeyEvent.KEYCODE_F2 to "F2",
- KeyEvent.KEYCODE_F3 to "F3",
- KeyEvent.KEYCODE_F4 to "F4",
- KeyEvent.KEYCODE_F5 to "F5",
- KeyEvent.KEYCODE_F6 to "F6",
- KeyEvent.KEYCODE_F7 to "F7",
- KeyEvent.KEYCODE_F8 to "F8",
- KeyEvent.KEYCODE_F9 to "F9",
- KeyEvent.KEYCODE_F10 to "F10",
- KeyEvent.KEYCODE_F11 to "F11",
- KeyEvent.KEYCODE_F12 to "F12",
- KeyEvent.KEYCODE_NUMPAD_0 to "Numpad 0",
- KeyEvent.KEYCODE_NUMPAD_1 to "Numpad 1",
- KeyEvent.KEYCODE_NUMPAD_2 to "Numpad 2",
- KeyEvent.KEYCODE_NUMPAD_3 to "Numpad 3",
- KeyEvent.KEYCODE_NUMPAD_4 to "Numpad 4",
- KeyEvent.KEYCODE_NUMPAD_5 to "Numpad 5",
- KeyEvent.KEYCODE_NUMPAD_6 to "Numpad 6",
- KeyEvent.KEYCODE_NUMPAD_7 to "Numpad 7",
- KeyEvent.KEYCODE_NUMPAD_8 to "Numpad 8",
- KeyEvent.KEYCODE_NUMPAD_9 to "Numpad 9",
- KeyEvent.KEYCODE_NUMPAD_DIVIDE to "Numpad Divide",
- KeyEvent.KEYCODE_NUMPAD_MULTIPLY to "Numpad Multiply",
- KeyEvent.KEYCODE_NUMPAD_SUBTRACT to "Numpad -",
- KeyEvent.KEYCODE_NUMPAD_ADD to "Numpad +",
- KeyEvent.KEYCODE_NUMPAD_DOT to "Numpad .",
- KeyEvent.KEYCODE_NUMPAD_COMMA to "Numpad ,",
- KeyEvent.KEYCODE_NUMPAD_ENTER to "Numpad Enter",
- KeyEvent.KEYCODE_NUMPAD_EQUALS to "Numpad =",
- KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN to "Numpad (",
- KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN to "Numpad )",
- KeyEvent.KEYCODE_VOLUME_MUTE to "Mute Volume",
- KeyEvent.KEYCODE_INFO to "Info",
- KeyEvent.KEYCODE_CHANNEL_UP to "Channel Up",
- KeyEvent.KEYCODE_CHANNEL_DOWN to "Channel Down",
- KeyEvent.KEYCODE_ZOOM_IN to "Zoom In",
- KeyEvent.KEYCODE_ZOOM_OUT to "Zoom Out",
- KeyEvent.KEYCODE_TV to "TV",
- KeyEvent.KEYCODE_WINDOW to "Window",
- KeyEvent.KEYCODE_GUIDE to "Guide",
- KeyEvent.KEYCODE_DVR to "DVR",
- KeyEvent.KEYCODE_BOOKMARK to "Bookmark",
- KeyEvent.KEYCODE_CAPTIONS to "Captions",
- KeyEvent.KEYCODE_SETTINGS to "Settings",
- KeyEvent.KEYCODE_TV_POWER to "TV Power",
- KeyEvent.KEYCODE_TV_INPUT to "TV Input",
- KeyEvent.KEYCODE_STB_POWER to "STB Power",
- KeyEvent.KEYCODE_STB_INPUT to "STB Input",
- KeyEvent.KEYCODE_AVR_POWER to "AVR Power",
- KeyEvent.KEYCODE_AVR_INPUT to "AVR Input",
- KeyEvent.KEYCODE_PROG_RED to "TV Red",
- KeyEvent.KEYCODE_PROG_GREEN to "TV Green",
- KeyEvent.KEYCODE_PROG_YELLOW to "TV Yellow",
- KeyEvent.KEYCODE_PROG_BLUE to "TV Blue",
- KeyEvent.KEYCODE_LANGUAGE_SWITCH to "Language Switch",
- KeyEvent.KEYCODE_MANNER_MODE to "Manner Mode",
- KeyEvent.KEYCODE_3D_MODE to "3D Mode",
- KeyEvent.KEYCODE_CONTACTS to "Contacts",
- KeyEvent.KEYCODE_CALENDAR to "Calendar",
- KeyEvent.KEYCODE_MUSIC to "Music",
- KeyEvent.KEYCODE_CALCULATOR to "Calculator",
- KeyEvent.KEYCODE_ZENKAKU_HANKAKU to "Zenkaku Hankaku",
- KeyEvent.KEYCODE_EISU to "Eisu",
- KeyEvent.KEYCODE_MUHENKAN to "Muhenkan",
- KeyEvent.KEYCODE_HENKAN to "Henkan",
- KeyEvent.KEYCODE_KATAKANA_HIRAGANA to "Katakana Hiragana",
- KeyEvent.KEYCODE_YEN to "Yen",
- KeyEvent.KEYCODE_RO to "Ro",
- KeyEvent.KEYCODE_KANA to "Kana",
- KeyEvent.KEYCODE_ASSIST to "Assist",
- KeyEvent.KEYCODE_BRIGHTNESS_DOWN to "Brightness Down",
- KeyEvent.KEYCODE_BRIGHTNESS_UP to "Brightness Up",
- KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK to "Audio Track",
- KeyEvent.KEYCODE_PAIRING to "Pairing",
- KeyEvent.KEYCODE_MEDIA_TOP_MENU to "Media Top Menu",
- KeyEvent.KEYCODE_11 to "11",
- KeyEvent.KEYCODE_12 to "12",
- KeyEvent.KEYCODE_LAST_CHANNEL to "Last Channel",
- KeyEvent.KEYCODE_TV_DATA_SERVICE to "TV Data Service",
- KeyEvent.KEYCODE_VOICE_ASSIST to "Voice Assist",
- KeyEvent.KEYCODE_TV_RADIO_SERVICE to "TV Radio Service",
- KeyEvent.KEYCODE_TV_TELETEXT to "TV Teletext",
- KeyEvent.KEYCODE_TV_NUMBER_ENTRY to "TV Number Entry",
- KeyEvent.KEYCODE_TV_TERRESTRIAL_ANALOG to "TV Terrestrial Analog",
- KeyEvent.KEYCODE_TV_TERRESTRIAL_DIGITAL to "TV Terrestrial Digital",
- KeyEvent.KEYCODE_TV_SATELLITE to "TV Satellite",
- KeyEvent.KEYCODE_TV_SATELLITE_BS to "TV Satellite BS",
- KeyEvent.KEYCODE_TV_SATELLITE_CS to "TV Satellite CS",
- KeyEvent.KEYCODE_TV_SATELLITE_SERVICE to "TV Satellite Service",
- KeyEvent.KEYCODE_TV_NETWORK to "TV Network",
- KeyEvent.KEYCODE_TV_ANTENNA_CABLE to "TV Antenna Cable",
- KeyEvent.KEYCODE_TV_INPUT_HDMI_1 to "TV HDMI 1",
- KeyEvent.KEYCODE_TV_INPUT_HDMI_2 to "TV HDMI 2",
- KeyEvent.KEYCODE_TV_INPUT_HDMI_3 to "TV HDMI 3",
- KeyEvent.KEYCODE_TV_INPUT_HDMI_4 to "TV HDMI 4",
- KeyEvent.KEYCODE_TV_INPUT_COMPOSITE_1 to "TV Composite 1",
- KeyEvent.KEYCODE_TV_INPUT_COMPOSITE_2 to "TV Composite 2",
- KeyEvent.KEYCODE_TV_INPUT_COMPONENT_1 to "TV Component 1",
- KeyEvent.KEYCODE_TV_INPUT_COMPONENT_2 to "TV Component 2",
- KeyEvent.KEYCODE_TV_INPUT_VGA_1 to "TV VGA 1",
- KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION to "TV Audio Description",
- KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP to "TV Audio Description Vol Up",
- KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN to "TV Audio Description Vol Down",
- KeyEvent.KEYCODE_TV_ZOOM_MODE to "TV Zoom Mode",
- KeyEvent.KEYCODE_TV_CONTENTS_MENU to "TV Contents Menu",
- KeyEvent.KEYCODE_TV_MEDIA_CONTEXT_MENU to "TV Media Context Menu",
- KeyEvent.KEYCODE_TV_TIMER_PROGRAMMING to "TV Timer Programming",
- KeyEvent.KEYCODE_HELP to "Help",
- ),
- )
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- yieldAll(
- listOf(
- KeyEvent.KEYCODE_NAVIGATE_PREVIOUS to "Navigate Previous",
- KeyEvent.KEYCODE_NAVIGATE_NEXT to "Navigate Next",
- KeyEvent.KEYCODE_NAVIGATE_IN to "Navigate In",
- KeyEvent.KEYCODE_NAVIGATE_OUT to "Navigate Out",
- KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD to "Media Skip Forward",
- KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD to "Media Skip Backward",
- KeyEvent.KEYCODE_MEDIA_STEP_FORWARD to "Media Step Forward",
- KeyEvent.KEYCODE_MEDIA_STEP_BACKWARD to "Media Step Backward",
- ),
- )
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- yieldAll(
- listOf(
- KeyEvent.KEYCODE_STEM_PRIMARY to "Stem Primary",
- KeyEvent.KEYCODE_STEM_1 to "Stem 1",
- KeyEvent.KEYCODE_STEM_2 to "Stem 2",
- KeyEvent.KEYCODE_STEM_3 to "Stem 3",
- KeyEvent.KEYCODE_DPAD_UP_LEFT to "DPAD Up Left",
- KeyEvent.KEYCODE_DPAD_DOWN_LEFT to "DPAD Down Left",
- KeyEvent.KEYCODE_DPAD_UP_RIGHT to "DPAD Up Right",
- KeyEvent.KEYCODE_DPAD_DOWN_RIGHT to "DPAD Down Right",
- KeyEvent.KEYCODE_SOFT_SLEEP to "Soft Sleep",
- KeyEvent.KEYCODE_CUT to "Cut",
- KeyEvent.KEYCODE_COPY to "Copy",
- KeyEvent.KEYCODE_PASTE to "Paste",
- ),
- )
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
- yieldAll(
- listOf(
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP to "System Nav Up",
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN to "System Nav Down",
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT to "System Nav Left",
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT to "System Nav Right",
- ),
- )
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- yield(KeyEvent.KEYCODE_ALL_APPS to "All Apps")
- }
- }.toMap()
-
- private val KEYCODES: Set
- get() = setOf(
- KeyEvent.KEYCODE_SOFT_LEFT,
- KeyEvent.KEYCODE_SOFT_RIGHT,
- KeyEvent.KEYCODE_HOME,
- KeyEvent.KEYCODE_BACK,
- KeyEvent.KEYCODE_CALL,
- KeyEvent.KEYCODE_ENDCALL,
- KeyEvent.KEYCODE_0,
- KeyEvent.KEYCODE_1,
- KeyEvent.KEYCODE_2,
- KeyEvent.KEYCODE_3,
- KeyEvent.KEYCODE_4,
- KeyEvent.KEYCODE_5,
- KeyEvent.KEYCODE_6,
- KeyEvent.KEYCODE_7,
- KeyEvent.KEYCODE_8,
- KeyEvent.KEYCODE_9,
- KeyEvent.KEYCODE_STAR,
- KeyEvent.KEYCODE_POUND,
- KeyEvent.KEYCODE_DPAD_UP,
- KeyEvent.KEYCODE_DPAD_DOWN,
- KeyEvent.KEYCODE_DPAD_LEFT,
- KeyEvent.KEYCODE_DPAD_RIGHT,
- KeyEvent.KEYCODE_DPAD_CENTER,
- KeyEvent.KEYCODE_VOLUME_UP,
- KeyEvent.KEYCODE_VOLUME_DOWN,
- KeyEvent.KEYCODE_POWER,
- KeyEvent.KEYCODE_CAMERA,
- KeyEvent.KEYCODE_CLEAR,
- KeyEvent.KEYCODE_A,
- KeyEvent.KEYCODE_B,
- KeyEvent.KEYCODE_C,
- KeyEvent.KEYCODE_D,
- KeyEvent.KEYCODE_E,
- KeyEvent.KEYCODE_F,
- KeyEvent.KEYCODE_G,
- KeyEvent.KEYCODE_H,
- KeyEvent.KEYCODE_I,
- KeyEvent.KEYCODE_J,
- KeyEvent.KEYCODE_K,
- KeyEvent.KEYCODE_L,
- KeyEvent.KEYCODE_M,
- KeyEvent.KEYCODE_N,
- KeyEvent.KEYCODE_O,
- KeyEvent.KEYCODE_P,
- KeyEvent.KEYCODE_Q,
- KeyEvent.KEYCODE_R,
- KeyEvent.KEYCODE_S,
- KeyEvent.KEYCODE_T,
- KeyEvent.KEYCODE_U,
- KeyEvent.KEYCODE_V,
- KeyEvent.KEYCODE_W,
- KeyEvent.KEYCODE_X,
- KeyEvent.KEYCODE_Y,
- KeyEvent.KEYCODE_Z,
- KeyEvent.KEYCODE_COMMA,
- KeyEvent.KEYCODE_PERIOD,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_ALT_RIGHT,
- KeyEvent.KEYCODE_SHIFT_LEFT,
- KeyEvent.KEYCODE_SHIFT_RIGHT,
- KeyEvent.KEYCODE_TAB,
- KeyEvent.KEYCODE_SPACE,
- KeyEvent.KEYCODE_SYM,
- KeyEvent.KEYCODE_EXPLORER,
- KeyEvent.KEYCODE_ENVELOPE,
- KeyEvent.KEYCODE_ENTER,
- KeyEvent.KEYCODE_FORWARD_DEL,
- KeyEvent.KEYCODE_DEL,
- KeyEvent.KEYCODE_GRAVE,
- KeyEvent.KEYCODE_MINUS,
- KeyEvent.KEYCODE_EQUALS,
- KeyEvent.KEYCODE_LEFT_BRACKET,
- KeyEvent.KEYCODE_RIGHT_BRACKET,
- KeyEvent.KEYCODE_BACKSLASH,
- KeyEvent.KEYCODE_SEMICOLON,
- KeyEvent.KEYCODE_APOSTROPHE,
- KeyEvent.KEYCODE_SLASH,
- KeyEvent.KEYCODE_AT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_HEADSETHOOK,
- KeyEvent.KEYCODE_FOCUS,
- KeyEvent.KEYCODE_PLUS,
- KeyEvent.KEYCODE_MENU,
- KeyEvent.KEYCODE_NOTIFICATION,
- KeyEvent.KEYCODE_SEARCH,
- KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
- KeyEvent.KEYCODE_MEDIA_STOP,
- KeyEvent.KEYCODE_MEDIA_NEXT,
- KeyEvent.KEYCODE_MEDIA_PREVIOUS,
- KeyEvent.KEYCODE_MEDIA_REWIND,
- KeyEvent.KEYCODE_MEDIA_FAST_FORWARD,
- KeyEvent.KEYCODE_PAGE_UP,
- KeyEvent.KEYCODE_PAGE_DOWN,
- KeyEvent.KEYCODE_PICTSYMBOLS,
- KeyEvent.KEYCODE_SWITCH_CHARSET,
- KeyEvent.KEYCODE_BUTTON_A,
- KeyEvent.KEYCODE_BUTTON_B,
- KeyEvent.KEYCODE_BUTTON_C,
- KeyEvent.KEYCODE_BUTTON_X,
- KeyEvent.KEYCODE_BUTTON_Y,
- KeyEvent.KEYCODE_BUTTON_Z,
- KeyEvent.KEYCODE_BUTTON_L1,
- KeyEvent.KEYCODE_BUTTON_R1,
- KeyEvent.KEYCODE_BUTTON_L2,
- KeyEvent.KEYCODE_BUTTON_R2,
- KeyEvent.KEYCODE_BUTTON_THUMBL,
- KeyEvent.KEYCODE_BUTTON_THUMBR,
- KeyEvent.KEYCODE_BUTTON_START,
- KeyEvent.KEYCODE_BUTTON_SELECT,
- KeyEvent.KEYCODE_BUTTON_MODE,
- KeyEvent.KEYCODE_ESCAPE,
- KeyEvent.KEYCODE_DEL,
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_CTRL_RIGHT,
- KeyEvent.KEYCODE_CAPS_LOCK,
- KeyEvent.KEYCODE_SCROLL_LOCK,
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_META_RIGHT,
- KeyEvent.KEYCODE_FUNCTION,
- KeyEvent.KEYCODE_SYSRQ,
- KeyEvent.KEYCODE_BREAK,
- KeyEvent.KEYCODE_MOVE_HOME,
- KeyEvent.KEYCODE_MOVE_END,
- KeyEvent.KEYCODE_INSERT,
- KeyEvent.KEYCODE_FORWARD,
- KeyEvent.KEYCODE_MEDIA_PLAY,
- KeyEvent.KEYCODE_MEDIA_PAUSE,
- KeyEvent.KEYCODE_MEDIA_CLOSE,
- KeyEvent.KEYCODE_MEDIA_EJECT,
- KeyEvent.KEYCODE_MEDIA_RECORD,
- KeyEvent.KEYCODE_F1,
- KeyEvent.KEYCODE_F2,
- KeyEvent.KEYCODE_F3,
- KeyEvent.KEYCODE_F4,
- KeyEvent.KEYCODE_F5,
- KeyEvent.KEYCODE_F6,
- KeyEvent.KEYCODE_F7,
- KeyEvent.KEYCODE_F8,
- KeyEvent.KEYCODE_F9,
- KeyEvent.KEYCODE_F10,
- KeyEvent.KEYCODE_F11,
- KeyEvent.KEYCODE_F12,
- KeyEvent.KEYCODE_NUM,
- KeyEvent.KEYCODE_NUM_LOCK,
- KeyEvent.KEYCODE_NUMPAD_0,
- KeyEvent.KEYCODE_NUMPAD_1,
- KeyEvent.KEYCODE_NUMPAD_2,
- KeyEvent.KEYCODE_NUMPAD_3,
- KeyEvent.KEYCODE_NUMPAD_4,
- KeyEvent.KEYCODE_NUMPAD_5,
- KeyEvent.KEYCODE_NUMPAD_6,
- KeyEvent.KEYCODE_NUMPAD_7,
- KeyEvent.KEYCODE_NUMPAD_8,
- KeyEvent.KEYCODE_NUMPAD_9,
- KeyEvent.KEYCODE_NUMPAD_DIVIDE,
- KeyEvent.KEYCODE_NUMPAD_MULTIPLY,
- KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
- KeyEvent.KEYCODE_NUMPAD_ADD,
- KeyEvent.KEYCODE_NUMPAD_DOT,
- KeyEvent.KEYCODE_NUMPAD_COMMA,
- KeyEvent.KEYCODE_NUMPAD_ENTER,
- KeyEvent.KEYCODE_NUMPAD_EQUALS,
- KeyEvent.KEYCODE_NUMPAD_LEFT_PAREN,
- KeyEvent.KEYCODE_NUMPAD_RIGHT_PAREN,
- KeyEvent.KEYCODE_MUTE,
- KeyEvent.KEYCODE_VOLUME_MUTE,
- KeyEvent.KEYCODE_INFO,
- KeyEvent.KEYCODE_CHANNEL_UP,
- KeyEvent.KEYCODE_CHANNEL_DOWN,
- KeyEvent.KEYCODE_ZOOM_IN,
- KeyEvent.KEYCODE_ZOOM_OUT,
- KeyEvent.KEYCODE_TV,
- KeyEvent.KEYCODE_WINDOW,
- KeyEvent.KEYCODE_GUIDE,
- KeyEvent.KEYCODE_DVR,
- KeyEvent.KEYCODE_BOOKMARK,
- KeyEvent.KEYCODE_CAPTIONS,
- KeyEvent.KEYCODE_SETTINGS,
- KeyEvent.KEYCODE_TV_POWER,
- KeyEvent.KEYCODE_TV_INPUT,
- KeyEvent.KEYCODE_STB_POWER,
- KeyEvent.KEYCODE_STB_INPUT,
- KeyEvent.KEYCODE_AVR_POWER,
- KeyEvent.KEYCODE_AVR_INPUT,
- KeyEvent.KEYCODE_PROG_RED,
- KeyEvent.KEYCODE_PROG_GREEN,
- KeyEvent.KEYCODE_PROG_YELLOW,
- KeyEvent.KEYCODE_PROG_BLUE,
- KeyEvent.KEYCODE_APP_SWITCH,
- KeyEvent.KEYCODE_BUTTON_1,
- KeyEvent.KEYCODE_BUTTON_2,
- KeyEvent.KEYCODE_BUTTON_3,
- KeyEvent.KEYCODE_BUTTON_4,
- KeyEvent.KEYCODE_BUTTON_5,
- KeyEvent.KEYCODE_BUTTON_6,
- KeyEvent.KEYCODE_BUTTON_7,
- KeyEvent.KEYCODE_BUTTON_8,
- KeyEvent.KEYCODE_BUTTON_9,
- KeyEvent.KEYCODE_BUTTON_10,
- KeyEvent.KEYCODE_BUTTON_11,
- KeyEvent.KEYCODE_BUTTON_12,
- KeyEvent.KEYCODE_BUTTON_13,
- KeyEvent.KEYCODE_BUTTON_14,
- KeyEvent.KEYCODE_BUTTON_15,
- KeyEvent.KEYCODE_BUTTON_16,
- KeyEvent.KEYCODE_LANGUAGE_SWITCH,
- KeyEvent.KEYCODE_MANNER_MODE,
- KeyEvent.KEYCODE_3D_MODE,
- KeyEvent.KEYCODE_CONTACTS,
- KeyEvent.KEYCODE_CALENDAR,
- KeyEvent.KEYCODE_MUSIC,
- KeyEvent.KEYCODE_CALCULATOR,
- KeyEvent.KEYCODE_ZENKAKU_HANKAKU,
- KeyEvent.KEYCODE_EISU,
- KeyEvent.KEYCODE_MUHENKAN,
- KeyEvent.KEYCODE_HENKAN,
- KeyEvent.KEYCODE_KATAKANA_HIRAGANA,
- KeyEvent.KEYCODE_YEN,
- KeyEvent.KEYCODE_RO,
- KeyEvent.KEYCODE_KANA,
- KeyEvent.KEYCODE_ASSIST,
- KeyEvent.KEYCODE_POWER,
- KeyEvent.KEYCODE_BRIGHTNESS_DOWN,
- KeyEvent.KEYCODE_BRIGHTNESS_UP,
- KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK,
- KeyEvent.KEYCODE_PAIRING,
- KeyEvent.KEYCODE_MEDIA_TOP_MENU,
- KeyEvent.KEYCODE_11,
- KeyEvent.KEYCODE_12,
- KeyEvent.KEYCODE_LAST_CHANNEL,
- KeyEvent.KEYCODE_TV_DATA_SERVICE,
- KeyEvent.KEYCODE_VOICE_ASSIST,
- KeyEvent.KEYCODE_TV_RADIO_SERVICE,
- KeyEvent.KEYCODE_TV_TELETEXT,
- KeyEvent.KEYCODE_TV_NUMBER_ENTRY,
- KeyEvent.KEYCODE_TV_TERRESTRIAL_ANALOG,
- KeyEvent.KEYCODE_TV_TERRESTRIAL_DIGITAL,
- KeyEvent.KEYCODE_TV_SATELLITE,
- KeyEvent.KEYCODE_TV_SATELLITE_BS,
- KeyEvent.KEYCODE_TV_SATELLITE_CS,
- KeyEvent.KEYCODE_TV_SATELLITE_SERVICE,
- KeyEvent.KEYCODE_TV_NETWORK,
- KeyEvent.KEYCODE_TV_ANTENNA_CABLE,
- KeyEvent.KEYCODE_TV_INPUT_HDMI_1,
- KeyEvent.KEYCODE_TV_INPUT_HDMI_2,
- KeyEvent.KEYCODE_TV_INPUT_HDMI_3,
- KeyEvent.KEYCODE_TV_INPUT_HDMI_4,
- KeyEvent.KEYCODE_TV_INPUT_COMPOSITE_1,
- KeyEvent.KEYCODE_TV_INPUT_COMPOSITE_2,
- KeyEvent.KEYCODE_TV_INPUT_COMPONENT_1,
- KeyEvent.KEYCODE_TV_INPUT_COMPONENT_2,
- KeyEvent.KEYCODE_TV_INPUT_VGA_1,
- KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION,
- KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP,
- KeyEvent.KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN,
- KeyEvent.KEYCODE_TV_ZOOM_MODE,
- KeyEvent.KEYCODE_TV_CONTENTS_MENU,
- KeyEvent.KEYCODE_TV_MEDIA_CONTEXT_MENU,
- KeyEvent.KEYCODE_TV_TIMER_PROGRAMMING,
- KeyEvent.KEYCODE_HELP,
- )
-
- private val KEYCODES_API_23: Set
- get() = setOf(
- KeyEvent.KEYCODE_NAVIGATE_PREVIOUS,
- KeyEvent.KEYCODE_NAVIGATE_NEXT,
- KeyEvent.KEYCODE_NAVIGATE_IN,
- KeyEvent.KEYCODE_NAVIGATE_OUT,
- KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD,
- KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD,
- KeyEvent.KEYCODE_MEDIA_STEP_FORWARD,
- KeyEvent.KEYCODE_MEDIA_STEP_BACKWARD,
- )
-
- private val KEYCODES_API_24: Set
- get() = setOf(
- KeyEvent.KEYCODE_STEM_PRIMARY,
- KeyEvent.KEYCODE_STEM_1,
- KeyEvent.KEYCODE_STEM_2,
- KeyEvent.KEYCODE_STEM_3,
- KeyEvent.KEYCODE_DPAD_UP_LEFT,
- KeyEvent.KEYCODE_DPAD_DOWN_LEFT,
- KeyEvent.KEYCODE_DPAD_UP_RIGHT,
- KeyEvent.KEYCODE_DPAD_DOWN_RIGHT,
- KeyEvent.KEYCODE_SOFT_SLEEP,
- KeyEvent.KEYCODE_CUT,
- KeyEvent.KEYCODE_COPY,
- KeyEvent.KEYCODE_PASTE,
- )
-
- private val KEYCODES_API_25: Set
- get() = setOf(
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
- KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT,
- )
-
- private val KEYCODES_API_28: Set
- get() = setOf(
- KeyEvent.KEYCODE_ALL_APPS,
- )
-
- /**
- * These are key code maps for the getevent command. These names aren't the same as the
- * KeyEvent key codes in the Android SDK so these have to be manually whitelisted
- * as people need.
- */
- val GET_EVENT_LABEL_TO_KEYCODE: List> = listOf(
- "KEY_VOLUMEDOWN" to KeyEvent.KEYCODE_VOLUME_DOWN,
- "KEY_VOLUMEUP" to KeyEvent.KEYCODE_VOLUME_UP,
- "KEY_MEDIA" to KeyEvent.KEYCODE_HEADSETHOOK,
- "KEY_HEADSETHOOK" to KeyEvent.KEYCODE_HEADSETHOOK,
- "KEY_CAMERA_FOCUS" to KeyEvent.KEYCODE_FOCUS,
- "02fe" to KeyEvent.KEYCODE_CAMERA,
- "00fa" to KeyEvent.KEYCODE_CAMERA,
-
- // This kernel key event code seems to be the Bixby button
- // but different ROMs have different key maps and so
- // it is reported as different Android key codes.
- "02bf" to KeyEvent.KEYCODE_MENU,
- "02bf" to KeyEvent.KEYCODE_ASSIST,
-
- "KEY_SEARCH" to KeyEvent.KEYCODE_SEARCH,
- )
-
- fun canDetectKeyWhenScreenOff(keyCode: Int): Boolean = GET_EVENT_LABEL_TO_KEYCODE.any { it.second == keyCode }
-
- val MODIFIER_KEYCODES: Set
- get() = setOf(
- KeyEvent.KEYCODE_SHIFT_LEFT,
- KeyEvent.KEYCODE_SHIFT_RIGHT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_ALT_RIGHT,
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_CTRL_RIGHT,
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_META_RIGHT,
- KeyEvent.KEYCODE_SYM,
- KeyEvent.KEYCODE_NUM,
- KeyEvent.KEYCODE_FUNCTION,
- )
-
- /**
- * Used for keyCode to scanCode fallback to go past possible keyCode values
- */
- val KEYCODE_TO_SCANCODE_OFFSET: Int = 1000
-
- /**
- * Create a text representation of a key event. E.g if the control key was pressed,
- * "Ctrl" will be returned
- */
- fun keyCodeToString(keyCode: Int): String = NON_CHARACTER_KEY_LABELS[keyCode].let {
- if (keyCode >= KEYCODE_TO_SCANCODE_OFFSET || keyCode < 0) {
- "scancode $keyCode"
- } else {
- it ?: "unknown keycode $keyCode"
- }
- }
-
- fun isModifierKey(keyCode: Int): Boolean = keyCode in MODIFIER_KEYCODES
-
- fun isGamepadKeyCode(keyCode: Int): Boolean {
- when (keyCode) {
- KeyEvent.KEYCODE_BUTTON_A,
- KeyEvent.KEYCODE_BUTTON_B,
- KeyEvent.KEYCODE_BUTTON_C,
- KeyEvent.KEYCODE_BUTTON_X,
- KeyEvent.KEYCODE_BUTTON_Y,
- KeyEvent.KEYCODE_BUTTON_Z,
- KeyEvent.KEYCODE_BUTTON_L1,
- KeyEvent.KEYCODE_BUTTON_R1,
- KeyEvent.KEYCODE_BUTTON_L2,
- KeyEvent.KEYCODE_BUTTON_R2,
- KeyEvent.KEYCODE_BUTTON_THUMBL,
- KeyEvent.KEYCODE_BUTTON_THUMBR,
- KeyEvent.KEYCODE_BUTTON_START,
- KeyEvent.KEYCODE_BUTTON_SELECT,
- KeyEvent.KEYCODE_BUTTON_MODE,
- KeyEvent.KEYCODE_BUTTON_1,
- KeyEvent.KEYCODE_BUTTON_2,
- KeyEvent.KEYCODE_BUTTON_3,
- KeyEvent.KEYCODE_BUTTON_4,
- KeyEvent.KEYCODE_BUTTON_5,
- KeyEvent.KEYCODE_BUTTON_6,
- KeyEvent.KEYCODE_BUTTON_7,
- KeyEvent.KEYCODE_BUTTON_8,
- KeyEvent.KEYCODE_BUTTON_9,
- KeyEvent.KEYCODE_BUTTON_10,
- KeyEvent.KEYCODE_BUTTON_11,
- KeyEvent.KEYCODE_BUTTON_12,
- KeyEvent.KEYCODE_BUTTON_13,
- KeyEvent.KEYCODE_BUTTON_14,
- KeyEvent.KEYCODE_BUTTON_15,
- KeyEvent.KEYCODE_BUTTON_16,
- -> return true
-
- else -> return false
- }
- }
-
- /**
- * Get all the valid key codes which work on the Android version for the device.
- */
- fun getKeyCodes(): List {
- val keyCodes = KEYCODES.toMutableList()
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- keyCodes.addAll(KEYCODES_API_23)
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- keyCodes.addAll(KEYCODES_API_24)
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
- keyCodes.addAll(KEYCODES_API_25)
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- keyCodes.addAll(KEYCODES_API_28)
- }
-
- return keyCodes
- }
-
- fun modifierKeycodeToMetaState(modifier: Int) = when (modifier) {
- KeyEvent.KEYCODE_ALT_LEFT -> KeyEvent.META_ALT_LEFT_ON.withFlag(KeyEvent.META_ALT_ON)
- KeyEvent.KEYCODE_ALT_RIGHT -> KeyEvent.META_ALT_RIGHT_ON.withFlag(KeyEvent.META_ALT_ON)
-
- KeyEvent.KEYCODE_SHIFT_LEFT -> KeyEvent.META_SHIFT_LEFT_ON.withFlag(KeyEvent.META_SHIFT_ON)
- KeyEvent.KEYCODE_SHIFT_RIGHT -> KeyEvent.META_SHIFT_RIGHT_ON.withFlag(KeyEvent.META_SHIFT_ON)
-
- KeyEvent.KEYCODE_SYM -> KeyEvent.META_SYM_ON
-
- KeyEvent.KEYCODE_FUNCTION -> KeyEvent.META_FUNCTION_ON
-
- KeyEvent.KEYCODE_CTRL_LEFT -> KeyEvent.META_CTRL_LEFT_ON.withFlag(KeyEvent.META_CTRL_ON)
- KeyEvent.KEYCODE_CTRL_RIGHT -> KeyEvent.META_CTRL_RIGHT_ON.withFlag(KeyEvent.META_CTRL_ON)
-
- KeyEvent.KEYCODE_META_LEFT -> KeyEvent.META_META_LEFT_ON.withFlag(KeyEvent.META_META_ON)
- KeyEvent.KEYCODE_META_RIGHT -> KeyEvent.META_META_RIGHT_ON.withFlag(KeyEvent.META_META_ON)
-
- KeyEvent.KEYCODE_CAPS_LOCK -> KeyEvent.META_CAPS_LOCK_ON
- KeyEvent.KEYCODE_NUM_LOCK -> KeyEvent.META_NUM_LOCK_ON
- KeyEvent.KEYCODE_SCROLL_LOCK -> KeyEvent.META_SCROLL_LOCK_ON
-
- else -> throw Exception("can't convert modifier $modifier to meta state")
- }
-
- val MODIFIER_LABELS = mapOf(
- KeyEvent.META_CTRL_ON to R.string.meta_state_ctrl,
- KeyEvent.META_CTRL_LEFT_ON to R.string.meta_state_ctrl_left,
- KeyEvent.META_CTRL_RIGHT_ON to R.string.meta_state_ctrl_right,
-
- KeyEvent.META_ALT_ON to R.string.meta_state_alt,
- KeyEvent.META_ALT_LEFT_ON to R.string.meta_state_alt_left,
- KeyEvent.META_ALT_RIGHT_ON to R.string.meta_state_alt_right,
-
- KeyEvent.META_SHIFT_ON to R.string.meta_state_shift,
- KeyEvent.META_SHIFT_LEFT_ON to R.string.meta_state_shift_left,
- KeyEvent.META_SHIFT_RIGHT_ON to R.string.meta_state_shift_right,
-
- KeyEvent.META_META_ON to R.string.meta_state_meta,
- KeyEvent.META_META_LEFT_ON to R.string.meta_state_meta_left,
- KeyEvent.META_META_RIGHT_ON to R.string.meta_state_meta_right,
-
- KeyEvent.META_SYM_ON to R.string.meta_state_sym,
- KeyEvent.META_CAPS_LOCK_ON to R.string.meta_state_caps_lock,
- KeyEvent.META_NUM_LOCK_ON to R.string.meta_state_num_lock,
- KeyEvent.META_SCROLL_LOCK_ON to R.string.meta_state_scroll_lock,
- KeyEvent.META_FUNCTION_ON to R.string.meta_state_function,
- )
-
- fun isDpadKeyCode(code: Int): Boolean {
- return code == KeyEvent.KEYCODE_DPAD_LEFT ||
- code == KeyEvent.KEYCODE_DPAD_RIGHT ||
- code == KeyEvent.KEYCODE_DPAD_UP ||
- code == KeyEvent.KEYCODE_DPAD_DOWN ||
- code == KeyEvent.KEYCODE_DPAD_UP_LEFT ||
- code == KeyEvent.KEYCODE_DPAD_UP_RIGHT ||
- code == KeyEvent.KEYCODE_DPAD_DOWN_LEFT ||
- code == KeyEvent.KEYCODE_DPAD_DOWN_RIGHT
- }
-
- fun isDpadDevice(event: InputEvent): Boolean = // Check that input comes from a device with directional pads.
- event.source and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD
-
- fun isGamepadButton(keyCode: Int): Boolean {
- return when (keyCode) {
- KeyEvent.KEYCODE_BUTTON_A,
- KeyEvent.KEYCODE_BUTTON_B,
- KeyEvent.KEYCODE_BUTTON_C,
- KeyEvent.KEYCODE_BUTTON_X,
- KeyEvent.KEYCODE_BUTTON_Y,
- KeyEvent.KEYCODE_BUTTON_Z,
- KeyEvent.KEYCODE_BUTTON_L1,
- KeyEvent.KEYCODE_BUTTON_R1,
- KeyEvent.KEYCODE_BUTTON_L2,
- KeyEvent.KEYCODE_BUTTON_R2,
- KeyEvent.KEYCODE_BUTTON_THUMBL,
- KeyEvent.KEYCODE_BUTTON_THUMBR,
- KeyEvent.KEYCODE_BUTTON_START,
- KeyEvent.KEYCODE_BUTTON_SELECT,
- KeyEvent.KEYCODE_BUTTON_MODE,
- KeyEvent.KEYCODE_BUTTON_1,
- KeyEvent.KEYCODE_BUTTON_2,
- KeyEvent.KEYCODE_BUTTON_3,
- KeyEvent.KEYCODE_BUTTON_4,
- KeyEvent.KEYCODE_BUTTON_5,
- KeyEvent.KEYCODE_BUTTON_6,
- KeyEvent.KEYCODE_BUTTON_7,
- KeyEvent.KEYCODE_BUTTON_8,
- KeyEvent.KEYCODE_BUTTON_9,
- KeyEvent.KEYCODE_BUTTON_10,
- KeyEvent.KEYCODE_BUTTON_11,
- KeyEvent.KEYCODE_BUTTON_12,
- KeyEvent.KEYCODE_BUTTON_13,
- KeyEvent.KEYCODE_BUTTON_14,
- KeyEvent.KEYCODE_BUTTON_15,
- KeyEvent.KEYCODE_BUTTON_16,
- -> true
-
- else -> false
- }
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeHelper.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeHelper.kt
deleted file mode 100644
index e62df2ad11..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/KeyMapperImeHelper.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-package io.github.sds100.keymapper.system.inputmethod
-
-import io.github.sds100.keymapper.Constants
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.Success
-import io.github.sds100.keymapper.util.firstBlocking
-import io.github.sds100.keymapper.util.onSuccess
-import io.github.sds100.keymapper.util.suspendThen
-import io.github.sds100.keymapper.util.then
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/**
- * Created by sds100 on 16/03/2021.
- */
-
-class KeyMapperImeHelper(private val imeAdapter: InputMethodAdapter) {
- companion object {
- const val KEY_MAPPER_GUI_IME_PACKAGE =
- "io.github.sds100.keymapper.inputmethod.latin"
-
- private const val KEY_MAPPER_LEANBACK_IME_PACKAGE =
- "io.github.sds100.keymapper.inputmethod.leanback"
-
- private const val KEY_MAPPER_HACKERS_KEYBOARD_PACKAGE =
- "io.github.sds100.keymapper.inputmethod.hackers"
-
- val KEY_MAPPER_IME_PACKAGE_LIST = arrayOf(
- Constants.PACKAGE_NAME,
- KEY_MAPPER_GUI_IME_PACKAGE,
- KEY_MAPPER_LEANBACK_IME_PACKAGE,
- KEY_MAPPER_HACKERS_KEYBOARD_PACKAGE,
- )
-
- const val MIN_SUPPORTED_GUI_KEYBOARD_VERSION_CODE: Int = 20
- }
-
- val isCompatibleImeEnabledFlow: Flow =
- imeAdapter.inputMethods
- .map { containsCompatibleIme(it) }
-
- suspend fun enableCompatibleInputMethods() {
- KEY_MAPPER_IME_PACKAGE_LIST.forEach { packageName ->
- imeAdapter.getInfoByPackageName(packageName).onSuccess {
- imeAdapter.enableIme(it.id)
- }
- }
- }
-
- suspend fun chooseCompatibleInputMethod(): Result = getLastUsedCompatibleImeId().suspendThen {
- imeAdapter.chooseImeWithoutUserInput(it)
- }
-
- suspend fun chooseLastUsedIncompatibleInputMethod(): Result = getLastUsedIncompatibleImeId().then {
- imeAdapter.chooseImeWithoutUserInput(it)
- }
-
- suspend fun toggleCompatibleInputMethod(): Result = if (isCompatibleImeChosen()) {
- chooseLastUsedIncompatibleInputMethod()
- } else {
- chooseCompatibleInputMethod()
- }
-
- fun isCompatibleImeChosen(): Boolean = imeAdapter.chosenIme.value?.packageName in KEY_MAPPER_IME_PACKAGE_LIST
-
- fun isCompatibleImeEnabled(): Boolean = imeAdapter.inputMethods
- .map { containsCompatibleIme(it) }
- .firstBlocking()
-
- private fun containsCompatibleIme(imeList: List): Boolean = imeList
- .filter { it.isEnabled }
- .any { it.packageName in KEY_MAPPER_IME_PACKAGE_LIST }
-
- private fun getLastUsedCompatibleImeId(): Result {
- for (ime in imeAdapter.inputMethodHistory.firstBlocking()) {
- if (ime.packageName in KEY_MAPPER_IME_PACKAGE_LIST && ime.isEnabled) {
- return Success(ime.id)
- }
- }
-
- imeAdapter.getInfoByPackageName(KEY_MAPPER_GUI_IME_PACKAGE).onSuccess { ime ->
- if (ime.isEnabled) {
- return Success(ime.id)
- }
- }
-
- return imeAdapter.getInfoByPackageName(Constants.PACKAGE_NAME).then { ime ->
- if (ime.isEnabled) {
- Success(ime.id)
- } else {
- Error.NoCompatibleImeEnabled
- }
- }
- }
-
- private fun getLastUsedIncompatibleImeId(): Result {
- for (ime in imeAdapter.inputMethodHistory.firstBlocking()) {
- if (ime.packageName !in KEY_MAPPER_IME_PACKAGE_LIST) {
- return Success(ime.id)
- }
- }
-
- return Error.NoIncompatibleKeyboardsInstalled
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ShowHideInputMethodUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ShowHideInputMethodUseCase.kt
deleted file mode 100644
index feb12d0e2a..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ShowHideInputMethodUseCase.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package io.github.sds100.keymapper.system.inputmethod
-
-import io.github.sds100.keymapper.system.accessibility.ServiceAdapter
-import io.github.sds100.keymapper.util.ServiceEvent
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.runBlocking
-
-/**
- * Created by sds100 on 16/04/2021.
- */
-
-class ShowHideInputMethodUseCaseImpl(
- private val serviceAdapter: ServiceAdapter,
-) : ShowHideInputMethodUseCase {
- override val onHiddenChange: Flow = serviceAdapter.eventReceiver.mapNotNull {
- when (it) {
- ServiceEvent.OnHideKeyboardEvent -> true
- ServiceEvent.OnShowKeyboardEvent -> false
- else -> null
- }
- }
-
- override fun show() {
- runBlocking { serviceAdapter.send(ServiceEvent.ShowKeyboard) }
- }
-
- override fun hide() {
- runBlocking { serviceAdapter.send(ServiceEvent.HideKeyboard) }
- }
-}
-
-interface ShowHideInputMethodUseCase {
- val onHiddenChange: Flow
- fun show()
- fun hide()
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ToggleCompatibleImeUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ToggleCompatibleImeUseCase.kt
deleted file mode 100644
index 86e559f13e..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ToggleCompatibleImeUseCase.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package io.github.sds100.keymapper.system.inputmethod
-
-import io.github.sds100.keymapper.util.Result
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Created by sds100 on 16/04/2021.
- */
-
-class ToggleCompatibleImeUseCaseImpl(
- private val inputMethodAdapter: InputMethodAdapter,
-) : ToggleCompatibleImeUseCase {
- private val keyMapperImeHelper = KeyMapperImeHelper(inputMethodAdapter)
-
- override val sufficientPermissions: Flow =
- inputMethodAdapter.isUserInputRequiredToChangeIme
-
- override suspend fun toggle(): Result =
- keyMapperImeHelper.toggleCompatibleInputMethod()
-}
-
-interface ToggleCompatibleImeUseCase {
- val sufficientPermissions: Flow
-
- suspend fun toggle(): Result
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/nfc/NfcAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/nfc/NfcAdapter.kt
deleted file mode 100644
index 68e0286fff..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/nfc/NfcAdapter.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package io.github.sds100.keymapper.system.nfc
-
-import io.github.sds100.keymapper.util.Result
-
-/**
- * Created by sds100 on 24/04/2021.
- */
-interface NfcAdapter {
- fun isEnabled(): Boolean
- fun enable(): Result<*>
- fun disable(): Result<*>
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/popup/AndroidToastAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/popup/AndroidToastAdapter.kt
deleted file mode 100644
index 633fdb5424..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/popup/AndroidToastAdapter.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package io.github.sds100.keymapper.system.popup
-
-import android.content.Context
-import splitties.toast.toast
-
-/**
- * Created by sds100 on 17/04/2021.
- */
-class AndroidToastAdapter(context: Context) : PopupMessageAdapter {
- private val ctx: Context = context.applicationContext
-
- override fun showPopupMessage(message: String) {
- ctx.toast(message)
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/popup/PopupMessageAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/popup/PopupMessageAdapter.kt
deleted file mode 100644
index 6f0fc56638..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/popup/PopupMessageAdapter.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package io.github.sds100.keymapper.system.popup
-
-/**
- * Created by sds100 on 17/04/2021.
- */
-interface PopupMessageAdapter {
- fun showPopupMessage(message: String)
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/shell/ShellAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/shell/ShellAdapter.kt
deleted file mode 100644
index 553bc744bd..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/shell/ShellAdapter.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.github.sds100.keymapper.system.shell
-
-import io.github.sds100.keymapper.util.Result
-
-/**
- * Created by sds100 on 21/04/2021.
- */
-
-interface ShellAdapter {
- fun execute(command: String): Result<*>
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/url/AndroidOpenUrlAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/url/AndroidOpenUrlAdapter.kt
deleted file mode 100644
index 6109d32e98..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/url/AndroidOpenUrlAdapter.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package io.github.sds100.keymapper.system.url
-
-import android.content.Context
-import io.github.sds100.keymapper.util.Result
-
-/**
- * Created by sds100 on 24/04/2021.
- */
-class AndroidOpenUrlAdapter(context: Context) : OpenUrlAdapter {
-
- private val ctx = context.applicationContext
-
- override fun openUrl(url: String): Result<*> = UrlUtils.openUrl(ctx, url)
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/url/OpenUrlAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/url/OpenUrlAdapter.kt
deleted file mode 100644
index a6c01f428d..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/system/url/OpenUrlAdapter.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.github.sds100.keymapper.system.url
-
-import io.github.sds100.keymapper.util.Result
-
-/**
- * Created by sds100 on 24/04/2021.
- */
-interface OpenUrlAdapter {
- fun openUrl(url: String): Result<*>
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt
new file mode 100644
index 0000000000..cc85192142
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt
@@ -0,0 +1,155 @@
+package io.github.sds100.keymapper.trigger
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+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.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.SheetState
+import androidx.compose.material3.SheetValue.Expanded
+import androidx.compose.material3.Text
+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.LocalDensity
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.compose.KeyMapperTheme
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AdvancedTriggersBottomSheet(
+ modifier: Modifier = Modifier,
+ onDismissRequest: () -> Unit,
+ viewModel: ConfigTriggerViewModel,
+ sheetState: SheetState,
+) {
+ AdvancedTriggersBottomSheet(
+ modifier,
+ onDismissRequest,
+ sheetState,
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun AdvancedTriggersBottomSheet(
+ modifier: Modifier = Modifier,
+ onDismissRequest: () -> Unit,
+ sheetState: SheetState,
+) {
+ val scope = rememberCoroutineScope()
+
+ ModalBottomSheet(
+ modifier = modifier,
+ onDismissRequest = onDismissRequest,
+ sheetState = sheetState,
+ // Hide drag handle because other bottom sheets don't have it
+ dragHandle = {},
+ ) {
+ Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ text = stringResource(R.string.advanced_triggers_sheet_title),
+ style = MaterialTheme.typography.headlineMedium,
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ text = stringResource(R.string.advanced_triggers_sheet_text),
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ text = stringResource(R.string.purchasing_not_implemented_bottom_sheet_text),
+ fontStyle = FontStyle.Italic,
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ val uriHandler = LocalUriHandler.current
+ val googlePlayUrl = stringResource(R.string.url_play_store_listing)
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ OutlinedButton(
+ modifier = Modifier,
+ onClick = {
+ scope.launch {
+ sheetState.hide()
+ onDismissRequest()
+ }
+ },
+ ) {
+ Text(stringResource(R.string.neg_cancel))
+ }
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+ FilledTonalButton(
+ modifier = Modifier,
+ onClick = {
+ scope.launch {
+ uriHandler.openUri(googlePlayUrl)
+ }
+ },
+ ) {
+ Text(stringResource(R.string.purchasing_download_key_mapper_from_google_play))
+ }
+ }
+
+ Spacer(Modifier.height(16.dp))
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Composable
+private fun Preview() {
+ KeyMapperTheme {
+ val sheetState = SheetState(
+ skipPartiallyExpanded = true,
+ density = LocalDensity.current,
+ initialValue = Expanded,
+ )
+
+ AdvancedTriggersBottomSheet(
+ onDismissRequest = {},
+ sheetState = sheetState,
+ )
+ }
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/ConfigTriggerViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/ConfigTriggerViewModel.kt
new file mode 100644
index 0000000000..1654023097
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/trigger/ConfigTriggerViewModel.kt
@@ -0,0 +1,48 @@
+package io.github.sds100.keymapper.trigger
+
+import io.github.sds100.keymapper.base.keymaps.ConfigKeyMapUseCase
+import io.github.sds100.keymapper.base.keymaps.CreateKeyMapShortcutUseCase
+import io.github.sds100.keymapper.base.keymaps.DisplayKeyMapUseCase
+import io.github.sds100.keymapper.base.keymaps.FingerprintGesturesSupportedUseCase
+import io.github.sds100.keymapper.base.onboarding.OnboardingUseCase
+import io.github.sds100.keymapper.base.purchasing.PurchasingManager
+import io.github.sds100.keymapper.base.trigger.BaseConfigTriggerViewModel
+import io.github.sds100.keymapper.base.trigger.RecordTriggerUseCase
+import io.github.sds100.keymapper.base.trigger.SetupGuiKeyboardUseCase
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import kotlinx.coroutines.CoroutineScope
+import javax.inject.Inject
+
+class ConfigTriggerViewModel @Inject constructor(
+ private val coroutineScope: CoroutineScope,
+ private val onboarding: OnboardingUseCase,
+ private val config: ConfigKeyMapUseCase,
+ private val recordTrigger: RecordTriggerUseCase,
+ private val createKeyMapShortcut: CreateKeyMapShortcutUseCase,
+ private val displayKeyMap: DisplayKeyMapUseCase,
+ private val purchasingManager: PurchasingManager,
+ private val setupGuiKeyboard: SetupGuiKeyboardUseCase,
+ private val fingerprintGesturesSupported: FingerprintGesturesSupportedUseCase,
+ resourceProvider: ResourceProvider,
+ navigationProvider: NavigationProvider,
+ dialogProvider: DialogProvider,
+) : BaseConfigTriggerViewModel(
+ coroutineScope = coroutineScope,
+ onboarding = onboarding,
+ config = config,
+ recordTrigger = recordTrigger,
+ createKeyMapShortcut = createKeyMapShortcut,
+ displayKeyMap = displayKeyMap,
+ purchasingManager = purchasingManager,
+ setupGuiKeyboard = setupGuiKeyboard,
+ fingerprintGesturesSupported = fingerprintGesturesSupported,
+ resourceProvider = resourceProvider,
+ navigationProvider = navigationProvider,
+ dialogProvider = dialogProvider,
+) {
+ override fun onEditFloatingButtonClick() {}
+
+ override fun onEditFloatingLayoutClick() {}
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt
new file mode 100644
index 0000000000..6c1da6cf77
--- /dev/null
+++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt
@@ -0,0 +1,27 @@
+package io.github.sds100.keymapper.trigger
+
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import io.github.sds100.keymapper.base.trigger.BaseTriggerScreen
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TriggerScreen(modifier: Modifier = Modifier, viewModel: ConfigTriggerViewModel) {
+ val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+
+ if (viewModel.showAdvancedTriggersBottomSheet) {
+ AdvancedTriggersBottomSheet(
+ modifier = Modifier.systemBarsPadding(),
+ viewModel = viewModel,
+ onDismissRequest = {
+ viewModel.showAdvancedTriggersBottomSheet = false
+ },
+ sheetState = sheetState,
+ )
+ }
+
+ BaseTriggerScreen(modifier, viewModel)
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ErrorUtils.kt b/app/src/main/java/io/github/sds100/keymapper/util/ErrorUtils.kt
deleted file mode 100644
index 8a047e25b0..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ErrorUtils.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-package io.github.sds100.keymapper.util
-
-import android.content.pm.PackageManager
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.purchasing.ProductId
-import io.github.sds100.keymapper.system.BuildUtils
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-
-/**
- * Created by sds100 on 29/02/2020.
- */
-
-fun Error.getFullMessage(resourceProvider: ResourceProvider): String = when (this) {
- is Error.PermissionDenied ->
- Error.PermissionDenied.getMessageForPermission(
- resourceProvider,
- permission,
- )
-
- is Error.AppNotFound -> resourceProvider.getString(
- R.string.error_app_isnt_installed,
- packageName,
- )
-
- is Error.AppDisabled -> resourceProvider.getString(
- R.string.error_app_is_disabled_package_name,
- this.packageName,
- )
-
- is Error.NoCompatibleImeEnabled -> resourceProvider.getString(R.string.error_key_mapper_ime_service_disabled)
- is Error.NoCompatibleImeChosen -> resourceProvider.getString(R.string.error_ime_must_be_chosen)
- is Error.SystemFeatureNotSupported -> when (this.feature) {
- PackageManager.FEATURE_NFC -> resourceProvider.getString(R.string.error_system_feature_nfc_unsupported)
- PackageManager.FEATURE_CAMERA -> resourceProvider.getString(R.string.error_system_feature_camera_unsupported)
- PackageManager.FEATURE_FINGERPRINT -> resourceProvider.getString(R.string.error_system_feature_fingerprint_unsupported)
- PackageManager.FEATURE_WIFI -> resourceProvider.getString(R.string.error_system_feature_wifi_unsupported)
- PackageManager.FEATURE_BLUETOOTH -> resourceProvider.getString(R.string.error_system_feature_bluetooth_unsupported)
- PackageManager.FEATURE_DEVICE_ADMIN -> resourceProvider.getString(R.string.error_system_feature_device_admin_unsupported)
- PackageManager.FEATURE_CAMERA_FLASH -> resourceProvider.getString(R.string.error_system_feature_camera_flash_unsupported)
- PackageManager.FEATURE_TELEPHONY -> resourceProvider.getString(R.string.error_system_feature_telephony_unsupported)
- else -> throw Exception("Don't know how to get error message for this system feature ${this.feature}")
- }
-
- is Error.ExtraNotFound -> resourceProvider.getString(R.string.error_extra_not_found, extraId)
- is Error.SdkVersionTooLow -> resourceProvider.getString(
- R.string.error_sdk_version_too_low,
- BuildUtils.getSdkVersionName(minSdk),
- )
-
- is Error.SdkVersionTooHigh -> resourceProvider.getString(
- R.string.error_sdk_version_too_high,
- BuildUtils.getSdkVersionName(maxSdk),
- )
-
- is Error.InputMethodNotFound -> resourceProvider.getString(
- R.string.error_ime_not_found,
- imeLabel,
- )
-
- is Error.FrontFlashNotFound -> resourceProvider.getString(R.string.error_front_flash_not_found)
- is Error.BackFlashNotFound -> resourceProvider.getString(R.string.error_back_flash_not_found)
- is Error.DeviceNotFound -> resourceProvider.getString(R.string.error_device_not_found)
- is Error.Exception -> exception.toString()
- is Error.EmptyJson -> resourceProvider.getString(R.string.error_empty_json)
- is Error.InvalidNumber -> resourceProvider.getString(R.string.error_invalid_number)
- is Error.NumberTooSmall -> resourceProvider.getString(R.string.error_number_too_small, min)
- is Error.NumberTooBig -> resourceProvider.getString(R.string.error_number_too_big, max)
- is Error.EmptyText -> resourceProvider.getString(R.string.error_cant_be_empty)
- Error.BackupVersionTooNew -> resourceProvider.getString(R.string.error_backup_version_too_new)
- Error.NoIncompatibleKeyboardsInstalled -> resourceProvider.getString(R.string.error_no_incompatible_input_methods_installed)
- Error.NoMediaSessions -> resourceProvider.getString(R.string.error_no_media_sessions)
- Error.NoVoiceAssistant -> resourceProvider.getString(R.string.error_voice_assistant_not_found)
- Error.AccessibilityServiceDisabled -> resourceProvider.getString(R.string.error_accessibility_service_disabled)
- Error.LauncherShortcutsNotSupported -> resourceProvider.getString(R.string.error_launcher_shortcuts_not_supported)
- Error.AccessibilityServiceCrashed -> resourceProvider.getString(R.string.error_accessibility_service_crashed)
- Error.CantFindImeSettings -> resourceProvider.getString(R.string.error_cant_find_ime_settings)
- Error.CantShowImePickerInBackground -> resourceProvider.getString(R.string.error_cant_show_ime_picker_in_background)
- Error.FailedToFindAccessibilityNode -> resourceProvider.getString(R.string.error_failed_to_find_accessibility_node)
- is Error.FailedToPerformAccessibilityGlobalAction -> resourceProvider.getString(
- R.string.error_failed_to_perform_accessibility_global_action,
- action,
- )
-
- Error.FailedToDispatchGesture -> resourceProvider.getString(R.string.error_failed_to_dispatch_gesture)
- Error.AppShortcutCantBeOpened -> resourceProvider.getString(R.string.error_opening_app_shortcut)
- Error.InsufficientPermissionsToOpenAppShortcut -> resourceProvider.getString(R.string.error_keymapper_doesnt_have_permission_app_shortcut)
- Error.NoAppToPhoneCall -> resourceProvider.getString(R.string.error_no_app_to_phone_call)
-
- Error.CameraInUse -> resourceProvider.getString(R.string.error_camera_in_use)
- Error.CameraError -> resourceProvider.getString(R.string.error_camera_error)
- Error.CameraDisabled -> resourceProvider.getString(R.string.error_camera_disabled)
- Error.CameraDisconnected -> resourceProvider.getString(R.string.error_camera_disconnected)
- Error.MaxCamerasInUse -> resourceProvider.getString(R.string.error_max_cameras_in_use)
- Error.CameraVariableFlashlightStrengthUnsupported -> resourceProvider.getString(R.string.error_variable_flashlight_strength_unsupported)
-
- is Error.FailedToModifySystemSetting -> resourceProvider.getString(
- R.string.error_failed_to_modify_system_setting,
- setting,
- )
-
- is Error.ImeDisabled -> resourceProvider.getString(R.string.error_ime_disabled, this.ime.label)
- Error.FailedToChangeIme -> resourceProvider.getString(R.string.error_failed_to_change_ime)
- Error.NoCameraApp -> resourceProvider.getString(R.string.error_no_camera_app)
- Error.NoDeviceAssistant -> resourceProvider.getString(R.string.error_no_device_assistant)
- Error.NoSettingsApp -> resourceProvider.getString(R.string.error_no_settings_app)
- Error.NoAppToOpenUrl -> resourceProvider.getString(R.string.error_no_app_to_open_url)
-
- Error.CantFindSoundFile -> resourceProvider.getString(R.string.error_cant_find_sound_file)
- is Error.CorruptJsonFile -> reason
-
- is Error.CannotCreateFileInTarget -> resourceProvider.getString(
- R.string.error_file_access_denied,
- uri,
- )
-
- Error.FileOperationCancelled -> resourceProvider.getString(R.string.error_file_operation_cancelled)
- is Error.NoSpaceLeftOnTarget -> resourceProvider.getString(
- R.string.error_no_space_left_at_target,
- uri,
- )
-
- is Error.NotADirectory -> resourceProvider.getString(R.string.error_not_a_directory, uri)
- is Error.NotAFile -> resourceProvider.getString(R.string.error_not_a_file, uri)
- is Error.SourceFileNotFound -> resourceProvider.getString(
- R.string.error_source_file_not_found,
- uri,
- )
-
- Error.StoragePermissionDenied -> resourceProvider.getString(R.string.error_storage_permission_denied)
- Error.TargetDirectoryMatchesSourceDirectory -> resourceProvider.getString(R.string.error_matching_source_and_target_paths)
- is Error.TargetDirectoryNotFound -> resourceProvider.getString(
- R.string.error_directory_not_found,
- uri,
- )
-
- is Error.TargetFileNotFound -> resourceProvider.getString(
- R.string.error_target_file_not_found,
- uri,
- )
-
- Error.UnknownIOError -> resourceProvider.getString(R.string.error_io_error)
- Error.ShizukuNotStarted -> resourceProvider.getString(R.string.error_shizuku_not_started)
- Error.NoFileName -> resourceProvider.getString(R.string.error_no_file_name)
- Error.CantDetectKeyEventsInPhoneCall -> resourceProvider.getString(R.string.trigger_error_cant_detect_in_phone_call_explanation)
- Error.GestureStrokeCountTooHigh -> resourceProvider.getString(R.string.trigger_error_gesture_stroke_count_too_high)
- Error.GestureDurationTooHigh -> resourceProvider.getString(R.string.trigger_error_gesture_duration_too_high)
-
- Error.PurchasingError.Cancelled -> resourceProvider.getString(R.string.purchasing_error_cancelled)
- Error.PurchasingError.NetworkError -> resourceProvider.getString(R.string.purchasing_error_network)
- Error.PurchasingError.ProductNotFound -> resourceProvider.getString(R.string.purchasing_error_product_not_found)
- Error.PurchasingError.StoreProblem -> resourceProvider.getString(R.string.purchasing_error_store_problem)
- Error.PurchasingError.PaymentPending -> resourceProvider.getString(R.string.purchasing_error_payment_pending)
- Error.PurchasingError.PurchaseInvalid -> resourceProvider.getString(R.string.purchasing_error_purchase_invalid)
- is Error.PurchasingError.Unexpected -> this.message
-
- is Error.ProductNotPurchased -> when (this.product) {
- ProductId.ASSISTANT_TRIGGER -> resourceProvider.getString(R.string.purchasing_error_assistant_not_purchased_home_screen)
- ProductId.FLOATING_BUTTONS -> resourceProvider.getString(R.string.purchasing_error_floating_buttons_not_purchased_home_screen)
- }
-
- Error.PurchasingNotImplemented -> resourceProvider.getString(R.string.purchasing_error_not_implemented)
- Error.DpadTriggerImeNotSelected -> resourceProvider.getString(R.string.trigger_error_dpad_ime_not_selected)
- Error.InvalidBackup -> resourceProvider.getString(R.string.error_invalid_backup)
- Error.MalformedUrl -> resourceProvider.getString(R.string.error_malformed_url)
- Error.UiElementNotFound -> resourceProvider.getString(R.string.error_ui_element_not_found)
-}
-
-val Error.isFixable: Boolean
- get() = when (this) {
- is Error.AppNotFound,
- is Error.AppDisabled,
- Error.NoCompatibleImeEnabled,
- Error.NoCompatibleImeChosen,
- is Error.ImeDisabled,
- Error.AccessibilityServiceDisabled,
- Error.AccessibilityServiceCrashed,
- is Error.PermissionDenied,
- is Error.ShizukuNotStarted,
- is Error.CantDetectKeyEventsInPhoneCall,
-
- -> true
-
- else -> false
- }
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
deleted file mode 100644
index 6bb6786364..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt
+++ /dev/null
@@ -1,260 +0,0 @@
-package io.github.sds100.keymapper.util
-
-import android.content.Context
-import androidx.lifecycle.lifecycleScope
-import io.github.sds100.keymapper.KeyMapperApp
-import io.github.sds100.keymapper.ServiceLocator
-import io.github.sds100.keymapper.UseCases
-import io.github.sds100.keymapper.actions.ChooseActionViewModel
-import io.github.sds100.keymapper.actions.TestActionUseCaseImpl
-import io.github.sds100.keymapper.actions.keyevent.ChooseKeyCodeViewModel
-import io.github.sds100.keymapper.actions.keyevent.ConfigKeyEventActionViewModel
-import io.github.sds100.keymapper.actions.keyevent.ConfigKeyEventUseCaseImpl
-import io.github.sds100.keymapper.actions.pinchscreen.PinchPickDisplayCoordinateViewModel
-import io.github.sds100.keymapper.actions.sound.ChooseSoundFileUseCaseImpl
-import io.github.sds100.keymapper.actions.sound.ChooseSoundFileViewModel
-import io.github.sds100.keymapper.actions.swipescreen.SwipePickDisplayCoordinateViewModel
-import io.github.sds100.keymapper.actions.tapscreen.PickDisplayCoordinateViewModel
-import io.github.sds100.keymapper.actions.uielement.InteractUiElementViewModel
-import io.github.sds100.keymapper.api.KeyEventRelayServiceWrapper
-import io.github.sds100.keymapper.backup.BackupRestoreMappingsUseCaseImpl
-import io.github.sds100.keymapper.constraints.ChooseConstraintViewModel
-import io.github.sds100.keymapper.constraints.CreateConstraintUseCaseImpl
-import io.github.sds100.keymapper.home.HomeViewModel
-import io.github.sds100.keymapper.home.ShowHomeScreenAlertsUseCaseImpl
-import io.github.sds100.keymapper.logging.DisplayLogUseCaseImpl
-import io.github.sds100.keymapper.logging.LogViewModel
-import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCaseImpl
-import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapViewModel
-import io.github.sds100.keymapper.mappings.keymaps.CreateKeyMapShortcutViewModel
-import io.github.sds100.keymapper.mappings.keymaps.ListKeyMapsUseCaseImpl
-import io.github.sds100.keymapper.mappings.keymaps.trigger.SetupGuiKeyboardUseCaseImpl
-import io.github.sds100.keymapper.settings.ConfigSettingsUseCaseImpl
-import io.github.sds100.keymapper.settings.SettingsViewModel
-import io.github.sds100.keymapper.sorting.SortKeyMapsUseCaseImpl
-import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceController
-import io.github.sds100.keymapper.system.accessibility.MyAccessibilityService
-import io.github.sds100.keymapper.system.apps.ChooseActivityViewModel
-import io.github.sds100.keymapper.system.apps.ChooseAppShortcutViewModel
-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
-
-/**
- * Created by sds100 on 26/01/2020.
- */
-
-object Inject {
-
- fun chooseActionViewModel(ctx: Context): ChooseActionViewModel.Factory = ChooseActionViewModel.Factory(
- UseCases.createAction(ctx),
- ServiceLocator.resourceProvider(ctx),
- )
-
- fun chooseAppViewModel(context: Context): ChooseAppViewModel.Factory = ChooseAppViewModel.Factory(
- UseCases.displayPackages(context),
- )
-
- fun chooseActivityViewModel(context: Context): ChooseActivityViewModel.Factory = ChooseActivityViewModel.Factory(
- UseCases.displayPackages(context),
- )
-
- fun chooseAppShortcutViewModel(context: Context): ChooseAppShortcutViewModel.Factory = ChooseAppShortcutViewModel.Factory(
- DisplayAppShortcutsUseCaseImpl(
- ServiceLocator.appShortcutAdapter(context),
- ),
- ServiceLocator.resourceProvider(context),
- )
-
- fun chooseConstraintListViewModel(ctx: Context): ChooseConstraintViewModel.Factory = ChooseConstraintViewModel.Factory(
- CreateConstraintUseCaseImpl(
- ServiceLocator.networkAdapter(ctx),
- ServiceLocator.inputMethodAdapter(ctx),
- ServiceLocator.settingsRepository(ctx),
- ServiceLocator.cameraAdapter(ctx),
- ),
- ServiceLocator.resourceProvider(ctx),
- )
-
- fun configKeyEventViewModel(
- context: Context,
- ): ConfigKeyEventActionViewModel.Factory {
- val useCase = ConfigKeyEventUseCaseImpl(
- preferenceRepository = ServiceLocator.settingsRepository(context),
- devicesAdapter = ServiceLocator.devicesAdapter(context),
- )
- return ConfigKeyEventActionViewModel.Factory(
- useCase,
- ServiceLocator.resourceProvider(context),
- )
- }
-
- fun chooseKeyCodeViewModel(): ChooseKeyCodeViewModel.Factory = ChooseKeyCodeViewModel.Factory()
-
- fun configIntentViewModel(ctx: Context): ConfigIntentViewModel.Factory = ConfigIntentViewModel.Factory(ServiceLocator.resourceProvider(ctx))
-
- fun soundFileActionTypeViewModel(ctx: Context): ChooseSoundFileViewModel.Factory = ChooseSoundFileViewModel.Factory(
- ServiceLocator.resourceProvider(ctx),
- ChooseSoundFileUseCaseImpl(
- ServiceLocator.fileAdapter(ctx),
- ServiceLocator.soundsManager(ctx),
- ),
- )
-
- fun tapCoordinateActionTypeViewModel(context: Context): PickDisplayCoordinateViewModel.Factory = PickDisplayCoordinateViewModel.Factory(
- ServiceLocator.resourceProvider(context),
- )
-
- fun swipeCoordinateActionTypeViewModel(context: Context): SwipePickDisplayCoordinateViewModel.Factory = SwipePickDisplayCoordinateViewModel.Factory(
- ServiceLocator.resourceProvider(context),
- )
-
- fun pinchCoordinateActionTypeViewModel(context: Context): PinchPickDisplayCoordinateViewModel.Factory = PinchPickDisplayCoordinateViewModel.Factory(
- ServiceLocator.resourceProvider(context),
- )
-
- fun configKeyMapViewModel(
- ctx: Context,
- ): ConfigKeyMapViewModel.Factory = ConfigKeyMapViewModel.Factory(
- UseCases.configKeyMap(ctx),
- TestActionUseCaseImpl(ServiceLocator.accessibilityServiceAdapter(ctx)),
- UseCases.onboarding(ctx),
- (ctx.applicationContext as KeyMapperApp).recordTriggerController,
- UseCases.createKeymapShortcut(ctx),
- UseCases.displayKeyMap(ctx),
- UseCases.createAction(ctx),
- ServiceLocator.resourceProvider(ctx),
- ServiceLocator.purchasingManager(ctx),
- SetupGuiKeyboardUseCaseImpl(
- ServiceLocator.inputMethodAdapter(ctx),
- ServiceLocator.packageManagerAdapter(ctx),
- ),
- FingerprintGesturesSupportedUseCaseImpl(ServiceLocator.settingsRepository(ctx)),
- )
-
- fun createActionShortcutViewModel(
- ctx: Context,
- ): CreateKeyMapShortcutViewModel.Factory = CreateKeyMapShortcutViewModel.Factory(
- UseCases.configKeyMap(ctx),
- ListKeyMapsUseCaseImpl(
- ServiceLocator.roomKeyMapRepository(ctx),
- ServiceLocator.groupRepository(ctx),
- ServiceLocator.floatingButtonRepository(ctx),
- ServiceLocator.fileAdapter(ctx),
- ServiceLocator.backupManager(ctx),
- ServiceLocator.resourceProvider(ctx),
- UseCases.displayKeyMap(ctx),
- ),
- UseCases.createKeymapShortcut(ctx),
- ServiceLocator.resourceProvider(ctx),
- )
-
- fun homeViewModel(ctx: Context): HomeViewModel.Factory = HomeViewModel.Factory(
- ListKeyMapsUseCaseImpl(
- ServiceLocator.roomKeyMapRepository(ctx),
- ServiceLocator.groupRepository(ctx),
- ServiceLocator.floatingButtonRepository(ctx),
- ServiceLocator.fileAdapter(ctx),
- ServiceLocator.backupManager(ctx),
- ServiceLocator.resourceProvider(ctx),
- UseCases.displayKeyMap(ctx),
- ),
- UseCases.pauseKeyMaps(ctx),
- BackupRestoreMappingsUseCaseImpl(
- ServiceLocator.fileAdapter(ctx),
- ServiceLocator.backupManager(ctx),
- ),
- ShowHomeScreenAlertsUseCaseImpl(
- ServiceLocator.settingsRepository(ctx),
- ServiceLocator.permissionAdapter(ctx),
- ServiceLocator.accessibilityServiceAdapter(ctx),
- UseCases.pauseKeyMaps(ctx),
- ),
- UseCases.onboarding(ctx),
- ServiceLocator.resourceProvider(ctx),
- SetupGuiKeyboardUseCaseImpl(
- ServiceLocator.inputMethodAdapter(ctx),
- ServiceLocator.packageManagerAdapter(ctx),
- ),
- SortKeyMapsUseCaseImpl(
- ServiceLocator.settingsRepository(ctx),
- UseCases.displayKeyMap(ctx),
- ),
- UseCases.listFloatingLayouts(ctx),
- ShowInputMethodPickerUseCaseImpl(ServiceLocator.inputMethodAdapter(ctx)),
- )
-
- fun settingsViewModel(context: Context): SettingsViewModel.Factory = SettingsViewModel.Factory(
- ConfigSettingsUseCaseImpl(
- ServiceLocator.settingsRepository(context),
- ServiceLocator.permissionAdapter(context),
- ServiceLocator.inputMethodAdapter(context),
- ServiceLocator.soundsManager(context),
- ServiceLocator.suAdapter(context),
- ServiceLocator.packageManagerAdapter(context),
- ServiceLocator.shizukuAdapter(context),
- ServiceLocator.devicesAdapter(context),
- ),
- ServiceLocator.resourceProvider(context),
- )
-
- fun accessibilityServiceController(
- service: MyAccessibilityService,
- keyEventRelayService: KeyEventRelayServiceWrapper,
- ): AccessibilityServiceController = AccessibilityServiceController(
- coroutineScope = service.lifecycleScope,
- accessibilityService = service,
- inputEvents = ServiceLocator.accessibilityServiceAdapter(service).eventsToService,
- outputEvents = ServiceLocator.accessibilityServiceAdapter(service).eventReceiver,
- detectConstraintsUseCase = UseCases.detectConstraints(service),
- performActionsUseCase = UseCases.performActions(
- ctx = service,
- service = service,
- keyEventRelayService = keyEventRelayService,
- ),
- detectKeyMapsUseCase = UseCases.detectKeyMaps(
- ctx = service,
- service = service,
- keyEventRelayService = keyEventRelayService,
- ),
- fingerprintGesturesSupportedUseCase = UseCases.fingerprintGesturesSupported(service),
- pauseKeyMapsUseCase = UseCases.pauseKeyMaps(service),
- devicesAdapter = ServiceLocator.devicesAdapter(service),
- suAdapter = ServiceLocator.suAdapter(service),
- rerouteKeyEventsUseCase = UseCases.rerouteKeyEvents(
- ctx = service,
- keyEventRelayService = keyEventRelayService,
- ),
- inputMethodAdapter = ServiceLocator.inputMethodAdapter(service),
- settingsRepository = ServiceLocator.settingsRepository(service),
- nodeRepository = ServiceLocator.accessibilityNodeRepository(service),
- )
-
- fun chooseBluetoothDeviceViewModel(ctx: Context): ChooseBluetoothDeviceViewModel.Factory = ChooseBluetoothDeviceViewModel.Factory(
- ChooseBluetoothDeviceUseCaseImpl(
- ServiceLocator.devicesAdapter(ctx),
- ServiceLocator.permissionAdapter(ctx),
- ),
- ServiceLocator.resourceProvider(ctx),
- )
-
- fun logViewModel(ctx: Context): LogViewModel.Factory = LogViewModel.Factory(
- DisplayLogUseCaseImpl(
- ServiceLocator.logRepository(ctx),
- ServiceLocator.resourceProvider(ctx),
- ServiceLocator.clipboardAdapter(ctx),
- ServiceLocator.fileAdapter(ctx),
- ),
- ServiceLocator.resourceProvider(ctx),
- )
-
- fun interactUiElementViewModel(
- ctx: Context,
- ): InteractUiElementViewModel.Factory = InteractUiElementViewModel.Factory(
- (ctx.applicationContext as KeyMapperApp).interactUiElementController,
- resourceProvider = ServiceLocator.resourceProvider(ctx),
- )
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/InputEventType.kt b/app/src/main/java/io/github/sds100/keymapper/util/InputEventType.kt
deleted file mode 100644
index fb6769cffd..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/InputEventType.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.github.sds100.keymapper.util
-
-/**
- * Created by sds100 on 28/07/20.
- */
-enum class InputEventType {
- DOWN_UP,
- DOWN,
- UP,
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/LoggingUtils.kt b/app/src/main/java/io/github/sds100/keymapper/util/LoggingUtils.kt
deleted file mode 100644
index b5d0f02ef0..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/LoggingUtils.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.github.sds100.keymapper.util
-
-import timber.log.Timber
-import kotlin.system.measureTimeMillis
-
-/**
- * Created by sds100 on 19/01/21.
- */
-
-inline fun logMeasureTimeMillis(block: () -> T): T {
- val result: T
- val time = measureTimeMillis {
- result = block.invoke()
- }
-
- Timber.e("$time ms")
-
- return result
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/Result.kt b/app/src/main/java/io/github/sds100/keymapper/util/Result.kt
deleted file mode 100644
index 8fe8251769..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/Result.kt
+++ /dev/null
@@ -1,225 +0,0 @@
-package io.github.sds100.keymapper.util
-
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.purchasing.ProductId
-import io.github.sds100.keymapper.system.inputmethod.ImeInfo
-import io.github.sds100.keymapper.system.permissions.Permission
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-
-/**
- * Created by sds100 on 26/02/2020.
- */
-
-/**
- * Inspired from @antonyharfield great example!
- */
-
-sealed class Result
-
-data class Success(val value: T) : Result()
-
-sealed class Error : Result() {
- data class Exception(val exception: java.lang.Exception) : Error()
- data class SystemFeatureNotSupported(val feature: String) : Error()
- data class ExtraNotFound(val extraId: String) : Error()
- data class SdkVersionTooLow(val minSdk: Int) : Error()
- data class SdkVersionTooHigh(val maxSdk: Int) : Error()
- data class InputMethodNotFound(val imeLabel: String) : Error()
- data object NoVoiceAssistant : Error()
- data object NoDeviceAssistant : Error()
- data object NoCameraApp : Error()
- data object NoSettingsApp : Error()
- data object FrontFlashNotFound : Error()
- data object BackFlashNotFound : Error()
- data class ImeDisabled(val ime: ImeInfo) : Error()
- data class DeviceNotFound(val descriptor: String) : Error()
- data object InvalidNumber : Error()
- data class NumberTooBig(val max: Int) : Error()
- data class NumberTooSmall(val min: Int) : Error()
- data object EmptyText : Error()
- data object NoIncompatibleKeyboardsInstalled : Error()
- data object NoMediaSessions : Error()
- data object BackupVersionTooNew : Error()
- data object LauncherShortcutsNotSupported : Error()
-
- data class AppNotFound(val packageName: String) : Error()
- data class AppDisabled(val packageName: String) : Error()
- data object AppShortcutCantBeOpened : Error()
- data object InsufficientPermissionsToOpenAppShortcut : Error()
- data object NoCompatibleImeEnabled : Error()
- data object NoCompatibleImeChosen : Error()
-
- data object AccessibilityServiceDisabled : Error()
- data object AccessibilityServiceCrashed : Error()
-
- data object CantShowImePickerInBackground : Error()
- data object CantFindImeSettings : Error()
- data object GestureStrokeCountTooHigh : Error()
- data object GestureDurationTooHigh : Error()
-
- data class PermissionDenied(val permission: Permission) : Error() {
- companion object {
-
- fun getMessageForPermission(
- resourceProvider: ResourceProvider,
- permission: Permission,
- ): String {
- val resId = when (permission) {
- Permission.WRITE_SETTINGS -> R.string.error_action_requires_write_settings_permission
- Permission.CAMERA -> R.string.error_action_requires_camera_permission
- Permission.DEVICE_ADMIN -> R.string.error_need_to_enable_device_admin
- Permission.READ_PHONE_STATE -> R.string.error_action_requires_read_phone_state_permission
- Permission.ACCESS_NOTIFICATION_POLICY -> R.string.error_action_notification_policy_permission
- Permission.WRITE_SECURE_SETTINGS -> R.string.error_need_write_secure_settings_permission
- Permission.NOTIFICATION_LISTENER -> R.string.error_denied_notification_listener_service_permission
- Permission.CALL_PHONE -> R.string.error_denied_call_phone_permission
- Permission.ROOT -> R.string.error_requires_root
- Permission.IGNORE_BATTERY_OPTIMISATION -> R.string.error_battery_optimisation_enabled
- Permission.SHIZUKU -> R.string.error_shizuku_permission_denied
- Permission.ACCESS_FINE_LOCATION -> R.string.error_access_fine_location_permission_denied
- Permission.ANSWER_PHONE_CALL -> R.string.error_answer_end_phone_call_permission_denied
- Permission.FIND_NEARBY_DEVICES -> R.string.error_find_nearby_devices_permission_denied
- Permission.POST_NOTIFICATIONS -> R.string.error_notifications_permission_denied
- }
-
- return resourceProvider.getString(resId)
- }
- }
- }
-
- data object FailedToFindAccessibilityNode : Error()
- data class FailedToPerformAccessibilityGlobalAction(val action: Int) : Error()
- data object FailedToDispatchGesture : Error()
-
- data object CameraInUse : Error()
- data object CameraDisconnected : Error()
- data object CameraDisabled : Error()
- data object MaxCamerasInUse : Error()
- data object CameraError : Error()
- data object CameraVariableFlashlightStrengthUnsupported : Error()
-
- data class FailedToModifySystemSetting(val setting: String) : Error()
- data object FailedToChangeIme : Error()
- data object NoAppToOpenUrl : Error()
- data object NoAppToPhoneCall : Error()
-
- data class NotAFile(val uri: String) : Error()
- data class NotADirectory(val uri: String) : Error()
- data object StoragePermissionDenied : Error()
- data class CannotCreateFileInTarget(val uri: String) : Error()
- data class SourceFileNotFound(val uri: String) : Error()
- data class TargetFileNotFound(val uri: String) : Error()
- data class TargetDirectoryNotFound(val uri: String) : Error()
- data object UnknownIOError : Error()
- data object FileOperationCancelled : Error()
- data object TargetDirectoryMatchesSourceDirectory : Error()
- data class NoSpaceLeftOnTarget(val uri: String) : Error()
- data object NoFileName : Error()
- data object InvalidBackup : Error()
-
- data object EmptyJson : Error()
- data object CantFindSoundFile : Error()
- data class CorruptJsonFile(val reason: String) : Error()
-
- data object ShizukuNotStarted : Error()
- data object CantDetectKeyEventsInPhoneCall : Error()
-
- // This is returned from the PurchasingManager on FOSS builds that don't
- // have the pro features implemented.
- data object PurchasingNotImplemented : Error()
-
- data class ProductNotPurchased(val product: ProductId) : Error()
-
- sealed class PurchasingError : Error() {
- data object ProductNotFound : PurchasingError()
- data object Cancelled : PurchasingError()
- data object StoreProblem : PurchasingError()
- data object NetworkError : PurchasingError()
- data object PaymentPending : PurchasingError()
- data object PurchaseInvalid : PurchasingError()
- data class Unexpected(val message: String) : PurchasingError()
- }
-
- /**
- * DPAD triggers require a Key Mapper keyboard to be selected.
- */
- data object DpadTriggerImeNotSelected : Error()
- data object MalformedUrl : Error()
-
- data object UiElementNotFound : Error()
-}
-
-inline fun Result.onSuccess(f: (T) -> Unit): Result {
- if (this is Success) {
- f(this.value)
- }
-
- return this
-}
-
-inline fun Result.onFailure(f: (error: Error) -> U): Result {
- if (this is Error) {
- f(this)
- }
-
- return this
-}
-
-inline infix fun Result.then(f: (T) -> Result) = when (this) {
- is Success -> f(this.value)
- is Error -> this
-}
-
-suspend infix fun Result.suspendThen(f: suspend (T) -> Result) = when (this) {
- is Success -> f(this.value)
- is Error -> this
-}
-
-inline infix fun Result.otherwise(f: (error: Error) -> Result) = when (this) {
- is Success -> this
- is Error -> f(this)
-}
-
-inline fun Result.resolve(
- onSuccess: (value: T) -> U,
- onFailure: (error: Error) -> U,
-) = when (this) {
- is Success -> onSuccess(this.value)
- is Error -> onFailure(this)
-}
-
-inline infix fun Result.valueIfFailure(f: (error: Error) -> T): T = when (this) {
- is Success -> this.value
- is Error -> f(this)
-}
-
-fun Result.errorOrNull(): Error? {
- when (this) {
- is Error -> return this
- else -> Unit
- }
-
- return null
-}
-
-fun Result.valueOrNull(): T? {
- when (this) {
- is Success -> return this.value
- else -> Unit
- }
-
- return null
-}
-
-val Result.isError: Boolean
- get() = this is Error
-
-val Result.isSuccess: Boolean
- get() = this is Success
-
-fun Result.handle(onSuccess: (value: T) -> U, onError: (error: Error) -> U): U = when (this) {
- is Success -> onSuccess(value)
- is Error -> onError(this)
-}
-
-fun T.success() = Success(this)
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt b/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt
deleted file mode 100644
index 4bacac2040..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package io.github.sds100.keymapper.util
-
-import android.os.Parcelable
-import io.github.sds100.keymapper.actions.ActionData
-import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyEventDetectionSource
-import io.github.sds100.keymapper.system.accessibility.RecordAccessibilityNodeState
-import io.github.sds100.keymapper.system.devices.InputDeviceInfo
-import kotlinx.parcelize.Parcelize
-import kotlinx.serialization.Serializable
-
-@Serializable
-sealed class ServiceEvent {
-
- @Serializable
- data class Ping(val key: String) : ServiceEvent()
-
- @Serializable
- data class Pong(val key: String) : ServiceEvent()
-
- @Parcelize
- @Serializable
- data class RecordedTriggerKey(
- val keyCode: Int,
- val device: InputDeviceInfo?,
- val detectionSource: KeyEventDetectionSource,
- ) : ServiceEvent(),
- Parcelable
-
- @Serializable
- object StartRecordingTrigger : ServiceEvent()
-
- @Serializable
- object StopRecordingTrigger : ServiceEvent()
-
- @Serializable
- data class OnIncrementRecordTriggerTimer(val timeLeft: Int) : ServiceEvent()
-
- @Serializable
- object OnStoppedRecordingTrigger : ServiceEvent()
-
- @Serializable
- object OnHideKeyboardEvent : ServiceEvent()
-
- @Serializable
- object OnShowKeyboardEvent : ServiceEvent()
-
- @Serializable
- object HideKeyboard : ServiceEvent()
-
- @Serializable
- object ShowKeyboard : ServiceEvent()
-
- @Serializable
- data class TestAction(val action: ActionData) : ServiceEvent()
-
- @Serializable
- data class ChangeIme(val imeId: String) : ServiceEvent()
-
- @Serializable
- object DisableService : ServiceEvent()
-
- @Serializable
- object DismissLastNotification : ServiceEvent()
-
- @Serializable
- object DismissAllNotifications : ServiceEvent()
-
- @Serializable
- data class OnInputFocusChange(val isFocussed: Boolean) : ServiceEvent()
-
- @Serializable
- data class TriggerKeyMap(val uid: String) : ServiceEvent()
-
- @Serializable
- data class EnableInputMethod(val imeId: String) : ServiceEvent()
-
- @Serializable
- data object StartRecordingNodes : ServiceEvent()
-
- @Serializable
- data object StopRecordingNodes : ServiceEvent()
-
- @Serializable
- data class OnRecordNodeStateChanged(val state: RecordAccessibilityNodeState) : ServiceEvent()
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/ISearchable.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/ISearchable.kt
deleted file mode 100644
index f74bcfac2b..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ui/ISearchable.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package io.github.sds100.keymapper.util.ui
-
-/**
- * Created by sds100 on 13/01/21.
- */
-interface ISearchable {
- fun getSearchableString(): String
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/ListItem.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/ListItem.kt
deleted file mode 100644
index 25ba907c59..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ui/ListItem.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package io.github.sds100.keymapper.util.ui
-
-interface ListItem {
- val id: String
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavResult.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavResult.kt
deleted file mode 100644
index f89f8efa7b..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavResult.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.github.sds100.keymapper.util.ui
-
-/**
- * Created by sds100 on 23/03/2021.
- */
-data class NavResult(val key: String, val result: Any?)
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigateEvent.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigateEvent.kt
deleted file mode 100644
index 0f3d9f354f..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigateEvent.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.github.sds100.keymapper.util.ui
-
-/**
- * Created by sds100 on 25/07/2021.
- */
-data class NavigateEvent(val key: String, val destination: NavDestination<*>)
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt
deleted file mode 100644
index 510eb75792..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt
+++ /dev/null
@@ -1,340 +0,0 @@
-package io.github.sds100.keymapper.util.ui
-
-import android.os.Bundle
-import androidx.core.os.bundleOf
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.clearFragmentResultListener
-import androidx.fragment.app.setFragmentResultListener
-import androidx.lifecycle.lifecycleScope
-import androidx.navigation.fragment.findNavController
-import io.github.sds100.keymapper.NavAppDirections
-import io.github.sds100.keymapper.actions.ActionData
-import io.github.sds100.keymapper.actions.ChooseActionFragment
-import io.github.sds100.keymapper.actions.keyevent.ChooseKeyCodeFragment
-import io.github.sds100.keymapper.actions.keyevent.ConfigKeyEventActionFragment
-import io.github.sds100.keymapper.actions.pinchscreen.PinchPickCoordinateResult
-import io.github.sds100.keymapper.actions.pinchscreen.PinchPickDisplayCoordinateFragment
-import io.github.sds100.keymapper.actions.sound.ChooseSoundFileFragment
-import io.github.sds100.keymapper.actions.swipescreen.SwipePickCoordinateResult
-import io.github.sds100.keymapper.actions.swipescreen.SwipePickDisplayCoordinateFragment
-import io.github.sds100.keymapper.actions.tapscreen.PickCoordinateResult
-import io.github.sds100.keymapper.actions.tapscreen.PickDisplayCoordinateFragment
-import io.github.sds100.keymapper.actions.uielement.InteractUiElementFragment
-import io.github.sds100.keymapper.constraints.ChooseConstraintFragment
-import io.github.sds100.keymapper.constraints.Constraint
-import io.github.sds100.keymapper.system.apps.ActivityInfo
-import io.github.sds100.keymapper.system.apps.ChooseActivityFragment
-import io.github.sds100.keymapper.system.apps.ChooseAppFragment
-import io.github.sds100.keymapper.system.apps.ChooseAppShortcutFragment
-import io.github.sds100.keymapper.system.apps.ChooseAppShortcutResult
-import io.github.sds100.keymapper.system.bluetooth.BluetoothDeviceInfo
-import io.github.sds100.keymapper.system.bluetooth.ChooseBluetoothDeviceFragment
-import io.github.sds100.keymapper.system.intents.ConfigIntentFragment
-import io.github.sds100.keymapper.system.intents.ConfigIntentResult
-import io.github.sds100.keymapper.ui.utils.getJsonSerializable
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.dropWhile
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.serialization.json.Json
-
-/**
- * Created by sds100 on 25/07/2021.
- */
-
-class NavigationViewModelImpl : NavigationViewModel {
- private val _onNavResult by lazy { MutableSharedFlow() }
- override val onNavResult by lazy { _onNavResult.asSharedFlow() }
-
- private val _navigate = MutableSharedFlow()
- override val navigate = _navigate.asSharedFlow()
-
- override suspend fun navigate(event: NavigateEvent) {
- // wait for the view to collect so navigating can happen
- _navigate.subscriptionCount.first { it > 0 }
-
- _navigate.emit(event)
- }
-
- override fun onNavResult(result: NavResult) {
- runBlocking { _onNavResult.emit(result) }
- }
-}
-
-interface NavigationViewModel {
- val navigate: SharedFlow
- val onNavResult: SharedFlow
-
- fun onNavResult(result: NavResult)
- suspend fun navigate(event: NavigateEvent)
-}
-
-suspend inline fun NavigationViewModel.navigate(
- key: String,
- destination: NavDestination,
-): R? {
- navigate(NavigateEvent(key, destination))
-
- /*
- This ensures only one job for a dialog is active at once by cancelling previous jobs when a new
- dialog is shown with the same key
- */
- return merge(
- navigate.dropWhile { it.key != key }.map { null },
- onNavResult.dropWhile { it.result !is R? && it.key != key }.map { it.result },
- ).first() as R?
-}
-
-/**
- * Must call in fragment's onCreate
- */
-fun NavigationViewModel.setupNavigation(fragment: Fragment) {
- val navigationSavedStateKey = "navigation:${this.javaClass.name}"
-
- val pendingResultsKeysExtra = "pending_results_keys"
- val pendingResultsDestinationsExtra = "pending_results_destinations"
-
- /**
- * Maps request keys to their destination.
- */
- val pendingResults = mutableMapOf()
-
- fragment.savedStateRegistry.registerSavedStateProvider(navigationSavedStateKey) {
- bundleOf(
- pendingResultsKeysExtra to pendingResults.keys.toTypedArray(),
- pendingResultsDestinationsExtra to pendingResults.values.toTypedArray(),
- )
- }
-
- fragment.savedStateRegistry.consumeRestoredStateForKey(navigationSavedStateKey)
- ?.let { bundle ->
- val oldPendingResultsKeys: Array =
- bundle.getStringArray(pendingResultsKeysExtra)!!
-
- val oldPendingResultsDestinations: Array =
- bundle.getStringArray(pendingResultsDestinationsExtra)!!
-
- oldPendingResultsKeys.forEachIndexed { i, requestKey ->
- val destination = oldPendingResultsDestinations[i]
-
- pendingResults[requestKey] = destination
-
- fragment.setFragmentResultListener(requestKey) { _, bundle ->
- sendNavResultFromBundle(requestKey, destination, bundle)
- }
- }
- }
-
- navigate.onEach { event ->
- val (requestKey, destination) = event
-
- pendingResults[requestKey] = destination.id
-
- fragment.clearFragmentResultListener(requestKey)
-
- fragment.setFragmentResultListener(requestKey) { _, bundle ->
- pendingResults.remove(event.key)
- sendNavResultFromBundle(event.key, event.destination.id, bundle)
- }
-
- val direction = when (destination) {
- is NavDestination.ChooseApp -> NavAppDirections.chooseApp(
- destination.allowHiddenApps,
- requestKey,
- )
-
- NavDestination.ChooseAppShortcut -> NavAppDirections.chooseAppShortcut(requestKey)
- NavDestination.ChooseKeyCode -> NavAppDirections.chooseKeyCode(requestKey)
- is NavDestination.ConfigKeyEventAction -> {
- val json = destination.action?.let {
- Json.encodeToString(it)
- }
-
- NavAppDirections.configKeyEvent(requestKey, json)
- }
-
- is NavDestination.PickCoordinate -> {
- val json = destination.result?.let {
- Json.encodeToString(it)
- }
-
- NavAppDirections.pickDisplayCoordinate(requestKey, json)
- }
-
- is NavDestination.PickSwipeCoordinate -> {
- val json = destination.result?.let {
- Json.encodeToString(it)
- }
-
- NavAppDirections.swipePickDisplayCoordinate(requestKey, json)
- }
-
- is NavDestination.PickPinchCoordinate -> {
- val json = destination.result?.let {
- Json.encodeToString(it)
- }
-
- NavAppDirections.pinchPickDisplayCoordinate(requestKey, json)
- }
-
- is NavDestination.ConfigIntent -> {
- val json = destination.result?.let {
- Json.encodeToString(it)
- }
-
- NavAppDirections.configIntent(requestKey, json)
- }
-
- is NavDestination.ChooseActivity -> NavAppDirections.chooseActivity(requestKey)
- is NavDestination.ChooseSound -> NavAppDirections.chooseSoundFile(requestKey)
- NavDestination.ChooseAction -> NavAppDirections.toChooseActionFragment(requestKey)
- is NavDestination.ChooseConstraint -> NavAppDirections.chooseConstraint(
- requestKey = requestKey,
- )
-
- is NavDestination.ChooseBluetoothDevice -> NavAppDirections.chooseBluetoothDevice(
- requestKey,
- )
-
- NavDestination.About -> NavAppDirections.actionGlobalAboutFragment()
- NavDestination.Settings -> NavAppDirections.toSettingsFragment()
-
- is NavDestination.ConfigKeyMap -> when (destination) {
- is NavDestination.ConfigKeyMap.New ->
- NavAppDirections.actionToConfigKeymap(
- groupUid = destination.groupUid,
- showAdvancedTriggers = destination.showAdvancedTriggers,
- )
-
- is NavDestination.ConfigKeyMap.Open ->
- NavAppDirections.actionToConfigKeymap(
- keyMapUid = destination.keyMapUid,
- showAdvancedTriggers = destination.showAdvancedTriggers,
- )
- }
-
- is NavDestination.ChooseFloatingLayout -> NavAppDirections.toChooseFloatingLayoutFragment()
- NavDestination.ShizukuSettings -> NavAppDirections.toShizukuSettingsFragment()
- is NavDestination.ConfigFloatingButton -> NavAppDirections.toConfigFloatingButton(
- destination.buttonUid,
- )
-
- is NavDestination.InteractUiElement -> NavAppDirections.interactUiElement(
- requestKey = requestKey,
- action = destination.action?.let { Json.encodeToString(destination.action) },
- )
- }
-
- fragment.findNavController().navigate(direction)
- }.launchIn(fragment.lifecycleScope)
-}
-
-fun NavigationViewModel.sendNavResultFromBundle(
- requestKey: String,
- destinationId: String,
- bundle: Bundle,
-) {
- when (destinationId) {
- NavDestination.ID_CHOOSE_APP -> {
- val packageName = bundle.getString(ChooseAppFragment.EXTRA_PACKAGE_NAME)
-
- onNavResult(NavResult(requestKey, packageName!!))
- }
-
- NavDestination.ID_CHOOSE_APP_SHORTCUT -> {
- val result = bundle.getJsonSerializable(
- ChooseAppShortcutFragment.EXTRA_RESULT,
- )
-
- onNavResult(NavResult(requestKey, result))
- }
-
- NavDestination.ID_KEY_CODE -> {
- val keyCode = bundle.getInt(ChooseKeyCodeFragment.EXTRA_KEYCODE)
-
- onNavResult(NavResult(requestKey, keyCode))
- }
-
- NavDestination.ID_KEY_EVENT -> {
- val json = bundle.getString(ConfigKeyEventActionFragment.EXTRA_RESULT)!!
- val keyEventAction = Json.decodeFromString(json)
-
- onNavResult(NavResult(requestKey, keyEventAction))
- }
-
- NavDestination.ID_PICK_COORDINATE -> {
- val json = bundle.getString(PickDisplayCoordinateFragment.EXTRA_RESULT)!!
- val result = Json.decodeFromString(json)
-
- onNavResult(NavResult(requestKey, result))
- }
-
- NavDestination.ID_PICK_SWIPE_COORDINATE -> {
- val json = bundle.getString(SwipePickDisplayCoordinateFragment.EXTRA_RESULT)!!
- val result = Json.decodeFromString(json)
-
- onNavResult(NavResult(requestKey, result))
- }
-
- NavDestination.ID_PICK_PINCH_COORDINATE -> {
- val json = bundle.getString(PinchPickDisplayCoordinateFragment.EXTRA_RESULT)!!
- val result = Json.decodeFromString(json)
-
- onNavResult(NavResult(requestKey, result))
- }
-
- NavDestination.ID_CONFIG_INTENT -> {
- val json = bundle.getString(ConfigIntentFragment.EXTRA_RESULT)!!
- val result = Json.decodeFromString(json)
-
- onNavResult(NavResult(requestKey, result))
- }
-
- NavDestination.ID_CHOOSE_ACTIVITY -> {
- val json = bundle.getString(ChooseActivityFragment.EXTRA_RESULT)!!
- val result = Json.decodeFromString(json)
-
- onNavResult(NavResult(requestKey, result))
- }
-
- NavDestination.ID_CHOOSE_SOUND -> {
- val json = bundle.getString(ChooseSoundFileFragment.EXTRA_RESULT)!!
- val result = Json.decodeFromString(json)
-
- onNavResult(NavResult(requestKey, result))
- }
-
- NavDestination.ID_CHOOSE_ACTION -> {
- val json = bundle.getString(ChooseActionFragment.EXTRA_ACTION)!!
- val action = Json.decodeFromString(json)
-
- onNavResult(NavResult(requestKey, action))
- }
-
- NavDestination.ID_CHOOSE_CONSTRAINT -> {
- val json = bundle.getString(ChooseConstraintFragment.EXTRA_CONSTRAINT)!!
- val constraint = Json.decodeFromString(json)
-
- onNavResult(NavResult(requestKey, constraint))
- }
-
- NavDestination.ID_CHOOSE_BLUETOOTH_DEVICE -> {
- val address = bundle.getString(ChooseBluetoothDeviceFragment.EXTRA_ADDRESS)!!
- val name = bundle.getString(ChooseBluetoothDeviceFragment.EXTRA_NAME)!!
-
- onNavResult(NavResult(requestKey, BluetoothDeviceInfo(address, name)))
- }
-
- NavDestination.ID_INTERACT_UI_ELEMENT_ACTION -> {
- val json = bundle.getString(InteractUiElementFragment.EXTRA_ACTION)!!
- val result = Json.decodeFromString(json)
- onNavResult(NavResult(requestKey, result))
- }
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/OnPopupResponseEvent.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/OnPopupResponseEvent.kt
deleted file mode 100644
index bcf274a7d4..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ui/OnPopupResponseEvent.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.github.sds100.keymapper.util.ui
-
-/**
- * Created by sds100 on 23/03/2021.
- */
-data class OnPopupResponseEvent(val key: String, val response: Any?)
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/ShowPopupEvent.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/ShowPopupEvent.kt
deleted file mode 100644
index fdf17c5ce7..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ui/ShowPopupEvent.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package io.github.sds100.keymapper.util.ui
-
-/**
- * Created by sds100 on 23/03/2021.
- */
-data class ShowPopupEvent(val key: String, val ui: PopupUi<*>)
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/SnackBarUtils.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/SnackBarUtils.kt
deleted file mode 100644
index c7e283e2a5..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ui/SnackBarUtils.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package io.github.sds100.keymapper.util.ui
-
-import androidx.coordinatorlayout.widget.CoordinatorLayout
-import io.github.sds100.keymapper.R
-import kotlinx.coroutines.suspendCancellableCoroutine
-import splitties.snackbar.action
-import splitties.snackbar.longSnack
-import splitties.snackbar.onDismiss
-import splitties.snackbar.snack
-import kotlin.coroutines.resume
-
-/**
- * Created by sds100 on 06/04/2021.
- */
-object SnackBarUtils {
-
- suspend fun show(
- view: CoordinatorLayout,
- text: String,
- actionText: String? = null,
- long: Boolean = false,
- ) =
- suspendCancellableCoroutine { continuation ->
-
- val snackBar = if (long) {
- view.longSnack(text) {
- if (actionText != null) {
- action(actionText) {
- if (!continuation.isCompleted) {
- continuation.resume(Unit)
- }
- }
- }
-
- anchorView = view.findViewById(R.id.fab)
- }
- } else {
- view.snack(text) {
- if (actionText != null) {
- action(actionText) {
- if (!continuation.isCompleted) {
- continuation.resume(Unit)
- }
- }
- }
-
- anchorView = view.findViewById(R.id.fab)
- }
- }
-
- // if there is no action then there is no point waiting for a user response
- if (actionText == null) {
- continuation.resume(null)
- }
-
- snackBar.onDismiss {
- if (!continuation.isCompleted) {
- continuation.resume(null)
- }
- }
- }
-}
diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/icons/KeyMapperIcons.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/icons/KeyMapperIcons.kt
deleted file mode 100644
index 343b292720..0000000000
--- a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/icons/KeyMapperIcons.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package io.github.sds100.keymapper.util.ui.compose.icons
-
-object KeyMapperIcons
diff --git a/app/src/main/res/layout/fragment_edittext.xml b/app/src/main/res/layout/fragment_edittext.xml
deleted file mode 100644
index cdf8cf826f..0000000000
--- a/app/src/main/res/layout/fragment_edittext.xml
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_options.xml b/app/src/main/res/layout/fragment_options.xml
deleted file mode 100644
index 88f10e8282..0000000000
--- a/app/src/main/res/layout/fragment_options.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/menu/menu_config_mapping.xml b/app/src/main/res/menu/menu_config_mapping.xml
deleted file mode 100644
index f1dce6e2aa..0000000000
--- a/app/src/main/res/menu/menu_config_mapping.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/navigation/nav_config_keymap.xml b/app/src/main/res/navigation/nav_config_keymap.xml
deleted file mode 100644
index 824d90ac2c..0000000000
--- a/app/src/main/res/navigation/nav_config_keymap.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-ar/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-cs/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-de/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-es/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-fr/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
deleted file mode 100644
index 96247417f6..0000000000
--- a/app/src/main/res/values-hu/strings.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
- Engedd el a kulcsokat!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-id/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-ka/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-ko/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-pl/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-pt/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-ru/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-sk/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-uk/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-vi/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
deleted file mode 100644
index 1d669e715e..0000000000
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt
deleted file mode 100644
index 608de9dcfa..0000000000
--- a/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-package io.github.sds100.keymapper.actions
-
-import android.view.KeyEvent
-import io.github.sds100.keymapper.shizuku.ShizukuAdapter
-import io.github.sds100.keymapper.system.inputmethod.ImeInfo
-import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter
-import io.github.sds100.keymapper.system.permissions.PermissionAdapter
-import io.github.sds100.keymapper.util.Error
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
-import org.hamcrest.Matchers.nullValue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.junit.MockitoJUnitRunner
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-/**
- * Created by sds100 on 01/05/2021.
- */
-
-@ExperimentalCoroutinesApi
-@RunWith(MockitoJUnitRunner::class)
-class GetActionFailedUseCaseTest {
-
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
- private lateinit var useCase: GetActionErrorUseCaseImpl
-
- private lateinit var mockShizukuAdapter: ShizukuAdapter
- private lateinit var mockInputMethodAdapter: InputMethodAdapter
- private lateinit var mockPermissionAdapter: PermissionAdapter
-
- @Before
- fun init() {
- mockShizukuAdapter = mock()
- mockInputMethodAdapter = mock()
- mockPermissionAdapter = mock()
-
- useCase = GetActionErrorUseCaseImpl(
- packageManager = mock(),
- inputMethodAdapter = mockInputMethodAdapter,
- permissionAdapter = mockPermissionAdapter,
- systemFeatureAdapter = mock(),
- cameraAdapter = mock(),
- soundsManager = mock(),
- shizukuAdapter = mockShizukuAdapter,
- ringtoneAdapter = mock(),
- )
- }
-
- /**
- * #776
- */
- @Test
- fun `don't show Shizuku errors if a compatible ime is selected`() = testScope.runTest {
- // GIVEN
- whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) }
- whenever(mockInputMethodAdapter.chosenIme).then {
- MutableStateFlow(
- ImeInfo(
- id = "ime_id",
- packageName = "io.github.sds100.keymapper.inputmethod.latin",
- label = "Key Mapper GUI Keyboard",
- isEnabled = true,
- isChosen = true,
- ),
- )
- }
-
- val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN)
-
- // WHEN
- val error = useCase.actionErrorSnapshot.first().getError(action)
-
- // THEN
- assertThat(error, nullValue())
- }
-
- /**
- * #776
- */
- @Test
- fun `show Shizuku errors if a compatible ime is not selected and Shizuku is installed`() = testScope.runTest {
- // GIVEN
- whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) }
- whenever(mockShizukuAdapter.isStarted).then { MutableStateFlow(false) }
-
- whenever(mockInputMethodAdapter.chosenIme).then {
- MutableStateFlow(
- ImeInfo(
- id = "ime_id",
- packageName = "io.gboard",
- label = "Gboard",
- isEnabled = true,
- isChosen = true,
- ),
- )
- }
-
- val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN)
- // WHEN
- val error = useCase.actionErrorSnapshot.first().getError(action)
-
- // THEN
- assertThat(error, `is`(Error.ShizukuNotStarted))
- }
-}
diff --git a/app/src/test/java/io/github/sds100/keymapper/util/JsonTestUtils.kt b/app/src/test/java/io/github/sds100/keymapper/util/JsonTestUtils.kt
deleted file mode 100644
index 71e2d52856..0000000000
--- a/app/src/test/java/io/github/sds100/keymapper/util/JsonTestUtils.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package io.github.sds100.keymapper.util
-
-import com.github.salomonbrys.kotson.contains
-import com.github.salomonbrys.kotson.forEach
-import com.github.salomonbrys.kotson.get
-import com.google.gson.JsonArray
-import com.google.gson.JsonElement
-import com.google.gson.JsonObject
-import com.google.gson.JsonPrimitive
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.core.Is.`is`
-import org.junit.Assert
-
-/**
- * Created by sds100 on 25/01/21.
- */
-object JsonTestUtils {
- private const val NAME_SEPARATOR = '/'
-
- fun compareBothWays(element: JsonElement, elementName: String, other: JsonElement, otherName: String) {
- compare("", element, elementName, other, otherName)
- compare("", other, elementName, element, elementName)
- }
-
- private fun compare(parentNamePath: String = "", element: JsonElement, elementName: String, rootToCompare: JsonElement, rootName: String) {
- when (element) {
- is JsonObject -> {
- element.forEach { name, jsonElement ->
- val newPath = if (parentNamePath.isBlank()) {
- name
- } else {
- "$parentNamePath$NAME_SEPARATOR$name"
- }
-
- compare(newPath, jsonElement, elementName, rootToCompare, rootName)
- }
- }
-
- is JsonArray -> {
- val pathToArrayToCompare = parentNamePath.split(NAME_SEPARATOR)
- var arrayToCompare: JsonArray? = null
-
- var parentElement: JsonElement = rootToCompare
- pathToArrayToCompare.forEach {
- if (it == "") return@forEach
-
- parentElement = parentElement[it]
- }
-
- if (parentElement is JsonArray) {
- arrayToCompare = parentElement as JsonArray
- }
-
- Assert.assertNotNull("can't find array $elementName/$parentNamePath in $rootName", arrayToCompare)
- arrayToCompare ?: return
-
- element.forEachIndexed { index, arrayElement ->
- val validIndex = index <= arrayToCompare.toList().lastIndex
-
- assertThat("$rootName/${pathToArrayToCompare.last()} doesn't contain $arrayElement at $index index", validIndex)
-
- compare("", arrayElement, "$elementName/${pathToArrayToCompare.last()}", arrayToCompare[index]!!, "$rootName/${pathToArrayToCompare.last()}")
- }
- }
-
- is JsonPrimitive -> {
- val names = parentNamePath.split(NAME_SEPARATOR)
- var parentElement: JsonElement = rootToCompare
-
- if (names == listOf("")) {
- assertThat("$elementName/:$element doesn't match $rootName/:$parentElement", (parentElement), `is`(element))
- } else {
- names.forEachIndexed { index, name ->
- if (parentElement is JsonObject) {
- assertThat("$elementName/$parentNamePath not found in $rootName", (parentElement as JsonObject).contains(name))
- }
-
- parentElement = parentElement[name]
-
- if (index == names.lastIndex) {
- assertThat("$elementName/$parentNamePath:$element doesn't match $rootName/$parentNamePath:$parentElement", (parentElement as JsonPrimitive), `is`(element))
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/version.properties b/app/version.properties
index 04dc99c438..c4f447ccfe 100644
--- a/app/version.properties
+++ b/app/version.properties
@@ -1,3 +1,3 @@
-VERSION_NAME=3.1.1
-VERSION_CODE=118
+VERSION_NAME=3.2.0
+VERSION_CODE=131
VERSION_NUM=0
\ No newline at end of file
diff --git a/base/.gitignore b/base/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/base/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/base/build.gradle.kts b/base/build.gradle.kts
new file mode 100644
index 0000000000..86e093e95d
--- /dev/null
+++ b/base/build.gradle.kts
@@ -0,0 +1,153 @@
+import java.util.Properties
+
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.compose)
+ alias(libs.plugins.kotlin.kapt)
+ alias(libs.plugins.kotlin.serialization)
+ alias(libs.plugins.kotlin.parcelize)
+ alias(libs.plugins.androidx.navigation.safeargs.kotlin)
+ alias(libs.plugins.google.devtools.ksp)
+ alias(libs.plugins.jlleitschuh.gradle.ktlint)
+ alias(libs.plugins.dagger.hilt.android)
+}
+
+android {
+ namespace = "io.github.sds100.keymapper.base"
+ compileSdk = libs.versions.compile.sdk.get().toInt()
+
+ val versionProperties = Properties().apply {
+ project.file("../app/version.properties").inputStream().use { load(it) }
+ }
+
+ defaultConfig {
+ minSdk = libs.versions.min.sdk.get().toInt()
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+
+ buildConfigField(
+ "Integer",
+ "VERSION_CODE",
+ versionProperties.getProperty("VERSION_CODE"),
+ )
+ }
+
+ buildTypes {
+ release {
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro",
+ )
+ }
+ }
+
+ buildFeatures {
+ dataBinding = true
+ viewBinding = true
+ aidl = true
+ buildConfig = true
+ compose = true
+ }
+
+ compileOptions {
+ isCoreLibraryDesugaringEnabled = true
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+
+ kapt {
+ correctErrorTypes = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
+ }
+}
+
+dependencies {
+ implementation(project(":common"))
+ implementation(project(":data"))
+ implementation(project(":system"))
+ implementation(project(":systemstubs"))
+
+ // kotlin stuff
+ implementation(libs.kotlinx.coroutines.android)
+ implementation(libs.kotlinx.serialization.json)
+
+ // random stuff
+ implementation(libs.google.android.material)
+ implementation(libs.kotson)
+ implementation(libs.airbnb.epoxy)
+ implementation(libs.airbnb.epoxy.databinding)
+ debugImplementation(libs.androidx.ui.tooling)
+ kapt(libs.airbnb.epoxy.processor)
+ implementation(libs.jakewharton.timber)
+ implementation(libs.anggrayudi.storage)
+ implementation(libs.github.mflisar.dragselectrecyclerview)
+ implementation(libs.google.flexbox)
+ implementation(libs.squareup.okhttp)
+ coreLibraryDesugaring(libs.desugar.jdk.libs)
+ implementation(libs.canopas.introshowcaseview)
+ implementation(libs.dagger.hilt.android)
+ ksp(libs.dagger.hilt.android.compiler)
+ implementation(libs.bundles.splitties)
+
+ // androidx
+ implementation(libs.androidx.legacy.support.core.ui)
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.activity.ktx)
+ implementation(libs.androidx.fragment.ktx)
+ implementation(libs.bundles.androidx.lifecycle)
+ implementation(libs.bundles.androidx.navigation)
+ implementation(libs.androidx.multidex)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.recyclerview)
+ implementation(libs.androidx.preference.ktx)
+ implementation(libs.androidx.constraintlayout)
+ implementation(libs.androidx.lifecycle.extensions) // Note: Deprecated
+ implementation(libs.androidx.viewpager2)
+ implementation(libs.androidx.datastore.preferences)
+ implementation(libs.androidx.core.splashscreen)
+ implementation(libs.androidx.hilt.navigation.compose)
+
+ // Compose
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.compose.foundation)
+ implementation(libs.androidx.compose.ui.android)
+ implementation(libs.androidx.compose.material3)
+ implementation(libs.androidx.compose.ui.tooling.preview)
+ implementation(libs.androidx.compose.material.icons.extended)
+ implementation(libs.androidx.compose.material3.adaptive)
+ implementation(libs.androidx.compose.material3.adaptive.navigation)
+ implementation(libs.google.accompanist.drawablepainter)
+ implementation(libs.androidx.compose.ui.tooling)
+
+ // Tests
+
+ testImplementation(libs.junit)
+ testImplementation(libs.hamcrest.all)
+ testImplementation(libs.androidx.junit.ktx) // androidx.test.ext:junit-ktx
+ testImplementation(libs.androidx.test.core.ktx)
+ testImplementation(libs.androidx.test.core)
+ testImplementation(libs.robolectric)
+ testImplementation(libs.androidx.arch.core.testing)
+ testImplementation(libs.kotlinx.coroutines.test)
+ testImplementation(libs.junit.params)
+ testImplementation(libs.mockito.kotlin)
+ testImplementation(libs.mockito.core)
+ testImplementation(libs.mockito.inline)
+ testDebugImplementation(libs.androidx.fragment.testing)
+
+ // Dependencies for Android instrumented tests
+ androidTestImplementation(libs.androidx.test.ext.junit) // androidx.test.ext:junit
+ androidTestImplementation(libs.junit) // Repeated, fine
+ androidTestImplementation(libs.androidx.navigation.testing)
+ androidTestImplementation(libs.androidx.room.testing.legacy)
+ androidTestImplementation(libs.mockito.android)
+}
diff --git a/app/src/test/resources/backup-manager-test/restore-all.zip/sounds/sound.ogg b/base/consumer-rules.pro
similarity index 100%
rename from app/src/test/resources/backup-manager-test/restore-all.zip/sounds/sound.ogg
rename to base/consumer-rules.pro
diff --git a/base/proguard-rules.pro b/base/proguard-rules.pro
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/base/src/main/AndroidManifest.xml b/base/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..b14e766002
--- /dev/null
+++ b/base/src/main/AndroidManifest.xml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/base/src/main/assets/whats-new.txt b/base/src/main/assets/whats-new.txt
new file mode 100644
index 0000000000..50b5221179
--- /dev/null
+++ b/base/src/main/assets/whats-new.txt
@@ -0,0 +1,5 @@
+⌨️ Action to move cursor to previous/next character, word, line, paragraph, or page.
+
+Many other bug fixes and optimisations.
+
+See all the changes at http://changelog.keymapper.club.
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/ActivityViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/ActivityViewModel.kt
new file mode 100644
index 0000000000..a8b48bb2b9
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/ActivityViewModel.kt
@@ -0,0 +1,34 @@
+package io.github.sds100.keymapper.base
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProviderImpl
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.ViewModelHelper
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ActivityViewModel @Inject constructor(
+ resourceProvider: ResourceProvider,
+ dialogProvider: DialogProvider,
+) : ViewModel(),
+ ResourceProvider by resourceProvider,
+ DialogProvider by dialogProvider,
+ NavigationProvider by NavigationProviderImpl() {
+
+ var handledActivityLaunchIntent: Boolean = false
+ var previousNightMode: Int? = null
+
+ fun onCantFindAccessibilitySettings() {
+ viewModelScope.launch {
+ ViewModelHelper.handleCantFindAccessibilitySettings(
+ resourceProvider = this@ActivityViewModel,
+ dialogProvider = this@ActivityViewModel,
+ )
+ }
+ }
+}
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/BaseKeyMapperApp.kt b/base/src/main/java/io/github/sds100/keymapper/base/BaseKeyMapperApp.kt
new file mode 100644
index 0000000000..62d85ba466
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/BaseKeyMapperApp.kt
@@ -0,0 +1,190 @@
+package io.github.sds100.keymapper.base
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.os.Build
+import android.os.UserManager
+import android.util.Log
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.content.getSystemService
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+import androidx.lifecycle.ProcessLifecycleOwner
+import androidx.multidex.MultiDexApplication
+import io.github.sds100.keymapper.base.logging.KeyMapperLoggingTree
+import io.github.sds100.keymapper.base.settings.ThemeUtils
+import io.github.sds100.keymapper.base.system.accessibility.AccessibilityServiceAdapterImpl
+import io.github.sds100.keymapper.base.system.inputmethod.AutoSwitchImeController
+import io.github.sds100.keymapper.base.system.notifications.NotificationController
+import io.github.sds100.keymapper.base.system.permissions.AutoGrantPermissionController
+import io.github.sds100.keymapper.data.Keys
+import io.github.sds100.keymapper.data.entities.LogEntryEntity
+import io.github.sds100.keymapper.data.repositories.LogRepository
+import io.github.sds100.keymapper.data.repositories.SettingsPreferenceRepository
+import io.github.sds100.keymapper.system.apps.AndroidPackageManagerAdapter
+import io.github.sds100.keymapper.system.devices.AndroidDevicesAdapter
+import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter
+import io.github.sds100.keymapper.system.permissions.Permission
+import io.github.sds100.keymapper.system.root.SuAdapterImpl
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import timber.log.Timber
+import java.util.Calendar
+import javax.inject.Inject
+
+@SuppressLint("LogNotTimber")
+abstract class BaseKeyMapperApp : MultiDexApplication() {
+ private val tag = BaseKeyMapperApp::class.simpleName
+
+ @Inject
+ lateinit var appCoroutineScope: CoroutineScope
+
+ @Inject
+ lateinit var notificationController: NotificationController
+
+ @Inject
+ lateinit var autoSwitchImeController: AutoSwitchImeController
+
+ @Inject
+ lateinit var packageManagerAdapter: AndroidPackageManagerAdapter
+
+ @Inject
+ lateinit var devicesAdapter: AndroidDevicesAdapter
+
+ @Inject
+ lateinit var permissionAdapter: AndroidPermissionAdapter
+
+ @Inject
+ lateinit var accessibilityServiceAdapter: AccessibilityServiceAdapterImpl
+
+ @Inject
+ lateinit var suAdapter: SuAdapterImpl
+
+ @Inject
+ lateinit var autoGrantPermissionController: AutoGrantPermissionController
+
+ @Inject
+ lateinit var loggingTree: KeyMapperLoggingTree
+
+ @Inject
+ lateinit var settingsRepository: SettingsPreferenceRepository
+
+ @Inject
+ lateinit var logRepository: LogRepository
+
+ private val processLifecycleOwner by lazy { ProcessLifecycleOwner.get() }
+
+ private val userManager: UserManager? by lazy { getSystemService() }
+
+ private val initLock: Any = Any()
+ private var initialized = false
+
+ override fun onCreate() {
+ val priorExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
+
+ Log.i(tag, "KeyMapperApp: OnCreate")
+
+ Thread.setDefaultUncaughtExceptionHandler { thread, exception ->
+ // log in a blocking manner and always log regardless of whether the setting is turned on
+ val entry = LogEntryEntity(
+ id = 0,
+ time = Calendar.getInstance().timeInMillis,
+ severity = LogEntryEntity.SEVERITY_ERROR,
+ message = exception.stackTraceToString(),
+ )
+
+ runBlocking {
+ logRepository.insertSuspend(entry)
+ }
+
+ priorExceptionHandler?.uncaughtException(thread, exception)
+ }
+
+ super.onCreate()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && userManager?.isUserUnlocked == false) {
+ Log.i(tag, "KeyMapperApp: Delay init because locked.")
+ // If the device is still encrypted and locked do not initialize anything that
+ // may potentially need the encrypted app storage like databases.
+ return
+ }
+
+ synchronized(initLock) {
+ init()
+ initialized = true
+ }
+ }
+
+ fun onBootUnlocked() {
+ synchronized(initLock) {
+ if (!initialized) {
+ init()
+ }
+ initialized = true
+ }
+ }
+
+ private fun init() {
+ Log.i(tag, "KeyMapperApp: Init")
+
+ settingsRepository.get(Keys.darkTheme)
+ .map { it?.toIntOrNull() }
+ .map {
+ when (it) {
+ ThemeUtils.DARK -> AppCompatDelegate.MODE_NIGHT_YES
+ ThemeUtils.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO
+ else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
+ }
+ }
+ .onEach { mode -> AppCompatDelegate.setDefaultNightMode(mode) }
+ .launchIn(appCoroutineScope)
+
+ if (BuildConfig.BUILD_TYPE == "debug" || BuildConfig.BUILD_TYPE == "debug_release") {
+ Timber.plant(Timber.DebugTree())
+ }
+
+ Timber.plant(loggingTree)
+
+ notificationController.init()
+
+ autoSwitchImeController.init()
+
+ processLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ fun onResume() {
+ // when the user returns to the app let everything know that the permissions could have changed
+ notificationController.onOpenApp()
+
+ if (BuildConfig.DEBUG && permissionAdapter.isGranted(Permission.WRITE_SECURE_SETTINGS)) {
+ accessibilityServiceAdapter.start()
+ }
+ }
+ })
+
+ appCoroutineScope.launch {
+ notificationController.openApp.collectLatest { intentAction ->
+ Intent(this@BaseKeyMapperApp, getMainActivityClass()).apply {
+ action = intentAction
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+
+ startActivity(this)
+ }
+ }
+ }
+
+ notificationController.showToast.onEach { toast ->
+ Toast.makeText(this, toast, Toast.LENGTH_SHORT).show()
+ }.launchIn(appCoroutineScope)
+
+ autoGrantPermissionController.start()
+ }
+
+ abstract fun getMainActivityClass(): Class<*>
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt
similarity index 72%
rename from app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt
index 98cf6232c0..6c6dae42eb 100644
--- a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainActivity.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper
+package io.github.sds100.keymapper.base
import android.content.BroadcastReceiver
import android.content.Context
@@ -17,7 +17,6 @@ import androidx.compose.ui.graphics.toArgb
import androidx.core.content.ContextCompat
import androidx.core.content.IntentCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
@@ -26,59 +25,69 @@ import androidx.navigation.findNavController
import com.anggrayudi.storage.extension.openInputStream
import com.anggrayudi.storage.extension.openOutputStream
import com.anggrayudi.storage.extension.toDocumentFile
-import io.github.sds100.keymapper.Constants.PACKAGE_NAME
-import io.github.sds100.keymapper.compose.ComposeColors
-import io.github.sds100.keymapper.databinding.ActivityMainBinding
-import io.github.sds100.keymapper.mappings.keymaps.trigger.RecordTriggerController
-import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter
+import io.github.sds100.keymapper.base.compose.ComposeColors
+import io.github.sds100.keymapper.base.onboarding.OnboardingUseCase
+import io.github.sds100.keymapper.base.system.accessibility.AccessibilityServiceAdapterImpl
+import io.github.sds100.keymapper.base.system.permissions.RequestPermissionDelegate
+import io.github.sds100.keymapper.base.trigger.RecordTriggerController
+import io.github.sds100.keymapper.base.utils.ui.ResourceProviderImpl
+import io.github.sds100.keymapper.base.utils.ui.launchRepeatOnLifecycle
+import io.github.sds100.keymapper.common.BuildConfigProvider
import io.github.sds100.keymapper.system.files.FileUtils
import io.github.sds100.keymapper.system.inputevents.MyMotionEvent
+import io.github.sds100.keymapper.system.notifications.NotificationReceiverAdapterImpl
import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter
-import io.github.sds100.keymapper.system.permissions.RequestPermissionDelegate
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.ui.showPopups
+import io.github.sds100.keymapper.system.shizuku.ShizukuAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
-
-/**
- * Created by sds100 on 19/02/2020.
- */
+import javax.inject.Inject
abstract class BaseMainActivity : AppCompatActivity() {
companion object {
const val ACTION_SHOW_ACCESSIBILITY_SETTINGS_NOT_FOUND_DIALOG =
- "$PACKAGE_NAME.ACTION_SHOW_ACCESSIBILITY_SETTINGS_NOT_FOUND_DIALOG"
+ "${BuildConfig.LIBRARY_PACKAGE_NAME}.ACTION_SHOW_ACCESSIBILITY_SETTINGS_NOT_FOUND_DIALOG"
const val ACTION_USE_FLOATING_BUTTONS =
- "$PACKAGE_NAME.ACTION_USE_FLOATING_BUTTONS"
+ "${BuildConfig.LIBRARY_PACKAGE_NAME}.ACTION_USE_FLOATING_BUTTONS"
- const val ACTION_SAVE_FILE = "$PACKAGE_NAME.ACTION_SAVE_FILE"
- const val EXTRA_FILE_URI = "$PACKAGE_NAME.EXTRA_FILE_URI"
+ const val ACTION_SAVE_FILE = "${BuildConfig.LIBRARY_PACKAGE_NAME}.ACTION_SAVE_FILE"
+ const val EXTRA_FILE_URI = "${BuildConfig.LIBRARY_PACKAGE_NAME}.EXTRA_FILE_URI"
}
- private val permissionAdapter: AndroidPermissionAdapter by lazy {
- ServiceLocator.permissionAdapter(this)
- }
+ @Inject
+ lateinit var permissionAdapter: AndroidPermissionAdapter
- val serviceAdapter: AccessibilityServiceAdapter by lazy {
- ServiceLocator.accessibilityServiceAdapter(this)
- }
+ @Inject
+ lateinit var serviceAdapter: AccessibilityServiceAdapterImpl
- val viewModel by viewModels {
- ActivityViewModel.Factory(ServiceLocator.resourceProvider(this))
- }
+ @Inject
+ lateinit var resourceProvider: ResourceProviderImpl
+
+ @Inject
+ lateinit var onboardingUseCase: OnboardingUseCase
+
+ @Inject
+ lateinit var recordTriggerController: RecordTriggerController
+
+ @Inject
+ lateinit var notificationReceiverAdapter: NotificationReceiverAdapterImpl
+
+ @Inject
+ lateinit var shizukuAdapter: ShizukuAdapter
+
+ @Inject
+ lateinit var buildConfigProvider: BuildConfigProvider
+
+ private lateinit var requestPermissionDelegate: RequestPermissionDelegate
private val currentNightMode: Int
get() = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
- private lateinit var requestPermissionDelegate: RequestPermissionDelegate
- private val recordTriggerController: RecordTriggerController by lazy {
- (applicationContext as KeyMapperApp).recordTriggerController
- }
+ val viewModel by viewModels()
private var originalFileUri: Uri? = null
@@ -124,17 +133,19 @@ abstract class BaseMainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
if (viewModel.previousNightMode != currentNightMode) {
- ServiceLocator.resourceProvider(this).onThemeChange()
+ resourceProvider.onThemeChange()
}
- val binding =
- DataBindingUtil.setContentView(this, R.layout.activity_main)
-
- viewModel.showPopups(this, binding.coordinatorLayout)
-
- requestPermissionDelegate = RequestPermissionDelegate(this, showDialogs = true)
+ requestPermissionDelegate = RequestPermissionDelegate(
+ this,
+ showDialogs = true,
+ permissionAdapter,
+ notificationReceiverAdapter = notificationReceiverAdapter,
+ buildConfigProvider = buildConfigProvider,
+ shizukuAdapter = shizukuAdapter,
+ )
- ServiceLocator.permissionAdapter(this@BaseMainActivity).request
+ permissionAdapter.request
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.onEach { permission ->
requestPermissionDelegate.requestPermission(
@@ -174,18 +185,18 @@ abstract class BaseMainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
- Timber.i("MainActivity: onResume. Version: ${Constants.VERSION}")
+ Timber.i("MainActivity: onResume. Version: ${buildConfigProvider.version} ${buildConfigProvider.versionCode}")
// This must be after onResume to ensure all the fragment lifecycles' have also
// resumed which are observing these events.
// This is checked here and not in KeyMapperApp's lifecycle observer because
// the activities have not necessarily resumed at that point.
permissionAdapter.onPermissionsChanged()
- serviceAdapter.updateWhetherServiceIsEnabled()
+ serviceAdapter.invalidateState()
}
override fun onDestroy() {
- UseCases.onboarding(this).shownAppIntro = true
+ onboardingUseCase.shownAppIntro = true
viewModel.previousNightMode = currentNightMode
unregisterReceiver(broadcastReceiver)
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/BaseMainNavHost.kt b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainNavHost.kt
new file mode 100644
index 0000000000..4589823952
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/BaseMainNavHost.kt
@@ -0,0 +1,59 @@
+package io.github.sds100.keymapper.base
+
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import io.github.sds100.keymapper.base.actions.uielement.InteractUiElementScreen
+import io.github.sds100.keymapper.base.actions.uielement.InteractUiElementViewModel
+import io.github.sds100.keymapper.base.constraints.ChooseConstraintScreen
+import io.github.sds100.keymapper.base.constraints.ChooseConstraintViewModel
+import io.github.sds100.keymapper.base.utils.navigation.NavDestination
+import io.github.sds100.keymapper.base.utils.navigation.handleRouteArgs
+import kotlinx.serialization.json.Json
+
+@Composable
+fun BaseMainNavHost(
+ modifier: Modifier = Modifier,
+ navController: NavHostController,
+ composableDestinations: NavGraphBuilder.() -> Unit = {},
+) {
+ NavHost(
+ modifier = modifier,
+ navController = navController,
+ startDestination = NavDestination.Home,
+ enterTransition = { slideIntoContainer(towards = AnimatedContentTransitionScope.SlideDirection.Left) },
+ exitTransition = { slideOutOfContainer(towards = AnimatedContentTransitionScope.SlideDirection.Right) },
+ popEnterTransition = { slideIntoContainer(towards = AnimatedContentTransitionScope.SlideDirection.Right) },
+ popExitTransition = { slideOutOfContainer(towards = AnimatedContentTransitionScope.SlideDirection.Right) },
+ ) {
+ composable { backStackEntry ->
+ val viewModel: InteractUiElementViewModel = hiltViewModel()
+
+ backStackEntry.handleRouteArgs { destination ->
+ destination.actionJson?.let { viewModel.loadAction(Json.decodeFromString(it)) }
+ }
+
+ InteractUiElementScreen(
+ modifier = Modifier.fillMaxSize(),
+ viewModel = viewModel,
+ )
+ }
+
+ composable {
+ val viewModel: ChooseConstraintViewModel = hiltViewModel()
+
+ ChooseConstraintScreen(
+ modifier = Modifier.fillMaxSize(),
+ viewModel = viewModel,
+ )
+ }
+
+ composableDestinations()
+ }
+}
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/BaseSingletonHiltModule.kt b/base/src/main/java/io/github/sds100/keymapper/base/BaseSingletonHiltModule.kt
new file mode 100644
index 0000000000..55a0ffabfe
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/BaseSingletonHiltModule.kt
@@ -0,0 +1,137 @@
+package io.github.sds100.keymapper.base
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import io.github.sds100.keymapper.base.actions.GetActionErrorUseCase
+import io.github.sds100.keymapper.base.actions.GetActionErrorUseCaseImpl
+import io.github.sds100.keymapper.base.actions.sound.SoundsManager
+import io.github.sds100.keymapper.base.actions.sound.SoundsManagerImpl
+import io.github.sds100.keymapper.base.actions.uielement.InteractUiElementController
+import io.github.sds100.keymapper.base.actions.uielement.InteractUiElementUseCase
+import io.github.sds100.keymapper.base.backup.BackupManager
+import io.github.sds100.keymapper.base.backup.BackupManagerImpl
+import io.github.sds100.keymapper.base.constraints.GetConstraintErrorUseCase
+import io.github.sds100.keymapper.base.constraints.GetConstraintErrorUseCaseImpl
+import io.github.sds100.keymapper.base.keymaps.ConfigKeyMapUseCase
+import io.github.sds100.keymapper.base.keymaps.ConfigKeyMapUseCaseController
+import io.github.sds100.keymapper.base.keymaps.FingerprintGesturesSupportedUseCase
+import io.github.sds100.keymapper.base.keymaps.FingerprintGesturesSupportedUseCaseImpl
+import io.github.sds100.keymapper.base.keymaps.PauseKeyMapsUseCase
+import io.github.sds100.keymapper.base.keymaps.PauseKeyMapsUseCaseImpl
+import io.github.sds100.keymapper.base.onboarding.OnboardingUseCase
+import io.github.sds100.keymapper.base.onboarding.OnboardingUseCaseImpl
+import io.github.sds100.keymapper.base.system.accessibility.AccessibilityServiceAdapterImpl
+import io.github.sds100.keymapper.base.system.accessibility.ControlAccessibilityServiceUseCase
+import io.github.sds100.keymapper.base.system.accessibility.ControlAccessibilityServiceUseCaseImpl
+import io.github.sds100.keymapper.base.system.inputmethod.ShowHideInputMethodUseCase
+import io.github.sds100.keymapper.base.system.inputmethod.ShowHideInputMethodUseCaseImpl
+import io.github.sds100.keymapper.base.system.inputmethod.ShowInputMethodPickerUseCase
+import io.github.sds100.keymapper.base.system.inputmethod.ShowInputMethodPickerUseCaseImpl
+import io.github.sds100.keymapper.base.system.inputmethod.ToggleCompatibleImeUseCase
+import io.github.sds100.keymapper.base.system.inputmethod.ToggleCompatibleImeUseCaseImpl
+import io.github.sds100.keymapper.base.system.notifications.AndroidNotificationAdapter
+import io.github.sds100.keymapper.base.system.notifications.ManageNotificationsUseCase
+import io.github.sds100.keymapper.base.system.notifications.ManageNotificationsUseCaseImpl
+import io.github.sds100.keymapper.base.trigger.RecordTriggerController
+import io.github.sds100.keymapper.base.trigger.RecordTriggerUseCase
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProviderImpl
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.DialogProviderImpl
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.ResourceProviderImpl
+import io.github.sds100.keymapper.common.utils.DefaultUuidGenerator
+import io.github.sds100.keymapper.common.utils.UuidGenerator
+import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter
+import io.github.sds100.keymapper.system.notifications.NotificationAdapter
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class BaseSingletonHiltModule {
+ @Singleton
+ @Binds
+ abstract fun provideNotificationAdapter(impl: AndroidNotificationAdapter): NotificationAdapter
+
+ @Singleton
+ @Binds
+ abstract fun provideAccessibilityAdapter(impl: AccessibilityServiceAdapterImpl): AccessibilityServiceAdapter
+
+ @Singleton
+ @Binds
+ abstract fun provideResourceProvider(impl: ResourceProviderImpl): ResourceProvider
+
+ @Singleton
+ @Binds
+ abstract fun provideOnboardingUseCase(impl: OnboardingUseCaseImpl): OnboardingUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindPauseKeyMapsUseCase(impl: PauseKeyMapsUseCaseImpl): PauseKeyMapsUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindShowInputMethodPickerUseCase(impl: ShowInputMethodPickerUseCaseImpl): ShowInputMethodPickerUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindControlAccessibilityServiceUseCase(impl: ControlAccessibilityServiceUseCaseImpl): ControlAccessibilityServiceUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindToggleCompatibleImeUseCase(impl: ToggleCompatibleImeUseCaseImpl): ToggleCompatibleImeUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindInteractUiElementUseCase(impl: InteractUiElementController): InteractUiElementUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindShowHideInputMethodUseCase(impl: ShowHideInputMethodUseCaseImpl): ShowHideInputMethodUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindBackupManager(impl: BackupManagerImpl): BackupManager
+
+ @Binds
+ @Singleton
+ abstract fun bindSoundsManager(impl: SoundsManagerImpl): SoundsManager
+
+ @Binds
+ @Singleton
+ abstract fun bindConfigKeyMapUseCase(impl: ConfigKeyMapUseCaseController): ConfigKeyMapUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindRecordTriggerUseCase(impl: RecordTriggerController): RecordTriggerUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindFingerprintGesturesSupportedUseCase(impl: FingerprintGesturesSupportedUseCaseImpl): FingerprintGesturesSupportedUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindGetActionErrorUseCase(impl: GetActionErrorUseCaseImpl): GetActionErrorUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindGetConstraintErrorUseCase(impl: GetConstraintErrorUseCaseImpl): GetConstraintErrorUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindManageNotificationsUseCase(impl: ManageNotificationsUseCaseImpl): ManageNotificationsUseCase
+
+ @Binds
+ @Singleton
+ abstract fun bindUuidGenerator(impl: DefaultUuidGenerator): UuidGenerator
+
+ @Binds
+ @Singleton
+ abstract fun bindNavigationProvider(impl: NavigationProviderImpl): NavigationProvider
+
+ @Binds
+ @Singleton
+ abstract fun bindDialogProvider(impl: DialogProviderImpl): DialogProvider
+}
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/BaseViewModelHiltModule.kt b/base/src/main/java/io/github/sds100/keymapper/base/BaseViewModelHiltModule.kt
new file mode 100644
index 0000000000..526b59f6e7
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/BaseViewModelHiltModule.kt
@@ -0,0 +1,113 @@
+package io.github.sds100.keymapper.base
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ViewModelComponent
+import dagger.hilt.android.scopes.ViewModelScoped
+import io.github.sds100.keymapper.base.actions.CreateActionUseCase
+import io.github.sds100.keymapper.base.actions.CreateActionUseCaseImpl
+import io.github.sds100.keymapper.base.actions.TestActionUseCase
+import io.github.sds100.keymapper.base.actions.TestActionUseCaseImpl
+import io.github.sds100.keymapper.base.actions.keyevent.ConfigKeyEventUseCase
+import io.github.sds100.keymapper.base.actions.keyevent.ConfigKeyEventUseCaseImpl
+import io.github.sds100.keymapper.base.actions.sound.ChooseSoundFileUseCase
+import io.github.sds100.keymapper.base.actions.sound.ChooseSoundFileUseCaseImpl
+import io.github.sds100.keymapper.base.backup.BackupRestoreMappingsUseCase
+import io.github.sds100.keymapper.base.backup.BackupRestoreMappingsUseCaseImpl
+import io.github.sds100.keymapper.base.constraints.CreateConstraintUseCase
+import io.github.sds100.keymapper.base.constraints.CreateConstraintUseCaseImpl
+import io.github.sds100.keymapper.base.home.ShowHomeScreenAlertsUseCase
+import io.github.sds100.keymapper.base.home.ShowHomeScreenAlertsUseCaseImpl
+import io.github.sds100.keymapper.base.keymaps.CreateKeyMapShortcutUseCase
+import io.github.sds100.keymapper.base.keymaps.CreateKeyMapShortcutUseCaseImpl
+import io.github.sds100.keymapper.base.keymaps.DisplayKeyMapUseCase
+import io.github.sds100.keymapper.base.keymaps.DisplayKeyMapUseCaseImpl
+import io.github.sds100.keymapper.base.keymaps.ListKeyMapsUseCase
+import io.github.sds100.keymapper.base.keymaps.ListKeyMapsUseCaseImpl
+import io.github.sds100.keymapper.base.logging.DisplayLogUseCase
+import io.github.sds100.keymapper.base.logging.DisplayLogUseCaseImpl
+import io.github.sds100.keymapper.base.settings.ConfigSettingsUseCase
+import io.github.sds100.keymapper.base.settings.ConfigSettingsUseCaseImpl
+import io.github.sds100.keymapper.base.sorting.SortKeyMapsUseCase
+import io.github.sds100.keymapper.base.sorting.SortKeyMapsUseCaseImpl
+import io.github.sds100.keymapper.base.system.apps.DisplayAppShortcutsUseCase
+import io.github.sds100.keymapper.base.system.apps.DisplayAppShortcutsUseCaseImpl
+import io.github.sds100.keymapper.base.system.apps.DisplayAppsUseCase
+import io.github.sds100.keymapper.base.system.apps.DisplayAppsUseCaseImpl
+import io.github.sds100.keymapper.base.system.bluetooth.ChooseBluetoothDeviceUseCase
+import io.github.sds100.keymapper.base.system.bluetooth.ChooseBluetoothDeviceUseCaseImpl
+import io.github.sds100.keymapper.base.trigger.SetupGuiKeyboardUseCase
+import io.github.sds100.keymapper.base.trigger.SetupGuiKeyboardUseCaseImpl
+
+@Module
+@InstallIn(ViewModelComponent::class)
+abstract class BaseViewModelHiltModule {
+ @Binds
+ @ViewModelScoped
+ abstract fun bindDisplayKeyMapUseCase(impl: DisplayKeyMapUseCaseImpl): DisplayKeyMapUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindListKeyMapsUseCase(impl: ListKeyMapsUseCaseImpl): ListKeyMapsUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindBackupRestoreMappingsUseCase(impl: BackupRestoreMappingsUseCaseImpl): BackupRestoreMappingsUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindShowHomeScreenAlertsUseCase(impl: ShowHomeScreenAlertsUseCaseImpl): ShowHomeScreenAlertsUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindSortKeyMapsUseCase(impl: SortKeyMapsUseCaseImpl): SortKeyMapsUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindDisplayLogUseCase(impl: DisplayLogUseCaseImpl): DisplayLogUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindConfigSettingsUseCase(impl: ConfigSettingsUseCaseImpl): ConfigSettingsUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindChooseBluetoothDeviceUseCase(impl: ChooseBluetoothDeviceUseCaseImpl): ChooseBluetoothDeviceUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindChooseSoundFileUseCase(impl: ChooseSoundFileUseCaseImpl): ChooseSoundFileUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindConfigKeyEventUseCase(impl: ConfigKeyEventUseCaseImpl): ConfigKeyEventUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindDisplayAppShortcutsUseCase(impl: DisplayAppShortcutsUseCaseImpl): DisplayAppShortcutsUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindDisplayAppsUseCase(impl: DisplayAppsUseCaseImpl): DisplayAppsUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindTestActionUseCase(impl: TestActionUseCaseImpl): TestActionUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindCreateKeyMapShortcutUseCase(impl: CreateKeyMapShortcutUseCaseImpl): CreateKeyMapShortcutUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindSetupGuiKeyboardUseCase(impl: SetupGuiKeyboardUseCaseImpl): SetupGuiKeyboardUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindCreateActionUseCase(impl: CreateActionUseCaseImpl): CreateActionUseCase
+
+ @Binds
+ @ViewModelScoped
+ abstract fun bindCreateConstraintUseCase(impl: CreateConstraintUseCaseImpl): CreateConstraintUseCase
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/system/BootBroadcastReceiver.kt b/base/src/main/java/io/github/sds100/keymapper/base/BootBroadcastReceiver.kt
similarity index 61%
rename from app/src/main/java/io/github/sds100/keymapper/system/BootBroadcastReceiver.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/BootBroadcastReceiver.kt
index c61aab628b..247b5282bc 100644
--- a/app/src/main/java/io/github/sds100/keymapper/system/BootBroadcastReceiver.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/BootBroadcastReceiver.kt
@@ -1,20 +1,15 @@
-package io.github.sds100.keymapper.system
+package io.github.sds100.keymapper.base
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import io.github.sds100.keymapper.KeyMapperApp
-
-/**
- * Created by sds100 on 24/03/2019.
- */
class BootBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
context ?: return
if (intent?.action == Intent.ACTION_LOCKED_BOOT_COMPLETED) {
- (context.applicationContext as? KeyMapperApp)?.onBootUnlocked()
+ (context.applicationContext as? BaseKeyMapperApp)?.onBootUnlocked()
}
}
}
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/Constants.kt b/base/src/main/java/io/github/sds100/keymapper/base/Constants.kt
new file mode 100644
index 0000000000..d5f8c15086
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/Constants.kt
@@ -0,0 +1,8 @@
+package io.github.sds100.keymapper.base
+
+import android.os.Build
+
+object Constants {
+ const val MIN_API: Int = Build.VERSION_CODES.LOLLIPOP
+ const val MAX_API: Int = 1000
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/about/AboutFragment.kt b/base/src/main/java/io/github/sds100/keymapper/base/about/AboutFragment.kt
similarity index 81%
rename from app/src/main/java/io/github/sds100/keymapper/about/AboutFragment.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/about/AboutFragment.kt
index 7c994503ae..cabec8079b 100644
--- a/app/src/main/java/io/github/sds100/keymapper/about/AboutFragment.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/about/AboutFragment.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.about
+package io.github.sds100.keymapper.base.about
import android.os.Bundle
import android.view.LayoutInflater
@@ -10,15 +10,17 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
-import io.github.sds100.keymapper.Constants
-import io.github.sds100.keymapper.databinding.FragmentAboutBinding
-
-/**
- * Created by sds100 on 05/04/2020.
- */
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.databinding.FragmentAboutBinding
+import io.github.sds100.keymapper.common.BuildConfigProvider
+import javax.inject.Inject
+@AndroidEntryPoint
class AboutFragment : Fragment() {
+ @Inject
+ lateinit var buildConfigProvider: BuildConfigProvider
+
/**
* Scoped to the lifecycle of the fragment's view (between onCreateView and onDestroyView)
*/
@@ -57,7 +59,7 @@ class AboutFragment : Fragment() {
onBackPressed()
}
- version = "${Constants.VERSION} ${Constants.VERSION_CODE}"
+ version = "${buildConfigProvider.version} ${buildConfigProvider.versionCode}"
}
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/Action.kt
similarity index 94%
rename from app/src/main/java/io/github/sds100/keymapper/actions/Action.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/Action.kt
index b5a2a5db4e..ca071be8d2 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/Action.kt
@@ -1,21 +1,17 @@
-package io.github.sds100.keymapper.actions
-
+package io.github.sds100.keymapper.base.actions
+
+import io.github.sds100.keymapper.base.keymaps.KeyMap
+import io.github.sds100.keymapper.common.utils.hasFlag
+import io.github.sds100.keymapper.common.utils.success
+import io.github.sds100.keymapper.common.utils.then
+import io.github.sds100.keymapper.common.utils.valueOrNull
+import io.github.sds100.keymapper.common.utils.withFlag
import io.github.sds100.keymapper.data.entities.ActionEntity
import io.github.sds100.keymapper.data.entities.EntityExtra
import io.github.sds100.keymapper.data.entities.getData
-import io.github.sds100.keymapper.mappings.keymaps.KeyMap
-import io.github.sds100.keymapper.util.success
-import io.github.sds100.keymapper.util.then
-import io.github.sds100.keymapper.util.valueOrNull
import kotlinx.serialization.Serializable
-import splitties.bitflags.hasFlag
-import splitties.bitflags.withFlag
import java.util.UUID
-/**
- * Created by sds100 on 09/03/2021.
- */
-
@Serializable
data class Action(
val uid: String = UUID.randomUUID().toString(),
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionCategory.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionCategory.kt
similarity index 61%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionCategory.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionCategory.kt
index 6de92e797a..6b7cde4839 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionCategory.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionCategory.kt
@@ -1,14 +1,10 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
-/**
- * Created by sds100 on 23/03/2021.
- */
enum class ActionCategory {
APPS,
INPUT,
- CAMERA_SOUND,
+ FLASHLIGHT,
CONNECTIVITY,
- CONTENT,
NAVIGATION,
VOLUME,
DISPLAY,
@@ -17,4 +13,5 @@ enum class ActionCategory {
INTERFACE,
TELEPHONY,
NOTIFICATIONS,
+ SPECIAL,
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt
similarity index 97%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt
index 7889ee4f2a..3923f4d70b 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt
@@ -1,9 +1,9 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
-import io.github.sds100.keymapper.actions.pinchscreen.PinchScreenType
-import io.github.sds100.keymapper.actions.uielement.NodeInteractionType
+import io.github.sds100.keymapper.common.utils.NodeInteractionType
+import io.github.sds100.keymapper.common.utils.Orientation
+import io.github.sds100.keymapper.common.utils.PinchScreenType
import io.github.sds100.keymapper.system.camera.CameraLens
-import io.github.sds100.keymapper.system.display.Orientation
import io.github.sds100.keymapper.system.intents.IntentExtraModel
import io.github.sds100.keymapper.system.intents.IntentTarget
import io.github.sds100.keymapper.system.network.HttpMethod
@@ -764,8 +764,21 @@ sealed class ActionData : Comparable {
}
@Serializable
- data object MoveCursorToEnd : ActionData() {
- override val id = ActionId.MOVE_CURSOR_TO_END
+ data class MoveCursor(val moveType: Type, val direction: Direction) : ActionData() {
+ override val id = ActionId.MOVE_CURSOR
+
+ enum class Type {
+ CHAR,
+ WORD,
+ LINE,
+ PARAGRAPH,
+ PAGE,
+ }
+
+ enum class Direction {
+ START,
+ END,
+ }
}
@Serializable
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt
similarity index 91%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt
index b56a2856ee..d9c0dd80cc 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt
@@ -1,8 +1,16 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import androidx.core.net.toUri
-import io.github.sds100.keymapper.actions.pinchscreen.PinchScreenType
-import io.github.sds100.keymapper.actions.uielement.NodeInteractionType
+import io.github.sds100.keymapper.common.utils.KMError
+import io.github.sds100.keymapper.common.utils.KMResult
+import io.github.sds100.keymapper.common.utils.NodeInteractionType
+import io.github.sds100.keymapper.common.utils.PinchScreenType
+import io.github.sds100.keymapper.common.utils.Success
+import io.github.sds100.keymapper.common.utils.getKey
+import io.github.sds100.keymapper.common.utils.hasFlag
+import io.github.sds100.keymapper.common.utils.success
+import io.github.sds100.keymapper.common.utils.then
+import io.github.sds100.keymapper.common.utils.valueOrNull
import io.github.sds100.keymapper.data.db.typeconverter.ConstantTypeConverters
import io.github.sds100.keymapper.data.db.typeconverter.NodeInteractionTypeSetTypeConverter
import io.github.sds100.keymapper.data.entities.ActionEntity
@@ -15,19 +23,7 @@ import io.github.sds100.keymapper.system.network.HttpMethod
import io.github.sds100.keymapper.system.volume.DndMode
import io.github.sds100.keymapper.system.volume.RingerMode
import io.github.sds100.keymapper.system.volume.VolumeStream
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.Success
-import io.github.sds100.keymapper.util.getKey
-import io.github.sds100.keymapper.util.success
-import io.github.sds100.keymapper.util.then
-import io.github.sds100.keymapper.util.valueOrNull
import kotlinx.serialization.json.Json
-import splitties.bitflags.hasFlag
-
-/**
- * Created by sds100 on 13/03/2021.
- */
object ActionDataEntityMapper {
@@ -502,7 +498,6 @@ object ActionDataEntityMapper {
ActionId.DISABLE_NFC -> ActionData.Nfc.Disable
ActionId.TOGGLE_NFC -> ActionData.Nfc.Toggle
- ActionId.MOVE_CURSOR_TO_END -> ActionData.MoveCursorToEnd
ActionId.TOGGLE_KEYBOARD -> ActionData.ToggleKeyboard
ActionId.SHOW_KEYBOARD -> ActionData.ShowKeyboard
ActionId.HIDE_KEYBOARD -> ActionData.HideKeyboard
@@ -610,13 +605,46 @@ object ActionDataEntityMapper {
nodeActions = actions,
)
}
+
+ ActionId.MOVE_CURSOR -> {
+ // For compatibility with the old "Move cursor to the end" action.
+ if (entity.extras.isEmpty()) {
+ return ActionData.MoveCursor(
+ moveType = ActionData.MoveCursor.Type.PAGE,
+ ActionData.MoveCursor.Direction.END,
+ )
+ }
+
+ val type =
+ entity.extras.getData(ActionEntity.EXTRA_MOVE_CURSOR_TYPE).then { value ->
+ when (value) {
+ ActionEntity.CURSOR_TYPE_CHAR -> Success(ActionData.MoveCursor.Type.CHAR)
+ ActionEntity.CURSOR_TYPE_WORD -> Success(ActionData.MoveCursor.Type.WORD)
+ ActionEntity.CURSOR_TYPE_LINE -> Success(ActionData.MoveCursor.Type.LINE)
+ ActionEntity.CURSOR_TYPE_PARAGRAPH -> Success(ActionData.MoveCursor.Type.PARAGRAPH)
+ ActionEntity.CURSOR_TYPE_PAGE -> Success(ActionData.MoveCursor.Type.PAGE)
+ else -> KMError.Exception(IllegalArgumentException("Unknown move cursor type: $value"))
+ }
+ }.valueOrNull() ?: return null
+
+ val direction =
+ entity.extras.getData(ActionEntity.EXTRA_MOVE_CURSOR_DIRECTION).then { value ->
+ when (value) {
+ ActionEntity.CURSOR_DIRECTION_START -> Success(ActionData.MoveCursor.Direction.START)
+ ActionEntity.CURSOR_DIRECTION_END -> Success(ActionData.MoveCursor.Direction.END)
+ else -> KMError.Exception(IllegalArgumentException("Unknown move cursor direction: $value"))
+ }
+ }.valueOrNull() ?: return null
+
+ ActionData.MoveCursor(moveType = type, direction = direction)
+ }
}
}
- private fun convertNodeInteractionType(string: String): Result = try {
+ private fun convertNodeInteractionType(string: String): KMResult = try {
Success(NodeInteractionType.valueOf(string))
} catch (e: IllegalArgumentException) {
- Error.Exception(e)
+ KMError.Exception(e)
}
fun toEntity(data: ActionData): ActionEntity {
@@ -922,6 +950,23 @@ object ActionDataEntityMapper {
}
}
+ is ActionData.MoveCursor -> buildList {
+ val typeString = when (data.moveType) {
+ ActionData.MoveCursor.Type.CHAR -> ActionEntity.CURSOR_TYPE_CHAR
+ ActionData.MoveCursor.Type.WORD -> ActionEntity.CURSOR_TYPE_WORD
+ ActionData.MoveCursor.Type.LINE -> ActionEntity.CURSOR_TYPE_LINE
+ ActionData.MoveCursor.Type.PARAGRAPH -> ActionEntity.CURSOR_TYPE_PARAGRAPH
+ ActionData.MoveCursor.Type.PAGE -> ActionEntity.CURSOR_TYPE_PAGE
+ }
+ add(EntityExtra(ActionEntity.EXTRA_MOVE_CURSOR_TYPE, typeString))
+
+ val directionString = when (data.direction) {
+ ActionData.MoveCursor.Direction.START -> ActionEntity.CURSOR_DIRECTION_START
+ ActionData.MoveCursor.Direction.END -> ActionEntity.CURSOR_DIRECTION_END
+ }
+ add(EntityExtra(ActionEntity.EXTRA_MOVE_CURSOR_DIRECTION, directionString))
+ }
+
else -> emptyList()
}
@@ -1055,7 +1100,9 @@ object ActionDataEntityMapper {
ActionId.DISABLE_NFC to "nfc_disable",
ActionId.TOGGLE_NFC to "nfc_toggle",
- ActionId.MOVE_CURSOR_TO_END to "move_cursor_to_end",
+ // This action used to just move the cursor to the end. Do not change the
+ // id for compatibility reasons.
+ ActionId.MOVE_CURSOR to "move_cursor_to_end",
ActionId.TOGGLE_KEYBOARD to "toggle_keyboard",
ActionId.SHOW_KEYBOARD to "show_keyboard",
ActionId.HIDE_KEYBOARD to "hide_keyboard",
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionErrorSnapshot.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt
similarity index 60%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionErrorSnapshot.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt
index 11c4129b8c..fb2d13bde6 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionErrorSnapshot.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionErrorSnapshot.kt
@@ -1,19 +1,23 @@
-package io.github.sds100.keymapper.actions
-
-import io.github.sds100.keymapper.actions.sound.SoundsManager
-import io.github.sds100.keymapper.shizuku.ShizukuAdapter
+package io.github.sds100.keymapper.base.actions
+
+import io.github.sds100.keymapper.base.actions.sound.SoundsManager
+import io.github.sds100.keymapper.base.system.inputmethod.KeyMapperImeHelper
+import io.github.sds100.keymapper.common.BuildConfigProvider
+import io.github.sds100.keymapper.common.utils.KMError
+import io.github.sds100.keymapper.common.utils.onFailure
+import io.github.sds100.keymapper.common.utils.onSuccess
+import io.github.sds100.keymapper.common.utils.valueOrNull
+import io.github.sds100.keymapper.system.SystemError
import io.github.sds100.keymapper.system.apps.PackageManagerAdapter
import io.github.sds100.keymapper.system.camera.CameraAdapter
import io.github.sds100.keymapper.system.camera.CameraLens
+import io.github.sds100.keymapper.system.inputmethod.ImeInfo
import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter
-import io.github.sds100.keymapper.system.inputmethod.KeyMapperImeHelper
import io.github.sds100.keymapper.system.permissions.Permission
import io.github.sds100.keymapper.system.permissions.PermissionAdapter
import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter
import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.onFailure
-import io.github.sds100.keymapper.util.onSuccess
+import io.github.sds100.keymapper.system.shizuku.ShizukuAdapter
class LazyActionErrorSnapshot(
private val packageManager: PackageManagerAdapter,
@@ -24,13 +28,15 @@ class LazyActionErrorSnapshot(
private val soundsManager: SoundsManager,
shizukuAdapter: ShizukuAdapter,
private val ringtoneAdapter: RingtoneAdapter,
+ private val buildConfigProvider: BuildConfigProvider,
) : ActionErrorSnapshot,
IsActionSupportedUseCase by IsActionSupportedUseCaseImpl(
systemFeatureAdapter,
cameraAdapter,
permissionAdapter,
) {
- private val keyMapperImeHelper = KeyMapperImeHelper(inputMethodAdapter)
+ private val keyMapperImeHelper =
+ KeyMapperImeHelper(inputMethodAdapter, buildConfigProvider.packageName)
private val isCompatibleImeEnabled by lazy { keyMapperImeHelper.isCompatibleImeEnabled() }
private val isCompatibleImeChosen by lazy { keyMapperImeHelper.isCompatibleImeChosen() }
@@ -50,7 +56,40 @@ class LazyActionErrorSnapshot(
}
}
- override fun getError(action: ActionData): Error? {
+ override fun getErrors(actions: List): Map {
+ // Fixes #797 and #1719
+ // Store which input method would be selected if the actions run successfully.
+ // Errors should not be thrown for actions that will be fixed by previous ones.
+ var currentImeFromActions: ImeInfo? = null
+
+ val errorMap = mutableMapOf()
+
+ for (action in actions) {
+ if (action is ActionData.SwitchKeyboard) {
+ currentImeFromActions = inputMethodAdapter.getInfoById(action.imeId).valueOrNull()
+ }
+
+ var error = getError(action)
+
+ if (error == KMError.NoCompatibleImeChosen && currentImeFromActions != null) {
+ val isCurrentImeCompatible =
+ KeyMapperImeHelper.isKeyMapperInputMethod(
+ currentImeFromActions.packageName,
+ buildConfigProvider.packageName,
+ )
+
+ if (isCurrentImeCompatible) {
+ error = null
+ }
+ }
+
+ errorMap[action] = error
+ }
+
+ return errorMap
+ }
+
+ override fun getError(action: ActionData): KMError? {
val isSupportedError = isSupported(action.id)
if (isSupportedError != null) {
@@ -60,26 +99,26 @@ class LazyActionErrorSnapshot(
if (action.canUseShizukuToPerform() && isShizukuInstalled) {
if (!(action.canUseImeToPerform() && isCompatibleImeChosen)) {
when {
- !isShizukuStarted -> return Error.ShizukuNotStarted
+ !isShizukuStarted -> return KMError.ShizukuNotStarted
- !isPermissionGranted(Permission.SHIZUKU) -> return Error.PermissionDenied(
+ !isPermissionGranted(Permission.SHIZUKU) -> return SystemError.PermissionDenied(
Permission.SHIZUKU,
)
}
}
} else if (action.canUseImeToPerform()) {
if (!isCompatibleImeEnabled) {
- return Error.NoCompatibleImeEnabled
+ return KMError.NoCompatibleImeEnabled
}
if (!isCompatibleImeChosen) {
- return Error.NoCompatibleImeChosen
+ return KMError.NoCompatibleImeChosen
}
}
- ActionUtils.getRequiredPermissions(action.id).forEach { permission ->
+ for (permission in ActionUtils.getRequiredPermissions(action.id)) {
if (!isPermissionGranted(permission)) {
- return Error.PermissionDenied(permission)
+ return SystemError.PermissionDenied(permission)
}
}
@@ -98,7 +137,7 @@ class LazyActionErrorSnapshot(
if (
action.useShell && !isPermissionGranted(Permission.ROOT)
) {
- return Error.PermissionDenied(Permission.ROOT)
+ return SystemError.PermissionDenied(Permission.ROOT)
}
is ActionData.Sound.SoundFile -> {
@@ -109,21 +148,21 @@ class LazyActionErrorSnapshot(
is ActionData.Sound.Ringtone -> {
if (!ringtoneAdapter.exists(action.uri)) {
- return Error.CantFindSoundFile
+ return KMError.CantFindSoundFile
}
}
is ActionData.VoiceAssistant -> {
if (!isVoiceAssistantInstalled) {
- return Error.NoVoiceAssistant
+ return KMError.NoVoiceAssistant
}
}
is ActionData.Flashlight ->
if (!flashLenses.contains(action.lens)) {
return when (action.lens) {
- CameraLens.FRONT -> Error.FrontFlashNotFound
- CameraLens.BACK -> Error.BackFlashNotFound
+ CameraLens.FRONT -> KMError.FrontFlashNotFound
+ CameraLens.BACK -> KMError.BackFlashNotFound
}
}
@@ -138,15 +177,15 @@ class LazyActionErrorSnapshot(
return null
}
- private fun getAppError(packageName: String): Error? {
+ private fun getAppError(packageName: String): KMError? {
packageManager.isAppEnabled(packageName).onSuccess { isEnabled ->
if (!isEnabled) {
- return Error.AppDisabled(packageName)
+ return KMError.AppDisabled(packageName)
}
}
if (!packageManager.isAppInstalled(packageName)) {
- return Error.AppNotFound(packageName)
+ return KMError.AppNotFound(packageName)
}
return null
@@ -164,5 +203,6 @@ class LazyActionErrorSnapshot(
}
interface ActionErrorSnapshot {
- fun getError(action: ActionData): Error?
+ fun getError(action: ActionData): KMError?
+ fun getErrors(actions: List): Map
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt
similarity index 95%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt
index 5b93ee03a7..03798087ed 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt
@@ -1,21 +1,18 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
-/**
- * Created by sds100 on 15/03/2021.
- */
enum class ActionId {
APP,
APP_SHORTCUT,
KEY_CODE,
KEY_EVENT,
+ TEXT,
TAP_SCREEN,
SWIPE_SCREEN,
PINCH_SCREEN,
- TEXT,
URL,
+ HTTP_REQUEST,
INTENT,
PHONE_CALL,
- SOUND,
INTERACT_UI_ELEMENT,
TOGGLE_WIFI,
@@ -65,6 +62,7 @@ enum class ActionId {
TOGGLE_QUICK_SETTINGS,
COLLAPSE_STATUS_BAR,
+ SOUND,
PAUSE_MEDIA,
PAUSE_MEDIA_PACKAGE,
PLAY_MEDIA,
@@ -102,15 +100,15 @@ enum class ActionId {
DISABLE_NFC,
TOGGLE_NFC,
- MOVE_CURSOR_TO_END,
- TOGGLE_KEYBOARD,
- SHOW_KEYBOARD,
- HIDE_KEYBOARD,
- SHOW_KEYBOARD_PICKER,
TEXT_CUT,
TEXT_COPY,
TEXT_PASTE,
+ MOVE_CURSOR,
SELECT_WORD_AT_CURSOR,
+ TOGGLE_KEYBOARD,
+ SHOW_KEYBOARD,
+ HIDE_KEYBOARD,
+ SHOW_KEYBOARD_PICKER,
SWITCH_KEYBOARD,
@@ -136,6 +134,4 @@ enum class ActionId {
ANSWER_PHONE_CALL,
END_PHONE_CALL,
DEVICE_CONTROLS,
-
- HTTP_REQUEST,
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionListItem.kt
similarity index 97%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionListItem.kt
index 92276cfeb8..f842381aae 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionListItem.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
@@ -43,11 +43,11 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.accompanist.drawablepainter.rememberDrawablePainter
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.util.drawable
-import io.github.sds100.keymapper.util.ui.LinkType
-import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
-import io.github.sds100.keymapper.util.ui.compose.DragDropState
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.utils.ui.LinkType
+import io.github.sds100.keymapper.base.utils.ui.compose.ComposeIconInfo
+import io.github.sds100.keymapper.base.utils.ui.compose.DragDropState
+import io.github.sds100.keymapper.base.utils.ui.drawable
@Composable
fun ActionListItem(
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionOptionsBottomSheet.kt
similarity index 96%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionOptionsBottomSheet.kt
index 4a8de35bf8..fac1d4ae7c 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionOptionsBottomSheet.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionOptionsBottomSheet.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -37,15 +37,15 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.util.ui.SliderMaximums
-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.RadioButtonText
-import io.github.sds100.keymapper.util.ui.compose.SliderOptionText
-import io.github.sds100.keymapper.util.ui.compose.openUriSafe
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.utils.ui.SliderMaximums
+import io.github.sds100.keymapper.base.utils.ui.SliderMinimums
+import io.github.sds100.keymapper.base.utils.ui.SliderStepSizes
+import io.github.sds100.keymapper.base.utils.ui.compose.CheckBoxText
+import io.github.sds100.keymapper.base.utils.ui.compose.RadioButtonText
+import io.github.sds100.keymapper.base.utils.ui.compose.SliderOptionText
+import io.github.sds100.keymapper.base.utils.ui.compose.openUriSafe
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt
similarity index 89%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt
index 77515ce344..64cd9d1d3a 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt
@@ -1,27 +1,27 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import android.os.Build
import android.view.KeyEvent
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Android
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.actions.pinchscreen.PinchScreenType
-import io.github.sds100.keymapper.mappings.keymaps.KeyMap
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.keymaps.KeyMap
+import io.github.sds100.keymapper.base.utils.DndModeStrings
+import io.github.sds100.keymapper.base.utils.InputEventStrings
+import io.github.sds100.keymapper.base.utils.RingerModeStrings
+import io.github.sds100.keymapper.base.utils.VolumeStreamStrings
+import io.github.sds100.keymapper.base.utils.ui.IconInfo
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.TintType
+import io.github.sds100.keymapper.base.utils.ui.compose.ComposeIconInfo
+import io.github.sds100.keymapper.common.utils.Orientation
+import io.github.sds100.keymapper.common.utils.PinchScreenType
+import io.github.sds100.keymapper.common.utils.handle
+import io.github.sds100.keymapper.common.utils.hasFlag
+import io.github.sds100.keymapper.common.utils.toPercentString
import io.github.sds100.keymapper.system.camera.CameraLens
import io.github.sds100.keymapper.system.devices.InputDeviceUtils
-import io.github.sds100.keymapper.system.display.OrientationUtils
-import io.github.sds100.keymapper.system.inputevents.InputEventUtils
import io.github.sds100.keymapper.system.intents.IntentTarget
-import io.github.sds100.keymapper.system.volume.DndModeUtils
-import io.github.sds100.keymapper.system.volume.RingerModeUtils
-import io.github.sds100.keymapper.system.volume.VolumeStreamUtils
-import io.github.sds100.keymapper.util.handle
-import io.github.sds100.keymapper.util.toPercentString
-import io.github.sds100.keymapper.util.ui.IconInfo
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.TintType
-import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
-import splitties.bitflags.hasFlag
class ActionUiHelper(
displayActionUseCase: DisplayActionUseCase,
@@ -50,9 +50,9 @@ class ActionUiHelper(
getString(R.string.description_keyevent_through_shell, keyCodeString)
} else {
val metaStateString = buildString {
- InputEventUtils.MODIFIER_LABELS.entries.forEach {
- val modifier = it.key
- val labelRes = it.value
+ for (label in InputEventStrings.MODIFIER_LABELS.entries) {
+ val modifier = label.key
+ val labelRes = label.value
if (action.metaState.hasFlag(modifier)) {
append("${getString(labelRes)} + ")
@@ -90,7 +90,7 @@ class ActionUiHelper(
}
is ActionData.DoNotDisturb.Enable -> {
- val dndModeString = getString(DndModeUtils.getLabel(action.dndMode))
+ val dndModeString = getString(DndModeStrings.getLabel(action.dndMode))
getString(
R.string.action_enable_dnd_mode_formatted,
dndModeString,
@@ -98,7 +98,7 @@ class ActionUiHelper(
}
is ActionData.DoNotDisturb.Toggle -> {
- val dndModeString = getString(DndModeUtils.getLabel(action.dndMode))
+ val dndModeString = getString(DndModeStrings.getLabel(action.dndMode))
getString(
R.string.action_toggle_dnd_mode_formatted,
dndModeString,
@@ -108,7 +108,7 @@ class ActionUiHelper(
ActionData.DoNotDisturb.Disable -> getString(R.string.action_disable_dnd_mode)
is ActionData.Volume.SetRingerMode -> {
- val ringerModeString = getString(RingerModeUtils.getLabel(action.ringerMode))
+ val ringerModeString = getString(RingerModeStrings.getLabel(action.ringerMode))
getString(R.string.action_change_ringer_mode_formatted, ringerModeString)
}
@@ -120,7 +120,7 @@ class ActionUiHelper(
when (action) {
is ActionData.Volume.Stream -> {
val streamString = getString(
- VolumeStreamUtils.getLabel(action.volumeStream),
+ VolumeStreamStrings.getLabel(action.volumeStream),
)
if (action.showVolumeUi) {
@@ -190,7 +190,7 @@ class ActionUiHelper(
is ActionData.Volume.SetRingerMode -> {
val ringerModeString =
- getString(RingerModeUtils.getLabel(action.ringerMode))
+ getString(RingerModeStrings.getLabel(action.ringerMode))
string = getString(
R.string.action_change_ringer_mode_formatted,
@@ -492,7 +492,29 @@ class ActionUiHelper(
ActionData.MobileData.Enable -> getString(R.string.action_enable_mobile_data)
ActionData.MobileData.Toggle -> getString(R.string.action_toggle_mobile_data)
- ActionData.MoveCursorToEnd -> getString(R.string.action_move_to_end_of_text)
+ is ActionData.MoveCursor -> {
+ when (action.direction) {
+ ActionData.MoveCursor.Direction.START -> {
+ when (action.moveType) {
+ ActionData.MoveCursor.Type.CHAR -> getString(R.string.action_move_cursor_prev_character)
+ ActionData.MoveCursor.Type.WORD -> getString(R.string.action_move_cursor_start_word)
+ ActionData.MoveCursor.Type.LINE -> getString(R.string.action_move_cursor_start_line)
+ ActionData.MoveCursor.Type.PARAGRAPH -> getString(R.string.action_move_cursor_start_paragraph)
+ ActionData.MoveCursor.Type.PAGE -> getString(R.string.action_move_cursor_start_page)
+ }
+ }
+
+ ActionData.MoveCursor.Direction.END -> {
+ when (action.moveType) {
+ ActionData.MoveCursor.Type.CHAR -> getString(R.string.action_move_cursor_next_character)
+ ActionData.MoveCursor.Type.WORD -> getString(R.string.action_move_cursor_end_word)
+ ActionData.MoveCursor.Type.LINE -> getString(R.string.action_move_cursor_end_line)
+ ActionData.MoveCursor.Type.PARAGRAPH -> getString(R.string.action_move_cursor_end_paragraph)
+ ActionData.MoveCursor.Type.PAGE -> getString(R.string.action_move_cursor_end_page)
+ }
+ }
+ }
+ }
ActionData.Nfc.Disable -> getString(R.string.action_nfc_disable)
ActionData.Nfc.Enable -> getString(R.string.action_nfc_enable)
@@ -502,8 +524,13 @@ class ActionUiHelper(
ActionData.OpenSettings -> getString(R.string.action_open_settings)
is ActionData.Rotation.CycleRotations -> {
- val orientationStrings = action.orientations.map {
- getString(OrientationUtils.getLabel(it))
+ val orientationStrings = action.orientations.map { orientation ->
+ when (orientation) {
+ Orientation.ORIENTATION_0 -> R.string.orientation_0
+ Orientation.ORIENTATION_90 -> R.string.orientation_90
+ Orientation.ORIENTATION_180 -> R.string.orientation_180
+ Orientation.ORIENTATION_270 -> R.string.orientation_270
+ }
}
getString(
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt
similarity index 95%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt
index 7cff7aeb8d..903c562c44 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import android.content.pm.PackageManager
import android.os.Build
@@ -71,22 +71,18 @@ import androidx.compose.material.icons.rounded.ContentPaste
import androidx.compose.material.icons.rounded.Wifi
import androidx.compose.material.icons.rounded.WifiOff
import androidx.compose.ui.graphics.vector.ImageVector
-import io.github.sds100.keymapper.Constants
-import io.github.sds100.keymapper.R
+import io.github.sds100.keymapper.base.Constants
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.HomeIotDevice
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.InstantMix
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.JumpToElement
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.KeyMapperIcons
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.MatchWord
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.NfcOff
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.TextSelectEnd
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.TopPanelClose
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.TopPanelOpen
import io.github.sds100.keymapper.system.permissions.Permission
-import io.github.sds100.keymapper.util.ui.compose.icons.HomeIotDevice
-import io.github.sds100.keymapper.util.ui.compose.icons.InstantMix
-import io.github.sds100.keymapper.util.ui.compose.icons.JumpToElement
-import io.github.sds100.keymapper.util.ui.compose.icons.KeyMapperIcons
-import io.github.sds100.keymapper.util.ui.compose.icons.MatchWord
-import io.github.sds100.keymapper.util.ui.compose.icons.NfcOff
-import io.github.sds100.keymapper.util.ui.compose.icons.TextSelectEnd
-import io.github.sds100.keymapper.util.ui.compose.icons.TopPanelClose
-import io.github.sds100.keymapper.util.ui.compose.icons.TopPanelOpen
-
-/**
- * Created by sds100 on 16/03/2021.
- */
object ActionUtils {
@@ -98,17 +94,16 @@ object ActionUtils {
ActionCategory.KEYBOARD -> R.string.action_cat_keyboard
ActionCategory.APPS -> R.string.action_cat_apps
ActionCategory.INPUT -> R.string.action_cat_input
- ActionCategory.CAMERA_SOUND -> R.string.action_cat_camera_sound
+ ActionCategory.FLASHLIGHT -> R.string.action_cat_flashlight
ActionCategory.CONNECTIVITY -> R.string.action_cat_connectivity
- ActionCategory.CONTENT -> R.string.action_cat_content
ActionCategory.INTERFACE -> R.string.action_cat_interface
ActionCategory.TELEPHONY -> R.string.action_cat_telephony
ActionCategory.DISPLAY -> R.string.action_cat_display
ActionCategory.NOTIFICATIONS -> R.string.action_cat_notifications
+ ActionCategory.SPECIAL -> R.string.action_cat_special
}
fun getCategory(id: ActionId): ActionCategory = when (id) {
- ActionId.CONSUME_KEY_EVENT -> ActionCategory.INPUT
ActionId.KEY_CODE -> ActionCategory.INPUT
ActionId.KEY_EVENT -> ActionCategory.INPUT
ActionId.TAP_SCREEN -> ActionCategory.INPUT
@@ -123,6 +118,8 @@ object ActionUtils {
ActionId.APP -> ActionCategory.APPS
ActionId.APP_SHORTCUT -> ActionCategory.APPS
ActionId.INTENT -> ActionCategory.APPS
+ ActionId.URL -> ActionCategory.APPS
+ ActionId.HTTP_REQUEST -> ActionCategory.APPS
ActionId.TOGGLE_WIFI -> ActionCategory.CONNECTIVITY
ActionId.ENABLE_WIFI -> ActionCategory.CONNECTIVITY
@@ -141,6 +138,7 @@ object ActionUtils {
ActionId.ENABLE_AUTO_BRIGHTNESS -> ActionCategory.DISPLAY
ActionId.INCREASE_BRIGHTNESS -> ActionCategory.DISPLAY
ActionId.DECREASE_BRIGHTNESS -> ActionCategory.DISPLAY
+ ActionId.SCREENSHOT -> ActionCategory.DISPLAY
ActionId.TOGGLE_AUTO_ROTATE -> ActionCategory.INTERFACE
ActionId.ENABLE_AUTO_ROTATE -> ActionCategory.INTERFACE
@@ -171,6 +169,7 @@ object ActionUtils {
ActionId.TOGGLE_QUICK_SETTINGS -> ActionCategory.NAVIGATION
ActionId.COLLAPSE_STATUS_BAR -> ActionCategory.NAVIGATION
+ ActionId.SOUND -> ActionCategory.MEDIA
ActionId.PAUSE_MEDIA -> ActionCategory.MEDIA
ActionId.PAUSE_MEDIA_PACKAGE -> ActionCategory.MEDIA
ActionId.PLAY_MEDIA -> ActionCategory.MEDIA
@@ -199,11 +198,10 @@ object ActionUtils {
ActionId.GO_LAST_APP -> ActionCategory.NAVIGATION
ActionId.OPEN_MENU -> ActionCategory.NAVIGATION
- ActionId.TOGGLE_FLASHLIGHT -> ActionCategory.CAMERA_SOUND
- ActionId.ENABLE_FLASHLIGHT -> ActionCategory.CAMERA_SOUND
- ActionId.DISABLE_FLASHLIGHT -> ActionCategory.CAMERA_SOUND
- ActionId.CHANGE_FLASHLIGHT_STRENGTH -> ActionCategory.CAMERA_SOUND
- ActionId.SOUND -> ActionCategory.CAMERA_SOUND
+ ActionId.TOGGLE_FLASHLIGHT -> ActionCategory.FLASHLIGHT
+ ActionId.ENABLE_FLASHLIGHT -> ActionCategory.FLASHLIGHT
+ ActionId.DISABLE_FLASHLIGHT -> ActionCategory.FLASHLIGHT
+ ActionId.CHANGE_FLASHLIGHT_STRENGTH -> ActionCategory.FLASHLIGHT
ActionId.ENABLE_NFC -> ActionCategory.CONNECTIVITY
ActionId.DISABLE_NFC -> ActionCategory.CONNECTIVITY
@@ -213,7 +211,10 @@ object ActionUtils {
ActionId.ENABLE_AIRPLANE_MODE -> ActionCategory.CONNECTIVITY
ActionId.DISABLE_AIRPLANE_MODE -> ActionCategory.CONNECTIVITY
- ActionId.MOVE_CURSOR_TO_END -> ActionCategory.KEYBOARD
+ ActionId.TEXT_CUT -> ActionCategory.KEYBOARD
+ ActionId.TEXT_COPY -> ActionCategory.KEYBOARD
+ ActionId.TEXT_PASTE -> ActionCategory.KEYBOARD
+ ActionId.MOVE_CURSOR -> ActionCategory.KEYBOARD
ActionId.TOGGLE_KEYBOARD -> ActionCategory.KEYBOARD
ActionId.SHOW_KEYBOARD -> ActionCategory.KEYBOARD
ActionId.HIDE_KEYBOARD -> ActionCategory.KEYBOARD
@@ -226,13 +227,6 @@ object ActionUtils {
ActionId.SECURE_LOCK_DEVICE -> ActionCategory.INTERFACE
ActionId.SHOW_POWER_MENU -> ActionCategory.INTERFACE
- ActionId.TEXT_CUT -> ActionCategory.CONTENT
- ActionId.TEXT_COPY -> ActionCategory.CONTENT
- ActionId.TEXT_PASTE -> ActionCategory.CONTENT
- ActionId.SCREENSHOT -> ActionCategory.CONTENT
- ActionId.URL -> ActionCategory.CONTENT
- ActionId.HTTP_REQUEST -> ActionCategory.CONTENT
-
ActionId.PHONE_CALL -> ActionCategory.TELEPHONY
ActionId.ANSWER_PHONE_CALL -> ActionCategory.TELEPHONY
ActionId.END_PHONE_CALL -> ActionCategory.TELEPHONY
@@ -242,6 +236,8 @@ object ActionUtils {
ActionId.DEVICE_CONTROLS -> ActionCategory.APPS
ActionId.INTERACT_UI_ELEMENT -> ActionCategory.APPS
+
+ ActionId.CONSUME_KEY_EVENT -> ActionCategory.SPECIAL
}
@StringRes
@@ -319,7 +315,7 @@ object ActionUtils {
ActionId.ENABLE_NFC -> R.string.action_nfc_enable
ActionId.DISABLE_NFC -> R.string.action_nfc_disable
ActionId.TOGGLE_NFC -> R.string.action_nfc_toggle
- ActionId.MOVE_CURSOR_TO_END -> R.string.action_move_to_end_of_text
+ ActionId.MOVE_CURSOR -> R.string.action_move_cursor
ActionId.TOGGLE_KEYBOARD -> R.string.action_toggle_keyboard
ActionId.SHOW_KEYBOARD -> R.string.action_show_keyboard
ActionId.HIDE_KEYBOARD -> R.string.action_hide_keyboard
@@ -438,7 +434,7 @@ object ActionUtils {
ActionId.ENABLE_NFC -> R.drawable.ic_outline_nfc_24
ActionId.DISABLE_NFC -> R.drawable.ic_nfc_off
ActionId.TOGGLE_NFC -> R.drawable.ic_outline_nfc_24
- ActionId.MOVE_CURSOR_TO_END -> R.drawable.ic_cursor
+ ActionId.MOVE_CURSOR -> R.drawable.ic_cursor
ActionId.TOGGLE_KEYBOARD -> R.drawable.ic_outline_keyboard_24
ActionId.SHOW_KEYBOARD -> R.drawable.ic_outline_keyboard_24
ActionId.HIDE_KEYBOARD -> R.drawable.ic_outline_keyboard_hide_24
@@ -652,7 +648,7 @@ object ActionUtils {
ActionId.TOGGLE_AIRPLANE_MODE,
ActionId.ENABLE_AIRPLANE_MODE,
ActionId.DISABLE_AIRPLANE_MODE,
- -> Permission.ROOT
+ -> return listOf(Permission.ROOT)
ActionId.SCREENSHOT -> if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return listOf(Permission.ROOT)
@@ -667,8 +663,7 @@ object ActionUtils {
ActionId.DISMISS_ALL_NOTIFICATIONS,
ActionId.DISMISS_MOST_RECENT_NOTIFICATION,
- ->
- return listOf(Permission.NOTIFICATION_LISTENER)
+ -> return listOf(Permission.NOTIFICATION_LISTENER)
ActionId.ANSWER_PHONE_CALL,
ActionId.END_PHONE_CALL,
@@ -681,7 +676,7 @@ object ActionUtils {
return listOf(Permission.FIND_NEARBY_DEVICES)
}
- else -> Unit
+ else -> return emptyList()
}
return emptyList()
@@ -761,7 +756,7 @@ object ActionUtils {
ActionId.ENABLE_NFC -> Icons.Outlined.Nfc
ActionId.DISABLE_NFC -> KeyMapperIcons.NfcOff
ActionId.TOGGLE_NFC -> Icons.Outlined.Nfc
- ActionId.MOVE_CURSOR_TO_END -> KeyMapperIcons.TextSelectEnd
+ ActionId.MOVE_CURSOR -> KeyMapperIcons.TextSelectEnd
ActionId.TOGGLE_KEYBOARD -> Icons.Outlined.Keyboard
ActionId.SHOW_KEYBOARD -> Icons.Outlined.Keyboard
ActionId.HIDE_KEYBOARD -> Icons.Outlined.KeyboardHide
@@ -815,13 +810,11 @@ fun ActionData.canBeHeldDown(): Boolean = when (this) {
fun ActionData.canUseImeToPerform(): Boolean = when (this) {
is ActionData.InputKeyEvent -> !useShell
is ActionData.Text -> true
- is ActionData.MoveCursorToEnd -> true
else -> false
}
fun ActionData.canUseShizukuToPerform(): Boolean = when (this) {
is ActionData.InputKeyEvent -> true
- is ActionData.MoveCursorToEnd -> true
else -> false
}
@@ -855,6 +848,7 @@ fun ActionData.isEditable(): Boolean = when (this) {
is ActionData.PhoneCall,
is ActionData.HttpRequest,
is ActionData.InteractUiElement,
+ is ActionData.MoveCursor,
-> true
else -> false
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionsScreen.kt
similarity index 93%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ActionsScreen.kt
index 6e4126b876..14eadb9da1 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionsScreen.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -37,16 +37,16 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
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.mappings.keymaps.ShortcutModel
-import io.github.sds100.keymapper.mappings.keymaps.ShortcutRow
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.keymaps.ShortcutModel
+import io.github.sds100.keymapper.base.keymaps.ShortcutRow
+import io.github.sds100.keymapper.base.utils.ui.LinkType
+import io.github.sds100.keymapper.base.utils.ui.compose.ComposeIconInfo
+import io.github.sds100.keymapper.base.utils.ui.compose.DraggableItem
+import io.github.sds100.keymapper.base.utils.ui.compose.rememberDragDropState
+import io.github.sds100.keymapper.common.utils.State
import io.github.sds100.keymapper.system.camera.CameraLens
-import io.github.sds100.keymapper.util.State
-import io.github.sds100.keymapper.util.ui.LinkType
-import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
-import io.github.sds100.keymapper.util.ui.compose.DraggableItem
-import io.github.sds100.keymapper.util.ui.compose.rememberDragDropState
import kotlinx.coroutines.flow.update
@OptIn(ExperimentalMaterial3Api::class)
@@ -125,7 +125,7 @@ private fun ActionsScreen(
State.Loading -> Loading()
is State.Data -> Surface(modifier = modifier) {
Column {
- when (state.data) {
+ when (val data = state.data) {
is ConfigActionsState.Empty -> {
Column(
modifier = Modifier.weight(1f),
@@ -140,7 +140,7 @@ private fun ActionsScreen(
textAlign = TextAlign.Center,
)
- if (state.data.shortcuts.isNotEmpty()) {
+ if (data.shortcuts.isNotEmpty()) {
Text(
text = stringResource(R.string.recently_used_actions),
style = MaterialTheme.typography.titleSmall,
@@ -152,7 +152,7 @@ private fun ActionsScreen(
modifier = Modifier
.padding(horizontal = 32.dp)
.fillMaxWidth(),
- shortcuts = state.data.shortcuts,
+ shortcuts = data.shortcuts,
onClick = onClickShortcut,
)
}
@@ -162,7 +162,7 @@ private fun ActionsScreen(
is ConfigActionsState.Loaded -> {
Spacer(Modifier.height(8.dp))
- if (state.data.actions.isNotEmpty()) {
+ if (data.actions.isNotEmpty()) {
Spacer(Modifier.height(8.dp))
Text(
@@ -176,9 +176,9 @@ private fun ActionsScreen(
ActionList(
modifier = Modifier.weight(1f),
- actionList = state.data.actions,
- shortcuts = state.data.shortcuts,
- isReorderingEnabled = state.data.isReorderingEnabled,
+ actionList = data.actions,
+ shortcuts = data.shortcuts,
+ isReorderingEnabled = data.isReorderingEnabled,
onRemoveClick = {
actionToDelete = it
showDeleteDialog = true
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt
similarity index 93%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionScreen.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt
index 34cc3a8616..fce1f2f42c 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionScreen.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -20,7 +20,6 @@ import androidx.compose.material.icons.rounded.Bluetooth
import androidx.compose.material.icons.rounded.Wifi
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.CircularProgressIndicator
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
@@ -38,22 +37,21 @@ import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
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.State
-import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
-import io.github.sds100.keymapper.util.ui.compose.SearchAppBarActions
-import io.github.sds100.keymapper.util.ui.compose.SimpleListItemFixedHeight
-import io.github.sds100.keymapper.util.ui.compose.SimpleListItemGroup
-import io.github.sds100.keymapper.util.ui.compose.SimpleListItemHeader
-import io.github.sds100.keymapper.util.ui.compose.SimpleListItemModel
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.utils.ui.compose.ComposeIconInfo
+import io.github.sds100.keymapper.base.utils.ui.compose.SearchAppBarActions
+import io.github.sds100.keymapper.base.utils.ui.compose.SimpleListItemFixedHeight
+import io.github.sds100.keymapper.base.utils.ui.compose.SimpleListItemGroup
+import io.github.sds100.keymapper.base.utils.ui.compose.SimpleListItemHeader
+import io.github.sds100.keymapper.base.utils.ui.compose.SimpleListItemModel
+import io.github.sds100.keymapper.common.utils.State
import kotlinx.coroutines.flow.update
@Composable
fun ChooseActionScreen(
modifier: Modifier = Modifier,
viewModel: ChooseActionViewModel,
- onNavigateBack: () -> Unit,
) {
val state by viewModel.groups.collectAsStateWithLifecycle()
val query by viewModel.searchQuery.collectAsStateWithLifecycle()
@@ -69,11 +67,10 @@ fun ChooseActionScreen(
onQueryChange = { newQuery -> viewModel.searchQuery.update { newQuery } },
onCloseSearch = { viewModel.searchQuery.update { null } },
onClickAction = viewModel::onListItemClick,
- onNavigateBack = onNavigateBack,
+ onNavigateBack = viewModel::onNavigateBack,
)
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ChooseActionScreen(
modifier: Modifier = Modifier,
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionViewModel.kt
similarity index 73%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionViewModel.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionViewModel.kt
index ad7f075949..5961652602 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionViewModel.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionViewModel.kt
@@ -1,25 +1,23 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import io.github.sds100.keymapper.R
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.utils.containsQuery
+import io.github.sds100.keymapper.base.utils.getFullMessage
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
+import io.github.sds100.keymapper.base.utils.ui.DialogModel
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.DialogResponse
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.compose.ComposeIconInfo
+import io.github.sds100.keymapper.base.utils.ui.compose.SimpleListItemGroup
+import io.github.sds100.keymapper.base.utils.ui.compose.SimpleListItemModel
+import io.github.sds100.keymapper.base.utils.ui.showDialog
+import io.github.sds100.keymapper.common.utils.State
+import io.github.sds100.keymapper.system.SystemError
import io.github.sds100.keymapper.system.permissions.Permission
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.State
-import io.github.sds100.keymapper.util.containsQuery
-import io.github.sds100.keymapper.util.getFullMessage
-import io.github.sds100.keymapper.util.ui.DialogResponse
-import io.github.sds100.keymapper.util.ui.NavigationViewModel
-import io.github.sds100.keymapper.util.ui.NavigationViewModelImpl
-import io.github.sds100.keymapper.util.ui.PopupUi
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.PopupViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
-import io.github.sds100.keymapper.util.ui.compose.SimpleListItemGroup
-import io.github.sds100.keymapper.util.ui.compose.SimpleListItemModel
-import io.github.sds100.keymapper.util.ui.showPopup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -27,36 +25,37 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.serialization.json.Json
+import javax.inject.Inject
-/**
- * Created by sds100 on 22/07/2021.
- */
-class ChooseActionViewModel(
+@HiltViewModel
+class ChooseActionViewModel @Inject constructor(
private val useCase: CreateActionUseCase,
resourceProvider: ResourceProvider,
+ navigationProvider: NavigationProvider,
+ dialogProvider: DialogProvider,
) : ViewModel(),
ResourceProvider by resourceProvider,
- PopupViewModel by PopupViewModelImpl(),
- NavigationViewModel by NavigationViewModelImpl() {
+ DialogProvider by dialogProvider,
+ NavigationProvider by navigationProvider {
companion object {
private val CATEGORY_ORDER = arrayOf(
ActionCategory.INPUT,
ActionCategory.APPS,
+ ActionCategory.FLASHLIGHT,
ActionCategory.NAVIGATION,
ActionCategory.VOLUME,
ActionCategory.DISPLAY,
ActionCategory.MEDIA,
ActionCategory.INTERFACE,
- ActionCategory.CONTENT,
ActionCategory.KEYBOARD,
ActionCategory.CONNECTIVITY,
ActionCategory.TELEPHONY,
- ActionCategory.CAMERA_SOUND,
ActionCategory.NOTIFICATIONS,
+ ActionCategory.SPECIAL,
)
}
@@ -65,9 +64,6 @@ class ChooseActionViewModel(
private val allGroupedListItems: List by lazy { buildListGroups() }
- val returnAction = createActionDelegate.actionResult.filterNotNull()
- .shareIn(viewModelScope, SharingStarted.Eagerly)
-
val searchQuery = MutableStateFlow(null)
val groups: StateFlow>> =
@@ -86,19 +82,33 @@ class ChooseActionViewModel(
State.Data(groups)
}.flowOn(Dispatchers.Default).stateIn(viewModelScope, SharingStarted.Eagerly, State.Loading)
+ init {
+ viewModelScope.launch {
+ createActionDelegate.actionResult.filterNotNull().collect { action ->
+ popBackStackWithResult(Json.encodeToString(action))
+ }
+ }
+ }
+
fun onListItemClick(id: String) {
viewModelScope.launch {
val actionId = ActionId.valueOf(id)
val approvedMessage = showMessageForAction(actionId)
if (!approvedMessage) {
- showMessageForAction(actionId)
+ return@launch
}
createActionDelegate.createAction(actionId)
}
}
+ fun onNavigateBack() {
+ viewModelScope.launch {
+ popBackStack()
+ }
+ }
+
private fun buildListGroups(): List = buildList {
val listItems = buildListItems(ActionId.entries)
@@ -141,7 +151,7 @@ class ChooseActionViewModel(
val icon = ActionUtils.getComposeIcon(actionId)
val subtitle = when {
- error == Error.PermissionDenied(Permission.ROOT) -> getString(R.string.choose_action_warning_requires_root)
+ error == SystemError.PermissionDenied(Permission.ROOT) -> getString(R.string.choose_action_warning_requires_root)
error != null -> error.getFullMessage(this@ChooseActionViewModel)
else -> null
}
@@ -165,9 +175,9 @@ class ChooseActionViewModel(
private suspend fun showMessageForAction(id: ActionId): Boolean {
// See issue #1379
if (id == ActionId.APP) {
- val response = showPopup(
+ val response = showDialog(
"show_app_action_warning_dialog",
- PopupUi.Dialog(
+ DialogModel.Alert(
message = getString(R.string.action_open_app_dialog_message),
title = getString(R.string.action_open_app_dialog_title),
positiveButtonText = getString(R.string.action_open_app_dialog_read_more_button),
@@ -176,9 +186,9 @@ class ChooseActionViewModel(
)
if (response == DialogResponse.POSITIVE) {
- showPopup(
+ showDialog(
"app_action_permission_info",
- PopupUi.OpenUrl(getString(R.string.url_action_guide)),
+ DialogModel.OpenUrl(getString(R.string.url_action_guide)),
)
return false
} else {
@@ -195,8 +205,6 @@ class ChooseActionViewModel(
ActionId.REWIND,
-> R.string.action_rewind_message
- ActionId.MOVE_CURSOR_TO_END -> R.string.action_move_to_end_of_text_message
-
ActionId.TOGGLE_KEYBOARD,
ActionId.SHOW_KEYBOARD,
ActionId.HIDE_KEYBOARD,
@@ -209,9 +217,9 @@ class ChooseActionViewModel(
}
if (messageToShow != null) {
- val response = showPopup(
+ val response = showDialog(
"show_action_message",
- PopupUi.Ok(message = getString(messageToShow)),
+ DialogModel.Ok(message = getString(messageToShow)),
)
return response != null
@@ -219,13 +227,4 @@ class ChooseActionViewModel(
return true
}
-
- @Suppress("UNCHECKED_CAST")
- class Factory(
- private val useCase: CreateActionUseCase,
- private val resourceProvider: ResourceProvider,
- ) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T = ChooseActionViewModel(useCase, resourceProvider) as T
- }
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsViewModel.kt
similarity index 87%
rename from app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsViewModel.kt
index f501900c70..0e1a4ca5d2 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsViewModel.kt
@@ -1,32 +1,31 @@
-package io.github.sds100.keymapper.actions
-
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCase
-import io.github.sds100.keymapper.mappings.keymaps.KeyMap
-import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel
-import io.github.sds100.keymapper.onboarding.OnboardingUseCase
+package io.github.sds100.keymapper.base.actions
+
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.keymaps.ConfigKeyMapUseCase
+import io.github.sds100.keymapper.base.keymaps.KeyMap
+import io.github.sds100.keymapper.base.keymaps.ShortcutModel
+import io.github.sds100.keymapper.base.onboarding.OnboardingUseCase
+import io.github.sds100.keymapper.base.utils.getFullMessage
+import io.github.sds100.keymapper.base.utils.isFixable
+import io.github.sds100.keymapper.base.utils.navigation.NavDestination
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
+import io.github.sds100.keymapper.base.utils.navigation.navigate
+import io.github.sds100.keymapper.base.utils.ui.ChooseAppStoreModel
+import io.github.sds100.keymapper.base.utils.ui.DialogModel
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.DialogResponse
+import io.github.sds100.keymapper.base.utils.ui.LinkType
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.ViewModelHelper
+import io.github.sds100.keymapper.base.utils.ui.compose.ComposeIconInfo
+import io.github.sds100.keymapper.base.utils.ui.showDialog
+import io.github.sds100.keymapper.common.utils.KMError
+import io.github.sds100.keymapper.common.utils.State
+import io.github.sds100.keymapper.common.utils.dataOrNull
+import io.github.sds100.keymapper.common.utils.mapData
+import io.github.sds100.keymapper.common.utils.onFailure
+import io.github.sds100.keymapper.system.SystemError
import io.github.sds100.keymapper.system.permissions.Permission
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.State
-import io.github.sds100.keymapper.util.dataOrNull
-import io.github.sds100.keymapper.util.getFullMessage
-import io.github.sds100.keymapper.util.isFixable
-import io.github.sds100.keymapper.util.mapData
-import io.github.sds100.keymapper.util.onFailure
-import io.github.sds100.keymapper.util.ui.ChooseAppStoreModel
-import io.github.sds100.keymapper.util.ui.DialogResponse
-import io.github.sds100.keymapper.util.ui.LinkType
-import io.github.sds100.keymapper.util.ui.NavDestination
-import io.github.sds100.keymapper.util.ui.NavigationViewModel
-import io.github.sds100.keymapper.util.ui.NavigationViewModelImpl
-import io.github.sds100.keymapper.util.ui.PopupUi
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.PopupViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.ViewModelHelper
-import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
-import io.github.sds100.keymapper.util.ui.navigate
-import io.github.sds100.keymapper.util.ui.showPopup
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -41,10 +40,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-/**
- * Created by sds100 on 22/11/20.
- */
-
class ConfigActionsViewModel(
private val coroutineScope: CoroutineScope,
private val displayAction: DisplayActionUseCase,
@@ -53,10 +48,12 @@ class ConfigActionsViewModel(
private val config: ConfigKeyMapUseCase,
private val onboarding: OnboardingUseCase,
resourceProvider: ResourceProvider,
+ navigationProvider: NavigationProvider,
+ dialogProvider: DialogProvider,
) : ActionOptionsBottomSheetCallback,
ResourceProvider by resourceProvider,
- PopupViewModel by PopupViewModelImpl(),
- NavigationViewModel by NavigationViewModelImpl() {
+ DialogProvider by dialogProvider,
+ NavigationProvider by navigationProvider {
val createActionDelegate =
CreateActionDelegate(coroutineScope, createAction, this, this, this)
@@ -115,11 +112,11 @@ class ConfigActionsViewModel(
val error =
actionErrorSnapshot.filterNotNull().first().getError(actionData) ?: return@launch
- if (error == Error.PermissionDenied(Permission.ACCESS_NOTIFICATION_POLICY)) {
+ if (error == SystemError.PermissionDenied(Permission.ACCESS_NOTIFICATION_POLICY)) {
coroutineScope.launch {
ViewModelHelper.showDialogExplainingDndAccessBeingUnavailable(
resourceProvider = this@ConfigActionsViewModel,
- popupViewModel = this@ConfigActionsViewModel,
+ dialogProvider = this@ConfigActionsViewModel,
neverShowDndTriggerErrorAgain = { displayAction.neverShowDndTriggerError() },
fixError = { displayAction.fixError(error) },
)
@@ -127,7 +124,7 @@ class ConfigActionsViewModel(
} else {
ViewModelHelper.showFixErrorDialog(
resourceProvider = this@ConfigActionsViewModel,
- popupViewModel = this@ConfigActionsViewModel,
+ dialogProvider = this@ConfigActionsViewModel,
error,
) {
displayAction.fixError(error)
@@ -254,18 +251,18 @@ class ConfigActionsViewModel(
private suspend fun attemptTestAction(actionData: ActionData) {
testAction.invoke(actionData).onFailure { error ->
- if (error is Error.AccessibilityServiceDisabled) {
+ if (error is KMError.AccessibilityServiceDisabled) {
ViewModelHelper.handleAccessibilityServiceStoppedDialog(
resourceProvider = this,
- popupViewModel = this,
+ dialogProvider = this,
startService = displayAction::startAccessibilityService,
)
}
- if (error is Error.AccessibilityServiceCrashed) {
+ if (error is KMError.AccessibilityServiceCrashed) {
ViewModelHelper.handleAccessibilityServiceCrashedDialog(
resourceProvider = this,
- popupViewModel = this,
+ dialogProvider = this,
restartService = displayAction::restartAccessibilityService,
)
}
@@ -278,7 +275,7 @@ class ConfigActionsViewModel(
githubLink = getString(R.string.url_github_keymapper_leanback_keyboard),
)
- val dialog = PopupUi.ChooseAppStore(
+ val dialog = DialogModel.ChooseAppStore(
title = getString(R.string.dialog_title_install_leanback_keyboard),
message = getString(R.string.dialog_message_install_leanback_keyboard),
appStoreModel,
@@ -286,7 +283,7 @@ class ConfigActionsViewModel(
negativeButtonText = getString(R.string.neg_cancel),
)
- val response = showPopup("download_leanback_ime", dialog) ?: return
+ val response = showDialog("download_leanback_ime", dialog) ?: return
if (response == DialogResponse.POSITIVE) {
onboarding.neverShowGuiKeyboardPromptsAgain()
@@ -298,7 +295,7 @@ class ConfigActionsViewModel(
githubLink = getString(R.string.url_github_keymapper_gui_keyboard),
)
- val dialog = PopupUi.ChooseAppStore(
+ val dialog = DialogModel.ChooseAppStore(
title = getString(R.string.dialog_title_install_gui_keyboard),
message = getString(R.string.dialog_message_install_gui_keyboard),
appStoreModel,
@@ -306,7 +303,7 @@ class ConfigActionsViewModel(
negativeButtonText = getString(R.string.neg_cancel),
)
- val response = showPopup("download_gui_keyboard", dialog) ?: return
+ val response = showDialog("download_gui_keyboard", dialog) ?: return
if (response == DialogResponse.POSITIVE) {
onboarding.neverShowGuiKeyboardPromptsAgain()
@@ -316,7 +313,7 @@ class ConfigActionsViewModel(
private suspend fun promptToInstallShizukuOrGuiKeyboard() {
if (onboarding.isTvDevice()) {
- val chooseSolutionDialog = PopupUi.Dialog(
+ val chooseSolutionDialog = DialogModel.Alert(
title = getText(R.string.dialog_title_install_shizuku_or_leanback_keyboard),
message = getText(R.string.dialog_message_install_shizuku_or_leanback_keyboard),
positiveButtonText = getString(R.string.dialog_button_install_shizuku),
@@ -325,7 +322,7 @@ class ConfigActionsViewModel(
)
val chooseSolutionResponse =
- showPopup("choose_solution", chooseSolutionDialog) ?: return
+ showDialog("choose_solution", chooseSolutionDialog) ?: return
when (chooseSolutionResponse) {
// install shizuku
@@ -343,7 +340,7 @@ class ConfigActionsViewModel(
// download leanback keyboard
DialogResponse.NEGATIVE -> {
- val chooseAppStoreDialog = PopupUi.ChooseAppStore(
+ val chooseAppStoreDialog = DialogModel.ChooseAppStore(
title = getString(R.string.dialog_title_choose_download_leanback_keyboard),
message = getString(R.string.dialog_message_choose_download_leanback_keyboard),
model = ChooseAppStoreModel(
@@ -353,7 +350,7 @@ class ConfigActionsViewModel(
negativeButtonText = getString(R.string.neg_cancel),
)
- val response = showPopup("install_leanback_keyboard", chooseAppStoreDialog)
+ val response = showDialog("install_leanback_keyboard", chooseAppStoreDialog)
if (response == DialogResponse.POSITIVE) {
onboarding.neverShowGuiKeyboardPromptsAgain()
@@ -361,7 +358,7 @@ class ConfigActionsViewModel(
}
}
} else {
- val chooseSolutionDialog = PopupUi.Dialog(
+ val chooseSolutionDialog = DialogModel.Alert(
title = getText(R.string.dialog_title_install_shizuku_or_gui_keyboard),
message = getText(R.string.dialog_message_install_shizuku_or_gui_keyboard),
positiveButtonText = getString(R.string.dialog_button_install_shizuku),
@@ -370,7 +367,7 @@ class ConfigActionsViewModel(
)
val chooseSolutionResponse =
- showPopup("choose_solution", chooseSolutionDialog) ?: return
+ showDialog("choose_solution", chooseSolutionDialog) ?: return
when (chooseSolutionResponse) {
// install shizuku
@@ -388,7 +385,7 @@ class ConfigActionsViewModel(
// download gui keyboard
DialogResponse.NEGATIVE -> {
- val chooseAppStoreDialog = PopupUi.ChooseAppStore(
+ val chooseAppStoreDialog = DialogModel.ChooseAppStore(
title = getString(R.string.dialog_title_choose_download_gui_keyboard),
message = getString(R.string.dialog_message_choose_download_gui_keyboard),
model = ChooseAppStoreModel(
@@ -400,7 +397,7 @@ class ConfigActionsViewModel(
negativeButtonText = getString(R.string.neg_cancel),
)
- val response = showPopup("install_gui_keyboard", chooseAppStoreDialog)
+ val response = showDialog("install_gui_keyboard", chooseAppStoreDialog)
if (response == DialogResponse.POSITIVE) {
onboarding.neverShowGuiKeyboardPromptsAgain()
@@ -429,7 +426,7 @@ class ConfigActionsViewModel(
}
val actions =
- createListItems(keyMap, showDeviceDescriptors, errorSnapshot, shortcuts.size)
+ createListItems(keyMap, showDeviceDescriptors, errorSnapshot)
return ConfigActionsState.Loaded(
actions = actions,
@@ -442,8 +439,9 @@ class ConfigActionsViewModel(
keyMap: KeyMap,
showDeviceDescriptors: Boolean,
errorSnapshot: ActionErrorSnapshot,
- shortcutCount: Int,
): List {
+ val actionErrors = errorSnapshot.getErrors(keyMap.actionList.map { it.data })
+
return keyMap.actionList.mapIndexed { index, action ->
val title: String = if (action.multiplier != null && action.multiplier > 1) {
@@ -454,7 +452,7 @@ class ConfigActionsViewModel(
}
val icon: ComposeIconInfo = uiHelper.getIcon(action.data)
- val error: Error? = errorSnapshot.getError(action.data)
+ val error: KMError? = actionErrors[action.data]
val extraInfo = buildString {
val midDot = getString(R.string.middot)
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt
similarity index 82%
rename from app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt
index e034b3e9f8..b25b3ce758 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt
@@ -1,53 +1,48 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import android.text.InputType
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.actions.pinchscreen.PinchPickCoordinateResult
-import io.github.sds100.keymapper.actions.swipescreen.SwipePickCoordinateResult
-import io.github.sds100.keymapper.actions.tapscreen.PickCoordinateResult
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.actions.pinchscreen.PinchPickCoordinateResult
+import io.github.sds100.keymapper.base.actions.swipescreen.SwipePickCoordinateResult
+import io.github.sds100.keymapper.base.actions.tapscreen.PickCoordinateResult
+import io.github.sds100.keymapper.base.system.intents.ConfigIntentResult
+import io.github.sds100.keymapper.base.utils.DndModeStrings
+import io.github.sds100.keymapper.base.utils.RingerModeStrings
+import io.github.sds100.keymapper.base.utils.VolumeStreamStrings
+import io.github.sds100.keymapper.base.utils.navigation.NavDestination
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
+import io.github.sds100.keymapper.base.utils.navigation.navigate
+import io.github.sds100.keymapper.base.utils.ui.DialogModel
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.MultiChoiceItem
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.showDialog
+import io.github.sds100.keymapper.common.utils.Orientation
import io.github.sds100.keymapper.system.camera.CameraLens
-import io.github.sds100.keymapper.system.camera.CameraLensUtils
-import io.github.sds100.keymapper.system.display.Orientation
-import io.github.sds100.keymapper.system.display.OrientationUtils
-import io.github.sds100.keymapper.system.intents.ConfigIntentResult
import io.github.sds100.keymapper.system.network.HttpMethod
import io.github.sds100.keymapper.system.volume.DndMode
-import io.github.sds100.keymapper.system.volume.DndModeUtils
import io.github.sds100.keymapper.system.volume.RingerMode
-import io.github.sds100.keymapper.system.volume.RingerModeUtils
import io.github.sds100.keymapper.system.volume.VolumeStream
-import io.github.sds100.keymapper.system.volume.VolumeStreamUtils
-import io.github.sds100.keymapper.util.ui.MultiChoiceItem
-import io.github.sds100.keymapper.util.ui.NavDestination
-import io.github.sds100.keymapper.util.ui.NavigationViewModel
-import io.github.sds100.keymapper.util.ui.PopupUi
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.navigate
-import io.github.sds100.keymapper.util.ui.showPopup
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
-
-/**
- * Created by sds100 on 26/07/2021.
- */
+import kotlinx.serialization.json.Json
class CreateActionDelegate(
private val coroutineScope: CoroutineScope,
private val useCase: CreateActionUseCase,
- popupViewModel: PopupViewModel,
- navigationViewModelImpl: NavigationViewModel,
+ dialogProvider: DialogProvider,
+ navigationProvider: NavigationProvider,
resourceProvider: ResourceProvider,
) : ResourceProvider by resourceProvider,
- PopupViewModel by popupViewModel,
- NavigationViewModel by navigationViewModelImpl {
+ DialogProvider by dialogProvider,
+ NavigationProvider by navigationProvider {
val actionResult: MutableStateFlow = MutableStateFlow(null)
var enableFlashlightActionState: EnableFlashlightActionState? by mutableStateOf(null)
@@ -168,7 +163,7 @@ class CreateActionDelegate(
}
val imeId =
- showPopup("choose_ime", PopupUi.SingleChoice(items)) ?: return null
+ showDialog("choose_ime", DialogModel.SingleChoice(items)) ?: return null
val imeName = inputMethods.single { it.id == imeId }.label
return ActionData.SwitchKeyboard(imeId, imeName)
@@ -254,9 +249,9 @@ class CreateActionDelegate(
),
)
- val showVolumeUiDialog = PopupUi.MultiChoice(items = dialogItems)
+ val showVolumeUiDialog = DialogModel.MultiChoice(items = dialogItems)
- val chosenFlags = showPopup("show_volume_ui", showVolumeUiDialog) ?: return null
+ val chosenFlags = showDialog("show_volume_ui", showVolumeUiDialog) ?: return null
val showVolumeUi = chosenFlags.contains(showVolumeUiId)
@@ -293,17 +288,17 @@ class CreateActionDelegate(
),
)
- val showVolumeUiDialog = PopupUi.MultiChoice(items = dialogItems)
+ val showVolumeUiDialog = DialogModel.MultiChoice(items = dialogItems)
val chosenFlags =
- showPopup("show_volume_ui", showVolumeUiDialog) ?: return null
+ showDialog("show_volume_ui", showVolumeUiDialog) ?: return null
val showVolumeUi = chosenFlags.contains(showVolumeUiId)
- val items = VolumeStream.values()
- .map { it to getString(VolumeStreamUtils.getLabel(it)) }
+ val items = VolumeStream.entries
+ .map { it to getString(VolumeStreamStrings.getLabel(it)) }
- val stream = showPopup("pick_volume_stream", PopupUi.SingleChoice(items))
+ val stream = showDialog("pick_volume_stream", DialogModel.SingleChoice(items))
?: return null
val action = when (actionId) {
@@ -320,11 +315,11 @@ class CreateActionDelegate(
}
ActionId.CHANGE_RINGER_MODE -> {
- val items = RingerMode.values()
- .map { it to getString(RingerModeUtils.getLabel(it)) }
+ val items = RingerMode.entries
+ .map { it to getString(RingerModeStrings.getLabel(it)) }
val ringerMode =
- showPopup("pick_ringer_mode", PopupUi.SingleChoice(items))
+ showDialog("pick_ringer_mode", DialogModel.SingleChoice(items))
?: return null
return ActionData.Volume.SetRingerMode(ringerMode)
@@ -334,11 +329,11 @@ class CreateActionDelegate(
ActionId.TOGGLE_DND_MODE,
ActionId.ENABLE_DND_MODE,
-> {
- val items = DndMode.values()
- .map { it to getString(DndModeUtils.getLabel(it)) }
+ val items = DndMode.entries
+ .map { it to getString(DndModeStrings.getLabel(it)) }
val dndMode =
- showPopup("pick_dnd_mode", PopupUi.SingleChoice(items))
+ showDialog("pick_dnd_mode", DialogModel.SingleChoice(items))
?: return null
val action = when (actionId) {
@@ -360,15 +355,22 @@ class CreateActionDelegate(
false
}
+ val label = when (orientation) {
+ Orientation.ORIENTATION_0 -> R.string.orientation_0
+ Orientation.ORIENTATION_90 -> R.string.orientation_90
+ Orientation.ORIENTATION_180 -> R.string.orientation_180
+ Orientation.ORIENTATION_270 -> R.string.orientation_270
+ }
+
MultiChoiceItem(
orientation,
- getString(OrientationUtils.getLabel(orientation)),
+ getString(label),
isChecked,
)
}
val orientations =
- showPopup("pick_orientations", PopupUi.MultiChoice(items)) ?: return null
+ showDialog("pick_orientations", DialogModel.MultiChoice(items)) ?: return null
return ActionData.Rotation.CycleRotations(orientations)
}
@@ -445,14 +447,17 @@ class CreateActionDelegate(
ActionId.DISABLE_FLASHLIGHT,
-> {
- val items = useCase.getFlashlightLenses().map {
- it to getString(CameraLensUtils.getLabel(it))
+ val items = useCase.getFlashlightLenses().map { lens ->
+ when (lens) {
+ CameraLens.FRONT -> lens to getString(R.string.lens_front)
+ CameraLens.BACK -> lens to getString(R.string.lens_back)
+ }
}
if (items.size == 1) {
return ActionData.Flashlight.Disable(items.first().first)
} else {
- val lens = showPopup("pick_lens", PopupUi.SingleChoice(items))
+ val lens = showDialog("pick_lens", DialogModel.SingleChoice(items))
?: return null
return ActionData.Flashlight.Disable(lens)
@@ -484,8 +489,7 @@ class CreateActionDelegate(
ActionId.KEY_CODE -> {
val keyCode =
- navigate("choose_key_code", NavDestination.ChooseKeyCode)
- ?: return null
+ navigate("choose_key_code", NavDestination.ChooseKeyCode) ?: return null
return ActionData.InputKeyEvent(keyCode = keyCode)
}
@@ -610,9 +614,9 @@ class CreateActionDelegate(
""
}
- val text = showPopup(
+ val text = showDialog(
"create_text_action",
- PopupUi.Text(
+ DialogModel.Text(
hint = getString(R.string.hint_create_text_action),
allowEmpty = false,
text = oldText,
@@ -629,9 +633,9 @@ class CreateActionDelegate(
""
}
- val text = showPopup(
+ val text = showDialog(
"create_url_action",
- PopupUi.Text(
+ DialogModel.Text(
hint = getString(R.string.hint_create_url_action),
allowEmpty = false,
inputType = InputType.TYPE_TEXT_VARIATION_URI,
@@ -674,9 +678,9 @@ class CreateActionDelegate(
""
}
- val text = showPopup(
+ val text = showDialog(
"create_phone_call_action",
- PopupUi.Text(
+ DialogModel.Text(
hint = getString(R.string.hint_create_phone_call_action),
allowEmpty = false,
inputType = InputType.TYPE_CLASS_PHONE,
@@ -751,7 +755,6 @@ class CreateActionDelegate(
ActionId.DISABLE_NFC -> return ActionData.Nfc.Disable
ActionId.TOGGLE_NFC -> return ActionData.Nfc.Toggle
- ActionId.MOVE_CURSOR_TO_END -> return ActionData.MoveCursorToEnd
ActionId.TOGGLE_KEYBOARD -> return ActionData.ToggleKeyboard
ActionId.SHOW_KEYBOARD -> return ActionData.ShowKeyboard
ActionId.HIDE_KEYBOARD -> return ActionData.HideKeyboard
@@ -802,9 +805,68 @@ class CreateActionDelegate(
return navigate(
"config_interact_ui_element_action",
- NavDestination.InteractUiElement(oldAction),
+ NavDestination.InteractUiElement(oldAction?.let { Json.encodeToString(it) }),
)
}
+
+ ActionId.MOVE_CURSOR -> return createMoverCursorAction()
}
}
+
+ private suspend fun createMoverCursorAction(): ActionData.MoveCursor? {
+ val choices = listOf(
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.CHAR,
+ ActionData.MoveCursor.Direction.START,
+ ) to getString(R.string.action_move_cursor_prev_character),
+
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.CHAR,
+ ActionData.MoveCursor.Direction.END,
+ ) to getString(R.string.action_move_cursor_next_character),
+
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.WORD,
+ ActionData.MoveCursor.Direction.START,
+ ) to getString(R.string.action_move_cursor_start_word),
+
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.WORD,
+ ActionData.MoveCursor.Direction.END,
+ ) to getString(R.string.action_move_cursor_end_word),
+
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.LINE,
+ ActionData.MoveCursor.Direction.START,
+ ) to getString(R.string.action_move_cursor_start_line),
+
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.LINE,
+ ActionData.MoveCursor.Direction.END,
+ ) to getString(R.string.action_move_cursor_end_line),
+
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.PARAGRAPH,
+ ActionData.MoveCursor.Direction.START,
+ ) to getString(R.string.action_move_cursor_start_paragraph),
+
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.PARAGRAPH,
+ ActionData.MoveCursor.Direction.END,
+ ) to getString(R.string.action_move_cursor_end_paragraph),
+
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.PAGE,
+ ActionData.MoveCursor.Direction.START,
+ ) to getString(R.string.action_move_cursor_start_page),
+
+ ActionData.MoveCursor(
+ ActionData.MoveCursor.Type.PAGE,
+ ActionData.MoveCursor.Direction.END,
+ ) to getString(R.string.action_move_cursor_end_page),
+ )
+
+ val dialog = DialogModel.SingleChoice(choices)
+ return showDialog("create_move_cursor_action", dialog)
+ }
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionUseCase.kt
similarity index 95%
rename from app/src/main/java/io/github/sds100/keymapper/actions/CreateActionUseCase.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionUseCase.kt
index 37ddcecac2..90ba5f6d72 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionUseCase.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionUseCase.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import io.github.sds100.keymapper.system.camera.CameraAdapter
import io.github.sds100.keymapper.system.camera.CameraFlashInfo
@@ -10,12 +10,9 @@ import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.merge
+import javax.inject.Inject
-/**
- * Created by sds100 on 25/07/2021.
- */
-
-class CreateActionUseCaseImpl(
+class CreateActionUseCaseImpl @Inject constructor(
private val inputMethodAdapter: InputMethodAdapter,
private val systemFeatureAdapter: SystemFeatureAdapter,
private val cameraAdapter: CameraAdapter,
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/DisplayActionUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/DisplayActionUseCase.kt
new file mode 100644
index 0000000000..ceb8958484
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/DisplayActionUseCase.kt
@@ -0,0 +1,18 @@
+package io.github.sds100.keymapper.base.actions
+
+import android.graphics.drawable.Drawable
+import io.github.sds100.keymapper.common.utils.KMError
+import io.github.sds100.keymapper.common.utils.KMResult
+import kotlinx.coroutines.flow.Flow
+
+interface DisplayActionUseCase : GetActionErrorUseCase {
+ val showDeviceDescriptors: Flow
+ fun getAppName(packageName: String): KMResult
+ fun getAppIcon(packageName: String): KMResult
+ fun getInputMethodLabel(imeId: String): KMResult
+ fun getRingtoneLabel(uri: String): KMResult
+ suspend fun fixError(error: KMError)
+ fun neverShowDndTriggerError()
+ fun startAccessibilityService(): Boolean
+ fun restartAccessibilityService(): Boolean
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/FlashlightActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/FlashlightActionBottomSheet.kt
similarity index 98%
rename from app/src/main/java/io/github/sds100/keymapper/actions/FlashlightActionBottomSheet.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/FlashlightActionBottomSheet.kt
index 1482dd5327..13b4490d76 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/FlashlightActionBottomSheet.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/FlashlightActionBottomSheet.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import android.os.Build
import androidx.compose.animation.AnimatedContent
@@ -46,13 +46,13 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperSliderThumb
+import io.github.sds100.keymapper.base.utils.ui.compose.OptionsHeaderRow
+import io.github.sds100.keymapper.base.utils.ui.compose.RadioButtonText
import io.github.sds100.keymapper.system.camera.CameraFlashInfo
import io.github.sds100.keymapper.system.camera.CameraLens
-import io.github.sds100.keymapper.util.ui.compose.KeyMapperSliderThumb
-import io.github.sds100.keymapper.util.ui.compose.OptionsHeaderRow
-import io.github.sds100.keymapper.util.ui.compose.RadioButtonText
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/GetActionErrorUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/GetActionErrorUseCase.kt
similarity index 77%
rename from app/src/main/java/io/github/sds100/keymapper/actions/GetActionErrorUseCase.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/GetActionErrorUseCase.kt
index ce8a18b9e1..d165b5025b 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/GetActionErrorUseCase.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/GetActionErrorUseCase.kt
@@ -1,22 +1,26 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
-import io.github.sds100.keymapper.actions.sound.SoundsManager
-import io.github.sds100.keymapper.shizuku.ShizukuAdapter
+import io.github.sds100.keymapper.base.actions.sound.SoundsManager
+import io.github.sds100.keymapper.common.BuildConfigProvider
import io.github.sds100.keymapper.system.apps.PackageManagerAdapter
import io.github.sds100.keymapper.system.camera.CameraAdapter
import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter
import io.github.sds100.keymapper.system.permissions.PermissionAdapter
import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter
import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter
+import io.github.sds100.keymapper.system.shizuku.ShizukuAdapter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import javax.inject.Inject
+import javax.inject.Singleton
-class GetActionErrorUseCaseImpl(
- private val packageManager: PackageManagerAdapter,
+@Singleton
+class GetActionErrorUseCaseImpl @Inject constructor(
+ private val packageManagerAdapter: PackageManagerAdapter,
private val inputMethodAdapter: InputMethodAdapter,
private val permissionAdapter: PermissionAdapter,
private val systemFeatureAdapter: SystemFeatureAdapter,
@@ -24,6 +28,7 @@ class GetActionErrorUseCaseImpl(
private val soundsManager: SoundsManager,
private val shizukuAdapter: ShizukuAdapter,
private val ringtoneAdapter: RingtoneAdapter,
+ private val buildConfigProvider: BuildConfigProvider,
) : GetActionErrorUseCase {
private val invalidateActionErrors = merge(
@@ -34,7 +39,7 @@ class GetActionErrorUseCaseImpl(
soundsManager.soundFiles.drop(1).map { },
shizukuAdapter.isStarted.drop(1).map { },
shizukuAdapter.isInstalled.drop(1).map { },
- packageManager.onPackagesChanged,
+ packageManagerAdapter.onPackagesChanged,
)
override val actionErrorSnapshot: Flow = channelFlow {
@@ -46,7 +51,7 @@ class GetActionErrorUseCaseImpl(
}
private fun createSnapshot(): ActionErrorSnapshot = LazyActionErrorSnapshot(
- packageManager,
+ packageManagerAdapter,
inputMethodAdapter,
permissionAdapter,
systemFeatureAdapter,
@@ -54,6 +59,7 @@ class GetActionErrorUseCaseImpl(
soundsManager,
shizukuAdapter,
ringtoneAdapter,
+ buildConfigProvider,
)
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/HoldDownMode.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/HoldDownMode.kt
similarity index 61%
rename from app/src/main/java/io/github/sds100/keymapper/actions/HoldDownMode.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/HoldDownMode.kt
index 886b5a2d68..cf10c89e7e 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/HoldDownMode.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/HoldDownMode.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
enum class HoldDownMode {
TRIGGER_RELEASED,
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/HttpRequestBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/HttpRequestBottomSheet.kt
similarity index 98%
rename from app/src/main/java/io/github/sds100/keymapper/actions/HttpRequestBottomSheet.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/HttpRequestBottomSheet.kt
index 15d0063245..696b6f2f9f 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/HttpRequestBottomSheet.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/HttpRequestBottomSheet.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -37,10 +37,10 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperDropdownMenu
import io.github.sds100.keymapper.system.network.HttpMethod
-import io.github.sds100.keymapper.util.ui.compose.KeyMapperDropdownMenu
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/IsActionSupportedUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/IsActionSupportedUseCase.kt
similarity index 70%
rename from app/src/main/java/io/github/sds100/keymapper/actions/IsActionSupportedUseCase.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/IsActionSupportedUseCase.kt
index b488ea686a..d1c7a3d404 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/IsActionSupportedUseCase.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/IsActionSupportedUseCase.kt
@@ -1,13 +1,14 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import android.content.pm.PackageManager
import android.os.Build
+import io.github.sds100.keymapper.common.utils.KMError
+import io.github.sds100.keymapper.system.SystemError
import io.github.sds100.keymapper.system.camera.CameraAdapter
import io.github.sds100.keymapper.system.camera.CameraLens
import io.github.sds100.keymapper.system.permissions.Permission
import io.github.sds100.keymapper.system.permissions.PermissionAdapter
import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter
-import io.github.sds100.keymapper.util.Error
class IsActionSupportedUseCaseImpl(
private val adapter: SystemFeatureAdapter,
@@ -15,24 +16,24 @@ class IsActionSupportedUseCaseImpl(
private val permissionAdapter: PermissionAdapter,
) : IsActionSupportedUseCase {
- override fun isSupported(id: ActionId): Error? {
+ override fun isSupported(id: ActionId): KMError? {
if (Build.VERSION.SDK_INT != 0) {
val minApi = ActionUtils.getMinApi(id)
if (Build.VERSION.SDK_INT < minApi) {
- return Error.SdkVersionTooLow(minApi)
+ return KMError.SdkVersionTooLow(minApi)
}
val maxApi = ActionUtils.getMaxApi(id)
if (Build.VERSION.SDK_INT > maxApi) {
- return Error.SdkVersionTooHigh(maxApi)
+ return KMError.SdkVersionTooHigh(maxApi)
}
}
ActionUtils.getRequiredSystemFeatures(id).forEach { feature ->
if (!adapter.hasSystemFeature(feature)) {
- return Error.SystemFeatureNotSupported(feature)
+ return KMError.SystemFeatureNotSupported(feature)
}
}
@@ -40,7 +41,7 @@ class IsActionSupportedUseCaseImpl(
if (cameraAdapter.getFlashInfo(CameraLens.BACK) == null &&
cameraAdapter.getFlashInfo(CameraLens.FRONT) == null
) {
- return Error.SystemFeatureNotSupported(PackageManager.FEATURE_CAMERA_FLASH)
+ return KMError.SystemFeatureNotSupported(PackageManager.FEATURE_CAMERA_FLASH)
}
}
@@ -48,15 +49,14 @@ class IsActionSupportedUseCaseImpl(
if (cameraAdapter.getFlashInfo(CameraLens.BACK)?.supportsVariableStrength != true &&
cameraAdapter.getFlashInfo(CameraLens.FRONT)?.supportsVariableStrength != true
) {
- return Error.CameraVariableFlashlightStrengthUnsupported
+ return KMError.CameraVariableFlashlightStrengthUnsupported
}
}
- if (ActionUtils.getRequiredPermissions(id)
- .contains(Permission.ROOT) &&
+ if (ActionUtils.getRequiredPermissions(id).contains(Permission.ROOT) &&
!permissionAdapter.isGranted(Permission.ROOT)
) {
- return Error.PermissionDenied(Permission.ROOT)
+ return SystemError.PermissionDenied(Permission.ROOT)
}
return null
@@ -64,5 +64,5 @@ class IsActionSupportedUseCaseImpl(
}
interface IsActionSupportedUseCase {
- fun isSupported(id: ActionId): Error?
+ fun isSupported(id: ActionId): KMError?
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt
similarity index 74%
rename from app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt
index 11d66a7ae0..690635ed06 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt
@@ -1,19 +1,40 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
import android.accessibilityservice.AccessibilityService
import android.os.Build
import android.view.InputDevice
import android.view.KeyEvent
import android.view.accessibility.AccessibilityNodeInfo
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.actions.sound.SoundsManager
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.actions.sound.SoundsManager
+import io.github.sds100.keymapper.base.system.accessibility.AccessibilityNodeAction
+import io.github.sds100.keymapper.base.system.accessibility.AccessibilityNodeModel
+import io.github.sds100.keymapper.base.system.accessibility.IAccessibilityService
+import io.github.sds100.keymapper.base.system.inputmethod.ImeInputEventInjector
+import io.github.sds100.keymapper.base.system.navigation.OpenMenuHelper
+import io.github.sds100.keymapper.base.utils.getFullMessage
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.common.utils.InputEventType
+import io.github.sds100.keymapper.common.utils.KMError
+import io.github.sds100.keymapper.common.utils.KMResult
+import io.github.sds100.keymapper.common.utils.Orientation
+import io.github.sds100.keymapper.common.utils.Success
+import io.github.sds100.keymapper.common.utils.dataOrNull
+import io.github.sds100.keymapper.common.utils.firstBlocking
+import io.github.sds100.keymapper.common.utils.getWordBoundaries
+import io.github.sds100.keymapper.common.utils.ifIsData
+import io.github.sds100.keymapper.common.utils.onFailure
+import io.github.sds100.keymapper.common.utils.onSuccess
+import io.github.sds100.keymapper.common.utils.otherwise
+import io.github.sds100.keymapper.common.utils.success
+import io.github.sds100.keymapper.common.utils.then
+import io.github.sds100.keymapper.common.utils.withFlag
import io.github.sds100.keymapper.data.Keys
import io.github.sds100.keymapper.data.PreferenceDefaults
import io.github.sds100.keymapper.data.repositories.PreferenceRepository
-import io.github.sds100.keymapper.system.accessibility.AccessibilityNodeAction
-import io.github.sds100.keymapper.system.accessibility.AccessibilityNodeModel
-import io.github.sds100.keymapper.system.accessibility.IAccessibilityService
-import io.github.sds100.keymapper.system.accessibility.ServiceAdapter
import io.github.sds100.keymapper.system.airplanemode.AirplaneModeAdapter
import io.github.sds100.keymapper.system.apps.AppShortcutAdapter
import io.github.sds100.keymapper.system.apps.PackageManagerAdapter
@@ -21,48 +42,31 @@ import io.github.sds100.keymapper.system.bluetooth.BluetoothAdapter
import io.github.sds100.keymapper.system.camera.CameraAdapter
import io.github.sds100.keymapper.system.devices.DevicesAdapter
import io.github.sds100.keymapper.system.display.DisplayAdapter
-import io.github.sds100.keymapper.system.display.Orientation
import io.github.sds100.keymapper.system.files.FileAdapter
import io.github.sds100.keymapper.system.files.FileUtils
-import io.github.sds100.keymapper.system.inputevents.InputEventInjector
import io.github.sds100.keymapper.system.inputevents.InputEventUtils
-import io.github.sds100.keymapper.system.inputmethod.ImeInputEventInjector
import io.github.sds100.keymapper.system.inputmethod.InputKeyModel
import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter
import io.github.sds100.keymapper.system.intents.IntentAdapter
import io.github.sds100.keymapper.system.intents.IntentTarget
import io.github.sds100.keymapper.system.lock.LockScreenAdapter
import io.github.sds100.keymapper.system.media.MediaAdapter
-import io.github.sds100.keymapper.system.navigation.OpenMenuHelper
import io.github.sds100.keymapper.system.network.NetworkAdapter
import io.github.sds100.keymapper.system.nfc.NfcAdapter
+import io.github.sds100.keymapper.system.notifications.NotificationReceiverAdapter
+import io.github.sds100.keymapper.system.notifications.NotificationServiceEvent
import io.github.sds100.keymapper.system.permissions.Permission
import io.github.sds100.keymapper.system.permissions.PermissionAdapter
import io.github.sds100.keymapper.system.phone.PhoneAdapter
-import io.github.sds100.keymapper.system.popup.PopupMessageAdapter
+import io.github.sds100.keymapper.system.popup.ToastAdapter
import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter
import io.github.sds100.keymapper.system.root.SuAdapter
import io.github.sds100.keymapper.system.shell.ShellAdapter
+import io.github.sds100.keymapper.system.shizuku.ShizukuInputEventInjector
import io.github.sds100.keymapper.system.url.OpenUrlAdapter
import io.github.sds100.keymapper.system.volume.RingerMode
import io.github.sds100.keymapper.system.volume.VolumeAdapter
import io.github.sds100.keymapper.system.volume.VolumeStream
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.InputEventType
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.ServiceEvent
-import io.github.sds100.keymapper.util.Success
-import io.github.sds100.keymapper.util.dataOrNull
-import io.github.sds100.keymapper.util.firstBlocking
-import io.github.sds100.keymapper.util.getFullMessage
-import io.github.sds100.keymapper.util.getWordBoundaries
-import io.github.sds100.keymapper.util.ifIsData
-import io.github.sds100.keymapper.util.onFailure
-import io.github.sds100.keymapper.util.onSuccess
-import io.github.sds100.keymapper.util.otherwise
-import io.github.sds100.keymapper.util.success
-import io.github.sds100.keymapper.util.then
-import io.github.sds100.keymapper.util.ui.ResourceProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -71,30 +75,26 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import splitties.bitflags.withFlag
import timber.log.Timber
-/**
- * Created by sds100 on 14/02/21.
- */
-
-class PerformActionsUseCaseImpl(
- private val coroutineScope: CoroutineScope,
- private val accessibilityService: IAccessibilityService,
+class PerformActionsUseCaseImpl @AssistedInject constructor(
+ private val appCoroutineScope: CoroutineScope,
+ @Assisted
+ private val service: IAccessibilityService,
private val inputMethodAdapter: InputMethodAdapter,
private val fileAdapter: FileAdapter,
private val suAdapter: SuAdapter,
- private val shellAdapter: ShellAdapter,
+ private val shell: ShellAdapter,
private val intentAdapter: IntentAdapter,
- private val getActionError: GetActionErrorUseCase,
- private val imeInputEventInjector: ImeInputEventInjector,
- private val shizukuInputEventInjector: InputEventInjector,
+ private val getActionErrorUseCase: GetActionErrorUseCase,
+ @Assisted
+ private val keyMapperImeMessenger: ImeInputEventInjector,
private val packageManagerAdapter: PackageManagerAdapter,
private val appShortcutAdapter: AppShortcutAdapter,
- private val popupMessageAdapter: PopupMessageAdapter,
- private val deviceAdapter: DevicesAdapter,
+ private val toastAdapter: ToastAdapter,
+ private val devicesAdapter: DevicesAdapter,
private val phoneAdapter: PhoneAdapter,
- private val volumeAdapter: VolumeAdapter,
+ private val audioAdapter: VolumeAdapter,
private val cameraAdapter: CameraAdapter,
private val displayAdapter: DisplayAdapter,
private val lockScreenAdapter: LockScreenAdapter,
@@ -105,20 +105,30 @@ class PerformActionsUseCaseImpl(
private val nfcAdapter: NfcAdapter,
private val openUrlAdapter: OpenUrlAdapter,
private val resourceProvider: ResourceProvider,
- private val preferenceRepository: PreferenceRepository,
private val soundsManager: SoundsManager,
private val permissionAdapter: PermissionAdapter,
- private val notificationReceiverAdapter: ServiceAdapter,
+ private val notificationReceiverAdapter: NotificationReceiverAdapter,
private val ringtoneAdapter: RingtoneAdapter,
+ private val settingsRepository: PreferenceRepository,
) : PerformActionsUseCase {
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ accessibilityService: IAccessibilityService,
+ imeInputEventInjector: ImeInputEventInjector,
+ ): PerformActionsUseCaseImpl
+ }
+
+ private val shizukuInputEventInjector: ShizukuInputEventInjector = ShizukuInputEventInjector()
+
private val openMenuHelper by lazy {
OpenMenuHelper(
suAdapter,
- accessibilityService,
+ service,
shizukuInputEventInjector,
permissionAdapter,
- coroutineScope,
+ appCoroutineScope,
)
}
@@ -127,7 +137,7 @@ class PerformActionsUseCaseImpl(
*/
private val inputKeyEventsWithShizuku: StateFlow =
permissionAdapter.isGrantedFlow(Permission.SHIZUKU)
- .stateIn(coroutineScope, SharingStarted.Eagerly, false)
+ .stateIn(appCoroutineScope, SharingStarted.Eagerly, false)
override suspend fun perform(
action: ActionData,
@@ -137,7 +147,7 @@ class PerformActionsUseCaseImpl(
/**
* Is null if the action is being performed asynchronously
*/
- val result: Result<*>
+ val result: KMResult<*>
when (action) {
is ActionData.App -> {
@@ -179,7 +189,7 @@ class PerformActionsUseCaseImpl(
action.useShell -> suAdapter.execute("input keyevent ${model.keyCode}")
else -> {
- imeInputEventInjector.inputKeyEvent(model)
+ keyMapperImeMessenger.inputKeyEvent(model)
Success(Unit)
}
@@ -191,19 +201,19 @@ class PerformActionsUseCaseImpl(
}
is ActionData.DoNotDisturb.Enable -> {
- result = volumeAdapter.enableDndMode(action.dndMode)
+ result = audioAdapter.enableDndMode(action.dndMode)
}
is ActionData.DoNotDisturb.Toggle -> {
- result = if (volumeAdapter.isDndEnabled()) {
- volumeAdapter.disableDndMode()
+ result = if (audioAdapter.isDndEnabled()) {
+ audioAdapter.disableDndMode()
} else {
- volumeAdapter.enableDndMode(action.dndMode)
+ audioAdapter.enableDndMode(action.dndMode)
}
}
is ActionData.Volume.SetRingerMode -> {
- result = volumeAdapter.setRingerMode(action.ringerMode)
+ result = audioAdapter.setRingerMode(action.ringerMode)
}
is ActionData.ControlMediaForApp.FastForward -> {
@@ -286,50 +296,50 @@ class PerformActionsUseCaseImpl(
R.string.toast_chose_keyboard,
it.label,
)
- popupMessageAdapter.showPopupMessage(message)
+ toastAdapter.show(message)
}
}
is ActionData.Volume.Down -> {
- result = volumeAdapter.lowerVolume(showVolumeUi = action.showVolumeUi)
+ result = audioAdapter.lowerVolume(showVolumeUi = action.showVolumeUi)
}
is ActionData.Volume.Up -> {
- result = volumeAdapter.raiseVolume(showVolumeUi = action.showVolumeUi)
+ result = audioAdapter.raiseVolume(showVolumeUi = action.showVolumeUi)
}
is ActionData.Volume.Mute -> {
- result = volumeAdapter.muteVolume(showVolumeUi = action.showVolumeUi)
+ result = audioAdapter.muteVolume(showVolumeUi = action.showVolumeUi)
}
is ActionData.Volume.Stream.Decrease -> {
- result = volumeAdapter.lowerVolume(
+ result = audioAdapter.lowerVolume(
stream = action.volumeStream,
showVolumeUi = action.showVolumeUi,
)
}
is ActionData.Volume.Stream.Increase -> {
- result = volumeAdapter.raiseVolume(
+ result = audioAdapter.raiseVolume(
stream = action.volumeStream,
showVolumeUi = action.showVolumeUi,
)
}
is ActionData.Volume.ToggleMute -> {
- result = volumeAdapter.toggleMuteVolume(showVolumeUi = action.showVolumeUi)
+ result = audioAdapter.toggleMuteVolume(showVolumeUi = action.showVolumeUi)
}
is ActionData.Volume.UnMute -> {
- result = volumeAdapter.unmuteVolume(showVolumeUi = action.showVolumeUi)
+ result = audioAdapter.unmuteVolume(showVolumeUi = action.showVolumeUi)
}
is ActionData.TapScreen -> {
- result = accessibilityService.tapScreen(action.x, action.y, inputEventType)
+ result = service.tapScreen(action.x, action.y, inputEventType)
}
is ActionData.SwipeScreen -> {
- result = accessibilityService.swipeScreen(
+ result = service.swipeScreen(
action.xStart,
action.yStart,
action.xEnd,
@@ -341,7 +351,7 @@ class PerformActionsUseCaseImpl(
}
is ActionData.PinchScreen -> {
- result = accessibilityService.pinchScreen(
+ result = service.pinchScreen(
action.x,
action.y,
action.distance,
@@ -353,7 +363,7 @@ class PerformActionsUseCaseImpl(
}
is ActionData.Text -> {
- imeInputEventInjector.inputText(action.text)
+ keyMapperImeMessenger.inputText(action.text)
result = Success(Unit)
}
@@ -483,42 +493,42 @@ class PerformActionsUseCaseImpl(
}
is ActionData.Volume.CycleRingerMode -> {
- result = when (volumeAdapter.ringerMode) {
- RingerMode.NORMAL -> volumeAdapter.setRingerMode(RingerMode.VIBRATE)
- RingerMode.VIBRATE -> volumeAdapter.setRingerMode(RingerMode.SILENT)
- RingerMode.SILENT -> volumeAdapter.setRingerMode(RingerMode.NORMAL)
+ result = when (audioAdapter.ringerMode) {
+ RingerMode.NORMAL -> audioAdapter.setRingerMode(RingerMode.VIBRATE)
+ RingerMode.VIBRATE -> audioAdapter.setRingerMode(RingerMode.SILENT)
+ RingerMode.SILENT -> audioAdapter.setRingerMode(RingerMode.NORMAL)
}
}
is ActionData.Volume.CycleVibrateRing -> {
- result = when (volumeAdapter.ringerMode) {
- RingerMode.NORMAL -> volumeAdapter.setRingerMode(RingerMode.VIBRATE)
- RingerMode.VIBRATE -> volumeAdapter.setRingerMode(RingerMode.NORMAL)
- RingerMode.SILENT -> volumeAdapter.setRingerMode(RingerMode.NORMAL)
+ result = when (audioAdapter.ringerMode) {
+ RingerMode.NORMAL -> audioAdapter.setRingerMode(RingerMode.VIBRATE)
+ RingerMode.VIBRATE -> audioAdapter.setRingerMode(RingerMode.NORMAL)
+ RingerMode.SILENT -> audioAdapter.setRingerMode(RingerMode.NORMAL)
}
}
is ActionData.DoNotDisturb.Disable -> {
- result = volumeAdapter.disableDndMode()
+ result = audioAdapter.disableDndMode()
}
is ActionData.StatusBar.ExpandNotifications -> {
val globalAction = AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS
- result = accessibilityService.doGlobalAction(globalAction).otherwise {
- shellAdapter.execute("cmd statusbar expand-notifications")
+ result = service.doGlobalAction(globalAction).otherwise {
+ shell.execute("cmd statusbar expand-notifications")
}
}
is ActionData.StatusBar.ToggleNotifications -> {
result =
- if (accessibilityService.rootNode?.packageName == "com.android.systemui") {
+ if (service.rootNode?.packageName == "com.android.systemui") {
closeStatusBarShade()
} else {
val globalAction = AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS
- accessibilityService.doGlobalAction(globalAction).otherwise {
- shellAdapter.execute("cmd statusbar expand-notifications")
+ service.doGlobalAction(globalAction).otherwise {
+ shell.execute("cmd statusbar expand-notifications")
}
}
}
@@ -527,20 +537,20 @@ class PerformActionsUseCaseImpl(
val globalAction = AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS
result =
- accessibilityService.doGlobalAction(globalAction).otherwise {
- shellAdapter.execute("cmd statusbar expand-settings")
+ service.doGlobalAction(globalAction).otherwise {
+ shell.execute("cmd statusbar expand-settings")
}
}
is ActionData.StatusBar.ToggleQuickSettings -> {
result =
- if (accessibilityService.rootNode?.packageName == "com.android.systemui") {
+ if (service.rootNode?.packageName == "com.android.systemui") {
closeStatusBarShade()
} else {
val globalAction = AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS
- accessibilityService.doGlobalAction(globalAction).otherwise {
- shellAdapter.execute("cmd statusbar expand-settings")
+ service.doGlobalAction(globalAction).otherwise {
+ shell.execute("cmd statusbar expand-settings")
}
}
}
@@ -591,32 +601,32 @@ class PerformActionsUseCaseImpl(
is ActionData.GoBack -> {
result =
- accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
+ service.doGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
}
is ActionData.GoHome -> {
result =
- accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME)
+ service.doGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME)
}
is ActionData.OpenRecents -> {
result =
- accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
+ service.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
}
is ActionData.ToggleSplitScreen -> {
result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN)
+ service.doGlobalAction(AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN)
} else {
- Error.SdkVersionTooLow(minSdk = Build.VERSION_CODES.N)
+ KMError.SdkVersionTooLow(minSdk = Build.VERSION_CODES.N)
}
}
is ActionData.GoLastApp -> {
- accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
+ service.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
delay(100)
result =
- accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
+ service.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
}
is ActionData.OpenMenu -> {
@@ -639,39 +649,49 @@ class PerformActionsUseCaseImpl(
}
}
- is ActionData.MoveCursorToEnd -> {
- val keyModel = InputKeyModel(
- keyCode = KeyEvent.KEYCODE_MOVE_END,
- metaState = KeyEvent.META_CTRL_ON,
- )
+ is ActionData.MoveCursor -> {
+ result = service.performActionOnNode({ it.isFocused }) {
+ val actionType = when (action.direction) {
+ ActionData.MoveCursor.Direction.START -> AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ ActionData.MoveCursor.Direction.END -> AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ }
- if (inputKeyEventsWithShizuku.value) {
- shizukuInputEventInjector.inputKeyEvent(keyModel)
- } else {
- imeInputEventInjector.inputKeyEvent(keyModel)
- }
+ val granularity = when (action.moveType) {
+ ActionData.MoveCursor.Type.CHAR -> AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ ActionData.MoveCursor.Type.WORD -> AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ ActionData.MoveCursor.Type.LINE -> AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ ActionData.MoveCursor.Type.PARAGRAPH -> AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ ActionData.MoveCursor.Type.PAGE -> AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE
+ }
- result = Success(Unit)
+ AccessibilityNodeAction(
+ actionType,
+ mapOf(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT to granularity,
+ AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN to false,
+ ),
+ )
+ }
}
is ActionData.ToggleKeyboard -> {
- val isHidden = accessibilityService.isKeyboardHidden.firstBlocking()
+ val isHidden = service.isKeyboardHidden.firstBlocking()
if (isHidden) {
- accessibilityService.showKeyboard()
+ service.showKeyboard()
} else {
- accessibilityService.hideKeyboard()
+ service.hideKeyboard()
}
result = Success(Unit)
}
is ActionData.ShowKeyboard -> {
- accessibilityService.showKeyboard()
+ service.showKeyboard()
result = Success(Unit)
}
is ActionData.HideKeyboard -> {
- accessibilityService.hideKeyboard()
+ service.hideKeyboard()
result = Success(Unit)
}
@@ -680,25 +700,25 @@ class PerformActionsUseCaseImpl(
}
is ActionData.CutText -> {
- result = accessibilityService.performActionOnNode({ it.isFocused }) {
+ result = service.performActionOnNode({ it.isFocused }) {
AccessibilityNodeAction(AccessibilityNodeInfo.ACTION_CUT)
}
}
is ActionData.CopyText -> {
- result = accessibilityService.performActionOnNode({ it.isFocused }) {
+ result = service.performActionOnNode({ it.isFocused }) {
AccessibilityNodeAction(AccessibilityNodeInfo.ACTION_COPY)
}
}
is ActionData.PasteText -> {
- result = accessibilityService.performActionOnNode({ it.isFocused }) {
+ result = service.performActionOnNode({ it.isFocused }) {
AccessibilityNodeAction(AccessibilityNodeInfo.ACTION_PASTE)
}
}
is ActionData.SelectWordAtCursor -> {
- result = accessibilityService.performActionOnNode({ it.isFocused }) { node ->
+ result = service.performActionOnNode({ it.isFocused }) { node ->
// it is at the cursor position if they both return the same value
if (node.textSelectionStart == node.textSelectionEnd) {
val cursorPosition = node.textSelectionStart
@@ -752,7 +772,7 @@ class PerformActionsUseCaseImpl(
// Wait 3 seconds so the message isn't shown in the screenshot.
delay(3000)
- popupMessageAdapter.showPopupMessage(
+ toastAdapter.show(
resourceProvider.getString(
R.string.toast_screenshot_taken,
),
@@ -760,7 +780,7 @@ class PerformActionsUseCaseImpl(
}
} else {
result =
- accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT)
+ service.doGlobalAction(AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT)
}
}
@@ -780,7 +800,7 @@ class PerformActionsUseCaseImpl(
result = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
suAdapter.execute("input keyevent ${KeyEvent.KEYCODE_POWER}")
} else {
- accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN)
+ service.doGlobalAction(AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN)
}
}
@@ -802,19 +822,21 @@ class PerformActionsUseCaseImpl(
is ActionData.ShowPowerMenu -> {
result =
- accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG)
+ service.doGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG)
}
is ActionData.Volume.ShowDialog -> {
- result = volumeAdapter.showVolumeUi()
+ result = audioAdapter.showVolumeUi()
}
ActionData.DismissAllNotifications -> {
- result = notificationReceiverAdapter.send(ServiceEvent.DismissAllNotifications)
+ result =
+ notificationReceiverAdapter.send(NotificationServiceEvent.DismissAllNotifications)
}
ActionData.DismissLastNotification -> {
- result = notificationReceiverAdapter.send(ServiceEvent.DismissLastNotification)
+ result =
+ notificationReceiverAdapter.send(NotificationServiceEvent.DismissLastNotification)
}
ActionData.AnswerCall -> {
@@ -845,22 +867,22 @@ class PerformActionsUseCaseImpl(
}
is ActionData.InteractUiElement -> {
- if (accessibilityService.activeWindowPackage.first() != action.packageName) {
- result = Error.UiElementNotFound
+ if (service.activeWindowPackage.first() != action.packageName) {
+ result = KMError.UiElementNotFound
} else {
- result = accessibilityService.performActionOnNode(
+ result = service.performActionOnNode(
findNode = { node ->
matchAccessibilityNode(node, action)
},
performAction = { AccessibilityNodeAction(action = action.nodeAction.accessibilityActionId) },
- ).otherwise { Error.UiElementNotFound }
+ ).otherwise { KMError.UiElementNotFound }
}
}
}
when (result) {
is Success -> Timber.d("Performed action $action, input event type: $inputEventType, key meta state: $keyMetaState")
- is Error -> Timber.d(
+ is KMError -> Timber.d(
"Failed to perform action $action, reason: ${result.getFullMessage(resourceProvider)}, action: $action, input event type: $inputEventType, key meta state: $keyMetaState",
)
}
@@ -869,21 +891,21 @@ class PerformActionsUseCaseImpl(
}
override fun getErrorSnapshot(): ActionErrorSnapshot {
- return getActionError.actionErrorSnapshot.firstBlocking()
+ return getActionErrorUseCase.actionErrorSnapshot.firstBlocking()
}
override val defaultRepeatDelay: Flow =
- preferenceRepository.get(Keys.defaultRepeatDelay)
+ settingsRepository.get(Keys.defaultRepeatDelay)
.map { it ?: PreferenceDefaults.REPEAT_DELAY }
.map { it.toLong() }
override val defaultRepeatRate: Flow =
- preferenceRepository.get(Keys.defaultRepeatRate)
+ settingsRepository.get(Keys.defaultRepeatRate)
.map { it ?: PreferenceDefaults.REPEAT_RATE }
.map { it.toLong() }
override val defaultHoldDownDuration: Flow =
- preferenceRepository.get(Keys.defaultHoldDownDuration)
+ settingsRepository.get(Keys.defaultHoldDownDuration)
.map { it ?: PreferenceDefaults.HOLD_DOWN_DURATION }
.map { it.toLong() }
@@ -892,7 +914,7 @@ class PerformActionsUseCaseImpl(
// automatically select a game controller as the input device for game controller key events
if (InputEventUtils.isGamepadKeyCode(action.keyCode)) {
- deviceAdapter.connectedInputDevices.value.ifIsData { inputDevices ->
+ devicesAdapter.connectedInputDevices.value.ifIsData { inputDevices ->
val device = inputDevices.find { it.isGameController }
if (device != null) {
@@ -904,7 +926,7 @@ class PerformActionsUseCaseImpl(
return 0
}
- val inputDevices = deviceAdapter.connectedInputDevices.value
+ val inputDevices = devicesAdapter.connectedInputDevices.value
val devicesWithSameDescriptor =
inputDevices.dataOrNull()
@@ -924,7 +946,7 @@ class PerformActionsUseCaseImpl(
code. if none do then use the first one
*/
val deviceThatHasKey = devicesWithSameDescriptor.singleOrNull {
- deviceAdapter.deviceHasKey(it.id, action.keyCode)
+ devicesAdapter.deviceHasKey(it.id, action.keyCode)
}
val device = deviceThatHasKey
@@ -934,18 +956,18 @@ class PerformActionsUseCaseImpl(
return device.id
}
- private fun closeStatusBarShade(): Result<*> {
+ private fun closeStatusBarShade(): KMResult<*> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- return accessibilityService
+ return service
.doGlobalAction(AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE)
} else {
- return shellAdapter.execute("cmd statusbar collapse")
+ return shell.execute("cmd statusbar collapse")
}
}
- private fun Result<*>.showErrorMessageOnFail() {
+ private fun KMResult<*>.showErrorMessageOnFail() {
onFailure {
- popupMessageAdapter.showPopupMessage(it.getFullMessage(resourceProvider))
+ toastAdapter.show(it.getFullMessage(resourceProvider))
}
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/RepeatMode.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/RepeatMode.kt
similarity index 52%
rename from app/src/main/java/io/github/sds100/keymapper/actions/RepeatMode.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/RepeatMode.kt
index 5a6abc9631..e9eeb2ae76 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/RepeatMode.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/RepeatMode.kt
@@ -1,8 +1,5 @@
-package io.github.sds100.keymapper.actions
+package io.github.sds100.keymapper.base.actions
-/**
- * Created by sds100 on 15/05/2021.
- */
enum class RepeatMode {
TRIGGER_RELEASED,
LIMIT_REACHED,
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/TestActionEvent.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/TestActionEvent.kt
new file mode 100644
index 0000000000..f6e061950d
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/TestActionEvent.kt
@@ -0,0 +1,7 @@
+package io.github.sds100.keymapper.base.actions
+
+import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceEvent
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class TestActionEvent(val action: ActionData) : AccessibilityServiceEvent()
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/TestActionUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/TestActionUseCase.kt
new file mode 100644
index 0000000000..5459f60f53
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/TestActionUseCase.kt
@@ -0,0 +1,15 @@
+package io.github.sds100.keymapper.base.actions
+
+import io.github.sds100.keymapper.common.utils.KMResult
+import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter
+import javax.inject.Inject
+
+class TestActionUseCaseImpl @Inject constructor(
+ private val serviceAdapter: AccessibilityServiceAdapter,
+) : TestActionUseCase {
+ override suspend fun invoke(action: ActionData): KMResult<*> = serviceAdapter.send(TestActionEvent(action))
+}
+
+interface TestActionUseCase {
+ suspend operator fun invoke(action: ActionData): KMResult<*>
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ChooseKeyCodeFragment.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ChooseKeyCodeFragment.kt
similarity index 70%
rename from app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ChooseKeyCodeFragment.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ChooseKeyCodeFragment.kt
index 06ef9ba635..20ee488091 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ChooseKeyCodeFragment.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ChooseKeyCodeFragment.kt
@@ -1,24 +1,21 @@
-package io.github.sds100.keymapper.actions.keyevent
+package io.github.sds100.keymapper.base.actions.keyevent
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.navArgs
import com.airbnb.epoxy.EpoxyRecyclerView
-import io.github.sds100.keymapper.databinding.FragmentSimpleRecyclerviewBinding
-import io.github.sds100.keymapper.simple
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.State
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.ui.RecyclerViewUtils
-import io.github.sds100.keymapper.util.ui.SimpleListItemOld
-import io.github.sds100.keymapper.util.ui.SimpleRecyclerViewFragment
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.databinding.FragmentSimpleRecyclerviewBinding
+import io.github.sds100.keymapper.base.simple
+import io.github.sds100.keymapper.base.utils.ui.RecyclerViewUtils
+import io.github.sds100.keymapper.base.utils.ui.SimpleListItemOld
+import io.github.sds100.keymapper.base.utils.ui.SimpleRecyclerViewFragment
+import io.github.sds100.keymapper.base.utils.ui.launchRepeatOnLifecycle
+import io.github.sds100.keymapper.common.utils.State
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
-/**
- * Created by sds100 on 30/03/2020.
- */
-
+@AndroidEntryPoint
class ChooseKeyCodeFragment : SimpleRecyclerViewFragment() {
companion object {
const val EXTRA_KEYCODE = "extra_keycode"
@@ -28,9 +25,8 @@ class ChooseKeyCodeFragment : SimpleRecyclerViewFragment() {
override var searchStateKey: String? = SEARCH_STATE_KEY
private val args: ChooseKeyCodeFragmentArgs by navArgs()
- private val viewModel: ChooseKeyCodeViewModel by viewModels {
- Inject.chooseKeyCodeViewModel()
- }
+
+ private val viewModel: ChooseKeyCodeViewModel by viewModels()
override val listItems: Flow>>
get() = viewModel.state
@@ -40,7 +36,7 @@ class ChooseKeyCodeFragment : SimpleRecyclerViewFragment() {
RecyclerViewUtils.applySimpleListItemDecorations(binding.epoxyRecyclerView)
- viewLifecycleOwner.launchRepeatOnLifecycle(Lifecycle.State.RESUMED) {
+ viewLifecycleOwner.launchRepeatOnLifecycle(Lifecycle.State.CREATED) {
viewModel.returnResult.collectLatest {
returnResult(EXTRA_KEYCODE to it)
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ChooseKeyCodeViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ChooseKeyCodeViewModel.kt
similarity index 75%
rename from app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ChooseKeyCodeViewModel.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ChooseKeyCodeViewModel.kt
index d6eed56979..67987c742a 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ChooseKeyCodeViewModel.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ChooseKeyCodeViewModel.kt
@@ -1,14 +1,14 @@
-package io.github.sds100.keymapper.actions.keyevent
+package io.github.sds100.keymapper.base.actions.keyevent
import android.view.KeyEvent
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.utils.filterByQuery
+import io.github.sds100.keymapper.base.utils.ui.DefaultSimpleListItem
+import io.github.sds100.keymapper.base.utils.ui.SimpleListItemOld
+import io.github.sds100.keymapper.common.utils.State
import io.github.sds100.keymapper.system.inputevents.InputEventUtils
-import io.github.sds100.keymapper.util.State
-import io.github.sds100.keymapper.util.filterByQuery
-import io.github.sds100.keymapper.util.ui.DefaultSimpleListItem
-import io.github.sds100.keymapper.util.ui.SimpleListItemOld
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -19,12 +19,10 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import javax.inject.Inject
-/**
- * Created by sds100 on 31/03/2020.
- */
-
-class ChooseKeyCodeViewModel : ViewModel() {
+@HiltViewModel
+class ChooseKeyCodeViewModel @Inject constructor() : ViewModel() {
val searchQuery = MutableStateFlow(null)
@@ -67,10 +65,4 @@ class ChooseKeyCodeViewModel : ViewModel() {
_returnResult.emit(id.toInt())
}
}
-
- @Suppress("UNCHECKED_CAST")
- class Factory : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T = ChooseKeyCodeViewModel() as T
- }
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ConfigKeyEventActionFragment.kt
similarity index 89%
rename from app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ConfigKeyEventActionFragment.kt
index 1f6dfcf81d..4f2b33a30b 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ConfigKeyEventActionFragment.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.keyevent
+package io.github.sds100.keymapper.base.actions.keyevent
import android.os.Bundle
import android.view.LayoutInflater
@@ -16,21 +16,18 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.databinding.FragmentConfigKeyEventBinding
-import io.github.sds100.keymapper.ui.utils.putJsonSerializable
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.str
-import io.github.sds100.keymapper.util.ui.configuredCheckBox
-import io.github.sds100.keymapper.util.ui.setupNavigation
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.databinding.FragmentConfigKeyEventBinding
+import io.github.sds100.keymapper.base.utils.navigation.setupFragmentNavigation
+import io.github.sds100.keymapper.base.utils.ui.configuredCheckBox
+import io.github.sds100.keymapper.base.utils.ui.launchRepeatOnLifecycle
+import io.github.sds100.keymapper.base.utils.ui.str
+import io.github.sds100.keymapper.common.utils.putJsonSerializable
import kotlinx.coroutines.flow.collectLatest
import kotlinx.serialization.json.Json
-/**
- * Created by sds100 on 30/03/2020.
- */
-
+@AndroidEntryPoint
class ConfigKeyEventActionFragment : Fragment() {
companion object {
const val EXTRA_RESULT = "extra_result"
@@ -40,9 +37,7 @@ class ConfigKeyEventActionFragment : Fragment() {
private val requestKey: String by lazy { args.requestKey }
- private val viewModel: ConfigKeyEventActionViewModel by viewModels {
- Inject.configKeyEventViewModel(requireContext())
- }
+ private val viewModel: ConfigKeyEventActionViewModel by viewModels()
/**
* Scoped to the lifecycle of the fragment's view (between onCreateView and onDestroyView)
@@ -62,7 +57,7 @@ class ConfigKeyEventActionFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- viewModel.setupNavigation(this)
+ viewModel.setupFragmentNavigation(this)
if (args.keyEventAction != null) {
viewModel.loadAction(Json.decodeFromString(args.keyEventAction!!))
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ConfigKeyEventActionViewModel.kt
similarity index 78%
rename from app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ConfigKeyEventActionViewModel.kt
index 30d1236f18..02fd7545a0 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ConfigKeyEventActionViewModel.kt
@@ -1,30 +1,33 @@
-package io.github.sds100.keymapper.actions.keyevent
+package io.github.sds100.keymapper.base.actions.keyevent
import android.annotation.SuppressLint
import android.view.KeyEvent
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.actions.ActionData
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.actions.ActionData
+import io.github.sds100.keymapper.base.utils.InputEventStrings
+import io.github.sds100.keymapper.base.utils.getFullMessage
+import io.github.sds100.keymapper.base.utils.navigation.NavDestination
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
+import io.github.sds100.keymapper.base.utils.navigation.NavigationProviderImpl
+import io.github.sds100.keymapper.base.utils.navigation.navigate
+import io.github.sds100.keymapper.base.utils.ui.CheckBoxListItem
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.common.utils.KMError
+import io.github.sds100.keymapper.common.utils.KMResult
+import io.github.sds100.keymapper.common.utils.Success
+import io.github.sds100.keymapper.common.utils.errorOrNull
+import io.github.sds100.keymapper.common.utils.handle
+import io.github.sds100.keymapper.common.utils.hasFlag
+import io.github.sds100.keymapper.common.utils.isSuccess
+import io.github.sds100.keymapper.common.utils.minusFlag
+import io.github.sds100.keymapper.common.utils.success
+import io.github.sds100.keymapper.common.utils.valueOrNull
+import io.github.sds100.keymapper.common.utils.withFlag
import io.github.sds100.keymapper.system.devices.InputDeviceInfo
import io.github.sds100.keymapper.system.devices.InputDeviceUtils
-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.Success
-import io.github.sds100.keymapper.util.errorOrNull
-import io.github.sds100.keymapper.util.getFullMessage
-import io.github.sds100.keymapper.util.handle
-import io.github.sds100.keymapper.util.isSuccess
-import io.github.sds100.keymapper.util.success
-import io.github.sds100.keymapper.util.ui.CheckBoxListItem
-import io.github.sds100.keymapper.util.ui.NavDestination
-import io.github.sds100.keymapper.util.ui.NavigationViewModel
-import io.github.sds100.keymapper.util.ui.NavigationViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.navigate
-import io.github.sds100.keymapper.util.valueOrNull
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -34,20 +37,15 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
-import splitties.bitflags.hasFlag
-import splitties.bitflags.minusFlag
-import splitties.bitflags.withFlag
+import javax.inject.Inject
-/**
- * Created by sds100 on 30/03/2020.
- */
-
-class ConfigKeyEventActionViewModel(
+@HiltViewModel
+class ConfigKeyEventActionViewModel @Inject constructor(
private val useCase: ConfigKeyEventUseCase,
- resourceProvider: ResourceProvider,
+ private val resourceProvider: ResourceProvider,
) : ViewModel(),
ResourceProvider by resourceProvider,
- NavigationViewModel by NavigationViewModelImpl() {
+ NavigationProvider by NavigationProviderImpl() {
private val keyEventState = MutableStateFlow(KeyEventState())
@@ -115,8 +113,8 @@ class ConfigKeyEventActionViewModel(
fun onKeyCodeTextChanged(text: String) {
val keyCodeState = when {
- text.isBlank() -> Error.EmptyText
- text.toIntOrNull() == null -> Error.InvalidNumber
+ text.isBlank() -> KMError.EmptyText
+ text.toIntOrNull() == null -> KMError.InvalidNumber
else -> text.toInt().success()
}
@@ -192,7 +190,7 @@ class ConfigKeyEventActionViewModel(
onError = { "" },
)
- val modifierListItems = InputEventUtils.MODIFIER_LABELS.map { (modifier, label) ->
+ val modifierListItems = InputEventStrings.MODIFIER_LABELS.map { (modifier, label) ->
CheckBoxListItem(
id = modifier.toString(),
label = getString(label),
@@ -238,17 +236,8 @@ class ConfigKeyEventActionViewModel(
)
}
- @Suppress("UNCHECKED_CAST")
- class Factory(
- private val useCase: ConfigKeyEventUseCase,
- private val resourceProvider: ResourceProvider,
- ) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T = ConfigKeyEventActionViewModel(useCase, resourceProvider) as T
- }
-
private data class KeyEventState(
- val keyCode: Result = Error.EmptyText,
+ val keyCode: KMResult = KMError.EmptyText,
val chosenDevice: InputDeviceInfo? = null,
val useShell: Boolean = false,
val metaState: Int = 0,
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ConfigKeyEventUseCase.kt
similarity index 83%
rename from app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventUseCase.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ConfigKeyEventUseCase.kt
index a5c13e898a..a304220fab 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventUseCase.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/keyevent/ConfigKeyEventUseCase.kt
@@ -1,18 +1,15 @@
-package io.github.sds100.keymapper.actions.keyevent
+package io.github.sds100.keymapper.base.actions.keyevent
+import io.github.sds100.keymapper.common.utils.State
import io.github.sds100.keymapper.data.Keys
import io.github.sds100.keymapper.data.repositories.PreferenceRepository
import io.github.sds100.keymapper.system.devices.DevicesAdapter
import io.github.sds100.keymapper.system.devices.InputDeviceInfo
-import io.github.sds100.keymapper.util.State
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
+import javax.inject.Inject
-/**
- * Created by sds100 on 01/05/2021.
- */
-
-class ConfigKeyEventUseCaseImpl(
+class ConfigKeyEventUseCaseImpl @Inject constructor(
private val preferenceRepository: PreferenceRepository,
private val devicesAdapter: DevicesAdapter,
) : ConfigKeyEventUseCase {
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/pinchscreen/PinchPickCoordinateResult.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/pinchscreen/PinchPickCoordinateResult.kt
similarity index 68%
rename from app/src/main/java/io/github/sds100/keymapper/actions/pinchscreen/PinchPickCoordinateResult.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/pinchscreen/PinchPickCoordinateResult.kt
index b790c293b1..b3624183d4 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/pinchscreen/PinchPickCoordinateResult.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/pinchscreen/PinchPickCoordinateResult.kt
@@ -1,12 +1,8 @@
-package io.github.sds100.keymapper.actions.pinchscreen
+package io.github.sds100.keymapper.base.actions.pinchscreen
+import io.github.sds100.keymapper.common.utils.PinchScreenType
import kotlinx.serialization.Serializable
-enum class PinchScreenType {
- PINCH_IN,
- PINCH_OUT,
-}
-
@Serializable
data class PinchPickCoordinateResult(
val x: Int,
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/pinchscreen/PinchPickDisplayCoordinateFragment.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/pinchscreen/PinchPickDisplayCoordinateFragment.kt
similarity index 90%
rename from app/src/main/java/io/github/sds100/keymapper/actions/pinchscreen/PinchPickDisplayCoordinateFragment.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/pinchscreen/PinchPickDisplayCoordinateFragment.kt
index 654619d14b..8ed81bd516 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/pinchscreen/PinchPickDisplayCoordinateFragment.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/pinchscreen/PinchPickDisplayCoordinateFragment.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.pinchscreen
+package io.github.sds100.keymapper.base.actions.pinchscreen
import android.annotation.SuppressLint
import android.graphics.Bitmap
@@ -21,17 +21,16 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.databinding.FragmentPinchPickCoordinatesBinding
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.databinding.FragmentPinchPickCoordinatesBinding
+import io.github.sds100.keymapper.base.utils.ui.launchRepeatOnLifecycle
+import io.github.sds100.keymapper.base.utils.ui.str
import io.github.sds100.keymapper.system.files.FileUtils
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.str
-import io.github.sds100.keymapper.util.ui.showPopups
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
+@AndroidEntryPoint
class PinchPickDisplayCoordinateFragment : Fragment() {
companion object {
const val EXTRA_RESULT = "extra_result"
@@ -41,9 +40,7 @@ class PinchPickDisplayCoordinateFragment : Fragment() {
private val requestKey: String by lazy { args.requestKey }
private var pinchTypesDisplayValues = mutableListOf()
- private val viewModel: PinchPickDisplayCoordinateViewModel by viewModels {
- Inject.pinchCoordinateActionTypeViewModel(requireContext())
- }
+ private val viewModel: PinchPickDisplayCoordinateViewModel by viewModels()
private val screenshotLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
@@ -113,7 +110,6 @@ class PinchPickDisplayCoordinateFragment : Fragment() {
android.R.layout.simple_spinner_dropdown_item,
pinchTypesDisplayValues,
)
- viewModel.showPopups(this, binding)
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
findNavController().navigateUp()
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/pinchscreen/PinchPickDisplayCoordinateViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/pinchscreen/PinchPickDisplayCoordinateViewModel.kt
similarity index 89%
rename from app/src/main/java/io/github/sds100/keymapper/actions/pinchscreen/PinchPickDisplayCoordinateViewModel.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/pinchscreen/PinchPickDisplayCoordinateViewModel.kt
index 5cff836fe5..4450f70f65 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/pinchscreen/PinchPickDisplayCoordinateViewModel.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/pinchscreen/PinchPickDisplayCoordinateViewModel.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.pinchscreen
+package io.github.sds100.keymapper.base.actions.pinchscreen
import android.accessibilityservice.GestureDescription
import android.graphics.Bitmap
@@ -7,14 +7,14 @@ import android.os.Build
import android.view.View
import android.widget.AdapterView
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.util.ui.PopupUi
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.PopupViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.showPopup
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.utils.ui.DialogModel
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.showDialog
+import io.github.sds100.keymapper.common.utils.PinchScreenType
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -25,13 +25,16 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import javax.inject.Inject
import kotlin.math.roundToInt
-class PinchPickDisplayCoordinateViewModel(
+@HiltViewModel
+class PinchPickDisplayCoordinateViewModel @Inject constructor(
resourceProvider: ResourceProvider,
+ dialogProvider: DialogProvider,
) : ViewModel(),
ResourceProvider by resourceProvider,
- PopupViewModel by PopupViewModelImpl() {
+ DialogProvider by dialogProvider {
private val pinchTypes = arrayOf(PinchScreenType.PINCH_IN.name, PinchScreenType.PINCH_OUT.name)
@@ -160,11 +163,11 @@ class PinchPickDisplayCoordinateViewModel(
(displaySize.y != newBitmap.width && displaySize.x != newBitmap.height)
) {
viewModelScope.launch {
- val snackBar = PopupUi.SnackBar(
+ val snackBar = DialogModel.SnackBar(
message = getString(R.string.toast_incorrect_screenshot_resolution),
)
- showPopup("incorrect_resolution", snackBar)
+ showDialog("incorrect_resolution", snackBar)
}
return
@@ -224,9 +227,9 @@ class PinchPickDisplayCoordinateViewModel(
val fingerCount = fingerCount.value ?: return@launch
val duration = duration.value ?: return@launch
- val description = showPopup(
+ val description = showDialog(
"coordinate_description",
- PopupUi.Text(
+ DialogModel.Text(
getString(R.string.hint_tap_coordinate_title),
allowEmpty = true,
text = description.value ?: "",
@@ -269,13 +272,4 @@ class PinchPickDisplayCoordinateViewModel(
super.onCleared()
}
-
- @Suppress("UNCHECKED_CAST")
- class Factory(
- private val resourceProvider: ResourceProvider,
- ) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T =
- PinchPickDisplayCoordinateViewModel(resourceProvider) as T
- }
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileFragment.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/ChooseSoundFileFragment.kt
similarity index 91%
rename from app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileFragment.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/sound/ChooseSoundFileFragment.kt
index 9584e52bd0..ab1088467b 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileFragment.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/ChooseSoundFileFragment.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.sound
+package io.github.sds100.keymapper.base.actions.sound
import android.app.Activity
import android.content.Intent
@@ -21,21 +21,17 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
-import io.github.sds100.keymapper.databinding.FragmentChooseSoundFileBinding
-import io.github.sds100.keymapper.simple
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.databinding.FragmentChooseSoundFileBinding
+import io.github.sds100.keymapper.base.simple
+import io.github.sds100.keymapper.base.utils.ui.launchRepeatOnLifecycle
import io.github.sds100.keymapper.system.files.FileUtils
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.ui.showPopups
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.update
import kotlinx.serialization.json.Json
-/**
- * Created by sds100 on 22/06/2021.
- */
-
+@AndroidEntryPoint
class ChooseSoundFileFragment : Fragment() {
companion object {
const val EXTRA_RESULT = "extra_sound_file_result"
@@ -44,9 +40,7 @@ class ChooseSoundFileFragment : Fragment() {
private val args: ChooseSoundFileFragmentArgs by navArgs()
private val requestKey: String by lazy { args.requestKey }
- private val viewModel: ChooseSoundFileViewModel by viewModels {
- Inject.soundFileActionTypeViewModel(requireContext())
- }
+ private val viewModel: ChooseSoundFileViewModel by viewModels()
private val chooseSoundFileLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) {
@@ -98,7 +92,6 @@ class ChooseSoundFileFragment : Fragment() {
}
binding.viewModel = viewModel
- viewModel.showPopups(this, binding)
viewLifecycleOwner.launchRepeatOnLifecycle(Lifecycle.State.RESUMED) {
viewModel.chooseSoundFile.collectLatest {
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/ChooseSoundFileUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/ChooseSoundFileUseCase.kt
new file mode 100644
index 0000000000..7f6566006d
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/ChooseSoundFileUseCase.kt
@@ -0,0 +1,37 @@
+package io.github.sds100.keymapper.base.actions.sound
+
+import io.github.sds100.keymapper.common.utils.KMError
+import io.github.sds100.keymapper.common.utils.KMResult
+import io.github.sds100.keymapper.common.utils.Success
+import io.github.sds100.keymapper.system.files.FileAdapter
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+
+class ChooseSoundFileUseCaseImpl @Inject constructor(
+ private val fileAdapter: FileAdapter,
+ private val soundsManager: SoundsManager,
+) : ChooseSoundFileUseCase {
+ override val soundFiles = soundsManager.soundFiles
+
+ override suspend fun saveSound(uri: String): KMResult = soundsManager.saveNewSound(uri)
+
+ override fun getSoundFileName(uri: String): KMResult {
+ val name = fileAdapter.getFileFromUri(uri).name
+
+ return if (name == null) {
+ KMError.NoFileName
+ } else {
+ Success(name)
+ }
+ }
+}
+
+interface ChooseSoundFileUseCase {
+
+ /**
+ * @return the sound file uid
+ */
+ suspend fun saveSound(uri: String): KMResult
+ val soundFiles: StateFlow>
+ fun getSoundFileName(uri: String): KMResult
+}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/ChooseSoundFileViewModel.kt
similarity index 65%
rename from app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileViewModel.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/sound/ChooseSoundFileViewModel.kt
index 4967227dd6..f858528742 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileViewModel.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/ChooseSoundFileViewModel.kt
@@ -1,20 +1,19 @@
-package io.github.sds100.keymapper.actions.sound
+package io.github.sds100.keymapper.base.actions.sound
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.actions.ActionData
-import io.github.sds100.keymapper.util.getFullMessage
-import io.github.sds100.keymapper.util.onFailure
-import io.github.sds100.keymapper.util.onSuccess
-import io.github.sds100.keymapper.util.ui.DefaultSimpleListItem
-import io.github.sds100.keymapper.util.ui.PopupUi
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.PopupViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.showPopup
-import io.github.sds100.keymapper.util.valueOrNull
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.actions.ActionData
+import io.github.sds100.keymapper.base.utils.getFullMessage
+import io.github.sds100.keymapper.base.utils.ui.DefaultSimpleListItem
+import io.github.sds100.keymapper.base.utils.ui.DialogModel
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.showDialog
+import io.github.sds100.keymapper.common.utils.onFailure
+import io.github.sds100.keymapper.common.utils.onSuccess
+import io.github.sds100.keymapper.common.utils.valueOrNull
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -24,15 +23,15 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+import javax.inject.Inject
-/**
- * Created by sds100 on 31/03/2020.
- */
-class ChooseSoundFileViewModel(
+@HiltViewModel
+class ChooseSoundFileViewModel @Inject constructor(
resourceProvider: ResourceProvider,
+ dialogProvider: DialogProvider,
private val useCase: ChooseSoundFileUseCase,
) : ViewModel(),
- PopupViewModel by PopupViewModelImpl(),
+ DialogProvider by dialogProvider,
ResourceProvider by resourceProvider {
private val _chooseSoundFile = MutableSharedFlow()
@@ -67,13 +66,13 @@ class ChooseSoundFileViewModel(
viewModelScope.launch {
val soundFileInfo = useCase.soundFiles.value.find { it.uid == id } ?: return@launch
- val dialog = PopupUi.Text(
+ val dialog = DialogModel.Text(
hint = getString(R.string.hint_sound_file_description),
allowEmpty = false,
text = soundFileInfo.name,
)
- val soundDescription = showPopup("file_description", dialog) ?: return@launch
+ val soundDescription = showDialog("file_description", dialog) ?: return@launch
returnResult.update {
ActionData.Sound.SoundFile(
@@ -88,13 +87,13 @@ class ChooseSoundFileViewModel(
viewModelScope.launch {
val fileName = useCase.getSoundFileName(uri).valueOrNull() ?: return@launch
- val dialog = PopupUi.Text(
+ val dialog = DialogModel.Text(
hint = getString(R.string.hint_sound_file_description),
allowEmpty = false,
text = fileName,
)
- val soundDescription = showPopup("file_description", dialog)
+ val soundDescription = showDialog("file_description", dialog)
soundDescription ?: return@launch
@@ -107,8 +106,8 @@ class ChooseSoundFileViewModel(
)
}
}.onFailure { error ->
- val toast = PopupUi.Toast(error.getFullMessage(this@ChooseSoundFileViewModel))
- showPopup("failed_toast", toast)
+ val toast = DialogModel.Toast(error.getFullMessage(this@ChooseSoundFileViewModel))
+ showDialog("failed_toast", toast)
}
}
}
@@ -118,13 +117,4 @@ class ChooseSoundFileViewModel(
returnResult.update { ActionData.Sound.Ringtone(uri) }
}
}
-
- @Suppress("UNCHECKED_CAST")
- class Factory(
- private val resourceProvider: ResourceProvider,
- private val useCase: ChooseSoundFileUseCase,
- ) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T = ChooseSoundFileViewModel(resourceProvider, useCase) as T
- }
}
diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/SoundFileInfo.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/SoundFileInfo.kt
new file mode 100644
index 0000000000..a6faa7bf34
--- /dev/null
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/SoundFileInfo.kt
@@ -0,0 +1,3 @@
+package io.github.sds100.keymapper.base.actions.sound
+
+data class SoundFileInfo(val uid: String, val name: String)
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/sound/SoundsManager.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/SoundsManager.kt
similarity index 69%
rename from app/src/main/java/io/github/sds100/keymapper/actions/sound/SoundsManager.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/sound/SoundsManager.kt
index 8df6a65e38..a9ead62745 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/sound/SoundsManager.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/sound/SoundsManager.kt
@@ -1,25 +1,24 @@
-package io.github.sds100.keymapper.actions.sound
-
+package io.github.sds100.keymapper.base.actions.sound
+
+import io.github.sds100.keymapper.common.utils.KMError
+import io.github.sds100.keymapper.common.utils.KMResult
+import io.github.sds100.keymapper.common.utils.Success
+import io.github.sds100.keymapper.common.utils.onFailure
+import io.github.sds100.keymapper.common.utils.onSuccess
+import io.github.sds100.keymapper.common.utils.then
import io.github.sds100.keymapper.system.files.FileAdapter
import io.github.sds100.keymapper.system.files.IFile
-import io.github.sds100.keymapper.util.Error
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.Success
-import io.github.sds100.keymapper.util.onFailure
-import io.github.sds100.keymapper.util.onSuccess
-import io.github.sds100.keymapper.util.then
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
+import javax.inject.Inject
+import javax.inject.Singleton
-/**
- * Created by sds100 on 24/06/2021.
- */
-
-class SoundsManagerImpl(
+@Singleton
+class SoundsManagerImpl @Inject constructor(
private val coroutineScope: CoroutineScope,
private val fileAdapter: FileAdapter,
) : SoundsManager {
@@ -35,7 +34,7 @@ class SoundsManagerImpl(
}
}
- override suspend fun saveNewSound(uri: String): Result {
+ override suspend fun saveNewSound(uri: String): KMResult {
val uid = UUID.randomUUID().toString()
val soundFile = fileAdapter.getFileFromUri(uri)
@@ -48,13 +47,13 @@ class SoundsManagerImpl(
.then { Success(uid) }
.onSuccess { updateSoundFilesFlow() }
.onFailure {
- if (it is Error.Exception) {
+ if (it is KMError.Exception) {
Timber.d(it.exception)
}
}
}
- override suspend fun restoreSound(file: IFile): Result<*> {
+ override suspend fun restoreSound(file: IFile): KMResult<*> {
val soundsDir = fileAdapter.getPrivateFile(SOUNDS_DIR_NAME)
soundsDir.createDirectory()
@@ -69,20 +68,20 @@ class SoundsManagerImpl(
}
}
- override fun getSound(uid: String): Result {
+ override fun getSound(uid: String): KMResult {
val soundsDir = fileAdapter.getPrivateFile(SOUNDS_DIR_NAME)
soundsDir.createDirectory()
val matchingFile = soundsDir.listFiles()!!.find { it.name?.contains(uid) == true }
if (matchingFile == null) {
- return Error.CantFindSoundFile
+ return KMError.CantFindSoundFile
} else {
return Success(matchingFile)
}
}
- override fun deleteSound(uid: String): Result<*> = getSound(uid)
+ override fun deleteSound(uid: String): KMResult<*> = getSound(uid)
.then { Success(it.delete()) }
.onSuccess { updateSoundFilesFlow() }
@@ -106,12 +105,11 @@ class SoundsManagerImpl(
.map { getSoundFileInfo(it.name!!) }
}
- private fun createSoundCopyFileName(originalSoundFile: IFile, uid: String): String =
- buildString {
- append(originalSoundFile.baseName)
- append("_$uid")
- append(".${originalSoundFile.extension}")
- }
+ private fun createSoundCopyFileName(originalSoundFile: IFile, uid: String): String = buildString {
+ append(originalSoundFile.baseName)
+ append("_$uid")
+ append(".${originalSoundFile.extension}")
+ }
}
interface SoundsManager {
@@ -120,8 +118,8 @@ interface SoundsManager {
/**
* @return the sound file uid
*/
- suspend fun saveNewSound(uri: String): Result
- suspend fun restoreSound(file: IFile): Result<*>
- fun getSound(uid: String): Result
- fun deleteSound(uid: String): Result<*>
+ suspend fun saveNewSound(uri: String): KMResult
+ suspend fun restoreSound(file: IFile): KMResult<*>
+ fun getSound(uid: String): KMResult
+ fun deleteSound(uid: String): KMResult<*>
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/swipescreen/SwipePickCoordinateResult.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickCoordinateResult.kt
similarity index 81%
rename from app/src/main/java/io/github/sds100/keymapper/actions/swipescreen/SwipePickCoordinateResult.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickCoordinateResult.kt
index 0efe4ec64e..7f084200d0 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/swipescreen/SwipePickCoordinateResult.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickCoordinateResult.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.swipescreen
+package io.github.sds100.keymapper.base.actions.swipescreen
import kotlinx.serialization.Serializable
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/swipescreen/SwipePickDisplayCoordinateFragment.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateFragment.kt
similarity index 91%
rename from app/src/main/java/io/github/sds100/keymapper/actions/swipescreen/SwipePickDisplayCoordinateFragment.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateFragment.kt
index cfebafa3a9..888ae4826f 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/swipescreen/SwipePickDisplayCoordinateFragment.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateFragment.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.swipescreen
+package io.github.sds100.keymapper.base.actions.swipescreen
import android.annotation.SuppressLint
import android.graphics.Bitmap
@@ -20,15 +20,14 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
-import io.github.sds100.keymapper.databinding.FragmentSwipePickCoordinatesBinding
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.databinding.FragmentSwipePickCoordinatesBinding
+import io.github.sds100.keymapper.base.utils.ui.launchRepeatOnLifecycle
import io.github.sds100.keymapper.system.files.FileUtils
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.ui.showPopups
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
+@AndroidEntryPoint
class SwipePickDisplayCoordinateFragment : Fragment() {
companion object {
const val EXTRA_RESULT = "extra_result"
@@ -37,9 +36,7 @@ class SwipePickDisplayCoordinateFragment : Fragment() {
private val args: SwipePickDisplayCoordinateFragmentArgs by navArgs()
private val requestKey: String by lazy { args.requestKey }
- private val viewModel: SwipePickDisplayCoordinateViewModel by viewModels {
- Inject.swipeCoordinateActionTypeViewModel(requireContext())
- }
+ private val viewModel: SwipePickDisplayCoordinateViewModel by viewModels()
private val screenshotLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
@@ -99,8 +96,6 @@ class SwipePickDisplayCoordinateFragment : Fragment() {
binding.viewModel = viewModel
- viewModel.showPopups(this, binding)
-
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
findNavController().navigateUp()
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt
similarity index 89%
rename from app/src/main/java/io/github/sds100/keymapper/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt
index 6fbfe7cf64..c5afe9ad3f 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/swipescreen/SwipePickDisplayCoordinateViewModel.kt
@@ -1,18 +1,17 @@
-package io.github.sds100.keymapper.actions.swipescreen
+package io.github.sds100.keymapper.base.actions.swipescreen
import android.accessibilityservice.GestureDescription
import android.graphics.Bitmap
import android.graphics.Point
import android.os.Build
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.util.ui.PopupUi
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.PopupViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.showPopup
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.utils.ui.DialogModel
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.showDialog
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -23,6 +22,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import javax.inject.Inject
import kotlin.math.roundToInt
enum class ScreenshotTouchType {
@@ -30,11 +30,13 @@ enum class ScreenshotTouchType {
END,
}
-class SwipePickDisplayCoordinateViewModel(
+@HiltViewModel
+class SwipePickDisplayCoordinateViewModel @Inject constructor(
resourceProvider: ResourceProvider,
+ dialogProvider: DialogProvider,
) : ViewModel(),
ResourceProvider by resourceProvider,
- PopupViewModel by PopupViewModelImpl() {
+ DialogProvider by dialogProvider {
val screenshotTouchTypeStart = ScreenshotTouchType.START
val screenshotTouchTypeEnd = ScreenshotTouchType.END
@@ -162,11 +164,11 @@ class SwipePickDisplayCoordinateViewModel(
(displaySize.y != newBitmap.width && displaySize.x != newBitmap.height)
) {
viewModelScope.launch {
- val snackBar = PopupUi.SnackBar(
+ val snackBar = DialogModel.SnackBar(
message = getString(R.string.toast_incorrect_screenshot_resolution),
)
- showPopup("incorrect_resolution", snackBar)
+ showDialog("incorrect_resolution", snackBar)
}
return
@@ -231,9 +233,9 @@ class SwipePickDisplayCoordinateViewModel(
val fingerCount = fingerCount.value ?: return@launch
val duration = duration.value ?: return@launch
- val description = showPopup(
+ val description = showDialog(
"coordinate_description",
- PopupUi.Text(
+ DialogModel.Text(
getString(R.string.hint_tap_coordinate_title),
allowEmpty = true,
text = description.value ?: "",
@@ -272,13 +274,4 @@ class SwipePickDisplayCoordinateViewModel(
super.onCleared()
}
-
- @Suppress("UNCHECKED_CAST")
- class Factory(
- private val resourceProvider: ResourceProvider,
- ) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T =
- SwipePickDisplayCoordinateViewModel(resourceProvider) as T
- }
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickCoordinateImageView.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickCoordinateImageView.kt
similarity index 90%
rename from app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickCoordinateImageView.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickCoordinateImageView.kt
index 04e72d5bdc..fcbd125cf2 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickCoordinateImageView.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickCoordinateImageView.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.tapscreen
+package io.github.sds100.keymapper.base.actions.tapscreen
import android.content.Context
import android.graphics.Canvas
@@ -7,14 +7,11 @@ import android.graphics.Point
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.appcompat.widget.AppCompatImageView
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.util.color
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.utils.ui.color
import kotlinx.coroutines.flow.MutableStateFlow
import kotlin.math.roundToInt
-/**
- * Created by sds100 on 08/08/20.
- */
class PickCoordinateImageView(
context: Context,
attrs: AttributeSet?,
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickCoordinateResult.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickCoordinateResult.kt
similarity index 58%
rename from app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickCoordinateResult.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickCoordinateResult.kt
index cdb05e20b3..8c45352d20 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickCoordinateResult.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickCoordinateResult.kt
@@ -1,9 +1,6 @@
-package io.github.sds100.keymapper.actions.tapscreen
+package io.github.sds100.keymapper.base.actions.tapscreen
import kotlinx.serialization.Serializable
-/**
- * Created by sds100 on 25/03/2021.
- */
@Serializable
data class PickCoordinateResult(val x: Int, val y: Int, val description: String)
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickDisplayCoordinateFragment.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickDisplayCoordinateFragment.kt
similarity index 90%
rename from app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickDisplayCoordinateFragment.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickDisplayCoordinateFragment.kt
index 173e483c29..1de00aca0f 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickDisplayCoordinateFragment.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickDisplayCoordinateFragment.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.tapscreen
+package io.github.sds100.keymapper.base.actions.tapscreen
import android.annotation.SuppressLint
import android.graphics.Bitmap
@@ -20,19 +20,14 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
-import io.github.sds100.keymapper.databinding.FragmentPickCoordinateBinding
+import dagger.hilt.android.AndroidEntryPoint
+import io.github.sds100.keymapper.base.databinding.FragmentPickCoordinateBinding
+import io.github.sds100.keymapper.base.utils.ui.launchRepeatOnLifecycle
import io.github.sds100.keymapper.system.files.FileUtils
-import io.github.sds100.keymapper.util.Inject
-import io.github.sds100.keymapper.util.launchRepeatOnLifecycle
-import io.github.sds100.keymapper.util.ui.showPopups
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
-/**
- * Created by sds100 on 30/03/2020.
- */
-
+@AndroidEntryPoint
class PickDisplayCoordinateFragment : Fragment() {
companion object {
const val EXTRA_RESULT = "extra_result"
@@ -41,9 +36,7 @@ class PickDisplayCoordinateFragment : Fragment() {
private val args: PickDisplayCoordinateFragmentArgs by navArgs()
private val requestKey: String by lazy { args.requestKey }
- private val viewModel: PickDisplayCoordinateViewModel by viewModels {
- Inject.tapCoordinateActionTypeViewModel(requireContext())
- }
+ private val viewModel: PickDisplayCoordinateViewModel by viewModels()
private val screenshotLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
@@ -103,8 +96,6 @@ class PickDisplayCoordinateFragment : Fragment() {
binding.viewModel = viewModel
- viewModel.showPopups(this, binding)
-
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
findNavController().navigateUp()
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickDisplayCoordinateViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickDisplayCoordinateViewModel.kt
similarity index 78%
rename from app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickDisplayCoordinateViewModel.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickDisplayCoordinateViewModel.kt
index 98a8be7e09..86afc6dc0f 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/tapscreen/PickDisplayCoordinateViewModel.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/tapscreen/PickDisplayCoordinateViewModel.kt
@@ -1,16 +1,15 @@
-package io.github.sds100.keymapper.actions.tapscreen
+package io.github.sds100.keymapper.base.actions.tapscreen
import android.graphics.Bitmap
import android.graphics.Point
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.util.ui.PopupUi
-import io.github.sds100.keymapper.util.ui.PopupViewModel
-import io.github.sds100.keymapper.util.ui.PopupViewModelImpl
-import io.github.sds100.keymapper.util.ui.ResourceProvider
-import io.github.sds100.keymapper.util.ui.showPopup
+import dagger.hilt.android.lifecycle.HiltViewModel
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.utils.ui.DialogModel
+import io.github.sds100.keymapper.base.utils.ui.DialogProvider
+import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
+import io.github.sds100.keymapper.base.utils.ui.showDialog
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -21,17 +20,16 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import javax.inject.Inject
import kotlin.math.roundToInt
-/**
- * Created by sds100 on 03/08/20.
- */
-
-class PickDisplayCoordinateViewModel(
+@HiltViewModel
+class PickDisplayCoordinateViewModel @Inject constructor(
resourceProvider: ResourceProvider,
+ dialogProvider: DialogProvider,
) : ViewModel(),
ResourceProvider by resourceProvider,
- PopupViewModel by PopupViewModelImpl() {
+ DialogProvider by dialogProvider {
private val x = MutableStateFlow(null)
private val y = MutableStateFlow(null)
@@ -69,11 +67,11 @@ class PickDisplayCoordinateViewModel(
(displaySize.y != newBitmap.width && displaySize.x != newBitmap.height)
) {
viewModelScope.launch {
- val snackBar = PopupUi.SnackBar(
+ val snackBar = DialogModel.SnackBar(
message = getString(R.string.toast_incorrect_screenshot_resolution),
)
- showPopup("incorrect_resolution", snackBar)
+ showDialog("incorrect_resolution", snackBar)
}
return
@@ -109,9 +107,9 @@ class PickDisplayCoordinateViewModel(
val x = x.value ?: return@launch
val y = y.value ?: return@launch
- val description = showPopup(
+ val description = showDialog(
"coordinate_description",
- PopupUi.Text(
+ DialogModel.Text(
getString(R.string.hint_tap_coordinate_title),
allowEmpty = true,
text = description.value ?: "",
@@ -136,13 +134,4 @@ class PickDisplayCoordinateViewModel(
super.onCleared()
}
-
- @Suppress("UNCHECKED_CAST")
- class Factory(
- private val resourceProvider: ResourceProvider,
- ) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T =
- PickDisplayCoordinateViewModel(resourceProvider) as T
- }
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/uielement/ChooseUiElementScreen.kt
similarity index 96%
rename from app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/uielement/ChooseUiElementScreen.kt
index 340f304358..7da4fd9033 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/uielement/ChooseUiElementScreen.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.uielement
+package io.github.sds100.keymapper.base.actions.uielement
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -49,13 +49,14 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.window.core.layout.WindowHeightSizeClass
import androidx.window.core.layout.WindowWidthSizeClass
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.util.State
-import io.github.sds100.keymapper.util.ui.compose.CheckBoxText
-import io.github.sds100.keymapper.util.ui.compose.KeyMapperDropdownMenu
-import io.github.sds100.keymapper.util.ui.compose.SearchAppBarActions
-import io.github.sds100.keymapper.util.ui.compose.WindowSizeClassExt.compareTo
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.utils.ui.compose.CheckBoxText
+import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperDropdownMenu
+import io.github.sds100.keymapper.base.utils.ui.compose.SearchAppBarActions
+import io.github.sds100.keymapper.base.utils.ui.compose.WindowSizeClassExt.compareTo
+import io.github.sds100.keymapper.common.utils.NodeInteractionType
+import io.github.sds100.keymapper.common.utils.State
@Composable
fun ChooseElementScreen(
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/uielement/InteractUiElementScreen.kt
similarity index 95%
rename from app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/uielement/InteractUiElementScreen.kt
index 7ff879f499..30c0d792c5 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/uielement/InteractUiElementScreen.kt
@@ -1,4 +1,4 @@
-package io.github.sds100.keymapper.actions.uielement
+package io.github.sds100.keymapper.base.actions.uielement
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContentTransitionScope
@@ -69,19 +69,20 @@ import androidx.navigation.compose.rememberNavController
import androidx.window.core.layout.WindowHeightSizeClass
import androidx.window.core.layout.WindowWidthSizeClass
import com.google.accompanist.drawablepainter.rememberDrawablePainter
-import io.github.sds100.keymapper.R
-import io.github.sds100.keymapper.compose.KeyMapperTheme
-import io.github.sds100.keymapper.compose.LocalCustomColorsPalette
-import io.github.sds100.keymapper.system.apps.ChooseAppScreen
-import io.github.sds100.keymapper.util.State
-import io.github.sds100.keymapper.util.drawable
-import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo
-import io.github.sds100.keymapper.util.ui.compose.KeyMapperDropdownMenu
-import io.github.sds100.keymapper.util.ui.compose.OptionsHeaderRow
-import io.github.sds100.keymapper.util.ui.compose.WindowSizeClassExt.compareTo
-import io.github.sds100.keymapper.util.ui.compose.icons.AdGroup
-import io.github.sds100.keymapper.util.ui.compose.icons.JumpToElement
-import io.github.sds100.keymapper.util.ui.compose.icons.KeyMapperIcons
+import io.github.sds100.keymapper.base.R
+import io.github.sds100.keymapper.base.compose.KeyMapperTheme
+import io.github.sds100.keymapper.base.compose.LocalCustomColorsPalette
+import io.github.sds100.keymapper.base.system.apps.ChooseAppScreen
+import io.github.sds100.keymapper.base.utils.ui.compose.ComposeIconInfo
+import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperDropdownMenu
+import io.github.sds100.keymapper.base.utils.ui.compose.OptionsHeaderRow
+import io.github.sds100.keymapper.base.utils.ui.compose.WindowSizeClassExt.compareTo
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.AdGroup
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.JumpToElement
+import io.github.sds100.keymapper.base.utils.ui.compose.icons.KeyMapperIcons
+import io.github.sds100.keymapper.base.utils.ui.drawable
+import io.github.sds100.keymapper.common.utils.NodeInteractionType
+import io.github.sds100.keymapper.common.utils.State
import kotlinx.coroutines.flow.update
private const val DEST_LANDING = "landing"
@@ -92,7 +93,6 @@ private const val DEST_SELECT_ELEMENT = "select_element"
fun InteractUiElementScreen(
modifier: Modifier = Modifier,
viewModel: InteractUiElementViewModel,
- navigateBack: () -> Unit,
) {
val navController = rememberNavController()
@@ -107,7 +107,7 @@ fun InteractUiElementScreen(
val onBackClick = {
if (!navController.navigateUp()) {
- navigateBack()
+ viewModel.onBackClick()
}
}
@@ -381,11 +381,11 @@ private fun RecordingSection(
openSelectAppScreen: () -> Unit = {},
) {
Column(modifier = modifier) {
- when (state) {
+ when (val state = state) {
is State.Data -> {
- val interactionCount: Int = when (state.data) {
- is RecordUiElementState.CountingDown -> state.data.interactionCount
- is RecordUiElementState.Recorded -> state.data.interactionCount
+ val interactionCount: Int = when (val data = state.data) {
+ is RecordUiElementState.CountingDown -> data.interactionCount
+ is RecordUiElementState.Recorded -> data.interactionCount
RecordUiElementState.Empty -> 0
}
diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/uielement/InteractUiElementUseCase.kt
similarity index 64%
rename from app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementUseCase.kt
rename to base/src/main/java/io/github/sds100/keymapper/base/actions/uielement/InteractUiElementUseCase.kt
index 9a325f22f3..e97be98810 100644
--- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementUseCase.kt
+++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/uielement/InteractUiElementUseCase.kt
@@ -1,16 +1,16 @@
-package io.github.sds100.keymapper.actions.uielement
+package io.github.sds100.keymapper.base.actions.uielement
import android.graphics.drawable.Drawable
+import io.github.sds100.keymapper.base.system.accessibility.RecordAccessibilityNodeEvent
+import io.github.sds100.keymapper.base.system.accessibility.RecordAccessibilityNodeState
+import io.github.sds100.keymapper.common.utils.KMResult
+import io.github.sds100.keymapper.common.utils.State
+import io.github.sds100.keymapper.common.utils.mapData
+import io.github.sds100.keymapper.common.utils.onFailure
import io.github.sds100.keymapper.data.entities.AccessibilityNodeEntity
import io.github.sds100.keymapper.data.repositories.AccessibilityNodeRepository
-import io.github.sds100.keymapper.system.accessibility.RecordAccessibilityNodeState
-import io.github.sds100.keymapper.system.accessibility.ServiceAdapter
+import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter
import io.github.sds100.keymapper.system.apps.PackageManagerAdapter
-import io.github.sds100.keymapper.util.Result
-import io.github.sds100.keymapper.util.ServiceEvent
-import io.github.sds100.keymapper.util.State
-import io.github.sds100.keymapper.util.mapData
-import io.github.sds100.keymapper.util.onFailure
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -20,10 +20,13 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
+import javax.inject.Inject
+import javax.inject.Singleton
-class InteractUiElementController(
+@Singleton
+class InteractUiElementController @Inject constructor(
private val coroutineScope: CoroutineScope,
- private val serviceAdapter: ServiceAdapter,
+ private val serviceAdapter: AccessibilityServiceAdapter,
private val nodeRepository: AccessibilityNodeRepository,
private val packageManagerAdapter: PackageManagerAdapter,
) : InteractUiElementUseCase {
@@ -41,7 +44,7 @@ class InteractUiElementController(
init {
serviceAdapter.eventReceiver
- .filterIsInstance()
+ .filterIsInstance()
.onEach { event -> recordState.update { event.state } }
.launchIn(coroutineScope)
}
@@ -58,17 +61,17 @@ class InteractUiElementController(
return nodeRepository.get(id)
}
- override fun getAppName(packageName: String): Result = packageManagerAdapter.getAppName(packageName)
+ override fun getAppName(packageName: String): KMResult = packageManagerAdapter.getAppName(packageName)
- override fun getAppIcon(packageName: String): Result = packageManagerAdapter.getAppIcon(packageName)
+ override fun getAppIcon(packageName: String): KMResult