From fda153770e2bbd7da6b45fa17ca5fc97f0e10580 Mon Sep 17 00:00:00 2001 From: linsui <36977733+linsui@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:56:07 +0000 Subject: [PATCH 001/181] CI: update fdroidserver image to bookworm (#482) Co-authored-by: linsui --- .github/workflows/fdroid.yml | 2 +- app/org.fcitx.fcitx5.android.yml | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/fdroid.yml b/.github/workflows/fdroid.yml index ab4ac517f..3967d8a06 100644 --- a/.github/workflows/fdroid.yml +++ b/.github/workflows/fdroid.yml @@ -21,7 +21,7 @@ defaults: jobs: fdroid-build: runs-on: ubuntu-latest - container: registry.gitlab.com/fdroid/fdroidserver:buildserver-bullseye + container: registry.gitlab.com/fdroid/fdroidserver:buildserver-bookworm strategy: matrix: abi: diff --git a/app/org.fcitx.fcitx5.android.yml b/app/org.fcitx.fcitx5.android.yml index c99e68d07..6c1dd0953 100644 --- a/app/org.fcitx.fcitx5.android.yml +++ b/app/org.fcitx.fcitx5.android.yml @@ -21,10 +21,9 @@ Builds: sudo: - apt-get update - apt-get install -y g++ libtool make automake gettext bzip2 xz-utils zstd pkg-config - cmake extra-cmake-modules ninja-build libfmt-dev libboost-all-dev libfcitx5utils-dev opencc openjdk-17-jdk-headless - ghc cabal-install libghc-shake-dev libghc-aeson-pretty-dev libghc-js-flot-data haskell-js-dgtable-utils - - update-java-alternatives -a - - apt-get install -y -t bullseye-backports fcitx5-modules + cmake extra-cmake-modules ninja-build libfmt-dev libboost-all-dev libfcitx5utils-dev opencc + ghc cabal-install libghc-shake-dev libghc-aeson-pretty-dev libghc-js-flot-data + haskell-js-dgtable-utils fcitx5-modules python-is-python3 gradle: - yes binary: https://jenkins.fcitx-im.org/job/android/job/fcitx5-android/lastSuccessfulBuild/artifact/out/org.fcitx.fcitx5.android-%v-%abi-release.apk From 59558c5b624359455911082b10750f4dcbd10fe8 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sun, 28 Apr 2024 15:01:27 +0800 Subject: [PATCH 002/181] Use form for issue template --- .github/ISSUE_TEMPLATE/bug-report.md | 52 ------------- .github/ISSUE_TEMPLATE/bug_report.yaml | 83 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 12 +++ .github/ISSUE_TEMPLATE/feature-request.md | 26 ------- .github/ISSUE_TEMPLATE/feature_request.yaml | 55 ++++++++++++++ 5 files changed, 150 insertions(+), 78 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml delete mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 2121e9d4d..000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: 问题报告 / Bug Report -about: 创建问题报告以帮助我们改进 / Create a bug report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -#### 摘要 / Summary - - -#### 重现步骤 / Steps to Reproduce - - -#### 预期行为 / Expected Behavior - - -#### 日志 / Log - - -#### 截图 / Screenshots - - -#### 设备信息 / Device Infomation - -- 系统版本 / OS Version: -- 应用版本 / App Version: -- 插件版本 / Plugins Version: - -#### 附加信息 / Additional Context - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 000000000..9a79cd21b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,83 @@ +name: 问题报告 / Bug Report +description: 创建问题报告以帮助我们改进 / Create a bug report to help us improve +labels: + - bug + +body: + - type: textarea + id: summary + attributes: + label: 摘要 / Summary + description: 简要描述遇到的问题。 / Briefly describe the bug. + validations: + required: true + + - type: textarea + id: step_to_reproduce + attributes: + label: 重现步骤 / Steps to Reproduce + description: 如何重现该问题。 / How to reproduce the bug. + placeholder: | + 1. 打开某界面 / Open page ... + 2. 点击某菜单 / Click menu ... + 3. 某处出问题 / Something went wrong ... + validations: + required: true + + - type: textarea + id: expected_behavior + attributes: + label: 预期行为 / Expected Behavior + description: 完成上述步骤后应该发生什么。 / What is expected to happen after the steps above. + validations: + required: true + + - type: textarea + id: log + attributes: + label: 日志 / Log + description: 附上日志以帮助定位问题。 / Attach log to help locate the bug. + validations: + required: false + + - type: textarea + id: screenshot + attributes: + label: 截图 / Screenshot + description: 附上截图以帮助解释问题。 / Attach screenshots to help explain the bug. + validations: + required: false + + - type: textarea + id: additional_context + attributes: + label: 附加信息 / Additional Context + description: 与此问题相关的上下文信息,比如在问题出现前做了什么。 / Additional context about the bug, eg. what did you do before the bug appears. + validations: + required: false + + - type: markdown + attributes: + value: | + ### 设备信息 / Device Infomation + + - type: input + id: os_version + attributes: + label: 系统版本 / OS Version + validations: + required: true + + - type: input + id: app_version + attributes: + label: 应用版本 / App Version + validations: + required: true + + - type: input + id: plugins_version + attributes: + label: 插件版本 / Plugins Version + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 99d680b0a..53f649c23 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,2 +1,14 @@ blank_issues_enabled: false +contact_links: + - name: GitHub 讨论区 / GitHub Discussions + url: https://github.com/fcitx5-android/fcitx5-android/discussions + about: 请在这里提出有关如何使用本输入法的疑问。 / Please ask questions about how to use the input method here. + + - name: Telegram Group / Telegram 群组 + url: https://t.me/fcitx5_android_group + about: 也可以群组中提问或讨论新功能。 / You may also ask questions or discuss new features in the group. + + - name: Matrix Room / Matrix 房间 + url: https://matrix.to/#/#fcitx5-android:mozilla.org + about: Matrix 房间与 Telegram 群组通过桥接机器人互通。 / Matrix Room and Telegram Group are connected through bridge bot. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md deleted file mode 100644 index 580097f71..000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: 功能请求 / Feature Request -about: 为本项目提供新功能建议 / Suggest a new feature for this project -title: '' -labels: enhancement -assignees: '' - ---- - -#### 摘要 / Summary - - -#### 替代方案 / Alternative Solution - - -#### 附加信息 / Additional Context - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 000000000..30f66b8bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,55 @@ +name: 功能请求 / Feature Request +description: 为本项目提供新功能建议 / Suggest a new feature for this project +labels: + - enhancement + +body: + - type: textarea + id: summary + attributes: + label: 摘要 / Summary + description: 新功能应该做什么。 / What the new feature should do. + validations: + required: true + + - type: textarea + id: alternative + attributes: + label: 替代方案 / Alternative Solution + description: 其它可能的解决方案(如果有)。 / Other possible solutions, if any. + validations: + required: false + + - type: textarea + id: additional_context + attributes: + label: 附加信息 / Additional Context + description: 与此功能请求有关的上下文信息或截图。 / Additional context or screenshots about the feature request. + validations: + required: false + + - type: markdown + attributes: + value: | + ### 设备信息 / Device Infomation + + - type: input + id: os_version + attributes: + label: 系统版本 / OS Version + validations: + required: true + + - type: input + id: app_version + attributes: + label: 应用版本 / App Version + validations: + required: true + + - type: input + id: plugins_version + attributes: + label: 插件版本 / Plugins Version + validations: + required: false From 9ed95181ce659c178ac5f63f0162858517fb51ba Mon Sep 17 00:00:00 2001 From: Rocka Date: Sun, 19 May 2024 22:50:21 +0800 Subject: [PATCH 003/181] Change all submodule url to https --- .gitmodules | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.gitmodules b/.gitmodules index 67596fc97..298284f3e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,54 +1,54 @@ [submodule "lib/fcitx5/src/main/cpp/fcitx5"] path = lib/fcitx5/src/main/cpp/fcitx5 - url = git@github.com:fcitx/fcitx5.git + url = https://github.com/fcitx/fcitx5.git [submodule "lib/fcitx5/src/main/cpp/prebuilt"] path = lib/fcitx5/src/main/cpp/prebuilt - url = git@github.com:fcitx5-android/prebuilt.git + url = https://github.com/fcitx5-android/prebuilt.git [submodule "lib/fcitx5-lua/src/main/cpp/fcitx5-lua"] path = lib/fcitx5-lua/src/main/cpp/fcitx5-lua - url = git@github.com:fcitx/fcitx5-lua.git + url = https://github.com/fcitx/fcitx5-lua.git [submodule "lib/libime/src/main/cpp/libime"] path = lib/libime/src/main/cpp/libime - url = git@github.com:fcitx/libime.git + url = https://github.com/fcitx/libime.git [submodule "lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons"] path = lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons - url = git@github.com:fcitx/fcitx5-chinese-addons.git + url = https://github.com/fcitx/fcitx5-chinese-addons.git [submodule "plugin/anthy/src/main/cpp/anthy-cmake"] path = plugin/anthy/src/main/cpp/anthy-cmake - url = git@github.com:fcitx5-android/anthy-cmake.git + url = https://github.com/fcitx5-android/anthy-cmake.git [submodule "plugin/anthy/src/main/cpp/fcitx5-anthy"] path = plugin/anthy/src/main/cpp/fcitx5-anthy - url = git@github.com:fcitx/fcitx5-anthy.git + url = https://github.com/fcitx/fcitx5-anthy.git [submodule "plugin/unikey/src/main/cpp/fcitx5-unikey"] path = plugin/unikey/src/main/cpp/fcitx5-unikey - url = git@github.com:fcitx/fcitx5-unikey.git + url = https://github.com/fcitx/fcitx5-unikey.git [submodule "plugin/rime/src/main/cpp/fcitx5-rime"] path = plugin/rime/src/main/cpp/fcitx5-rime - url = git@github.com:fcitx/fcitx5-rime.git + url = https://github.com/fcitx/fcitx5-rime.git [submodule "plugin/rime/src/main/cpp/rime-prelude"] path = plugin/rime/src/main/cpp/rime-prelude - url = git@github.com:rime/rime-prelude.git + url = https://github.com/rime/rime-prelude.git [submodule "plugin/rime/src/main/cpp/rime-essay"] path = plugin/rime/src/main/cpp/rime-essay - url = git@github.com:rime/rime-essay.git + url = https://github.com/rime/rime-essay.git [submodule "plugin/rime/src/main/cpp/rime-luna-pinyin"] path = plugin/rime/src/main/cpp/rime-luna-pinyin - url = git@github.com:rime/rime-luna-pinyin.git + url = https://github.com/rime/rime-luna-pinyin.git [submodule "plugin/rime/src/main/cpp/rime-stroke"] path = plugin/rime/src/main/cpp/rime-stroke - url = git@github.com:rime/rime-stroke.git + url = https://github.com/rime/rime-stroke.git [submodule "plugin/hangul/src/main/cpp/fcitx5-hangul"] path = plugin/hangul/src/main/cpp/fcitx5-hangul - url = git@github.com:fcitx/fcitx5-hangul.git + url = https://github.com/fcitx/fcitx5-hangul.git [submodule "plugin/chewing/src/main/cpp/fcitx5-chewing"] path = plugin/chewing/src/main/cpp/fcitx5-chewing - url = git@github.com:fcitx/fcitx5-chewing.git + url = https://github.com/fcitx/fcitx5-chewing.git [submodule "plugin/sayura/src/main/cpp/fcitx5-sayura"] path = plugin/sayura/src/main/cpp/fcitx5-sayura - url = git@github.com:fcitx/fcitx5-sayura.git + url = https://github.com/fcitx/fcitx5-sayura.git [submodule "plugin/jyutping/src/main/cpp/libime-jyutping"] path = plugin/jyutping/src/main/cpp/libime-jyutping - url = git@github.com:fcitx/libime-jyutping.git + url = https://github.com/fcitx/libime-jyutping.git [submodule "plugin/clipboard-filter/ClearURLsRules"] path = plugin/clipboard-filter/ClearURLsRules - url = git@github.com:ClearURLs/Rules.git + url = https://github.com/ClearURLs/Rules.git From faf44b619acfc402d283491660ab460187611868 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sun, 19 May 2024 22:59:06 +0800 Subject: [PATCH 004/181] Assign default externalBooleanStates to all EventStateMachine --- .../input/bar/ExpandButtonStateMachine.kt | 7 ++++++- .../android/input/bar/KawaiiBarStateMachine.kt | 10 ++++++++-- .../input/clipboard/ClipboardStateMachine.kt | 10 ++++++++-- .../android/input/clipboard/ClipboardWindow.kt | 18 +++++++----------- .../fcitx5/android/utils/EventStateMachine.kt | 2 +- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/bar/ExpandButtonStateMachine.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/bar/ExpandButtonStateMachine.kt index 7263f3849..3319ef324 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/bar/ExpandButtonStateMachine.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/bar/ExpandButtonStateMachine.kt @@ -40,7 +40,12 @@ object ExpandButtonStateMachine { } fun new(block: (State) -> Unit) = - EventStateMachine(Hidden).apply { + EventStateMachine( + initialState = Hidden, + externalBooleanStates = mutableMapOf( + ExpandedCandidatesEmpty to true + ) + ).apply { onNewStateListener = block } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/bar/KawaiiBarStateMachine.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/bar/KawaiiBarStateMachine.kt index fc1db5cbf..2deed8025 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/bar/KawaiiBarStateMachine.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/bar/KawaiiBarStateMachine.kt @@ -6,7 +6,9 @@ package org.fcitx.fcitx5.android.input.bar import org.fcitx.fcitx5.android.input.bar.KawaiiBarStateMachine.BooleanKey.CandidateEmpty import org.fcitx.fcitx5.android.input.bar.KawaiiBarStateMachine.BooleanKey.PreeditEmpty -import org.fcitx.fcitx5.android.input.bar.KawaiiBarStateMachine.State.* +import org.fcitx.fcitx5.android.input.bar.KawaiiBarStateMachine.State.Candidate +import org.fcitx.fcitx5.android.input.bar.KawaiiBarStateMachine.State.Idle +import org.fcitx.fcitx5.android.input.bar.KawaiiBarStateMachine.State.Title import org.fcitx.fcitx5.android.utils.BuildTransitionEvent import org.fcitx.fcitx5.android.utils.EventStateMachine import org.fcitx.fcitx5.android.utils.TransitionBuildBlock @@ -47,7 +49,11 @@ object KawaiiBarStateMachine { fun new(block: (State) -> Unit) = EventStateMachine( - Idle + initialState = Idle, + externalBooleanStates = mutableMapOf( + PreeditEmpty to true, + CandidateEmpty to true + ) ).apply { onNewStateListener = block } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardStateMachine.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardStateMachine.kt index adc70d806..aa5e705d0 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardStateMachine.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardStateMachine.kt @@ -42,8 +42,14 @@ object ClipboardStateMachine { }) } - fun new(initialState: State, block: (State) -> Unit) = - EventStateMachine(initialState).apply { + fun new(initial: State, empty: Boolean, listening: Boolean, block: (State) -> Unit) = + EventStateMachine( + initialState = initial, + externalBooleanStates = mutableMapOf( + ClipboardDbEmpty to empty, + ClipboardListeningEnabled to listening + ) + ).apply { onNewStateListener = block } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt index 993bedaf2..b8d53e0f2 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt @@ -48,7 +48,6 @@ import org.mechdancer.dependency.manager.must import splitties.dimensions.dp import splitties.resources.styledColor import splitties.views.dsl.core.withTheme -import kotlin.properties.Delegates class ClipboardWindow : InputWindow.ExtendedInputWindow() { @@ -62,12 +61,6 @@ class ClipboardWindow : InputWindow.ExtendedInputWindow() { private lateinit var stateMachine: EventStateMachine - private var isClipboardDbEmpty by Delegates.observable(ClipboardManager.itemCount == 0) { _, _, new -> - stateMachine.push( - ClipboardDbUpdated, ClipboardDbEmpty to new - ) - } - @Keep private val clipboardEnabledListener = ManagedPreference.OnChangeListener { _, it -> stateMachine.push( @@ -227,18 +220,21 @@ class ClipboardWindow : InputWindow.ExtendedInputWindow() { } override fun onAttached() { + val isEmpty = ClipboardManager.itemCount == 0 + val isListening = clipboardEnabledPref.getValue() val initialState = when { - !clipboardEnabledPref.getValue() -> EnableListening - isClipboardDbEmpty -> AddMore + !isListening -> EnableListening + isEmpty -> AddMore else -> Normal } - stateMachine = ClipboardStateMachine.new(initialState) { + stateMachine = ClipboardStateMachine.new(initialState, isEmpty, isListening) { ui.switchUiByState(it) } // manually switch to initial ui ui.switchUiByState(initialState) adapter.addLoadStateListener { - isClipboardDbEmpty = it.append.endOfPaginationReached && adapter.itemCount < 1 + val empty = it.append.endOfPaginationReached && adapter.itemCount < 1 + stateMachine.push(ClipboardDbUpdated, ClipboardDbEmpty to empty) } adapterSubmitJob = service.lifecycleScope.launch { clipboardEntriesPager.flow.collect { diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/EventStateMachine.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/EventStateMachine.kt index 8a55716f8..508e316eb 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/utils/EventStateMachine.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/utils/EventStateMachine.kt @@ -9,7 +9,7 @@ import timber.log.Timber class EventStateMachine, B : EventStateMachine.BooleanStateKey>( private val initialState: State, - private val externalBooleanStates: MutableMap = mutableMapOf() + private val externalBooleanStates: MutableMap ) { interface BooleanStateKey { From c8f6d25eb37b0be757d3ae99cf7969475e0c3eac Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 1 Jun 2024 20:36:07 +0800 Subject: [PATCH 005/181] Update fcitx5-rime submodule and prebuilt librime --- lib/fcitx5/src/main/cpp/prebuilt | 2 +- plugin/rime/licenses/libraries/librime.json | 2 +- plugin/rime/licenses/libraries/rime-luna-pinyin.json | 2 +- plugin/rime/licenses/libraries/rime-prelude.json | 2 +- plugin/rime/src/main/cpp/fcitx5-rime | 2 +- plugin/rime/src/main/cpp/rime-luna-pinyin | 2 +- plugin/rime/src/main/cpp/rime-prelude | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/fcitx5/src/main/cpp/prebuilt b/lib/fcitx5/src/main/cpp/prebuilt index 92d78b141..4ee83775a 160000 --- a/lib/fcitx5/src/main/cpp/prebuilt +++ b/lib/fcitx5/src/main/cpp/prebuilt @@ -1 +1 @@ -Subproject commit 92d78b141965f7b5d3ce61469c7140bf2271cfd9 +Subproject commit 4ee83775a997ca01c2aefaeecadc8a580c49d2b9 diff --git a/plugin/rime/licenses/libraries/librime.json b/plugin/rime/licenses/libraries/librime.json index bbf28f05b..bbfab4f1a 100644 --- a/plugin/rime/licenses/libraries/librime.json +++ b/plugin/rime/licenses/libraries/librime.json @@ -1,6 +1,6 @@ { "uniqueId": "rime/librime", - "artifactVersion": "1.11.0", + "artifactVersion": "1.11.2", "description": "Rime Input Method Engine", "name": "rime/librime", "website": "https://github.com/rime/librime", diff --git a/plugin/rime/licenses/libraries/rime-luna-pinyin.json b/plugin/rime/licenses/libraries/rime-luna-pinyin.json index 9c06d4187..db2de949e 100644 --- a/plugin/rime/licenses/libraries/rime-luna-pinyin.json +++ b/plugin/rime/licenses/libraries/rime-luna-pinyin.json @@ -1,6 +1,6 @@ { "uniqueId": "rime/rime-luna-pinyin", - "artifactVersion": "79aeae2", + "artifactVersion": "44e555d", "description": "Rime 預設的拼音輸入方案", "name": "rime/rime-luna-pinyin", "website": "https://github.com/rime/rime-luna-pinyin", diff --git a/plugin/rime/licenses/libraries/rime-prelude.json b/plugin/rime/licenses/libraries/rime-prelude.json index d20c98aa8..c90a79502 100644 --- a/plugin/rime/licenses/libraries/rime-prelude.json +++ b/plugin/rime/licenses/libraries/rime-prelude.json @@ -1,6 +1,6 @@ { "uniqueId": "rime/rime-prelude", - "artifactVersion": "dd84abe", + "artifactVersion": "3803f09", "description": "Essential files for building up your Rime configuration", "name": "rime/rime-prelude", "website": "https://github.com/rime/rime-prelude", diff --git a/plugin/rime/src/main/cpp/fcitx5-rime b/plugin/rime/src/main/cpp/fcitx5-rime index a5b5bf9f3..70f294e76 160000 --- a/plugin/rime/src/main/cpp/fcitx5-rime +++ b/plugin/rime/src/main/cpp/fcitx5-rime @@ -1 +1 @@ -Subproject commit a5b5bf9f3928c1e9aebd95ed35f6be43d496f3e7 +Subproject commit 70f294e76ddc15f0cafdb7174e1db6a7b6f3db8f diff --git a/plugin/rime/src/main/cpp/rime-luna-pinyin b/plugin/rime/src/main/cpp/rime-luna-pinyin index 79aeae200..44e555d10 160000 --- a/plugin/rime/src/main/cpp/rime-luna-pinyin +++ b/plugin/rime/src/main/cpp/rime-luna-pinyin @@ -1 +1 @@ -Subproject commit 79aeae200a7370720be98232844c0715f277e1c0 +Subproject commit 44e555d1090a56d62a41a58153088406bcf87abd diff --git a/plugin/rime/src/main/cpp/rime-prelude b/plugin/rime/src/main/cpp/rime-prelude index dd84abecc..3803f0945 160000 --- a/plugin/rime/src/main/cpp/rime-prelude +++ b/plugin/rime/src/main/cpp/rime-prelude @@ -1 +1 @@ -Subproject commit dd84abecc33f0b05469f1d744e32d2b60b3529e3 +Subproject commit 3803f09458072e03b9ed396692ce7e1d35c88c95 From 8d359a8b4d282dc1a3bb687c47c4199913ed9c70 Mon Sep 17 00:00:00 2001 From: Weng Xuetian Date: Sat, 1 Jun 2024 05:46:00 -0700 Subject: [PATCH 006/181] Add mapping to other brace characters on top of current "(" & ")" (#494) --- .../java/org/fcitx/fcitx5/android/input/popup/PopupPreset.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupPreset.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupPreset.kt index 5761466ce..5446da597 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupPreset.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupPreset.kt @@ -29,8 +29,8 @@ val PopupPreset: Map> = hashMapOf( "g" to arrayOf("=", "G", "ğ"), "h" to arrayOf("/", "H"), "j" to arrayOf("#", "J"), - "k" to arrayOf("(", "K"), - "l" to arrayOf(")", "L", "ł"), + "k" to arrayOf("(", "[", "{", "K"), + "l" to arrayOf(")", "]", "}", "L", "ł"), "z" to arrayOf("'", "Z", "`", "ž", "ź", "ż"), "x" to arrayOf(":", "X", "×"), "c" to arrayOf("\"", "C", "ç", "ć", "č"), From cf92f68485ca51dcb49a5acc1d12211afd49da3b Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 1 Jun 2024 21:08:35 +0800 Subject: [PATCH 007/181] Remove discouraged degree celsius/fahrenheit symbols --- .../fcitx/fcitx5/android/input/picker/PickerData.kt | 12 ++++++------ .../fcitx/fcitx5/android/input/popup/PopupPreset.kt | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerData.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerData.kt index 7dc848d02..d23b9b7d0 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerData.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerData.kt @@ -19,7 +19,7 @@ object PickerData { "'", "\"", "=", "_", "`", ":", ";", "?", "~", "|", "+", "-", "\\", "/", "[", "]", "{", "}", "<", ">", "“", "”", "·", "‘", "’", "¡", "¿", "¥", - "€", "£", "¢", "©", "®", "™", "℃", "℉", + "€", "£", "¢", "©", "®", "℗", "™", "℠", "°", "§", "№", "†", "‡", "‥", "…", "‰", "※", "‾", "⁄", "‼", "⁇", "⁈", "⁉", "√", "π", "±", "×", "÷", "¶", "∆", "¤", "µ", "‹", "›", "«", "»" @@ -152,7 +152,7 @@ object PickerData { "🪴", "🌲", "🌳", "🌴", "🌵", "🌾", "🌿", "☘️", "🍀", "🍁", "🍂", "🍃", "🪹", "🪺" ), - Category("🎂", R.drawable.ic_baseline_cake_24) to arrayOf ( + Category("🎂", R.drawable.ic_baseline_cake_24) to arrayOf( "🍇", "🍈", "🍉", "🍊", "🍋", "🍌", "🍍", "🥭", "🍎", "🍏", "🍐", "🍑", "🍒", "🍓", "🫐", "🥝", "🍅", "🫒", "🥥", "🥑", "🍆", "🥔", "🥕", "🌽", "🌶️", "🫑", "🥒", "🥬", "🥦", "🧄", "🧅", "🍄", "🥜", "🫘", "🌰", "🍞", "🥐", "🥖", "🫓", @@ -165,7 +165,7 @@ object PickerData { "🥂", "🥃", "🫗", "🥤", "🧋", "🧃", "🧉", "🧊", "🥢", "🍽️", "🍴", "🥄", "🔪", "🫙", "🏺" ), - Category("🚘", R.drawable.ic_baseline_directions_car_24) to arrayOf ( + Category("🚘", R.drawable.ic_baseline_directions_car_24) to arrayOf( "🌍", "🌎", "🌏", "🌐", "🗺️", "🗾", "🧭", "🏔️", "⛰️", "🌋", "🗻", "🏕️", "🏖️", "🏜️", "🏝️", "🏞️", "🏟️", "🏛️", "🏗️", "🧱", "🪨", "🪵", "🛖", "🏘️", "🏚️", "🏠", "🏡", "🏢", "🏣", "🏤", "🏥", "🏦", "🏨", "🏩", "🏪", "🏫", "🏬", "🏭", "🏯", @@ -184,7 +184,7 @@ object PickerData { "🌤️", "🌥️", "🌦️", "🌧️", "🌨️", "🌩️", "🌪️", "🌫️", "🌬️", "🌀", "🌈", "🌂", "☂️", "☔", "⛱️", "⚡", "❄️", "☃️", "⛄", "☄️", "🔥", "💧", "🌊" ), - Category("⚽", R.drawable.ic_baseline_sports_basketball_24) to arrayOf ( + Category("⚽", R.drawable.ic_baseline_sports_basketball_24) to arrayOf( "🎃", "🎄", "🎆", "🎇", "🧨", "✨", "🎈", "🎉", "🎊", "🎋", "🎍", "🎎", "🎏", "🎐", "🎑", "🧧", "🎀", "🎁", "🎗️", "🎟️", "🎫", "🎖️", "🏆", "🏅", "🥇", "🥈", "🥉", "⚽", "⚾", "🥎", "🏀", "🏐", "🏈", "🏉", "🎾", "🥏", "🎳", "🏏", "🏑", @@ -193,7 +193,7 @@ object PickerData { "🎲", "🧩", "🧸", "🪅", "🪩", "🪆", "♠️", "♥️", "♦️", "♣️", "♟️", "🃏", "🀄", "🎴", "🎭", "🖼️", "🎨", "🧵", "🪡", "🧶", "🪢" ), - Category("💡", R.drawable.ic_baseline_emoji_objects_24) to arrayOf ( + Category("💡", R.drawable.ic_baseline_emoji_objects_24) to arrayOf( "👓", "🕶️", "🥽", "🥼", "🦺", "👔", "👕", "👖", "🧣", "🧤", "🧥", "🧦", "👗", "👘", "🥻", "🩱", "🩲", "🩳", "👙", "👚", "👛", "👜", "👝", "🛍️", "🎒", "🩴", "👞", "👟", "🥾", "🥿", "👠", "👡", "🩰", "👢", "👑", "👒", "🎩", "🎓", "🧢", @@ -215,7 +215,7 @@ object PickerData { "🪤", "🪒", "🧴", "🧷", "🧹", "🧺", "🧻", "🪣", "🧼", "🫧", "🪥", "🧽", "🧯", "🛒", "🚬", "⚰️", "🪦", "⚱️", "🗿", "🪧", "🪪" ), - Category("🔣", R.drawable.ic_baseline_emoji_symbols_24) to arrayOf( + Category("🔣", R.drawable.ic_baseline_emoji_symbols_24) to arrayOf( "🏧", "🚮", "🚰", "♿", "🚹", "🚺", "🚻", "🚼", "🚾", "🛂", "🛃", "🛄", "🛅", "⚠️", "🚸", "⛔", "🚫", "🚳", "🚭", "🚯", "🚱", "🚷", "📵", "🔞", "☢️", "☣️", "⬆️", "↗️", "➡️", "↘️", "⬇️", "↙️", "⬅️", "↖️", "↕️", "↔️", "↩️", "↪️", "⤴️", "⤵️", diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupPreset.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupPreset.kt index 5446da597..11905d2f1 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupPreset.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupPreset.kt @@ -147,6 +147,7 @@ val PopupPreset: Map> = hashMapOf( "<" to arrayOf("≤", "«", "‹", "⟨"), "=" to arrayOf("∞", "≠", "≈"), ">" to arrayOf("≥", "»", "›", "⟩"), + "°" to arrayOf("′", "″", "‴"), // // Currency // From 0f1bf615f352ed2401ac47cc86db9a49ea58e74d Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 1 Jun 2024 21:41:54 +0800 Subject: [PATCH 008/181] Update fcitx5-chewing submodule and prebuilt libchewing --- lib/fcitx5/src/main/cpp/prebuilt | 2 +- plugin/chewing/licenses/libraries/fcitx5-chewing.json | 2 +- plugin/chewing/licenses/libraries/libchewing.json | 2 +- plugin/chewing/src/main/cpp/fcitx5-chewing | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/fcitx5/src/main/cpp/prebuilt b/lib/fcitx5/src/main/cpp/prebuilt index 4ee83775a..78fc216ae 160000 --- a/lib/fcitx5/src/main/cpp/prebuilt +++ b/lib/fcitx5/src/main/cpp/prebuilt @@ -1 +1 @@ -Subproject commit 4ee83775a997ca01c2aefaeecadc8a580c49d2b9 +Subproject commit 78fc216ae4714c8e9b14115baa4ad6800a2fd1ad diff --git a/plugin/chewing/licenses/libraries/fcitx5-chewing.json b/plugin/chewing/licenses/libraries/fcitx5-chewing.json index e98e24fac..e2cca9e09 100644 --- a/plugin/chewing/licenses/libraries/fcitx5-chewing.json +++ b/plugin/chewing/licenses/libraries/fcitx5-chewing.json @@ -1,6 +1,6 @@ { "uniqueId": "fcitx/fcitx5-chewing", - "artifactVersion": "5.1.2", + "artifactVersion": "5.1.4", "description": "Chewing Wrapper for Fcitx", "name": "fcitx/fcitx5-chewing", "website": "https://github.com/fcitx/fcitx5-chewing", diff --git a/plugin/chewing/licenses/libraries/libchewing.json b/plugin/chewing/licenses/libraries/libchewing.json index f8631cb5b..041aa8045 100644 --- a/plugin/chewing/licenses/libraries/libchewing.json +++ b/plugin/chewing/licenses/libraries/libchewing.json @@ -1,6 +1,6 @@ { "uniqueId": "chewing/libchewing", - "artifactVersion": "0.5.1", + "artifactVersion": "0.8.4", "description": "An intelligent phonetic (Zhuyin/Bopomofo) input method", "name": "chewing/libchewing", "website": "https://github.com/chewing/libchewing", diff --git a/plugin/chewing/src/main/cpp/fcitx5-chewing b/plugin/chewing/src/main/cpp/fcitx5-chewing index 28a373bb0..192086007 160000 --- a/plugin/chewing/src/main/cpp/fcitx5-chewing +++ b/plugin/chewing/src/main/cpp/fcitx5-chewing @@ -1 +1 @@ -Subproject commit 28a373bb01463fdbf6b53cde1b4d06fac6960c33 +Subproject commit 1920860074081e864adbd01c99fcba7abd9d2714 From 4b58f3b2cbb1df1598586f2537727998a61cd18d Mon Sep 17 00:00:00 2001 From: Rocka Date: Mon, 3 Jun 2024 19:52:55 +0800 Subject: [PATCH 009/181] Update fcitx5 submodules --- lib/fcitx5-lua/src/main/cpp/fcitx5-lua | 2 +- plugin/anthy/licenses/libraries/anthy-unicode.json | 2 +- plugin/anthy/src/main/cpp/anthy-cmake | 2 +- plugin/anthy/src/main/cpp/fcitx5-anthy | 2 +- plugin/hangul/licenses/libraries/fcitx5-hangul.json | 2 +- plugin/hangul/src/main/cpp/CMakeLists.txt | 1 + plugin/hangul/src/main/cpp/fcitx5-hangul | 2 +- plugin/jyutping/licenses/libraries/libime-jyutping.json | 2 +- plugin/jyutping/src/main/cpp/libime-jyutping | 2 +- plugin/rime/licenses/libraries/fcitx5-rime.json | 2 +- plugin/rime/licenses/libraries/librime-lua.json | 2 +- plugin/rime/licenses/libraries/librime-octagram.json | 2 +- plugin/rime/src/main/cpp/fcitx5-rime | 2 +- plugin/sayura/src/main/cpp/fcitx5-sayura | 2 +- plugin/unikey/src/main/cpp/fcitx5-unikey | 2 +- 15 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/fcitx5-lua/src/main/cpp/fcitx5-lua b/lib/fcitx5-lua/src/main/cpp/fcitx5-lua index cefaf1c8a..bd9656898 160000 --- a/lib/fcitx5-lua/src/main/cpp/fcitx5-lua +++ b/lib/fcitx5-lua/src/main/cpp/fcitx5-lua @@ -1 +1 @@ -Subproject commit cefaf1c8a0c45311fdfa77c6dd6f17f1d80654da +Subproject commit bd96568982258d009565f482deacbac531abd998 diff --git a/plugin/anthy/licenses/libraries/anthy-unicode.json b/plugin/anthy/licenses/libraries/anthy-unicode.json index 37445f0fa..4159e2763 100644 --- a/plugin/anthy/licenses/libraries/anthy-unicode.json +++ b/plugin/anthy/licenses/libraries/anthy-unicode.json @@ -1,6 +1,6 @@ { "uniqueId": "fujiwarat/anthy-unicode", - "artifactVersion": "1.0.0", + "artifactVersion": "1.0.0.20240502", "description": "Anthy Unicode - Another Anthy", "name": "fujiwarat/anthy-unicode", "website": "https://github.com/fujiwarat/anthy-unicode", diff --git a/plugin/anthy/src/main/cpp/anthy-cmake b/plugin/anthy/src/main/cpp/anthy-cmake index 94c94ca81..356540a63 160000 --- a/plugin/anthy/src/main/cpp/anthy-cmake +++ b/plugin/anthy/src/main/cpp/anthy-cmake @@ -1 +1 @@ -Subproject commit 94c94ca812599a2c99c393f391ac5173126a6016 +Subproject commit 356540a632146d22eb71ff9050d3d7e2e4e3b1d0 diff --git a/plugin/anthy/src/main/cpp/fcitx5-anthy b/plugin/anthy/src/main/cpp/fcitx5-anthy index c8b0b5e32..021e6355e 160000 --- a/plugin/anthy/src/main/cpp/fcitx5-anthy +++ b/plugin/anthy/src/main/cpp/fcitx5-anthy @@ -1 +1 @@ -Subproject commit c8b0b5e329b5b1987cde69075f5d2df0a4b62ea8 +Subproject commit 021e6355eadfe8fec9f3902da92401d7a8bc23a6 diff --git a/plugin/hangul/licenses/libraries/fcitx5-hangul.json b/plugin/hangul/licenses/libraries/fcitx5-hangul.json index 371eb38d5..5393bf3dc 100644 --- a/plugin/hangul/licenses/libraries/fcitx5-hangul.json +++ b/plugin/hangul/licenses/libraries/fcitx5-hangul.json @@ -1,6 +1,6 @@ { "uniqueId": "fcitx/fcitx5-hangul", - "artifactVersion": "5.1.3", + "artifactVersion": "5.1.4", "description": "Hangul Wrapper for Fcitx", "name": "fcitx/fcitx5-hangul", "website": "https://github.com/fcitx/fcitx5-hangul", diff --git a/plugin/hangul/src/main/cpp/CMakeLists.txt b/plugin/hangul/src/main/cpp/CMakeLists.txt index 266d84433..d0dfbce9b 100644 --- a/plugin/hangul/src/main/cpp/CMakeLists.txt +++ b/plugin/hangul/src/main/cpp/CMakeLists.txt @@ -23,4 +23,5 @@ set_target_properties(Hangul_static PROPERTIES ) set(HANGUL_TARGET Hangul_static) +option(ENABLE_TEST "" OFF) add_subdirectory(fcitx5-hangul) diff --git a/plugin/hangul/src/main/cpp/fcitx5-hangul b/plugin/hangul/src/main/cpp/fcitx5-hangul index 19b2ac41d..90408d221 160000 --- a/plugin/hangul/src/main/cpp/fcitx5-hangul +++ b/plugin/hangul/src/main/cpp/fcitx5-hangul @@ -1 +1 @@ -Subproject commit 19b2ac41d35a5ecc84a0bc8064e4565768572f4d +Subproject commit 90408d2216389172fefb49f75771d1d28146719c diff --git a/plugin/jyutping/licenses/libraries/libime-jyutping.json b/plugin/jyutping/licenses/libraries/libime-jyutping.json index b672aafe4..776e78322 100644 --- a/plugin/jyutping/licenses/libraries/libime-jyutping.json +++ b/plugin/jyutping/licenses/libraries/libime-jyutping.json @@ -1,6 +1,6 @@ { "uniqueId": "fcitx/fcitx5-jyutping", - "artifactVersion": "1.0.11", + "artifactVersion": "1.0.12", "description": "Jyutping (Cantonese Input Method) engine support for Fcitx5", "name": "fcitx/fcitx5-jyutping", "website": "https://github.com/fcitx/fcitx5-jyutping", diff --git a/plugin/jyutping/src/main/cpp/libime-jyutping b/plugin/jyutping/src/main/cpp/libime-jyutping index 94147665e..25231d82c 160000 --- a/plugin/jyutping/src/main/cpp/libime-jyutping +++ b/plugin/jyutping/src/main/cpp/libime-jyutping @@ -1 +1 @@ -Subproject commit 94147665e624bcd06d427559893006868c3b1dc5 +Subproject commit 25231d82c658753209f7b675273f2918d6be4f82 diff --git a/plugin/rime/licenses/libraries/fcitx5-rime.json b/plugin/rime/licenses/libraries/fcitx5-rime.json index 51ab103ce..59093fec7 100644 --- a/plugin/rime/licenses/libraries/fcitx5-rime.json +++ b/plugin/rime/licenses/libraries/fcitx5-rime.json @@ -1,6 +1,6 @@ { "uniqueId": "fcitx/fcitx5-rime", - "artifactVersion": "5.1.6", + "artifactVersion": "5.1.8", "description": "Rime Wrapper for Fcitx", "name": "fcitx/fcitx5-rime", "website": "https://github.com/fcitx/fcitx5-rime", diff --git a/plugin/rime/licenses/libraries/librime-lua.json b/plugin/rime/licenses/libraries/librime-lua.json index 1e63727a7..1a3d4c043 100644 --- a/plugin/rime/licenses/libraries/librime-lua.json +++ b/plugin/rime/licenses/libraries/librime-lua.json @@ -1,6 +1,6 @@ { "uniqueId": "hchunhui/librime-lua", - "artifactVersion": "5671021", + "artifactVersion": "7be6974", "description": "Extending RIME with Lua scripts", "name": "hchunhui/librime-lua", "website": "https://github.com/hchunhui/librime-lua", diff --git a/plugin/rime/licenses/libraries/librime-octagram.json b/plugin/rime/licenses/libraries/librime-octagram.json index d57b0fce8..98b5d8305 100644 --- a/plugin/rime/licenses/libraries/librime-octagram.json +++ b/plugin/rime/licenses/libraries/librime-octagram.json @@ -1,6 +1,6 @@ { "uniqueId": "lotem/librime-octagram", - "artifactVersion": "a6ced5a", + "artifactVersion": "bd12863", "description": "RIME octagram plugin", "name": "lotem/librime-octagram", "website": "https://github.com/lotem/librime-octagram", diff --git a/plugin/rime/src/main/cpp/fcitx5-rime b/plugin/rime/src/main/cpp/fcitx5-rime index 70f294e76..474063472 160000 --- a/plugin/rime/src/main/cpp/fcitx5-rime +++ b/plugin/rime/src/main/cpp/fcitx5-rime @@ -1 +1 @@ -Subproject commit 70f294e76ddc15f0cafdb7174e1db6a7b6f3db8f +Subproject commit 4740634720b75498a6c84eea36fd78e74affa2a2 diff --git a/plugin/sayura/src/main/cpp/fcitx5-sayura b/plugin/sayura/src/main/cpp/fcitx5-sayura index 230ba5371..23335a857 160000 --- a/plugin/sayura/src/main/cpp/fcitx5-sayura +++ b/plugin/sayura/src/main/cpp/fcitx5-sayura @@ -1 +1 @@ -Subproject commit 230ba53719d000d16eb403caa06bc237c780cb0b +Subproject commit 23335a8578ec574e31038cbbcf7210e3e86c9b0d diff --git a/plugin/unikey/src/main/cpp/fcitx5-unikey b/plugin/unikey/src/main/cpp/fcitx5-unikey index 617dcdc6a..939d5a6f4 160000 --- a/plugin/unikey/src/main/cpp/fcitx5-unikey +++ b/plugin/unikey/src/main/cpp/fcitx5-unikey @@ -1 +1 @@ -Subproject commit 617dcdc6a9309759e0a3e478cca5b5519e2f9119 +Subproject commit 939d5a6f4cfbee653ca535c11007a2e4af295bb6 From 9663224f3ce6539e406553a7f35a46915380c016 Mon Sep 17 00:00:00 2001 From: Rocka Date: Mon, 3 Jun 2024 19:53:15 +0800 Subject: [PATCH 010/181] Add license for clipboard-filter plugin --- plugin/clipboard-filter/ClearURLsRules | 2 +- plugin/clipboard-filter/build.gradle.kts | 4 ++++ plugin/clipboard-filter/licenses/libraries/rules.json | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 plugin/clipboard-filter/licenses/libraries/rules.json diff --git a/plugin/clipboard-filter/ClearURLsRules b/plugin/clipboard-filter/ClearURLsRules index 6a301edb8..0d1c6e00d 160000 --- a/plugin/clipboard-filter/ClearURLsRules +++ b/plugin/clipboard-filter/ClearURLsRules @@ -1 +1 @@ -Subproject commit 6a301edb838ae2ee89282478aea0b8ab60d5987f +Subproject commit 0d1c6e00d2b91df1d70c84067c16c320e7976a3f diff --git a/plugin/clipboard-filter/build.gradle.kts b/plugin/clipboard-filter/build.gradle.kts index cd46f996f..893bd9cd4 100644 --- a/plugin/clipboard-filter/build.gradle.kts +++ b/plugin/clipboard-filter/build.gradle.kts @@ -23,6 +23,10 @@ android { } } +aboutLibraries { + configPath = "plugin/clipboard-filter/licenses" +} + generateDataDescriptor{ excludes.add("data.min.json") } diff --git a/plugin/clipboard-filter/licenses/libraries/rules.json b/plugin/clipboard-filter/licenses/libraries/rules.json new file mode 100644 index 000000000..c40f4fa99 --- /dev/null +++ b/plugin/clipboard-filter/licenses/libraries/rules.json @@ -0,0 +1,11 @@ +{ + "uniqueId": "ClearURLs/Rules", + "artifactVersion": "0d1c6e0", + "description": "Rules database of the ClearURLs WebExtension.", + "name": "ClearURLs/Rules", + "website": "https://github.com/ClearURLs/Rules", + "tag": "native", + "licenses": [ + "LGPL-3.0-or-later" + ] +} From 4a78704d536c1e68695de775a157c3dc5bfc27a7 Mon Sep 17 00:00:00 2001 From: Rocka Date: Mon, 3 Jun 2024 18:33:03 +0800 Subject: [PATCH 011/181] Rename build-metadata.json for plugins --- .../convention/src/main/kotlin/BuildMetadataPlugin.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build-logic/convention/src/main/kotlin/BuildMetadataPlugin.kt b/build-logic/convention/src/main/kotlin/BuildMetadataPlugin.kt index ac1dde005..9f56ef3e1 100644 --- a/build-logic/convention/src/main/kotlin/BuildMetadataPlugin.kt +++ b/build-logic/convention/src/main/kotlin/BuildMetadataPlugin.kt @@ -38,7 +38,12 @@ class BuildMetadataPlugin : Plugin { // create metadata file after package, because it's outputDirectory would // be cleared at some time before package mustRunAfter(packageTask) - outputFile.set(packageTask.outputDirectory.file("build-metadata.json")) + val fileName = target.path.let { + // ":app" -> "" || ":plugin:anthy" -> ".plugin.anthy" + val suffix = if (it == ":app") "" else it.replace(':', '.') + "build-metadata${suffix}.json" + } + outputFile.set(packageTask.outputDirectory.file(fileName)) }.also { assembleProvider.get().dependsOn(it) // assemble${Variant} task } From 1865b15fe46cadd1265b5b8d400ed11b64504d05 Mon Sep 17 00:00:00 2001 From: Rocka Date: Mon, 3 Jun 2024 18:34:15 +0800 Subject: [PATCH 012/181] Remove kotlin null checks in plugins release build --- lib/plugin-base/proguard-rules.pro | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/plugin-base/proguard-rules.pro b/lib/plugin-base/proguard-rules.pro index 9d70c8128..785ba342f 100644 --- a/lib/plugin-base/proguard-rules.pro +++ b/lib/plugin-base/proguard-rules.pro @@ -3,3 +3,14 @@ # preserve the line number information for debugging stack traces -keepattributes SourceFile,LineNumberTable + +# remove kotlin null checks +-assumenosideeffects class kotlin.jvm.internal.Intrinsics { + static void checkNotNull(...); + static void checkExpressionValueIsNotNull(...); + static void checkNotNullExpressionValue(...); + static void checkReturnedValueIsNotNull(...); + static void checkFieldIsNotNull(...); + static void checkParameterIsNotNull(...); + static void checkNotNullParameter(...); +} From 2fb0c033683d7c3f9fbdf43fd3557b7200dfc04c Mon Sep 17 00:00:00 2001 From: Rocka Date: Sun, 2 Jun 2024 00:26:48 +0800 Subject: [PATCH 013/181] Adopt fcitx5 candidate action API --- .../cpp/androidfrontend/androidfrontend.cpp | 88 ++++++++++++++----- .../cpp/androidfrontend/androidfrontend.h | 6 +- .../androidfrontend/androidfrontend_public.h | 10 ++- app/src/main/cpp/helper-types.h | 19 ++++ app/src/main/cpp/jni-utils.h | 6 ++ app/src/main/cpp/native-lib.cpp | 46 +++++++--- app/src/main/cpp/object-conversion.h | 12 +++ .../org/fcitx/fcitx5/android/core/Fcitx.kt | 16 +++- .../org/fcitx/fcitx5/android/core/FcitxAPI.kt | 4 +- .../org/fcitx/fcitx5/android/core/Types.kt | 9 ++ .../input/candidates/CandidateItemUi.kt | 38 -------- .../input/candidates/CandidateViewHolder.kt | 13 +++ .../HorizontalCandidateComponent.kt | 61 +++++++++---- .../adapter/GridPagingCandidateViewAdapter.kt | 3 +- .../adapter/HorizontalCandidateViewAdapter.kt | 17 ++-- .../adapter/PagingCandidateViewAdapter.kt | 18 ++-- .../window/BaseExpandedCandidateWindow.kt | 15 ++-- .../window/FlexboxExpandedCandidateWindow.kt | 5 +- .../window/GridExpandedCandidateWindow.kt | 3 +- .../src/main/cpp/fcitx5-chinese-addons | 2 +- lib/fcitx5/src/main/cpp/fcitx5 | 2 +- lib/libime/src/main/cpp/libime | 2 +- 22 files changed, 263 insertions(+), 132 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateViewHolder.kt diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.cpp b/app/src/main/cpp/androidfrontend/androidfrontend.cpp index 37ceff737..ef1128eb1 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.cpp +++ b/app/src/main/cpp/androidfrontend/androidfrontend.cpp @@ -142,6 +142,66 @@ class AndroidInputContext : public InputContextV2 { return candidates; } + bool doesCandidateHaveAction(const int idx) { + const auto &list = inputPanel().candidateList(); + if (!list) return false; + const auto &actionable = list->toActionable(); + if (!actionable) return false; + if (idx >= list->size()) { + const auto &bulk = list->toBulk(); + if (bulk && idx < bulk->totalSize()) { + const auto &c = bulk->candidateFromAll(idx); + return actionable->hasAction(c); + } + return false; + } else { + const auto &c = list->candidate(idx); + return actionable->hasAction(c); + } + } + + std::vector getCandidateAction(const int idx) { + std::vector actions; + const auto &list = inputPanel().candidateList(); + if (list) { + const auto &actionable = list->toActionable(); + if (actionable) { + if (idx >= list->size()) { + const auto &bulk = list->toBulk(); + if (bulk && idx < bulk->totalSize()) { + const auto &c = bulk->candidateFromAll(idx); + for (const auto &a: actionable->candidateActions(c)) { + actions.emplace_back(a); + } + } + } else { + const auto &c = list->candidate(idx); + for (const auto &a: actionable->candidateActions(c)) { + actions.emplace_back(a); + } + } + } + } + return actions; + } + + void triggerCandidateAction(const int idx, const int actionIdx) { + const auto &list = inputPanel().candidateList(); + if (!list) return; + const auto &actionable = list->toActionable(); + if (!actionable) return; + if (idx >= list->size()) { + const auto &bulk = list->toBulk(); + if (bulk && idx < bulk->totalSize()) { + const auto &c = bulk->candidateFromAll(idx); + actionable->triggerAction(c, actionIdx); + } + } else { + const auto &c = list->candidate(idx); + actionable->triggerAction(c, actionIdx); + } + } + private: AndroidFrontend *frontend_; int uid_; @@ -228,26 +288,14 @@ bool AndroidFrontend::selectCandidate(int idx) { return activeIC_->selectCandidate(idx); } -bool AndroidFrontend::forgetCandidate(int idx) { - if (!activeIC_) return false; - // check current engine, only pinyin and table engine support deleting words - auto *entry = instance_->inputMethodEntry(activeIC_); - if (entry->addon() != "pinyin" && entry->addon() != "table") return false; - // do we have candidate list? - auto list = activeIC_->inputPanel().candidateList(); - if (!list) return false; - // Ctrl+7 to activate forget candidate mode - Key key(FcitxKey_7, Flags(KeyState::Ctrl)); - KeyEvent pressEvent(activeIC_, key, false); - auto handled = activeIC_->keyEvent(pressEvent); - if (handled) { - KeyEvent releaseEvent(activeIC_, key, true); - activeIC_->keyEvent(releaseEvent); - } else { - // something went wrong - return false; - } - return activeIC_->selectCandidate(idx); +std::vector AndroidFrontend::getCandidateActions(const int idx) { + if (!activeIC_) return {}; + return activeIC_->getCandidateAction(idx); +} + +void AndroidFrontend::triggerCandidateAction(const int idx, const int actionIdx) { + if (!activeIC_) return; + activeIC_->triggerCandidateAction(idx, actionIdx); } bool AndroidFrontend::isInputPanelEmpty() { diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.h b/app/src/main/cpp/androidfrontend/androidfrontend.h index 796973d7c..349cdd8e3 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend.h @@ -40,6 +40,8 @@ class AndroidFrontend : public AddonInstance { InputContext *activeInputContext() const; void setCapabilityFlags(uint64_t flag); std::vector getCandidates(const int offset, const int limit); + std::vector getCandidateActions(const int idx); + void triggerCandidateAction(const int idx, const int actionIdx); void deleteSurrounding(const int before, const int after); void showToast(const std::string &s); void setCandidateListCallback(const CandidateListCallback &callback); @@ -51,7 +53,6 @@ class AndroidFrontend : public AddonInstance { void setStatusAreaUpdateCallback(const StatusAreaUpdateCallback &callback); void setDeleteSurroundingCallback(const DeleteSurroundingCallback &callback); void setToastCallback(const ToastCallback &callback); - bool forgetCandidate(int idx); private: FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, keyEvent); @@ -65,6 +66,8 @@ class AndroidFrontend : public AddonInstance { FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, deactivateInputContext); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCapabilityFlags); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, getCandidates); + FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, getCandidateActions); + FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, triggerCandidateAction); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, showToast); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidateListCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCommitStringCallback); @@ -75,7 +78,6 @@ class AndroidFrontend : public AddonInstance { FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setStatusAreaUpdateCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setDeleteSurroundingCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setToastCallback); - FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, forgetCandidate); Instance *instance_; FocusGroup focusGroup_; diff --git a/app/src/main/cpp/androidfrontend/androidfrontend_public.h b/app/src/main/cpp/androidfrontend/androidfrontend_public.h index 76ded712b..23952e49d 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend_public.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend_public.h @@ -7,6 +7,7 @@ #include #include +#include #include typedef std::function &, const int)> CandidateListCallback; @@ -52,6 +53,12 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCapabilityFlags, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, getCandidates, std::vector(const int, const int)) +FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, getCandidateActions, + std::vector(const int)) + +FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, triggerCandidateAction, + void(const int, const int)) + FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, showToast, void(const std::string &)) @@ -82,7 +89,4 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setDeleteSurroundingCallback, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setToastCallback, void(const ToastCallback &)) -FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, forgetCandidate, - bool(int idx)) - #endif // _FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H_ diff --git a/app/src/main/cpp/helper-types.h b/app/src/main/cpp/helper-types.h index a0be956c9..ffed72bc2 100644 --- a/app/src/main/cpp/helper-types.h +++ b/app/src/main/cpp/helper-types.h @@ -8,6 +8,7 @@ #include #include #include +#include class InputMethodStatus { public: @@ -83,4 +84,22 @@ class ActionEntity { } }; +class CandidateActionEntity { +public: + int id; + std::string text; + bool isSeparator; + std::string icon; + bool isCheckable; + bool isChecked; + + CandidateActionEntity(const fcitx::CandidateAction &act) : + id(act.id()), + text(act.text()), + isSeparator(act.isSeparator()), + icon(act.icon()), + isCheckable(act.isCheckable()), + isChecked(act.isChecked()) {} +}; + #endif //FCITX5_ANDROID_HELPER_TYPES_H diff --git a/app/src/main/cpp/jni-utils.h b/app/src/main/cpp/jni-utils.h index 1e8bec212..6e5721c70 100644 --- a/app/src/main/cpp/jni-utils.h +++ b/app/src/main/cpp/jni-utils.h @@ -138,6 +138,9 @@ class GlobalRefSingleton { jfieldID PinyinCustomPhraseOrder; jfieldID PinyinCustomPhraseValue; + jclass CandidateAction; + jmethodID CandidateActionInit; + GlobalRefSingleton(JavaVM *jvm_) : jvm(jvm_) { JNIEnv *env; jvm->AttachCurrentThread(&env, nullptr); @@ -184,6 +187,9 @@ class GlobalRefSingleton { PinyinCustomPhraseKey = env->GetFieldID(PinyinCustomPhrase, "key", "Ljava/lang/String;"); PinyinCustomPhraseOrder = env->GetFieldID(PinyinCustomPhrase, "order", "I"); PinyinCustomPhraseValue = env->GetFieldID(PinyinCustomPhrase, "value", "Ljava/lang/String;"); + + CandidateAction = reinterpret_cast(env->NewGlobalRef(env->FindClass("org/fcitx/fcitx5/android/core/CandidateAction"))); + CandidateActionInit = env->GetMethodID(CandidateAction, "", "(ILjava/lang/String;ZLjava/lang/String;ZZ)V"); } const JEnv AttachEnv() const { return JEnv(jvm); } diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index ca4be07ac..4fd5952ae 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -120,10 +120,6 @@ class Fcitx { return p_frontend->call(idx); } - bool forgetCandidate(int idx) { - return p_frontend->call(idx); - } - bool isInputPanelEmpty() { return p_frontend->call(); } @@ -419,6 +415,18 @@ class Fcitx { return p_frontend->call(offset, limit); } + std::vector getCandidateActions(int idx) { + auto actions = std::vector(); + for (const auto &a: p_frontend->call(idx)) { + actions.emplace_back(a); + } + return actions; + } + + void triggerCandidateAction(int idx, int actionIdx) { + return p_frontend->call(idx, actionIdx); + } + void save() { p_instance->save(); } @@ -753,18 +761,9 @@ extern "C" JNIEXPORT jboolean JNICALL Java_org_fcitx_fcitx5_android_core_Fcitx_selectCandidate(JNIEnv *env, jclass clazz, jint idx) { RETURN_VALUE_IF_NOT_RUNNING(false) - FCITX_DEBUG() << "selectCandidate: #" << idx; return Fcitx::Instance().select(idx); } -extern "C" -JNIEXPORT jboolean JNICALL -Java_org_fcitx_fcitx5_android_core_Fcitx_forgetCandidate(JNIEnv *env, jclass clazz, jint idx) { - RETURN_VALUE_IF_NOT_RUNNING(false) - FCITX_DEBUG() << "forgetCandidate: #" << idx; - return Fcitx::Instance().forgetCandidate(idx); -} - extern "C" JNIEXPORT jboolean JNICALL Java_org_fcitx_fcitx5_android_core_Fcitx_isInputPanelEmpty(JNIEnv *env, jclass clazz) { @@ -1027,6 +1026,27 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_getFcitxCandidates(JNIEnv *env, jclass return array; } +extern "C" +JNIEXPORT jobjectArray JNICALL +Java_org_fcitx_fcitx5_android_core_Fcitx_getFcitxCandidateActions(JNIEnv *env, jclass clazz, jint idx) { + RETURN_VALUE_IF_NOT_RUNNING(nullptr) + auto actions = Fcitx::Instance().getCandidateActions(idx); + int size = static_cast(actions.size()); + jobjectArray array = env->NewObjectArray(size, GlobalRef->CandidateAction, nullptr); + for (int i = 0; i < size; i++) { + auto obj = JRef(env, fcitxCandidateActionToObject(env, actions[i])); + env->SetObjectArrayElement(array, i, obj); + } + return array; +} + +extern "C" +JNIEXPORT void JNICALL +Java_org_fcitx_fcitx5_android_core_Fcitx_triggerFcitxCandidateAction(JNIEnv *env, jclass clazz, jint idx, jint action_idx) { + RETURN_IF_NOT_RUNNING + Fcitx::Instance().triggerCandidateAction(idx, action_idx); +} + extern "C" JNIEXPORT void JNICALL Java_org_fcitx_fcitx5_android_core_Fcitx_loopOnce(JNIEnv *env, jclass clazz) { diff --git a/app/src/main/cpp/object-conversion.h b/app/src/main/cpp/object-conversion.h index 45c2e2306..1486e2206 100644 --- a/app/src/main/cpp/object-conversion.h +++ b/app/src/main/cpp/object-conversion.h @@ -161,4 +161,16 @@ jobject fcitxTextToJObject(JNIEnv *env, const fcitx::Text &text) { return obj; } +jobject fcitxCandidateActionToObject(JNIEnv *env, const CandidateActionEntity &act) { + auto obj = env->NewObject(GlobalRef->CandidateAction, GlobalRef->CandidateActionInit, + act.id, + *JString(env, act.text), + act.isSeparator, + *JString(env, act.icon), + act.isCheckable, + act.isChecked + ); + return obj; +} + #endif //FCITX5_ANDROID_OBJECT_CONVERSION_H diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt index 8a16264f8..a1b6b9f65 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt @@ -86,7 +86,6 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { withFcitxContext { sendKeySymToFcitx(sym.sym, states.toInt(), up, timestamp) } override suspend fun select(idx: Int): Boolean = withFcitxContext { selectCandidate(idx) } - override suspend fun forget(idx: Int): Boolean = withFcitxContext { forgetCandidate(idx) } override suspend fun isEmpty(): Boolean = withFcitxContext { isInputPanelEmpty() } override suspend fun reset() = withFcitxContext { resetInputContext() } override suspend fun moveCursor(position: Int) = withFcitxContext { repositionCursor(position) } @@ -164,6 +163,12 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { override suspend fun getCandidates(offset: Int, limit: Int): Array = withFcitxContext { getFcitxCandidates(offset, limit) ?: emptyArray() } + override suspend fun getCandidateActions(idx: Int): Array = + withFcitxContext { getFcitxCandidateActions(idx) ?: emptyArray() } + + override suspend fun triggerCandidateAction(idx: Int, actionIdx: Int) = + withFcitxContext { triggerFcitxCandidateAction(idx, actionIdx) } + init { if (lifecycle.currentState != FcitxLifecycle.State.STOPPED) throw IllegalAccessException("Fcitx5 has already been created!") @@ -238,9 +243,6 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { @JvmStatic external fun selectCandidate(idx: Int): Boolean - @JvmStatic - external fun forgetCandidate(idx: Int): Boolean - @JvmStatic external fun isInputPanelEmpty(): Boolean @@ -331,6 +333,12 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { @JvmStatic external fun getFcitxCandidates(offset: Int, limit: Int): Array? + @JvmStatic + external fun getFcitxCandidateActions(idx: Int): Array? + + @JvmStatic + external fun triggerFcitxCandidateAction(idx: Int, actionIdx: Int) + @JvmStatic external fun loopOnce() diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt index 16a832c43..d37d527a0 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt @@ -52,7 +52,6 @@ interface FcitxAPI { suspend fun sendKey(sym: KeySym, states: KeyStates, up: Boolean = false, timestamp: Int = -1) suspend fun select(idx: Int): Boolean - suspend fun forget(idx: Int): Boolean suspend fun isEmpty(): Boolean suspend fun reset() suspend fun moveCursor(position: Int) @@ -101,4 +100,7 @@ interface FcitxAPI { suspend fun getCandidates(offset: Int, limit: Int): Array + suspend fun getCandidateActions(idx: Int): Array + suspend fun triggerCandidateAction(idx: Int, actionIdx: Int) + } \ No newline at end of file diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/Types.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/Types.kt index ac863b111..5967874e4 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/Types.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/Types.kt @@ -260,3 +260,12 @@ data class Action( return result } } + +data class CandidateAction( + val id: Int, + val text: String, + val isSeparator: Boolean, + val icon: String, + val isCheckable: Boolean, + val isChecked: Boolean +) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateItemUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateItemUi.kt index 17c9e0a80..3ecd3cc83 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateItemUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateItemUi.kt @@ -5,16 +5,10 @@ package org.fcitx.fcitx5.android.input.candidates import android.content.Context -import android.widget.PopupMenu -import androidx.core.text.bold -import androidx.core.text.buildSpannedString -import androidx.core.text.color -import org.fcitx.fcitx5.android.R import org.fcitx.fcitx5.android.data.theme.Theme import org.fcitx.fcitx5.android.input.AutoScaleTextView import org.fcitx.fcitx5.android.input.keyboard.CustomGestureView import org.fcitx.fcitx5.android.utils.pressHighlightDrawable -import splitties.resources.styledColor import splitties.views.dsl.core.Ui import splitties.views.dsl.core.add import splitties.views.dsl.core.lParams @@ -40,36 +34,4 @@ class CandidateItemUi(override val ctx: Context, theme: Theme) : Ui { gravity = gravityCenter }) } - - private var promptMenu: PopupMenu? = null - - fun showExtraActionMenu(onForget: () -> Unit) { - promptMenu?.dismiss() - promptMenu = PopupMenu(ctx, root).apply { - menu.apply { - add(buildSpannedString { - bold { - color(ctx.styledColor(android.R.attr.colorAccent)) { - append(text.text.toString()) - } - } - }).apply { - isEnabled = false - } - add(R.string.action_forget_candidate_word).apply { - setOnMenuItemClickListener { - onForget() - true - } - } - add(android.R.string.cancel).apply { - setOnMenuItemClickListener { true } - } - } - setOnDismissListener { - if (it === promptMenu) promptMenu = null - } - show() - } - } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateViewHolder.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateViewHolder.kt new file mode 100644 index 000000000..1bde332c6 --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateViewHolder.kt @@ -0,0 +1,13 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.input.candidates + +import androidx.recyclerview.widget.RecyclerView + +class CandidateViewHolder(val ui: CandidateItemUi) : RecyclerView.ViewHolder(ui.root) { + var idx = -1 + var text = "" +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateComponent.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateComponent.kt index 9f3d3ec46..6c3f18ac9 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateComponent.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateComponent.kt @@ -7,16 +7,21 @@ package org.fcitx.fcitx5.android.input.candidates import android.content.res.Configuration import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.RectShape +import android.widget.PopupMenu +import androidx.core.text.bold +import androidx.core.text.buildSpannedString +import androidx.core.text.color import androidx.core.view.updateLayoutParams +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import com.google.android.flexbox.FlexboxLayoutManager import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.fcitx.fcitx5.android.R import org.fcitx.fcitx5.android.core.FcitxEvent -import org.fcitx.fcitx5.android.core.InputMethodEntry import org.fcitx.fcitx5.android.daemon.launchOnReady import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.input.bar.ExpandButtonStateMachine.BooleanKey.ExpandedCandidatesEmpty @@ -31,14 +36,17 @@ import org.fcitx.fcitx5.android.input.candidates.expanded.decoration.FlexboxVert import org.fcitx.fcitx5.android.input.dependency.UniqueViewComponent import org.fcitx.fcitx5.android.input.dependency.context import org.fcitx.fcitx5.android.input.dependency.fcitx +import org.fcitx.fcitx5.android.input.dependency.inputMethodService import org.fcitx.fcitx5.android.input.dependency.theme import org.mechdancer.dependency.manager.must import splitties.dimensions.dp +import splitties.resources.styledColor import kotlin.math.max class HorizontalCandidateComponent : UniqueViewComponent(), InputBroadcastReceiver { + private val service by manager.inputMethodService() private val context by manager.context() private val fcitx by manager.fcitx() private val theme by manager.theme() @@ -87,7 +95,7 @@ class HorizontalCandidateComponent : val adapter: HorizontalCandidateViewAdapter by lazy { object : HorizontalCandidateViewAdapter(theme) { - override fun onBindViewHolder(holder: ViewHolder, position: Int) { + override fun onBindViewHolder(holder: CandidateViewHolder, position: Int) { super.onBindViewHolder(holder, position) holder.itemView.updateLayoutParams { minWidth = layoutMinWidth @@ -96,15 +104,9 @@ class HorizontalCandidateComponent : holder.itemView.setOnClickListener { fcitx.launchOnReady { it.select(holder.idx) } } - if (canForgetWord) { - holder.itemView.setOnLongClickListener { _ -> - holder.ui.showExtraActionMenu(onForget = { - fcitx.launchOnReady { it.forget(holder.idx) } - }) - true - } - } else { - holder.itemView.setOnLongClickListener(null) + holder.itemView.setOnLongClickListener { + showCandidateActionMenu(holder.idx, candidates[position], holder.ui) + true } } } @@ -195,10 +197,39 @@ class HorizontalCandidateComponent : } } - var canForgetWord: Boolean = false - private set + private fun triggerCandidateAction(idx: Int, actionIdx: Int): Boolean { + fcitx.runIfReady { triggerCandidateAction(idx, actionIdx) } + return true + } - override fun onImeUpdate(ime: InputMethodEntry) { - canForgetWord = (ime.addon == "pinyin" || ime.addon == "table") + private var candidateActionMenu: PopupMenu? = null + + fun showCandidateActionMenu(idx: Int, text: String, ui: CandidateItemUi) { + candidateActionMenu?.dismiss() + candidateActionMenu = null + service.lifecycleScope.launch { + val actions = fcitx.runOnReady { getCandidateActions(idx) } + if (actions.isEmpty()) return@launch + candidateActionMenu = PopupMenu(context, ui.root).apply { + menu.add(buildSpannedString { + bold { + color(context.styledColor(android.R.attr.colorAccent)) { + append(text) + } + } + }).apply { + isEnabled = false + } + actions.forEach { action -> + menu.add(action.text).setOnMenuItemClickListener { + triggerCandidateAction(idx, action.id) + } + } + setOnDismissListener { + candidateActionMenu = null + } + show() + } + } } } \ No newline at end of file diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/GridPagingCandidateViewAdapter.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/GridPagingCandidateViewAdapter.kt index 6472c2bdb..64c986c88 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/GridPagingCandidateViewAdapter.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/GridPagingCandidateViewAdapter.kt @@ -10,6 +10,7 @@ import android.util.LruCache import android.view.ViewGroup import androidx.recyclerview.widget.GridLayoutManager import org.fcitx.fcitx5.android.data.theme.Theme +import org.fcitx.fcitx5.android.input.candidates.CandidateViewHolder import splitties.dimensions.dp import splitties.views.dsl.core.matchParent @@ -30,7 +31,7 @@ abstract class GridPagingCandidateViewAdapter(theme: Theme) : PagingCandidateVie } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CandidateViewHolder { return super.onCreateViewHolder(parent, viewType).apply { itemView.apply { layoutParams = GridLayoutManager.LayoutParams(matchParent, dp(40)) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/HorizontalCandidateViewAdapter.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/HorizontalCandidateViewAdapter.kt index abd7b8ee3..ed3ac225f 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/HorizontalCandidateViewAdapter.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/HorizontalCandidateViewAdapter.kt @@ -11,17 +11,14 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.flexbox.FlexboxLayoutManager import org.fcitx.fcitx5.android.data.theme.Theme import org.fcitx.fcitx5.android.input.candidates.CandidateItemUi +import org.fcitx.fcitx5.android.input.candidates.CandidateViewHolder import splitties.dimensions.dp import splitties.views.dsl.core.matchParent import splitties.views.dsl.core.wrapContent import splitties.views.setPaddingDp open class HorizontalCandidateViewAdapter(val theme: Theme) : - RecyclerView.Adapter() { - - inner class ViewHolder(val ui: CandidateItemUi) : RecyclerView.ViewHolder(ui.root) { - var idx = -1 - } + RecyclerView.Adapter() { var candidates: Array = arrayOf() private set @@ -39,19 +36,21 @@ open class HorizontalCandidateViewAdapter(val theme: Theme) : override fun getItemCount() = candidates.size @CallSuper - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CandidateViewHolder { val ui = CandidateItemUi(parent.context, theme) ui.root.apply { minimumWidth = dp(40) setPaddingDp(10, 0, 10, 0) layoutParams = FlexboxLayoutManager.LayoutParams(wrapContent, matchParent) } - return ViewHolder(ui) + return CandidateViewHolder(ui) } @CallSuper - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.ui.text.text = candidates[position] + override fun onBindViewHolder(holder: CandidateViewHolder, position: Int) { + val text = candidates[position] + holder.ui.text.text = text + holder.text = text holder.idx = position } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/PagingCandidateViewAdapter.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/PagingCandidateViewAdapter.kt index e3615e878..da9f0e59e 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/PagingCandidateViewAdapter.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/adapter/PagingCandidateViewAdapter.kt @@ -7,12 +7,12 @@ package org.fcitx.fcitx5.android.input.candidates.adapter import android.view.ViewGroup import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView import org.fcitx.fcitx5.android.data.theme.Theme import org.fcitx.fcitx5.android.input.candidates.CandidateItemUi +import org.fcitx.fcitx5.android.input.candidates.CandidateViewHolder open class PagingCandidateViewAdapter(val theme: Theme) : - PagingDataAdapter(diffCallback) { + PagingDataAdapter(diffCallback) { companion object { private val diffCallback = object : DiffUtil.ItemCallback() { @@ -26,10 +26,6 @@ open class PagingCandidateViewAdapter(val theme: Theme) : } } - class ViewHolder(val ui: CandidateItemUi) : RecyclerView.ViewHolder(ui.root) { - var idx = -1 - } - var offset = 0 private set @@ -38,12 +34,14 @@ open class PagingCandidateViewAdapter(val theme: Theme) : refresh() } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - return ViewHolder(CandidateItemUi(parent.context, theme)) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CandidateViewHolder { + return CandidateViewHolder(CandidateItemUi(parent.context, theme)) } - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.ui.text.text = getItem(position)!! + override fun onBindViewHolder(holder: CandidateViewHolder, position: Int) { + val text = getItem(position)!! + holder.ui.text.text = text + holder.text = text holder.idx = position + offset } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/BaseExpandedCandidateWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/BaseExpandedCandidateWindow.kt index a5ba98735..66998ec62 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/BaseExpandedCandidateWindow.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/BaseExpandedCandidateWindow.kt @@ -24,6 +24,7 @@ import org.fcitx.fcitx5.android.input.bar.ExpandButtonStateMachine.TransitionEve import org.fcitx.fcitx5.android.input.bar.KawaiiBarComponent import org.fcitx.fcitx5.android.input.broadcast.InputBroadcastReceiver import org.fcitx.fcitx5.android.input.broadcast.ReturnKeyDrawableComponent +import org.fcitx.fcitx5.android.input.candidates.CandidateViewHolder import org.fcitx.fcitx5.android.input.candidates.HorizontalCandidateComponent import org.fcitx.fcitx5.android.input.candidates.adapter.PagingCandidateViewAdapter import org.fcitx.fcitx5.android.input.candidates.expanded.CandidatesPagingSource @@ -129,19 +130,13 @@ abstract class BaseExpandedCandidateWindow> : } } - fun bindCandidateUiViewHolder(holder: PagingCandidateViewAdapter.ViewHolder) { + fun bindCandidateUiViewHolder(holder: CandidateViewHolder) { holder.itemView.setOnClickListener { fcitx.launchOnReady { it.select(holder.idx) } } - if (horizontalCandidate.canForgetWord) { - holder.itemView.setOnLongClickListener { _ -> - holder.ui.showExtraActionMenu(onForget = { - fcitx.launchOnReady { it.forget(holder.idx) } - }) - true - } - } else { - holder.itemView.setOnLongClickListener(null) + holder.itemView.setOnLongClickListener { + horizontalCandidate.showCandidateActionMenu(holder.idx, holder.text, holder.ui) + true } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/FlexboxExpandedCandidateWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/FlexboxExpandedCandidateWindow.kt index 1b6417aef..b9cc2db3b 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/FlexboxExpandedCandidateWindow.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/FlexboxExpandedCandidateWindow.kt @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.flexbox.AlignItems import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.flexbox.JustifyContent +import org.fcitx.fcitx5.android.input.candidates.CandidateViewHolder import org.fcitx.fcitx5.android.input.candidates.adapter.PagingCandidateViewAdapter import org.fcitx.fcitx5.android.input.candidates.expanded.ExpandedCandidateLayout import org.fcitx.fcitx5.android.input.candidates.expanded.decoration.FlexboxHorizontalDecoration @@ -23,7 +24,7 @@ class FlexboxExpandedCandidateWindow : override val adapter by lazy { object : PagingCandidateViewAdapter(theme) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CandidateViewHolder { return super.onCreateViewHolder(parent, viewType).apply { itemView.apply { minimumWidth = dp(40) @@ -34,7 +35,7 @@ class FlexboxExpandedCandidateWindow : } } - override fun onBindViewHolder(holder: ViewHolder, position: Int) { + override fun onBindViewHolder(holder: CandidateViewHolder, position: Int) { super.onBindViewHolder(holder, position) bindCandidateUiViewHolder(holder) } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/GridExpandedCandidateWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/GridExpandedCandidateWindow.kt index e8f1045d2..2d56ea159 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/GridExpandedCandidateWindow.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/window/GridExpandedCandidateWindow.kt @@ -10,6 +10,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import org.fcitx.fcitx5.android.data.prefs.AppPrefs +import org.fcitx.fcitx5.android.input.candidates.CandidateViewHolder import org.fcitx.fcitx5.android.input.candidates.adapter.GridPagingCandidateViewAdapter import org.fcitx.fcitx5.android.input.candidates.expanded.ExpandedCandidateLayout import org.fcitx.fcitx5.android.input.candidates.expanded.SpanHelper @@ -29,7 +30,7 @@ class GridExpandedCandidateWindow : override val adapter by lazy { object : GridPagingCandidateViewAdapter(theme) { - override fun onBindViewHolder(holder: ViewHolder, position: Int) { + override fun onBindViewHolder(holder: CandidateViewHolder, position: Int) { super.onBindViewHolder(holder, position) bindCandidateUiViewHolder(holder) } diff --git a/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons b/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons index 0c5d9d156..d6fc7a0a6 160000 --- a/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons +++ b/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons @@ -1 +1 @@ -Subproject commit 0c5d9d156e6666d4347b0bc24bbefa6bab63f1aa +Subproject commit d6fc7a0a60ec0c543eca2255d2d9ee87bd0ef7ae diff --git a/lib/fcitx5/src/main/cpp/fcitx5 b/lib/fcitx5/src/main/cpp/fcitx5 index 824a755b3..716fee751 160000 --- a/lib/fcitx5/src/main/cpp/fcitx5 +++ b/lib/fcitx5/src/main/cpp/fcitx5 @@ -1 +1 @@ -Subproject commit 824a755b322acae396024c43b350454ba0697098 +Subproject commit 716fee751feea4ddaa83ec16a3e632bcfa1bb6ea diff --git a/lib/libime/src/main/cpp/libime b/lib/libime/src/main/cpp/libime index e90ef361d..ceff28119 160000 --- a/lib/libime/src/main/cpp/libime +++ b/lib/libime/src/main/cpp/libime @@ -1 +1 @@ -Subproject commit e90ef361d0c5c7d8ee4211b0663b426372e0bad9 +Subproject commit ceff28119cd80c3104e165ca3c5753a48498b73b From e80d05776b8ecb6a7261cbd486de735bee6fa4ce Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 19 Jun 2024 23:08:02 +0800 Subject: [PATCH 014/181] Perform long press haptic feedback only when actions not empty --- .../fcitx5/android/input/candidates/CandidateItemUi.kt | 5 +++++ .../input/candidates/HorizontalCandidateComponent.kt | 2 ++ .../fcitx5/android/input/keyboard/CustomGestureView.kt | 9 ++++++++- .../src/main/cpp/fcitx5-chinese-addons | 2 +- lib/fcitx5/src/main/cpp/fcitx5 | 2 +- lib/libime/src/main/cpp/libime | 2 +- 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateItemUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateItemUi.kt index 3ecd3cc83..f59c72a32 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateItemUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/CandidateItemUi.kt @@ -30,6 +30,11 @@ class CandidateItemUi(override val ctx: Context, theme: Theme) : Ui { override val root = view(::CustomGestureView) { background = pressHighlightDrawable(theme.keyPressHighlightColor) + /** + * candidate long press feedback is handled by [HorizontalCandidateComponent.showCandidateActionMenu] + */ + longPressFeedbackEnabled = false + add(text, lParams(wrapContent, matchParent) { gravity = gravityCenter }) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateComponent.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateComponent.kt index 6c3f18ac9..3ad99837b 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateComponent.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateComponent.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.runBlocking import org.fcitx.fcitx5.android.R import org.fcitx.fcitx5.android.core.FcitxEvent import org.fcitx.fcitx5.android.daemon.launchOnReady +import org.fcitx.fcitx5.android.data.InputFeedbacks import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.input.bar.ExpandButtonStateMachine.BooleanKey.ExpandedCandidatesEmpty import org.fcitx.fcitx5.android.input.bar.ExpandButtonStateMachine.TransitionEvent.ExpandedCandidatesUpdated @@ -210,6 +211,7 @@ class HorizontalCandidateComponent : service.lifecycleScope.launch { val actions = fcitx.runOnReady { getCandidateActions(idx) } if (actions.isEmpty()) return@launch + InputFeedbacks.hapticFeedback(ui.root, longPress = true) candidateActionMenu = PopupMenu(context, ui.root).apply { menu.add(buildSpannedString { bold { diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CustomGestureView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CustomGestureView.kt index ba7b92b91..8e101dd7d 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CustomGestureView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CustomGestureView.kt @@ -58,6 +58,9 @@ open class CustomGestureView(ctx: Context) : FrameLayout(ctx) { var longPressEnabled = false private var longPressJob: Job? = null + @Volatile + var longPressFeedbackEnabled = true + @Volatile private var repeatStarted = false var repeatEnabled = false @@ -90,7 +93,9 @@ open class CustomGestureView(ctx: Context) : FrameLayout(ctx) { private val touchSlop: Float = ViewConfiguration.get(ctx).scaledTouchSlop.toFloat() init { + // disable system sound effect and haptic feedback isSoundEffectsEnabled = false + isHapticFeedbackEnabled = false } override fun setEnabled(enabled: Boolean) { @@ -148,7 +153,9 @@ open class CustomGestureView(ctx: Context) : FrameLayout(ctx) { longPressJob?.cancel() longPressJob = lifecycleScope.launch { delay(longPressDelay.toLong()) - InputFeedbacks.hapticFeedback(this@CustomGestureView, true) + if (longPressFeedbackEnabled) { + InputFeedbacks.hapticFeedback(this@CustomGestureView, true) + } longPressTriggered = performLongClick() } } diff --git a/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons b/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons index d6fc7a0a6..9e57f69f9 160000 --- a/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons +++ b/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons @@ -1 +1 @@ -Subproject commit d6fc7a0a60ec0c543eca2255d2d9ee87bd0ef7ae +Subproject commit 9e57f69f979baa477caec60b4323fe5c6f4d1ff6 diff --git a/lib/fcitx5/src/main/cpp/fcitx5 b/lib/fcitx5/src/main/cpp/fcitx5 index 716fee751..57f77691a 160000 --- a/lib/fcitx5/src/main/cpp/fcitx5 +++ b/lib/fcitx5/src/main/cpp/fcitx5 @@ -1 +1 @@ -Subproject commit 716fee751feea4ddaa83ec16a3e632bcfa1bb6ea +Subproject commit 57f77691a5001e5f42af34eba607b47ffb2557e7 diff --git a/lib/libime/src/main/cpp/libime b/lib/libime/src/main/cpp/libime index ceff28119..1e5ae72fd 160000 --- a/lib/libime/src/main/cpp/libime +++ b/lib/libime/src/main/cpp/libime @@ -1 +1 @@ -Subproject commit ceff28119cd80c3104e165ca3c5753a48498b73b +Subproject commit 1e5ae72fd15cf51efc0b243363fa927bd98529cb From 7ded3e7f237a638500a53bd4321665c4e3fe0d14 Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 19 Jun 2024 23:30:57 +0800 Subject: [PATCH 015/181] Update fcitx5 submodules --- plugin/chewing/src/main/cpp/fcitx5-chewing | 2 +- plugin/rime/src/main/cpp/fcitx5-rime | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/chewing/src/main/cpp/fcitx5-chewing b/plugin/chewing/src/main/cpp/fcitx5-chewing index 192086007..5ad232a6e 160000 --- a/plugin/chewing/src/main/cpp/fcitx5-chewing +++ b/plugin/chewing/src/main/cpp/fcitx5-chewing @@ -1 +1 @@ -Subproject commit 1920860074081e864adbd01c99fcba7abd9d2714 +Subproject commit 5ad232a6e4b094fb1ede33b04432d030c6695ad5 diff --git a/plugin/rime/src/main/cpp/fcitx5-rime b/plugin/rime/src/main/cpp/fcitx5-rime index 474063472..91bcb0d81 160000 --- a/plugin/rime/src/main/cpp/fcitx5-rime +++ b/plugin/rime/src/main/cpp/fcitx5-rime @@ -1 +1 @@ -Subproject commit 4740634720b75498a6c84eea36fd78e74affa2a2 +Subproject commit 91bcb0d81160f8561303bce499e00feb6ccc54ae From 5c67e248b0ef6f13ea8da3bb2681ce7203425c48 Mon Sep 17 00:00:00 2001 From: Potato Hatsue <1793913507@qq.com> Date: Fri, 21 Jun 2024 01:33:10 +0900 Subject: [PATCH 016/181] nix: update lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 27aa732cc..80b28dcb1 100644 --- a/flake.lock +++ b/flake.lock @@ -36,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1711163522, - "narHash": "sha256-YN/Ciidm+A0fmJPWlHBGvVkcarYWSC+s3NTPk/P+q3c=", + "lastModified": 1718714799, + "narHash": "sha256-FUZpz9rg3gL8NVPKbqU8ei1VkPLsTIfAJ2fdAf5qjak=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "44d0940ea560dee511026a53f0e2e2cde489b4d4", + "rev": "c00d587b1a1afbf200b1d8f0b0e4ba9deb1c7f0e", "type": "github" }, "original": { From db49e705a01027697d342a9cc399fcd3f7bcdbdc Mon Sep 17 00:00:00 2001 From: Rocka Date: Fri, 21 Jun 2024 00:33:10 +0800 Subject: [PATCH 017/181] Refactor get{Global,Secure,System}Settings util functions --- .../fcitx5/android/data/InputFeedbacks.kt | 6 +-- .../fcitx5/android/utils/InputMethodUtil.kt | 2 +- .../fcitx/fcitx5/android/utils/Settings.kt | 38 +++++++++++++++++++ .../fcitx5/android/utils/SystemSettings.kt | 20 ---------- 4 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/utils/Settings.kt delete mode 100644 app/src/main/java/org/fcitx/fcitx5/android/utils/SystemSettings.kt diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt index f9d71acc6..329ead69e 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt @@ -14,7 +14,7 @@ import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.data.prefs.ManagedPreference import org.fcitx.fcitx5.android.utils.appContext import org.fcitx.fcitx5.android.utils.audioManager -import org.fcitx.fcitx5.android.utils.isSystemSettingEnabled +import org.fcitx.fcitx5.android.utils.getSystemSettings import org.fcitx.fcitx5.android.utils.vibrator object InputFeedbacks { @@ -31,11 +31,11 @@ object InputFeedbacks { private var systemHapticFeedback = false fun syncSystemPrefs() { - systemSoundEffects = isSystemSettingEnabled(Settings.System.SOUND_EFFECTS_ENABLED) + systemSoundEffects = getSystemSettings(Settings.System.SOUND_EFFECTS_ENABLED) == 1 // it says "Replaced by using android.os.VibrationAttributes.USAGE_TOUCH" // but gives no clue about how to use it, and this one still works @Suppress("DEPRECATION") - systemHapticFeedback = isSystemSettingEnabled(Settings.System.HAPTIC_FEEDBACK_ENABLED) + systemHapticFeedback = getSystemSettings(Settings.System.HAPTIC_FEEDBACK_ENABLED) == 1 } private val soundOnKeyPress by AppPrefs.getInstance().keyboard.soundOnKeyPress diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/InputMethodUtil.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/InputMethodUtil.kt index 747421c32..3f3f9d305 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/utils/InputMethodUtil.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/utils/InputMethodUtil.kt @@ -34,7 +34,7 @@ object InputMethodUtil { it.packageName == BuildConfig.APPLICATION_ID && it.serviceName == serviceName } ?: false } else { - getSecureSettings(Settings.Secure.DEFAULT_INPUT_METHOD) == componentName + getSecureSettings(Settings.Secure.DEFAULT_INPUT_METHOD) == componentName } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/Settings.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/Settings.kt new file mode 100644 index 000000000..eae1aac36 --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/utils/Settings.kt @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.utils + +import android.provider.Settings + +inline fun getGlobalSettings(name: String): T { + return when (T::class.java) { + String::class.java -> Settings.Global.getString(appContext.contentResolver, name) + Float::class.javaObjectType -> Settings.Global.getFloat(appContext.contentResolver, name, 0f) + Long::class.javaObjectType -> Settings.Global.getLong(appContext.contentResolver, name, 0L) + Int::class.javaObjectType -> Settings.Global.getInt(appContext.contentResolver, name, 0) + else -> throw IllegalArgumentException("Invalid settings type ${T::class.java.name}") + } as T +} + +inline fun getSecureSettings(name: String): T { + return when (T::class.java) { + String::class.java -> Settings.Secure.getString(appContext.contentResolver, name) + Float::class.javaObjectType -> Settings.Secure.getFloat(appContext.contentResolver, name, 0f) + Long::class.javaObjectType -> Settings.Secure.getLong(appContext.contentResolver, name, 0L) + Int::class.javaObjectType -> Settings.Secure.getInt(appContext.contentResolver, name, 0) + else -> throw IllegalArgumentException("Invalid settings type ${T::class.java.name}") + } as T +} + +inline fun getSystemSettings(name: String): T { + return when (T::class.java) { + String::class.java -> Settings.System.getString(appContext.contentResolver, name) + Float::class.javaObjectType -> Settings.System.getFloat(appContext.contentResolver, name, 0f) + Long::class.javaObjectType -> Settings.System.getLong(appContext.contentResolver, name, 0L) + Int::class.javaObjectType -> Settings.System.getInt(appContext.contentResolver, name, 0) + else -> throw IllegalArgumentException("Invalid settings type ${T::class.java.name}") + } as T +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/SystemSettings.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/SystemSettings.kt deleted file mode 100644 index a7aebabcf..000000000 --- a/app/src/main/java/org/fcitx/fcitx5/android/utils/SystemSettings.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors - */ - -package org.fcitx.fcitx5.android.utils - -import android.provider.Settings - -fun isSystemSettingEnabled(key: String): Boolean { - return try { - Settings.System.getInt(appContext.contentResolver, key) == 1 - } catch (e: Exception) { - false - } -} - -fun getSecureSettings(name: String): String? { - return Settings.Secure.getString(appContext.contentResolver, name) -} From f699abb37a7e28b6ee6c3fb50d4731783bb1b24f Mon Sep 17 00:00:00 2001 From: Rocka Date: Fri, 21 Jun 2024 00:56:07 +0800 Subject: [PATCH 018/181] Split DynamicListPreset and ProgressBarDialogIndeterminate --- .../android/ui/common/DynamicListPreset.kt | 50 +++++++++++++++++++ ...t.kt => ProgressBarDialogIndeterminate.kt} | 45 +---------------- 2 files changed, 52 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/ui/common/DynamicListPreset.kt rename app/src/main/java/org/fcitx/fcitx5/android/ui/common/{Preset.kt => ProgressBarDialogIndeterminate.kt} (61%) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/ui/common/DynamicListPreset.kt b/app/src/main/java/org/fcitx/fcitx5/android/ui/common/DynamicListPreset.kt new file mode 100644 index 000000000..779a0b8e8 --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/ui/common/DynamicListPreset.kt @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2021-2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.ui.common + +import android.content.Context +import android.view.View +import android.widget.CheckBox +import android.widget.ImageButton + +@Suppress("FunctionName") +fun Context.DynamicListUi( + mode: BaseDynamicListUi.Mode, + initialEntries: List, + enableOrder: Boolean = false, + initCheckBox: (CheckBox.(T) -> Unit) = { visibility = View.GONE }, + initSettingsButton: (ImageButton.(T) -> Unit) = { visibility = View.GONE }, + show: (T) -> String +): BaseDynamicListUi = object : + BaseDynamicListUi( + this, + mode, + initialEntries, + enableOrder, + initCheckBox, + initSettingsButton + ) { + init { + addTouchCallback() + } + + override fun showEntry(x: T): String = show(x) +} + +@Suppress("FunctionName") +fun Context.CheckBoxListUi( + initialEntries: List, + initCheckBox: (CheckBox.(T) -> Unit), + initSettingsButton: (ImageButton.(T) -> Unit), + show: (T) -> String +) = DynamicListUi( + BaseDynamicListUi.Mode.Immutable(), + initialEntries, + false, + initCheckBox, + initSettingsButton, + show +) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/ui/common/Preset.kt b/app/src/main/java/org/fcitx/fcitx5/android/ui/common/ProgressBarDialogIndeterminate.kt similarity index 61% rename from app/src/main/java/org/fcitx/fcitx5/android/ui/common/Preset.kt rename to app/src/main/java/org/fcitx/fcitx5/android/ui/common/ProgressBarDialogIndeterminate.kt index 33e9b9bf8..621066d5b 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/ui/common/Preset.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/ui/common/ProgressBarDialogIndeterminate.kt @@ -1,13 +1,11 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2021-2024 Fcitx5 for Android Contributors */ + package org.fcitx.fcitx5.android.ui.common import android.content.Context -import android.view.View -import android.widget.CheckBox -import android.widget.ImageButton import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.lifecycle.LifecycleCoroutineScope @@ -24,45 +22,6 @@ import splitties.views.dsl.core.styles.AndroidStyles import splitties.views.dsl.core.verticalLayout import splitties.views.dsl.core.verticalMargin -@Suppress("FunctionName") -fun Context.DynamicListUi( - mode: BaseDynamicListUi.Mode, - initialEntries: List, - enableOrder: Boolean = false, - initCheckBox: (CheckBox.(T) -> Unit) = { visibility = View.GONE }, - initSettingsButton: (ImageButton.(T) -> Unit) = { visibility = View.GONE }, - show: (T) -> String -): BaseDynamicListUi = object : - BaseDynamicListUi( - this, - mode, - initialEntries, - enableOrder, - initCheckBox, - initSettingsButton - ) { - init { - addTouchCallback() - } - - override fun showEntry(x: T): String = show(x) -} - -@Suppress("FunctionName") -fun Context.CheckBoxListUi( - initialEntries: List, - initCheckBox: (CheckBox.(T) -> Unit), - initSettingsButton: (ImageButton.(T) -> Unit), - show: (T) -> String -) = DynamicListUi( - BaseDynamicListUi.Mode.Immutable(), - initialEntries, - false, - initCheckBox, - initSettingsButton, - show -) - @Suppress("FunctionName") fun Context.ProgressBarDialogIndeterminate(@StringRes title: Int): AlertDialog.Builder { val androidStyles = AndroidStyles(this) From 2ebb9a04db64e0fe764c113ff462b53ca0df23b4 Mon Sep 17 00:00:00 2001 From: Rocka Date: Fri, 21 Jun 2024 00:57:50 +0800 Subject: [PATCH 019/181] Show text instead of indeterminate progress bar when animation disabled --- .../common/ProgressBarDialogIndeterminate.kt | 23 +++++++++++++++++-- app/src/main/res/values/strings.xml | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/ui/common/ProgressBarDialogIndeterminate.kt b/app/src/main/java/org/fcitx/fcitx5/android/ui/common/ProgressBarDialogIndeterminate.kt index 621066d5b..373857cd6 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/ui/common/ProgressBarDialogIndeterminate.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/ui/common/ProgressBarDialogIndeterminate.kt @@ -5,7 +5,10 @@ package org.fcitx.fcitx5.android.ui.common +import android.animation.ValueAnimator import android.content.Context +import android.os.Build +import android.provider.Settings import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.lifecycle.LifecycleCoroutineScope @@ -13,14 +16,18 @@ import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.fcitx.fcitx5.android.R +import org.fcitx.fcitx5.android.utils.getGlobalSettings import splitties.dimensions.dp +import splitties.resources.resolveThemeAttribute import splitties.views.dsl.core.add import splitties.views.dsl.core.horizontalMargin import splitties.views.dsl.core.lParams import splitties.views.dsl.core.matchParent import splitties.views.dsl.core.styles.AndroidStyles +import splitties.views.dsl.core.textView import splitties.views.dsl.core.verticalLayout import splitties.views.dsl.core.verticalMargin +import splitties.views.textAppearance @Suppress("FunctionName") fun Context.ProgressBarDialogIndeterminate(@StringRes title: Int): AlertDialog.Builder { @@ -28,8 +35,20 @@ fun Context.ProgressBarDialogIndeterminate(@StringRes title: Int): AlertDialog.B return AlertDialog.Builder(this) .setTitle(title) .setView(verticalLayout { - add(androidStyles.progressBar.horizontal { - isIndeterminate = true + val shouldAnimate = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ValueAnimator.areAnimatorsEnabled() + } else { + getGlobalSettings(Settings.Global.ANIMATOR_DURATION_SCALE) > 0f + } + add(if (shouldAnimate) { + androidStyles.progressBar.horizontal { + isIndeterminate = true + } + } else { + textView { + setText(R.string.please_wait) + textAppearance = resolveThemeAttribute(android.R.attr.textAppearanceListItem) + } }, lParams { width = matchParent verticalMargin = dp(20) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ab1543278..2b07bedec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -259,4 +259,5 @@ Application data directory will be opened. RIME user data directory is located in \"data/rime\". Application will restart to apply new settings. Proceed? Swipe on space key to move cursor + Please wait… From 8b4c3a77381e3148ce62cdaf3918a39b30b558c3 Mon Sep 17 00:00:00 2001 From: Rocka Date: Fri, 21 Jun 2024 01:25:44 +0800 Subject: [PATCH 020/181] Swipe down voice input / expand candidate button to hide keyboard --- .../android/input/bar/KawaiiBarComponent.kt | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/bar/KawaiiBarComponent.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/bar/KawaiiBarComponent.kt index 1e7dd375c..1ee08f983 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/bar/KawaiiBarComponent.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/bar/KawaiiBarComponent.kt @@ -58,6 +58,7 @@ import org.fcitx.fcitx5.android.input.dependency.inputMethodService import org.fcitx.fcitx5.android.input.dependency.theme import org.fcitx.fcitx5.android.input.editing.TextEditingWindow import org.fcitx.fcitx5.android.input.keyboard.CommonKeyActionListener +import org.fcitx.fcitx5.android.input.keyboard.CustomGestureView import org.fcitx.fcitx5.android.input.keyboard.KeyboardWindow import org.fcitx.fcitx5.android.input.popup.PopupComponent import org.fcitx.fcitx5.android.input.status.StatusAreaWindow @@ -185,6 +186,13 @@ class KawaiiBarComponent : UniqueViewComponent( service.requestHideSelf(0) } + private val swipeDownHideKeyboardCallback = CustomGestureView.OnGestureListener { _, e -> + if (e.type == CustomGestureView.GestureType.Up && e.totalY > 0) { + service.requestHideSelf(0) + true + } else false + } + private var voiceInputSubtype: Pair? = null private val switchToVoiceInputCallback = View.OnClickListener { @@ -214,7 +222,12 @@ class KawaiiBarComponent : UniqueViewComponent( launchClipboardTimeoutJob() } } - hideKeyboardButton.setOnClickListener(hideKeyboardCallback) + hideKeyboardButton.apply { + setOnClickListener(hideKeyboardCallback) + swipeEnabled = true + swipeThresholdY = dp(HEIGHT.toFloat()) + onGestureListener = swipeDownHideKeyboardCallback + } buttonsUi.apply { undoButton.setOnClickListener { service.sendCombinationKeyEvents(KeyEvent.KEYCODE_Z, ctrl = true) @@ -253,7 +266,13 @@ class KawaiiBarComponent : UniqueViewComponent( } private val candidateUi by lazy { - CandidateUi(context, theme, horizontalCandidate.view) + CandidateUi(context, theme, horizontalCandidate.view).apply { + expandButton.apply { + swipeEnabled = true + swipeThresholdY = dp(HEIGHT.toFloat()) + onGestureListener = swipeDownHideKeyboardCallback + } + } } private val titleUi by lazy { From 506341bfe0b5ba4c5322fff964ce9e650da471a8 Mon Sep 17 00:00:00 2001 From: Rocka Date: Fri, 21 Jun 2024 01:33:34 +0800 Subject: [PATCH 021/181] Fix composing state tracking when interrupting input --- .../org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt index 78acd8624..01e0a9438 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt @@ -794,6 +794,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { override fun onFinishInputView(finishingInput: Boolean) { Timber.d("onFinishInputView: finishingInput=$finishingInput") currentInputConnection?.finishComposingText() + resetComposingState() postFcitxJob { focus(false) } From ea2d5c8a439a4bc400ae976c5ded278f39c5ce12 Mon Sep 17 00:00:00 2001 From: zhichengroup Date: Fri, 5 Jul 2024 00:01:37 +0800 Subject: [PATCH 022/181] Add option to perform haptic feedback on keyup (#539) Co-authored-by: Rocka --- .../java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt | 5 +++-- .../java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt | 5 +++++ .../fcitx/fcitx5/android/input/keyboard/CustomGestureView.kt | 1 + app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt index 329ead69e..701cfba6f 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt @@ -41,6 +41,7 @@ object InputFeedbacks { private val soundOnKeyPress by AppPrefs.getInstance().keyboard.soundOnKeyPress private val soundOnKeyPressVolume by AppPrefs.getInstance().keyboard.soundOnKeyPressVolume private val hapticOnKeyPress by AppPrefs.getInstance().keyboard.hapticOnKeyPress + private val hapticOnKeyUp by AppPrefs.getInstance().keyboard.hapticOnKeyUp private val buttonPressVibrationMilliseconds by AppPrefs.getInstance().keyboard.buttonPressVibrationMilliseconds private val buttonLongPressVibrationMilliseconds by AppPrefs.getInstance().keyboard.buttonLongPressVibrationMilliseconds private val buttonPressVibrationAmplitude by AppPrefs.getInstance().keyboard.buttonPressVibrationAmplitude @@ -53,13 +54,13 @@ object InputFeedbacks { private val audioManager = appContext.audioManager - fun hapticFeedback(view: View, longPress: Boolean = false) { + fun hapticFeedback(view: View, longPress: Boolean = false, keyUp: Boolean = false) { when (hapticOnKeyPress) { InputFeedbackMode.Enabled -> {} InputFeedbackMode.Disabled -> return InputFeedbackMode.FollowingSystem -> if (!systemHapticFeedback) return } - + if (keyUp && !hapticOnKeyUp) return val duration: Long val amplitude: Int val hfc: Int diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt index 4a3fe9997..ab48383d4 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt @@ -62,6 +62,11 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) { R.string.disabled ) ) + val hapticOnKeyUp = switch( + R.string.button_up_haptic_feedback, + "haptic_on_keyup", + false + ) { hapticOnKeyPress.getValue() != InputFeedbackMode.Disabled } val buttonPressVibrationMilliseconds: ManagedPreference.PInt val buttonLongPressVibrationMilliseconds: ManagedPreference.PInt diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CustomGestureView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CustomGestureView.kt index 8e101dd7d..b76804469 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CustomGestureView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CustomGestureView.kt @@ -180,6 +180,7 @@ open class CustomGestureView(ctx: Context) : FrameLayout(ctx) { } MotionEvent.ACTION_UP -> { isPressed = false + InputFeedbacks.hapticFeedback(this, longPress = true, keyUp = true) dispatchGestureEvent(GestureType.Up, event.x, event.y) val shouldPerformClick = !(touchMovedOutside || longPressTriggered || diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 6db0aaff5..373714385 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -14,6 +14,7 @@ 隐藏快捷键配置 (不可用) 按键时振动 + 按键松开时振动 按键振动时长 短按 长按 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2b07bedec..776b404fa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ Hide hotkey configurations (Not Available) Haptic feedback on keypress + Haptic feedback on keyup Keypress vibration duration Press Long press From e071ccfba4861faeafae138d9df4e0e0f4676c0f Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 6 Jul 2024 03:02:14 +0800 Subject: [PATCH 023/181] Configurable clipboard entry radius Co-authored-by: cc --- .../org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt | 3 +++ .../fcitx5/android/input/clipboard/ClipboardAdapter.kt | 3 ++- .../fcitx5/android/input/clipboard/ClipboardEntryUi.kt | 3 +-- .../fcitx5/android/input/clipboard/ClipboardWindow.kt | 9 ++++++++- app/src/main/res/values/strings.xml | 1 + 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt index 521b56608..c06c20fa1 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt @@ -76,6 +76,9 @@ class ThemePrefs(sharedPreferences: SharedPreferences) : val keyRadius = int(R.string.key_radius, "key_radius", 4, 0, 48, "dp") + val clipboardEntryRadius = + int(R.string.clipboard_entry_radius, "clipboard_entry_radius", 2, 0, 48, "dp") + enum class PunctuationPosition { Bottom, TopRight; diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardAdapter.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardAdapter.kt index 753e01a4a..787177091 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardAdapter.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardAdapter.kt @@ -22,6 +22,7 @@ import kotlin.math.min abstract class ClipboardAdapter( private val theme: Theme, + private val entryRadius: Float, private val maskSensitive: Boolean ) : PagingDataAdapter(diffCallback) { @@ -87,7 +88,7 @@ abstract class ClipboardAdapter( class ViewHolder(val entryUi: ClipboardEntryUi) : RecyclerView.ViewHolder(entryUi.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = - ViewHolder(ClipboardEntryUi(parent.context, theme)) + ViewHolder(ClipboardEntryUi(parent.context, theme, entryRadius)) override fun onBindViewHolder(holder: ViewHolder, position: Int) { val entry = getItem(position) ?: return diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardEntryUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardEntryUi.kt index 7c423a3b3..6d6eb53ec 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardEntryUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardEntryUi.kt @@ -31,7 +31,7 @@ import splitties.views.dsl.core.wrapContent import splitties.views.imageDrawable import splitties.views.setPaddingDp -class ClipboardEntryUi(override val ctx: Context, private val theme: Theme) : Ui { +class ClipboardEntryUi(override val ctx: Context, private val theme: Theme, radius: Float) : Ui { val textView = textView { minLines = 1 @@ -62,7 +62,6 @@ class ClipboardEntryUi(override val ctx: Context, private val theme: Theme) : Ui override val root = CustomGestureView(ctx).apply { isClickable = true minimumHeight = dp(30) - val radius = dp(2f) foreground = RippleDrawable( ColorStateList.valueOf(theme.keyPressHighlightColor), null, GradientDrawable().apply { diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt index b8d53e0f2..cbc2f52ca 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt @@ -29,6 +29,7 @@ import org.fcitx.fcitx5.android.data.clipboard.ClipboardManager import org.fcitx.fcitx5.android.data.clipboard.db.ClipboardEntry import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.data.prefs.ManagedPreference +import org.fcitx.fcitx5.android.data.theme.ThemeManager import org.fcitx.fcitx5.android.input.FcitxInputMethodService import org.fcitx.fcitx5.android.input.clipboard.ClipboardStateMachine.BooleanKey.ClipboardDbEmpty import org.fcitx.fcitx5.android.input.clipboard.ClipboardStateMachine.BooleanKey.ClipboardListeningEnabled @@ -74,13 +75,19 @@ class ClipboardWindow : InputWindow.ExtendedInputWindow() { private val clipboardReturnAfterPaste by prefs.clipboardReturnAfterPaste private val clipboardMaskSensitive by prefs.clipboardMaskSensitive + private val clipboardEntryRadius by ThemeManager.prefs.clipboardEntryRadius + private val clipboardEntriesPager by lazy { Pager(PagingConfig(pageSize = 16)) { ClipboardManager.allEntries() } } private var adapterSubmitJob: Job? = null private val adapter: ClipboardAdapter by lazy { - object : ClipboardAdapter(this@ClipboardWindow.theme, clipboardMaskSensitive) { + object : ClipboardAdapter( + theme, + context.dp(clipboardEntryRadius.toFloat()), + clipboardMaskSensitive + ) { override fun onPin(id: Int) { service.lifecycleScope.launch { ClipboardManager.pin(id) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 776b404fa..37d0306d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,6 +107,7 @@ Enable key border Brightness Key radius + Clipboard entry radius Key horizontal margin Key vertical margin Configure From e7e88eb55c6c31743093ed6511cbd99ca5c785b9 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 6 Jul 2024 03:03:42 +0800 Subject: [PATCH 024/181] Apply keyBorder prefs to TextEditingUi Co-authored-by: cc --- .../fcitx5/android/data/theme/ThemePrefs.kt | 3 + .../input/editing/TextEditingButton.kt | 207 ++++++++++++++++++ .../android/input/editing/TextEditingUi.kt | 111 ++-------- .../input/editing/TextEditingWindow.kt | 7 +- app/src/main/res/values/strings.xml | 1 + 5 files changed, 239 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingButton.kt diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt index c06c20fa1..3e5e3b3a3 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt @@ -76,6 +76,9 @@ class ThemePrefs(sharedPreferences: SharedPreferences) : val keyRadius = int(R.string.key_radius, "key_radius", 4, 0, 48, "dp") + val textEditingButtonRadius = + int(R.string.text_editing_button_radius, "text_editing_button_radius", 8, 0, 48, "dp") + val clipboardEntryRadius = int(R.string.clipboard_entry_radius, "clipboard_entry_radius", 2, 0, 48, "dp") diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingButton.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingButton.kt new file mode 100644 index 000000000..b54d6997a --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingButton.kt @@ -0,0 +1,207 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.input.editing + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.InsetDrawable +import android.graphics.drawable.LayerDrawable +import android.graphics.drawable.RippleDrawable +import android.graphics.drawable.StateListDrawable +import androidx.annotation.DrawableRes +import org.fcitx.fcitx5.android.data.theme.Theme +import org.fcitx.fcitx5.android.input.keyboard.CustomGestureView +import org.fcitx.fcitx5.android.utils.borderDrawable +import org.fcitx.fcitx5.android.utils.pressHighlightDrawable +import org.fcitx.fcitx5.android.utils.rippleDrawable +import splitties.dimensions.dp +import splitties.views.dsl.core.add +import splitties.views.dsl.core.imageView +import splitties.views.dsl.core.lParams +import splitties.views.dsl.core.textView +import splitties.views.dsl.core.wrapContent +import splitties.views.gravityCenter +import splitties.views.imageResource +import kotlin.math.max + +@SuppressLint("ViewConstructor") +class TextEditingButton( + ctx: Context, + private val theme: Theme, + private val rippled: Boolean, + private val bordered: Boolean, + private val radius: Float, + private val altStyle: Boolean = false +) : CustomGestureView(ctx) { + + // bordered + private val shadowWidth = dp(1) + private val hMargin = dp(4) + private val vMargin = dp(4) + + // !bordered + private val lineWidth = max(1, dp(1) / 2) + + init { + if (bordered) { + background = LayerDrawable( + arrayOf( + GradientDrawable().apply { + cornerRadius = radius + setColor(theme.keyShadowColor) + }, + GradientDrawable().apply { + cornerRadius = radius + setColor(if (altStyle) theme.altKeyBackgroundColor else theme.keyBackgroundColor) + } + ) + ).apply { + setLayerInset(0, hMargin, vMargin, hMargin, vMargin - shadowWidth) + setLayerInset(1, hMargin, vMargin, hMargin, vMargin) + } + if (rippled) { + foreground = RippleDrawable( + ColorStateList.valueOf(theme.keyPressHighlightColor), null, + // ripple should be masked with an opaque color + InsetDrawable( + GradientDrawable().apply { + cornerRadius = radius + setColor(Color.WHITE) + }, + hMargin, vMargin, hMargin, vMargin + ) + ) + } else { + foreground = StateListDrawable().apply { + addState( + intArrayOf(android.R.attr.state_pressed), + // use mask drawable as highlight directly + InsetDrawable( + GradientDrawable().apply { + cornerRadius = radius + setColor(theme.keyPressHighlightColor) + }, + hMargin, vMargin, hMargin, vMargin + ) + ) + } + } + } else { + background = borderDrawable(lineWidth, theme.dividerColor) + foreground = + if (rippled) rippleDrawable(theme.keyPressHighlightColor) + else pressHighlightDrawable(theme.keyPressHighlightColor) + } + } + + val textView = textView { + isClickable = false + isFocusable = false + background = null + setTextColor(if (altStyle) theme.altKeyTextColor else theme.keyTextColor) + } + + val imageView = imageView { + isClickable = false + isFocusable = false + imageTintList = ColorStateList.valueOf(theme.altKeyTextColor) + } + + fun setText(id: Int) { + textView.setText(id) + removeView(imageView) + add(textView, lParams(wrapContent, wrapContent, gravityCenter)) + } + + fun setIcon(@DrawableRes icon: Int) { + imageView.imageResource = icon + removeView(textView) + add(imageView, lParams(wrapContent, wrapContent, gravityCenter)) + } + + fun enableActivatedState() { + textView.setTextColor( + ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_activated), + intArrayOf(android.R.attr.state_enabled) + ), + intArrayOf( + theme.genericActiveForegroundColor, + if (altStyle) theme.altKeyTextColor else theme.keyTextColor + ) + ) + ) + imageView.imageTintList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_activated), + intArrayOf(android.R.attr.state_enabled) + ), + intArrayOf( + theme.genericActiveForegroundColor, + theme.altKeyTextColor + ) + ) + if (bordered) { + background = StateListDrawable().apply { + addState( + intArrayOf(android.R.attr.state_activated), + LayerDrawable( + arrayOf( + GradientDrawable().apply { + cornerRadius = radius + setColor(theme.keyShadowColor) + }, + GradientDrawable().apply { + cornerRadius = radius + setColor(theme.genericActiveBackgroundColor) + } + ) + ).apply { + setLayerInset(0, hMargin, vMargin, hMargin, vMargin - shadowWidth) + setLayerInset(1, hMargin, vMargin, hMargin, vMargin) + } + ) + addState( + intArrayOf(android.R.attr.state_enabled), + LayerDrawable( + arrayOf( + GradientDrawable().apply { + cornerRadius = radius + setColor(theme.keyShadowColor) + }, + GradientDrawable().apply { + cornerRadius = radius + setColor(theme.keyBackgroundColor) + } + ) + ).apply { + setLayerInset(0, hMargin, vMargin, hMargin, vMargin - shadowWidth) + setLayerInset(1, hMargin, vMargin, hMargin, vMargin) + } + ) + } + } else { + background = StateListDrawable().apply { + addState( + intArrayOf(android.R.attr.state_activated), + borderDrawable( + lineWidth, + theme.dividerColor, + theme.genericActiveBackgroundColor + ) + ) + addState( + intArrayOf(android.R.attr.state_enabled), + borderDrawable(lineWidth, theme.dividerColor) + ) + } + } + } +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingUi.kt index 546189ba8..20e23b241 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingUi.kt @@ -5,22 +5,14 @@ package org.fcitx.fcitx5.android.input.editing import android.content.Context -import android.content.res.ColorStateList -import android.graphics.drawable.StateListDrawable import android.view.View import androidx.annotation.DrawableRes import androidx.annotation.StringRes import org.fcitx.fcitx5.android.R import org.fcitx.fcitx5.android.data.InputFeedbacks import org.fcitx.fcitx5.android.data.theme.Theme -import org.fcitx.fcitx5.android.data.theme.ThemeManager import org.fcitx.fcitx5.android.input.bar.ui.ToolButton -import org.fcitx.fcitx5.android.input.keyboard.CustomGestureView -import org.fcitx.fcitx5.android.utils.borderDrawable -import org.fcitx.fcitx5.android.utils.pressHighlightDrawable -import org.fcitx.fcitx5.android.utils.rippleDrawable import splitties.dimensions.dp -import splitties.resources.drawable import splitties.views.dsl.constraintlayout.above import splitties.views.dsl.constraintlayout.below import splitties.views.dsl.constraintlayout.bottomOfParent @@ -34,64 +26,25 @@ import splitties.views.dsl.constraintlayout.topOfParent import splitties.views.dsl.core.Ui import splitties.views.dsl.core.add import splitties.views.dsl.core.horizontalLayout -import splitties.views.dsl.core.imageView import splitties.views.dsl.core.lParams -import splitties.views.dsl.core.textView -import splitties.views.dsl.core.wrapContent -import splitties.views.gravityCenter -import splitties.views.imageDrawable -import splitties.views.padding -class TextEditingUi(override val ctx: Context, private val theme: Theme) : Ui { - - private val keyRippleEffect by ThemeManager.prefs.keyRippleEffect - - private val borderWidth = ctx.dp(1) / 2 - - private fun View.applyBorderedBackground() { - background = borderDrawable(borderWidth, theme.dividerColor) - foreground = - if (keyRippleEffect) rippleDrawable(theme.keyPressHighlightColor) - else pressHighlightDrawable(theme.keyPressHighlightColor) - } - - class GTextButton(context: Context) : CustomGestureView(context) { - val text = textView { - isClickable = false - isFocusable = false - background = null - } - - init { - add(text, lParams(wrapContent, wrapContent, gravityCenter)) - } - } - - class GImageButton(context: Context) : CustomGestureView(context) { - val image = imageView { - isClickable = false - isFocusable = false +class TextEditingUi( + override val ctx: Context, + private val theme: Theme, + private val ripple: Boolean, + private val border: Boolean, + private val radius: Float +) : Ui { + + private fun textButton(@StringRes id: Int, altStyle: Boolean = false) = + TextEditingButton(ctx, theme, ripple, border, radius, altStyle).apply { + setText(id) } - init { - add(image, lParams(wrapContent, wrapContent, gravityCenter)) + private fun iconButton(@DrawableRes icon: Int, altStyle: Boolean = false) = + TextEditingButton(ctx, theme, ripple, border, radius, altStyle).apply { + setIcon(icon) } - } - - private fun textButton(@StringRes id: Int) = GTextButton(ctx).apply { - text.setText(id) - text.setTextColor(theme.keyTextColor) - stateListAnimator = null - applyBorderedBackground() - } - - private fun iconButton(@DrawableRes icon: Int) = GImageButton(ctx).apply { - image.imageDrawable = drawable(icon)!!.apply { - setTint(theme.altKeyTextColor) - } - padding = dp(10) - applyBorderedBackground() - } val upButton = iconButton(R.drawable.ic_baseline_keyboard_arrow_up_24) @@ -102,44 +55,24 @@ class TextEditingUi(override val ctx: Context, private val theme: Theme) : Ui { val leftButton = iconButton(R.drawable.ic_baseline_keyboard_arrow_left_24) val selectButton = textButton(R.string.select).apply { - text.setTextColor( - ColorStateList( - arrayOf( - intArrayOf(android.R.attr.state_activated), - intArrayOf(android.R.attr.state_enabled) - ), - intArrayOf(theme.genericActiveForegroundColor, theme.keyTextColor) - ) - ) - background = StateListDrawable().apply { - addState( - intArrayOf(android.R.attr.state_activated), - borderDrawable( - borderWidth, - theme.dividerColor, - theme.genericActiveBackgroundColor - ) - ) - addState( - intArrayOf(android.R.attr.state_enabled), - borderDrawable(borderWidth, theme.dividerColor) - ) - } + enableActivatedState() } val homeButton = iconButton(R.drawable.ic_baseline_first_page_24) val endButton = iconButton(R.drawable.ic_baseline_last_page_24) - val selectAllButton = textButton(android.R.string.selectAll) + val selectAllButton = textButton(android.R.string.selectAll, altStyle = true) - val cutButton = textButton(android.R.string.cut).apply { visibility = View.GONE } + val cutButton = textButton(android.R.string.cut, altStyle = true).apply { + visibility = View.GONE + } - val copyButton = textButton(android.R.string.copy) + val copyButton = textButton(android.R.string.copy, altStyle = true) - val pasteButton = textButton(android.R.string.paste) + val pasteButton = textButton(android.R.string.paste, altStyle = true) - val backspaceButton = iconButton(R.drawable.ic_baseline_backspace_24).apply { + val backspaceButton = iconButton(R.drawable.ic_baseline_backspace_24, altStyle = true).apply { soundEffect = InputFeedbacks.SoundEffect.Delete } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingWindow.kt index fa5feceff..ede4e89e3 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingWindow.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingWindow.kt @@ -7,6 +7,7 @@ package org.fcitx.fcitx5.android.input.editing import android.view.KeyEvent import android.view.View import org.fcitx.fcitx5.android.R +import org.fcitx.fcitx5.android.data.theme.ThemeManager import org.fcitx.fcitx5.android.input.FcitxInputMethodService import org.fcitx.fcitx5.android.input.broadcast.InputBroadcastReceiver import org.fcitx.fcitx5.android.input.clipboard.ClipboardWindow @@ -24,6 +25,10 @@ class TextEditingWindow : InputWindow.ExtendedInputWindow(), private val windowManager: InputWindowManager by manager.must() private val theme by manager.theme() + private val buttonRipple by ThemeManager.prefs.keyRippleEffect + private val buttonBorder by ThemeManager.prefs.keyBorder + private val buttonRadius by ThemeManager.prefs.textEditingButtonRadius + private var hasSelection = false private var userSelection = false @@ -32,7 +37,7 @@ class TextEditingWindow : InputWindow.ExtendedInputWindow(), } private val ui by lazy { - TextEditingUi(context, theme).apply { + TextEditingUi(context, theme, buttonRipple, buttonBorder, buttonRadius.toFloat()).apply { fun CustomGestureView.onClickWithRepeating(block: () -> Unit) { setOnClickListener { block() } repeatEnabled = true diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 37d0306d4..2e6159200 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,6 +107,7 @@ Enable key border Brightness Key radius + Text editing button radius Clipboard entry radius Key horizontal margin Key vertical margin From 57a15d8ce2e628be5b31c1be3f6c073211fd4cb9 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 6 Jul 2024 03:05:26 +0800 Subject: [PATCH 025/181] Apply keyBorder prefs to symbolPicker Co-authored-by: cc --- .../android/input/picker/PickerPageUi.kt | 28 +++++++++++-------- .../input/picker/PickerPagesAdapter.kt | 7 +++-- .../android/input/picker/PickerWindow.kt | 13 ++++++--- .../input/picker/PickerWindowPreset.kt | 6 ++-- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPageUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPageUi.kt index 422c7c834..f728e048d 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPageUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPageUi.kt @@ -4,6 +4,7 @@ */ package org.fcitx.fcitx5.android.input.picker +import android.annotation.SuppressLint import android.content.Context import android.view.ViewGroup import org.fcitx.fcitx5.android.R @@ -43,7 +44,12 @@ import splitties.views.dsl.core.Ui import splitties.views.dsl.core.add import splitties.views.dsl.core.matchParent -class PickerPageUi(override val ctx: Context, val theme: Theme, private val density: Density) : Ui { +class PickerPageUi( + override val ctx: Context, + theme: Theme, + density: Density, + bordered: Boolean = false +) : Ui { enum class Density( val pageSize: Int, @@ -63,15 +69,7 @@ class PickerPageUi(override val ctx: Context, val theme: Theme, private val dens } companion object { - val BackspaceAppearance = Appearance.Image( - src = R.drawable.ic_baseline_backspace_24, - variant = Variant.Alternative, - border = Border.Off, - viewId = R.id.button_backspace - ) - val BackspaceAction = SymAction(KeySym(FcitxKeyMapping.FcitxKey_BackSpace)) - private var popupOnKeyPress by AppPrefs.getInstance().keyboard.popupOnKeyPress } @@ -82,7 +80,7 @@ class PickerPageUi(override val ctx: Context, val theme: Theme, private val dens displayText = "", textSize = density.textSize, variant = Variant.Normal, - border = Border.Off + border = if (bordered) Border.On else Border.Off ) private val keyViews = Array(density.pageSize) { @@ -96,7 +94,14 @@ class PickerPageUi(override val ctx: Context, val theme: Theme, private val dens } } - private val backspaceKey = ImageKeyView(ctx, theme, BackspaceAppearance).apply { + private val backspaceAppearance = Appearance.Image( + src = R.drawable.ic_baseline_backspace_24, + variant = Variant.Alternative, + border = if (bordered) Border.On else Border.Off, + viewId = R.id.button_backspace + ) + + private val backspaceKey = ImageKeyView(ctx, theme, backspaceAppearance).apply { setOnClickListener { onBackspaceClick() } repeatEnabled = true onRepeatListener = { onBackspaceClick() } @@ -204,6 +209,7 @@ class PickerPageUi(override val ctx: Context, val theme: Theme, private val dens keyView.apply { if (i >= items.size) { isEnabled = false + @SuppressLint("SetTextI18n") mainText.text = "" setOnClickListener(null) setOnLongClickListener(null) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPagesAdapter.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPagesAdapter.kt index 539788b20..6dec5a05e 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPagesAdapter.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPagesAdapter.kt @@ -16,8 +16,9 @@ class PickerPagesAdapter( private val keyActionListener: KeyActionListener, private val popupActionListener: PopupActionListener, data: List>>, - val density: PickerPageUi.Density, - recentlyUsedFileName: String + private val density: PickerPageUi.Density, + recentlyUsedFileName: String, + private val bordered: Boolean = false ) : RecyclerView.Adapter() { class ViewHolder(val ui: PickerPageUi) : RecyclerView.ViewHolder(ui.root) @@ -110,7 +111,7 @@ class PickerPagesAdapter( override fun getItemCount() = pages.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - return ViewHolder(PickerPageUi(parent.context, theme, density)) + return ViewHolder(PickerPageUi(parent.context, theme, density, bordered)) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindow.kt index 3c4b2d954..a7f5163a7 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindow.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindow.kt @@ -12,6 +12,7 @@ import androidx.transition.Transition import androidx.viewpager2.widget.ViewPager2 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.fcitx.fcitx5.android.data.theme.ThemeManager import org.fcitx.fcitx5.android.input.broadcast.ReturnKeyDrawableComponent import org.fcitx.fcitx5.android.input.dependency.inputMethodService import org.fcitx.fcitx5.android.input.dependency.theme @@ -30,10 +31,11 @@ import org.mechdancer.dependency.manager.must class PickerWindow( override val key: Key, - val data: List>>, - val density: PickerPageUi.Density, + private val data: List>>, + private val density: PickerPageUi.Density, private val switchKey: KeyDef, - val popupPreview: Boolean = true + private val popupPreview: Boolean = true, + private val followKeyBorder: Boolean = true ) : InputWindow.ExtendedInputWindow(), EssentialWindow { enum class Key : EssentialWindow.Key { @@ -49,6 +51,8 @@ class PickerWindow( private val popup: PopupComponent by manager.must() private val returnKeyDrawable: ReturnKeyDrawableComponent by manager.must() + private val keyBorder by ThemeManager.prefs.keyBorder + private lateinit var pickerLayout: PickerLayout private lateinit var pickerPagesAdapter: PickerPagesAdapter @@ -116,8 +120,9 @@ class PickerWindow( override fun onCreateView() = PickerLayout(context, theme, switchKey).apply { pickerLayout = this + val bordered = followKeyBorder && keyBorder pickerPagesAdapter = PickerPagesAdapter( - theme, keyActionListener, popupActionListener, data, density, key.name + theme, keyActionListener, popupActionListener, data, density, key.name, bordered ) tabsUi.apply { setTabs(pickerPagesAdapter.categories) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindowPreset.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindowPreset.kt index 5d6bd6d95..5fae0d885 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindowPreset.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindowPreset.kt @@ -21,14 +21,16 @@ fun emojiPicker(): PickerWindow = PickerWindow( key = PickerWindow.Key.Emoji, data = PickerData.Emoji, density = PickerPageUi.Density.Medium, - popupPreview = false, switchKey = TextPickerSwitchKey(":-)", PickerWindow.Key.Emoticon), + popupPreview = false, + followKeyBorder = false ) fun emoticonPicker(): PickerWindow = PickerWindow( key = PickerWindow.Key.Emoticon, data = PickerData.Emoticon, density = PickerPageUi.Density.Low, + switchKey = ImagePickerSwitchKey(R.drawable.ic_baseline_tag_faces_24, PickerWindow.Key.Emoji), popupPreview = false, - switchKey = ImagePickerSwitchKey(R.drawable.ic_baseline_tag_faces_24, PickerWindow.Key.Emoji) + followKeyBorder = false ) From a9a9666fcc65c772d58c433db209fdb5a41b2770 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 6 Jul 2024 04:24:25 +0800 Subject: [PATCH 026/181] Reuse drawables between KeyView and TextEditingButton --- .../input/editing/TextEditingButton.kt | 98 +++++-------------- .../android/input/keyboard/KeyDrawable.kt | 60 ++++++++++++ .../fcitx5/android/input/keyboard/KeyView.kt | 84 +++++----------- .../fcitx/fcitx5/android/utils/Drawable.kt | 27 +++-- 4 files changed, 129 insertions(+), 140 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDrawable.kt diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingButton.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingButton.kt index b54d6997a..356e7709c 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingButton.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/editing/TextEditingButton.kt @@ -8,15 +8,13 @@ package org.fcitx.fcitx5.android.input.editing import android.annotation.SuppressLint import android.content.Context import android.content.res.ColorStateList -import android.graphics.Color -import android.graphics.drawable.GradientDrawable -import android.graphics.drawable.InsetDrawable -import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.graphics.drawable.StateListDrawable import androidx.annotation.DrawableRes import org.fcitx.fcitx5.android.data.theme.Theme import org.fcitx.fcitx5.android.input.keyboard.CustomGestureView +import org.fcitx.fcitx5.android.input.keyboard.borderedKeyBackgroundDrawable +import org.fcitx.fcitx5.android.input.keyboard.insetRadiusDrawable import org.fcitx.fcitx5.android.utils.borderDrawable import org.fcitx.fcitx5.android.utils.pressHighlightDrawable import org.fcitx.fcitx5.android.utils.rippleDrawable @@ -42,53 +40,29 @@ class TextEditingButton( // bordered private val shadowWidth = dp(1) - private val hMargin = dp(4) - private val vMargin = dp(4) + private val hInset = dp(4) + private val vInset = dp(4) // !bordered private val lineWidth = max(1, dp(1) / 2) init { if (bordered) { - background = LayerDrawable( - arrayOf( - GradientDrawable().apply { - cornerRadius = radius - setColor(theme.keyShadowColor) - }, - GradientDrawable().apply { - cornerRadius = radius - setColor(if (altStyle) theme.altKeyBackgroundColor else theme.keyBackgroundColor) - } - ) - ).apply { - setLayerInset(0, hMargin, vMargin, hMargin, vMargin - shadowWidth) - setLayerInset(1, hMargin, vMargin, hMargin, vMargin) - } - if (rippled) { - foreground = RippleDrawable( + val bkgColor = if (altStyle) theme.altKeyBackgroundColor else theme.keyBackgroundColor + background = borderedKeyBackgroundDrawable( + bkgColor, theme.keyShadowColor, + radius, shadowWidth, hInset, vInset + ) + foreground = if (rippled) { + RippleDrawable( ColorStateList.valueOf(theme.keyPressHighlightColor), null, - // ripple should be masked with an opaque color - InsetDrawable( - GradientDrawable().apply { - cornerRadius = radius - setColor(Color.WHITE) - }, - hMargin, vMargin, hMargin, vMargin - ) + insetRadiusDrawable(hInset, vInset, radius) ) } else { - foreground = StateListDrawable().apply { + StateListDrawable().apply { addState( intArrayOf(android.R.attr.state_pressed), - // use mask drawable as highlight directly - InsetDrawable( - GradientDrawable().apply { - cornerRadius = radius - setColor(theme.keyPressHighlightColor) - }, - hMargin, vMargin, hMargin, vMargin - ) + insetRadiusDrawable(hInset, vInset, radius, theme.keyPressHighlightColor) ) } } @@ -148,47 +122,25 @@ class TextEditingButton( theme.altKeyTextColor ) ) - if (bordered) { - background = StateListDrawable().apply { + background = if (bordered) { + StateListDrawable().apply { addState( intArrayOf(android.R.attr.state_activated), - LayerDrawable( - arrayOf( - GradientDrawable().apply { - cornerRadius = radius - setColor(theme.keyShadowColor) - }, - GradientDrawable().apply { - cornerRadius = radius - setColor(theme.genericActiveBackgroundColor) - } - ) - ).apply { - setLayerInset(0, hMargin, vMargin, hMargin, vMargin - shadowWidth) - setLayerInset(1, hMargin, vMargin, hMargin, vMargin) - } + borderedKeyBackgroundDrawable( + theme.genericActiveBackgroundColor, theme.keyShadowColor, + radius, shadowWidth, hInset, vInset + ) ) addState( intArrayOf(android.R.attr.state_enabled), - LayerDrawable( - arrayOf( - GradientDrawable().apply { - cornerRadius = radius - setColor(theme.keyShadowColor) - }, - GradientDrawable().apply { - cornerRadius = radius - setColor(theme.keyBackgroundColor) - } - ) - ).apply { - setLayerInset(0, hMargin, vMargin, hMargin, vMargin - shadowWidth) - setLayerInset(1, hMargin, vMargin, hMargin, vMargin) - } + borderedKeyBackgroundDrawable( + theme.keyBackgroundColor, theme.keyShadowColor, + radius, shadowWidth, hInset, vInset + ) ) } } else { - background = StateListDrawable().apply { + StateListDrawable().apply { addState( intArrayOf(android.R.attr.state_activated), borderDrawable( diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDrawable.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDrawable.kt new file mode 100644 index 000000000..f5ea1e1b5 --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDrawable.kt @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.input.keyboard + +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.InsetDrawable +import android.graphics.drawable.LayerDrawable +import androidx.annotation.ColorInt + +fun radiusDrawable( + r: Float, @ColorInt + color: Int = Color.WHITE +): Drawable = GradientDrawable().apply { + setColor(color) + cornerRadius = r +} + +fun insetRadiusDrawable( + hInset: Int, + vInset: Int, + r: Float = 0f, + @ColorInt color: Int = Color.WHITE +): Drawable = InsetDrawable( + radiusDrawable(r, color), + hInset, vInset, hInset, vInset +) + +fun insetOvalDrawable( + hInset: Int, + vInset: Int, + @ColorInt color: Int = Color.WHITE +): Drawable = InsetDrawable( + GradientDrawable().apply { + shape = GradientDrawable.OVAL + setColor(color) + }, + hInset, vInset, hInset, vInset +) + +fun borderedKeyBackgroundDrawable( + @ColorInt bkgColor: Int, + @ColorInt shadowColor: Int, + radius: Float, + shadowWidth: Int, + hMargin: Int, + vMargin: Int +): Drawable = LayerDrawable( + arrayOf( + radiusDrawable(radius, shadowColor), + radiusDrawable(radius, bkgColor), + ) +).apply { + setLayerInset(0, hMargin, vMargin, hMargin, vMargin - shadowWidth) + setLayerInset(1, hMargin, vMargin, hMargin, vMargin) +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyView.kt index 3a09bbd2e..382d27209 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyView.kt @@ -13,9 +13,7 @@ import android.graphics.Rect import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable -import android.graphics.drawable.GradientDrawable import android.graphics.drawable.InsetDrawable -import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.graphics.drawable.StateListDrawable import android.util.TypedValue @@ -119,29 +117,17 @@ abstract class KeyView(ctx: Context, val theme: Theme, val def: KeyDef.Appearanc } // key border if ((bordered && def.border != Border.Off) || def.border == Border.On) { - // background: key border - appearanceView.background = LayerDrawable( - arrayOf( - GradientDrawable().apply { - cornerRadius = radius - setColor(theme.keyShadowColor) - }, - GradientDrawable().apply { - cornerRadius = radius - setColor( - when (def.variant) { - Variant.Normal, Variant.AltForeground -> theme.keyBackgroundColor - Variant.Alternative -> theme.altKeyBackgroundColor - Variant.Accent -> theme.accentKeyBackgroundColor - } - ) - } - ) - ).apply { - val shadowWidth = dp(1) - setLayerInset(0, hMargin, vMargin, hMargin, vMargin - shadowWidth) - setLayerInset(1, hMargin, vMargin, hMargin, vMargin) + val bkgColor = when (def.variant) { + Variant.Normal, Variant.AltForeground -> theme.keyBackgroundColor + Variant.Alternative -> theme.altKeyBackgroundColor + Variant.Accent -> theme.accentKeyBackgroundColor } + val shadowWidth = dp(1) + // background: key border + appearanceView.background = borderedKeyBackgroundDrawable( + bkgColor, theme.keyShadowColor, + radius, shadowWidth, hMargin, vMargin + ) // foreground: press highlight or ripple setupPressHighlight() } else { @@ -155,13 +141,13 @@ abstract class KeyView(ctx: Context, val theme: Theme, val def: KeyDef.Appearanc } private fun setupPressHighlight(mask: Drawable? = null) { - appearanceView.foreground = if (rippled) + appearanceView.foreground = if (rippled) { RippleDrawable( ColorStateList.valueOf(theme.keyPressHighlightColor), null, // ripple should be masked with an opaque color mask ?: highlightMaskDrawable(Color.WHITE) ) - else + } else { StateListDrawable().apply { addState( intArrayOf(android.R.attr.state_pressed), @@ -169,16 +155,13 @@ abstract class KeyView(ctx: Context, val theme: Theme, val def: KeyDef.Appearanc mask ?: highlightMaskDrawable(theme.keyPressHighlightColor) ) } + } } - private fun highlightMaskDrawable(@ColorInt color: Int) = - InsetDrawable( - if (bordered) GradientDrawable().apply { - cornerRadius = radius - setColor(color) - } else ColorDrawable(color), - hMargin, vMargin, hMargin, vMargin - ) + private fun highlightMaskDrawable(@ColorInt color: Int): Drawable { + return if (bordered) insetRadiusDrawable(hMargin, vMargin, radius, color) + else InsetDrawable(ColorDrawable(color), hMargin, vMargin, hMargin, vMargin) + } override fun setEnabled(enabled: Boolean) { super.setEnabled(enabled) @@ -219,23 +202,16 @@ abstract class KeyView(ctx: Context, val theme: Theme, val def: KeyDef.Appearanc val minHeight = dp(26) val hInset = dp(10) val vInset = if (h < minHeight) 0 else min((h - minHeight) / 2, dp(16)) - appearanceView.background = InsetDrawable( - GradientDrawable().apply { - cornerRadius = bkgRadius - setColor(theme.spaceBarColor) - }, - hInset, vInset, hInset, vInset + appearanceView.background = insetRadiusDrawable( + hInset, vInset, bkgRadius, theme.spaceBarColor ) // InsetDrawable sets padding to container view; remove padding to prevent text from bing clipped appearanceView.padding = 0 // apply press highlight for background area setupPressHighlight( - InsetDrawable( - GradientDrawable().apply { - cornerRadius = bkgRadius - setColor(if (rippled) Color.WHITE else theme.keyPressHighlightColor) - }, - hInset, vInset, hInset, vInset + insetRadiusDrawable( + hInset, vInset, bkgRadius, + if (rippled) Color.WHITE else theme.keyPressHighlightColor ) ) } @@ -243,21 +219,13 @@ abstract class KeyView(ctx: Context, val theme: Theme, val def: KeyDef.Appearanc val drawableSize = min(min(w, h), dp(35)) val hInset = (w - drawableSize) / 2 val vInset = (h - drawableSize) / 2 - appearanceView.background = InsetDrawable( - GradientDrawable().apply { - shape = GradientDrawable.OVAL - setColor(theme.accentKeyBackgroundColor) - }, - hInset, vInset, hInset, vInset + appearanceView.background = insetOvalDrawable( + hInset, vInset, theme.accentKeyBackgroundColor ) appearanceView.padding = 0 setupPressHighlight( - InsetDrawable( - GradientDrawable().apply { - shape = GradientDrawable.OVAL - setColor(if (rippled) Color.WHITE else theme.keyPressHighlightColor) - }, - hInset, vInset, hInset, vInset + insetOvalDrawable( + hInset, vInset, if (rippled) Color.WHITE else theme.keyPressHighlightColor ) ) } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/Drawable.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/Drawable.kt index 94f706895..f5368506b 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/utils/Drawable.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/utils/Drawable.kt @@ -7,6 +7,7 @@ package org.fcitx.fcitx5.android.utils import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.RippleDrawable import android.graphics.drawable.ShapeDrawable @@ -14,19 +15,27 @@ import android.graphics.drawable.StateListDrawable import android.graphics.drawable.shapes.OvalShape import androidx.annotation.ColorInt -fun rippleDrawable(@ColorInt color: Int) = - RippleDrawable(ColorStateList.valueOf(color), null, ColorDrawable(Color.WHITE)) +fun rippleDrawable( + @ColorInt color: Int, + mask: Drawable = ColorDrawable(Color.WHITE) +): Drawable = RippleDrawable(ColorStateList.valueOf(color), null, mask) -fun borderlessRippleDrawable(@ColorInt color: Int, r: Int = RippleDrawable.RADIUS_AUTO) = - RippleDrawable(ColorStateList.valueOf(color), null, null).apply { - radius = r - } +fun borderlessRippleDrawable( + @ColorInt color: Int, + r: Int = RippleDrawable.RADIUS_AUTO +): Drawable = RippleDrawable(ColorStateList.valueOf(color), null, null).apply { + radius = r +} -fun pressHighlightDrawable(@ColorInt color: Int) = StateListDrawable().apply { +fun pressHighlightDrawable( + @ColorInt color: Int +): Drawable = StateListDrawable().apply { addState(intArrayOf(android.R.attr.state_pressed), ColorDrawable(color)) } -fun circlePressHighlightDrawable(@ColorInt color: Int) = StateListDrawable().apply { +fun circlePressHighlightDrawable( + @ColorInt color: Int +): Drawable = StateListDrawable().apply { addState( intArrayOf(android.R.attr.state_pressed), ShapeDrawable(OvalShape()).apply { paint.color = color } @@ -37,7 +46,7 @@ fun borderDrawable( width: Int, @ColorInt stroke: Int, @ColorInt background: Int = Color.TRANSPARENT -) = GradientDrawable().apply { +): Drawable = GradientDrawable().apply { setStroke(width, stroke) setColor(background) } From 683a1f343d0c78d779b6599ec5646123334d9e14 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 6 Jul 2024 04:27:45 +0800 Subject: [PATCH 027/181] Allow cursor to move out of preedit in androidkeyboard --- app/src/main/cpp/androidkeyboard/androidkeyboard.cpp | 8 ++++---- app/src/main/cpp/androidkeyboard/androidkeyboard.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/cpp/androidkeyboard/androidkeyboard.cpp b/app/src/main/cpp/androidkeyboard/androidkeyboard.cpp index 153108750..2ef6485fc 100644 --- a/app/src/main/cpp/androidkeyboard/androidkeyboard.cpp +++ b/app/src/main/cpp/androidkeyboard/androidkeyboard.cpp @@ -142,16 +142,16 @@ void AndroidKeyboardEngine::keyEvent(const InputMethodEntry &entry, KeyEvent &ev auto cursor = buffer.cursor(); if (cursor > 0) { buffer.setCursor(cursor - 1); + event.filterAndAccept(); + return updateCandidate(entry, inputContext); } - event.filterAndAccept(); - return updateCandidate(entry, inputContext); } else if (key.check(FcitxKey_Right) || key.check(FcitxKey_KP_Right)) { auto cursor = buffer.cursor(); if (cursor < buffer.size()) { buffer.setCursor(buffer.cursor() + 1); + event.filterAndAccept(); + return updateCandidate(entry, inputContext); } - event.filterAndAccept(); - return updateCandidate(entry, inputContext); } } diff --git a/app/src/main/cpp/androidkeyboard/androidkeyboard.h b/app/src/main/cpp/androidkeyboard/androidkeyboard.h index 21fd52da7..43ac9246b 100644 --- a/app/src/main/cpp/androidkeyboard/androidkeyboard.h +++ b/app/src/main/cpp/androidkeyboard/androidkeyboard.h @@ -59,8 +59,8 @@ class AndroidKeyboardEngine final : public InputMethodEngineV3 { static int constexpr MaxBufferSize = 20; static int constexpr SpellCandidateSize = 20; - AndroidKeyboardEngine(Instance *instance); - ~AndroidKeyboardEngine() = default; + explicit AndroidKeyboardEngine(Instance *instance); + ~AndroidKeyboardEngine() override = default; Instance *instance() { return instance_; } From 25bdee19d910ac81bb7bac49aefa38f93cf02c08 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 6 Jul 2024 13:31:40 +0800 Subject: [PATCH 028/181] Update dependencies Kotlin 1.9.24, AGP 8.5.0 --- build-logic/convention/build.gradle.kts | 2 +- .../fcitx5/android/codegen/GenKeyMapping.kt | 11 +++++-- gradle/libs.versions.toml | 31 ++++++++++--------- lib/common/build.gradle.kts | 2 +- lib/plugin-base/build.gradle.kts | 2 +- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index 716d300ef..c4129700b 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `kotlin-dsl` kotlin("plugin.serialization") version embeddedKotlinVersion `maven-publish` - id("com.palantir.git-version") version "3.0.0" + alias(libs.plugins.gitVersion) `java-gradle-plugin` } diff --git a/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenKeyMapping.kt b/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenKeyMapping.kt index fd002d2e7..2c3c8754f 100644 --- a/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenKeyMapping.kt +++ b/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenKeyMapping.kt @@ -1,7 +1,8 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2021-2024 Fcitx5 for Android Contributors */ + package org.fcitx.fcitx5.android.codegen import com.google.devtools.ksp.processing.Resolver @@ -9,10 +10,14 @@ import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider import com.google.devtools.ksp.symbol.KSAnnotated -import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asTypeName import com.squareup.kotlinpoet.ksp.writeTo - private typealias FcitxKeyName = String private typealias FcitxKeySym = Int private typealias AndroidKeyCode = String diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68abcca77..5a138710e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,26 +1,26 @@ [versions] -androidGradlePlugin = "8.3.2" +androidGradlePlugin = "8.5.0" androidDesugarJDKLibs = "2.0.4" -kotlin = "1.9.23" -ksp = "1.9.23-1.0.20" -lifecycle = "2.7.0" +kotlin = "1.9.24" +ksp = "1.9.24-1.0.20" +lifecycle = "2.8.3" navigation = "2.7.7" room = "2.6.1" splitties = "3.0.0" -aboutlibraries = "11.1.3" +aboutlibraries = "11.2.2" [libraries] android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } android-desugarJDKLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJDKLibs" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } -kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version = "1.8.0" } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version = "1.8.1" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } -androidx-activity = { module = "androidx.activity:activity-ktx", version = "1.8.2" } -androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.6.1" } +androidx-activity = { module = "androidx.activity:activity-ktx", version = "1.9.0" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" } androidx-autofill = { module = "androidx.autofill:autofill", version = "1.1.0" } androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.4" } androidx-coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version = "1.2.0" } -androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.12.0" } +androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.13.1" } androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } androidx-lifecycle-livedata = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" } androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } @@ -28,7 +28,7 @@ androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common-java androidx-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle" } androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" } androidx-navigation-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" } -androidx-paging = { module = "androidx.paging:paging-runtime-ktx", version = "3.2.1" } +androidx-paging = { module = "androidx.paging:paging-runtime-ktx", version = "3.3.0" } androidx-preference = { module = "androidx.preference:preference-ktx", version = "1.2.1" } androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.3.2" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } @@ -36,8 +36,8 @@ androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = " androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "room" } androidx-startup = { module = "androidx.startup:startup-runtime", version = "1.1.1" } -androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version = "1.1.0-beta02" } -material = { module = "com.google.android.material:material", version = "1.11.0" } +androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version = "1.1.0" } +material = { module = "com.google.android.material:material", version = "1.12.0" } arrow = { module = "io.arrow-kt:arrow-core", version = "1.2.4" } imagecropper = { module = "com.vanniktech:android-image-cropper", version = "4.5.0" } flexbox = { module = "com.google.android.flexbox:flexbox", version = "3.0.0" } @@ -55,10 +55,10 @@ splitties-views-recyclerview = { module = "com.louiscad.splitties:splitties-view aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlibraries" } aboutlibraries-plugin = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutlibraries" } junit = { module = "junit:junit", version = "4.13.2" } -androidx-test-runner = { module = "androidx.test:runner", version = "1.5.2" } -androidx-test-rules = { module = "androidx.test:rules", version = "1.5.0" } +androidx-test-runner = { module = "androidx.test:runner", version = "1.6.1" } +androidx-test-rules = { module = "androidx.test:rules", version = "1.6.1" } androidx-lifecycle-testing = { module = "androidx.lifecycle:lifecycle-runtime-testing", version.ref = "lifecycle" } -kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version = "1.16.0" } +kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version = "1.18.0" } ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } [plugins] @@ -70,3 +70,4 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +gitVersion = { id = "com.palantir.git-version", version = "3.1.0" } diff --git a/lib/common/build.gradle.kts b/lib/common/build.gradle.kts index 8921be50c..e19bc1ce0 100644 --- a/lib/common/build.gradle.kts +++ b/lib/common/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("org.fcitx.fcitx5.android.lib-convention") `maven-publish` - id("com.palantir.git-version") version "3.0.0" + alias(libs.plugins.gitVersion) } android { diff --git a/lib/plugin-base/build.gradle.kts b/lib/plugin-base/build.gradle.kts index 98e79de7e..5b69f7abb 100644 --- a/lib/plugin-base/build.gradle.kts +++ b/lib/plugin-base/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("org.fcitx.fcitx5.android.lib-convention") `maven-publish` - id("com.palantir.git-version") version "3.0.0" + alias(libs.plugins.gitVersion) } android { From 7927dc959bdf6bae7eff7d66734cac174a4a4236 Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 10 Jul 2024 01:14:47 +0800 Subject: [PATCH 029/181] Fix some clang-tidy warnings --- .idea/dictionaries/project.xml | 1 + app/src/main/cpp/androidaddonloader/androidaddonloader.h | 8 ++++---- app/src/main/cpp/androidfrontend/androidfrontend.h | 4 ++-- app/src/main/cpp/androidfrontend/androidfrontend_public.h | 6 +++--- app/src/main/cpp/androidkeyboard/androidkeyboard.h | 7 +++---- .../main/cpp/androidnotification/androidnotification.h | 3 +-- app/src/main/cpp/helper-types.h | 2 +- app/src/main/cpp/jni-utils.h | 8 ++++---- app/src/main/cpp/native-lib.cpp | 2 +- 9 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index 5c40d32cd..de51628fc 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -42,6 +42,7 @@ unfocus unikey zhuyin + zlib \ No newline at end of file diff --git a/app/src/main/cpp/androidaddonloader/androidaddonloader.h b/app/src/main/cpp/androidaddonloader/androidaddonloader.h index ff3a444b9..76074a772 100644 --- a/app/src/main/cpp/androidaddonloader/androidaddonloader.h +++ b/app/src/main/cpp/androidaddonloader/androidaddonloader.h @@ -20,7 +20,7 @@ namespace fcitx { class AndroidSharedLibraryFactory { public: - AndroidSharedLibraryFactory(Library lib) : library_(std::move(lib)) { + explicit AndroidSharedLibraryFactory(Library lib) : library_(std::move(lib)) { auto *funcPtr = library_.resolve("fcitx_addon_factory_instance"); if (!funcPtr) { throw std::runtime_error(library_.error()); @@ -43,9 +43,9 @@ typedef std::unordered_map> Android class AndroidSharedLibraryLoader : public AddonLoader { public: - AndroidSharedLibraryLoader(AndroidLibraryDependency dependency); - ~AndroidSharedLibraryLoader() = default; - std::string type() const override { return "SharedLibrary"; } + explicit AndroidSharedLibraryLoader(AndroidLibraryDependency dependency); + + [[nodiscard]] std::string type() const override { return "SharedLibrary"; } AddonInstance *load(const AddonInfo &info, AddonManager *manager) override; diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.h b/app/src/main/cpp/androidfrontend/androidfrontend.h index 349cdd8e3..9fcb2dbbe 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend.h @@ -18,7 +18,7 @@ class AndroidInputContext; class AndroidFrontend : public AddonInstance { public: - AndroidFrontend(Instance *instance); + explicit AndroidFrontend(Instance *instance); Instance *instance() { return instance_; } @@ -37,7 +37,7 @@ class AndroidFrontend : public AddonInstance { void focusInputContext(bool focus); void activateInputContext(const int uid, const std::string &pkgName); void deactivateInputContext(const int uid); - InputContext *activeInputContext() const; + [[nodiscard]] InputContext *activeInputContext() const; void setCapabilityFlags(uint64_t flag); std::vector getCandidates(const int offset, const int limit); std::vector getCandidateActions(const int idx); diff --git a/app/src/main/cpp/androidfrontend/androidfrontend_public.h b/app/src/main/cpp/androidfrontend/androidfrontend_public.h index 23952e49d..8fb141ba2 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend_public.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend_public.h @@ -2,8 +2,8 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ -#ifndef _FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H_ -#define _FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H_ +#ifndef FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H +#define FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H #include #include @@ -89,4 +89,4 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setDeleteSurroundingCallback, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setToastCallback, void(const ToastCallback &)) -#endif // _FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H_ +#endif // FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H diff --git a/app/src/main/cpp/androidkeyboard/androidkeyboard.h b/app/src/main/cpp/androidkeyboard/androidkeyboard.h index 43ac9246b..0f35e7466 100644 --- a/app/src/main/cpp/androidkeyboard/androidkeyboard.h +++ b/app/src/main/cpp/androidkeyboard/androidkeyboard.h @@ -2,8 +2,8 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ -#ifndef _FCITX5_ANDROID_ANDROIDKEYBOARD_H_ -#define _FCITX5_ANDROID_ANDROIDKEYBOARD_H_ +#ifndef FCITX5_ANDROID_ANDROIDKEYBOARD_H +#define FCITX5_ANDROID_ANDROIDKEYBOARD_H #include #include @@ -60,7 +60,6 @@ class AndroidKeyboardEngine final : public InputMethodEngineV3 { static int constexpr SpellCandidateSize = 20; explicit AndroidKeyboardEngine(Instance *instance); - ~AndroidKeyboardEngine() override = default; Instance *instance() { return instance_; } @@ -133,4 +132,4 @@ class AndroidKeyboardEngineFactory : public AddonFactory { } -#endif //_FCITX5_ANDROID_ANDROIDKEYBOARD_H_ +#endif //FCITX5_ANDROID_ANDROIDKEYBOARD_H diff --git a/app/src/main/cpp/androidnotification/androidnotification.h b/app/src/main/cpp/androidnotification/androidnotification.h index 4dbd35ba9..242e2857b 100644 --- a/app/src/main/cpp/androidnotification/androidnotification.h +++ b/app/src/main/cpp/androidnotification/androidnotification.h @@ -22,12 +22,11 @@ namespace fcitx { FCITX_CONFIGURATION(NotificationsConfig, fcitx::Option> hiddenNotifications{ this, "HiddenNotifications", - _("Hidden Notifications")};); + _("Hidden Notifications")};) class Notifications final : public AddonInstance { public: explicit Notifications(Instance *instance); - ~Notifications() override = default; Instance *instance() { return instance_; } diff --git a/app/src/main/cpp/helper-types.h b/app/src/main/cpp/helper-types.h index ffed72bc2..169eda4d7 100644 --- a/app/src/main/cpp/helper-types.h +++ b/app/src/main/cpp/helper-types.h @@ -93,7 +93,7 @@ class CandidateActionEntity { bool isCheckable; bool isChecked; - CandidateActionEntity(const fcitx::CandidateAction &act) : + explicit CandidateActionEntity(const fcitx::CandidateAction &act) : id(act.id()), text(act.text()), isSeparator(act.isSeparator()), diff --git a/app/src/main/cpp/jni-utils.h b/app/src/main/cpp/jni-utils.h index 6e5721c70..701b58fba 100644 --- a/app/src/main/cpp/jni-utils.h +++ b/app/src/main/cpp/jni-utils.h @@ -77,10 +77,10 @@ class JString { class JEnv { private: - JNIEnv *env; + JNIEnv *env = nullptr; public: - JEnv(JavaVM *jvm) { + explicit JEnv(JavaVM *jvm) { if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) == JNI_EDETACHED) { jvm->AttachCurrentThread(&env, nullptr); } @@ -141,7 +141,7 @@ class GlobalRefSingleton { jclass CandidateAction; jmethodID CandidateActionInit; - GlobalRefSingleton(JavaVM *jvm_) : jvm(jvm_) { + explicit GlobalRefSingleton(JavaVM *jvm_) : jvm(jvm_) { JNIEnv *env; jvm->AttachCurrentThread(&env, nullptr); @@ -192,7 +192,7 @@ class GlobalRefSingleton { CandidateActionInit = env->GetMethodID(CandidateAction, "", "(ILjava/lang/String;ZLjava/lang/String;ZZ)V"); } - const JEnv AttachEnv() const { return JEnv(jvm); } + [[nodiscard]] JEnv AttachEnv() const { return JEnv(jvm); } }; extern GlobalRefSingleton *GlobalRef; diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index 4fd5952ae..e1bd6364f 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -81,7 +81,7 @@ class Fcitx { return uv_run(get_event_base(), UV_RUN_ONCE); } - void startup(fcitx::AndroidLibraryDependency dependency, + void startup(const fcitx::AndroidLibraryDependency& dependency, const std::function &setupCallback) { p_instance = std::make_unique(0, nullptr); p_instance->addonManager().registerLoader(std::make_unique(dependency)); From 4a4d330bd628510e36ef505dbde7abedc67ea33f Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 27 Jul 2024 02:57:50 +0800 Subject: [PATCH 030/181] Change Java version to 1.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit minSdk = 23 ¯\_(ツ)_/¯ --- .../convention/src/main/kotlin/AndroidAppConventionPlugin.kt | 1 - .../convention/src/main/kotlin/AndroidBaseConventionPlugin.kt | 3 --- build-logic/convention/src/main/kotlin/Versions.kt | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt index 5130619f2..caba1afc6 100644 --- a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt @@ -70,7 +70,6 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { } // Make data descriptor depend on fcitx component if have // Since we are using finalizeDsl, there is no need to do afterEvaluate - @Suppress("UnstableApiUsage") finalizeDsl { target.tasks.findByName(FcitxComponentPlugin.INSTALL_TASK)?.also { componentTask -> target.tasks.findByName(DataDescriptorPlugin.TASK)?.dependsOn(componentTask) diff --git a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt index 923eac2ed..5dd555405 100644 --- a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt @@ -32,9 +32,6 @@ open class AndroidBaseConventionPlugin : Plugin { kotlinOptions { // https://youtrack.jetbrains.com/issue/KT-55947 jvmTarget = Versions.java.toString() - // https://issuetracker.google.com/issues/250197571 - // https://kotlinlang.org/docs/whatsnew1520.html#string-concatenation-via-invokedynamic - freeCompilerArgs += "-Xstring-concat=inline" } } diff --git a/build-logic/convention/src/main/kotlin/Versions.kt b/build-logic/convention/src/main/kotlin/Versions.kt index 36034d021..a80b5dae5 100644 --- a/build-logic/convention/src/main/kotlin/Versions.kt +++ b/build-logic/convention/src/main/kotlin/Versions.kt @@ -7,7 +7,7 @@ import org.gradle.api.Project object Versions { - val java = JavaVersion.VERSION_11 + val java = JavaVersion.VERSION_1_8 const val compileSdk = 34 const val minSdk = 23 const val targetSdk = 34 From d0a6ede6c249f9b82f013f38d39575644bf50d83 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 27 Jul 2024 02:59:19 +0800 Subject: [PATCH 031/181] Update gradle publish action --- .github/workflows/publish.yml | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 96f19566e..1b37bf145 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,9 +11,6 @@ on: jobs: publish: runs-on: ubuntu-22.04 - env: - GITHUB_TOKEN: ${{ secrets.PACKAGE_TOKEN }} - GITHUB_ACTOR: android-fcitx5 steps: - name: Fetch source code uses: actions/checkout@v4 @@ -21,6 +18,11 @@ jobs: fetch-depth: 0 submodules: recursive + - name: Install system dependencies + run: | + sudo apt update + sudo apt install extra-cmake-modules gettext + - name: Setup Java uses: actions/setup-java@v4 with: @@ -29,22 +31,17 @@ jobs: - name: Setup Android environment uses: android-actions/setup-android@v3 - - - name: Install Android NDK - run: | - sdkmanager --install "cmake;3.22.1" - - - name: Install system dependencies - run: | - sudo apt update - sudo apt install extra-cmake-modules gettext + with: + packages: cmake;3.22.1 - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - name: Publish build convention and libs + env: + GITHUB_TOKEN: ${{ secrets.PACKAGE_TOKEN }} + GITHUB_ACTOR: fcitx5-android-bot run: | ./gradlew :build-logic:convention:publish ./gradlew :lib:common:publish ./gradlew :lib:plugin-base:publish - From 06fa99e7c9fb4bc3d66cd45d6b08216588a02a13 Mon Sep 17 00:00:00 2001 From: Qijia Liu Date: Sat, 6 Jul 2024 16:23:24 -0400 Subject: [PATCH 032/181] Send keycode to fcitx Use KeyEvent#getScanCode() for physical key events Generate mapping for virutal key events assuming qwerty layout --- app/src/main/cpp/native-lib.cpp | 15 +- .../org/fcitx/fcitx5/android/core/Fcitx.kt | 22 +- .../org/fcitx/fcitx5/android/core/FcitxAPI.kt | 9 +- .../org/fcitx/fcitx5/android/core/KeyState.kt | 2 + .../android/input/FcitxInputMethodService.kt | 4 +- .../input/keyboard/CommonKeyActionListener.kt | 3 +- .../android/input/keyboard/KeyAction.kt | 18 +- .../android/input/keyboard/TextKeyboard.kt | 36 +- .../android/codegen/GenScancodeMapping.kt | 395 ++++++++++++++++++ ...ols.ksp.processing.SymbolProcessorProvider | 3 +- 10 files changed, 460 insertions(+), 47 deletions(-) create mode 100644 codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenScancodeMapping.kt diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index e1bd6364f..401a874d9 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -732,28 +732,31 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_reloadFcitxConfig(JNIEnv *env, jclass c extern "C" JNIEXPORT void JNICALL -Java_org_fcitx_fcitx5_android_core_Fcitx_sendKeyToFcitxString(JNIEnv *env, jclass clazz, jstring key, jint state, jboolean up, jint timestamp) { +Java_org_fcitx_fcitx5_android_core_Fcitx_sendKeyToFcitxString(JNIEnv *env, jclass clazz, jstring key, jint state, jint code, jboolean up, jint timestamp) { RETURN_IF_NOT_RUNNING fcitx::Key parsedKey{fcitx::Key::keySymFromString(CString(env, key)), - fcitx::KeyStates(static_cast(state))}; + fcitx::KeyStates(static_cast(state)), + code + /* evdev offset */ 8}; Fcitx::Instance().sendKey(parsedKey, up, timestamp); } extern "C" JNIEXPORT void JNICALL -Java_org_fcitx_fcitx5_android_core_Fcitx_sendKeyToFcitxChar(JNIEnv *env, jclass clazz, jchar c, jint state, jboolean up, jint timestamp) { +Java_org_fcitx_fcitx5_android_core_Fcitx_sendKeyToFcitxChar(JNIEnv *env, jclass clazz, jchar c, jint state, jint code, jboolean up, jint timestamp) { RETURN_IF_NOT_RUNNING const fcitx::Key parsedKey{fcitx::Key::keySymFromString(reinterpret_cast(&c)), - fcitx::KeyStates(static_cast(state))}; + fcitx::KeyStates(static_cast(state)), + code + /* evdev offset */ 8}; Fcitx::Instance().sendKey(parsedKey, up, timestamp); } extern "C" JNIEXPORT void JNICALL -Java_org_fcitx_fcitx5_android_core_Fcitx_sendKeySymToFcitx(JNIEnv *env, jclass clazz, jint sym, jint state, jboolean up, jint timestamp) { +Java_org_fcitx_fcitx5_android_core_Fcitx_sendKeySymToFcitx(JNIEnv *env, jclass clazz, jint sym, jint state, jint code, jboolean up, jint timestamp) { RETURN_IF_NOT_RUNNING fcitx::Key key{fcitx::KeySym(static_cast(sym)), - fcitx::KeyStates(static_cast(state))}; + fcitx::KeyStates(static_cast(state)), + code + /* evdev offset */ 8}; Fcitx::Instance().sendKey(key, up, timestamp); } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt index a1b6b9f65..0d98f290b 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt @@ -73,17 +73,17 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { override suspend fun save() = withFcitxContext { saveFcitxState() } override suspend fun reloadConfig() = withFcitxContext { reloadFcitxConfig() } - override suspend fun sendKey(key: String, states: UInt, up: Boolean, timestamp: Int) = - withFcitxContext { sendKeyToFcitxString(key, states.toInt(), up, timestamp) } + override suspend fun sendKey(key: String, states: UInt, code: Int, up: Boolean, timestamp: Int) = + withFcitxContext { sendKeyToFcitxString(key, states.toInt(), code, up, timestamp) } - override suspend fun sendKey(c: Char, states: UInt, up: Boolean, timestamp: Int) = - withFcitxContext { sendKeyToFcitxChar(c, states.toInt(), up, timestamp) } + override suspend fun sendKey(c: Char, states: UInt, code: Int, up: Boolean, timestamp: Int) = + withFcitxContext { sendKeyToFcitxChar(c, states.toInt(), code, up, timestamp) } - override suspend fun sendKey(sym: Int, states: UInt, up: Boolean, timestamp: Int) = - withFcitxContext { sendKeySymToFcitx(sym, states.toInt(), up, timestamp) } + override suspend fun sendKey(sym: Int, states: UInt, code: Int, up: Boolean, timestamp: Int) = + withFcitxContext { sendKeySymToFcitx(sym, states.toInt(), code, up, timestamp) } - override suspend fun sendKey(sym: KeySym, states: KeyStates, up: Boolean, timestamp: Int) = - withFcitxContext { sendKeySymToFcitx(sym.sym, states.toInt(), up, timestamp) } + override suspend fun sendKey(sym: KeySym, states: KeyStates, code: Int, up: Boolean, timestamp: Int) = + withFcitxContext { sendKeySymToFcitx(sym.sym, states.toInt(), code, up, timestamp) } override suspend fun select(idx: Int): Boolean = withFcitxContext { selectCandidate(idx) } override suspend fun isEmpty(): Boolean = withFcitxContext { isInputPanelEmpty() } @@ -232,13 +232,13 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { external fun reloadFcitxConfig() @JvmStatic - external fun sendKeyToFcitxString(key: String, state: Int, up: Boolean, timestamp: Int) + external fun sendKeyToFcitxString(key: String, state: Int, code: Int, up: Boolean, timestamp: Int) @JvmStatic - external fun sendKeyToFcitxChar(c: Char, state: Int, up: Boolean, timestamp: Int) + external fun sendKeyToFcitxChar(c: Char, state: Int, code: Int, up: Boolean, timestamp: Int) @JvmStatic - external fun sendKeySymToFcitx(sym: Int, state: Int, up: Boolean, timestamp: Int) + external fun sendKeySymToFcitx(sym: Int, state: Int, code: Int, up: Boolean, timestamp: Int) @JvmStatic external fun selectCandidate(idx: Int): Boolean diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt index d37d527a0..98a03f66f 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.flow.SharedFlow */ interface FcitxAPI { - enum class AddonDep { Required, Optional @@ -43,13 +42,13 @@ interface FcitxAPI { suspend fun reloadConfig() - suspend fun sendKey(key: String, states: UInt = 0u, up: Boolean = false, timestamp: Int = -1) + suspend fun sendKey(key: String, states: UInt = 0u, code: Int = 0, up: Boolean = false, timestamp: Int = -1) - suspend fun sendKey(c: Char, states: UInt = 0u, up: Boolean = false, timestamp: Int = -1) + suspend fun sendKey(c: Char, states: UInt = 0u, code: Int = 0, up: Boolean = false, timestamp: Int = -1) - suspend fun sendKey(sym: Int, states: UInt = 0u, up: Boolean = false, timestamp: Int = -1) + suspend fun sendKey(sym: Int, states: UInt = 0u, code: Int = 0, up: Boolean = false, timestamp: Int = -1) - suspend fun sendKey(sym: KeySym, states: KeyStates, up: Boolean = false, timestamp: Int = -1) + suspend fun sendKey(sym: KeySym, states: KeyStates, code: Int = 0, up: Boolean = false, timestamp: Int = -1) suspend fun select(idx: Int): Boolean suspend fun isEmpty(): Boolean diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/KeyState.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/KeyState.kt index 4d4f2da3b..19ab51286 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/KeyState.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/KeyState.kt @@ -92,6 +92,8 @@ value class KeyStates(val states: UInt) { companion object { val Empty = KeyStates(0u) + val Virtual = KeyStates(KeyState.Virtual) + fun of(v: Int) = KeyStates(v.toUInt()) fun fromKeyEvent(event: KeyEvent): KeyStates { diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt index 01e0a9438..e16752360 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt @@ -473,14 +473,14 @@ class FcitxInputMethodService : LifecycleInputMethodService() { // skip \n, because fcitx wants \r for return if (charCode > 0 && charCode != '\t'.code && charCode != '\n'.code) { postFcitxJob { - sendKey(charCode, states.states, up, timestamp) + sendKey(charCode, states.states, event.scanCode, up, timestamp) } return true } val keySym = KeySym.fromKeyEvent(event) if (keySym != null) { postFcitxJob { - sendKey(keySym, states, up, timestamp) + sendKey(keySym, states, event.scanCode, up, timestamp) } return true } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CommonKeyActionListener.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CommonKeyActionListener.kt index 772d9a8eb..36dfff199 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CommonKeyActionListener.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CommonKeyActionListener.kt @@ -8,7 +8,6 @@ import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.launch import org.fcitx.fcitx5.android.core.FcitxAPI -import org.fcitx.fcitx5.android.core.KeyState import org.fcitx.fcitx5.android.daemon.launchOnReady import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.input.broadcast.PreeditEmptyStateComponent @@ -92,7 +91,7 @@ class CommonKeyActionListener : KeyActionListener { action, _ -> when (action) { is FcitxKeyAction -> service.postFcitxJob { - sendKey(action.act, KeyState.Virtual.state) + sendKey(action.act, action.states.states, action.code) } is SymAction -> service.postFcitxJob { sendKey(action.sym, action.states) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyAction.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyAction.kt index 4182bb38f..192cfbadb 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyAction.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyAction.kt @@ -4,22 +4,22 @@ */ package org.fcitx.fcitx5.android.input.keyboard -import org.fcitx.fcitx5.android.core.KeyState import org.fcitx.fcitx5.android.core.KeyStates import org.fcitx.fcitx5.android.core.KeySym +import org.fcitx.fcitx5.android.core.ScancodeMapping import org.fcitx.fcitx5.android.input.picker.PickerWindow sealed class KeyAction { - data class FcitxKeyAction(var act: String) : KeyAction() + data class FcitxKeyAction( + val act: String, + val code: Int = ScancodeMapping.charToScancode(act[0]), + val states: KeyStates = KeyStates.Virtual + ) : KeyAction() - data class SymAction(val sym: KeySym, val states: KeyStates = VirtualState) : KeyAction() { - companion object { - val VirtualState = KeyStates(KeyState.Virtual) - } - } + data class SymAction(val sym: KeySym, val states: KeyStates = KeyStates.Virtual) : KeyAction() - data class CommitAction(var text: String) : KeyAction() + data class CommitAction(val text: String) : KeyAction() data class CapsAction(val lock: Boolean) : KeyAction() @@ -39,5 +39,5 @@ sealed class KeyAction { data class PickerSwitchAction(val key: PickerWindow.Key? = null) : KeyAction() - data object SpaceLongPressAction: KeyAction() + data object SpaceLongPressAction : KeyAction() } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt index dc987c415..d58cc204a 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt @@ -11,6 +11,8 @@ import androidx.annotation.Keep import androidx.core.view.allViews import org.fcitx.fcitx5.android.R import org.fcitx.fcitx5.android.core.InputMethodEntry +import org.fcitx.fcitx5.android.core.KeyState +import org.fcitx.fcitx5.android.core.KeyStates import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.data.prefs.ManagedPreference import org.fcitx.fcitx5.android.data.theme.Theme @@ -118,22 +120,34 @@ class TextKeyboard( } override fun onAction(action: KeyAction, source: KeyActionListener.Source) { + var transformed = action when (action) { - is KeyAction.FcitxKeyAction -> if (source == KeyActionListener.Source.Keyboard) { - transformKeyAction(action) + is KeyAction.FcitxKeyAction -> { + if (source == KeyActionListener.Source.Keyboard && action.act.length == 1) { + when (capsState) { + CapsState.None -> { + transformed = action.copy(act = action.act.lowercase()) + } + CapsState.Once -> { + transformed = action.copy( + act = action.act.uppercase(), + states = KeyStates(KeyState.Virtual, KeyState.Shift) + ) + switchCapsState() + } + CapsState.Lock -> { + transformed = action.copy( + act = action.act.uppercase(), + states = KeyStates(KeyState.Virtual, KeyState.CapsLock) + ) + } + } + } } is KeyAction.CapsAction -> switchCapsState(action.lock) else -> {} } - super.onAction(action, source) - } - - private fun transformKeyAction(action: KeyAction.FcitxKeyAction) { - if (action.act.length > 1) { - return - } - action.act = transformAlphabet(action.act) - if (capsState == CapsState.Once) switchCapsState() + super.onAction(transformed, source) } override fun onAttach() { diff --git a/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenScancodeMapping.kt b/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenScancodeMapping.kt new file mode 100644 index 000000000..650639d66 --- /dev/null +++ b/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenScancodeMapping.kt @@ -0,0 +1,395 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.codegen + +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.google.devtools.ksp.symbol.KSAnnotated +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.ksp.writeTo + +internal class GenScancodeMappingProcessor(private val environment: SymbolProcessorEnvironment) : + SymbolProcessor { + + companion object { + // https://github.com/torvalds/linux/blob/v6.10/include/uapi/linux/input-event-codes.h#L75 + private val SCANCODE_MAP: List> = listOf( + "KEY_RESERVED" to 0, + "KEY_ESC" to 1, + "KEY_1" to 2, + "KEY_2" to 3, + "KEY_3" to 4, + "KEY_4" to 5, + "KEY_5" to 6, + "KEY_6" to 7, + "KEY_7" to 8, + "KEY_8" to 9, + "KEY_9" to 10, + "KEY_0" to 11, + "KEY_MINUS" to 12, + "KEY_EQUAL" to 13, + "KEY_BACKSPACE" to 14, + "KEY_TAB" to 15, + "KEY_Q" to 16, + "KEY_W" to 17, + "KEY_E" to 18, + "KEY_R" to 19, + "KEY_T" to 20, + "KEY_Y" to 21, + "KEY_U" to 22, + "KEY_I" to 23, + "KEY_O" to 24, + "KEY_P" to 25, + "KEY_LEFTBRACE" to 26, + "KEY_RIGHTBRACE" to 27, + "KEY_ENTER" to 28, + "KEY_LEFTCTRL" to 29, + "KEY_A" to 30, + "KEY_S" to 31, + "KEY_D" to 32, + "KEY_F" to 33, + "KEY_G" to 34, + "KEY_H" to 35, + "KEY_J" to 36, + "KEY_K" to 37, + "KEY_L" to 38, + "KEY_SEMICOLON" to 39, + "KEY_APOSTROPHE" to 40, + "KEY_GRAVE" to 41, + "KEY_LEFTSHIFT" to 42, + "KEY_BACKSLASH" to 43, + "KEY_Z" to 44, + "KEY_X" to 45, + "KEY_C" to 46, + "KEY_V" to 47, + "KEY_B" to 48, + "KEY_N" to 49, + "KEY_M" to 50, + "KEY_COMMA" to 51, + "KEY_DOT" to 52, + "KEY_SLASH" to 53, + "KEY_RIGHTSHIFT" to 54, + "KEY_KPASTERISK" to 55, + "KEY_LEFTALT" to 56, + "KEY_SPACE" to 57, + "KEY_CAPSLOCK" to 58, + "KEY_F1" to 59, + "KEY_F2" to 60, + "KEY_F3" to 61, + "KEY_F4" to 62, + "KEY_F5" to 63, + "KEY_F6" to 64, + "KEY_F7" to 65, + "KEY_F8" to 66, + "KEY_F9" to 67, + "KEY_F10" to 68, + "KEY_NUMLOCK" to 69, + "KEY_SCROLLLOCK" to 70, + "KEY_KP7" to 71, + "KEY_KP8" to 72, + "KEY_KP9" to 73, + "KEY_KPMINUS" to 74, + "KEY_KP4" to 75, + "KEY_KP5" to 76, + "KEY_KP6" to 77, + "KEY_KPPLUS" to 78, + "KEY_KP1" to 79, + "KEY_KP2" to 80, + "KEY_KP3" to 81, + "KEY_KP0" to 82, + "KEY_KPDOT" to 83, + + "KEY_ZENKAKUHANKAKU" to 85, + "KEY_102ND" to 86, + "KEY_F11" to 87, + "KEY_F12" to 88, + "KEY_RO" to 89, + "KEY_KATAKANA" to 90, + "KEY_HIRAGANA" to 91, + "KEY_HENKAN" to 92, + "KEY_KATAKANAHIRAGANA" to 93, + "KEY_MUHENKAN" to 94, + "KEY_KPJPCOMMA" to 95, + "KEY_KPENTER" to 96, + "KEY_RIGHTCTRL" to 97, + "KEY_KPSLASH" to 98, + "KEY_SYSRQ" to 99, + "KEY_RIGHTALT" to 100, + "KEY_LINEFEED" to 101, + "KEY_HOME" to 102, + "KEY_UP" to 103, + "KEY_PAGEUP" to 104, + "KEY_LEFT" to 105, + "KEY_RIGHT" to 106, + "KEY_END" to 107, + "KEY_DOWN" to 108, + "KEY_PAGEDOWN" to 109, + "KEY_INSERT" to 110, + "KEY_DELETE" to 111, + "KEY_MACRO" to 112, + "KEY_MUTE" to 113, + "KEY_VOLUMEDOWN" to 114, + "KEY_VOLUMEUP" to 115, + "KEY_POWER" to 116, /* SC System Power Down */ + "KEY_KPEQUAL" to 117, + "KEY_KPPLUSMINUS" to 118, + "KEY_PAUSE" to 119, + "KEY_SCALE" to 120, /* AL Compiz Scale (Expose) */ + + "KEY_KPCOMMA" to 121, + "KEY_HANGEUL" to 122, + "KEY_HANGUEL" to 122, /* KEY_HANGUEL to KEY_HANGEUL */ + "KEY_HANJA" to 123, + "KEY_YEN" to 124, + "KEY_LEFTMETA" to 125, + "KEY_RIGHTMETA" to 126, + "KEY_COMPOSE" to 127, + + "KEY_STOP" to 128, /* AC Stop */ + "KEY_AGAIN" to 129, + "KEY_PROPS" to 130, /* AC Properties */ + "KEY_UNDO" to 131, /* AC Undo */ + "KEY_FRONT" to 132, + "KEY_COPY" to 133, /* AC Copy */ + "KEY_OPEN" to 134, /* AC Open */ + "KEY_PASTE" to 135, /* AC Paste */ + "KEY_FIND" to 136, /* AC Search */ + "KEY_CUT" to 137, /* AC Cut */ + "KEY_HELP" to 138, /* AL Integrated Help Center */ + "KEY_MENU" to 139, /* Menu (show menu) */ + "KEY_CALC" to 140, /* AL Calculator */ + "KEY_SETUP" to 141, + "KEY_SLEEP" to 142, /* SC System Sleep */ + "KEY_WAKEUP" to 143, /* System Wake Up */ + "KEY_FILE" to 144, /* AL Local Machine Browser */ + "KEY_SENDFILE" to 145, + "KEY_DELETEFILE" to 146, + "KEY_XFER" to 147, + "KEY_PROG1" to 148, + "KEY_PROG2" to 149, + "KEY_WWW" to 150, /* AL Internet Browser */ + "KEY_MSDOS" to 151, + "KEY_COFFEE" to 152, /* AL Terminal Lock/Screensaver */ + "KEY_SCREENLOCK" to 152, /* KEY_SCREENLOCK to KEY_COFFEE */ + "KEY_ROTATE_DISPLAY" to 153, /* Display orientation for e.g. tablets */ + "KEY_DIRECTION" to 153, /* KEY_DIRECTION KEY_ROTATE_DISPLAY */ + "KEY_CYCLEWINDOWS" to 154, + "KEY_MAIL" to 155, + "KEY_BOOKMARKS" to 156, /* AC Bookmarks */ + "KEY_COMPUTER" to 157, + "KEY_BACK" to 158, /* AC Back */ + "KEY_FORWARD" to 159, /* AC Forward */ + "KEY_CLOSECD" to 160, + "KEY_EJECTCD" to 161, + "KEY_EJECTCLOSECD" to 162, + "KEY_NEXTSONG" to 163, + "KEY_PLAYPAUSE" to 164, + "KEY_PREVIOUSSONG" to 165, + "KEY_STOPCD" to 166, + "KEY_RECORD" to 167, + "KEY_REWIND" to 168, + "KEY_PHONE" to 169, /* Media Select Telephone */ + "KEY_ISO" to 170, + "KEY_CONFIG" to 171, /* AL Consumer Control Configuration */ + "KEY_HOMEPAGE" to 172, /* AC Home */ + "KEY_REFRESH" to 173, /* AC Refresh */ + "KEY_EXIT" to 174, /* AC Exit */ + "KEY_MOVE" to 175, + "KEY_EDIT" to 176, + "KEY_SCROLLUP" to 177, + "KEY_SCROLLDOWN" to 178, + "KEY_KPLEFTPAREN" to 179, + "KEY_KPRIGHTPAREN" to 180, + "KEY_NEW" to 181, /* AC New */ + "KEY_REDO" to 182, /* AC Redo/Repeat */ + + "KEY_F13" to 183, + "KEY_F14" to 184, + "KEY_F15" to 185, + "KEY_F16" to 186, + "KEY_F17" to 187, + "KEY_F18" to 188, + "KEY_F19" to 189, + "KEY_F20" to 190, + "KEY_F21" to 191, + "KEY_F22" to 192, + "KEY_F23" to 193, + "KEY_F24" to 194, + + "KEY_PLAYCD" to 200, + "KEY_PAUSECD" to 201, + "KEY_PROG3" to 202, + "KEY_PROG4" to 203, + "KEY_ALL_APPLICATIONS" to 204, /* AC Desktop Show All Applications */ + "KEY_DASHBOARD" to 204, /* KEY_DASHBOARD to KEY_ALL_APPLICATIONS */ + "KEY_SUSPEND" to 205, + "KEY_CLOSE" to 206, /* AC Close */ + "KEY_PLAY" to 207, + "KEY_FASTFORWARD" to 208, + "KEY_BASSBOOST" to 209, + "KEY_PRINT" to 210, /* AC Print */ + "KEY_HP" to 211, + "KEY_CAMERA" to 212, + "KEY_SOUND" to 213, + "KEY_QUESTION" to 214, + "KEY_EMAIL" to 215, + "KEY_CHAT" to 216, + "KEY_SEARCH" to 217, + "KEY_CONNECT" to 218, + "KEY_FINANCE" to 219, /* AL Checkbook/Finance */ + "KEY_SPORT" to 220, + "KEY_SHOP" to 221, + "KEY_ALTERASE" to 222, + "KEY_CANCEL" to 223, /* AC Cancel */ + "KEY_BRIGHTNESSDOWN" to 224, + "KEY_BRIGHTNESSUP" to 225, + "KEY_MEDIA" to 226, + + "KEY_SWITCHVIDEOMODE" to 227, + /* Cycle between available video + outputs (Monitor/LCD/TV-out/etc) */ + "KEY_KBDILLUMTOGGLE" to 228, + "KEY_KBDILLUMDOWN" to 229, + "KEY_KBDILLUMUP" to 230, + + "KEY_SEND" to 231, /* AC Send */ + "KEY_REPLY" to 232, /* AC Reply */ + "KEY_FORWARDMAIL" to 233, /* AC Forward Msg */ + "KEY_SAVE" to 234, /* AC Save */ + "KEY_DOCUMENTS" to 235, + + "KEY_BATTERY" to 236, + + "KEY_BLUETOOTH" to 237, + "KEY_WLAN" to 238, + "KEY_UWB" to 239, + + "KEY_UNKNOWN" to 240, + + "KEY_VIDEO_NEXT" to 241, /* drive next video source */ + "KEY_VIDEO_PREV" to 242, /* drive previous video source */ + "KEY_BRIGHTNESS_CYCLE" to 243, /* brightness up, after max is min */ + "KEY_BRIGHTNESS_AUTO" to 244, + /* Set Auto Brightness: manual + brightness control is off, + rely on ambient */ + "KEY_BRIGHTNESS_ZERO" to 244, /* KEY_BRIGHTNESS_ZERO to KEY_BRIGHTNESS_AUTO */ + "KEY_DISPLAY_OFF" to 245, /* display device to off state */ + + "KEY_WWAN" to 246, /* Wireless WAN (LTE, UMTS, GSM, etc.) */ + "KEY_WIMAX" to 246, /* KEY_WIMAX to KEY_WWAN */ + "KEY_RFKILL" to 247, /* Key that controls all radios */ + + "KEY_MICMUTE" to 248, /* Mute / unmute the microphone */ + ) + + // assume qwerty layout + private val CHAR_MAP: List> = listOf( + "`" to "KEY_GRAVE", + "1" to "KEY_1", + "2" to "KEY_2", + "3" to "KEY_3", + "4" to "KEY_4", + "5" to "KEY_5", + "6" to "KEY_6", + "7" to "KEY_7", + "8" to "KEY_8", + "9" to "KEY_9", + "0" to "KEY_0", + "-" to "KEY_MINUS", + "=" to "KEY_EQUAL", + + "Q" to "KEY_Q", + "W" to "KEY_W", + "E" to "KEY_E", + "R" to "KEY_R", + "T" to "KEY_T", + "Y" to "KEY_Y", + "U" to "KEY_U", + "I" to "KEY_I", + "O" to "KEY_O", + "P" to "KEY_P", + "[" to "KEY_LEFTBRACE", + "]" to "KEY_RIGHTBRACE", + """\\""" to "KEY_BACKSLASH", + + "A" to "KEY_A", + "S" to "KEY_S", + "D" to "KEY_D", + "F" to "KEY_F", + "G" to "KEY_G", + "H" to "KEY_H", + "J" to "KEY_J", + "K" to "KEY_K", + "L" to "KEY_L", + ";" to "KEY_SEMICOLON", + """\'""" to "KEY_APOSTROPHE", + + "Z" to "KEY_Z", + "X" to "KEY_X", + "C" to "KEY_C", + "V" to "KEY_V", + "B" to "KEY_B", + "N" to "KEY_N", + "M" to "KEY_M", + "," to "KEY_COMMA", + "." to "KEY_DOT", + "/" to "KEY_SLASH", + ) + } + + // We don't process annotations at all + override fun process(resolver: Resolver) = emptyList() + + override fun finish() { + val charToScancode = FunSpec + .builder("charToScancode") + .addParameter("ch", Char::class) + .returns(Int::class) + .addCode( + """ + | return when (ch) { + | ${CHAR_MAP.joinToString(separator = "\n| ") { (ch, name) -> "'$ch' -> ScancodeMapping.$name" }} + | else -> ScancodeMapping.KEY_RESERVED + | } + """.trimMargin() + ) + .build() + + val tyScancodeMapping = TypeSpec + .objectBuilder("ScancodeMapping") + .apply { + SCANCODE_MAP.forEach { (name, code) -> + addProperty( + PropertySpec + .builder(name, Int::class, KModifier.CONST) + .initializer("$code") + .build() + ) + } + } + .addFunction(charToScancode) + .build() + + val file = FileSpec + .builder("org.fcitx.fcitx5.android.core", "ScancodeMapping") + .addType(tyScancodeMapping) + .build() + file.writeTo(environment.codeGenerator, false) + } +} + +class GenScancodeMappingProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = + GenScancodeMappingProcessor(environment) + +} diff --git a/codegen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/codegen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider index b0c094b26..a4e01f1d1 100644 --- a/codegen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider +++ b/codegen/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -1 +1,2 @@ -org.fcitx.fcitx5.android.codegen.GenKeyMappingProvider \ No newline at end of file +org.fcitx.fcitx5.android.codegen.GenKeyMappingProvider +org.fcitx.fcitx5.android.codegen.GenScancodeMappingProvider \ No newline at end of file From eab045c9910db75a81f8204c26bb79982ab33667 Mon Sep 17 00:00:00 2001 From: Qijia Liu Date: Sat, 6 Jul 2024 22:21:17 -0400 Subject: [PATCH 033/181] Add thai plugin --- .gitmodules | 3 ++ README.md | 1 + lib/fcitx5/src/main/cpp/prebuilt | 2 +- plugin/thai/build.gradle.kts | 53 +++++++++++++++++++ .../licenses/libraries/fcitx5-libthai.json | 11 ++++ plugin/thai/licenses/libraries/libiconv.json | 11 ++++ plugin/thai/licenses/libraries/libthai.json | 11 ++++ plugin/thai/src/main/AndroidManifest.xml | 6 +++ plugin/thai/src/main/cpp/CMakeLists.txt | 30 +++++++++++ plugin/thai/src/main/cpp/fcitx5-libthai | 1 + plugin/thai/src/main/res/values/strings.xml | 6 +++ plugin/thai/src/main/res/xml/plugin.xml | 6 +++ settings.gradle.kts | 1 + 13 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 plugin/thai/build.gradle.kts create mode 100644 plugin/thai/licenses/libraries/fcitx5-libthai.json create mode 100644 plugin/thai/licenses/libraries/libiconv.json create mode 100644 plugin/thai/licenses/libraries/libthai.json create mode 100644 plugin/thai/src/main/AndroidManifest.xml create mode 100644 plugin/thai/src/main/cpp/CMakeLists.txt create mode 160000 plugin/thai/src/main/cpp/fcitx5-libthai create mode 100644 plugin/thai/src/main/res/values/strings.xml create mode 100644 plugin/thai/src/main/res/xml/plugin.xml diff --git a/.gitmodules b/.gitmodules index 298284f3e..b710f89d6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -52,3 +52,6 @@ [submodule "plugin/clipboard-filter/ClearURLsRules"] path = plugin/clipboard-filter/ClearURLsRules url = https://github.com/ClearURLs/Rules.git +[submodule "plugin/thai/src/main/cpp/fcitx5-libthai"] + path = plugin/thai/src/main/cpp/fcitx5-libthai + url = https://github.com/fcitx/fcitx5-libthai diff --git a/README.md b/README.md index a628669ef..53b6fc35b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ GitHub: [![release version](https://img.shields.io/github/v/release/fcitx5-andro - Japanese (via [Anthy Plugin](./plugin/anthy)) - Korean (via [Hangul Plugin](./plugin/hangul)) - Sinhala (via [Sayura Plugin](./plugin/sayura)) +- Thai (via [Thai Plugin](./plugin/thai)) - Generic (via [RIME Plugin](./plugin/rime), supports importing custom schemas) ### Implemented Features diff --git a/lib/fcitx5/src/main/cpp/prebuilt b/lib/fcitx5/src/main/cpp/prebuilt index 78fc216ae..870970f8a 160000 --- a/lib/fcitx5/src/main/cpp/prebuilt +++ b/lib/fcitx5/src/main/cpp/prebuilt @@ -1 +1 @@ -Subproject commit 78fc216ae4714c8e9b14115baa4ad6800a2fd1ad +Subproject commit 870970f8ac4ea6e2c9639200448e0005d0c89c14 diff --git a/plugin/thai/build.gradle.kts b/plugin/thai/build.gradle.kts new file mode 100644 index 000000000..16896cefc --- /dev/null +++ b/plugin/thai/build.gradle.kts @@ -0,0 +1,53 @@ +@file:Suppress("UnstableApiUsage") + +plugins { + id("org.fcitx.fcitx5.android.app-convention") + id("org.fcitx.fcitx5.android.plugin-app-convention") + id("org.fcitx.fcitx5.android.native-app-convention") + id("org.fcitx.fcitx5.android.build-metadata") + id("org.fcitx.fcitx5.android.data-descriptor") + id("org.fcitx.fcitx5.android.fcitx-component") +} + +android { + namespace = "org.fcitx.fcitx5.android.plugin.thai" + + defaultConfig { + applicationId = "org.fcitx.fcitx5.android.plugin.thai" + + externalNativeBuild { + cmake { + targets( + "libthai" + ) + } + } + } + + buildTypes { + release { + resValue("string", "app_name", "@string/app_name_release") + } + debug { + resValue("string", "app_name", "@string/app_name_debug") + } + } + + packaging { + jniLibs { + excludes += setOf( + "**/libc++_shared.so", + "**/libFcitx5*" + ) + } + } +} + +aboutLibraries { + configPath = "plugin/thai/licenses" +} + +dependencies { + implementation(project(":lib:fcitx5")) + implementation(project(":lib:plugin-base")) +} diff --git a/plugin/thai/licenses/libraries/fcitx5-libthai.json b/plugin/thai/licenses/libraries/fcitx5-libthai.json new file mode 100644 index 000000000..2e9a1d7ea --- /dev/null +++ b/plugin/thai/licenses/libraries/fcitx5-libthai.json @@ -0,0 +1,11 @@ +{ + "uniqueId": "fcitx/fcitx5-libthai", + "artifactVersion": "5.1.3", + "description": "Thai Wrapper for Fcitx", + "name": "fcitx/fcitx5-libthai", + "website": "https://github.com/fcitx/fcitx5-libthai", + "tag": "native", + "licenses": [ + "GPL-2.0-or-later" + ] +} diff --git a/plugin/thai/licenses/libraries/libiconv.json b/plugin/thai/licenses/libraries/libiconv.json new file mode 100644 index 000000000..a12152603 --- /dev/null +++ b/plugin/thai/licenses/libraries/libiconv.json @@ -0,0 +1,11 @@ +{ + "uniqueId": "GNU/libiconv", + "artifactVersion": "1.17", + "description": "Text encoding conversion library", + "name": "GNU/libiconv", + "website": "https://savannah.gnu.org/projects/libiconv", + "tag": "native", + "licenses": [ + "LGPL-2.1-or-later" + ] +} diff --git a/plugin/thai/licenses/libraries/libthai.json b/plugin/thai/licenses/libraries/libthai.json new file mode 100644 index 000000000..c758d64ce --- /dev/null +++ b/plugin/thai/licenses/libraries/libthai.json @@ -0,0 +1,11 @@ +{ + "uniqueId": "tlwg/libthai", + "artifactVersion": "0.1.29", + "description": "Thai language support routines", + "name": "tlwg/libthai", + "website": "https://github.com/tlwg/libthai", + "tag": "native", + "licenses": [ + "LGPL-2.1-only" + ] +} diff --git a/plugin/thai/src/main/AndroidManifest.xml b/plugin/thai/src/main/AndroidManifest.xml new file mode 100644 index 000000000..654a95e5f --- /dev/null +++ b/plugin/thai/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugin/thai/src/main/cpp/CMakeLists.txt b/plugin/thai/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000..67631f99e --- /dev/null +++ b/plugin/thai/src/main/cpp/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.18) + +project(fcitx5-android-plugin-thai VERSION 0.0.9) + +# For reproducible build +add_link_options("LINKER:--hash-style=gnu,--build-id=none") + +# prefab dependency +find_package(fcitx5 REQUIRED CONFIG) +get_target_property(FCITX5_CMAKE_MODULES fcitx5::cmake INTERFACE_INCLUDE_DIRECTORIES) +set(CMAKE_MODULE_PATH ${FCITX5_CMAKE_MODULES} ${CMAKE_MODULE_PATH}) + +find_package(ECM MODULE) +find_package(Fcitx5Core MODULE) +find_package(Fcitx5Module MODULE) + +set(PREBUILT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../lib/fcitx5/src/main/cpp/prebuilt") + +set(Iconv_LIBRARY "${PREBUILT_DIR}/libiconv/${ANDROID_ABI}/lib/libiconv.a") + +add_definitions("-I${PREBUILT_DIR}/libiconv/${ANDROID_ABI}/include") + +add_library(Thai_static STATIC IMPORTED) +set_target_properties(Thai_static PROPERTIES + IMPORTED_LOCATION "${PREBUILT_DIR}/libthai/${ANDROID_ABI}/lib/libthai.a" + INTERFACE_INCLUDE_DIRECTORIES "${PREBUILT_DIR}/libthai/${ANDROID_ABI}/include" + ) + +set(THAI_TARGET Thai_static) +add_subdirectory(fcitx5-libthai) diff --git a/plugin/thai/src/main/cpp/fcitx5-libthai b/plugin/thai/src/main/cpp/fcitx5-libthai new file mode 160000 index 000000000..8f0432f35 --- /dev/null +++ b/plugin/thai/src/main/cpp/fcitx5-libthai @@ -0,0 +1 @@ +Subproject commit 8f0432f35af4da305dcb6e8a2e6aaeb28259ac5b diff --git a/plugin/thai/src/main/res/values/strings.xml b/plugin/thai/src/main/res/values/strings.xml new file mode 100644 index 000000000..69e7804f1 --- /dev/null +++ b/plugin/thai/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + Fcitx5 for Android (Thai Plugin | Debug) + Fcitx5 for Android (Thai Plugin) + Thai input method for Fcitx5 + \ No newline at end of file diff --git a/plugin/thai/src/main/res/xml/plugin.xml b/plugin/thai/src/main/res/xml/plugin.xml new file mode 100644 index 000000000..7eeb65b3a --- /dev/null +++ b/plugin/thai/src/main/res/xml/plugin.xml @@ -0,0 +1,6 @@ + + + 0.1 + fcitx5-libthai + @string/description + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index b6f9a683b..88a0a2a20 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,3 +35,4 @@ include(":plugin:hangul") include(":plugin:chewing") include(":plugin:sayura") include(":plugin:jyutping") +include(":plugin:thai") From 14cef4c75596aeec3050c99e351b08aca40b4e3d Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 31 Jul 2024 01:29:51 +0800 Subject: [PATCH 034/181] Exclude redundant files from apk --- .../main/kotlin/AndroidAppConventionPlugin.kt | 69 +++++++++++++++++++ .../kotlin/AndroidBaseConventionPlugin.kt | 29 ++++++++ 2 files changed, 98 insertions(+) diff --git a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt index caba1afc6..22dc5a7ac 100644 --- a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt @@ -2,13 +2,20 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ +import com.android.build.api.dsl.ApplicationExtension import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.gradle.internal.dsl.BaseAppModuleExtension +import com.android.build.gradle.tasks.PackageApplication import com.mikepenz.aboutlibraries.plugin.AboutLibrariesExtension import org.gradle.api.Project +import org.gradle.api.file.RegularFile +import org.gradle.api.internal.provider.Providers import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.File +import java.lang.reflect.Field /** * The prototype of an Android Application @@ -55,6 +62,68 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { } } + target.extensions.configure { + dependenciesInfo { + includeInApk = false + includeInBundle = false + } + packaging { + resources { + excludes += setOf( + "/META-INF/*.version", + "/META-INF/*.kotlin_module", // cannot be excluded actually + "/kotlin/**", + "/kotlin-tooling-metadata.json" + ) + } + } + } + + // remove META-INF/com/android/build/gradle/app-metadata.properties + target.tasks.withType { + var javaClass: Class<*>? = appMetadata.javaClass + var valueField: Field? = null + while (javaClass != null) { + valueField = javaClass.declaredFields.find { it.name == "value" } + if (valueField != null) break + else javaClass = javaClass.superclass + } + valueField?.isAccessible = true + var appMetadataPath: String? = null + target.afterEvaluate { + // writeReleaseAppMetadata was skipped... but why? + if (appMetadata.isPresent) { + appMetadata.asFile.get().apply { + appMetadataPath = path + parentFile.mkdirs() + // make sure appMetadata file exists before the task + writeText("") + } + } + } + doFirst { + appMetadataPath?.let { File(it).delete() } + valueField?.set(appMetadata, Providers.notDefined()) + allInputFilesWithNameOnlyPathSensitivity.removeAll { true } + } + } + + // try to remove -.kotlin_module, but it does not work ¯\_(ツ)_/¯ + target.tasks.withType { + doLast f@{ + val ktClass = outputs.files.files.filter { it.path.contains("kotlin-classes") } + if (ktClass.isEmpty()) return@f + val metaInf = ktClass.first().resolve("META-INF") + if (!metaInf.exists() || !metaInf.isDirectory) return@f + metaInf.listFiles()?.forEach { + if (it.name.endsWith(".kotlin_module")) { + println("deleting ${it.path}") + it.delete() + } + } + } + } + target.extensions.configure { // Add dependency relationships for data descriptor task onVariants { v -> diff --git a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt index 5dd555405..8e3fa7fed 100644 --- a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt @@ -26,6 +26,13 @@ open class AndroidBaseConventionPlugin : Plugin { sourceCompatibility = Versions.java targetCompatibility = Versions.java } + buildTypes { + onEach { + // remove META-INF/version-control-info.textproto + @Suppress("UnstableApiUsage") + it.vcsInfo.include = false + } + } } target.tasks.withType { @@ -40,6 +47,28 @@ open class AndroidBaseConventionPlugin : Plugin { languageSettings.optIn("kotlin.RequiresOptIn") } } + + target.afterEvaluate { + // remove assets/dexopt/baseline.prof{,m} (baseline profile) + target.tasks.findByName("prepareReleaseArtProfile")?.apply { + enabled = false + } + target.tasks.findByName("mergeReleaseArtProfile")?.apply { + enabled = false + } + target.tasks.findByName("expandReleaseL8ArtProfileWildcards")?.apply { + enabled = false + } + target.tasks.findByName("expandReleaseArtProfileWildcards")?.apply { + enabled = false + } + target.tasks.findByName("compileReleaseArtProfile")?.apply { + enabled = false + } + target.tasks.findByName("writeReleaseAppMetadata")?.apply { + enabled = false + } + } } } From 82b36cb239116f2ca81d401fd3d2b267f64c8e7b Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 31 Jul 2024 02:09:51 +0800 Subject: [PATCH 035/181] Don't exclude `/kotlin/**` from apk otherwise it would crash Resource not found in classpath: kotlin/kotlin.kotlin_builtins --- .../convention/src/main/kotlin/AndroidAppConventionPlugin.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt index 22dc5a7ac..6f60b8e5b 100644 --- a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt @@ -72,7 +72,6 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { excludes += setOf( "/META-INF/*.version", "/META-INF/*.kotlin_module", // cannot be excluded actually - "/kotlin/**", "/kotlin-tooling-metadata.json" ) } @@ -117,7 +116,6 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { if (!metaInf.exists() || !metaInf.isDirectory) return@f metaInf.listFiles()?.forEach { if (it.name.endsWith(".kotlin_module")) { - println("deleting ${it.path}") it.delete() } } From d70ca7a312166f4d32512d22b33e99d2e9b08af6 Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 31 Jul 2024 02:11:22 +0800 Subject: [PATCH 036/181] Reset caps lock state after switching input method --- .../android/input/keyboard/TextKeyboard.kt | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt index d58cc204a..f3476aafb 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt @@ -113,17 +113,11 @@ class TextKeyboard( private var punctuationMapping: Map = mapOf() private fun transformPunctuation(p: String) = punctuationMapping.getOrDefault(p, p) - private fun transformInputString(c: String): String { - if (c.length != 1) return c - if (c[0].isLetter()) return transformAlphabet(c) - return transformPunctuation(c) - } - override fun onAction(action: KeyAction, source: KeyActionListener.Source) { var transformed = action when (action) { - is KeyAction.FcitxKeyAction -> { - if (source == KeyActionListener.Source.Keyboard && action.act.length == 1) { + is KeyAction.FcitxKeyAction -> when (source) { + KeyActionListener.Source.Keyboard -> { when (capsState) { CapsState.None -> { transformed = action.copy(act = action.act.lowercase()) @@ -143,6 +137,11 @@ class TextKeyboard( } } } + KeyActionListener.Source.Popup -> { + if (capsState == CapsState.Once) { + switchCapsState() + } + } } is KeyAction.CapsAction -> switchCapsState(action.lock) else -> {} @@ -170,12 +169,21 @@ class TextKeyboard( append(ime.displayName) ime.subMode.run { label.ifEmpty { name.ifEmpty { null } } }?.let { append(" ($it)") } } + if (capsState != CapsState.None) { + switchCapsState() + } + } + + private fun transformPopupPreview(c: String): String { + if (c.length != 1) return c + if (c[0].isLetter()) return transformAlphabet(c) + return transformPunctuation(c) } override fun onPopupAction(action: PopupAction) { val newAction = when (action) { - is PopupAction.PreviewAction -> action.copy(content = transformInputString(action.content)) - is PopupAction.PreviewUpdateAction -> action.copy(content = transformInputString(action.content)) + is PopupAction.PreviewAction -> action.copy(content = transformPopupPreview(action.content)) + is PopupAction.PreviewUpdateAction -> action.copy(content = transformPopupPreview(action.content)) is PopupAction.ShowKeyboardAction -> { val label = action.keyboard.label if (label.length == 1 && label[0].isLetter()) @@ -188,13 +196,18 @@ class TextKeyboard( } private fun switchCapsState(lock: Boolean = false) { - capsState = if (lock) when (capsState) { - CapsState.Lock -> CapsState.None - else -> CapsState.Lock - } else when (capsState) { - CapsState.None -> CapsState.Once - else -> CapsState.None - } + capsState = + if (lock) { + when (capsState) { + CapsState.Lock -> CapsState.None + else -> CapsState.Lock + } + } else { + when (capsState) { + CapsState.None -> CapsState.Once + else -> CapsState.None + } + } updateCapsButtonIcon() updateAlphabetKeys() } From 0ead7662f177d8a74e99bc052ef47c33d280c9b7 Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 1 Aug 2024 00:50:40 +0800 Subject: [PATCH 037/181] Fix toolbar title reset when rotating screen --- .../fcitx5/android/ui/main/MainActivity.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/MainActivity.kt b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/MainActivity.kt index cbc967297..e1d2cd23d 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/MainActivity.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/MainActivity.kt @@ -17,14 +17,13 @@ import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.graphics.drawable.DrawerArrowDrawable import androidx.core.os.bundleOf import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updateLayoutParams import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.AppBarConfiguration -import androidx.navigation.ui.setupWithNavController import org.fcitx.fcitx5.android.R import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.databinding.ActivityMainBinding @@ -58,13 +57,14 @@ class MainActivity : AppCompatActivity() { windowInsets } setContentView(binding.root) - setSupportActionBar(binding.toolbar) - val appBarConfiguration = AppBarConfiguration( - // always show back icon regardless of `navController.currentDestination` - topLevelDestinationIds = setOf() - ) + // always show toolbar back arrow icon + // https://android.googlesource.com/platform/frameworks/support/+/32e643112d0217619237a0d7101b50919c6caf51/navigation/navigation-ui/src/main/java/androidx/navigation/ui/AbstractAppBarOnDestinationChangedListener.kt#80 + binding.toolbar.navigationIcon = DrawerArrowDrawable(this).apply { progress = 1f } + // show menu icon and other action icons on toolbar + // don't use `setSupportActionBar(binding.toolbar)` here, + // because navController would change toolbar title, we need to control it by ourselves + setupToolbarMenu(binding.toolbar.menu) navController = binding.navHostFragment.getFragment().navController - binding.toolbar.setupWithNavController(navController, appBarConfiguration) binding.toolbar.setNavigationOnClickListener { // prevent navigate up when child fragment has enabled `OnBackPressedCallback` if (onBackPressedDispatcher.hasEnabledCallbacks()) { @@ -81,6 +81,7 @@ class MainActivity : AppCompatActivity() { binding.toolbar.elevation = dp(if (it) 4f else 0f) } navController.addOnDestinationChangedListener { _, dest, _ -> + dest.label?.let { viewModel.setToolbarTitle(it.toString()) } when (dest.id) { R.id.themeFragment -> viewModel.disableToolbarShadow() else -> viewModel.enableToolbarShadow() @@ -116,7 +117,7 @@ class MainActivity : AppCompatActivity() { } } - override fun onCreateOptionsMenu(menu: Menu): Boolean { + private fun setupToolbarMenu(menu: Menu) { menu.apply { add(R.string.save).apply { icon = drawable(R.drawable.ic_baseline_save_24)!!.apply { @@ -191,7 +192,6 @@ class MainActivity : AppCompatActivity() { } } } - return true } private var needNotifications by AppPrefs.getInstance().internal.needNotifications From a20af29d44b9cb01f209e69f2adeda409a039578 Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 1 Aug 2024 01:33:55 +0800 Subject: [PATCH 038/181] Fix undoing consecutive deletions in ClipboardWindow --- .../input/clipboard/ClipboardWindow.kt | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt index cbc2f52ca..bd85134f9 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/clipboard/ClipboardWindow.kt @@ -20,6 +20,7 @@ import androidx.paging.PagingConfig import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager +import com.google.android.material.snackbar.BaseTransientBottomBar.BaseCallback import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.SnackbarContentLayout import kotlinx.coroutines.Job @@ -59,6 +60,7 @@ class ClipboardWindow : InputWindow.ExtendedInputWindow() { private val snackbarCtx by lazy { context.withTheme(R.style.InputViewSnackbarTheme) } + private var snackbarInstance: Snackbar? = null private lateinit var stateMachine: EventStateMachine @@ -192,22 +194,40 @@ class ClipboardWindow : InputWindow.ExtendedInputWindow() { } } + private val pendingDeleteIds = arrayListOf() + @SuppressLint("RestrictedApi") private fun showUndoSnackbar(vararg id: Int) { - val str = context.resources.getString(R.string.num_items_deleted, id.size) - Snackbar.make(snackbarCtx, ui.root, str, Snackbar.LENGTH_LONG) - .setBackgroundTint(theme.keyBackgroundColor) - .setTextColor(theme.keyTextColor) + id.forEach { pendingDeleteIds.add(it) } + val str = context.resources.getString(R.string.num_items_deleted, pendingDeleteIds.size) + snackbarInstance = Snackbar.make(snackbarCtx, ui.root, str, Snackbar.LENGTH_LONG) + .setBackgroundTint(theme.popupBackgroundColor) + .setTextColor(theme.popupTextColor) .setActionTextColor(theme.genericActiveBackgroundColor) .setAction(R.string.undo) { service.lifecycleScope.launch { - ClipboardManager.undoDelete(*id) + ClipboardManager.undoDelete(*pendingDeleteIds.toIntArray()) + pendingDeleteIds.clear() } } .addCallback(object : Snackbar.Callback() { override fun onDismissed(transientBottomBar: Snackbar, event: Int) { - service.lifecycleScope.launch { - ClipboardManager.realDelete() + if (snackbarInstance === transientBottomBar) { + snackbarInstance = null + } + when (event) { + BaseCallback.DISMISS_EVENT_SWIPE, + BaseCallback.DISMISS_EVENT_MANUAL, + BaseCallback.DISMISS_EVENT_TIMEOUT -> { + service.lifecycleScope.launch { + ClipboardManager.realDelete() + pendingDeleteIds.clear() + } + } + BaseCallback.DISMISS_EVENT_ACTION, + BaseCallback.DISMISS_EVENT_CONSECUTIVE -> { + // user clicked "undo" or deleted more items which makes a new snackbar + } } } }).apply { @@ -256,6 +276,7 @@ class ClipboardWindow : InputWindow.ExtendedInputWindow() { adapter.onDetached() adapterSubmitJob?.cancel() promptMenu?.dismiss() + snackbarInstance?.dismiss() } override val title: String by lazy { From 3b24b037aad72651bcc4ea093bb6b55a5f3f01d3 Mon Sep 17 00:00:00 2001 From: Rocka Date: Fri, 2 Aug 2024 23:34:03 +0800 Subject: [PATCH 039/181] Debloat apk further --- .../main/kotlin/AndroidAppConventionPlugin.kt | 43 +++++++++---------- .../kotlin/AndroidBaseConventionPlugin.kt | 29 ------------- .../main/kotlin/AndroidLibConventionPlugin.kt | 5 +++ 3 files changed, 26 insertions(+), 51 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt index 6f60b8e5b..cc8a60553 100644 --- a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt @@ -5,17 +5,20 @@ import com.android.build.api.dsl.ApplicationExtension import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.gradle.internal.dsl.BaseAppModuleExtension +import com.android.build.gradle.internal.tasks.CompileArtProfileTask +import com.android.build.gradle.internal.tasks.ExpandArtProfileWildcardsTask +import com.android.build.gradle.internal.tasks.MergeArtProfileTask import com.android.build.gradle.tasks.PackageApplication import com.mikepenz.aboutlibraries.plugin.AboutLibrariesExtension import org.gradle.api.Project import org.gradle.api.file.RegularFile +import org.gradle.api.internal.provider.AbstractProperty import org.gradle.api.internal.provider.Providers import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.File -import java.lang.reflect.Field /** * The prototype of an Android Application @@ -56,6 +59,11 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { debug { applicationIdSuffix = ".debug" } + all { + // remove META-INF/version-control-info.textproto + @Suppress("UnstableApiUsage") + vcsInfo.include = false + } } compileOptions { isCoreLibraryDesugaringEnabled = true @@ -72,6 +80,7 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { excludes += setOf( "/META-INF/*.version", "/META-INF/*.kotlin_module", // cannot be excluded actually + "/DebugProbesKt.bin", "/kotlin-tooling-metadata.json" ) } @@ -80,29 +89,14 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { // remove META-INF/com/android/build/gradle/app-metadata.properties target.tasks.withType { - var javaClass: Class<*>? = appMetadata.javaClass - var valueField: Field? = null - while (javaClass != null) { - valueField = javaClass.declaredFields.find { it.name == "value" } - if (valueField != null) break - else javaClass = javaClass.superclass - } - valueField?.isAccessible = true - var appMetadataPath: String? = null - target.afterEvaluate { - // writeReleaseAppMetadata was skipped... but why? - if (appMetadata.isPresent) { - appMetadata.asFile.get().apply { - appMetadataPath = path - parentFile.mkdirs() - // make sure appMetadata file exists before the task - writeText("") - } + val valueField = + AbstractProperty::class.java.declaredFields.find { it.name == "value" } ?: run { + println("class AbstractProperty field value not found, something could have gone wrong") + return@withType } - } + valueField.isAccessible = true doFirst { - appMetadataPath?.let { File(it).delete() } - valueField?.set(appMetadata, Providers.notDefined()) + valueField.set(appMetadata, Providers.notDefined()) allInputFilesWithNameOnlyPathSensitivity.removeAll { true } } } @@ -122,6 +116,11 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { } } + // remove assets/dexopt/baseline.prof{,m} (baseline profile) + target.tasks.withType { enabled = false } + target.tasks.withType { enabled = false } + target.tasks.withType { enabled = false } + target.extensions.configure { // Add dependency relationships for data descriptor task onVariants { v -> diff --git a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt index 8e3fa7fed..5dd555405 100644 --- a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt @@ -26,13 +26,6 @@ open class AndroidBaseConventionPlugin : Plugin { sourceCompatibility = Versions.java targetCompatibility = Versions.java } - buildTypes { - onEach { - // remove META-INF/version-control-info.textproto - @Suppress("UnstableApiUsage") - it.vcsInfo.include = false - } - } } target.tasks.withType { @@ -47,28 +40,6 @@ open class AndroidBaseConventionPlugin : Plugin { languageSettings.optIn("kotlin.RequiresOptIn") } } - - target.afterEvaluate { - // remove assets/dexopt/baseline.prof{,m} (baseline profile) - target.tasks.findByName("prepareReleaseArtProfile")?.apply { - enabled = false - } - target.tasks.findByName("mergeReleaseArtProfile")?.apply { - enabled = false - } - target.tasks.findByName("expandReleaseL8ArtProfileWildcards")?.apply { - enabled = false - } - target.tasks.findByName("expandReleaseArtProfileWildcards")?.apply { - enabled = false - } - target.tasks.findByName("compileReleaseArtProfile")?.apply { - enabled = false - } - target.tasks.findByName("writeReleaseAppMetadata")?.apply { - enabled = false - } - } } } diff --git a/build-logic/convention/src/main/kotlin/AndroidLibConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibConventionPlugin.kt index d8674c51c..0e301ab62 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibConventionPlugin.kt @@ -3,8 +3,10 @@ * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.tasks.ProcessLibraryArtProfileTask import org.gradle.api.Project import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType class AndroidLibConventionPlugin : AndroidBaseConventionPlugin() { @@ -20,6 +22,9 @@ class AndroidLibConventionPlugin : AndroidBaseConventionPlugin() { } } } + + // disable baseline profile tasks + target.tasks.withType { enabled = false } } } From 468d009116c92b58e0f11933f2a5d4935076179b Mon Sep 17 00:00:00 2001 From: rocka Date: Sat, 3 Aug 2024 18:44:57 +0800 Subject: [PATCH 040/181] Refactor build process (#554) - Use AGP splits.abi - Make prefab tasks run after native build tasks - Locate cmake in AGP way - Sign apk with gradle --- .github/workflows/fdroid.yml | 2 +- .github/workflows/nix.yml | 7 +- .github/workflows/pull_request.yml | 11 +-- build-logic/convention/build.gradle.kts | 8 -- .../main/kotlin/AndroidAppConventionPlugin.kt | 20 ++-- .../kotlin/AndroidBaseConventionPlugin.kt | 3 +- .../src/main/kotlin/AndroidSdkPathPlugin.kt | 36 -------- .../src/main/kotlin/CMakeDirPlugin.kt | 84 ----------------- .../src/main/kotlin/DataDescriptorPlugin.kt | 9 +- .../src/main/kotlin/FcitxComponentPlugin.kt | 26 +++--- .../src/main/kotlin/FcitxHeadersPlugin.kt | 28 +++--- .../main/kotlin/NativeAppConventionPlugin.kt | 12 +++ .../main/kotlin/NativeBaseConventionPlugin.kt | 27 ++---- .../src/main/kotlin/NativeBuildTasks.kt | 48 ++++++++++ .../main/kotlin/NativeLibConventionPlugin.kt | 19 ++-- .../convention/src/main/kotlin/PlayRelease.kt | 33 ------- .../src/main/kotlin/ProjectExtensions.kt | 91 +++++++++++++++++++ .../convention/src/main/kotlin/Utils.kt | 59 ++---------- .../convention/src/main/kotlin/Versions.kt | 25 ++--- gradle/libs.versions.toml | 2 +- lib/plugin-base/src/debug/AndroidManifest.xml | 13 ++- 21 files changed, 238 insertions(+), 325 deletions(-) delete mode 100644 build-logic/convention/src/main/kotlin/AndroidSdkPathPlugin.kt delete mode 100644 build-logic/convention/src/main/kotlin/CMakeDirPlugin.kt create mode 100644 build-logic/convention/src/main/kotlin/NativeBuildTasks.kt delete mode 100644 build-logic/convention/src/main/kotlin/PlayRelease.kt create mode 100644 build-logic/convention/src/main/kotlin/ProjectExtensions.kt diff --git a/.github/workflows/fdroid.yml b/.github/workflows/fdroid.yml index 3967d8a06..f77077e74 100644 --- a/.github/workflows/fdroid.yml +++ b/.github/workflows/fdroid.yml @@ -20,7 +20,7 @@ defaults: jobs: fdroid-build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: registry.gitlab.com/fdroid/fdroidserver:buildserver-bookworm strategy: matrix: diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index fc833b212..2c52911af 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -9,10 +9,9 @@ jobs: develop: strategy: matrix: - # Nix doesn't support Android toolchain on aarch64-darwin yet - # https://github.com/NixOS/nixpkgs/issues/303968 -# os: [ubuntu-latest, macOS-latest] - os: [ubuntu-latest] + os: + - ubuntu-22.04 + - macos-14 runs-on: ${{ matrix.os }} steps: - name: Fetch source code diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a5318369b..f9ff8a9f4 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -19,13 +19,6 @@ jobs: - macos-13 - macos-14 - windows-2022 - abi: - - armeabi-v7a - - arm64-v8a - - x86 - - x86_64 - env: - BUILD_ABI: ${{ matrix.abi }} steps: - name: Fetch source code uses: actions/checkout@v4 @@ -81,7 +74,7 @@ jobs: - name: Upload app uses: actions/upload-artifact@v4 with: - name: app-${{ matrix.os }}-${{ matrix.abi }} + name: app-${{ matrix.os }} path: app/build/outputs/apk/debug/ - name: Pack plugins @@ -99,5 +92,5 @@ jobs: - name: Upload plugins uses: actions/upload-artifact@v4 with: - name: plugins-${{ matrix.os }}-${{ matrix.abi }} + name: plugins-${{ matrix.os }} path: plugins-to-upload diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index c4129700b..bd9d5a608 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -40,10 +40,6 @@ gradlePlugin { id = "org.fcitx.fcitx5.android.build-metadata" implementationClass = "BuildMetadataPlugin" } - register("cmakeDir") { - id = "org.fcitx.fcitx5.android.cmake-dir" - implementationClass = "CMakeDirPlugin" - } register("dataDescriptor") { id = "org.fcitx.fcitx5.android.data-descriptor" implementationClass = "DataDescriptorPlugin" @@ -64,10 +60,6 @@ gradlePlugin { id = "org.fcitx.fcitx5.android.native-lib-convention" implementationClass = "NativeLibConventionPlugin" } - register("androidSdkPath") { - id = "org.fcitx.fcitx5.android.android-sdk-path" - implementationClass = "AndroidSdkPathPlugin" - } } } diff --git a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt index cc8a60553..c54bd9c57 100644 --- a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt @@ -18,7 +18,6 @@ import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.File /** * The prototype of an Android Application @@ -37,23 +36,20 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { target.extensions.configure { defaultConfig { targetSdk = Versions.targetSdk - versionCode = Versions.calculateVersionCode(target.buildABI) + versionCode = Versions.calculateVersionCode() versionName = target.buildVersionName } buildTypes { release { isMinifyEnabled = true isShrinkResources = true - // config singing key for play release - signingConfig = with(PlayRelease) { - if (target.buildPlayRelease) { - signingConfigs.create("playRelease") { - storeFile = File(target.storeFile!!) - storePassword = target.storePassword - keyAlias = target.keyAlias - keyPassword = target.keyPassword - } - } else null + signingConfig = target.signKey?.let { + signingConfigs.create("release") { + storeFile = it + storePassword = target.signKeyPwd + keyAlias = target.signKeyAlias + keyPassword = target.signKeyPwd + } } } debug { diff --git a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt index 5dd555405..79af36ae7 100644 --- a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt @@ -2,7 +2,6 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ -import Versions.buildTools import com.android.build.api.dsl.CommonExtension import org.gradle.api.Plugin import org.gradle.api.Project @@ -18,7 +17,7 @@ open class AndroidBaseConventionPlugin : Plugin { target.extensions.configure(CommonExtension::class.java) { compileSdk = Versions.compileSdk - buildToolsVersion = target.buildTools + buildToolsVersion = target.buildToolsVersion defaultConfig { minSdk = Versions.minSdk } diff --git a/build-logic/convention/src/main/kotlin/AndroidSdkPathPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidSdkPathPlugin.kt deleted file mode 100644 index 52f622c01..000000000 --- a/build-logic/convention/src/main/kotlin/AndroidSdkPathPlugin.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors - */ -import Versions.cmakeVersion -import com.android.build.api.variant.AndroidComponentsExtension -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.kotlin.dsl.getByName -import java.io.File - -val Project.androidSdkPath: File - get() = extensions.extraProperties.get(AndroidSdkPathPlugin.ANDROID_SDK_PATH) as? File - ?: error("Cannot find Android SDK path. Did you apply org.fcitx.fcitx5.android.android-sdk-path plugin?") - -val Project.cmakeBinary: String - get() = androidSdkPath.resolve("cmake/$cmakeVersion/bin/cmake").absolutePath - -class AndroidSdkPathPlugin : Plugin { - - companion object { - const val ANDROID_SDK_PATH = "AndroidSdkPath" - } - - private fun Project.setSdkPath(file: File) { - project.extensions.extraProperties.set(ANDROID_SDK_PATH, file) - } - - override fun apply(target: Project) { - val androidComponents = - target.extensions.getByName>("androidComponents") - val sdkPath = androidComponents.sdkComponents.sdkDirectory.get().asFile - target.setSdkPath(sdkPath) - } - -} diff --git a/build-logic/convention/src/main/kotlin/CMakeDirPlugin.kt b/build-logic/convention/src/main/kotlin/CMakeDirPlugin.kt deleted file mode 100644 index 518436545..000000000 --- a/build-logic/convention/src/main/kotlin/CMakeDirPlugin.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors - */ -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.Task -import java.io.File - -val Project.cmakeDir: File - get() { - try { - return extensions.extraProperties.get(CMakeDirPlugin.CMAKE_DIR) as File - } catch (e: Exception) { - error("Cannot find cmake dir. Did you apply org.fcitx.fcitx5.android.cmake-dir plugin and make your task `runAfterNativeConfigure`?") - } - } - -/** - * Important: make sure that the task runs after than the native task - * Since we can't declare the dependency relationship, a weaker running order constraint must be enforced - */ - -/** - * Note: native build tasks would be called `:buildCMake$Variant[$ABI][$target1,$target2,etc]` - * if `externalNativeBuild.cmake.targets` is provided with cmake target names, causing this method - * to be inaccurate. - * Consider using [Task.runAfterNativeConfigure] instead. - */ -fun Task.runAfterNativeBuild(project: Project) { - mustRunAfter("${project.path}:buildCMakeDebug[${project.buildABI}]") - mustRunAfter("${project.path}:buildCMakeRelWithDebInfo[${project.buildABI}]") -} - -fun Task.runAfterNativeConfigure(project: Project) { - mustRunAfter("${project.path}:configureCMakeDebug[${project.buildABI}]") - mustRunAfter("${project.path}:configureCMakeRelWithDebInfo[${project.buildABI}]") -} - -/** - * To obtain the cmake dir, the project should apply this plugin and call [Task.runAfterNativeConfigure] - * in the task that accesses [cmakeDir] - */ -class CMakeDirPlugin : Plugin { - - companion object { - const val CMAKE_DIR = "cmakeDir" - } - - private fun Project.setCmakeDir(file: File) { - project.extensions.extraProperties.set(CMAKE_DIR, file) - } - - /** - * Note *Graph* - * - * Installing fcitx components depends .cxx dir. - * Since the native task `buildCMake$Variant\[$ABI]` depend on the current variant and ABI, - * we should have registered installFcitxComponent tasks for the cartesian product of $Variant and $ABI, e.g. `installFcitxComponentDebug\[x86]` - * However, this would be way more tedious, as the build variant and ABI actually do not affect components we are going to install. - * The essential cause of this situation is that it's impossible for gradle to handle dynamic dependencies, - * where we cannot add dependency when running a task. So a trick is used here: when the task graph - * is evaluated, we look into it to find out the name of the native task which will be executed, and then store its output - * path in global variable. This results in our tasks can not be executed directly without executing the dependent of the native task, - * i.e. they are implicitly depending on the native task. - */ - override fun apply(target: Project) { - target.gradle.taskGraph.whenReady { - allTasks.find { - it.project.name == target.name && - (it.name.startsWith("configureCMakeDebug[") || it.name.startsWith("configureCMakeRelWithDebInfo[")) - }?.doLast { - val buildModelFile = outputs.files.first().resolve("build_model.json") - val buildModel = Json.parseToJsonElement(buildModelFile.readText()).jsonObject - val cxxBuildFolder = buildModel["cxxBuildFolder"]!!.jsonPrimitive.content - target.setCmakeDir(File(cxxBuildFolder)) - } - } - } - -} diff --git a/build-logic/convention/src/main/kotlin/DataDescriptorPlugin.kt b/build-logic/convention/src/main/kotlin/DataDescriptorPlugin.kt index 23aba6460..a88fbb82c 100644 --- a/build-logic/convention/src/main/kotlin/DataDescriptorPlugin.kt +++ b/build-logic/convention/src/main/kotlin/DataDescriptorPlugin.kt @@ -12,7 +12,13 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.logging.LogLevel import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty -import org.gradle.api.tasks.* +import org.gradle.api.tasks.Delete +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.task import org.gradle.work.ChangeType @@ -89,7 +95,6 @@ class DataDescriptorPlugin : Plugin { private val file by lazy { outputFile.get().asFile } - private fun serialize(files: Map, symlinks: Map) { if (symlinks.keys.intersect(files.keys).isNotEmpty()) throw IllegalArgumentException("Symlink target cannot be path in files") diff --git a/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt b/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt index bcb01723e..665fb24f0 100644 --- a/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt +++ b/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt @@ -27,8 +27,6 @@ class FcitxComponentPlugin : Plugin { } override fun apply(target: Project) { - target.pluginManager.apply("org.fcitx.fcitx5.android.android-sdk-path") - target.pluginManager.apply("org.fcitx.fcitx5.android.cmake-dir") registerCMakeTask(target, "generate-desktop-file", "config") registerCMakeTask(target, "translation-file", "translation") registerCleanTask(target) @@ -52,29 +50,27 @@ class FcitxComponentPlugin : Plugin { component: String, sourceProject: Project = project ) { - val dependencyTask = project.tasks.findByName(INSTALL_TASK) ?: project.task(INSTALL_TASK) val taskName = if (project === sourceProject) { "installProject${component.capitalized()}" } else { "installLibrary${component.capitalized()}[${sourceProject.name}]" } - project.task(taskName) { - runAfterNativeConfigure(sourceProject) - - doLast { - project.exec { - workingDir = sourceProject.cmakeDir - commandLine(project.cmakeBinary, "--build", ".", "--target", target) + val task = project.task(taskName) { + runAfterNativeConfigure(sourceProject) { abiModel -> + val cmake = abiModel.variant.module.cmake!!.cmakeExe!! + sourceProject.exec { + workingDir = abiModel.cxxBuildFolder + commandLine(cmake, "--build", ".", "--target", target) } - project.exec { - workingDir = sourceProject.cmakeDir + sourceProject.exec { + workingDir = abiModel.cxxBuildFolder environment("DESTDIR", project.assetsDir.absolutePath) - commandLine(project.cmakeBinary, "--install", ".", "--component", component) + commandLine(cmake, "--install", ".", "--component", component) } } - }.also { - dependencyTask.dependsOn(it) } + val dependencyTask = project.tasks.findByName(INSTALL_TASK) ?: project.task(INSTALL_TASK) + dependencyTask.dependsOn(task) } private fun registerCleanTask(project: Project) { diff --git a/build-logic/convention/src/main/kotlin/FcitxHeadersPlugin.kt b/build-logic/convention/src/main/kotlin/FcitxHeadersPlugin.kt index 935e771b1..81ecd4aa5 100644 --- a/build-logic/convention/src/main/kotlin/FcitxHeadersPlugin.kt +++ b/build-logic/convention/src/main/kotlin/FcitxHeadersPlugin.kt @@ -3,12 +3,13 @@ * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ import com.android.build.api.variant.LibraryAndroidComponentsExtension +import com.android.build.gradle.tasks.PrefabPackageConfigurationTask import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.tasks.Delete -import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.task +import org.gradle.kotlin.dsl.withType class FcitxHeadersPlugin : Plugin { @@ -21,35 +22,30 @@ class FcitxHeadersPlugin : Plugin { get() = file("build/headers") override fun apply(target: Project) { - target.pluginManager.apply("org.fcitx.fcitx5.android.android-sdk-path") - target.pluginManager.apply("org.fcitx.fcitx5.android.cmake-dir") registerInstallTask(target) registerCleanTask(target) } private fun registerInstallTask(project: Project) { val installHeadersTask = project.task(INSTALL_TASK) { - runAfterNativeConfigure(project) - - doLast { + runAfterNativeConfigure(project) { abiModel -> + val cmake = abiModel.variant.module.cmake!!.cmakeExe!! project.exec { - workingDir = project.cmakeDir + workingDir = abiModel.cxxBuildFolder environment("DESTDIR", project.headersInstallDir.absolutePath) - commandLine(project.cmakeBinary, "--install", ".", "--component", "header") + commandLine(cmake, "--install", ".", "--component", "header") } } } + // Make sure headers have been installed before configuring prefab package + project.tasks.withType().all { + dependsOn(installHeadersTask) + } + project.extensions.configure { - onVariants { - val variantName = it.name.capitalized() - project.afterEvaluate { - project.tasks.findByName("prefab${variantName}ConfigurePackage") - ?.dependsOn(installHeadersTask) - } - } - @Suppress("UnstableApiUsage") finalizeDsl { + @Suppress("UnstableApiUsage") it.prefab.forEach { library -> library.headers?.let { path -> project.file(path).mkdirs() } } diff --git a/build-logic/convention/src/main/kotlin/NativeAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/NativeAppConventionPlugin.kt index 515a0f4bf..e406d6143 100644 --- a/build-logic/convention/src/main/kotlin/NativeAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/NativeAppConventionPlugin.kt @@ -2,6 +2,8 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ +import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.api.variant.FilterConfiguration.FilterType import com.android.build.gradle.internal.dsl.BaseAppModuleExtension import org.gradle.api.Project import org.gradle.kotlin.dsl.configure @@ -23,6 +25,16 @@ class NativeAppConventionPlugin : NativeBaseConventionPlugin() { prefab = true } } + + target.extensions.configure { + onVariants { variant -> + // different version code based on abi + variant.outputs.forEach { output -> + val abi = output.filters.first { it.filterType == FilterType.ABI }.identifier + output.versionCode.set(Versions.calculateVersionCode(abi)) + } + } + } } } diff --git a/build-logic/convention/src/main/kotlin/NativeBaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/NativeBaseConventionPlugin.kt index f3f3481c5..c47d1f0c3 100644 --- a/build-logic/convention/src/main/kotlin/NativeBaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/NativeBaseConventionPlugin.kt @@ -2,8 +2,6 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ -import Versions.cmakeVersion -import Versions.ndkVersion import com.android.build.api.dsl.CommonExtension import org.gradle.api.Plugin import org.gradle.api.Project @@ -33,25 +31,12 @@ open class NativeBaseConventionPlugin : Plugin { path("src/main/cpp/CMakeLists.txt") } } - if (PlayRelease.run { target.buildPlayRelease }) { - // in this case, the version code of arm64-v8a will be used for the single production, - // unless `buildABI` is specified - defaultConfig { - ndk { - abiFilters.add("armeabi-v7a") - abiFilters.add("arm64-v8a") - abiFilters.add("x86") - abiFilters.add("x86_64") - } - } - } else { - splits { - abi { - isEnable = true - reset() - include(target.buildABI) - isUniversalApk = false - } + splits.abi { + isEnable = true + isUniversalApk = false + reset() + (target.buildAbiOverride?.split(",") ?: Versions.supportedABIs).forEach { + include(it) } } } diff --git a/build-logic/convention/src/main/kotlin/NativeBuildTasks.kt b/build-logic/convention/src/main/kotlin/NativeBuildTasks.kt new file mode 100644 index 000000000..3e57272da --- /dev/null +++ b/build-logic/convention/src/main/kotlin/NativeBuildTasks.kt @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ +import com.android.build.gradle.internal.cxx.configure.rewriteWithLocations +import com.android.build.gradle.internal.cxx.logging.PassThroughRecordingLoggingEnvironment +import com.android.build.gradle.internal.cxx.model.CxxAbiModel +import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask +import com.android.build.gradle.tasks.ExternalNativeBuildTask +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.kotlin.dsl.withType + +fun ExternalNativeBuildJsonTask.abiModel(): CxxAbiModel { + val abi = ExternalNativeBuildJsonTask::class.java.declaredFields.find { it.name == "abi" }!! + abi.isAccessible = true + return abi.get(this) as CxxAbiModel +} + +/** + * Important: make sure that the task runs after than the native task + * Since we can't declare the dependency relationship, a weaker running order constraint must be enforced + */ + +fun Task.runAfterNativeConfigure(project: Project, action: (abiModel: CxxAbiModel) -> Unit) { + lateinit var abiModel: CxxAbiModel + project.tasks.withType().all externalNativeBuild@{ + this@runAfterNativeConfigure.mustRunAfter(this@externalNativeBuild) + doFirst { + // `CxxAbiModel.rewriteWithLocations` requires a "Non-default logger" + // https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/cxx/configure/NativeLocationsBuildService.kt;drc=b5516899015633c99dc64b510d9729c4e001e89c;l=67 + // just supply a random LoggingEnvironment or whatever this is + PassThroughRecordingLoggingEnvironment().use { + abiModel = this@externalNativeBuild.abiModel() + .rewriteWithLocations(nativeLocationsBuildService.get()) + } + } + } + doLast { + action.invoke(abiModel) + } +} + +fun Task.runAfterNativeBuild(project: Project) { + project.tasks.withType().all externalNativeBuild@{ + this@runAfterNativeBuild.mustRunAfter(this@externalNativeBuild) + } +} diff --git a/build-logic/convention/src/main/kotlin/NativeLibConventionPlugin.kt b/build-logic/convention/src/main/kotlin/NativeLibConventionPlugin.kt index 638aa652d..2cf87bfe2 100644 --- a/build-logic/convention/src/main/kotlin/NativeLibConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/NativeLibConventionPlugin.kt @@ -3,9 +3,10 @@ * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.tasks.PrefabPackageConfigurationTask import org.gradle.api.Project -import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType class NativeLibConventionPlugin : NativeBaseConventionPlugin() { @@ -24,13 +25,15 @@ class NativeLibConventionPlugin : NativeBaseConventionPlugin() { prefab = true prefabPublishing = true } - libraryVariants.all { - // The output of PrefabConfigurePackageTask is up-to-date even after running clean. - // This is probably a bug of AGP. To work around, we need always rerun this task. - target.tasks.named("prefab${name.capitalized()}ConfigurePackage").configure { - doNotTrackState("The up-to-date checking of PrefabConfigurePackageTask is incorrect") - } - } + } + + target.tasks.withType().all { + // The output of PrefabConfigurePackageTask is up-to-date even after running clean. + // This is probably a bug of AGP. To work around, we need always rerun this task. + doNotTrackState("The up-to-date checking of PrefabConfigurePackageTask is incorrect") + // Native libraries must be built before we can properly configure prefab package, + // otherwise it would produce empty IMPORTED_LOCATION in cmake config. + runAfterNativeBuild(target) } } diff --git a/build-logic/convention/src/main/kotlin/PlayRelease.kt b/build-logic/convention/src/main/kotlin/PlayRelease.kt deleted file mode 100644 index 9e8366ddd..000000000 --- a/build-logic/convention/src/main/kotlin/PlayRelease.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors - */ -import org.gradle.api.Project -import java.io.File - -object PlayRelease { - private fun Project.epn(env: String, prop: String) = - System.getenv(env)?.takeIf { it.isNotBlank() } - ?: runCatching { property(prop)!!.toString() }.getOrNull() - - private fun Project.epr(env: String, prop: String) = ep( - env, - prop - ) { error("Neither environment variable $env nor project property $prop is set") } - - val Project.storeFile - get() = epn("PLAY_STORE_FILE", "playStoreFile") - - val Project.storePassword - get() = epr("PLAY_STORE_PASSWORD", "playStorePassword") - - val Project.keyAlias - get() = epr("PLAY_KEY_ALIAS", "playKeyAlias") - - val Project.keyPassword - get() = epr("PLAY_KEY_PASSWORD", "playKeyPassword") - - val Project.buildPlayRelease - get() = storeFile?.let { File(it).exists() } ?: false -} - diff --git a/build-logic/convention/src/main/kotlin/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/ProjectExtensions.kt new file mode 100644 index 000000000..6d539d8a4 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/ProjectExtensions.kt @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType +import java.io.ByteArrayOutputStream +import java.io.File +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + +fun Project.runCmd(cmd: String): String = ByteArrayOutputStream().use { + project.exec { + commandLine = cmd.split(" ") + standardOutput = it + } + it.toString().trim() +} + +val Project.versionCatalog: VersionCatalog + get() = extensions.getByType().named("libs") + +val Project.assetsDir: File + get() = file("src/main/assets").also { it.mkdirs() } + +val Project.cleanTask: Task + get() = tasks.getByName("clean") + +val Project.cmakeVersion + get() = ep("CMAKE_VERSION", "cmakeVersion") { Versions.defaultCMake } + +val Project.ndkVersion + get() = ep("NDK_VERSION", "ndkVersion") { Versions.defaultNDK } + +val Project.buildToolsVersion + get() = ep("BUILD_TOOLS_VERSION", "buildTools") { Versions.defaultBuildTools } + +val Project.buildVersionName + get() = ep("BUILD_VERSION_NAME", "buildVersionName") { + runCmd("git describe --tags --long --always") + } + +val Project.buildCommitHash + get() = ep("BUILD_COMMIT_HASH", "buildCommitHash") { + runCmd("git rev-parse HEAD") + } + +val Project.buildTimestamp + get() = ep("BUILD_TIMESTAMP", "buildTimestamp") { + System.currentTimeMillis().toString() + } + +val Project.buildAbiOverride: String? + get() = epn("BUILD_ABI", "buildABI") + +val Project.signKeyBase64: String? + get() = epn("SIGN_KEY_BASE64", "signKeyBase64") + +val Project.signKeyFile: String? + get() = epn("SIGN_KEY_FILE", "signKeyFile") + +val Project.signKey: File? + get() { + signKeyFile?.let { + val file = File(it) + if (file.exists()) return file + } + @OptIn(ExperimentalEncodingApi::class) + signKeyBase64?.let { + val buildDir = layout.buildDirectory.asFile.get() + buildDir.mkdirs() + val file = File.createTempFile("sign-", ".ks", buildDir) + try { + file.writeBytes(Base64.decode(it)) + return file + } catch (e: Exception) { + println(e.localizedMessage ?: e.stackTraceToString()) + file.delete() + } + } + return null + } + +val Project.signKeyPwd: String? + get() = epn("SIGN_KEY_PWD", "signKeyPwd") + +val Project.signKeyAlias: String? + get() = epn("SIGN_KEY_ALIAS", "signKeyAlias") diff --git a/build-logic/convention/src/main/kotlin/Utils.kt b/build-logic/convention/src/main/kotlin/Utils.kt index 155215cf8..e09d32db1 100644 --- a/build-logic/convention/src/main/kotlin/Utils.kt +++ b/build-logic/convention/src/main/kotlin/Utils.kt @@ -4,30 +4,14 @@ */ import kotlinx.serialization.json.Json import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.artifacts.VersionCatalog -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.getByType -import java.io.ByteArrayOutputStream -import java.io.File + +val json = Json { prettyPrint = true } inline fun envOrDefault(env: String, default: () -> String) = System.getenv(env)?.takeIf { it.isNotBlank() } ?: default() inline fun Project.propertyOrDefault(prop: String, default: () -> String) = - runCatching { property(prop)!!.toString() }.getOrElse { - default() - } - -fun Project.runCmd(cmd: String): String = ByteArrayOutputStream().use { - project.exec { - commandLine = cmd.split(" ") - standardOutput = it - } - it.toString().trim() -} - -val json = Json { prettyPrint = true } + runCatching { property(prop)!!.toString() }.getOrElse { default() } internal inline fun Project.ep(env: String, prop: String, block: () -> String) = envOrDefault(env) { @@ -36,35 +20,10 @@ internal inline fun Project.ep(env: String, prop: String, block: () -> String) = } } -val Project.versionCatalog: VersionCatalog - get() = extensions.getByType().named("libs") - -val Project.assetsDir: File - get() = file("src/main/assets").also { it.mkdirs() } - -val Project.cleanTask: Task - get() = tasks.getByName("clean") - -// Change default ABI here -val Project.buildABI - get() = ep("BUILD_ABI", "buildABI") { -// "armeabi-v7a" - "arm64-v8a" -// "x86" -// "x86_64" - } - -val Project.buildVersionName - get() = ep("BUILD_VERSION_NAME", "buildVersionName") { - runCmd("git describe --tags --long --always") - } +fun Project.epn(env: String, prop: String) = + System.getenv(env)?.takeIf { it.isNotBlank() } + ?: runCatching { property(prop)!!.toString() }.getOrNull() -val Project.buildCommitHash - get() = ep("BUILD_COMMIT_HASH", "buildCommitHash") { - runCmd("git rev-parse HEAD") - } - -val Project.buildTimestamp - get() = ep("BUILD_TIMESTAMP", "buildTimestamp") { - System.currentTimeMillis().toString() - } +fun Project.epr(env: String, prop: String) = ep(env, prop) { + error("Neither environment variable $env nor project property $prop is set") +} diff --git a/build-logic/convention/src/main/kotlin/Versions.kt b/build-logic/convention/src/main/kotlin/Versions.kt index a80b5dae5..bb2ae827b 100644 --- a/build-logic/convention/src/main/kotlin/Versions.kt +++ b/build-logic/convention/src/main/kotlin/Versions.kt @@ -3,7 +3,6 @@ * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ import org.gradle.api.JavaVersion -import org.gradle.api.Project object Versions { @@ -12,14 +11,17 @@ object Versions { const val minSdk = 23 const val targetSdk = 34 - private const val defaultCMake = "3.22.1" - private const val defaultNDK = "25.2.9519653" - private const val defaultBuildTools = "34.0.0" + const val defaultCMake = "3.22.1" + const val defaultNDK = "25.2.9519653" + const val defaultBuildTools = "34.0.0" // NOTE: increase this value to bump version code - private const val baseVersionCode = 7 + const val baseVersionCode = 7 - fun calculateVersionCode(abi: String): Int { + val supportedABIs = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") + const val fallbackABI = "arm64-v8a" + + fun calculateVersionCode(abi: String = fallbackABI): Int { val abiId = when (abi) { "armeabi-v7a" -> 1 "arm64-v8a" -> 2 @@ -29,13 +31,4 @@ object Versions { } return baseVersionCode * 10 + abiId } - - val Project.cmakeVersion - get() = ep("CMAKE_VERSION", "cmakeVersion") { defaultCMake } - - val Project.ndkVersion - get() = ep("NDK_VERSION", "ndkVersion") { defaultNDK } - - val Project.buildTools - get() = ep("BUILD_TOOLS_VERSION", "buildTools") { defaultBuildTools } -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5a138710e..4d4273617 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -androidGradlePlugin = "8.5.0" +androidGradlePlugin = "8.5.1" androidDesugarJDKLibs = "2.0.4" kotlin = "1.9.24" ksp = "1.9.24-1.0.20" diff --git a/lib/plugin-base/src/debug/AndroidManifest.xml b/lib/plugin-base/src/debug/AndroidManifest.xml index 6662c6746..0286b4ee7 100644 --- a/lib/plugin-base/src/debug/AndroidManifest.xml +++ b/lib/plugin-base/src/debug/AndroidManifest.xml @@ -2,10 +2,11 @@ + + + + - @@ -16,10 +17,8 @@ android:exported="true" android:theme="@style/DeviceSettingsTheme" tools:targetApi="24"> - - + + From 9b0875a1dd4a57f129b50713a19d5d54b909cc91 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 3 Aug 2024 20:44:05 +0800 Subject: [PATCH 041/181] Migrate from `BuildConfig.VERSION_CODE` Split apks built by gradle would have same VERSION_CODE, causing issues with reproducible builds --- .../fcitx5/android/data/UserDataManager.kt | 8 +++++--- .../fcitx/fcitx5/android/utils/DeviceInfo.kt | 9 ++++++--- .../fcitx/fcitx5/android/utils/PackageInfo.kt | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/utils/PackageInfo.kt diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/UserDataManager.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/UserDataManager.kt index c0c65ec39..c67b69965 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/UserDataManager.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/UserDataManager.kt @@ -14,6 +14,7 @@ import org.fcitx.fcitx5.android.utils.Const import org.fcitx.fcitx5.android.utils.appContext import org.fcitx.fcitx5.android.utils.errorRuntime import org.fcitx.fcitx5.android.utils.extract +import org.fcitx.fcitx5.android.utils.versionCodeCompat import org.fcitx.fcitx5.android.utils.withTempDir import timber.log.Timber import java.io.File @@ -30,7 +31,7 @@ object UserDataManager { @Serializable data class Metadata( val packageName: String, - val versionCode: Int, + val versionCode: Long, val versionName: String, val exportTime: Long ) @@ -68,9 +69,10 @@ object UserDataManager { writeFileTree(recentlyUsedDir, "recently_used", zipStream) // metadata zipStream.putNextEntry(ZipEntry("metadata.json")) + val pkgInfo = appContext.packageManager.getPackageInfo(appContext.packageName, 0) val metadata = Metadata( - BuildConfig.APPLICATION_ID, - BuildConfig.VERSION_CODE, + pkgInfo.packageName, + pkgInfo.versionCodeCompat, Const.versionName, timestamp ) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/DeviceInfo.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/DeviceInfo.kt index b1a9099a4..d393a89e2 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/utils/DeviceInfo.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/utils/DeviceInfo.kt @@ -33,10 +33,13 @@ object DeviceInfo { } }" ) + appendLine("--------- Package Info") + val pkgInfo = context.packageManager.getPackageInfo(context.packageName, 0) + appendLine("Package Name: ${pkgInfo.packageName}") + appendLine("Version Code: ${pkgInfo.versionCodeCompat}") + appendLine("Version Name: ${pkgInfo.versionName}") appendLine("--------- Build Info") - appendLine("Package Name: ${BuildConfig.APPLICATION_ID}") - appendLine("Version Code: ${BuildConfig.VERSION_CODE}") - appendLine("Version Name: ${Const.versionName}") + appendLine("Build Type: ${BuildConfig.BUILD_TYPE}") appendLine("Build Time: ${iso8601UTCDateTime(BuildConfig.BUILD_TIME)}") appendLine("Build Git Hash: ${BuildConfig.BUILD_GIT_HASH}") } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/PackageInfo.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/PackageInfo.kt new file mode 100644 index 000000000..4ec6c4e93 --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/utils/PackageInfo.kt @@ -0,0 +1,17 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.utils + +import android.content.pm.PackageInfo +import android.os.Build + +val PackageInfo.versionCodeCompat: Long + get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + longVersionCode + } else { + @Suppress("DEPRECATION") + versionCode.toLong() + } From 15cf2d64e412f09203b2b3d5ade2aa1d82608fd2 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 3 Aug 2024 23:47:18 +0800 Subject: [PATCH 042/181] Delete temp file produced by base64 signing key on exit --- .../convention/src/main/kotlin/ProjectExtensions.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build-logic/convention/src/main/kotlin/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/ProjectExtensions.kt index 6d539d8a4..eabefc9d2 100644 --- a/build-logic/convention/src/main/kotlin/ProjectExtensions.kt +++ b/build-logic/convention/src/main/kotlin/ProjectExtensions.kt @@ -62,6 +62,8 @@ val Project.signKeyBase64: String? val Project.signKeyFile: String? get() = epn("SIGN_KEY_FILE", "signKeyFile") +private var signKeyTempFile: File? = null + val Project.signKey: File? get() { signKeyFile?.let { @@ -70,11 +72,16 @@ val Project.signKey: File? } @OptIn(ExperimentalEncodingApi::class) signKeyBase64?.let { + if (signKeyTempFile?.exists() == true) { + return signKeyTempFile + } val buildDir = layout.buildDirectory.asFile.get() buildDir.mkdirs() val file = File.createTempFile("sign-", ".ks", buildDir) try { file.writeBytes(Base64.decode(it)) + file.deleteOnExit() + signKeyTempFile = file return file } catch (e: Exception) { println(e.localizedMessage ?: e.stackTraceToString()) From ebbff8bf66706d02181669011bbc65f6c66f002d Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 3 Aug 2024 23:50:06 +0800 Subject: [PATCH 043/181] Exclude tables that nobody would use --- app/build.gradle.kts | 4 ++++ .../src/main/kotlin/AndroidAppConventionPlugin.kt | 6 ++++-- .../src/main/kotlin/FcitxComponentPlugin.kt | 14 ++++++++++++++ lib/fcitx5/src/main/cpp/prebuilt | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e54b4a83f..9471fe788 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -78,6 +78,10 @@ fcitxComponent { "libime", "fcitx5-chinese-addons" ) + // exclude (delete immediately after install) tables that nobody would use + excludeFiles = listOf("cangjie", "erbi", "qxm", "wanfeng").map { + "usr/share/fcitx5/inputmethod/$it.conf" + } } ksp { diff --git a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt index c54bd9c57..b054c99bd 100644 --- a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt @@ -133,8 +133,10 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { // Make data descriptor depend on fcitx component if have // Since we are using finalizeDsl, there is no need to do afterEvaluate finalizeDsl { - target.tasks.findByName(FcitxComponentPlugin.INSTALL_TASK)?.also { componentTask -> - target.tasks.findByName(DataDescriptorPlugin.TASK)?.dependsOn(componentTask) + target.tasks.findByName(DataDescriptorPlugin.TASK)?.also { dataDescriptorTask -> + FcitxComponentPlugin.DEPENDENT_TASKS.forEach { componentTask -> + dataDescriptorTask.dependsOn(componentTask) + } } // applicationId is not set upon apply it.defaultConfig { diff --git a/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt b/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt index 665fb24f0..d0a71edb2 100644 --- a/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt +++ b/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt @@ -19,11 +19,15 @@ class FcitxComponentPlugin : Plugin { abstract class FcitxComponentExtension { var installLibraries: List = emptyList() + var excludeFiles: List = emptyList() } companion object { const val INSTALL_TASK = "installFcitxComponent" + const val DELETE_TASK = "deleteFcitxComponentExcludeFiles" const val CLEAN_TASK = "cleanFcitxComponents" + + val DEPENDENT_TASKS = arrayOf(INSTALL_TASK, DELETE_TASK) } override fun apply(target: Project) { @@ -38,6 +42,16 @@ class FcitxComponentPlugin : Plugin { registerCMakeTask(target, "generate-desktop-file", "config", project) registerCMakeTask(target, "translation-file", "translation", project) } + if (ext.excludeFiles.isNotEmpty()) { + target.task(DELETE_TASK) { + dependsOn(target.tasks.findByName(INSTALL_TASK)) + doLast { + ext.excludeFiles.forEach { + project.assetsDir.resolve(it).delete() + } + } + } + } } } diff --git a/lib/fcitx5/src/main/cpp/prebuilt b/lib/fcitx5/src/main/cpp/prebuilt index 870970f8a..6fa5fc926 160000 --- a/lib/fcitx5/src/main/cpp/prebuilt +++ b/lib/fcitx5/src/main/cpp/prebuilt @@ -1 +1 @@ -Subproject commit 870970f8ac4ea6e2c9639200448e0005d0c89c14 +Subproject commit 6fa5fc9260bbe860bcfc06f6e3f1d28327118c89 From 4bf7ed38c7f02cead0e29ab74fb30c53b357e59c Mon Sep 17 00:00:00 2001 From: Rocka Date: Sun, 4 Aug 2024 00:47:46 +0800 Subject: [PATCH 044/181] Fix build plugins --- .../src/main/kotlin/AndroidAppConventionPlugin.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt index b054c99bd..b733af15f 100644 --- a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt @@ -134,9 +134,9 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { // Since we are using finalizeDsl, there is no need to do afterEvaluate finalizeDsl { target.tasks.findByName(DataDescriptorPlugin.TASK)?.also { dataDescriptorTask -> - FcitxComponentPlugin.DEPENDENT_TASKS.forEach { componentTask -> - dataDescriptorTask.dependsOn(componentTask) - } + FcitxComponentPlugin.DEPENDENT_TASKS + .mapNotNull { taskName -> target.tasks.findByName(taskName) } + .forEach { componentTask -> dataDescriptorTask.dependsOn(componentTask) } } // applicationId is not set upon apply it.defaultConfig { From 8b4299ed8520c0856c84bd8b6015a6686806ac3f Mon Sep 17 00:00:00 2001 From: ketal <41381927+keta1@users.noreply.github.com> Date: Sat, 10 Aug 2024 01:20:13 +0800 Subject: [PATCH 045/181] Upgrade to kotlin 2.0 (#555) Co-authored-by: Rocka --- .gitignore | 3 +++ build-logic/convention/build.gradle.kts | 5 +++++ .../main/kotlin/AndroidAppConventionPlugin.kt | 17 ++--------------- .../main/kotlin/AndroidBaseConventionPlugin.kt | 5 +++-- .../src/main/kotlin/ProjectExtensions.kt | 9 ++++----- gradle/libs.versions.toml | 14 +++++++------- lib/common/build.gradle.kts | 6 ------ lib/plugin-base/build.gradle.kts | 1 + plugin/thai/build.gradle.kts | 3 +-- 9 files changed, 26 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 7538326e1..b19b889f6 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,6 @@ lint/tmp/ # Android Profiling *.hprof + +### Kotlin ### +.kotlin/ diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index bd9d5a608..d27cfdbf5 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -1,3 +1,5 @@ +import org.gradle.accessors.dm.LibrariesForLibs + plugins { `kotlin-dsl` kotlin("plugin.serialization") version embeddedKotlinVersion @@ -20,6 +22,9 @@ dependencies { compileOnly(libs.kotlin.gradlePlugin) compileOnly(libs.aboutlibraries.plugin) implementation(libs.kotlinx.serialization.json) + // A workaround to enable version catalog usage in the convention plugin, + // see https://github.com/gradle/gradle/issues/15383#issuecomment-779893192 + implementation(files(LibrariesForLibs::class.java.protectionDomain.codeSource.location)) } gradlePlugin { diff --git a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt index b733af15f..97e476207 100644 --- a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt @@ -145,13 +145,7 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { } } - runCatching { - target.pluginManager.apply( - target.versionCatalog.findPlugin("aboutlibraries").get().get().pluginId - ) - }.onFailure { - it.printStackTrace() - } + target.pluginManager.apply(target.libs.plugins.aboutlibraries.get().pluginId) target.configure { excludeFields = arrayOf( @@ -162,14 +156,7 @@ class AndroidAppConventionPlugin : AndroidBaseConventionPlugin() { includePlatform = false } - runCatching { - target.dependencies.add( - "coreLibraryDesugaring", - target.versionCatalog.findLibrary("android.desugarJDKLibs").get() - ) - }.onFailure { - it.printStackTrace() - } + target.dependencies.add("coreLibraryDesugaring", target.libs.android.desugarJDKLibs) } } diff --git a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt index 79af36ae7..7083c5d2f 100644 --- a/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidBaseConventionPlugin.kt @@ -7,6 +7,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -28,9 +29,9 @@ open class AndroidBaseConventionPlugin : Plugin { } target.tasks.withType { - kotlinOptions { + compilerOptions { // https://youtrack.jetbrains.com/issue/KT-55947 - jvmTarget = Versions.java.toString() + jvmTarget.set(JvmTarget.fromTarget(Versions.java.toString())) } } diff --git a/build-logic/convention/src/main/kotlin/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/ProjectExtensions.kt index eabefc9d2..b47c08ea4 100644 --- a/build-logic/convention/src/main/kotlin/ProjectExtensions.kt +++ b/build-logic/convention/src/main/kotlin/ProjectExtensions.kt @@ -2,11 +2,11 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors */ + +import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.Project import org.gradle.api.Task -import org.gradle.api.artifacts.VersionCatalog -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.the import java.io.ByteArrayOutputStream import java.io.File import kotlin.io.encoding.Base64 @@ -20,8 +20,7 @@ fun Project.runCmd(cmd: String): String = ByteArrayOutputStream().use { it.toString().trim() } -val Project.versionCatalog: VersionCatalog - get() = extensions.getByType().named("libs") +val Project.libs get() = the() val Project.assetsDir: File get() = file("src/main/assets").also { it.mkdirs() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4d4273617..65a573d2b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -androidGradlePlugin = "8.5.1" +androidGradlePlugin = "8.5.2" androidDesugarJDKLibs = "2.0.4" -kotlin = "1.9.24" -ksp = "1.9.24-1.0.20" -lifecycle = "2.8.3" +kotlin = "2.0.10" +ksp = "2.0.10-1.0.24" +lifecycle = "2.8.4" navigation = "2.7.7" room = "2.6.1" splitties = "3.0.0" @@ -15,7 +15,7 @@ android-desugarJDKLibs = { group = "com.android.tools", name = "desugar_jdk_libs kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version = "1.8.1" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } -androidx-activity = { module = "androidx.activity:activity-ktx", version = "1.9.0" } +androidx-activity = { module = "androidx.activity:activity-ktx", version = "1.9.1" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" } androidx-autofill = { module = "androidx.autofill:autofill", version = "1.1.0" } androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.4" } @@ -28,7 +28,7 @@ androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common-java androidx-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle" } androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" } androidx-navigation-ui = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" } -androidx-paging = { module = "androidx.paging:paging-runtime-ktx", version = "3.3.0" } +androidx-paging = { module = "androidx.paging:paging-runtime-ktx", version = "3.3.2" } androidx-preference = { module = "androidx.preference:preference-ktx", version = "1.2.1" } androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.3.2" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } @@ -58,7 +58,7 @@ junit = { module = "junit:junit", version = "4.13.2" } androidx-test-runner = { module = "androidx.test:runner", version = "1.6.1" } androidx-test-rules = { module = "androidx.test:rules", version = "1.6.1" } androidx-lifecycle-testing = { module = "androidx.lifecycle:lifecycle-runtime-testing", version.ref = "lifecycle" } -kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version = "1.18.0" } +kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version = "1.18.1" } ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } [plugins] diff --git a/lib/common/build.gradle.kts b/lib/common/build.gradle.kts index e19bc1ce0..8dd4ef441 100644 --- a/lib/common/build.gradle.kts +++ b/lib/common/build.gradle.kts @@ -7,12 +7,6 @@ plugins { android { namespace = "org.fcitx.fcitx5.android.lib.common" - buildTypes { - release { - isMinifyEnabled = false - } - } - buildFeatures { aidl = true } diff --git a/lib/plugin-base/build.gradle.kts b/lib/plugin-base/build.gradle.kts index 5b69f7abb..621536a7c 100644 --- a/lib/plugin-base/build.gradle.kts +++ b/lib/plugin-base/build.gradle.kts @@ -14,6 +14,7 @@ android { } } publishing { + // :lib:plugin_base contains different AndroidManifest.xml for debug and release variant multipleVariants { allVariants() } } } diff --git a/plugin/thai/build.gradle.kts b/plugin/thai/build.gradle.kts index 16896cefc..62b07b001 100644 --- a/plugin/thai/build.gradle.kts +++ b/plugin/thai/build.gradle.kts @@ -1,5 +1,3 @@ -@file:Suppress("UnstableApiUsage") - plugins { id("org.fcitx.fcitx5.android.app-convention") id("org.fcitx.fcitx5.android.plugin-app-convention") @@ -15,6 +13,7 @@ android { defaultConfig { applicationId = "org.fcitx.fcitx5.android.plugin.thai" + @Suppress("UnstableApiUsage") externalNativeBuild { cmake { targets( From 000963863b7255b1b0eadfa20c63fa53de36ce4e Mon Sep 17 00:00:00 2001 From: WhiredPlanck Date: Sun, 11 Aug 2024 23:55:46 +0800 Subject: [PATCH 046/181] Simplify creating Preference with enum class (#528) Co-authored-by: Potato Hatsue <1793913507@qq.com> Co-authored-by: Rocka --- .../fcitx5/android/data/InputFeedbacks.kt | 13 +-- .../fcitx5/android/data/prefs/AppPrefs.kt | 105 +++--------------- .../data/prefs/ManagedPreferenceCategory.kt | 15 +++ .../data/prefs/ManagedPreferenceEnum.kt | 13 +++ .../fcitx5/android/data/theme/ThemePrefs.kt | 51 ++------- .../candidates/HorizontalCandidateMode.kt | 17 ++- .../expanded/ExpandedCandidateStyle.kt | 15 +-- .../input/keyboard/LangSwitchBehavior.kt | 15 +-- .../input/keyboard/SpaceLongPressBehavior.kt | 17 ++- .../input/keyboard/SwipeSymbolDirection.kt | 15 +-- 10 files changed, 92 insertions(+), 184 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/data/prefs/ManagedPreferenceEnum.kt diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt index 701cfba6f..3c2af962b 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/InputFeedbacks.kt @@ -10,8 +10,9 @@ import android.os.VibrationEffect import android.provider.Settings import android.view.HapticFeedbackConstants import android.view.View +import org.fcitx.fcitx5.android.R import org.fcitx.fcitx5.android.data.prefs.AppPrefs -import org.fcitx.fcitx5.android.data.prefs.ManagedPreference +import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum import org.fcitx.fcitx5.android.utils.appContext import org.fcitx.fcitx5.android.utils.audioManager import org.fcitx.fcitx5.android.utils.getSystemSettings @@ -19,12 +20,10 @@ import org.fcitx.fcitx5.android.utils.vibrator object InputFeedbacks { - enum class InputFeedbackMode { - Enabled, Disabled, FollowingSystem; - - companion object : ManagedPreference.StringLikeCodec { - override fun decode(raw: String) = InputFeedbackMode.valueOf(raw) - } + enum class InputFeedbackMode(override val stringRes: Int) : ManagedPreferenceEnum { + FollowingSystem(R.string.following_system_settings), + Enabled(R.string.enabled), + Disabled(R.string.disabled); } private var systemSoundEffects = false diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt index ab48383d4..ca7bbfbe5 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt @@ -46,21 +46,10 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) { inner class Keyboard : ManagedPreferenceCategory(R.string.keyboard, sharedPreferences) { val hapticOnKeyPress = - list( + enumList( R.string.button_haptic_feedback, "haptic_on_keypress", - InputFeedbackMode.FollowingSystem, - InputFeedbackMode, - listOf( - InputFeedbackMode.FollowingSystem, - InputFeedbackMode.Enabled, - InputFeedbackMode.Disabled - ), - listOf( - R.string.following_system_settings, - R.string.enabled, - R.string.disabled - ) + InputFeedbackMode.FollowingSystem ) val hapticOnKeyUp = switch( R.string.button_up_haptic_feedback, @@ -113,21 +102,10 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) { buttonLongPressVibrationAmplitude = secondary } - val soundOnKeyPress = list( + val soundOnKeyPress = enumList( R.string.button_sound, "sound_on_keypress", - InputFeedbackMode.FollowingSystem, - InputFeedbackMode, - listOf( - InputFeedbackMode.FollowingSystem, - InputFeedbackMode.Enabled, - InputFeedbackMode.Disabled - ), - listOf( - R.string.following_system_settings, - R.string.enabled, - R.string.disabled - ) + InputFeedbackMode.FollowingSystem ) val soundOnKeyPressVolume = int( R.string.button_sound_volume, @@ -157,21 +135,10 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) { switch(R.string.show_voice_input_button, "show_voice_input_button", false) val expandKeypressArea = switch(R.string.expand_keypress_area, "expand_keypress_area", false) - val swipeSymbolDirection = list( + val swipeSymbolDirection = enumList( R.string.swipe_symbol_behavior, "swipe_symbol_behavior", - SwipeSymbolDirection.Down, - SwipeSymbolDirection, - listOf( - SwipeSymbolDirection.Up, - SwipeSymbolDirection.Down, - SwipeSymbolDirection.Disabled - ), - listOf( - R.string.swipe_up, - R.string.swipe_down, - R.string.disabled - ) + SwipeSymbolDirection.Down ) val longPressDelay = int( R.string.keyboard_long_press_delay, @@ -182,43 +149,19 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) { "ms", 10 ) - val spaceKeyLongPressBehavior = list( + val spaceKeyLongPressBehavior = enumList( R.string.space_long_press_behavior, "space_long_press_behavior", - SpaceLongPressBehavior.None, - SpaceLongPressBehavior, - listOf( - SpaceLongPressBehavior.None, - SpaceLongPressBehavior.Enumerate, - SpaceLongPressBehavior.ToggleActivate, - SpaceLongPressBehavior.ShowPicker - ), - listOf( - R.string.space_behavior_none, - R.string.space_behavior_enumerate, - R.string.space_behavior_activate, - R.string.space_behavior_picker - ) + SpaceLongPressBehavior.None ) val spaceSwipeMoveCursor = switch(R.string.space_swipe_move_cursor, "space_swipe_move_cursor", true) val showLangSwitchKey = switch(R.string.show_lang_switch_key, "show_lang_switch_key", true) - val langSwitchKeyBehavior = list( + val langSwitchKeyBehavior = enumList( R.string.lang_switch_key_behavior, "lang_switch_key_behavior", - LangSwitchBehavior.Enumerate, - LangSwitchBehavior, - listOf( - LangSwitchBehavior.Enumerate, - LangSwitchBehavior.ToggleActivate, - LangSwitchBehavior.NextInputMethodApp - ), - listOf( - R.string.space_behavior_enumerate, - R.string.space_behavior_activate, - R.string.lang_switch_behavior_next_ime_app - ) + LangSwitchBehavior.Enumerate ) { showLangSwitchKey.getValue() } val keyboardHeightPercent: ManagedPreference.PInt @@ -281,35 +224,15 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) { keyboardBottomPaddingLandscape = secondary } - val horizontalCandidateStyle = list( + val horizontalCandidateStyle = enumList( R.string.horizontal_candidate_style, "horizontal_candidate_style", - HorizontalCandidateMode.AutoFillWidth, - HorizontalCandidateMode, - listOf( - HorizontalCandidateMode.NeverFillWidth, - HorizontalCandidateMode.AutoFillWidth, - HorizontalCandidateMode.AlwaysFillWidth, - ), - listOf( - R.string.horizontal_candidate_never_fill, - R.string.horizontal_candidate_auto_fill, - R.string.horizontal_candidate_always_fill - ) + HorizontalCandidateMode.AutoFillWidth ) - val expandedCandidateStyle = list( + val expandedCandidateStyle = enumList( R.string.expanded_candidate_style, "expanded_candidate_style", - ExpandedCandidateStyle.Grid, - ExpandedCandidateStyle, - listOf( - ExpandedCandidateStyle.Grid, - ExpandedCandidateStyle.Flexbox - ), - listOf( - R.string.expanded_candidate_style_grid, - R.string.expanded_candidate_style_flexbox - ) + ExpandedCandidateStyle.Grid ) val expandedCandidateGridSpanCount: ManagedPreference.PInt diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/ManagedPreferenceCategory.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/ManagedPreferenceCategory.kt index 8cd5fdaa2..1c8a84994 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/ManagedPreferenceCategory.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/ManagedPreferenceCategory.kt @@ -49,6 +49,21 @@ abstract class ManagedPreferenceCategory( return pref } + protected inline fun enumList( + @StringRes + title: Int, + key: String, + defaultValue: T, + noinline enableUiOn: (() -> Boolean)? = null + ): ManagedPreference.PStringLike where T : Enum, T : ManagedPreferenceEnum { + val codec = object : ManagedPreference.StringLikeCodec { + override fun decode(raw: String): T = enumValueOf(raw) + } + val entryValues = enumValues().toList() + val entryLabels = entryValues.map { it.stringRes } + return list(title, key, defaultValue, codec, entryValues, entryLabels, enableUiOn) + } + protected fun int( @StringRes title: Int, diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/ManagedPreferenceEnum.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/ManagedPreferenceEnum.kt new file mode 100644 index 000000000..a67e8407a --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/prefs/ManagedPreferenceEnum.kt @@ -0,0 +1,13 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.data.prefs + +import androidx.annotation.StringRes + +interface ManagedPreferenceEnum { + @get:StringRes + val stringRes: Int +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt b/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt index 3e5e3b3a3..5129fdbb1 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/data/theme/ThemePrefs.kt @@ -10,6 +10,7 @@ import androidx.annotation.StringRes import org.fcitx.fcitx5.android.R import org.fcitx.fcitx5.android.data.prefs.ManagedPreference import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceCategory +import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum class ThemePrefs(sharedPreferences: SharedPreferences) : ManagedPreferenceCategory(R.string.theme, sharedPreferences) { @@ -82,55 +83,27 @@ class ThemePrefs(sharedPreferences: SharedPreferences) : val clipboardEntryRadius = int(R.string.clipboard_entry_radius, "clipboard_entry_radius", 2, 0, 48, "dp") - enum class PunctuationPosition { - Bottom, - TopRight; - - companion object : ManagedPreference.StringLikeCodec { - override fun decode(raw: String): PunctuationPosition = valueOf(raw) - } + enum class PunctuationPosition(override val stringRes: Int) : ManagedPreferenceEnum { + Bottom(R.string.punctuation_pos_bottom), + TopRight(R.string.punctuation_pos_top_right); } - val punctuationPosition = list( + val punctuationPosition = enumList( R.string.punctuation_position, "punctuation_position", - PunctuationPosition.Bottom, - PunctuationPosition, - listOf( - PunctuationPosition.Bottom, - PunctuationPosition.TopRight - ), - listOf( - R.string.punctuation_pos_bottom, - R.string.punctuation_pos_top_right - ) + PunctuationPosition.Bottom ) - enum class NavbarBackground { - None, - ColorOnly, - Full; - - companion object : ManagedPreference.StringLikeCodec { - override fun decode(raw: String): NavbarBackground = valueOf(raw) - } + enum class NavbarBackground(override val stringRes: Int) : ManagedPreferenceEnum { + None(R.string.navbar_bkg_none), + ColorOnly(R.string.navbar_bkg_color_only), + Full(R.string.navbar_bkg_full); } - val navbarBackground = list( + val navbarBackground = enumList( R.string.navbar_background, "navbar_background", - NavbarBackground.ColorOnly, - NavbarBackground, - listOf( - NavbarBackground.None, - NavbarBackground.ColorOnly, - NavbarBackground.Full - ), - listOf( - R.string.navbar_bkg_none, - R.string.navbar_bkg_color_only, - R.string.navbar_bkg_full - ) + NavbarBackground.ColorOnly ) /** diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateMode.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateMode.kt index cd17a3308..b395d708a 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateMode.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/HorizontalCandidateMode.kt @@ -4,14 +4,11 @@ */ package org.fcitx.fcitx5.android.input.candidates -import org.fcitx.fcitx5.android.data.prefs.ManagedPreference +import org.fcitx.fcitx5.android.R +import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum -enum class HorizontalCandidateMode { - NeverFillWidth, - AutoFillWidth, - AlwaysFillWidth; - - companion object : ManagedPreference.StringLikeCodec { - override fun decode(raw: String): HorizontalCandidateMode = valueOf(raw) - } -} \ No newline at end of file +enum class HorizontalCandidateMode(override val stringRes: Int) : ManagedPreferenceEnum { + NeverFillWidth(R.string.horizontal_candidate_never_fill), + AutoFillWidth(R.string.horizontal_candidate_auto_fill), + AlwaysFillWidth(R.string.horizontal_candidate_always_fill); +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/ExpandedCandidateStyle.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/ExpandedCandidateStyle.kt index cb125c2f2..65e6cd2e5 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/ExpandedCandidateStyle.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/expanded/ExpandedCandidateStyle.kt @@ -4,13 +4,10 @@ */ package org.fcitx.fcitx5.android.input.candidates.expanded -import org.fcitx.fcitx5.android.data.prefs.ManagedPreference +import org.fcitx.fcitx5.android.R +import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum -enum class ExpandedCandidateStyle { - Grid, - Flexbox; - - companion object : ManagedPreference.StringLikeCodec { - override fun decode(raw: String): ExpandedCandidateStyle = valueOf(raw) - } -} \ No newline at end of file +enum class ExpandedCandidateStyle(override val stringRes: Int) : ManagedPreferenceEnum { + Grid(R.string.expanded_candidate_style_grid), + Flexbox(R.string.expanded_candidate_style_flexbox); +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/LangSwitchBehavior.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/LangSwitchBehavior.kt index ef743669a..d4b905471 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/LangSwitchBehavior.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/LangSwitchBehavior.kt @@ -4,14 +4,11 @@ */ package org.fcitx.fcitx5.android.input.keyboard -import org.fcitx.fcitx5.android.data.prefs.ManagedPreference +import org.fcitx.fcitx5.android.R +import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum -enum class LangSwitchBehavior { - Enumerate, - ToggleActivate, - NextInputMethodApp; - - companion object : ManagedPreference.StringLikeCodec { - override fun decode(raw: String): LangSwitchBehavior = valueOf(raw) - } +enum class LangSwitchBehavior(override val stringRes: Int) : ManagedPreferenceEnum { + Enumerate(R.string.space_behavior_enumerate), + ToggleActivate(R.string.space_behavior_activate), + NextInputMethodApp(R.string.lang_switch_behavior_next_ime_app); } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/SpaceLongPressBehavior.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/SpaceLongPressBehavior.kt index 116053a59..adea1fce6 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/SpaceLongPressBehavior.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/SpaceLongPressBehavior.kt @@ -4,15 +4,12 @@ */ package org.fcitx.fcitx5.android.input.keyboard -import org.fcitx.fcitx5.android.data.prefs.ManagedPreference +import org.fcitx.fcitx5.android.R +import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum -enum class SpaceLongPressBehavior { - None, - Enumerate, - ToggleActivate, - ShowPicker; - - companion object : ManagedPreference.StringLikeCodec { - override fun decode(raw: String): SpaceLongPressBehavior = valueOf(raw) - } +enum class SpaceLongPressBehavior(override val stringRes: Int) : ManagedPreferenceEnum { + None(R.string.space_behavior_none), + Enumerate(R.string.space_behavior_enumerate), + ToggleActivate(R.string.space_behavior_activate), + ShowPicker(R.string.space_behavior_picker); } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/SwipeSymbolDirection.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/SwipeSymbolDirection.kt index ed22ba4f7..7ccf2315f 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/SwipeSymbolDirection.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/SwipeSymbolDirection.kt @@ -4,17 +4,14 @@ */ package org.fcitx.fcitx5.android.input.keyboard -import org.fcitx.fcitx5.android.data.prefs.ManagedPreference +import org.fcitx.fcitx5.android.R +import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum -enum class SwipeSymbolDirection { - Up, - Down, - Disabled; +enum class SwipeSymbolDirection(override val stringRes: Int): ManagedPreferenceEnum { + Up(R.string.swipe_up), + Down(R.string.swipe_down), + Disabled(R.string.disabled); fun checkY(totalY: Int): Boolean = (this != Disabled) && (totalY != 0) && ((totalY > 0) == (this == Down)) - - companion object : ManagedPreference.StringLikeCodec { - override fun decode(raw: String): SwipeSymbolDirection = valueOf(raw) - } } From fbf5f799d713500b538f83fe01397e34760dcfaa Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 14 Aug 2024 23:24:25 +0800 Subject: [PATCH 047/181] Fix first backspace swipe after initialization --- .../fcitx5/android/input/keyboard/CommonKeyActionListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CommonKeyActionListener.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CommonKeyActionListener.kt index 36dfff199..9f16bee78 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CommonKeyActionListener.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/CommonKeyActionListener.kt @@ -137,7 +137,7 @@ class CommonKeyActionListener : Stopped -> { backspaceSwipeState = if ( preeditState.isEmpty && - horizontalCandidate.adapter.total == 0 + horizontalCandidate.adapter.total <= 0 // total is -1 on initialization ) { service.applySelectionOffset(action.start, action.end) Selection From 99b9452c58960e3919b5d9dfafb642033942feca Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 15 Aug 2024 00:41:51 +0800 Subject: [PATCH 048/181] Add Android key code to scancode mapping Maybe able to fix focus problems when moving cursor by space swipe --- .../android/input/FcitxInputMethodService.kt | 5 +- .../android/codegen/GenScancodeMapping.kt | 534 +++++++++--------- 2 files changed, 282 insertions(+), 257 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt index e16752360..ab25ab90e 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt @@ -49,6 +49,7 @@ import org.fcitx.fcitx5.android.core.FcitxEvent import org.fcitx.fcitx5.android.core.FormattedText import org.fcitx.fcitx5.android.core.KeyStates import org.fcitx.fcitx5.android.core.KeySym +import org.fcitx.fcitx5.android.core.ScancodeMapping import org.fcitx.fcitx5.android.core.SubtypeManager import org.fcitx.fcitx5.android.daemon.FcitxConnection import org.fcitx.fcitx5.android.daemon.FcitxDaemon @@ -333,7 +334,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, - 0, + ScancodeMapping.keyCodeToScancode(keyEventCode), KeyEvent.FLAG_SOFT_KEYBOARD or KeyEvent.FLAG_KEEP_TOUCH_MODE ) ) @@ -349,7 +350,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, - 0, + ScancodeMapping.keyCodeToScancode(keyEventCode), KeyEvent.FLAG_SOFT_KEYBOARD or KeyEvent.FLAG_KEEP_TOUCH_MODE ) ) diff --git a/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenScancodeMapping.kt b/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenScancodeMapping.kt index 650639d66..c03432400 100644 --- a/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenScancodeMapping.kt +++ b/codegen/src/main/java/org/fcitx/fcitx5/android/codegen/GenScancodeMapping.kt @@ -20,277 +20,280 @@ import com.squareup.kotlinpoet.ksp.writeTo internal class GenScancodeMappingProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor { + data class Scancode(val name: String, val value: Int) + data class Mapping(val scancode: Scancode, val androidKey: String) + companion object { // https://github.com/torvalds/linux/blob/v6.10/include/uapi/linux/input-event-codes.h#L75 - private val SCANCODE_MAP: List> = listOf( - "KEY_RESERVED" to 0, - "KEY_ESC" to 1, - "KEY_1" to 2, - "KEY_2" to 3, - "KEY_3" to 4, - "KEY_4" to 5, - "KEY_5" to 6, - "KEY_6" to 7, - "KEY_7" to 8, - "KEY_8" to 9, - "KEY_9" to 10, - "KEY_0" to 11, - "KEY_MINUS" to 12, - "KEY_EQUAL" to 13, - "KEY_BACKSPACE" to 14, - "KEY_TAB" to 15, - "KEY_Q" to 16, - "KEY_W" to 17, - "KEY_E" to 18, - "KEY_R" to 19, - "KEY_T" to 20, - "KEY_Y" to 21, - "KEY_U" to 22, - "KEY_I" to 23, - "KEY_O" to 24, - "KEY_P" to 25, - "KEY_LEFTBRACE" to 26, - "KEY_RIGHTBRACE" to 27, - "KEY_ENTER" to 28, - "KEY_LEFTCTRL" to 29, - "KEY_A" to 30, - "KEY_S" to 31, - "KEY_D" to 32, - "KEY_F" to 33, - "KEY_G" to 34, - "KEY_H" to 35, - "KEY_J" to 36, - "KEY_K" to 37, - "KEY_L" to 38, - "KEY_SEMICOLON" to 39, - "KEY_APOSTROPHE" to 40, - "KEY_GRAVE" to 41, - "KEY_LEFTSHIFT" to 42, - "KEY_BACKSLASH" to 43, - "KEY_Z" to 44, - "KEY_X" to 45, - "KEY_C" to 46, - "KEY_V" to 47, - "KEY_B" to 48, - "KEY_N" to 49, - "KEY_M" to 50, - "KEY_COMMA" to 51, - "KEY_DOT" to 52, - "KEY_SLASH" to 53, - "KEY_RIGHTSHIFT" to 54, - "KEY_KPASTERISK" to 55, - "KEY_LEFTALT" to 56, - "KEY_SPACE" to 57, - "KEY_CAPSLOCK" to 58, - "KEY_F1" to 59, - "KEY_F2" to 60, - "KEY_F3" to 61, - "KEY_F4" to 62, - "KEY_F5" to 63, - "KEY_F6" to 64, - "KEY_F7" to 65, - "KEY_F8" to 66, - "KEY_F9" to 67, - "KEY_F10" to 68, - "KEY_NUMLOCK" to 69, - "KEY_SCROLLLOCK" to 70, - "KEY_KP7" to 71, - "KEY_KP8" to 72, - "KEY_KP9" to 73, - "KEY_KPMINUS" to 74, - "KEY_KP4" to 75, - "KEY_KP5" to 76, - "KEY_KP6" to 77, - "KEY_KPPLUS" to 78, - "KEY_KP1" to 79, - "KEY_KP2" to 80, - "KEY_KP3" to 81, - "KEY_KP0" to 82, - "KEY_KPDOT" to 83, + private val SCANCODE_MAP = listOf( + "KEY_RESERVED" to 0 to "KEYCODE_UNKNOWN", + "KEY_ESC" to 1 to "KEYCODE_ESCAPE", + "KEY_1" to 2 to "KEYCODE_1", + "KEY_2" to 3 to "KEYCODE_2", + "KEY_3" to 4 to "KEYCODE_3", + "KEY_4" to 5 to "KEYCODE_4", + "KEY_5" to 6 to "KEYCODE_5", + "KEY_6" to 7 to "KEYCODE_6", + "KEY_7" to 8 to "KEYCODE_7", + "KEY_8" to 9 to "KEYCODE_8", + "KEY_9" to 10 to "KEYCODE_9", + "KEY_0" to 11 to "KEYCODE_0", + "KEY_MINUS" to 12 to "KEYCODE_MINUS", + "KEY_EQUAL" to 13 to "KEYCODE_EQUALS", + "KEY_BACKSPACE" to 14 to "KEYCODE_DEL", + "KEY_TAB" to 15 to "KEYCODE_TAB", + "KEY_Q" to 16 to "KEYCODE_Q", + "KEY_W" to 17 to "KEYCODE_W", + "KEY_E" to 18 to "KEYCODE_E", + "KEY_R" to 19 to "KEYCODE_R", + "KEY_T" to 20 to "KEYCODE_T", + "KEY_Y" to 21 to "KEYCODE_Y", + "KEY_U" to 22 to "KEYCODE_U", + "KEY_I" to 23 to "KEYCODE_I", + "KEY_O" to 24 to "KEYCODE_O", + "KEY_P" to 25 to "KEYCODE_P", + "KEY_LEFTBRACE" to 26 to "KEYCODE_LEFT_BRACKET", + "KEY_RIGHTBRACE" to 27 to "KEYCODE_RIGHT_BRACKET", + "KEY_ENTER" to 28 to "KEYCODE_ENTER", + "KEY_LEFTCTRL" to 29 to "KEYCODE_CTRL_LEFT", + "KEY_A" to 30 to "KEYCODE_A", + "KEY_S" to 31 to "KEYCODE_S", + "KEY_D" to 32 to "KEYCODE_D", + "KEY_F" to 33 to "KEYCODE_F", + "KEY_G" to 34 to "KEYCODE_G", + "KEY_H" to 35 to "KEYCODE_H", + "KEY_J" to 36 to "KEYCODE_J", + "KEY_K" to 37 to "KEYCODE_K", + "KEY_L" to 38 to "KEYCODE_L", + "KEY_SEMICOLON" to 39 to "KEYCODE_SEMICOLON", + "KEY_APOSTROPHE" to 40 to "KEYCODE_APOSTROPHE", + "KEY_GRAVE" to 41 to "KEYCODE_GRAVE", + "KEY_LEFTSHIFT" to 42 to "KEYCODE_SHIFT_LEFT", + "KEY_BACKSLASH" to 43 to "KEYCODE_BACKSLASH", + "KEY_Z" to 44 to "KEYCODE_Z", + "KEY_X" to 45 to "KEYCODE_X", + "KEY_C" to 46 to "KEYCODE_C", + "KEY_V" to 47 to "KEYCODE_V", + "KEY_B" to 48 to "KEYCODE_B", + "KEY_N" to 49 to "KEYCODE_N", + "KEY_M" to 50 to "KEYCODE_M", + "KEY_COMMA" to 51 to "KEYCODE_COMMA", + "KEY_DOT" to 52 to "KEYCODE_PERIOD", + "KEY_SLASH" to 53 to "KEYCODE_SLASH", + "KEY_RIGHTSHIFT" to 54 to "KEYCODE_SHIFT_RIGHT", + "KEY_KPASTERISK" to 55 to "KEYCODE_NUMPAD_MULTIPLY", + "KEY_LEFTALT" to 56 to "KEYCODE_ALT_LEFT", + "KEY_SPACE" to 57 to "KEYCODE_SPACE", + "KEY_CAPSLOCK" to 58 to "KEYCODE_CAPS_LOCK", + "KEY_F1" to 59 to "KEYCODE_F1", + "KEY_F2" to 60 to "KEYCODE_F2", + "KEY_F3" to 61 to "KEYCODE_F3", + "KEY_F4" to 62 to "KEYCODE_F4", + "KEY_F5" to 63 to "KEYCODE_F5", + "KEY_F6" to 64 to "KEYCODE_F6", + "KEY_F7" to 65 to "KEYCODE_F7", + "KEY_F8" to 66 to "KEYCODE_F8", + "KEY_F9" to 67 to "KEYCODE_F9", + "KEY_F10" to 68 to "KEYCODE_F10", + "KEY_NUMLOCK" to 69 to "KEYCODE_NUM_LOCK", + "KEY_SCROLLLOCK" to 70 to "KEYCODE_SCROLL_LOCK", + "KEY_KP7" to 71 to "KEYCODE_NUMPAD_7", + "KEY_KP8" to 72 to "KEYCODE_NUMPAD_8", + "KEY_KP9" to 73 to "KEYCODE_NUMPAD_9", + "KEY_KPMINUS" to 74 to "KEYCODE_NUMPAD_SUBTRACT", + "KEY_KP4" to 75 to "KEYCODE_NUMPAD_4", + "KEY_KP5" to 76 to "KEYCODE_NUMPAD_5", + "KEY_KP6" to 77 to "KEYCODE_NUMPAD_6", + "KEY_KPPLUS" to 78 to "KEYCODE_NUMPAD_ADD", + "KEY_KP1" to 79 to "KEYCODE_NUMPAD_1", + "KEY_KP2" to 80 to "KEYCODE_NUMPAD_2", + "KEY_KP3" to 81 to "KEYCODE_NUMPAD_3", + "KEY_KP0" to 82 to "KEYCODE_NUMPAD_0", + "KEY_KPDOT" to 83 to "KEYCODE_NUMPAD_DOT", - "KEY_ZENKAKUHANKAKU" to 85, - "KEY_102ND" to 86, - "KEY_F11" to 87, - "KEY_F12" to 88, - "KEY_RO" to 89, - "KEY_KATAKANA" to 90, - "KEY_HIRAGANA" to 91, - "KEY_HENKAN" to 92, - "KEY_KATAKANAHIRAGANA" to 93, - "KEY_MUHENKAN" to 94, - "KEY_KPJPCOMMA" to 95, - "KEY_KPENTER" to 96, - "KEY_RIGHTCTRL" to 97, - "KEY_KPSLASH" to 98, - "KEY_SYSRQ" to 99, - "KEY_RIGHTALT" to 100, - "KEY_LINEFEED" to 101, - "KEY_HOME" to 102, - "KEY_UP" to 103, - "KEY_PAGEUP" to 104, - "KEY_LEFT" to 105, - "KEY_RIGHT" to 106, - "KEY_END" to 107, - "KEY_DOWN" to 108, - "KEY_PAGEDOWN" to 109, - "KEY_INSERT" to 110, - "KEY_DELETE" to 111, - "KEY_MACRO" to 112, - "KEY_MUTE" to 113, - "KEY_VOLUMEDOWN" to 114, - "KEY_VOLUMEUP" to 115, - "KEY_POWER" to 116, /* SC System Power Down */ - "KEY_KPEQUAL" to 117, - "KEY_KPPLUSMINUS" to 118, - "KEY_PAUSE" to 119, - "KEY_SCALE" to 120, /* AL Compiz Scale (Expose) */ + "KEY_ZENKAKUHANKAKU" to 85 to "KEYCODE_ZENKAKU_HANKAKU", + "KEY_102ND" to 86 to "", + "KEY_F11" to 87 to "KEYCODE_F11", + "KEY_F12" to 88 to "KEYCODE_F12", + "KEY_RO" to 89 to "KEYCODE_RO", + "KEY_KATAKANA" to 90 to "", + "KEY_HIRAGANA" to 91 to "", + "KEY_HENKAN" to 92 to "KEYCODE_HENKAN", + "KEY_KATAKANAHIRAGANA" to 93 to "KEYCODE_KATAKANA_HIRAGANA", + "KEY_MUHENKAN" to 94 to "KEYCODE_MUHENKAN", + "KEY_KPJPCOMMA" to 95 to "", + "KEY_KPENTER" to 96 to "KEYCODE_NUMPAD_ENTER", + "KEY_RIGHTCTRL" to 97 to "KEYCODE_CTRL_RIGHT", + "KEY_KPSLASH" to 98 to "KEYCODE_NUMPAD_DIVIDE", + "KEY_SYSRQ" to 99 to "KEYCODE_SYSRQ", + "KEY_RIGHTALT" to 100 to "KEYCODE_ALT_RIGHT", + "KEY_LINEFEED" to 101 to "", + "KEY_HOME" to 102 to "KEYCODE_MOVE_HOME", + "KEY_UP" to 103 to "KEYCODE_DPAD_UP", + "KEY_PAGEUP" to 104 to "KEYCODE_PAGE_UP", + "KEY_LEFT" to 105 to "KEYCODE_DPAD_LEFT", + "KEY_RIGHT" to 106 to "KEYCODE_DPAD_RIGHT", + "KEY_END" to 107 to "KEYCODE_MOVE_END", + "KEY_DOWN" to 108 to "KEYCODE_DPAD_DOWN", + "KEY_PAGEDOWN" to 109 to "KEYCODE_PAGE_DOWN", + "KEY_INSERT" to 110 to "KEYCODE_INSERT", + "KEY_DELETE" to 111 to "KEYCODE_FORWARD_DEL", + "KEY_MACRO" to 112 to "", + "KEY_MUTE" to 113 to "KEYCODE_VOLUME_MUTE", + "KEY_VOLUMEDOWN" to 114 to "KEYCODE_VOLUME_DOWN", + "KEY_VOLUMEUP" to 115 to "KEYCODE_VOLUME_UP", + "KEY_POWER" to 116 to "KEYCODE_POWER", /* SC System Power Down */ + "KEY_KPEQUAL" to 117 to "KEYCODE_NUMPAD_EQUALS", + "KEY_KPPLUSMINUS" to 118 to "", + "KEY_PAUSE" to 119 to "KEYCODE_BREAK", + "KEY_SCALE" to 120 to "", /* AL Compiz Scale (Expose) */ - "KEY_KPCOMMA" to 121, - "KEY_HANGEUL" to 122, - "KEY_HANGUEL" to 122, /* KEY_HANGUEL to KEY_HANGEUL */ - "KEY_HANJA" to 123, - "KEY_YEN" to 124, - "KEY_LEFTMETA" to 125, - "KEY_RIGHTMETA" to 126, - "KEY_COMPOSE" to 127, + "KEY_KPCOMMA" to 121 to "KEYCODE_NUMPAD_COMMA", + "KEY_HANGEUL" to 122 to "", + "KEY_HANGUEL" to 122 to "", /* KEY_HANGUEL to KEY_HANGEUL */ + "KEY_HANJA" to 123 to "", + "KEY_YEN" to 124 to "KEYCODE_YEN", + "KEY_LEFTMETA" to 125 to "KEYCODE_META_LEFT", + "KEY_RIGHTMETA" to 126 to "KEYCODE_META_RIGHT", + "KEY_COMPOSE" to 127 to "", - "KEY_STOP" to 128, /* AC Stop */ - "KEY_AGAIN" to 129, - "KEY_PROPS" to 130, /* AC Properties */ - "KEY_UNDO" to 131, /* AC Undo */ - "KEY_FRONT" to 132, - "KEY_COPY" to 133, /* AC Copy */ - "KEY_OPEN" to 134, /* AC Open */ - "KEY_PASTE" to 135, /* AC Paste */ - "KEY_FIND" to 136, /* AC Search */ - "KEY_CUT" to 137, /* AC Cut */ - "KEY_HELP" to 138, /* AL Integrated Help Center */ - "KEY_MENU" to 139, /* Menu (show menu) */ - "KEY_CALC" to 140, /* AL Calculator */ - "KEY_SETUP" to 141, - "KEY_SLEEP" to 142, /* SC System Sleep */ - "KEY_WAKEUP" to 143, /* System Wake Up */ - "KEY_FILE" to 144, /* AL Local Machine Browser */ - "KEY_SENDFILE" to 145, - "KEY_DELETEFILE" to 146, - "KEY_XFER" to 147, - "KEY_PROG1" to 148, - "KEY_PROG2" to 149, - "KEY_WWW" to 150, /* AL Internet Browser */ - "KEY_MSDOS" to 151, - "KEY_COFFEE" to 152, /* AL Terminal Lock/Screensaver */ - "KEY_SCREENLOCK" to 152, /* KEY_SCREENLOCK to KEY_COFFEE */ - "KEY_ROTATE_DISPLAY" to 153, /* Display orientation for e.g. tablets */ - "KEY_DIRECTION" to 153, /* KEY_DIRECTION KEY_ROTATE_DISPLAY */ - "KEY_CYCLEWINDOWS" to 154, - "KEY_MAIL" to 155, - "KEY_BOOKMARKS" to 156, /* AC Bookmarks */ - "KEY_COMPUTER" to 157, - "KEY_BACK" to 158, /* AC Back */ - "KEY_FORWARD" to 159, /* AC Forward */ - "KEY_CLOSECD" to 160, - "KEY_EJECTCD" to 161, - "KEY_EJECTCLOSECD" to 162, - "KEY_NEXTSONG" to 163, - "KEY_PLAYPAUSE" to 164, - "KEY_PREVIOUSSONG" to 165, - "KEY_STOPCD" to 166, - "KEY_RECORD" to 167, - "KEY_REWIND" to 168, - "KEY_PHONE" to 169, /* Media Select Telephone */ - "KEY_ISO" to 170, - "KEY_CONFIG" to 171, /* AL Consumer Control Configuration */ - "KEY_HOMEPAGE" to 172, /* AC Home */ - "KEY_REFRESH" to 173, /* AC Refresh */ - "KEY_EXIT" to 174, /* AC Exit */ - "KEY_MOVE" to 175, - "KEY_EDIT" to 176, - "KEY_SCROLLUP" to 177, - "KEY_SCROLLDOWN" to 178, - "KEY_KPLEFTPAREN" to 179, - "KEY_KPRIGHTPAREN" to 180, - "KEY_NEW" to 181, /* AC New */ - "KEY_REDO" to 182, /* AC Redo/Repeat */ + "KEY_STOP" to 128 to "", /* AC Stop */ + "KEY_AGAIN" to 129 to "", + "KEY_PROPS" to 130 to "", /* AC Properties */ + "KEY_UNDO" to 131 to "", /* AC Undo */ + "KEY_FRONT" to 132 to "", + "KEY_COPY" to 133 to "KEYCODE_COPY", /* AC Copy */ + "KEY_OPEN" to 134 to "", /* AC Open */ + "KEY_PASTE" to 135 to "KEYCODE_PASTE", /* AC Paste */ + "KEY_FIND" to 136 to "", /* AC Search */ + "KEY_CUT" to 137 to "KEYCODE_CUT", /* AC Cut */ + "KEY_HELP" to 138 to "KEYCODE_HELP", /* AL Integrated Help Center */ + "KEY_MENU" to 139 to "KEYCODE_MENU", /* Menu (show menu) */ + "KEY_CALC" to 140 to "KEYCODE_CALCULATOR", /* AL Calculator */ + "KEY_SETUP" to 141 to "", + "KEY_SLEEP" to 142 to "KEYCODE_SLEEP", /* SC System Sleep */ + "KEY_WAKEUP" to 143 to "KEYCODE_WAKEUP", /* System Wake Up */ + "KEY_FILE" to 144 to "", /* AL Local Machine Browser */ + "KEY_SENDFILE" to 145 to "", + "KEY_DELETEFILE" to 146 to "", + "KEY_XFER" to 147 to "", + "KEY_PROG1" to 148 to "", + "KEY_PROG2" to 149 to "", + "KEY_WWW" to 150 to "KEYCODE_EXPLORER", /* AL Internet Browser */ + "KEY_MSDOS" to 151 to "", + "KEY_COFFEE" to 152 to "", /* AL Terminal Lock/Screensaver */ + "KEY_SCREENLOCK" to 152 to "", /* KEY_SCREENLOCK to KEY_COFFEE */ + "KEY_ROTATE_DISPLAY" to 153 to "", /* Display orientation for e.g. tablets */ + "KEY_DIRECTION" to 153 to "", /* KEY_DIRECTION KEY_ROTATE_DISPLAY */ + "KEY_CYCLEWINDOWS" to 154 to "", + "KEY_MAIL" to 155 to "KEYCODE_ENVELOPE", + "KEY_BOOKMARKS" to 156 to "KEYCODE_BOOKMARK", /* AC Bookmarks */ + "KEY_COMPUTER" to 157 to "", + "KEY_BACK" to 158 to "", /* AC Back */ + "KEY_FORWARD" to 159 to "", /* AC Forward */ + "KEY_CLOSECD" to 160 to "", + "KEY_EJECTCD" to 161 to "", + "KEY_EJECTCLOSECD" to 162 to "", + "KEY_NEXTSONG" to 163 to "KEYCODE_MEDIA_NEXT", + "KEY_PLAYPAUSE" to 164 to "KEYCODE_MEDIA_PLAY_PAUSE", + "KEY_PREVIOUSSONG" to 165 to "KEYCODE_MEDIA_PREVIOUS", + "KEY_STOPCD" to 166 to "", + "KEY_RECORD" to 167 to "", + "KEY_REWIND" to 168 to "", + "KEY_PHONE" to 169 to "", /* Media Select Telephone */ + "KEY_ISO" to 170 to "", + "KEY_CONFIG" to 171 to "", /* AL Consumer Control Configuration */ + "KEY_HOMEPAGE" to 172 to "", /* AC Home */ + "KEY_REFRESH" to 173 to "KEYCODE_REFRESH", /* AC Refresh */ + "KEY_EXIT" to 174 to "", /* AC Exit */ + "KEY_MOVE" to 175 to "", + "KEY_EDIT" to 176 to "", + "KEY_SCROLLUP" to 177 to "", + "KEY_SCROLLDOWN" to 178 to "", + "KEY_KPLEFTPAREN" to 179 to "", + "KEY_KPRIGHTPAREN" to 180 to "", + "KEY_NEW" to 181 to "", /* AC New */ + "KEY_REDO" to 182 to "", /* AC Redo/Repeat */ - "KEY_F13" to 183, - "KEY_F14" to 184, - "KEY_F15" to 185, - "KEY_F16" to 186, - "KEY_F17" to 187, - "KEY_F18" to 188, - "KEY_F19" to 189, - "KEY_F20" to 190, - "KEY_F21" to 191, - "KEY_F22" to 192, - "KEY_F23" to 193, - "KEY_F24" to 194, + "KEY_F13" to 183 to "", + "KEY_F14" to 184 to "", + "KEY_F15" to 185 to "", + "KEY_F16" to 186 to "", + "KEY_F17" to 187 to "", + "KEY_F18" to 188 to "", + "KEY_F19" to 189 to "", + "KEY_F20" to 190 to "", + "KEY_F21" to 191 to "", + "KEY_F22" to 192 to "", + "KEY_F23" to 193 to "", + "KEY_F24" to 194 to "", - "KEY_PLAYCD" to 200, - "KEY_PAUSECD" to 201, - "KEY_PROG3" to 202, - "KEY_PROG4" to 203, - "KEY_ALL_APPLICATIONS" to 204, /* AC Desktop Show All Applications */ - "KEY_DASHBOARD" to 204, /* KEY_DASHBOARD to KEY_ALL_APPLICATIONS */ - "KEY_SUSPEND" to 205, - "KEY_CLOSE" to 206, /* AC Close */ - "KEY_PLAY" to 207, - "KEY_FASTFORWARD" to 208, - "KEY_BASSBOOST" to 209, - "KEY_PRINT" to 210, /* AC Print */ - "KEY_HP" to 211, - "KEY_CAMERA" to 212, - "KEY_SOUND" to 213, - "KEY_QUESTION" to 214, - "KEY_EMAIL" to 215, - "KEY_CHAT" to 216, - "KEY_SEARCH" to 217, - "KEY_CONNECT" to 218, - "KEY_FINANCE" to 219, /* AL Checkbook/Finance */ - "KEY_SPORT" to 220, - "KEY_SHOP" to 221, - "KEY_ALTERASE" to 222, - "KEY_CANCEL" to 223, /* AC Cancel */ - "KEY_BRIGHTNESSDOWN" to 224, - "KEY_BRIGHTNESSUP" to 225, - "KEY_MEDIA" to 226, + "KEY_PLAYCD" to 200 to "", + "KEY_PAUSECD" to 201 to "", + "KEY_PROG3" to 202 to "", + "KEY_PROG4" to 203 to "", + "KEY_ALL_APPLICATIONS" to 204 to "", /* AC Desktop Show All Applications */ + "KEY_DASHBOARD" to 204 to "", /* KEY_DASHBOARD to KEY_ALL_APPLICATIONS */ + "KEY_SUSPEND" to 205 to "", + "KEY_CLOSE" to 206 to "", /* AC Close */ + "KEY_PLAY" to 207 to "", + "KEY_FASTFORWARD" to 208 to "", + "KEY_BASSBOOST" to 209 to "", + "KEY_PRINT" to 210 to "", /* AC Print */ + "KEY_HP" to 211 to "", + "KEY_CAMERA" to 212 to "", + "KEY_SOUND" to 213 to "", + "KEY_QUESTION" to 214 to "", + "KEY_EMAIL" to 215 to "", + "KEY_CHAT" to 216 to "", + "KEY_SEARCH" to 217 to "KEYCODE_SEARCH", + "KEY_CONNECT" to 218 to "", + "KEY_FINANCE" to 219 to "", /* AL Checkbook/Finance */ + "KEY_SPORT" to 220 to "", + "KEY_SHOP" to 221 to "", + "KEY_ALTERASE" to 222 to "", + "KEY_CANCEL" to 223 to "", /* AC Cancel */ + "KEY_BRIGHTNESSDOWN" to 224 to "", + "KEY_BRIGHTNESSUP" to 225 to "", + "KEY_MEDIA" to 226 to "", - "KEY_SWITCHVIDEOMODE" to 227, + "KEY_SWITCHVIDEOMODE" to 227 to "", /* Cycle between available video outputs (Monitor/LCD/TV-out/etc) */ - "KEY_KBDILLUMTOGGLE" to 228, - "KEY_KBDILLUMDOWN" to 229, - "KEY_KBDILLUMUP" to 230, + "KEY_KBDILLUMTOGGLE" to 228 to "", + "KEY_KBDILLUMDOWN" to 229 to "", + "KEY_KBDILLUMUP" to 230 to "", - "KEY_SEND" to 231, /* AC Send */ - "KEY_REPLY" to 232, /* AC Reply */ - "KEY_FORWARDMAIL" to 233, /* AC Forward Msg */ - "KEY_SAVE" to 234, /* AC Save */ - "KEY_DOCUMENTS" to 235, + "KEY_SEND" to 231 to "", /* AC Send */ + "KEY_REPLY" to 232 to "", /* AC Reply */ + "KEY_FORWARDMAIL" to 233 to "", /* AC Forward Msg */ + "KEY_SAVE" to 234 to "", /* AC Save */ + "KEY_DOCUMENTS" to 235 to "", - "KEY_BATTERY" to 236, + "KEY_BATTERY" to 236 to "", - "KEY_BLUETOOTH" to 237, - "KEY_WLAN" to 238, - "KEY_UWB" to 239, + "KEY_BLUETOOTH" to 237 to "", + "KEY_WLAN" to 238 to "", + "KEY_UWB" to 239 to "", - "KEY_UNKNOWN" to 240, + "KEY_UNKNOWN" to 240 to "", - "KEY_VIDEO_NEXT" to 241, /* drive next video source */ - "KEY_VIDEO_PREV" to 242, /* drive previous video source */ - "KEY_BRIGHTNESS_CYCLE" to 243, /* brightness up, after max is min */ - "KEY_BRIGHTNESS_AUTO" to 244, + "KEY_VIDEO_NEXT" to 241 to "", /* drive next video source */ + "KEY_VIDEO_PREV" to 242 to "", /* drive previous video source */ + "KEY_BRIGHTNESS_CYCLE" to 243 to "", /* brightness up to "", after max is min */ + "KEY_BRIGHTNESS_AUTO" to 244 to "", /* Set Auto Brightness: manual - brightness control is off, + brightness control is off to "", rely on ambient */ - "KEY_BRIGHTNESS_ZERO" to 244, /* KEY_BRIGHTNESS_ZERO to KEY_BRIGHTNESS_AUTO */ - "KEY_DISPLAY_OFF" to 245, /* display device to off state */ + "KEY_BRIGHTNESS_ZERO" to 244 to "", /* KEY_BRIGHTNESS_ZERO to KEY_BRIGHTNESS_AUTO */ + "KEY_DISPLAY_OFF" to 245 to "", /* display device to off state */ - "KEY_WWAN" to 246, /* Wireless WAN (LTE, UMTS, GSM, etc.) */ - "KEY_WIMAX" to 246, /* KEY_WIMAX to KEY_WWAN */ - "KEY_RFKILL" to 247, /* Key that controls all radios */ + "KEY_WWAN" to 246 to "", /* Wireless WAN (LTE to "", UMTS to "", GSM to "", etc.) */ + "KEY_WIMAX" to 246 to "", /* KEY_WIMAX to KEY_WWAN */ + "KEY_RFKILL" to 247 to "", /* Key that controls all radios */ - "KEY_MICMUTE" to 248, /* Mute / unmute the microphone */ - ) + "KEY_MICMUTE" to 248 to "KEYCODE_MUTE", /* Mute / unmute the microphone */ + ).map { Mapping(Scancode(it.first.first, it.first.second), it.second) } // assume qwerty layout private val CHAR_MAP: List> = listOf( @@ -365,23 +368,44 @@ internal class GenScancodeMappingProcessor(private val environment: SymbolProces ) .build() + val keyCodeToScancode = FunSpec + .builder("keyCodeToScancode") + .addParameter("keyCode", Int::class) + .returns(Int::class) + .addCode( + """ + | return when (keyCode) { + | ${ + SCANCODE_MAP.filter { it.androidKey.isNotEmpty() } + .joinToString(separator = "\n| ") { (scancode, androidKey) -> + "KeyEvent.${androidKey} -> ScancodeMapping.${scancode.name}" + } + } + | else -> ScancodeMapping.KEY_RESERVED + | } + """.trimMargin() + ) + .build() + val tyScancodeMapping = TypeSpec .objectBuilder("ScancodeMapping") .apply { - SCANCODE_MAP.forEach { (name, code) -> + SCANCODE_MAP.forEach { (scancode, androidKey) -> addProperty( PropertySpec - .builder(name, Int::class, KModifier.CONST) - .initializer("$code") + .builder(scancode.name, Int::class, KModifier.CONST) + .initializer("${scancode.value}") .build() ) } } .addFunction(charToScancode) + .addFunction(keyCodeToScancode) .build() val file = FileSpec .builder("org.fcitx.fcitx5.android.core", "ScancodeMapping") + .addImport("android.view", "KeyEvent") .addType(tyScancodeMapping) .build() file.writeTo(environment.codeGenerator, false) From 1253561a2670edf325f56f6ef43118d30eccac1e Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 15 Aug 2024 01:24:38 +0800 Subject: [PATCH 049/181] Keep AIDL generated interface and class --- lib/common/build.gradle.kts | 9 +++++++++ lib/common/proguard-rules.pro | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 lib/common/proguard-rules.pro diff --git a/lib/common/build.gradle.kts b/lib/common/build.gradle.kts index 8dd4ef441..812bc0ef6 100644 --- a/lib/common/build.gradle.kts +++ b/lib/common/build.gradle.kts @@ -7,6 +7,15 @@ plugins { android { namespace = "org.fcitx.fcitx5.android.lib.common" + buildTypes { + release { + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + consumerProguardFiles("proguard-rules.pro") + } + } buildFeatures { aidl = true } diff --git a/lib/common/proguard-rules.pro b/lib/common/proguard-rules.pro new file mode 100644 index 000000000..611d5fd80 --- /dev/null +++ b/lib/common/proguard-rules.pro @@ -0,0 +1,3 @@ +# Keep AIDL generated interface and class +-keep interface org.fcitx.fcitx5.android.common.ipc.IClipboardEntryTransformer { *; } +-keep class org.fcitx.fcitx5.android.common.ipc.IFcitxRemoteService$Stub { *; } From 94e5beeb2ed10661df8c55387cd2c4ab5682316a Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 15 Aug 2024 01:25:29 +0800 Subject: [PATCH 050/181] Run assembleRelease on GitHub Actions --- .github/workflows/nix.yml | 6 +++--- .github/workflows/pull_request.yml | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 2c52911af..791d2519a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -26,7 +26,7 @@ jobs: with: name: fcitx5-android authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - name: Build Debug APK + - name: Build Release APK run: | - nix develop .#noAS --command ./gradlew :app:assembleDebug - nix develop .#noAS --command ./gradlew :assembleDebugPlugins + nix develop .#noAS --command ./gradlew :app:assembleRelease + nix develop .#noAS --command ./gradlew :assembleReleasePlugins diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f9ff8a9f4..07f9a25ed 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -66,16 +66,16 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - - name: Build Debug APK + - name: Build Release APK run: | - ./gradlew :app:assembleDebug - ./gradlew :assembleDebugPlugins + ./gradlew :app:assembleRelease + ./gradlew :assembleReleasePlugins - name: Upload app uses: actions/upload-artifact@v4 with: name: app-${{ matrix.os }} - path: app/build/outputs/apk/debug/ + path: app/build/outputs/apk/release/ - name: Pack plugins shell: bash @@ -85,7 +85,7 @@ jobs: do if [ -d "plugin/${i}" ] then - mv "plugin/${i}/build/outputs/apk/debug" "plugins-to-upload/${i}" + mv "plugin/${i}/build/outputs/apk/release" "plugins-to-upload/${i}" fi done From 25b341ec0357a5b7528401c0756b2784892edfee Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 15 Aug 2024 22:19:55 +0800 Subject: [PATCH 051/181] Disable minify for android lib --- .../src/main/kotlin/AndroidLibConventionPlugin.kt | 10 ---------- lib/common/build.gradle.kts | 9 --------- lib/common/proguard-rules.pro | 3 --- 3 files changed, 22 deletions(-) delete mode 100644 lib/common/proguard-rules.pro diff --git a/build-logic/convention/src/main/kotlin/AndroidLibConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibConventionPlugin.kt index 0e301ab62..ec3e26ae6 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibConventionPlugin.kt @@ -2,10 +2,8 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ -import com.android.build.gradle.LibraryExtension import com.android.build.gradle.tasks.ProcessLibraryArtProfileTask import org.gradle.api.Project -import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.withType class AndroidLibConventionPlugin : AndroidBaseConventionPlugin() { @@ -15,14 +13,6 @@ class AndroidLibConventionPlugin : AndroidBaseConventionPlugin() { super.apply(target) - target.extensions.configure { - buildTypes { - release { - isMinifyEnabled = true - } - } - } - // disable baseline profile tasks target.tasks.withType { enabled = false } } diff --git a/lib/common/build.gradle.kts b/lib/common/build.gradle.kts index 812bc0ef6..8dd4ef441 100644 --- a/lib/common/build.gradle.kts +++ b/lib/common/build.gradle.kts @@ -7,15 +7,6 @@ plugins { android { namespace = "org.fcitx.fcitx5.android.lib.common" - buildTypes { - release { - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - consumerProguardFiles("proguard-rules.pro") - } - } buildFeatures { aidl = true } diff --git a/lib/common/proguard-rules.pro b/lib/common/proguard-rules.pro deleted file mode 100644 index 611d5fd80..000000000 --- a/lib/common/proguard-rules.pro +++ /dev/null @@ -1,3 +0,0 @@ -# Keep AIDL generated interface and class --keep interface org.fcitx.fcitx5.android.common.ipc.IClipboardEntryTransformer { *; } --keep class org.fcitx.fcitx5.android.common.ipc.IFcitxRemoteService$Stub { *; } From c4a07bbb3c423d30abcdcaecbae264d87bc31364 Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 15 Aug 2024 22:46:54 +0800 Subject: [PATCH 052/181] Allow uninstalling plugin from AboutActivity --- lib/plugin-base/src/main/AndroidManifest.xml | 3 ++ .../android/lib/plugin_base/AboutActivity.kt | 28 +++++++++++++++++-- .../src/main/res/values/strings.xml | 2 ++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/plugin-base/src/main/AndroidManifest.xml b/lib/plugin-base/src/main/AndroidManifest.xml index f58225b40..779faf75d 100644 --- a/lib/plugin-base/src/main/AndroidManifest.xml +++ b/lib/plugin-base/src/main/AndroidManifest.xml @@ -9,6 +9,9 @@ + + + Plugin Info Open-source Licenses Contains IPC service + App info + Uninstall \ No newline at end of file From 58a6335c1c337d0592a864dbb0f2ac00cebc34dc Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 17 Aug 2024 22:02:19 +0800 Subject: [PATCH 053/181] Write physical display size in Device Info --- .../ui/main/settings/PinyinDictionaryFragment.kt | 4 ++-- .../ui/main/settings/QuickPhraseListFragment.kt | 4 ++-- .../ui/main/settings/TableInputMethodFragment.kt | 2 +- .../java/org/fcitx/fcitx5/android/utils/DeviceInfo.kt | 11 ++++++++++- .../org/fcitx/fcitx5/android/utils/SystemService.kt | 8 ++++---- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/PinyinDictionaryFragment.kt b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/PinyinDictionaryFragment.kt index 00d6c2e75..3aae74410 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/PinyinDictionaryFragment.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/PinyinDictionaryFragment.kt @@ -117,7 +117,7 @@ class PinyinDictionaryFragment : Fragment(), OnItemChangedListener { getText(R.string.quickphrase_editor), NotificationManager.IMPORTANCE_HIGH ).apply { description = CHANNEL_ID } - notificationManager.createNotificationChannel(channel) + requireContext().notificationManager.createNotificationChannel(channel) } } @@ -269,7 +269,7 @@ class QuickPhraseListFragment : Fragment(), OnItemChangedListener { resetDustman() // save the reference to NotificationManager, in case we need to cancel notification // after Fragment detached - val nm = notificationManager + val nm = requireContext().notificationManager lifecycleScope.launch(NonCancellable + Dispatchers.IO) { if (busy.compareAndSet(false, true)) { val id = RELOAD_ID++ diff --git a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/TableInputMethodFragment.kt b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/TableInputMethodFragment.kt index d9c47c729..f685aef5c 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/TableInputMethodFragment.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/TableInputMethodFragment.kt @@ -135,7 +135,7 @@ class TableInputMethodFragment : Fragment(), OnItemChangedListener= Build.VERSION_CODES.R) { + context.display!! + } else { + context.windowManager.defaultDisplay + }.getRealSize(it) + } + appendLine("Screen Size: ${size.x} x ${size.y}") val metrics = context.resources.displayMetrics - appendLine("Screen Size: ${metrics.widthPixels} x ${metrics.heightPixels}") appendLine("Screen Density: ${metrics.density}") appendLine( "Screen orientation: ${ diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/SystemService.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/SystemService.kt index bcf02cc5b..2dfb02b24 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/utils/SystemService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/utils/SystemService.kt @@ -11,9 +11,9 @@ import android.media.AudioManager import android.os.UserManager import android.os.Vibrator import android.os.storage.StorageManager +import android.view.WindowManager import android.view.inputmethod.InputMethodManager import androidx.core.content.getSystemService -import androidx.fragment.app.Fragment val Context.audioManager get() = getSystemService()!! @@ -27,14 +27,14 @@ val Context.inputMethodManager val Context.notificationManager get() = getSystemService()!! -val Fragment.notificationManager - get() = requireContext().notificationManager - val Context.storageManager get() = getSystemService()!! val Context.vibrator get() = getSystemService()!! +val Context.windowManager + get() = getSystemService()!! + val Context.userManager get() = getSystemService()!! From 831858aebb7ca663891408f34182afdbdca9effc Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 17 Aug 2024 22:04:13 +0800 Subject: [PATCH 054/181] Fix deleting excluded files in FcitxComponentPlugin --- .../convention/src/main/kotlin/FcitxComponentPlugin.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt b/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt index d0a71edb2..f8cda0279 100644 --- a/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt +++ b/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt @@ -31,6 +31,8 @@ class FcitxComponentPlugin : Plugin { } override fun apply(target: Project) { + val installTask = target.task(INSTALL_TASK) + val deleteTask = target.task(DELETE_TASK) registerCMakeTask(target, "generate-desktop-file", "config") registerCMakeTask(target, "translation-file", "translation") registerCleanTask(target) @@ -43,8 +45,8 @@ class FcitxComponentPlugin : Plugin { registerCMakeTask(target, "translation-file", "translation", project) } if (ext.excludeFiles.isNotEmpty()) { - target.task(DELETE_TASK) { - dependsOn(target.tasks.findByName(INSTALL_TASK)) + deleteTask.apply { + dependsOn(installTask) doLast { ext.excludeFiles.forEach { project.assetsDir.resolve(it).delete() @@ -83,8 +85,7 @@ class FcitxComponentPlugin : Plugin { } } } - val dependencyTask = project.tasks.findByName(INSTALL_TASK) ?: project.task(INSTALL_TASK) - dependencyTask.dependsOn(task) + project.tasks.getByName(INSTALL_TASK).dependsOn(task) } private fun registerCleanTask(project: Project) { From ab747e35aec35fb355c33cca32c9776d1d331ca6 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 17 Aug 2024 22:28:12 +0800 Subject: [PATCH 055/181] Refresh PluginFragment on resume/package change --- .../fcitx5/android/core/data/DataManager.kt | 13 ++- .../fcitx5/android/ui/main/PluginFragment.kt | 101 +++++++++++++----- 2 files changed, 86 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/data/DataManager.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/data/DataManager.kt index fd19acf2c..893ef389a 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/data/DataManager.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/data/DataManager.kt @@ -29,6 +29,11 @@ import kotlin.concurrent.withLock */ object DataManager { + data class PluginSet( + val loaded: Set, + val failed: Map + ) + const val PLUGIN_INTENT = "${BuildConfig.APPLICATION_ID}.plugin.MANIFEST" private val lock = ReentrantLock() @@ -69,6 +74,8 @@ object DataManager { fun getLoadedPlugins(): Set = loadedPlugins fun getFailedPlugins(): Map = failedPlugins + fun getSyncedPluginSet() = PluginSet(loadedPlugins, failedPlugins) + /** * Will be cleared after each sync */ @@ -77,7 +84,7 @@ object DataManager { fun addOnNextSyncedCallback(block: () -> Unit) = callbacks.add(block) - fun detectPlugins(): Pair, Map> { + fun detectPlugins(): PluginSet { val toLoad = mutableSetOf() val preloadFailed = mutableMapOf() @@ -190,7 +197,7 @@ object DataManager { preloadFailed[packageName] = PluginLoadFailed.PluginDescriptorParseError } } - return toLoad to preloadFailed + return PluginSet(toLoad, preloadFailed) } fun sync() = lock.withLock { @@ -321,4 +328,4 @@ object DataManager { sync() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/PluginFragment.kt b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/PluginFragment.kt index 181795f62..c5a03a16f 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/PluginFragment.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/PluginFragment.kt @@ -5,8 +5,11 @@ package org.fcitx.fcitx5.android.ui.main import android.annotation.SuppressLint +import android.content.BroadcastReceiver import android.content.ComponentName +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.content.pm.PackageManager import android.net.Uri import android.os.Build @@ -27,34 +30,91 @@ import org.fcitx.fcitx5.android.utils.addPreference class PluginFragment : PaddingPreferenceFragment() { - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + private var firstRun = true + + private lateinit var synced: DataManager.PluginSet + private lateinit var detected: DataManager.PluginSet + + private val packageChangeReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + refreshPreferencesWhenNeeded() + } + } + + private fun DataManager.whenSynced(block: () -> Unit) { lifecycleScope.launch { - val needsReload = if (DataManager.synced) { - val (newPluginsToLoad, _) = DataManager.detectPlugins() - newPluginsToLoad != DataManager.getLoadedPlugins() - } else { - DataManager.waitSynced() - false + if (!synced) { + suspendCancellableCoroutine { + if (synced) { + it.resumeWith(Result.success(Unit)) + } else { + addOnNextSyncedCallback { + it.resumeWith(Result.success(Unit)) + } + } + } } - preferenceScreen = createPreferenceScreen(needsReload) + block.invoke() + } + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + DataManager.whenSynced { + synced = DataManager.getSyncedPluginSet() + detected = DataManager.detectPlugins() + preferenceScreen = createPreferenceScreen() } } - private fun createPreferenceScreen(needsReload: Boolean): PreferenceScreen = + private fun refreshPreferencesWhenNeeded() { + DataManager.whenSynced { + val newDetected = DataManager.detectPlugins() + if (detected != newDetected) { + detected = newDetected + preferenceScreen = createPreferenceScreen() + } + } + } + + override fun onResume() { + super.onResume() + requireContext().registerReceiver(packageChangeReceiver, IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addAction(Intent.ACTION_PACKAGE_REPLACED) + addDataScheme("package") + }) + /** + * [onResume] got called after [onCreatePreferences] when the fragment is created and + * shown for the first time + */ + if (firstRun) { + firstRun = false + return + } + // try refresh plugin list when the user navigate back from other apps + refreshPreferencesWhenNeeded() + } + + override fun onPause() { + super.onPause() + requireContext().unregisterReceiver(packageChangeReceiver) + } + + private fun createPreferenceScreen(): PreferenceScreen = preferenceManager.createPreferenceScreen(requireContext()).apply { - if (needsReload) { + if (synced != detected) { addPreference(R.string.plugin_needs_reload, icon = R.drawable.ic_baseline_info_24) { DataManager.addOnNextSyncedCallback { - // recreate the plugin list - // we only check new plugins on the first creation - preferenceScreen = createPreferenceScreen(false) + synced = DataManager.getSyncedPluginSet() + detected = DataManager.detectPlugins() + preferenceScreen = createPreferenceScreen() } // DataManager.sync and and restart fcitx FcitxDaemon.restartFcitx() } } - val loaded = DataManager.getLoadedPlugins() - val failed = DataManager.getFailedPlugins() + val (loaded, failed) = synced if (loaded.isEmpty() && failed.isEmpty()) { // use PreferenceCategory to show a divider below the "reload" preference addCategory(R.string.no_plugins) { @@ -112,15 +172,6 @@ class PluginFragment : PaddingPreferenceFragment() { } } - private suspend fun DataManager.waitSynced() = suspendCancellableCoroutine { - if (synced) - it.resumeWith(Result.success(Unit)) - else - addOnNextSyncedCallback { - it.resumeWith(Result.success(Unit)) - } - } - private fun startPluginAboutActivity(pkg: String): Boolean { val ctx = requireContext() val pm = ctx.packageManager @@ -148,4 +199,4 @@ class PluginFragment : PaddingPreferenceFragment() { return true } -} \ No newline at end of file +} From d7d6343267f417de266fd2afb457d863f820688b Mon Sep 17 00:00:00 2001 From: Rocka Date: Sun, 18 Aug 2024 22:08:39 +0800 Subject: [PATCH 056/181] Show floating CandidatesView for hardware keyboard --- .idea/dictionaries/project.xml | 3 + .../fcitx/fcitx5/android/core/FcitxEvent.kt | 2 +- .../fcitx5/android/input/CandidatesView.kt | 160 ++++++++++++++++++ .../android/input/FcitxInputMethodService.kt | 93 ++++++++-- .../fcitx/fcitx5/android/input/InputView.kt | 30 +++- .../fcitx5/android/input/preedit/PreeditUi.kt | 6 +- .../fcitx5/android/utils/InputConnection.kt | 20 +++ 7 files changed, 288 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index de51628fc..fa009839e 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -13,6 +13,7 @@ fcitx fmtlib icuuid + inputmethodservice iostreams iter jbytes @@ -21,6 +22,7 @@ jstring jyutping kawaii + keypress lgpl libevent libime @@ -37,6 +39,7 @@ shijienihao shuangpin sinhala + snackbar spdx stringutils unfocus diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt index 3172ba6f1..2db3780dc 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt @@ -13,7 +13,7 @@ sealed class FcitxEvent(open val data: T) { override val eventType = EventType.Candidate - data class Data(val total: Int, val candidates: Array) { + data class Data(val total: Int = -1, val candidates: Array = emptyArray()) { override fun toString(): String = "total=$total, candidates=[${candidates.joinToString(limit = 5)}]" diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt new file mode 100644 index 000000000..1e07a21ee --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt @@ -0,0 +1,160 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.input + +import android.annotation.SuppressLint +import android.graphics.Color +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.annotation.Size +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import org.fcitx.fcitx5.android.R +import org.fcitx.fcitx5.android.core.FcitxEvent +import org.fcitx.fcitx5.android.daemon.FcitxConnection +import org.fcitx.fcitx5.android.data.theme.Theme +import org.fcitx.fcitx5.android.input.candidates.CandidateItemUi +import org.fcitx.fcitx5.android.input.preedit.PreeditUi +import splitties.dimensions.dp +import splitties.views.backgroundColor +import splitties.views.dsl.constraintlayout.below +import splitties.views.dsl.constraintlayout.bottomOfParent +import splitties.views.dsl.constraintlayout.lParams +import splitties.views.dsl.constraintlayout.startOfParent +import splitties.views.dsl.constraintlayout.topOfParent +import splitties.views.dsl.core.add +import splitties.views.dsl.core.endMargin +import splitties.views.dsl.core.horizontalLayout +import splitties.views.dsl.core.lParams +import splitties.views.dsl.core.withTheme +import splitties.views.dsl.core.wrapContent +import splitties.views.horizontalPadding +import splitties.views.verticalPadding +import kotlin.math.min + +@SuppressLint("ViewConstructor") +class CandidatesView( + val service: FcitxInputMethodService, + val fcitx: FcitxConnection, + val theme: Theme +) : ConstraintLayout(service) { + + var handleEvents = false + set(value) { + field = value + visibility = View.GONE + if (field) { + setupFcitxEventHandler() + } else { + eventHandlerJob?.cancel() + eventHandlerJob = null + } + } + + private val ctx = context.withTheme(R.style.Theme_InputViewTheme) + + private var eventHandlerJob: Job? = null + + private var inputPanel = FcitxEvent.InputPanelEvent.Data() + private var candidates = FcitxEvent.CandidateListEvent.Data() + + private val preeditUi = object : PreeditUi(ctx, theme) { + override val bkgColor = Color.TRANSPARENT + } + + // TODO ui orientation from fcitx + private val candidatesUi = horizontalLayout { + horizontalPadding = dp(4) + } + + private fun handleFcitxEvent(it: FcitxEvent<*>) { + when (it) { + // TODO make a new candidate page event + is FcitxEvent.CandidateListEvent -> { + candidates = it.data + updateUI() + } + is FcitxEvent.InputPanelEvent -> { + inputPanel = it.data + updateUI() + } + else -> {} + } + } + + private fun evaluateVisibility(): Boolean { + return inputPanel.preedit.isNotEmpty() || + candidates.total > 0 || + inputPanel.auxUp.isNotEmpty() || + inputPanel.auxDown.isNotEmpty() + } + + private fun updateUI() { + if (evaluateVisibility()) { + visibility = View.VISIBLE + preeditUi.update(inputPanel) + preeditUi.root.isVisible = preeditUi.visible + updateCandidates() + } else { + visibility = View.GONE + } + } + + private fun updateCandidates() { + candidatesUi.apply { + removeAllViews() + val limit = min(candidates.candidates.size, 5) + for (i in 0.. { + leftMargin = pos[0].toInt() + topMargin = pos[1].toInt() + } + } + + private fun setupFcitxEventHandler() { + eventHandlerJob = service.lifecycleScope.launch { + fcitx.runImmediately { eventFlow }.collect { + handleFcitxEvent(it) + } + } + } + + init { + verticalPadding = dp(8) + backgroundColor = theme.backgroundColor + add(preeditUi.root, lParams(wrapContent, wrapContent) { + topOfParent() + startOfParent() + }) + add(candidatesUi, lParams(wrapContent, wrapContent) { + below(preeditUi.root) + startOfParent() + bottomOfParent() + }) + + layoutParams = ViewGroup.LayoutParams(wrapContent, wrapContent) + } + + override fun onDetachedFromWindow() { + handleEvents = false + super.onDetachedFromWindow() + } +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt index ab25ab90e..25a87d445 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt @@ -63,6 +63,7 @@ import org.fcitx.fcitx5.android.input.cursor.CursorTracker import org.fcitx.fcitx5.android.utils.InputMethodUtil import org.fcitx.fcitx5.android.utils.alpha import org.fcitx.fcitx5.android.utils.inputMethodManager +import org.fcitx.fcitx5.android.utils.monitorCursorAnchor import org.fcitx.fcitx5.android.utils.withBatchEdit import splitties.bitflags.hasFlag import splitties.dimensions.dp @@ -83,6 +84,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { private lateinit var pkgNameCache: PackageNameCache private var inputView: InputView? = null + private var candidatesView: CandidatesView? = null private var capabilityFlags = CapabilityFlags.DefaultFlags @@ -414,6 +416,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } override fun onCreateInputView(): View { + candidatesView = CandidatesView(this, fcitx, ThemeManager.activeTheme) // onCreateInputView will be called once, when the input area is first displayed, // during each onConfigurationChanged period. // That is, onCreateInputView would be called again, after system dark mode changes, @@ -429,12 +432,21 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } catch (e: Exception) { Timber.w("Device does not support android.R.attr.colorAccent which it should have.") } - window.window!!.decorView - .findViewById(android.R.id.inputArea) + val decor = window.window?.decorView ?: return + // input method layout has not changed in 11 years: + // https://android.googlesource.com/platform/frameworks/base/+/ae3349e1c34f7aceddc526cd11d9ac44951e97b6/core/res/res/layout/input_method.xml + // put CandidatesView directly under parentPanel + decor.findViewById(android.R.id.content).addView(candidatesView!!) + super.setInputView(view) + // expand inputArea to fullscreen + decor.findViewById(android.R.id.inputArea) .updateLayoutParams { height = ViewGroup.LayoutParams.MATCH_PARENT } - super.setInputView(view) + /** + * expand InputView to fullscreen, since [android.inputmethodservice.InputMethodService.setInputView] + * would set InputView's height to [ViewGroup.LayoutParams.WRAP_CONTENT] + */ view.updateLayoutParams { height = ViewGroup.LayoutParams.MATCH_PARENT } @@ -444,18 +456,28 @@ class FcitxInputMethodService : LifecycleInputMethodService() { win.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) } + private var inputViewLocation = intArrayOf(0, 0) + override fun onComputeInsets(outInsets: Insets) { - val (_, y) = intArrayOf(0, 0).also { inputView?.keyboardView?.getLocationInWindow(it) } + Timber.d("onComputeInsets") + if (candidatesView?.handleEvents == true) { + val h = window.window!!.decorView.height + outInsets.apply { + contentTopInsets = h + visibleTopInsets = h + touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE + } + return + } + inputView?.keyboardView?.getLocationInWindow(inputViewLocation) outInsets.apply { - contentTopInsets = y - touchableInsets = Insets.TOUCHABLE_INSETS_CONTENT - touchableRegion.setEmpty() - visibleTopInsets = y + contentTopInsets = inputViewLocation[1] + visibleTopInsets = inputViewLocation[1] + touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE } } - // TODO: candidate view for physical keyboard input - // always show InputView since we do not support physical keyboard input without it yet + // always show InputView since we delegate CandidatesView's visibility to it @SuppressLint("MissingSuperCall") override fun onEvaluateInputViewShown() = true @@ -576,12 +598,26 @@ class FcitxInputMethodService : LifecycleInputMethodService() { override fun onStartInputView(info: EditorInfo, restarting: Boolean) { Timber.d("onStartInputView: restarting=$restarting") + // monitor cursor anchor only when needed, ie + // InputView just becomes visible && using floating CandidatesView + if (!restarting && !super.onEvaluateInputViewShown()) { + currentInputConnection?.monitorCursorAnchor() + } postFcitxJob { focus(true) } - // because onStartInputView will always be called after onStartInput, - // editorInfo and capFlags should be up-to-date - inputView?.startInput(info, capabilityFlags, restarting) + if (super.onEvaluateInputViewShown()) { + candidatesView?.handleEvents = false + inputView?.handleEvents = true + inputView?.visibility = View.VISIBLE + // because onStartInputView will always be called after onStartInput, + // editorInfo and capFlags should be up-to-date + inputView?.startInput(info, capabilityFlags, restarting) + } else { + candidatesView?.handleEvents = true + inputView?.handleEvents = false + inputView?.visibility = View.GONE + } } override fun onUpdateSelection( @@ -599,8 +635,30 @@ class FcitxInputMethodService : LifecycleInputMethodService() { inputView?.updateSelection(newSelStart, newSelEnd) } + private val decorLocation = floatArrayOf(0f, 0f) + private val decorLocationInt = intArrayOf(0, 0) + private var decorLocationUpdated = false + + private fun updateDecorLocation() { + window.window!!.decorView.getLocationOnScreen(decorLocationInt) + decorLocation[0] = decorLocationInt[0].toFloat() + decorLocation[1] = decorLocationInt[1].toFloat() + decorLocationUpdated = true + } + + private val anchorPosition = floatArrayOf(0f, 0f) + override fun onUpdateCursorAnchorInfo(info: CursorAnchorInfo) { - // CursorAnchorInfo focus more on screen coordinates rather than selection + anchorPosition[0] = info.insertionMarkerHorizontal + anchorPosition[1] = info.insertionMarkerBottom + info.matrix.mapPoints(anchorPosition) + // avoid calling `decorView.getLocationOnScreen` repeatedly + if (!decorLocationUpdated) { + updateDecorLocation() + } + anchorPosition[0] -= decorLocation[0] + anchorPosition[1] -= decorLocation[1] + candidatesView?.updatePosition(anchorPosition) } private fun handleCursorUpdate(newSelStart: Int, newSelEnd: Int, updateIndex: Int) { @@ -794,7 +852,11 @@ class FcitxInputMethodService : LifecycleInputMethodService() { override fun onFinishInputView(finishingInput: Boolean) { Timber.d("onFinishInputView: finishingInput=$finishingInput") - currentInputConnection?.finishComposingText() + currentInputConnection?.run { + finishComposingText() + monitorCursorAnchor(false) + decorLocationUpdated = false + } resetComposingState() postFcitxJob { focus(false) @@ -830,6 +892,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { FcitxDaemon.disconnect(javaClass.name) } + @Suppress("ConstPropertyName") companion object { const val DeleteSurroundingFlag = "org.fcitx.fcitx5.android.DELETE_SURROUNDING" } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/InputView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/InputView.kt index c6872b870..9a50a81fc 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/InputView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/InputView.kt @@ -82,6 +82,17 @@ class InputView( val theme: Theme ) : ConstraintLayout(service) { + var handleEvents = true + set(value) { + field = value + if (field) { + setupFcitxEventHandler() + } else { + eventHandlerJob?.cancel() + eventHandlerJob = null + } + } + private var shouldUpdateNavbarForeground = false private var shouldUpdateNavbarBackground = false @@ -107,7 +118,15 @@ class InputView( setOnClickListener(placeholderOnClickListener) } - private val eventHandlerJob: Job + private var eventHandlerJob: Job? = null + + private fun setupFcitxEventHandler() { + eventHandlerJob = service.lifecycleScope.launch { + fcitx.runImmediately { eventFlow }.collect { + handleFcitxEvent(it) + } + } + } private val scope = DynamicScope() private val themedContext = context.withTheme(R.style.Theme_InputViewTheme) @@ -203,11 +222,7 @@ class InputView( // MUST call before any operation setupScope() - eventHandlerJob = service.lifecycleScope.launch { - fcitx.runImmediately { eventFlow }.collect { - handleFcitxEvent(it) - } - } + setupFcitxEventHandler() // restore punctuation mapping in case of InputView recreation fcitx.launchOnReady { @@ -226,6 +241,7 @@ class InputView( broadcaster.onImeUpdate(fcitx.runImmediately { inputMethodEntryCached }) + // TODO should be moved outside of InputView service.window.window!!.also { when (navbarBackground) { NavbarBackground.None -> { @@ -454,7 +470,7 @@ class InputView( showingDialog?.dismiss() // cancel eventHandlerJob and then clear DynamicScope, // implies that InputView should not be attached again after detached. - eventHandlerJob.cancel() + handleEvents = false scope.clear() super.onDetachedFromWindow() } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditUi.kt index d9c4b2370..c3d50f407 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditUi.kt @@ -27,7 +27,7 @@ import splitties.views.dsl.core.textView import splitties.views.dsl.core.verticalLayout import splitties.views.horizontalPadding -class PreeditUi(override val ctx: Context, private val theme: Theme) : Ui { +open class PreeditUi(override val ctx: Context, private val theme: Theme) : Ui { class CursorSpan(ctx: Context, @ColorInt color: Int, metrics: Paint.FontMetricsInt) : DynamicDrawableSpan() { @@ -45,13 +45,13 @@ class PreeditUi(override val ctx: Context, private val theme: Theme) : Ui { private val keyBorder by ThemeManager.prefs.keyBorder - private val barBackground = when (theme) { + open val bkgColor = when (theme) { is Theme.Builtin -> if (keyBorder) theme.backgroundColor else theme.barColor is Theme.Custom -> theme.backgroundColor } private fun createTextView() = textView { - backgroundColor = barBackground + backgroundColor = bkgColor horizontalPadding = dp(8) setTextColor(theme.keyTextColor) textSize = 16f diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/InputConnection.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/InputConnection.kt index bc7082fb1..6785f3c5e 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/utils/InputConnection.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/utils/InputConnection.kt @@ -5,6 +5,7 @@ package org.fcitx.fcitx5.android.utils +import android.os.Build import android.view.inputmethod.InputConnection fun InputConnection.withBatchEdit(block: InputConnection.() -> Unit) { @@ -12,3 +13,22 @@ fun InputConnection.withBatchEdit(block: InputConnection.() -> Unit) { block.invoke(this) endBatchEdit() } + +fun InputConnection.monitorCursorAnchor(enable: Boolean = true): Boolean { + if (!enable) { + requestCursorUpdates(0) + return false + } + var scheduled = false + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + scheduled = requestCursorUpdates( + InputConnection.CURSOR_UPDATE_MONITOR, + InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS or InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER + ) + } + if (!scheduled) { + scheduled = + requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR) + } + return scheduled +} From 6899bb245a7577f13684a97c53a407fff567df82 Mon Sep 17 00:00:00 2001 From: Rocka Date: Mon, 19 Aug 2024 00:34:02 +0800 Subject: [PATCH 057/181] Use translation on CandidatesView to avoid shrinking --- .../fcitx5/android/input/CandidatesView.kt | 37 ++++++++++--- .../android/input/FcitxInputMethodService.kt | 55 ++++++++++++------- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt index 1e07a21ee..a669d0f2f 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt @@ -9,11 +9,9 @@ import android.annotation.SuppressLint import android.graphics.Color import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout import androidx.annotation.Size import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -66,13 +64,19 @@ class CandidatesView( private var inputPanel = FcitxEvent.InputPanelEvent.Data() private var candidates = FcitxEvent.CandidateListEvent.Data() + /** + * horizontal, bottom, top + */ + private val anchorPosition = floatArrayOf(0f, 0f, 0f) + private val parentSize = floatArrayOf(0f, 0f) + private val preeditUi = object : PreeditUi(ctx, theme) { override val bkgColor = Color.TRANSPARENT } // TODO ui orientation from fcitx private val candidatesUi = horizontalLayout { - horizontalPadding = dp(4) + horizontalPadding = dp(8) } private fun handleFcitxEvent(it: FcitxEvent<*>) { @@ -103,6 +107,7 @@ class CandidatesView( preeditUi.update(inputPanel) preeditUi.root.isVisible = preeditUi.visible updateCandidates() + updatePosition() } else { visibility = View.GONE } @@ -117,16 +122,30 @@ class CandidatesView( text.textSize = 16f text.text = "${i + 1}. ${candidates.candidates[i]}" } - addView(item.root, lParams { endMargin = if (i == limit - 1) 0 else dp(4) }) + addView(item.root, lParams { endMargin = if (i == limit - 1) 0 else dp(8) }) } } } - fun updatePosition(@Size(2) pos: FloatArray) { - updateLayoutParams { - leftMargin = pos[0].toInt() - topMargin = pos[1].toInt() - } + private fun updatePosition() { + val (horizontal, bottom, top) = anchorPosition + val (parentWidth, parentHeight) = parentSize + val selfWidth = width.toFloat() + val selfHeight = height.toFloat() + translationX = + if (horizontal + selfWidth > parentWidth) parentWidth - selfWidth else horizontal + translationY = if (bottom + selfHeight > parentHeight) top - selfHeight else bottom + } + + fun updateCursorAnchor(@Size(4) anchor: FloatArray, @Size(2) parent: FloatArray) { + val (horizontal, bottom, _, top) = anchor + val (parentWidth, parentHeight) = parent + anchorPosition[0] = horizontal + anchorPosition[1] = bottom + anchorPosition[2] = top + parentSize[0] = parentWidth + parentSize[1] = parentHeight + updatePosition() } private fun setupFcitxEventHandler() { diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt index 25a87d445..a4a97ce7e 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt @@ -83,6 +83,8 @@ class FcitxInputMethodService : LifecycleInputMethodService() { private lateinit var pkgNameCache: PackageNameCache + private lateinit var decorView: View + private lateinit var contentView: FrameLayout private var inputView: InputView? = null private var candidatesView: CandidatesView? = null @@ -120,12 +122,14 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } private fun recreateInputView(theme: Theme) { - // InputView should be created first in onCreateInputView - // setInputView should be used to 'replace' current InputView only - InputView(this, fcitx, theme).also { - inputView = it - setInputView(it) - } + // InputView should be first created in `onCreateInputView` + // `setInputView` should only be used to 'replace' current InputView + inputView = InputView(this, fcitx, theme) + setInputView(inputView!!) + // replace CandidatesView manually + contentView.removeView(candidatesView) + candidatesView = CandidatesView(this, fcitx, ThemeManager.activeTheme) + contentView.addView(candidatesView) } private fun postJob(scope: CoroutineScope, block: suspend () -> Unit): Job { @@ -416,14 +420,17 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } override fun onCreateInputView(): View { - candidatesView = CandidatesView(this, fcitx, ThemeManager.activeTheme) // onCreateInputView will be called once, when the input area is first displayed, // during each onConfigurationChanged period. // That is, onCreateInputView would be called again, after system dark mode changes, // or screen orientation changes. - return InputView(this, fcitx, ThemeManager.activeTheme).also { - inputView = it - } + decorView = window.window!!.decorView + contentView = decorView.findViewById(android.R.id.content) + candidatesView = CandidatesView(this, fcitx, ThemeManager.activeTheme) + // put CandidatesView directly under content view + contentView.addView(candidatesView) + inputView = InputView(this, fcitx, ThemeManager.activeTheme) + return inputView!! } override fun setInputView(view: View) { @@ -432,14 +439,11 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } catch (e: Exception) { Timber.w("Device does not support android.R.attr.colorAccent which it should have.") } - val decor = window.window?.decorView ?: return + super.setInputView(view) // input method layout has not changed in 11 years: // https://android.googlesource.com/platform/frameworks/base/+/ae3349e1c34f7aceddc526cd11d9ac44951e97b6/core/res/res/layout/input_method.xml - // put CandidatesView directly under parentPanel - decor.findViewById(android.R.id.content).addView(candidatesView!!) - super.setInputView(view) // expand inputArea to fullscreen - decor.findViewById(android.R.id.inputArea) + decorView.findViewById(android.R.id.inputArea) .updateLayoutParams { height = ViewGroup.LayoutParams.MATCH_PARENT } @@ -461,7 +465,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { override fun onComputeInsets(outInsets: Insets) { Timber.d("onComputeInsets") if (candidatesView?.handleEvents == true) { - val h = window.window!!.decorView.height + val h = decorView.height outInsets.apply { contentTopInsets = h visibleTopInsets = h @@ -635,30 +639,39 @@ class FcitxInputMethodService : LifecycleInputMethodService() { inputView?.updateSelection(newSelStart, newSelEnd) } + private val contentSize = floatArrayOf(0f, 0f) private val decorLocation = floatArrayOf(0f, 0f) private val decorLocationInt = intArrayOf(0, 0) private var decorLocationUpdated = false private fun updateDecorLocation() { - window.window!!.decorView.getLocationOnScreen(decorLocationInt) + contentSize[0] = contentView.width.toFloat() + contentSize[1] = contentView.height.toFloat() + decorView.getLocationOnScreen(decorLocationInt) decorLocation[0] = decorLocationInt[0].toFloat() decorLocation[1] = decorLocationInt[1].toFloat() decorLocationUpdated = true } - private val anchorPosition = floatArrayOf(0f, 0f) + private val anchorPosition = floatArrayOf(0f, 0f, 0f, 0f) override fun onUpdateCursorAnchorInfo(info: CursorAnchorInfo) { anchorPosition[0] = info.insertionMarkerHorizontal anchorPosition[1] = info.insertionMarkerBottom + anchorPosition[2] = info.insertionMarkerHorizontal + anchorPosition[3] = info.insertionMarkerTop + // params of `Matrix.mapPoints` must be [x0, y0, x1, y1] info.matrix.mapPoints(anchorPosition) // avoid calling `decorView.getLocationOnScreen` repeatedly if (!decorLocationUpdated) { updateDecorLocation() } - anchorPosition[0] -= decorLocation[0] - anchorPosition[1] -= decorLocation[1] - candidatesView?.updatePosition(anchorPosition) + val (xOffset, yOffset) = decorLocation + anchorPosition[0] -= xOffset + anchorPosition[1] -= yOffset + anchorPosition[2] -= xOffset + anchorPosition[3] -= yOffset + candidatesView?.updateCursorAnchor(anchorPosition, contentSize) } private fun handleCursorUpdate(newSelStart: Int, newSelEnd: Int, updateIndex: Int) { From d07838d86878717190709c49dce5fa143846a33e Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 21 Aug 2024 01:03:05 +0800 Subject: [PATCH 058/181] Dedicated PagedCandidateEvent --- .../cpp/androidfrontend/androidfrontend.cpp | 55 ++++++++++++++++++- .../cpp/androidfrontend/androidfrontend.h | 14 ++++- .../androidfrontend/androidfrontend_public.h | 9 +++ app/src/main/cpp/helper-types.h | 37 +++++++++++++ app/src/main/cpp/jni-utils.h | 6 ++ app/src/main/cpp/native-lib.cpp | 31 +++++++++++ app/src/main/cpp/object-conversion.h | 9 +++ .../org/fcitx/fcitx5/android/core/Fcitx.kt | 6 ++ .../org/fcitx/fcitx5/android/core/FcitxAPI.kt | 2 + .../fcitx/fcitx5/android/core/FcitxEvent.kt | 39 ++++++++++++- .../fcitx5/android/input/CandidatesView.kt | 55 +++++++++++-------- .../android/input/FcitxInputMethodService.kt | 4 +- 12 files changed, 235 insertions(+), 32 deletions(-) diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.cpp b/app/src/main/cpp/androidfrontend/androidfrontend.cpp index ef1128eb1..092bda231 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.cpp +++ b/app/src/main/cpp/androidfrontend/androidfrontend.cpp @@ -66,9 +66,12 @@ class AndroidInputContext : public InputContextV2 { filterText(ip.auxUp()), filterText(ip.auxDown()) ); + } + + void updateCandidatesBulk() { std::vector candidates; int size = 0; - const auto &list = ip.candidateList(); + const auto &list = inputPanel().candidateList(); if (list) { const auto &bulk = list->toBulk(); if (bulk) { @@ -96,6 +99,32 @@ class AndroidInputContext : public InputContextV2 { frontend_->updateCandidateList(candidates, size); } + void updateCandidatesPaged() { + const auto &list = inputPanel().candidateList(); + if (!list) { + PagedCandidateEntity empty({}, -1, CandidateLayoutHint::NotSet, false, false); + frontend_->updatePagedCandidate(empty); + return; + } + int cursorIndex = list->cursorIndex(); + CandidateLayoutHint layoutHint = list->layoutHint(); + bool hasPrev = false; + bool hasNext = false; + const auto &pageable = list->toPageable(); + if (pageable) { + hasPrev = pageable->hasPrev(); + hasNext = pageable->hasNext(); + } + int size = list->size(); + std::vector candidates; + candidates.reserve(size); + for (int i = 0; i < size; i++) { + candidates.emplace_back(list->candidate(i), list->label(i)); + } + PagedCandidateEntity paged(candidates, cursorIndex, layoutHint, hasPrev, hasNext); + frontend_->updatePagedCandidate(paged); + } + bool selectCandidate(int idx) { const auto &list = inputPanel().candidateList(); if (!list) { @@ -220,7 +249,8 @@ AndroidFrontend::AndroidFrontend(Instance *instance) focusGroup_("android", instance->inputContextManager()), activeIC_(nullptr), icCache_(), - eventHandlers_() { + eventHandlers_(), + pagingMode_(0) { eventHandlers_.emplace_back(instance_->watchEvent( EventType::InputContextInputMethodActivated, EventWatcherPhase::Default, @@ -236,7 +266,14 @@ AndroidFrontend::AndroidFrontend(Instance *instance) auto &e = static_cast(event); switch (e.component()) { case UserInterfaceComponent::InputPanel: { - if (activeIC_) activeIC_->updateInputPanel(); + if (activeIC_) { + activeIC_->updateInputPanel(); + if (pagingMode_ == 0) { + activeIC_->updateCandidatesBulk(); + } else { + activeIC_->updateCandidatesPaged(); + } + } break; } case UserInterfaceComponent::StatusArea: { @@ -368,6 +405,14 @@ void AndroidFrontend::showToast(const std::string &s) { toastCallback(s); } +void AndroidFrontend::setCandidatePagingMode(const int mode) { + pagingMode_ = mode; +} + +void AndroidFrontend::updatePagedCandidate(const PagedCandidateEntity &paged) { + pagedCandidateCallback(paged); +} + void AndroidFrontend::setCommitStringCallback(const CommitStringCallback &callback) { commitStringCallback = callback; } @@ -400,6 +445,10 @@ void AndroidFrontend::setToastCallback(const ToastCallback &callback) { toastCallback = callback; } +void AndroidFrontend::setPagedCandidateCallback(const PagedCandidateCallback &callback) { + pagedCandidateCallback = callback; +} + class AndroidFrontendFactory : public AddonFactory { public: AddonInstance *create(AddonManager *manager) override { diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.h b/app/src/main/cpp/androidfrontend/androidfrontend.h index 9fcb2dbbe..9934ef8e7 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend.h @@ -2,8 +2,9 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors */ -#ifndef _FCITX5_ANDROID_ANDROIDFRONTEND_H_ -#define _FCITX5_ANDROID_ANDROIDFRONTEND_H_ + +#ifndef FCITX5_ANDROID_ANDROIDFRONTEND_H +#define FCITX5_ANDROID_ANDROIDFRONTEND_H #include #include @@ -27,6 +28,7 @@ class AndroidFrontend : public AddonInstance { void updateClientPreedit(const Text &clientPreedit); void updateInputPanel(const Text &preedit, const Text &auxUp, const Text &auxDown); void releaseInputContext(const int uid); + void updatePagedCandidate(const PagedCandidateEntity &paged); void keyEvent(const Key &key, bool isRelease, const int timestamp); void forwardKey(const Key &key, bool isRelease); @@ -44,6 +46,7 @@ class AndroidFrontend : public AddonInstance { void triggerCandidateAction(const int idx, const int actionIdx); void deleteSurrounding(const int before, const int after); void showToast(const std::string &s); + void setCandidatePagingMode(const int mode); void setCandidateListCallback(const CandidateListCallback &callback); void setCommitStringCallback(const CommitStringCallback &callback); void setPreeditCallback(const ClientPreeditCallback &callback); @@ -53,6 +56,7 @@ class AndroidFrontend : public AddonInstance { void setStatusAreaUpdateCallback(const StatusAreaUpdateCallback &callback); void setDeleteSurroundingCallback(const DeleteSurroundingCallback &callback); void setToastCallback(const ToastCallback &callback); + void setPagedCandidateCallback(const PagedCandidateCallback &callback); private: FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, keyEvent); @@ -69,6 +73,7 @@ class AndroidFrontend : public AddonInstance { FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, getCandidateActions); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, triggerCandidateAction); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, showToast); + FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidatePagingMode); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidateListCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCommitStringCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setPreeditCallback); @@ -78,12 +83,14 @@ class AndroidFrontend : public AddonInstance { FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setStatusAreaUpdateCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setDeleteSurroundingCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setToastCallback); + FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setPagedCandidateCallback); Instance *instance_; FocusGroup focusGroup_; AndroidInputContext *activeIC_; InputContextCache icCache_; std::vector>> eventHandlers_; + int pagingMode_; CandidateListCallback candidateListCallback = [](const std::vector &, const int) {}; CommitStringCallback commitStringCallback = [](const std::string &, const int) {}; @@ -94,7 +101,8 @@ class AndroidFrontend : public AddonInstance { StatusAreaUpdateCallback statusAreaUpdateCallback = [] {}; DeleteSurroundingCallback deleteSurroundingCallback = [](const int, const int) {}; ToastCallback toastCallback = [](const std::string &) {}; + PagedCandidateCallback pagedCandidateCallback = [](const PagedCandidateEntity &) {}; }; } // namespace fcitx -#endif //_FCITX5_ANDROID_ANDROIDFRONTEND_H_ +#endif //FCITX5_ANDROID_ANDROIDFRONTEND_H diff --git a/app/src/main/cpp/androidfrontend/androidfrontend_public.h b/app/src/main/cpp/androidfrontend/androidfrontend_public.h index 8fb141ba2..d144c9873 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend_public.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend_public.h @@ -10,6 +10,8 @@ #include #include +#include "../helper-types.h" + typedef std::function &, const int)> CandidateListCallback; typedef std::function CommitStringCallback; typedef std::function ClientPreeditCallback; @@ -19,6 +21,7 @@ typedef std::function InputMethodChangeCallback; typedef std::function StatusAreaUpdateCallback; typedef std::function DeleteSurroundingCallback; typedef std::function ToastCallback; +typedef std::function PagedCandidateCallback; FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, keyEvent, void(const fcitx::Key &, bool isRelease, const int timestamp)) @@ -62,6 +65,9 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, triggerCandidateAction, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, showToast, void(const std::string &)) +FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCandidatePagingMode, + void(const int)) + FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCandidateListCallback, void(const CandidateListCallback &)) @@ -89,4 +95,7 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setDeleteSurroundingCallback, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setToastCallback, void(const ToastCallback &)) +FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setPagedCandidateCallback, + void(const PagedCandidateCallback &)) + #endif // FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H diff --git a/app/src/main/cpp/helper-types.h b/app/src/main/cpp/helper-types.h index 169eda4d7..c28960a50 100644 --- a/app/src/main/cpp/helper-types.h +++ b/app/src/main/cpp/helper-types.h @@ -9,6 +9,11 @@ #include #include #include +#include +#include +#include + +#include class InputMethodStatus { public: @@ -102,4 +107,36 @@ class CandidateActionEntity { isChecked(act.isChecked()) {} }; +class CandidateEntity { +public: + std::string label; + std::string text; + std::string comment; + + explicit CandidateEntity(const fcitx::CandidateWord &c, const fcitx::Text &label) : + label(label.toString()), + text(c.text().toString()), + comment(c.comment().toString()) {} +}; + +class PagedCandidateEntity { +public: + std::vector candidates; + int cursorIndex; + fcitx::CandidateLayoutHint layoutHint; + bool hasPrev; + bool hasNext; + + explicit PagedCandidateEntity(std::vector candidates, + int cursorIndex, + fcitx::CandidateLayoutHint layoutHint, + bool hasPrev, + bool hasNext) : + candidates(std::move(candidates)), + cursorIndex(cursorIndex), + layoutHint(layoutHint), + hasPrev(hasPrev), + hasNext(hasNext) {} +}; + #endif //FCITX5_ANDROID_HELPER_TYPES_H diff --git a/app/src/main/cpp/jni-utils.h b/app/src/main/cpp/jni-utils.h index 701b58fba..6807b531d 100644 --- a/app/src/main/cpp/jni-utils.h +++ b/app/src/main/cpp/jni-utils.h @@ -141,6 +141,9 @@ class GlobalRefSingleton { jclass CandidateAction; jmethodID CandidateActionInit; + jclass Candidate; + jmethodID CandidateInit; + explicit GlobalRefSingleton(JavaVM *jvm_) : jvm(jvm_) { JNIEnv *env; jvm->AttachCurrentThread(&env, nullptr); @@ -190,6 +193,9 @@ class GlobalRefSingleton { CandidateAction = reinterpret_cast(env->NewGlobalRef(env->FindClass("org/fcitx/fcitx5/android/core/CandidateAction"))); CandidateActionInit = env->GetMethodID(CandidateAction, "", "(ILjava/lang/String;ZLjava/lang/String;ZZ)V"); + + Candidate = reinterpret_cast(env->NewGlobalRef(env->FindClass("org/fcitx/fcitx5/android/core/FcitxEvent$Candidate"))); + CandidateInit = env->GetMethodID(Candidate, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); } [[nodiscard]] JEnv AttachEnv() const { return JEnv(jvm); } diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index 401a874d9..f94d5b208 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -427,6 +427,10 @@ class Fcitx { return p_frontend->call(idx, actionIdx); } + void setCandidatePagingMode(int mode) { + return p_frontend->call(mode); + } + void save() { p_instance->save(); } @@ -678,6 +682,25 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx( env->SetObjectArrayElement(vararg, 0, intArray); env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 8, *vararg); }; + auto pagedCandidateCallback = [](const PagedCandidateEntity &paged) { + auto env = GlobalRef->AttachEnv(); + const int size = static_cast(paged.candidates.size()); + auto candidatesArray = JRef(env, env->NewObjectArray(size, GlobalRef->Candidate, nullptr)); + for (int i = 0; i < size; ++i) { + env->SetObjectArrayElement(candidatesArray, i, candidateEntityToObject(env, paged.candidates[i])); + } + auto cursorIndex = JRef(env, env->NewObject(GlobalRef->Integer, GlobalRef->IntegerInit, paged.cursorIndex)); + auto layoutHint = JRef(env, env->NewObject(GlobalRef->Integer, GlobalRef->IntegerInit, static_cast(paged.layoutHint))); + auto hasPrev = JRef(env, env->NewObject(GlobalRef->Boolean, GlobalRef->BooleanInit, paged.hasPrev)); + auto hasNext = JRef(env, env->NewObject(GlobalRef->Boolean, GlobalRef->BooleanInit, paged.hasNext)); + auto vararg = JRef(env, env->NewObjectArray(5, GlobalRef->Object, nullptr)); + env->SetObjectArrayElement(vararg, 0, candidatesArray); + env->SetObjectArrayElement(vararg, 1, cursorIndex); + env->SetObjectArrayElement(vararg, 2, layoutHint); + env->SetObjectArrayElement(vararg, 3, hasPrev); + env->SetObjectArrayElement(vararg, 4, hasNext); + env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 9, *vararg); + }; auto toastCallback = [](const std::string &s) { auto env = GlobalRef->AttachEnv(); env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->ShowToast, *JString(env, s)); @@ -697,6 +720,7 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx( androidfrontend->template call(imChangeCallback); androidfrontend->template call(statusAreaUpdateCallback); androidfrontend->template call(deleteSurroundingCallback); + androidfrontend->template call(pagedCandidateCallback); androidfrontend->template call(toastCallback); }); FCITX_INFO() << "Finishing startup"; @@ -1050,6 +1074,13 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_triggerFcitxCandidateAction(JNIEnv *env Fcitx::Instance().triggerCandidateAction(idx, action_idx); } +extern "C" +JNIEXPORT void JNICALL +Java_org_fcitx_fcitx5_android_core_Fcitx_setFcitxCandidatePagingMode(JNIEnv *env, jclass clazz, jint mode) { + RETURN_IF_NOT_RUNNING + Fcitx::Instance().setCandidatePagingMode(mode); +} + extern "C" JNIEXPORT void JNICALL Java_org_fcitx_fcitx5_android_core_Fcitx_loopOnce(JNIEnv *env, jclass clazz) { diff --git a/app/src/main/cpp/object-conversion.h b/app/src/main/cpp/object-conversion.h index 1486e2206..aa4155095 100644 --- a/app/src/main/cpp/object-conversion.h +++ b/app/src/main/cpp/object-conversion.h @@ -173,4 +173,13 @@ jobject fcitxCandidateActionToObject(JNIEnv *env, const CandidateActionEntity &a return obj; } +jobject candidateEntityToObject(JNIEnv *env, const CandidateEntity &c) { + auto obj = env->NewObject(GlobalRef->Candidate, GlobalRef->CandidateInit, + *JString(env, c.label), + *JString(env, c.text), + *JString(env, c.comment) + ); + return obj; +} + #endif //FCITX5_ANDROID_OBJECT_CONVERSION_H diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt index 0d98f290b..adafebb06 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt @@ -169,6 +169,9 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { override suspend fun triggerCandidateAction(idx: Int, actionIdx: Int) = withFcitxContext { triggerFcitxCandidateAction(idx, actionIdx) } + override suspend fun setCandidatePagingMode(mode: Int) = + withFcitxContext { setFcitxCandidatePagingMode(mode) } + init { if (lifecycle.currentState != FcitxLifecycle.State.STOPPED) throw IllegalAccessException("Fcitx5 has already been created!") @@ -339,6 +342,9 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { @JvmStatic external fun triggerFcitxCandidateAction(idx: Int, actionIdx: Int) + @JvmStatic + external fun setFcitxCandidatePagingMode(mode: Int) + @JvmStatic external fun loopOnce() diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt index 98a03f66f..4c3e55c8f 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt @@ -102,4 +102,6 @@ interface FcitxAPI { suspend fun getCandidateActions(idx: Int): Array suspend fun triggerCandidateAction(idx: Int, actionIdx: Int) + suspend fun setCandidatePagingMode(mode: Int) + } \ No newline at end of file diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt index 2db3780dc..0838a8cac 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt @@ -6,6 +6,8 @@ package org.fcitx.fcitx5.android.core sealed class FcitxEvent(open val data: T) { + data class Candidate(val label: String, val text: String, val comment: String) + abstract val eventType: EventType data class CandidateListEvent(override val data: Data) : @@ -121,11 +123,34 @@ sealed class FcitxEvent(open val data: T) { data class DeleteSurroundingEvent(override val data: Data) : FcitxEvent(data) { - override val eventType = EventType.DeleteSurrounding + override val eventType = EventType.DeleteSurrounding data class Data(val before: Int, val after: Int) } + data class PagedCandidateEvent(override val data: Data) : + FcitxEvent(data) { + + override val eventType = EventType.PagedCandidate + + enum class LayoutHint(value: Int) { + NotSet(0), Vertical(1), Horizontal(2); + + companion object { + private val Types = entries.toTypedArray() + fun of(value: Int) = Types[value] + } + } + + data class Data( + val candidates: Array = emptyArray(), + val cursorIndex: Int = -1, + val layoutHint: LayoutHint = LayoutHint.NotSet, + val hasPrev: Boolean = false, + val hasNext: Boolean = false + ) + } + data class UnknownEvent(override val data: Array) : FcitxEvent>(data) { override val eventType = EventType.Unknown @@ -156,12 +181,13 @@ sealed class FcitxEvent(open val data: T) { Change, StatusArea, DeleteSurrounding, + PagedCandidate, Unknown } companion object { - private val Types = EventType.values() + private val Types = EventType.entries.toTypedArray() @Suppress("UNCHECKED_CAST") fun create(type: Int, params: Array) = @@ -206,6 +232,15 @@ sealed class FcitxEvent(open val data: T) { EventType.DeleteSurrounding -> (params[0] as IntArray).let { DeleteSurroundingEvent(DeleteSurroundingEvent.Data(it[0], it[1])) } + EventType.PagedCandidate -> PagedCandidateEvent( + PagedCandidateEvent.Data( + params[0] as Array, + params[1] as Int, + PagedCandidateEvent.LayoutHint.of(params[2] as Int), + params[3] as Boolean, + params[4] as Boolean + ) + ) else -> UnknownEvent(params) } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt index a669d0f2f..4f04c3ca1 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt @@ -7,12 +7,16 @@ package org.fcitx.fcitx5.android.input import android.annotation.SuppressLint import android.graphics.Color +import android.graphics.drawable.GradientDrawable import android.view.View import android.view.ViewGroup import androidx.annotation.Size import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope +import com.google.android.flexbox.FlexDirection +import com.google.android.flexbox.FlexWrap +import com.google.android.flexbox.FlexboxLayout import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.fcitx.fcitx5.android.R @@ -29,14 +33,10 @@ import splitties.views.dsl.constraintlayout.lParams import splitties.views.dsl.constraintlayout.startOfParent import splitties.views.dsl.constraintlayout.topOfParent import splitties.views.dsl.core.add -import splitties.views.dsl.core.endMargin -import splitties.views.dsl.core.horizontalLayout -import splitties.views.dsl.core.lParams import splitties.views.dsl.core.withTheme import splitties.views.dsl.core.wrapContent import splitties.views.horizontalPadding import splitties.views.verticalPadding -import kotlin.math.min @SuppressLint("ViewConstructor") class CandidatesView( @@ -62,7 +62,7 @@ class CandidatesView( private var eventHandlerJob: Job? = null private var inputPanel = FcitxEvent.InputPanelEvent.Data() - private var candidates = FcitxEvent.CandidateListEvent.Data() + private var paged = FcitxEvent.PagedCandidateEvent.Data() /** * horizontal, bottom, top @@ -74,29 +74,35 @@ class CandidatesView( override val bkgColor = Color.TRANSPARENT } - // TODO ui orientation from fcitx - private val candidatesUi = horizontalLayout { + // TODO is it better to use RecyclerView + FlexboxLayoutManager ? + private val candidatesLayout = FlexboxLayout(ctx).apply { horizontalPadding = dp(8) + flexDirection = FlexDirection.ROW + flexWrap = FlexWrap.WRAP + // DividerVertical of FlexboxLayout is the vertical divider line between row of items + showDividerVertical = FlexboxLayout.SHOW_DIVIDER_MIDDLE + dividerDrawableVertical = GradientDrawable().apply { + setSize(dp(4), 0) + } } private fun handleFcitxEvent(it: FcitxEvent<*>) { when (it) { - // TODO make a new candidate page event - is FcitxEvent.CandidateListEvent -> { - candidates = it.data - updateUI() - } is FcitxEvent.InputPanelEvent -> { inputPanel = it.data updateUI() } + is FcitxEvent.PagedCandidateEvent -> { + paged = it.data + updateUI() + } else -> {} } } private fun evaluateVisibility(): Boolean { return inputPanel.preedit.isNotEmpty() || - candidates.total > 0 || + paged.candidates.isNotEmpty() || inputPanel.auxUp.isNotEmpty() || inputPanel.auxDown.isNotEmpty() } @@ -114,17 +120,20 @@ class CandidatesView( } private fun updateCandidates() { - candidatesUi.apply { - removeAllViews() - val limit = min(candidates.candidates.size, 5) - for (i in 0.. FlexDirection.COLUMN + else -> FlexDirection.ROW + } + paged.candidates.forEach { + val item = CandidateItemUi(ctx, theme).apply { + text.textSize = 16f + // TODO different font for comment + text.text = "${it.label}${it.text} ${it.comment}" } + candidatesLayout.addView(item.root, FlexboxLayout.LayoutParams(-2, -2)) } + // TODO paging indicator } private fun updatePosition() { @@ -163,7 +172,7 @@ class CandidatesView( topOfParent() startOfParent() }) - add(candidatesUi, lParams(wrapContent, wrapContent) { + add(candidatesLayout, lParams(wrapContent, wrapContent) { below(preeditUi.root) startOfParent() bottomOfParent() diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt index a4a97ce7e..b9c5cbbc8 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt @@ -607,10 +607,12 @@ class FcitxInputMethodService : LifecycleInputMethodService() { if (!restarting && !super.onEvaluateInputViewShown()) { currentInputConnection?.monitorCursorAnchor() } + val useVirtualKeyboard = super.onEvaluateInputViewShown() postFcitxJob { focus(true) + setCandidatePagingMode(if (useVirtualKeyboard) 0 else 1) } - if (super.onEvaluateInputViewShown()) { + if (useVirtualKeyboard) { candidatesView?.handleEvents = false inputView?.handleEvents = true inputView?.visibility = View.VISIBLE From 083611e6b5e84fefbc082d96073e368c99d171ee Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 22 Aug 2024 00:40:02 +0800 Subject: [PATCH 059/181] Disable word hint for physical keyboard by default --- app/src/main/cpp/androidkeyboard/androidkeyboard.cpp | 8 ++++---- app/src/main/cpp/androidkeyboard/androidkeyboard.h | 10 ++++++---- app/src/main/cpp/po/fcitx5-android.pot | 3 +++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/src/main/cpp/androidkeyboard/androidkeyboard.cpp b/app/src/main/cpp/androidkeyboard/androidkeyboard.cpp index 2ef6485fc..2a0915bea 100644 --- a/app/src/main/cpp/androidkeyboard/androidkeyboard.cpp +++ b/app/src/main/cpp/androidkeyboard/androidkeyboard.cpp @@ -108,8 +108,7 @@ void AndroidKeyboardEngine::keyEvent(const InputMethodEntry &entry, KeyEvent &ev } if (key.isLAZ() || key.isUAZ() || validSym || (!buffer.empty() && key.checkKeyList(FCITX_HYPHEN_APOS))) { - auto text = Key::keySymToUTF8(key.sym()); - if (updateBuffer(inputContext, text)) { + if (updateBuffer(inputContext, event)) { return event.filterAndAccept(); } } @@ -283,7 +282,7 @@ void AndroidKeyboardEngine::updateUI(InputContext *inputContext) { inputContext->updateUserInterface(UserInterfaceComponent::InputPanel); } -bool AndroidKeyboardEngine::updateBuffer(InputContext *inputContext, const std::string &chr) { +bool AndroidKeyboardEngine::updateBuffer(InputContext *inputContext, const KeyEvent& event) { auto *entry = instance_->inputMethodEntry(inputContext); if (!entry) { return false; @@ -292,6 +291,7 @@ bool AndroidKeyboardEngine::updateBuffer(InputContext *inputContext, const std:: auto *state = inputContext->propertyFor(&factory_); // word hint is disabled, input is password, or language not supported if (!*config_.enableWordHint || + (!*config_.hintOnPhysicalKeyboard && !event.isVirtual()) || (*config_.editorControlledWordHint && inputContext->capabilityFlags().test(CapabilityFlag::NoSpellCheck)) || inputContext->capabilityFlags().test(CapabilityFlag::Password) || !supportHint(entry->languageCode())) { @@ -305,7 +305,7 @@ bool AndroidKeyboardEngine::updateBuffer(InputContext *inputContext, const std:: buffer.type(preedit); } - buffer.type(chr); + buffer.type(Key::keySymToUTF8(event.key().sym())); if (buffer.size() >= MaxBufferSize) { commitBuffer(inputContext); diff --git a/app/src/main/cpp/androidkeyboard/androidkeyboard.h b/app/src/main/cpp/androidkeyboard/androidkeyboard.h index 0f35e7466..b365ac527 100644 --- a/app/src/main/cpp/androidkeyboard/androidkeyboard.h +++ b/app/src/main/cpp/androidkeyboard/androidkeyboard.h @@ -30,6 +30,8 @@ FCITX_CONFIGURATION( AndroidKeyboardEngineConfig, Option enableWordHint{this, "EnableWordHint", _("Enable word hint"), true}; + Option + hintOnPhysicalKeyboard{this, "WordHintOnPhysicalKeyboard", _("Enable word hint when using physical keyboard"), false}; Option editorControlledWordHint{this, "EditorControlledWordHint", _("Disable word hint based on editor attributes"), true}; Option @@ -95,10 +97,10 @@ class AndroidKeyboardEngine final : public InputMethodEngineV3 { auto factory() { return &factory_; } - // Return true if chr is pushed to buffer. - // Return false if chr will be skipped by buffer, usually this means caller - // need to call commit buffer and forward chr manually. - bool updateBuffer(InputContext *inputContext, const std::string &chr); + // Return true if event is pushed to buffer. + // Return false if event will be skipped by buffer, usually this means caller + // need to call commit buffer and forward event manually. + bool updateBuffer(InputContext *inputContext, const KeyEvent& event); // Commit current buffer, also reset the state. // See also preeditString(). diff --git a/app/src/main/cpp/po/fcitx5-android.pot b/app/src/main/cpp/po/fcitx5-android.pot index 31396fa35..0efb63065 100644 --- a/app/src/main/cpp/po/fcitx5-android.pot +++ b/app/src/main/cpp/po/fcitx5-android.pot @@ -23,6 +23,9 @@ msgstr "" msgid "Enable word hint" msgstr "" +msgid "Enable word hint when using physical keyboard" +msgstr "" + msgid "Disable word hint based on editor attributes" msgstr "" From a21fe4b5b7e6af8685a17e192e5b1a72f53000b4 Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 22 Aug 2024 00:49:53 +0800 Subject: [PATCH 060/181] Avoid creating unnecessary empty PagedCandidate* objects --- .../cpp/androidfrontend/androidfrontend.cpp | 3 +- app/src/main/cpp/helper-types.h | 8 +++ app/src/main/cpp/native-lib.cpp | 5 ++ .../fcitx/fcitx5/android/core/FcitxEvent.kt | 61 ++++++++++++++----- .../fcitx5/android/input/CandidatesView.kt | 2 +- 5 files changed, 62 insertions(+), 17 deletions(-) diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.cpp b/app/src/main/cpp/androidfrontend/androidfrontend.cpp index 092bda231..be00e467e 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.cpp +++ b/app/src/main/cpp/androidfrontend/androidfrontend.cpp @@ -102,8 +102,7 @@ class AndroidInputContext : public InputContextV2 { void updateCandidatesPaged() { const auto &list = inputPanel().candidateList(); if (!list) { - PagedCandidateEntity empty({}, -1, CandidateLayoutHint::NotSet, false, false); - frontend_->updatePagedCandidate(empty); + frontend_->updatePagedCandidate(PagedCandidateEntity::Empty); return; } int cursorIndex = list->cursorIndex(); diff --git a/app/src/main/cpp/helper-types.h b/app/src/main/cpp/helper-types.h index c28960a50..77c6b4dd0 100644 --- a/app/src/main/cpp/helper-types.h +++ b/app/src/main/cpp/helper-types.h @@ -137,6 +137,14 @@ class PagedCandidateEntity { layoutHint(layoutHint), hasPrev(hasPrev), hasNext(hasNext) {} + + static PagedCandidateEntity Empty; + +private: + PagedCandidateEntity() : + candidates({}), cursorIndex(-1), layoutHint(fcitx::CandidateLayoutHint::NotSet), hasPrev(false), hasNext(false) {} }; +PagedCandidateEntity PagedCandidateEntity::Empty = PagedCandidateEntity(); + #endif //FCITX5_ANDROID_HELPER_TYPES_H diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index f94d5b208..af481af42 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -685,6 +685,11 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx( auto pagedCandidateCallback = [](const PagedCandidateEntity &paged) { auto env = GlobalRef->AttachEnv(); const int size = static_cast(paged.candidates.size()); + if (size == 0) { + auto vararg = JRef(env, env->NewObjectArray(0, GlobalRef->Object, nullptr)); + env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 9, *vararg); + return; + } auto candidatesArray = JRef(env, env->NewObjectArray(size, GlobalRef->Candidate, nullptr)); for (int i = 0; i < size; ++i) { env->SetObjectArrayElement(candidatesArray, i, candidateEntityToObject(env, paged.candidates[i])); diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt index 0838a8cac..1c119fab2 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt @@ -143,12 +143,41 @@ sealed class FcitxEvent(open val data: T) { } data class Data( - val candidates: Array = emptyArray(), - val cursorIndex: Int = -1, - val layoutHint: LayoutHint = LayoutHint.NotSet, - val hasPrev: Boolean = false, - val hasNext: Boolean = false - ) + val candidates: Array, + val cursorIndex: Int, + val layoutHint: LayoutHint, + val hasPrev: Boolean, + val hasNext: Boolean + ) { + companion object { + @Suppress("BooleanLiteralArgument") + val Empty = Data(emptyArray(), -1, LayoutHint.NotSet, false, false) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Data + + if (!candidates.contentEquals(other.candidates)) return false + if (cursorIndex != other.cursorIndex) return false + if (layoutHint != other.layoutHint) return false + if (hasPrev != other.hasPrev) return false + if (hasNext != other.hasNext) return false + + return true + } + + override fun hashCode(): Int { + var result = candidates.contentHashCode() + result = 31 * result + cursorIndex + result = 31 * result + layoutHint.hashCode() + result = 31 * result + hasPrev.hashCode() + result = 31 * result + hasNext.hashCode() + return result + } + } } data class UnknownEvent(override val data: Array) : FcitxEvent>(data) { @@ -232,15 +261,19 @@ sealed class FcitxEvent(open val data: T) { EventType.DeleteSurrounding -> (params[0] as IntArray).let { DeleteSurroundingEvent(DeleteSurroundingEvent.Data(it[0], it[1])) } - EventType.PagedCandidate -> PagedCandidateEvent( - PagedCandidateEvent.Data( - params[0] as Array, - params[1] as Int, - PagedCandidateEvent.LayoutHint.of(params[2] as Int), - params[3] as Boolean, - params[4] as Boolean + EventType.PagedCandidate -> if (params.isEmpty()) { + PagedCandidateEvent(PagedCandidateEvent.Data.Empty) + } else { + PagedCandidateEvent( + PagedCandidateEvent.Data( + params[0] as Array, + params[1] as Int, + PagedCandidateEvent.LayoutHint.of(params[2] as Int), + params[3] as Boolean, + params[4] as Boolean + ) ) - ) + } else -> UnknownEvent(params) } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt index 4f04c3ca1..63051c823 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt @@ -62,7 +62,7 @@ class CandidatesView( private var eventHandlerJob: Job? = null private var inputPanel = FcitxEvent.InputPanelEvent.Data() - private var paged = FcitxEvent.PagedCandidateEvent.Data() + private var paged = FcitxEvent.PagedCandidateEvent.Data.Empty /** * horizontal, bottom, top From 4612eb57df1ed04175c701c1d451735f6fd92337 Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 22 Aug 2024 00:53:13 +0800 Subject: [PATCH 061/181] CadidatesView related InputMethodService improvements - Fix crash when modifying settings before onCreateInputView - Call `requestShowSelf` when pressing physical keyboard --- .../android/input/FcitxInputMethodService.kt | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt index b9c5cbbc8..dc70a0277 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt @@ -24,6 +24,7 @@ import android.view.inputmethod.CursorAnchorInfo import android.view.inputmethod.EditorInfo import android.view.inputmethod.InlineSuggestionsRequest import android.view.inputmethod.InlineSuggestionsResponse +import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodSubtype import android.widget.FrameLayout import android.widget.inline.InlinePresentationSpec @@ -88,6 +89,9 @@ class FcitxInputMethodService : LifecycleInputMethodService() { private var inputView: InputView? = null private var candidatesView: CandidatesView? = null + private var startedInput = false + private var startedInputView = false + private var capabilityFlags = CapabilityFlags.DefaultFlags private val selection = CursorTracker() @@ -170,6 +174,8 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } } super.onCreate() + decorView = window.window!!.decorView + contentView = decorView.findViewById(android.R.id.content) } private fun handleFcitxEvent(event: FcitxEvent<*>) { @@ -424,8 +430,6 @@ class FcitxInputMethodService : LifecycleInputMethodService() { // during each onConfigurationChanged period. // That is, onCreateInputView would be called again, after system dark mode changes, // or screen orientation changes. - decorView = window.window!!.decorView - contentView = decorView.findViewById(android.R.id.content) candidatesView = CandidatesView(this, fcitx, ThemeManager.activeTheme) // put CandidatesView directly under content view contentView.addView(candidatesView) @@ -463,7 +467,6 @@ class FcitxInputMethodService : LifecycleInputMethodService() { private var inputViewLocation = intArrayOf(0, 0) override fun onComputeInsets(outInsets: Insets) { - Timber.d("onComputeInsets") if (candidatesView?.handleEvents == true) { val h = decorView.height outInsets.apply { @@ -516,6 +519,21 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + // request to show floating CandidatesView when pressing physical keyboard + if (!startedInputView && event.displayLabel.isLetterOrDigit() && !super.onEvaluateInputViewShown()) { + postFcitxJob { + focus(true) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + requestShowSelf(InputMethodManager.SHOW_IMPLICIT) + } else { + @Suppress("DEPRECATION") + inputMethodManager.showSoftInputFromInputMethod( + window.window!!.attributes.token, + InputMethodManager.SHOW_FORCED + ) + } + } return forwardKeyEvent(event) || super.onKeyDown(keyCode, event) } @@ -577,6 +595,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } override fun onStartInput(attribute: EditorInfo, restarting: Boolean) { + startedInput = true // update selection as soon as possible // sometimes when restarting input, onUpdateSelection happens before onStartInput, and // initialSel{Start,End} is outdated. but it's the client app's responsibility to send @@ -601,13 +620,14 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } override fun onStartInputView(info: EditorInfo, restarting: Boolean) { + startedInputView = true Timber.d("onStartInputView: restarting=$restarting") + val useVirtualKeyboard = super.onEvaluateInputViewShown() // monitor cursor anchor only when needed, ie // InputView just becomes visible && using floating CandidatesView - if (!restarting && !super.onEvaluateInputViewShown()) { + if (!restarting && !useVirtualKeyboard) { currentInputConnection?.monitorCursorAnchor() } - val useVirtualKeyboard = super.onEvaluateInputViewShown() postFcitxJob { focus(true) setCandidatePagingMode(if (useVirtualKeyboard) 0 else 1) @@ -866,6 +886,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } override fun onFinishInputView(finishingInput: Boolean) { + startedInputView = false Timber.d("onFinishInputView: finishingInput=$finishingInput") currentInputConnection?.run { finishComposingText() @@ -880,6 +901,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() { } override fun onFinishInput() { + startedInput = false Timber.d("onFinishInput") capabilityFlags = CapabilityFlags.DefaultFlags } From bf8b37b0b100977dded44f1d6f8e3478d0ec1b3f Mon Sep 17 00:00:00 2001 From: Rocka Date: Thu, 22 Aug 2024 00:55:26 +0800 Subject: [PATCH 062/181] Pagination indicator for CandidatesView Also fixes flicker when repositioning --- .../fcitx5/android/input/CandidatesView.kt | 65 +++++++++++++++++-- .../android/input/preedit/PreeditComponent.kt | 7 +- .../fcitx5/android/input/preedit/PreeditUi.kt | 2 - .../drawable/ic_baseline_arrow_left_24.xml | 9 +++ .../drawable/ic_baseline_arrow_right_24.xml | 9 +++ 5 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_arrow_left_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_arrow_right_24.xml diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt index 63051c823..f0e0a2dca 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt @@ -6,14 +6,17 @@ package org.fcitx.fcitx5.android.input import android.annotation.SuppressLint +import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.view.View import android.view.ViewGroup +import android.widget.ImageView +import androidx.annotation.DrawableRes import androidx.annotation.Size import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope +import com.google.android.flexbox.AlignItems import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayout @@ -26,16 +29,24 @@ import org.fcitx.fcitx5.android.data.theme.Theme import org.fcitx.fcitx5.android.input.candidates.CandidateItemUi import org.fcitx.fcitx5.android.input.preedit.PreeditUi import splitties.dimensions.dp +import splitties.resources.drawable import splitties.views.backgroundColor +import splitties.views.dsl.constraintlayout.before import splitties.views.dsl.constraintlayout.below import splitties.views.dsl.constraintlayout.bottomOfParent +import splitties.views.dsl.constraintlayout.centerVertically +import splitties.views.dsl.constraintlayout.constraintLayout +import splitties.views.dsl.constraintlayout.endOfParent import splitties.views.dsl.constraintlayout.lParams +import splitties.views.dsl.constraintlayout.matchConstraints import splitties.views.dsl.constraintlayout.startOfParent import splitties.views.dsl.constraintlayout.topOfParent import splitties.views.dsl.core.add +import splitties.views.dsl.core.imageView import splitties.views.dsl.core.withTheme import splitties.views.dsl.core.wrapContent import splitties.views.horizontalPadding +import splitties.views.imageDrawable import splitties.views.verticalPadding @SuppressLint("ViewConstructor") @@ -70,6 +81,8 @@ class CandidatesView( private val anchorPosition = floatArrayOf(0f, 0f, 0f) private val parentSize = floatArrayOf(0f, 0f) + private var shouldUpdatePosition = false + private val preeditUi = object : PreeditUi(ctx, theme) { override val bkgColor = Color.TRANSPARENT } @@ -84,6 +97,37 @@ class CandidatesView( dividerDrawableVertical = GradientDrawable().apply { setSize(dp(4), 0) } + // update position after layout child views and before drawing to avoid flicker + viewTreeObserver.addOnPreDrawListener { + if (shouldUpdatePosition) { + updatePosition() + } + true + } + } + + private fun createIcon(@DrawableRes icon: Int) = imageView { + imageTintList = ColorStateList.valueOf(theme.keyTextColor) + imageDrawable = drawable(icon) + scaleType = ImageView.ScaleType.CENTER_CROP + } + + private val prevIcon = createIcon(R.drawable.ic_baseline_arrow_left_24) + private val nextIcon = createIcon(R.drawable.ic_baseline_arrow_right_24) + + private val paginationLayout = constraintLayout { + add(nextIcon, lParams(dp(10), matchConstraints) { + centerVertically() + endOfParent() + }) + add(prevIcon, lParams(dp(10), matchConstraints) { + centerVertically() + before(nextIcon) + }) + layoutParams = FlexboxLayout.LayoutParams(wrapContent, wrapContent).apply { + flexGrow = 1f + alignSelf = AlignItems.STRETCH + } } private fun handleFcitxEvent(it: FcitxEvent<*>) { @@ -109,11 +153,10 @@ class CandidatesView( private fun updateUI() { if (evaluateVisibility()) { - visibility = View.VISIBLE preeditUi.update(inputPanel) - preeditUi.root.isVisible = preeditUi.visible + preeditUi.root.visibility = if (preeditUi.visible) View.VISIBLE else View.GONE updateCandidates() - updatePosition() + visibility = View.VISIBLE } else { visibility = View.GONE } @@ -125,15 +168,24 @@ class CandidatesView( FcitxEvent.PagedCandidateEvent.LayoutHint.Vertical -> FlexDirection.COLUMN else -> FlexDirection.ROW } - paged.candidates.forEach { + paged.candidates.forEachIndexed { i, it -> val item = CandidateItemUi(ctx, theme).apply { text.textSize = 16f // TODO different font for comment text.text = "${it.label}${it.text} ${it.comment}" + if (i == paged.cursorIndex) { + root.backgroundColor = theme.genericActiveBackgroundColor + text.setTextColor(theme.genericActiveForegroundColor) + } } candidatesLayout.addView(item.root, FlexboxLayout.LayoutParams(-2, -2)) } - // TODO paging indicator + if (paged.hasPrev || paged.hasNext) { + prevIcon.alpha = if (paged.hasPrev) 1f else 0.4f + nextIcon.alpha = if (paged.hasNext) 1f else 0.4f + candidatesLayout.addView(paginationLayout) + } + shouldUpdatePosition = true } private fun updatePosition() { @@ -144,6 +196,7 @@ class CandidatesView( translationX = if (horizontal + selfWidth > parentWidth) parentWidth - selfWidth else horizontal translationY = if (bottom + selfHeight > parentHeight) top - selfHeight else bottom + shouldUpdatePosition = false } fun updateCursorAnchor(@Size(4) anchor: FloatArray, @Size(2) parent: FloatArray) { diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditComponent.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditComponent.kt index 1e7cf2322..0ec673cc7 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditComponent.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditComponent.kt @@ -20,7 +20,12 @@ class PreeditComponent : UniqueComponent(), Dependent, InputBr private val context by manager.context() private val theme by manager.theme() - val ui by lazy { PreeditUi(context, theme) } + val ui by lazy { + PreeditUi(context, theme).apply { + root.alpha = 0.8f + root.visibility = View.INVISIBLE + } + } override fun onInputPanelUpdate(data: FcitxEvent.InputPanelEvent.Data) { ui.update(data) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditUi.kt index c3d50f407..6d5e7f799 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/preedit/PreeditUi.kt @@ -65,8 +65,6 @@ open class PreeditUi(override val ctx: Context, private val theme: Theme) : Ui { private set override val root: View = verticalLayout { - alpha = 0.8f - visibility = View.INVISIBLE add(upView, lParams()) add(downView, lParams()) } diff --git a/app/src/main/res/drawable/ic_baseline_arrow_left_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_left_24.xml new file mode 100644 index 000000000..9dcab22a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_arrow_left_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_arrow_right_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_right_24.xml new file mode 100644 index 000000000..399c9df74 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_arrow_right_24.xml @@ -0,0 +1,9 @@ + + + From 19a20e49aa84b2e1a94691a5906af1678f46cd3d Mon Sep 17 00:00:00 2001 From: Rocka Date: Fri, 23 Aug 2024 00:59:38 +0800 Subject: [PATCH 063/181] Fix physical keyboard layout selection and requestShowSelf Fcitx's input method LanguageCode format is different from SubtypeLocale, use the value would break keyboard layout selection It turns out deprecation of `InputMethodManager.SHOW_FORCED` does not apply to input method itself --- .../main/java/org/fcitx/fcitx5/android/core/SubtypeManager.kt | 1 - .../org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/SubtypeManager.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/SubtypeManager.kt index 00706ed2a..a07d60cbe 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/SubtypeManager.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/SubtypeManager.kt @@ -40,7 +40,6 @@ object SubtypeManager { .setSubtypeId(im.uniqueName.hashCode()) .setSubtypeExtraValue(im.uniqueName) .setSubtypeNameOverride(im.displayName) - .setSubtypeLocale(im.languageCode) .setSubtypeMode(MODE_KEYBOARD) .setIsAsciiCapable(im.uniqueName == IM_KEYBOARD) .build() diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt index dc70a0277..06ebc730c 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt @@ -524,10 +524,10 @@ class FcitxInputMethodService : LifecycleInputMethodService() { postFcitxJob { focus(true) } + @Suppress("DEPRECATION") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - requestShowSelf(InputMethodManager.SHOW_IMPLICIT) + requestShowSelf(InputMethodManager.SHOW_FORCED) } else { - @Suppress("DEPRECATION") inputMethodManager.showSoftInputFromInputMethod( window.window!!.attributes.token, InputMethodManager.SHOW_FORCED From b381b414312e4180416dcb1d35b7339d575e6c94 Mon Sep 17 00:00:00 2001 From: Rocka Date: Fri, 23 Aug 2024 01:03:55 +0800 Subject: [PATCH 064/181] Use RecyclerView + FlexboxLayoutManager in CandidatesView Should make updatePosition more reliable --- .../fcitx5/android/input/CandidatesView.kt | 104 ++++++++++++------ 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt index f0e0a2dca..a6e8a4b3a 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt @@ -8,7 +8,6 @@ package org.fcitx.fcitx5.android.input import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.Color -import android.graphics.drawable.GradientDrawable import android.view.View import android.view.ViewGroup import android.widget.ImageView @@ -16,10 +15,11 @@ import androidx.annotation.DrawableRes import androidx.annotation.Size import androidx.constraintlayout.widget.ConstraintLayout import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView import com.google.android.flexbox.AlignItems import com.google.android.flexbox.FlexDirection import com.google.android.flexbox.FlexWrap -import com.google.android.flexbox.FlexboxLayout +import com.google.android.flexbox.FlexboxLayoutManager import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.fcitx.fcitx5.android.R @@ -41,10 +41,12 @@ import splitties.views.dsl.constraintlayout.lParams import splitties.views.dsl.constraintlayout.matchConstraints import splitties.views.dsl.constraintlayout.startOfParent import splitties.views.dsl.constraintlayout.topOfParent +import splitties.views.dsl.core.Ui import splitties.views.dsl.core.add import splitties.views.dsl.core.imageView import splitties.views.dsl.core.withTheme import splitties.views.dsl.core.wrapContent +import splitties.views.dsl.recyclerview.recyclerView import splitties.views.horizontalPadding import splitties.views.imageDrawable import splitties.views.verticalPadding @@ -87,16 +89,67 @@ class CandidatesView( override val bkgColor = Color.TRANSPARENT } - // TODO is it better to use RecyclerView + FlexboxLayoutManager ? - private val candidatesLayout = FlexboxLayout(ctx).apply { - horizontalPadding = dp(8) - flexDirection = FlexDirection.ROW - flexWrap = FlexWrap.WRAP - // DividerVertical of FlexboxLayout is the vertical divider line between row of items - showDividerVertical = FlexboxLayout.SHOW_DIVIDER_MIDDLE - dividerDrawableVertical = GradientDrawable().apply { - setSize(dp(4), 0) + inner class UiViewHolder(val ui: Ui) : RecyclerView.ViewHolder(ui.root) + + private val candidatesAdapter = object : RecyclerView.Adapter() { + override fun getItemCount() = + paged.candidates.size + (if (paged.hasPrev || paged.hasNext) 1 else 0) + + override fun getItemViewType(position: Int) = if (position < paged.candidates.size) 0 else 1 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UiViewHolder { + return when (viewType) { + 0 -> UiViewHolder(CandidateItemUi(ctx, theme).apply { + text.textSize = 16f + }) + else -> UiViewHolder(object : Ui { + override val ctx = this@CandidatesView.ctx + override val root = this@CandidatesView.paginationLayout + }) + } + } + + override fun onBindViewHolder(holder: UiViewHolder, position: Int) { + when (getItemViewType(position)) { + 0 -> { + holder.ui as CandidateItemUi + val candidate = paged.candidates[position] + // TODO different font for comment + holder.ui.text.text = "${candidate.label}${candidate.text} ${candidate.comment}" + if (position == paged.cursorIndex) { + holder.ui.text.setTextColor(theme.genericActiveForegroundColor) + holder.ui.root.backgroundColor = theme.genericActiveBackgroundColor + } else { + holder.ui.text.setTextColor(theme.keyTextColor) + holder.ui.root.backgroundColor = Color.TRANSPARENT + } + } + else -> { + prevIcon.alpha = if (paged.hasPrev) 1f else 0.4f + nextIcon.alpha = if (paged.hasNext) 1f else 0.4f + } + } + } + } + + private val candidatesLayoutManager = object : FlexboxLayoutManager(ctx) { + init { + flexDirection = FlexDirection.ROW + flexWrap = FlexWrap.WRAP + alignItems = AlignItems.FLEX_START + } + + override fun onLayoutCompleted(state: RecyclerView.State?) { + super.onLayoutCompleted(state) + shouldUpdatePosition = true } + } + + private val recyclerView = recyclerView { + isFocusable = false + horizontalPadding = dp(8) + adapter = candidatesAdapter + layoutManager = candidatesLayoutManager // update position after layout child views and before drawing to avoid flicker viewTreeObserver.addOnPreDrawListener { if (shouldUpdatePosition) { @@ -124,9 +177,10 @@ class CandidatesView( centerVertically() before(nextIcon) }) - layoutParams = FlexboxLayout.LayoutParams(wrapContent, wrapContent).apply { + layoutParams = FlexboxLayoutManager.LayoutParams(wrapContent, wrapContent).apply { flexGrow = 1f alignSelf = AlignItems.STRETCH + minHeight = dp(20) } } @@ -162,30 +216,13 @@ class CandidatesView( } } + @SuppressLint("NotifyDataSetChanged") private fun updateCandidates() { - candidatesLayout.removeAllViews() - candidatesLayout.flexDirection = when (paged.layoutHint) { + candidatesLayoutManager.flexDirection = when (paged.layoutHint) { FcitxEvent.PagedCandidateEvent.LayoutHint.Vertical -> FlexDirection.COLUMN else -> FlexDirection.ROW } - paged.candidates.forEachIndexed { i, it -> - val item = CandidateItemUi(ctx, theme).apply { - text.textSize = 16f - // TODO different font for comment - text.text = "${it.label}${it.text} ${it.comment}" - if (i == paged.cursorIndex) { - root.backgroundColor = theme.genericActiveBackgroundColor - text.setTextColor(theme.genericActiveForegroundColor) - } - } - candidatesLayout.addView(item.root, FlexboxLayout.LayoutParams(-2, -2)) - } - if (paged.hasPrev || paged.hasNext) { - prevIcon.alpha = if (paged.hasPrev) 1f else 0.4f - nextIcon.alpha = if (paged.hasNext) 1f else 0.4f - candidatesLayout.addView(paginationLayout) - } - shouldUpdatePosition = true + candidatesAdapter.notifyDataSetChanged() } private fun updatePosition() { @@ -225,12 +262,13 @@ class CandidatesView( topOfParent() startOfParent() }) - add(candidatesLayout, lParams(wrapContent, wrapContent) { + add(recyclerView, lParams(wrapContent, wrapContent) { below(preeditUi.root) startOfParent() bottomOfParent() }) + isFocusable = false layoutParams = ViewGroup.LayoutParams(wrapContent, wrapContent) } From 96506d1ec9e405682d84919981eef27647a644d9 Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 11 Sep 2024 21:26:40 +0800 Subject: [PATCH 065/181] Fix crash when opening table addon config while it's not loaded --- .../ui/main/settings/addon/AddonConfigFragment.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/addon/AddonConfigFragment.kt b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/addon/AddonConfigFragment.kt index 88a209d38..9915019f9 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/addon/AddonConfigFragment.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/addon/AddonConfigFragment.kt @@ -16,14 +16,15 @@ class AddonConfigFragment : FcitxPreferenceFragment() { val addon = requireStringArg(ARG_UNIQUE_NAME) val raw = fcitx.getAddonConfig(addon) if (addon == "table") { - val desc = raw["desc"]["TableGlobalConfig"] - val androidTable = RawConfig( - "AndroidTable", subItems = arrayOf( - RawConfig("Type", "External"), - RawConfig("Description", getString(R.string.manage_table_im)) + // append android specific "Manage Table Input Methods" to config of table addon + raw.findByName("desc")?.findByName("TableGlobalConfig")?.let { + it.subItems = (it.subItems ?: emptyArray()) + RawConfig( + "AndroidTable", subItems = arrayOf( + RawConfig("Type", "External"), + RawConfig("Description", getString(R.string.manage_table_im)) + ) ) - ) - desc.subItems = (desc.subItems ?: arrayOf()) + androidTable + } } return raw } From a5ddd58dd657bc52e81f56d5e21236f8229d5300 Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 11 Sep 2024 21:33:21 +0800 Subject: [PATCH 066/181] Fix memory leak in InputView/CandidatesView - avoid creating multiple `eventHandlerJob`s - remove viewTreeObserver listener on view detach --- .../fcitx5/android/input/CandidatesView.kt | 21 ++++++++++++------- .../fcitx/fcitx5/android/input/InputView.kt | 4 +++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt index a6e8a4b3a..cf225c0c5 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt @@ -10,6 +10,7 @@ import android.content.res.ColorStateList import android.graphics.Color import android.view.View import android.view.ViewGroup +import android.view.ViewTreeObserver.OnPreDrawListener import android.widget.ImageView import androidx.annotation.DrawableRes import androidx.annotation.Size @@ -28,6 +29,7 @@ import org.fcitx.fcitx5.android.daemon.FcitxConnection import org.fcitx.fcitx5.android.data.theme.Theme import org.fcitx.fcitx5.android.input.candidates.CandidateItemUi import org.fcitx.fcitx5.android.input.preedit.PreeditUi +import org.fcitx.fcitx5.android.utils.styledFloat import splitties.dimensions.dp import splitties.resources.drawable import splitties.views.backgroundColor @@ -63,7 +65,9 @@ class CandidatesView( field = value visibility = View.GONE if (field) { - setupFcitxEventHandler() + if (eventHandlerJob == null) { + setupFcitxEventHandler() + } } else { eventHandlerJob?.cancel() eventHandlerJob = null @@ -145,18 +149,20 @@ class CandidatesView( } } + private val updatePositionListener = OnPreDrawListener { + if (shouldUpdatePosition) { + updatePosition() + } + true + } + private val recyclerView = recyclerView { isFocusable = false horizontalPadding = dp(8) adapter = candidatesAdapter layoutManager = candidatesLayoutManager // update position after layout child views and before drawing to avoid flicker - viewTreeObserver.addOnPreDrawListener { - if (shouldUpdatePosition) { - updatePosition() - } - true - } + this@CandidatesView.viewTreeObserver.addOnPreDrawListener(updatePositionListener) } private fun createIcon(@DrawableRes icon: Int) = imageView { @@ -274,6 +280,7 @@ class CandidatesView( override fun onDetachedFromWindow() { handleEvents = false + viewTreeObserver.removeOnPreDrawListener(updatePositionListener) super.onDetachedFromWindow() } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/InputView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/InputView.kt index 9a50a81fc..f36035f5e 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/InputView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/InputView.kt @@ -86,7 +86,9 @@ class InputView( set(value) { field = value if (field) { - setupFcitxEventHandler() + if (eventHandlerJob == null) { + setupFcitxEventHandler() + } } else { eventHandlerJob?.cancel() eventHandlerJob = null From 7a4007ed1c5b7cc2bfc1e1e0e26e3bc9fc395b42 Mon Sep 17 00:00:00 2001 From: Rocka Date: Wed, 2 Oct 2024 02:22:51 +0800 Subject: [PATCH 067/181] Update fcitx5 submodules and prebuilt --- lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons | 2 +- lib/fcitx5/src/main/cpp/fcitx5 | 2 +- lib/fcitx5/src/main/cpp/prebuilt | 2 +- lib/libime/src/main/cpp/libime | 2 +- plugin/anthy/src/main/cpp/anthy-cmake | 2 +- plugin/anthy/src/main/cpp/fcitx5-anthy | 2 +- plugin/chewing/src/main/cpp/fcitx5-chewing | 2 +- plugin/clipboard-filter/ClearURLsRules | 2 +- plugin/rime/src/main/cpp/fcitx5-rime | 2 +- plugin/rime/src/main/cpp/rime-luna-pinyin | 2 +- plugin/unikey/src/main/cpp/fcitx5-unikey | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons b/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons index 9e57f69f9..64d6eae8e 160000 --- a/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons +++ b/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons @@ -1 +1 @@ -Subproject commit 9e57f69f979baa477caec60b4323fe5c6f4d1ff6 +Subproject commit 64d6eae8e073f17723dd26fabf77750e3aafc6b5 diff --git a/lib/fcitx5/src/main/cpp/fcitx5 b/lib/fcitx5/src/main/cpp/fcitx5 index 57f77691a..9ab829b25 160000 --- a/lib/fcitx5/src/main/cpp/fcitx5 +++ b/lib/fcitx5/src/main/cpp/fcitx5 @@ -1 +1 @@ -Subproject commit 57f77691a5001e5f42af34eba607b47ffb2557e7 +Subproject commit 9ab829b2587c0dd616f92c85068bcb8c220b628b diff --git a/lib/fcitx5/src/main/cpp/prebuilt b/lib/fcitx5/src/main/cpp/prebuilt index 6fa5fc926..45670662e 160000 --- a/lib/fcitx5/src/main/cpp/prebuilt +++ b/lib/fcitx5/src/main/cpp/prebuilt @@ -1 +1 @@ -Subproject commit 6fa5fc9260bbe860bcfc06f6e3f1d28327118c89 +Subproject commit 45670662e5d508cf27c047b26912139c50f06627 diff --git a/lib/libime/src/main/cpp/libime b/lib/libime/src/main/cpp/libime index 1e5ae72fd..d4f531360 160000 --- a/lib/libime/src/main/cpp/libime +++ b/lib/libime/src/main/cpp/libime @@ -1 +1 @@ -Subproject commit 1e5ae72fd15cf51efc0b243363fa927bd98529cb +Subproject commit d4f531360ddca9b9784c9b4f02411e6c10de433d diff --git a/plugin/anthy/src/main/cpp/anthy-cmake b/plugin/anthy/src/main/cpp/anthy-cmake index 356540a63..81208da07 160000 --- a/plugin/anthy/src/main/cpp/anthy-cmake +++ b/plugin/anthy/src/main/cpp/anthy-cmake @@ -1 +1 @@ -Subproject commit 356540a632146d22eb71ff9050d3d7e2e4e3b1d0 +Subproject commit 81208da0760f0820123c984a76879f5c8b14273f diff --git a/plugin/anthy/src/main/cpp/fcitx5-anthy b/plugin/anthy/src/main/cpp/fcitx5-anthy index 021e6355e..725175681 160000 --- a/plugin/anthy/src/main/cpp/fcitx5-anthy +++ b/plugin/anthy/src/main/cpp/fcitx5-anthy @@ -1 +1 @@ -Subproject commit 021e6355eadfe8fec9f3902da92401d7a8bc23a6 +Subproject commit 725175681f2f296884a827d33a42892eab6ada34 diff --git a/plugin/chewing/src/main/cpp/fcitx5-chewing b/plugin/chewing/src/main/cpp/fcitx5-chewing index 5ad232a6e..a7b0c7e54 160000 --- a/plugin/chewing/src/main/cpp/fcitx5-chewing +++ b/plugin/chewing/src/main/cpp/fcitx5-chewing @@ -1 +1 @@ -Subproject commit 5ad232a6e4b094fb1ede33b04432d030c6695ad5 +Subproject commit a7b0c7e5410382eec6790e65059da995a739d3ad diff --git a/plugin/clipboard-filter/ClearURLsRules b/plugin/clipboard-filter/ClearURLsRules index 0d1c6e00d..28cc1c67d 160000 --- a/plugin/clipboard-filter/ClearURLsRules +++ b/plugin/clipboard-filter/ClearURLsRules @@ -1 +1 @@ -Subproject commit 0d1c6e00d2b91df1d70c84067c16c320e7976a3f +Subproject commit 28cc1c67d6af2bac01bfa0389071d07daa7b04ff diff --git a/plugin/rime/src/main/cpp/fcitx5-rime b/plugin/rime/src/main/cpp/fcitx5-rime index 91bcb0d81..9a57e7de1 160000 --- a/plugin/rime/src/main/cpp/fcitx5-rime +++ b/plugin/rime/src/main/cpp/fcitx5-rime @@ -1 +1 @@ -Subproject commit 91bcb0d81160f8561303bce499e00feb6ccc54ae +Subproject commit 9a57e7de11404800adf73dec362e8f0abb1a8843 diff --git a/plugin/rime/src/main/cpp/rime-luna-pinyin b/plugin/rime/src/main/cpp/rime-luna-pinyin index 44e555d10..ce5c828d9 160000 --- a/plugin/rime/src/main/cpp/rime-luna-pinyin +++ b/plugin/rime/src/main/cpp/rime-luna-pinyin @@ -1 +1 @@ -Subproject commit 44e555d1090a56d62a41a58153088406bcf87abd +Subproject commit ce5c828d99b0777c7a6bc6b1699e34f8c25623ef diff --git a/plugin/unikey/src/main/cpp/fcitx5-unikey b/plugin/unikey/src/main/cpp/fcitx5-unikey index 939d5a6f4..74c990c72 160000 --- a/plugin/unikey/src/main/cpp/fcitx5-unikey +++ b/plugin/unikey/src/main/cpp/fcitx5-unikey @@ -1 +1 @@ -Subproject commit 939d5a6f4cfbee653ca535c11007a2e4af295bb6 +Subproject commit 74c990c72cb55b8663726cd4af69776e9e2935c5 From 6497103ed58b61f8e79241461efb7c296d67cd64 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 5 Oct 2024 16:26:46 +0800 Subject: [PATCH 068/181] Update dependencies Gradle 8.10.2, AGP 8.7.0 --- .github/workflows/nix.yml | 6 ++--- .github/workflows/publish.yml | 4 ++-- .github/workflows/pull_request.yml | 8 +++---- .../main/kotlin/AndroidAppConventionPlugin.kt | 1 - .../AndroidPluginAppConventionPlugin.kt | 1 - .../src/main/kotlin/BuildMetadataPlugin.kt | 1 - .../src/main/kotlin/FcitxComponentPlugin.kt | 1 - .../convention/src/main/kotlin/Utils.kt | 3 +++ .../convention/src/main/kotlin/Versions.kt | 2 +- gradle/libs.versions.toml | 24 +++++++++---------- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 11 files changed, 27 insertions(+), 28 deletions(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 791d2519a..8e35521db 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: - - ubuntu-22.04 + - ubuntu-24.04 - macos-14 runs-on: ${{ matrix.os }} steps: @@ -19,10 +19,10 @@ jobs: with: fetch-depth: 0 submodules: recursive - - uses: cachix/install-nix-action@v26 + - uses: cachix/install-nix-action@v29 with: github_access_token: ${{ secrets.GITHUB_TOKEN }} - - uses: cachix/cachix-action@v14 + - uses: cachix/cachix-action@v15 with: name: fcitx5-android authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1b37bf145..bb9b3ce50 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,7 +10,7 @@ on: jobs: publish: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Fetch source code uses: actions/checkout@v4 @@ -35,7 +35,7 @@ jobs: packages: cmake;3.22.1 - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + uses: gradle/actions/setup-gradle@v4 - name: Publish build convention and libs env: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 07f9a25ed..18e68d0cb 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: os: - - ubuntu-22.04 + - ubuntu-24.04 - macos-13 - macos-14 - windows-2022 @@ -46,7 +46,7 @@ jobs: sdkmanager --install "cmake;3.22.1" - name: Install system dependencies (Ubuntu) - if: ${{ matrix.os == 'ubuntu-22.04' }} + if: ${{ startsWith(matrix.os, 'ubuntu') }} run: | sudo apt update sudo apt install extra-cmake-modules gettext @@ -57,14 +57,14 @@ jobs: brew install extra-cmake-modules - name: Install system dependencies (Windows) - if: ${{ matrix.os == 'windows-2022' }} + if: ${{ startsWith(matrix.os, 'windows') }} run: | C:/msys64/usr/bin/pacman -Syu --noconfirm C:/msys64/usr/bin/pacman -S --noconfirm mingw-w64-ucrt-x86_64-gettext mingw-w64-ucrt-x86_64-extra-cmake-modules Add-Content $env:GITHUB_PATH "C:/msys64/ucrt64/bin" - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + uses: gradle/actions/setup-gradle@v4 - name: Build Release APK run: | diff --git a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt index 97e476207..38c8e71f5 100644 --- a/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidAppConventionPlugin.kt @@ -14,7 +14,6 @@ import org.gradle.api.Project import org.gradle.api.file.RegularFile import org.gradle.api.internal.provider.AbstractProperty import org.gradle.api.internal.provider.Providers -import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.tasks.KotlinCompile diff --git a/build-logic/convention/src/main/kotlin/AndroidPluginAppConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidPluginAppConventionPlugin.kt index 292e1fea9..c2c70b8d6 100644 --- a/build-logic/convention/src/main/kotlin/AndroidPluginAppConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidPluginAppConventionPlugin.kt @@ -5,7 +5,6 @@ import com.android.build.gradle.internal.dsl.BaseAppModuleExtension import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure /** diff --git a/build-logic/convention/src/main/kotlin/BuildMetadataPlugin.kt b/build-logic/convention/src/main/kotlin/BuildMetadataPlugin.kt index 9f56ef3e1..988549cd0 100644 --- a/build-logic/convention/src/main/kotlin/BuildMetadataPlugin.kt +++ b/build-logic/convention/src/main/kotlin/BuildMetadataPlugin.kt @@ -11,7 +11,6 @@ import org.gradle.api.Project import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction -import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.task diff --git a/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt b/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt index f8cda0279..58840b8b4 100644 --- a/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt +++ b/build-logic/convention/src/main/kotlin/FcitxComponentPlugin.kt @@ -6,7 +6,6 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.logging.LogLevel import org.gradle.api.tasks.Delete -import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.getByName import org.gradle.kotlin.dsl.task diff --git a/build-logic/convention/src/main/kotlin/Utils.kt b/build-logic/convention/src/main/kotlin/Utils.kt index e09d32db1..c77999383 100644 --- a/build-logic/convention/src/main/kotlin/Utils.kt +++ b/build-logic/convention/src/main/kotlin/Utils.kt @@ -27,3 +27,6 @@ fun Project.epn(env: String, prop: String) = fun Project.epr(env: String, prop: String) = ep(env, prop) { error("Neither environment variable $env nor project property $prop is set") } + +fun String.capitalized(): String = + if (get(0).isUpperCase()) this else replaceFirstChar { get(0).uppercaseChar() } diff --git a/build-logic/convention/src/main/kotlin/Versions.kt b/build-logic/convention/src/main/kotlin/Versions.kt index bb2ae827b..f56ed57b6 100644 --- a/build-logic/convention/src/main/kotlin/Versions.kt +++ b/build-logic/convention/src/main/kotlin/Versions.kt @@ -6,7 +6,7 @@ import org.gradle.api.JavaVersion object Versions { - val java = JavaVersion.VERSION_1_8 + val java = JavaVersion.VERSION_11 const val compileSdk = 34 const val minSdk = 23 const val targetSdk = 34 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 65a573d2b..da65e712c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,21 +1,21 @@ [versions] -androidGradlePlugin = "8.5.2" -androidDesugarJDKLibs = "2.0.4" -kotlin = "2.0.10" -ksp = "2.0.10-1.0.24" -lifecycle = "2.8.4" -navigation = "2.7.7" +androidGradlePlugin = "8.7.0" +androidDesugarJDKLibs = "2.1.2" +kotlin = "2.0.20" +ksp = "2.0.20-1.0.25" +lifecycle = "2.8.6" +navigation = "2.8.2" room = "2.6.1" splitties = "3.0.0" -aboutlibraries = "11.2.2" +aboutlibraries = "11.2.3" [libraries] android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } android-desugarJDKLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJDKLibs" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } -kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version = "1.8.1" } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version = "1.9.0" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } -androidx-activity = { module = "androidx.activity:activity-ktx", version = "1.9.1" } +androidx-activity = { module = "androidx.activity:activity-ktx", version = "1.9.2" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" } androidx-autofill = { module = "androidx.autofill:autofill", version = "1.1.0" } androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.4" } @@ -35,11 +35,11 @@ androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "room" } -androidx-startup = { module = "androidx.startup:startup-runtime", version = "1.1.1" } +androidx-startup = { module = "androidx.startup:startup-runtime", version = "1.2.0" } androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version = "1.1.0" } material = { module = "com.google.android.material:material", version = "1.12.0" } arrow = { module = "io.arrow-kt:arrow-core", version = "1.2.4" } -imagecropper = { module = "com.vanniktech:android-image-cropper", version = "4.5.0" } +imagecropper = { module = "com.vanniktech:android-image-cropper", version = "4.6.0" } flexbox = { module = "com.google.android.flexbox:flexbox", version = "3.0.0" } dependency = { module = "org.mechdancer:dependency", version = "0.1.2" } timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" } @@ -55,7 +55,7 @@ splitties-views-recyclerview = { module = "com.louiscad.splitties:splitties-view aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlibraries" } aboutlibraries-plugin = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutlibraries" } junit = { module = "junit:junit", version = "4.13.2" } -androidx-test-runner = { module = "androidx.test:runner", version = "1.6.1" } +androidx-test-runner = { module = "androidx.test:runner", version = "1.6.2" } androidx-test-rules = { module = "androidx.test:rules", version = "1.6.1" } androidx-lifecycle-testing = { module = "androidx.lifecycle:lifecycle-runtime-testing", version.ref = "lifecycle" } kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version = "1.18.1" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 624fecf29..780860a13 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 19:30:31 CST 2024 +#Wed Oct 02 14:34:18 CST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From fbab2892d804b0d730d3aaa372d961a57cf9097d Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 5 Oct 2024 16:41:41 +0800 Subject: [PATCH 069/181] Migrate off Cropper's built-in CropImageActivity --- app/src/main/AndroidManifest.xml | 7 +- .../fcitx/fcitx5/android/data/theme/Theme.kt | 7 +- .../android/ui/main/CropImageActivity.kt | 261 ++++++++++++++++++ .../settings/theme/CustomThemeActivity.kt | 161 ++++------- .../org/fcitx/fcitx5/android/utils/Menu.kt | 63 +++++ .../main/res/drawable/ic_baseline_flip_24.xml | 9 + .../drawable/ic_baseline_rotate_right_24.xml | 9 + app/src/main/res/values-night/themes.xml | 7 - app/src/main/res/values/strings.xml | 5 + app/src/main/res/values/themes.xml | 7 - 10 files changed, 413 insertions(+), 123 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/ui/main/CropImageActivity.kt create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/utils/Menu.kt create mode 100644 app/src/main/res/drawable/ic_baseline_flip_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_rotate_right_24.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7a5aecc0d..686b0355d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -62,12 +62,9 @@ android:configChanges="orientation|screenSize" android:exported="false" android:label="@string/edit_theme" /> - + android:name=".ui.main.CropImageActivity" + android:exported="false" /> () { + override fun createIntent(context: Context, input: CropOption): Intent { + return Intent(context, CropImageActivity::class.java).putExtra(CROP_OPTIONS, input) + } + + override fun parseResult(resultCode: Int, intent: Intent?): CropResult { + val result = intent?.parcelable(CROP_RESULT) + if (resultCode != RESULT_OK || result == null) { + return CropResult.Fail + } + return result + } + } + + private lateinit var cropOption: CropOption + + private lateinit var root: ConstraintLayout + private lateinit var toolbar: Toolbar + private lateinit var cropView: CropImageView + + private fun getDefaultCropImageOptions() = CropImageOptions( + // CropImageView + snapRadius = 0f, + guidelines = CropImageView.Guidelines.ON_TOUCH, + showProgressBar = true, + progressBarColor = styledColor(android.R.attr.colorAccent), + // CropOverlayView + borderLineThickness = dp(1f), + borderCornerOffset = 0f, + ) + + private var selectedImageUri: Uri? = null + + private val launcher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> + if (uri == null) { + setResult(RESULT_CANCELED) + finish() + } else { + selectedImageUri = uri + cropView.setImageUriAsync(uri) + } + } + + private lateinit var tempOutFile: File + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + cropOption = intent.parcelable(CROP_OPTIONS) ?: CropOption.New(1, 1) + enableEdgeToEdge() + setupRootView() + setContentView(root) + setupCropView(cropOption) + onBackPressedDispatcher.addCallback { + setResult(RESULT_CANCELED) + finish() + } + toolbar.setNavigationOnClickListener { + onBackPressedDispatcher.onBackPressed() + } + } + + private fun setupRootView() { + toolbar = view(::Toolbar) { + backgroundColor = styledColor(android.R.attr.colorPrimary) + elevation = dp(4f) + navigationIcon = DrawerArrowDrawable(context).apply { progress = 1f } + setupToolbarMenu(menu) + } + cropView = CropImageView(this).apply { + setOnCropImageCompleteListener(this@CropImageActivity) + setImageCropOptions(getDefaultCropImageOptions()) + } + root = constraintLayout { + add(toolbar, lParams(matchParent, wrapContent) { + topOfParent() + centerHorizontally() + }) + add(cropView, lParams(matchParent) { + below(toolbar) + centerHorizontally() + bottomOfParent() + }) + } + ViewCompat.setOnApplyWindowInsetsListener(root) { _, windowInsets -> + val statusBars = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars()) + val navBars = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()) + root.updateLayoutParams { + leftMargin = navBars.left + rightMargin = navBars.right + bottomMargin = navBars.bottom + } + toolbar.topPadding = statusBars.top + windowInsets + } + } + + private fun setupToolbarMenu(menu: Menu) { + val iconTint = styledColor(android.R.attr.colorControlNormal) + menu.item(R.string.rotate, R.drawable.ic_baseline_rotate_right_24, iconTint, true) { + cropView.rotateImage(90) + } + menu.subMenu(R.string.flip, R.drawable.ic_baseline_flip_24, iconTint, true) { + item(R.string.flip_vertically) { + cropView.flipImageVertically() + } + item(R.string.flip_horizontally) { + cropView.flipImageHorizontally() + } + } + menu.item(R.string.crop, R.drawable.ic_baseline_check_24, iconTint, true) { + onCropImage() + } + } + + private fun setupCropView(option: CropOption) { + cropView.setAspectRatio(option.width, option.height) + when (option) { + is CropOption.New -> { + launcher.launch("image/*") + } + is CropOption.Edit -> { + cropView.setOnSetImageUriCompleteListener { view, uri, e -> + view.cropRect = option.initialRect + view.rotatedDegrees = option.initialRotation + } + cropView.setImageUriAsync(option.sourceUri) + } + } + } + + private fun onCropImage() { + tempOutFile = File.createTempFile("cropped", ".png", cacheDir) + cropView.croppedImageAsync( + saveCompressFormat = Bitmap.CompressFormat.PNG, + reqWidth = cropOption.width, + reqHeight = cropOption.height, + options = CropImageView.RequestSizeOptions.RESIZE_INSIDE, + customOutputUri = Uri.fromFile(tempOutFile) + ) + } + + override fun onCropImageComplete(view: CropImageView, result: CropImageView.CropResult) { + try { + result + val success = CropResult.Success( + result.cropRect!!, + result.rotation, + tempOutFile, + (cropOption as? CropOption.Edit)?.sourceUri ?: selectedImageUri!! + ) + setResult(RESULT_OK, Intent().putExtra(CROP_RESULT, success)) + } catch (e: Exception) { + Timber.e("Exception when cropping image: $e") + toast(e) + setResult(RESULT_CANCELED) + } + finish() + } +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/theme/CustomThemeActivity.kt b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/theme/CustomThemeActivity.kt index 56fdfacf9..c056b8547 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/theme/CustomThemeActivity.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/ui/main/settings/theme/CustomThemeActivity.kt @@ -5,15 +5,14 @@ package org.fcitx.fcitx5.android.ui.main.settings.theme import android.annotation.SuppressLint -import android.app.Activity import android.app.AlertDialog import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory -import android.graphics.Color import android.graphics.Rect import android.graphics.drawable.BitmapDrawable +import android.net.Uri import android.os.Bundle import android.os.Parcelable import android.view.Menu @@ -29,15 +28,10 @@ import androidx.activity.result.contract.ActivityResultContract import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar -import androidx.core.net.toUri import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope -import com.canhub.cropper.CropImageContract -import com.canhub.cropper.CropImageContractOptions -import com.canhub.cropper.CropImageOptions -import com.canhub.cropper.CropImageView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -46,11 +40,14 @@ import org.fcitx.fcitx5.android.data.theme.Theme import org.fcitx.fcitx5.android.data.theme.ThemeFilesManager import org.fcitx.fcitx5.android.data.theme.ThemePreset import org.fcitx.fcitx5.android.ui.common.withLoadingDialog +import org.fcitx.fcitx5.android.ui.main.CropImageActivity.CropContract +import org.fcitx.fcitx5.android.ui.main.CropImageActivity.CropOption +import org.fcitx.fcitx5.android.ui.main.CropImageActivity.CropResult import org.fcitx.fcitx5.android.utils.DarkenColorFilter +import org.fcitx.fcitx5.android.utils.item import org.fcitx.fcitx5.android.utils.parcelable import splitties.dimensions.dp import splitties.resources.color -import splitties.resources.drawable import splitties.resources.resolveThemeAttribute import splitties.resources.styledColor import splitties.resources.styledDrawable @@ -218,11 +215,11 @@ class CustomThemeActivity : AppCompatActivity() { private lateinit var theme: Theme.Custom private class BackgroundStates { - lateinit var launcher: ActivityResultLauncher + lateinit var launcher: ActivityResultLauncher var srcImageExtension: String? = null var srcImageBuffer: ByteArray? = null - var tempImageFile: File? = null var cropRect: Rect? = null + var cropRotation: Int = 0 lateinit var croppedBitmap: Bitmap lateinit var filteredDrawable: BitmapDrawable lateinit var srcImageFile: File @@ -242,21 +239,15 @@ class CustomThemeActivity : AppCompatActivity() { background: Theme.Custom.CustomBackground, darkKeys: Boolean ) { - theme = if (darkKeys) - ThemePreset.TransparentLight.deriveCustomBackground( - theme.name, - background.croppedFilePath, - background.srcFilePath, - brightnessSeekBar.progress, - background.cropRect - ) else - ThemePreset.TransparentDark.deriveCustomBackground( - theme.name, - background.croppedFilePath, - background.srcFilePath, - brightnessSeekBar.progress, - background.cropRect - ) + val template = if (darkKeys) ThemePreset.TransparentLight else ThemePreset.TransparentDark + theme = template.deriveCustomBackground( + theme.name, + background.croppedFilePath, + background.srcFilePath, + brightnessSeekBar.progress, + background.cropRect, + background.cropRotation + ) previewUi.setTheme(theme, filteredDrawable) } @@ -269,6 +260,7 @@ class CustomThemeActivity : AppCompatActivity() { croppedImageFile = File(it.croppedFilePath) srcImageFile = File(it.srcFilePath) cropRect = it.cropRect + cropRotation = it.cropRotation croppedBitmap = BitmapFactory.decodeFile(it.croppedFilePath) filteredDrawable = BitmapDrawable(resources, croppedBitmap) } @@ -312,29 +304,27 @@ class CustomThemeActivity : AppCompatActivity() { whenHasBackground { background -> brightnessSeekBar.progress = background.brightness variantSwitch.isChecked = !theme.isDark - launcher = registerForActivityResult(CropImageContract()) { - if (!it.isSuccessful) { - if (newCreated) - cancel() - else - return@registerForActivityResult - } else { - if (newCreated) { - srcImageExtension = MimeTypeMap.getSingleton() - .getExtensionFromMimeType(contentResolver.getType(it.originalUri!!)) - srcImageBuffer = - contentResolver.openInputStream(it.originalUri!!)!! - .use { x -> x.readBytes() } + launcher = registerForActivityResult(CropContract()) { + when (it) { + CropResult.Fail -> { + if (newCreated) { + cancel() + } + } + is CropResult.Success -> { + if (newCreated) { + srcImageExtension = MimeTypeMap.getSingleton() + .getExtensionFromMimeType(contentResolver.getType(it.srcUri)) + srcImageBuffer = + contentResolver.openInputStream(it.srcUri)!! + .use { x -> x.readBytes() } + } + cropRect = it.rect + cropRotation = it.rotation + croppedBitmap = it.bitmap + filteredDrawable = BitmapDrawable(resources, croppedBitmap) + updateState() } - cropRect = it.cropRect!! - croppedBitmap = Bitmap.createScaledBitmap( - it.getBitmap(this@CustomThemeActivity)!!, - previewUi.intrinsicWidth, - previewUi.intrinsicHeight, - true - ) - filteredDrawable = BitmapDrawable(resources, croppedBitmap) - updateState() } } cropLabel.setOnClickListener { @@ -377,37 +367,19 @@ class CustomThemeActivity : AppCompatActivity() { } private fun BackgroundStates.launchCrop(w: Int, h: Int) { - if (tempImageFile == null || tempImageFile?.exists() != true) { - tempImageFile = File.createTempFile("cropped", ".png", cacheDir) - } - launcher.launch( - CropImageContractOptions( - uri = srcImageFile.takeIf { it.exists() }?.toUri(), - CropImageOptions( - initialCropWindowRectangle = cropRect, - guidelines = CropImageView.Guidelines.ON_TOUCH, - borderLineColor = Color.WHITE, - borderLineThickness = dp(1f), - borderCornerColor = Color.WHITE, - borderCornerOffset = 0f, - imageSourceIncludeGallery = true, - imageSourceIncludeCamera = false, - aspectRatioX = w, - aspectRatioY = h, - fixAspectRatio = true, - customOutputUri = tempImageFile!!.toUri(), - outputCompressFormat = Bitmap.CompressFormat.PNG, - cropMenuCropButtonIcon = R.drawable.ic_baseline_done_24, - showProgressBar = true, - progressBarColor = styledColor(android.R.attr.colorAccent), - activityMenuIconColor = styledColor(android.R.attr.colorControlNormal), - activityMenuTextColor = styledColor(android.R.attr.colorForeground), - activityBackgroundColor = styledColor(android.R.attr.colorBackground), - toolbarColor = styledColor(android.R.attr.colorPrimary), - toolbarBackButtonColor = styledColor(android.R.attr.colorControlNormal) + if (newCreated) { + launcher.launch(CropOption.New(w, h)) + } else { + launcher.launch( + CropOption.Edit( + width = w, + height = h, + Uri.fromFile(srcImageFile), + initialRect = cropRect, + initialRotation = cropRotation ) ) - ) + } } @SuppressLint("SetTextI18n") @@ -419,11 +391,8 @@ class CustomThemeActivity : AppCompatActivity() { } private fun cancel() { - whenHasBackground { - tempImageFile?.delete() - } setResult( - Activity.RESULT_CANCELED, + RESULT_CANCELED, Intent().apply { putExtra(RESULT, null as BackgroundResult?) } ) finish() @@ -433,7 +402,6 @@ class CustomThemeActivity : AppCompatActivity() { lifecycleScope.withLoadingDialog(this) { whenHasBackground { withContext(Dispatchers.IO) { - tempImageFile?.delete() croppedImageFile.delete() croppedImageFile.outputStream().use { croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, it) @@ -452,14 +420,15 @@ class CustomThemeActivity : AppCompatActivity() { } } setResult( - Activity.RESULT_OK, + RESULT_OK, Intent().apply { var newTheme = theme whenHasBackground { newTheme = theme.copy( backgroundImage = it.copy( brightness = brightnessSeekBar.progress, - cropRect = cropRect + cropRect = cropRect, + cropRotation = cropRotation ) ) } @@ -477,7 +446,7 @@ class CustomThemeActivity : AppCompatActivity() { private fun delete() { setResult( - Activity.RESULT_OK, + RESULT_OK, Intent().apply { putExtra(RESULT, BackgroundResult.Deleted(theme.name)) } @@ -498,26 +467,14 @@ class CustomThemeActivity : AppCompatActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { if (!newCreated) { - menu.add(R.string.delete).apply { - icon = drawable(R.drawable.ic_baseline_delete_24)!!.apply { - setTint(color(R.color.red_400)) - } - setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) - setOnMenuItemClickListener { - promptDelete() - true - } + val iconTint = color(R.color.red_400) + menu.item(R.string.save, R.drawable.ic_baseline_delete_24, iconTint, true) { + promptDelete() } } - menu.add(R.string.save).apply { - icon = drawable(R.drawable.ic_baseline_done_24)!!.apply { - setTint(styledColor(android.R.attr.colorControlNormal)) - } - setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) - setOnMenuItemClickListener { - done() - true - } + val iconTint = styledColor(android.R.attr.colorControlNormal) + menu.item(R.string.save, R.drawable.ic_baseline_check_24, iconTint, true) { + done() } return true } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/utils/Menu.kt b/app/src/main/java/org/fcitx/fcitx5/android/utils/Menu.kt new file mode 100644 index 000000000..cd2403615 --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/utils/Menu.kt @@ -0,0 +1,63 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.utils + +import android.content.res.ColorStateList +import android.os.Build +import android.view.Menu +import android.view.MenuItem +import android.view.SubMenu +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import splitties.resources.drawable + +fun MenuItem.setup( + @DrawableRes icon: Int, + @ColorInt iconTint: Int, + showAsAction: Boolean +): MenuItem { + if (icon != 0 && iconTint != 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + iconTintList = ColorStateList.valueOf(iconTint) + setIcon(icon) + } else { + setIcon(appContext.drawable(icon)?.apply { setTint(iconTint) }) + } + } + if (showAsAction) { + setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) + } + return this +} + +fun Menu.item( + @StringRes title: Int, + @DrawableRes icon: Int = 0, + @ColorInt iconTint: Int = 0, + showAsAction: Boolean = false, + callback: () -> Unit +): MenuItem { + val item = add(title).setup(icon, iconTint, showAsAction) + item.setOnMenuItemClickListener { + callback.invoke() + true + } + return item +} + +fun Menu.subMenu( + @StringRes title: Int, + @DrawableRes icon: Int, + @ColorInt iconTint: Int, + showAsAction: Boolean = false, + setup: SubMenu.() -> Unit +): SubMenu { + val sub = addSubMenu(title) + sub.item.setup(icon, iconTint, showAsAction) + setup.invoke(sub) + return sub +} diff --git a/app/src/main/res/drawable/ic_baseline_flip_24.xml b/app/src/main/res/drawable/ic_baseline_flip_24.xml new file mode 100644 index 000000000..8dbbb325f --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_flip_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_rotate_right_24.xml b/app/src/main/res/drawable/ic_baseline_rotate_right_24.xml new file mode 100644 index 000000000..116c1ddc9 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_rotate_right_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 985ab70c1..e616379fa 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -10,13 +10,6 @@ @color/grey_400 - - - -