diff --git a/LANGUAGEPACKS-CHINESE.md b/LANGUAGEPACKS-CHINESE.md new file mode 100644 index 00000000..dce0d70f --- /dev/null +++ b/LANGUAGEPACKS-CHINESE.md @@ -0,0 +1,143 @@ +# Chinese language pack + +[简体中文翻译点这里](#简体中文翻译) + +[繁体中文翻譯點這裡](#繁体中文翻譯) + + +## Default barebones Chinese shape-based pack + +Default barebones Chinese shape-based language pack which are always available. + +Please download the [full Chinese language pack](#full-chinese-shape-based-pack) to access input methods such as wubi, quick cangjie, and other versions of these input methods. + +This pack is released under the same license as Florisboard. + + +## Full Chinese shape-based pack + +Chinese shape-based language pack based on fcitx5-table-extra. + +This pack is released under a separate license. Please visit to download: [TODO: add link to release page](https://) + +:no_entry: :no_entry: **WARNING** :no_entry: :no_entry: + +- **Input methods that include pinyin, jyutping, zhuyin as (auxiliary) functions are only provided for convenience. Currently, FlorisBoard lacks phonetic input algorithms. These functions currently have poor user experience and are not recommended for daily use.** + +The following input methods are included in this language pack: + +- 中文 (中国) [T9笔画] / Chinese (China) [T9] +- 中文 (中国) [五笔98] / Chinese (China) [WUBI98] +- ~中文 (中国) [五笔98-拼音混打] / Chinese (China) [WUBI98PINYIN]~ +- 中文 (中国) [五笔98-单字] / Chinese (China) [WUBI98SINGLE] +- 中文 (中国) [五笔-大字库] / Chinese (China) [WUBILARGE] +- 中文 (中国) [郑码] / Chinese (China) [ZHENGMA] +- 中文 (中国) [郑码-大字库] / Chinese (China) [ZHENGMALARGE] +- ~中文 (中国) [郑码-拼音混打] / Chinese (China) [ZHENGMAPINYIN]~ +- ~中文 (香港) [廣東拼音] / Chinese (Hong Kong) [CANTONESE]~ +- ~中文 (香港) [港式廣東話] / Chinese (Hong Kong) [CANTONHK]~ +- 中文 (香港) [輕鬆-大字庫] / Chinese (Hong Kong) [EASYLARGE] +- ~中文 (香港) [粤語拼音-表格] / Chinese (Hong Kong) [JYUTPINGTABLE]~ +- 中文 (香港) [速成三代] / Chinese (Hong Kong) [QUICK3] +- 中文 (香港) [經典速成] / Chinese (Hong Kong) [QUICKCLASSIC] +- 中文 (香港) [筆順五碼] / Chinese (Hong Kong) [STROKE5] +- 中文 (台灣) [行列] / Chinese (Taiwan) [ARRAY30] +- 中文 (台灣) [行列-大字库] / Chinese (Taiwan) [ARRAY30LARGE] +- 中文 (台灣) [嘸蝦米] / Chinese (Taiwan) [BOSHIAMY] +- 中文 (台灣) [倉頡三代] / Chinese (Taiwan) [CANGJIE3] +- 中文 (台灣) [倉頡五代] / Chinese (Taiwan) [CANGJIE5] +- 中文 (台灣) [倉頡-大字庫] / Chinese (Taiwan) [CANGJIELARGE] +- 中文 (台灣) [速成五代] / Chinese (Taiwan) [QUICK5] +- 中文 (台灣) [快倉六] / Chinese (Taiwan) [SCJ6] +- ~中文 (台灣) [吳語注音] / Chinese (Taiwan) [WU]~ + +Third-party license: [https://github.com/fcitx/fcitx5-table-extra/blob/master/LICENSES/GPL-3.0-or-later.txt] + +# 简体中文翻译 +## 默认中文形码语言包 + +请下载[完整中文语言包](#fcitx5-中文形码语言包)来获得五笔、快速仓颉等输入法,及这些输入法的其他版本。 + +这一语言包的使用许可与FlorisBoard相同。 + +## Fcitx5 中文形码语言包 + +这一语言包由另一使用许可提供。下载请访问:[TODO: add link to release page](https://) + +:no_entry: :no_entry: **注意** :no_entry: :no_entry: + +- **含拼音、粤拼、注音等功能的输入法,仅为便利提供。目前,FlorisBoard缺乏音码输入算法,这些功能目前使用体验不佳,暂不推荐日常使用。** + +语言包内含以下输入法: + +- 中文 (中国) [T9笔画] / Chinese (China) [T9] +- 中文 (中国) [五笔98] / Chinese (China) [WUBI98] +- ~中文 (中国) [五笔98-拼音混打] / Chinese (China) [WUBI98PINYIN]~ +- 中文 (中国) [五笔98-单字] / Chinese (China) [WUBI98SINGLE] +- 中文 (中国) [五笔-大字库] / Chinese (China) [WUBILARGE] +- 中文 (中国) [郑码] / Chinese (China) [ZHENGMA] +- 中文 (中国) [郑码-大字库] / Chinese (China) [ZHENGMALARGE] +- ~中文 (中国) [郑码-拼音混打] / Chinese (China) [ZHENGMAPINYIN]~ +- ~中文 (香港) [廣東拼音] / Chinese (Hong Kong) [CANTONESE]~ +- ~中文 (香港) [港式廣東話] / Chinese (Hong Kong) [CANTONHK]~ +- 中文 (香港) [輕鬆-大字庫] / Chinese (Hong Kong) [EASYLARGE] +- ~中文 (香港) [粤語拼音-表格] / Chinese (Hong Kong) [JYUTPINGTABLE]~ +- 中文 (香港) [速成三代] / Chinese (Hong Kong) [QUICK3] +- 中文 (香港) [經典速成] / Chinese (Hong Kong) [QUICKCLASSIC] +- 中文 (香港) [筆順五碼] / Chinese (Hong Kong) [STROKE5] +- 中文 (台灣) [行列] / Chinese (Taiwan) [ARRAY30] +- 中文 (台灣) [行列-大字库] / Chinese (Taiwan) [ARRAY30LARGE] +- 中文 (台灣) [嘸蝦米] / Chinese (Taiwan) [BOSHIAMY] +- 中文 (台灣) [倉頡三代] / Chinese (Taiwan) [CANGJIE3] +- 中文 (台灣) [倉頡五代] / Chinese (Taiwan) [CANGJIE5] +- 中文 (台灣) [倉頡-大字庫] / Chinese (Taiwan) [CANGJIELARGE] +- 中文 (台灣) [速成五代] / Chinese (Taiwan) [QUICK5] +- 中文 (台灣) [快倉六] / Chinese (Taiwan) [SCJ6] +- ~中文 (台灣) [吳語注音] / Chinese (Taiwan) [WU]~ + +第三方许可证: [https://github.com/fcitx/fcitx5-table-extra/blob/master/LICENSES/GPL-3.0-or-later.txt] + + +# 繁体中文翻譯 +## 預設中文形碼語言包 + +請下載[完整中文語言包](#fcitx5-中文形碼語言包)來獲得五筆、快速倉頡等輸入法,及這些輸入法的其他版本。 + +這一語言包的使用許可與FlorisBoard相同。 + +## Fcitx5 中文形碼語言包 + +這一語言包由另一使用許可提供。下載請訪問:[TODO: add link to release page](https://) + +:no_entry: :no_entry: **注意** :no_entry: :no_entry: + +- **含拼音、粵拼、注音等功能的輸入法,僅為便利提供。目前,FlorisBoard缺乏音碼輸入算法,這些功能目前使用體驗不佳,暫不推薦日常使用。** + +語言包內含以下輸入法: + +- 中文 (中国) [T9笔画] / Chinese (China) [T9] +- 中文 (中国) [五笔98] / Chinese (China) [WUBI98] +- ~中文 (中国) [五笔98-拼音混打] / Chinese (China) [WUBI98PINYIN]~ +- 中文 (中国) [五笔98-单字] / Chinese (China) [WUBI98SINGLE] +- 中文 (中国) [五笔-大字库] / Chinese (China) [WUBILARGE] +- 中文 (中国) [郑码] / Chinese (China) [ZHENGMA] +- 中文 (中国) [郑码-大字库] / Chinese (China) [ZHENGMALARGE] +- ~中文 (中国) [郑码-拼音混打] / Chinese (China) [ZHENGMAPINYIN]~ +- ~中文 (香港) [廣東拼音] / Chinese (Hong Kong) [CANTONESE]~ +- ~中文 (香港) [港式廣東話] / Chinese (Hong Kong) [CANTONHK]~ +- 中文 (香港) [輕鬆-大字庫] / Chinese (Hong Kong) [EASYLARGE] +- ~中文 (香港) [粤語拼音-表格] / Chinese (Hong Kong) [JYUTPINGTABLE]~ +- 中文 (香港) [速成三代] / Chinese (Hong Kong) [QUICK3] +- 中文 (香港) [經典速成] / Chinese (Hong Kong) [QUICKCLASSIC] +- 中文 (香港) [筆順五碼] / Chinese (Hong Kong) [STROKE5] +- 中文 (台灣) [行列] / Chinese (Taiwan) [ARRAY30] +- 中文 (台灣) [行列-大字库] / Chinese (Taiwan) [ARRAY30LARGE] +- 中文 (台灣) [嘸蝦米] / Chinese (Taiwan) [BOSHIAMY] +- 中文 (台灣) [倉頡三代] / Chinese (Taiwan) [CANGJIE3] +- 中文 (台灣) [倉頡五代] / Chinese (Taiwan) [CANGJIE5] +- 中文 (台灣) [倉頡-大字庫] / Chinese (Taiwan) [CANGJIELARGE] +- 中文 (台灣) [速成五代] / Chinese (Taiwan) [QUICK5] +- 中文 (台灣) [快倉六] / Chinese (Taiwan) [SCJ6] +- ~中文 (台灣) [吳語注音] / Chinese (Taiwan) [WU]~ + +第三方許可證: [https://github.com/fcitx/fcitx5-table-extra/blob/master/LICENSES/GPL-3.0-or-later.txt] diff --git a/LANGUAGEPACKS.md b/LANGUAGEPACKS.md index 960db309..75ba44b7 100644 --- a/LANGUAGEPACKS.md +++ b/LANGUAGEPACKS.md @@ -3,7 +3,7 @@ ## Languages - [Summary](#summary) -- [Chinese / 中文](#chinese--中文) +- [Chinese / 中文](LANGUAGEPACKS-CHINESE.md) ## Summary @@ -14,54 +14,3 @@ language packs. The homepage of default language packs included in FlorisBoard should link to this page. -## Chinese / 中文 - -TODO: translate into native language - -### Default barebones Chinese shape-based pack - -默认中文形码语言包 / 預設中文形碼語言包 / Default barebones Chinese shape-based language pack which are always available. - -Please download the [full Chinese language pack](#full-chinese-shape-based-pack) to access input methods -such as wubi, boshiami, and other versions of cangjie. - -This pack is released under the same license as Florisboard. - -TODO: translate into native language - -### Full Chinese shape-based pack - -Fcitx5 中文形码语言包 / Fcitx5 中文形碼語言包 / Chinese shape-based language pack based on fcitx5-table-extra. - -This pack is released under a separate license. Please visit: [TODO: add link to release page](https://) - -The following input methods are included in this language pack: - -- 中文 (中国) [T9笔画] / Chinese (China) [T9] -- 中文 (中国) [五笔98] / Chinese (China) [WUBI98] -- 中文 (中国) [五笔98-拼音混打] / Chinese (China) [WUBI98PINYIN] -- 中文 (中国) [五笔98-单字] / Chinese (China) [WUBI98SINGLE] -- 中文 (中国) [五笔-大字库] / Chinese (China) [WUBILARGE] -- 中文 (中国) [郑码] / Chinese (China) [ZHENGMA] -- 中文 (中国) [郑码-大字库] / Chinese (China) [ZHENGMALARGE] -- 中文 (中国) [郑码-拼音混打] / Chinese (China) [ZHENGMAPINYIN] -- 中文 (香港) [廣東拼音] / Chinese (Hong Kong) [CANTONESE] -- 中文 (香港) [港式廣東話] / Chinese (Hong Kong) [CANTONHK] -- 中文 (香港) [輕鬆-大字庫] / Chinese (Hong Kong) [EASYLARGE] -- 中文 (香港) [粤語拼音-表格] / Chinese (Hong Kong) [JYUTPINGTABLE] -- 中文 (香港) [速成三代] / Chinese (Hong Kong) [QUICK3] -- 中文 (香港) [經典速成] / Chinese (Hong Kong) [QUICKCLASSIC] -- 中文 (香港) [筆順五碼] / Chinese (Hong Kong) [STROKE5] -- 中文 (台灣) [行列] / Chinese (Taiwan) [ARRAY30] -- 中文 (台灣) [行列-大字库] / Chinese (Taiwan) [ARRAY30LARGE] -- 中文 (台灣) [嘸蝦米] / Chinese (Taiwan) [BOSHIAMY] -- 中文 (台灣) [倉頡三代] / Chinese (Taiwan) [CANGJIE3] -- 中文 (台灣) [倉頡五代] / Chinese (Taiwan) [CANGJIE5] -- 中文 (台灣) [倉頡-大字庫] / Chinese (Taiwan) [CANGJIELARGE] -- 中文 (台灣) [速成五代] / Chinese (Taiwan) [QUICK5] -- 中文 (台灣) [快倉六] / Chinese (Taiwan) [SCJ6] -- 中文 (台灣) [吳語注音] / Chinese (Taiwan) [WU] - -Third-party license: [https://github.com/fcitx/fcitx5-table-extra/blob/master/LICENSES/GPL-3.0-or-later.txt] - -TODO: translate into native language diff --git a/app/src/main/assets/ime/keyboard/org.florisboard.layouts/layouts/symbolsMod/cjk.json b/app/src/main/assets/ime/keyboard/org.florisboard.layouts/layouts/symbolsMod/cjk.json index e2b028f1..93d9cf98 100644 --- a/app/src/main/assets/ime/keyboard/org.florisboard.layouts/layouts/symbolsMod/cjk.json +++ b/app/src/main/assets/ime/keyboard/org.florisboard.layouts/layouts/symbolsMod/cjk.json @@ -8,7 +8,7 @@ { "code": -201, "label": "view_characters", "type": "system_gui" }, { "$": "char_width_selector", "full": { "code": 12289, "label": "、", "popup": { - "main": { "code": 44, "label": "," } + "main": { "code": 65292, "label": "," } } }, "half": { "code": 65380, "label": "、", "popup": { diff --git a/app/src/main/assets/ime/languagepack/org.florisboard.hanshapebasedbasicpack/extension.json b/app/src/main/assets/ime/languagepack/org.florisboard.hanshapebasedbasicpack/extension.json index be1776a2..e4d0f0a0 100644 --- a/app/src/main/assets/ime/languagepack/org.florisboard.hanshapebasedbasicpack/extension.json +++ b/app/src/main/assets/ime/languagepack/org.florisboard.hanshapebasedbasicpack/extension.json @@ -6,7 +6,7 @@ "title": "Default barebones Chinese shape-based pack", "description": "默认中文形码语言包 / 預設中文形碼語言包 / Default barebones Chinese shape-based language pack which are always available.\n请访问下面的主页链接下载更多输入法 / 請訪問下面的主頁鏈接下載更多輸入法 / Please visit the homepage linked below to download more input methods.", "maintainers": [ "patrickgold ", "waelwindows", "moonbeamcelery" ], - "homepage": "https://github.com/florisboard/florisboard/blob/master/LANGUAGEPACKS.md#default-barebones-chinese-shape-based-pack", + "homepage": "https://github.com/florisboard/florisboard/blob/master/LANGUAGEPACKS-CHINESE.md#default-barebones-chinese-shape-based-pack", "license": "apache-2.0" }, "items": [ diff --git a/app/src/main/kotlin/dev/patrickgold/florisboard/FlorisImeService.kt b/app/src/main/kotlin/dev/patrickgold/florisboard/FlorisImeService.kt index 3320bc80..afc3bc48 100644 --- a/app/src/main/kotlin/dev/patrickgold/florisboard/FlorisImeService.kt +++ b/app/src/main/kotlin/dev/patrickgold/florisboard/FlorisImeService.kt @@ -25,6 +25,7 @@ import android.os.Bundle import android.util.Size import android.util.TypedValue import android.view.Gravity +import android.view.KeyEvent import android.view.View import android.view.ViewGroup import android.view.WindowManager @@ -635,6 +636,11 @@ class FlorisImeService : LifecycleInputMethodService() { } } + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean = + if (keyboardManager.onHardwareKeyDown(keyCode, event)) true + else super.onKeyDown(keyCode, event) + + private inner class ComposeInputView : AbstractComposeView(this) { init { isHapticFeedbackEnabled = true diff --git a/app/src/main/kotlin/dev/patrickgold/florisboard/app/devtools/DevtoolsOverlay.kt b/app/src/main/kotlin/dev/patrickgold/florisboard/app/devtools/DevtoolsOverlay.kt index 042be103..cce80cf2 100644 --- a/app/src/main/kotlin/dev/patrickgold/florisboard/app/devtools/DevtoolsOverlay.kt +++ b/app/src/main/kotlin/dev/patrickgold/florisboard/app/devtools/DevtoolsOverlay.kt @@ -112,6 +112,7 @@ private fun DevtoolsInputStateOverlay() { DevtoolsText(text = "After: \"${content.textAfterSelection}\"") DevtoolsText(text = "Composing: ${content.composing}") DevtoolsText(text = "CurrentWord: ${content.currentWord}") + DevtoolsText(text = "LastCommit: ${editorInstance.lastCommitPosition}") } } } diff --git a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/editor/AbstractEditorInstance.kt b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/editor/AbstractEditorInstance.kt index c60ce12d..b757aff8 100644 --- a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/editor/AbstractEditorInstance.kt +++ b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/editor/AbstractEditorInstance.kt @@ -17,7 +17,6 @@ package dev.patrickgold.florisboard.ime.editor import android.content.Context -import android.icu.text.BreakIterator import android.inputmethodservice.InputMethodService import android.os.SystemClock import android.text.TextUtils @@ -33,6 +32,8 @@ import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName import dev.patrickgold.florisboard.lib.kotlin.guardedByLock import dev.patrickgold.florisboard.nlpManager import dev.patrickgold.florisboard.subtypeManager +import kotlin.math.max +import kotlin.math.min import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -82,6 +83,9 @@ abstract class AbstractEditorInstance(context: Context) { _activeContentFlow.value = v } private val expectedContentQueue = ExpectedContentQueue() + private val _lastCommitPosition = LastCommitPosition() + val lastCommitPosition + get() = LastCommitPosition(_lastCommitPosition) fun expectedContent(): EditorContent? { return runBlocking { expectedContentQueue.peekNewestOrNull() } @@ -145,6 +149,7 @@ abstract class AbstractEditorInstance(context: Context) { if (composing.isValid) { currentInputConnection()?.setComposingRegion(EditorRange.Unspecified) } + _lastCommitPosition.handleUpdateSelection(newSelection) } open fun handleSelectionUpdate(oldSelection: EditorRange, newSelection: EditorRange, composing: EditorRange) { @@ -157,6 +162,7 @@ abstract class AbstractEditorInstance(context: Context) { return } + _lastCommitPosition.handleUpdateSelection(newSelection) val expected = runBlocking { expectedContentQueue.popUntilOrNull { it.selection == newSelection && it.composing == composing && @@ -207,6 +213,7 @@ abstract class AbstractEditorInstance(context: Context) { activeCursorCapsMode = InputAttributes.CapsMode.NONE activeContent = EditorContent.Unspecified runBlocking { expectedContentQueue.clear() } + _lastCommitPosition.reset() } private suspend fun generateContent( @@ -231,7 +238,7 @@ abstract class AbstractEditorInstance(context: Context) { // Determine local composing word range, if any val localCurrentWord = if (shouldDetermineComposingRegion(editorInfo) && localSelection.isCursorMode && textBeforeSelection.isNotEmpty()) { - determineLocalComposing(textBeforeSelection) + determineLocalComposing(textBeforeSelection, _lastCommitPosition.pos - offset) } else { EditorRange.Unspecified } @@ -253,7 +260,9 @@ abstract class AbstractEditorInstance(context: Context) { textAfterSelection: CharSequence = this.textAfterSelection, selectedText: CharSequence = this.selectedText, ): EditorContent { - return generateContent(editorInfo, selection, textBeforeSelection, textAfterSelection, selectedText) + return generateContent( + editorInfo, selection, textBeforeSelection, textAfterSelection, selectedText + ) } private fun EditorContent.cursorCapsMode(): InputAttributes.CapsMode { @@ -275,8 +284,10 @@ abstract class AbstractEditorInstance(context: Context) { return editorInfo.isRichInputEditor } - private suspend fun determineLocalComposing(textBeforeSelection: CharSequence): EditorRange { - return nlpManager.determineLocalComposing(textBeforeSelection, breakIterators) + private suspend fun determineLocalComposing( + textBeforeSelection: CharSequence, localLastCommitPosition: Int + ): EditorRange { + return nlpManager.determineLocalComposing(textBeforeSelection, breakIterators, localLastCommitPosition) } private fun InputConnection.setComposingRegion(composing: EditorRange) { @@ -403,8 +414,7 @@ abstract class AbstractEditorInstance(context: Context) { val newContent = content.generateCopy( selection = newSelection, textBeforeSelection = buildString { - append(content.textBeforeSelection) - removeSuffix(content.composingText) + append(content.textBeforeSelection.removeSuffix(content.composingText)) append(text) }, selectedText = "", @@ -412,6 +422,7 @@ abstract class AbstractEditorInstance(context: Context) { expectedContentQueue.push(newContent) ic.setComposingText(text, 1) ic.finishComposingText() + _lastCommitPosition.handleCommit(newContent.selection) } ic.endBatchEdit() return true @@ -424,7 +435,7 @@ abstract class AbstractEditorInstance(context: Context) { // Cannot perform below check due to editors which lie about their correct selection //if (content.selection.isValid && content.selection.start == 0) return true val oldTextBeforeSelection = content.textBeforeSelection - return if (activeInfo.isRawInputEditor || oldTextBeforeSelection.isEmpty()) { + return (if (activeInfo.isRawInputEditor || oldTextBeforeSelection.isEmpty()) { // If editor is rich and text before selection is empty we seem to have an invalid state here, so we fall // back to emulating a hardware backspace. when (type) { @@ -438,7 +449,8 @@ abstract class AbstractEditorInstance(context: Context) { TextType.CHARACTERS -> breakIterators.measureLastUChars(oldTextBeforeSelection, n, locale) TextType.WORDS -> breakIterators.measureLastUWords(oldTextBeforeSelection, n, locale) } - val newSelection = content.selection.translatedBy(-length) + val selection = content.selection + val newSelection = selection.translatedBy(-length) val newContent = content.generateCopy( selection = newSelection, textBeforeSelection = oldTextBeforeSelection.dropLast(length), @@ -451,6 +463,21 @@ abstract class AbstractEditorInstance(context: Context) { ic.endBatchEdit() true } + }).also { + deleteMoveLastCommitPosition() + } + } + + fun refreshComposing() { + val content = activeContent + val ic = currentInputConnection() + if (activeInfo.isRawInputEditor || ic == null) return + runBlocking { + val newContent = content.generateCopy() + if (newContent.composing != content.composing) { + expectedContentQueue.push(newContent) + ic.setComposingRegion(newContent.composing) + } } } @@ -632,4 +659,41 @@ abstract class AbstractEditorInstance(context: Context) { } } } + + fun updateLastCommitPosition() = _lastCommitPosition.handleCommit(activeContent.selection) + fun deleteMoveLastCommitPosition() = _lastCommitPosition.handleDelete(activeContent.selection) + + /** + * Class for handling history of last commit position. + */ + data class LastCommitPosition(var pos: Int = -1) { + + constructor(other: LastCommitPosition): this(other.pos) + + fun reset() { + pos = -1 + } + + fun handleCommit(selection: EditorRange) { + if (selection.isValid) { + pos = max(selection.start, selection.end) + } else { + reset() + } + } + + fun handleUpdateSelection(selection: EditorRange) { + val start = min(selection.start, selection.end) + if (start < pos) { + reset() + } + } + + fun handleDelete(selection: EditorRange) { + val start = min(selection.start, selection.end) + if (start < pos) { + pos = start + } + } + } } diff --git a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/editor/EditorInstance.kt b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/editor/EditorInstance.kt index bd1e9728..98d48499 100644 --- a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/editor/EditorInstance.kt +++ b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/editor/EditorInstance.kt @@ -139,7 +139,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) { } override fun determineComposingEnabled(): Boolean { - return prefs.suggestion.enabled.get() + return nlpManager.isSuggestionOn() } override fun determineComposer(composerName: ExtensionComponentName): Composer { @@ -264,6 +264,9 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) { super.commitText("$SPACE$text") } else { super.commitText(text) + }.also { + // handled in finalizeComposingText if content.composing.isValid + updateLastCommitPosition() } } } @@ -286,6 +289,8 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) { super.commitText("$SPACE$text") } else { super.commitText(text) + }.also { + updateLastCommitPosition() } } @@ -303,7 +308,9 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) { val mimeTypes = item.mimeTypes return when (item.type) { ItemType.TEXT -> { - commitText(item.text.toString()) + commitText(item.text.toString()).also { + updateLastCommitPosition() + } } ItemType.IMAGE, ItemType.VIDEO -> { item.uri ?: return false @@ -470,6 +477,14 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) { } } + fun tryPerformEnterCommitRaw(): Boolean { + return if (subtypeManager.activeSubtype.primaryLocale.language.startsWith("zh") && activeContent.composing.length > 0) { + finalizeComposingText(activeContent.composingText) + } else { + false + } + } + /** * Performs a given [action] on the current input editor. * diff --git a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/keyboard/KeyboardManager.kt b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/keyboard/KeyboardManager.kt index b0407c7e..dcd43e40 100644 --- a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/keyboard/KeyboardManager.kt +++ b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/keyboard/KeyboardManager.kt @@ -35,6 +35,7 @@ import dev.patrickgold.florisboard.ime.ImeUiMode import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn import dev.patrickgold.florisboard.ime.core.Subtype import dev.patrickgold.florisboard.ime.core.SubtypePreset +import dev.patrickgold.florisboard.ime.editor.EditorContent import dev.patrickgold.florisboard.ime.editor.FlorisEditorInfo import dev.patrickgold.florisboard.ime.editor.ImeOptions import dev.patrickgold.florisboard.ime.editor.InputAttributes @@ -56,6 +57,7 @@ import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyboardCache import dev.patrickgold.florisboard.lib.android.showLongToast import dev.patrickgold.florisboard.lib.android.showShortToast import dev.patrickgold.florisboard.lib.devtools.LogTopic +import dev.patrickgold.florisboard.lib.devtools.flogDebug import dev.patrickgold.florisboard.lib.devtools.flogError import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName import dev.patrickgold.florisboard.lib.kotlin.collectIn @@ -144,16 +146,14 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver { subtypeManager.activeSubtypeFlow.collectLatestIn(scope) { reevaluateInputShiftState() updateActiveEvaluators() + editorInstance.refreshComposing() + resetSuggestions(editorInstance.activeContent) } clipboardManager.primaryClipFlow.collectLatestIn(scope) { updateActiveEvaluators() } editorInstance.activeContentFlow.collectIn(scope) { content -> - if (!activeState.isComposingEnabled) { - nlpManager.clearSuggestions() - return@collectIn - } - nlpManager.suggest(subtypeManager.activeSubtype, content) + resetSuggestions(content) } prefs.devtools.enabled.observeForever { reevaluateDebugFlags() @@ -213,6 +213,14 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver { } } + fun resetSuggestions(content: EditorContent) { + if (!(activeState.isComposingEnabled || nlpManager.isSuggestionOn())) { + nlpManager.clearSuggestions() + return + } + nlpManager.suggest(subtypeManager.activeSubtype, content) + } + /** * @return If the language switch should be shown. */ @@ -429,6 +437,9 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver { private fun handleEnter() { val info = editorInstance.activeInfo val isShiftPressed = inputEventDispatcher.isPressed(KeyCode.SHIFT) + if (editorInstance.tryPerformEnterCommitRaw()) { + return + } if (info.imeOptions.flagNoEnterAction || info.inputAttributes.flagTextMultiLine && isShiftPressed) { editorInstance.performEnter() } else { @@ -497,6 +508,21 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver { activeState.inputShiftState = InputShiftState.UNSHIFTED } + /** + * Handles a hardware [KeyEvent.KEYCODE_SPACE] event. Same as [handleSpace], + * but skips handling changing to characters keyboard and double space periods. + */ + fun handleHardwareKeyboardSpace() { + val candidate = nlpManager.getAutoCommitCandidate() + candidate?.let { commitCandidate(it) } + // Skip handling changing to characters keyboard and double space periods + // TODO: this is whether we commit space after selecting candidate. Should be determined by SuggestionProvider + if (!subtypeManager.activeSubtype.primaryLocale.supportsAutoSpace && + candidate != null) { /* Do nothing */ } else { + editorInstance.commitText(KeyCode.SPACE.toChar().toString()) + } + } + /** * Handles a [KeyCode.SPACE] event. Also handles the auto-correction of two space taps if * enabled by the user. @@ -524,6 +550,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver { } } } + // TODO: this is whether we commit space after selecting candidate. Should be determined by SuggestionProvider if (!subtypeManager.activeSubtype.primaryLocale.supportsAutoSpace && candidate != null) { /* Do nothing */ } else { editorInstance.commitText(KeyCode.SPACE.toChar().toString()) @@ -797,6 +824,20 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver { } } + fun onHardwareKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + when (keyCode) { + KeyEvent.KEYCODE_SPACE -> { + handleHardwareKeyboardSpace() + return true + } + KeyEvent.KEYCODE_ENTER -> { + handleEnter() + return true + } + else -> return false + } + } + inner class KeyboardManagerResources { val composers = MutableLiveData>(emptyMap()) val currencySets = MutableLiveData>(emptyMap()) diff --git a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/NlpManager.kt b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/NlpManager.kt index 397e28a9..3cecc200 100644 --- a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/NlpManager.kt +++ b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/NlpManager.kt @@ -17,7 +17,6 @@ package dev.patrickgold.florisboard.ime.nlp import android.content.Context -import android.icu.text.BreakIterator import android.os.Build import android.os.SystemClock import android.util.LruCache @@ -71,6 +70,8 @@ class NlpManager(context: Context) { HanShapeBasedLanguageProvider.ProviderId to ProviderInstanceWrapper(HanShapeBasedLanguageProvider(context)), ) } + // lock unnecessary because values constant + private val providersForceSuggestionOn = mutableMapOf() private val internalSuggestionsGuard = Mutex() private var internalSuggestions by Delegates.observable(SystemClock.uptimeMillis() to listOf()) { _, _, _ -> @@ -174,12 +175,26 @@ class NlpManager(context: Context) { ) } - suspend fun determineLocalComposing(textBeforeSelection: CharSequence, breakIterators: BreakIteratorGroup): EditorRange { + suspend fun determineLocalComposing( + textBeforeSelection: CharSequence, breakIterators: BreakIteratorGroup, localLastCommitPosition: Int + ): EditorRange { return getSuggestionProvider(subtypeManager.activeSubtype).determineLocalComposing( - subtypeManager.activeSubtype, textBeforeSelection, breakIterators + subtypeManager.activeSubtype, textBeforeSelection, breakIterators, localLastCommitPosition ) } + fun providerForcesSuggestionOn(subtype: Subtype): Boolean { + // Using a cache because I have no idea how fast the runBlocking is + return providersForceSuggestionOn.getOrPut(subtype.nlpProviders.suggestion) { + runBlocking { + getSuggestionProvider(subtype).forcesSuggestionOn + } + } + } + + fun isSuggestionOn(): Boolean = + prefs.suggestion.enabled.get() || providerForcesSuggestionOn(subtypeManager.activeSubtype) + fun suggest(subtype: Subtype, content: EditorContent) { val reqTime = SystemClock.uptimeMillis() scope.launch { @@ -242,7 +257,7 @@ class NlpManager(context: Context) { private fun assembleCandidates() { runBlocking { val candidates = when { - prefs.suggestion.enabled.get() -> { + isSuggestionOn() -> { clipboardSuggestionProvider.suggest( subtype = Subtype.DEFAULT, content = editorInstance.activeContent, diff --git a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/NlpProviders.kt b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/NlpProviders.kt index 642a12c0..8a51dd62 100644 --- a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/NlpProviders.kt +++ b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/NlpProviders.kt @@ -208,7 +208,8 @@ interface SuggestionProvider : NlpProvider { suspend fun determineLocalComposing( subtype: Subtype, textBeforeSelection: CharSequence, - breakIterators: BreakIteratorGroup + breakIterators: BreakIteratorGroup, + localLastCommitPosition: Int, ): EditorRange { return breakIterators.word(subtype.primaryLocale) { it.setText(textBeforeSelection.toString()) @@ -222,6 +223,9 @@ interface SuggestionProvider : NlpProvider { } } } + + val forcesSuggestionOn + get() = false } /** diff --git a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/han/HanShapeBasedLanguageProvider.kt b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/han/HanShapeBasedLanguageProvider.kt index 140646b3..4292ff96 100644 --- a/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/han/HanShapeBasedLanguageProvider.kt +++ b/app/src/main/kotlin/dev/patrickgold/florisboard/ime/nlp/han/HanShapeBasedLanguageProvider.kt @@ -230,39 +230,41 @@ class HanShapeBasedLanguageProvider(val context: Context) : SpellingProvider, Su } override suspend fun getListOfWords(subtype: Subtype): List { - val (languagePackItem, languagePackExtension) = getLanguagePack(subtype) ?: return emptyList(); - val layout: String = languagePackItem.hanShapeBasedTable - try { - val database = languagePackExtension.hanShapeBasedSQLiteDatabase - val cur = database.query(layout, arrayOf ( "text" ), "", arrayOf(), "", "", "weight DESC, code ASC", ""); - cur.moveToFirst(); - val rowCount = cur.getCount(); - val suggestions = buildList { - for (n in 0 until rowCount) { - val word = cur.getString(0); - cur.moveToNext(); - add(word) - } - } - flogDebug { "Read ${suggestions.size} words for ${subtype.primaryLocale.localeTag()}" } - return suggestions; - } catch (e: SQLiteException) { - flogError { "Encountered an SQL error: ${e}" } - return emptyList(); - } + return emptyList() +// val (languagePackItem, languagePackExtension) = getLanguagePack(subtype) ?: return emptyList(); +// val layout: String = languagePackItem.hanShapeBasedTable +// try { +// val database = languagePackExtension.hanShapeBasedSQLiteDatabase +// val cur = database.query(layout, arrayOf ( "text" ), "", arrayOf(), "", "", "weight DESC, code ASC", ""); +// cur.moveToFirst(); +// val rowCount = cur.getCount(); +// val suggestions = buildList { +// for (n in 0 until rowCount) { +// val word = cur.getString(0); +// cur.moveToNext(); +// add(word) +// } +// } +// flogDebug { "Read ${suggestions.size} words for ${subtype.primaryLocale.localeTag()}" } +// return suggestions; +// } catch (e: SQLiteException) { +// flogError { "Encountered an SQL error: ${e}" } +// return emptyList(); +// } } override suspend fun getFrequencyForWord(subtype: Subtype, word: String): Double { - val (languagePackItem, languagePackExtension) = getLanguagePack(subtype) ?: return 0.0; - val layout: String = languagePackItem.hanShapeBasedTable - try { - val database = languagePackExtension.hanShapeBasedSQLiteDatabase - val cur = database.query(layout, arrayOf ( "weight" ), "code = ?", arrayOf(word), "", "", "", ""); - cur.moveToFirst(); - return try { cur.getDouble(0) } catch (e: Exception) { 0.0 }; - } catch (e: SQLiteException) { - return 0.0; - } + return 0.0 +// val (languagePackItem, languagePackExtension) = getLanguagePack(subtype) ?: return 0.0; +// val layout: String = languagePackItem.hanShapeBasedTable +// try { +// val database = languagePackExtension.hanShapeBasedSQLiteDatabase +// val cur = database.query(layout, arrayOf ( "weight" ), "code = ?", arrayOf(word), "", "", "", ""); +// cur.moveToFirst(); +// return try { cur.getDouble(0) } catch (e: Exception) { 0.0 }; +// } catch (e: SQLiteException) { +// return 0.0; +// } } override suspend fun destroy() { @@ -270,14 +272,19 @@ class HanShapeBasedLanguageProvider(val context: Context) : SpellingProvider, Su // the app process is killed (which will most likely always be the case). } - override suspend fun determineLocalComposing(subtype: Subtype, textBeforeSelection: CharSequence, breakIterators: BreakIteratorGroup): EditorRange { + override suspend fun determineLocalComposing( + subtype: Subtype, + textBeforeSelection: CharSequence, + breakIterators: BreakIteratorGroup, + localLastCommitPosition: Int + ): EditorRange { return breakIterators.character(subtype.primaryLocale) { it.setText(textBeforeSelection.toString()) val end = it.last() var start = end var next = it.previous() val keyCodeLocale = keyCode[subtype.primaryLocale.localeTag()]?: keyCode["default"]?: emptySet() - while (next != BreakIterator.DONE) { + while (next != BreakIterator.DONE && start > localLastCommitPosition) { val sub = textBeforeSelection.substring(next, start) if (! sub.all { char -> char in keyCodeLocale }) break @@ -294,4 +301,6 @@ class HanShapeBasedLanguageProvider(val context: Context) : SpellingProvider, Su } } + override val forcesSuggestionOn + get() = true }