From c491f1372c19adeef30f0d1f91c669f408f57238 Mon Sep 17 00:00:00 2001 From: Bing ZHEUNG Date: Wed, 1 Jan 2025 21:32:48 +0800 Subject: [PATCH] Decrease ToolBar height from 60 to 56; Re-implement CandidateView; Implement RemoveCandidateFromMemory --- .../jyutping/JyutpingInputMethodService.kt | 8 +- .../jyutping/keyboard/CandidateBoard.kt | 25 +++--- .../jyutping/keyboard/CandidateScrollBar.kt | 39 ++------- .../jyutping/keyboard/CandidateView.kt | 86 ++++++++++++++++++- .../org/jyutping/jyutping/keyboard/Engine.kt | 6 +- .../jyutping/presets/PresetConstant.kt | 2 +- .../jyutping/utilities/UserLexiconHelper.kt | 19 ++-- 7 files changed, 119 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/org/jyutping/jyutping/JyutpingInputMethodService.kt b/app/src/main/java/org/jyutping/jyutping/JyutpingInputMethodService.kt index 107937a..763399e 100644 --- a/app/src/main/java/org/jyutping/jyutping/JyutpingInputMethodService.kt +++ b/app/src/main/java/org/jyutping/jyutping/JyutpingInputMethodService.kt @@ -437,6 +437,9 @@ class JyutpingInputMethodService: LifecycleInputMethodService(), private val selectedCandidates: MutableList by lazy { mutableListOf() } private val userDB by lazy { UserLexiconHelper(this) } + fun forgetCandidate(candidate: Candidate) { + userDB.remove(candidate) + } fun clearUserLexicon() { userDB.deleteAll() } @@ -610,8 +613,7 @@ class JyutpingInputMethodService: LifecycleInputMethodService(), currentInputConnection.commitText(text, 1) adjustKeyboardCase() } - fun selectCandidate(candidate: Candidate? = null, index: Int = 0) { - val candidate: Candidate = candidate ?: candidates.value.getOrNull(index) ?: return + fun selectCandidate(candidate: Candidate) { currentInputConnection.commitText(candidate.text, 1) selectedCandidates.add(candidate) val firstChar = bufferText.firstOrNull() @@ -708,7 +710,7 @@ class JyutpingInputMethodService: LifecycleInputMethodService(), fun space() { if (isBuffering.value) { if (candidates.value.isNotEmpty()) { - selectCandidate() + candidates.value.firstOrNull()?.let { selectCandidate(it) } } else { currentInputConnection.commitText(bufferText, 1) bufferText = PresetString.EMPTY diff --git a/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateBoard.kt b/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateBoard.kt index de1b3f2..e49d31a 100644 --- a/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateBoard.kt +++ b/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateBoard.kt @@ -5,8 +5,6 @@ import android.view.HapticFeedbackConstants import android.view.SoundEffectConstants import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -30,7 +28,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -62,11 +59,14 @@ private class CandidateRow(val identifier: Int, val candidates: List, fun CandidateBoard(height: Dp) { val collapseWidth: Dp = 44.dp val collapseHeight: Dp = 44.dp - val interactionSource = remember { MutableInteractionSource() } val view = LocalView.current val context = LocalContext.current as JyutpingInputMethodService val commentStyle by context.commentStyle.collectAsState() - val rowVerticalAlignment: Alignment.Vertical = if (commentStyle.isBelow()) Alignment.Top else Alignment.Bottom + val rowVerticalAlignment: Alignment.Vertical = when (commentStyle) { + CommentStyle.AboveCandidates -> Alignment.Bottom + CommentStyle.BelowCandidates -> Alignment.Top + CommentStyle.NoComments -> Alignment.CenterVertically + } val isDarkMode by context.isDarkMode.collectAsState() val isHighContrastPreferred by context.isHighContrastPreferred.collectAsState() val extraBottomPadding by context.extraBottomPadding.collectAsState() @@ -137,18 +137,15 @@ fun CandidateBoard(height: Dp) { verticalAlignment = rowVerticalAlignment ) { row.candidates.map { - CandidateView( + AltCandidateView( + modifier = Modifier + .padding(2.dp) + .weight(it.width() / row.width), candidate = it, commentStyle = commentStyle, isDarkMode = isDarkMode, - modifier = Modifier - .clickable(interactionSource = interactionSource, indication = null) { - view.playSoundEffect(SoundEffectConstants.CLICK) - view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) - context.selectCandidate(it) - } - .padding(2.dp) - .weight(it.width() / row.width) + selection = { context.selectCandidate(it) }, + deletion = { context.forgetCandidate(it) } ) } if (row.identifier == minRowIdentifier) { diff --git a/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateScrollBar.kt b/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateScrollBar.kt index 298d76d..90ef04e 100644 --- a/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateScrollBar.kt +++ b/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateScrollBar.kt @@ -3,19 +3,16 @@ package org.jyutping.jyutping.keyboard import android.view.HapticFeedbackConstants import android.view.SoundEffectConstants import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -24,7 +21,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -42,25 +38,9 @@ import org.jyutping.jyutping.presets.PresetColor fun CandidateScrollBar() { val expanderWidth: Dp = 44.dp val dividerHeight: Dp = 24.dp - val interactionSource = remember { MutableInteractionSource() } val view = LocalView.current val context = LocalContext.current as JyutpingInputMethodService val commentStyle by context.commentStyle.collectAsState() - val candidateViewTopInset: Dp = when (commentStyle) { - CommentStyle.AboveCandidates -> 0.dp - CommentStyle.BelowCandidates -> 4.dp - CommentStyle.NoComments -> 0.dp - } - val candidateViewBottomInset: Dp = when (commentStyle) { - CommentStyle.AboveCandidates -> 12.dp - CommentStyle.BelowCandidates -> 0.dp - CommentStyle.NoComments -> 16.dp - } - val candidateRowVerticalAlignment: Alignment.Vertical = when (commentStyle) { - CommentStyle.AboveCandidates -> Alignment.Bottom - CommentStyle.BelowCandidates -> Alignment.Top - CommentStyle.NoComments -> Alignment.Bottom - } val isDarkMode by context.isDarkMode.collectAsState() val isHighContrastPreferred by context.isHighContrastPreferred.collectAsState() val state = rememberLazyListState() @@ -76,22 +56,15 @@ fun CandidateScrollBar() { modifier = Modifier.fillMaxSize(), state = state, horizontalArrangement = Arrangement.spacedBy(0.dp), - verticalAlignment = candidateRowVerticalAlignment + verticalAlignment = Alignment.CenterVertically ) { - itemsIndexed(candidates) { index, candidate -> + items(candidates) { CandidateView( - candidate = candidate, + candidate = it, commentStyle = commentStyle, isDarkMode = isDarkMode, - modifier = Modifier - .clickable(interactionSource = interactionSource, indication = null) { - view.playSoundEffect(SoundEffectConstants.CLICK) - view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) - context.selectCandidate(index = index) - } - .padding(horizontal = if (candidate.type.isCantonese()) 6.dp else 10.dp) - .padding(vertical = if (candidate.type.isCantonese()) 0.dp else 4.dp) - .padding(top = candidateViewTopInset, bottom = candidateViewBottomInset) + selection = { context.selectCandidate(it) }, + deletion = { context.forgetCandidate(it) } ) } } diff --git a/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateView.kt b/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateView.kt index 4a95836..2aff51d 100644 --- a/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateView.kt +++ b/app/src/main/java/org/jyutping/jyutping/keyboard/CandidateView.kt @@ -1,22 +1,104 @@ package org.jyutping.jyutping.keyboard +import android.view.HapticFeedbackConstants +import android.view.SoundEffectConstants +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import org.jyutping.jyutping.presets.PresetString @Composable -fun CandidateView(candidate: Candidate, commentStyle: CommentStyle, isDarkMode: Boolean, modifier: Modifier) { +fun CandidateView(candidate: Candidate, commentStyle: CommentStyle, isDarkMode: Boolean, selection: () -> Unit, deletion: () -> Unit) { val isCantonese: Boolean = candidate.type.isCantonese() val textColor: Color = if (isDarkMode) Color.White else Color.Black + val view = LocalView.current + Box( + modifier = Modifier + .pointerInput(Unit) { + detectTapGestures( + onLongPress = { + view.playSoundEffect(SoundEffectConstants.CLICK) + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + deletion() + }, + onPress = { + view.playSoundEffect(SoundEffectConstants.CLICK) + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS) + }, + onTap = { selection() } + ) + } + .padding(horizontal = if (isCantonese) 6.dp else 10.dp) + .fillMaxHeight(), + contentAlignment = Alignment.Center + ) { + Color.Transparent + Box( + modifier = Modifier + .alpha(if (commentStyle.isNone()) 0f else 1f) + .fillMaxHeight(), + contentAlignment = if (commentStyle.isBelow()) Alignment.BottomCenter else Alignment.TopCenter + ) { + Color.Transparent + Text( + text = if (isCantonese) candidate.romanization else PresetString.SPACE, + modifier = Modifier + .padding(vertical = 2.dp) + .height(20.dp), + color = textColor, + fontSize = 12.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } + Text( + text = candidate.text, + modifier = Modifier + .padding(bottom = if (commentStyle.isBelow()) 16.dp else 0.dp), + color = textColor, + fontSize = 20.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } +} + +@Composable +fun AltCandidateView(modifier: Modifier, candidate: Candidate, commentStyle: CommentStyle, isDarkMode: Boolean, selection: () -> Unit, deletion: () -> Unit) { + val isCantonese: Boolean = candidate.type.isCantonese() + val textColor: Color = if (isDarkMode) Color.White else Color.Black + val view = LocalView.current Column( - modifier = modifier, + modifier = modifier + .pointerInput(Unit) { + detectTapGestures( + onLongPress = { + view.playSoundEffect(SoundEffectConstants.CLICK) + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + deletion() + }, + onPress = { + view.playSoundEffect(SoundEffectConstants.CLICK) + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS) + }, + onTap = { selection() } + ) + }, verticalArrangement = Arrangement.spacedBy((-2).dp), horizontalAlignment = Alignment.CenterHorizontally ) { diff --git a/app/src/main/java/org/jyutping/jyutping/keyboard/Engine.kt b/app/src/main/java/org/jyutping/jyutping/keyboard/Engine.kt index 2bb2cd4..6aebf01 100644 --- a/app/src/main/java/org/jyutping/jyutping/keyboard/Engine.kt +++ b/app/src/main/java/org/jyutping/jyutping/keyboard/Engine.kt @@ -11,9 +11,9 @@ object Engine { return when (text.length) { 0 -> emptyList() 1 -> when (text) { - "a" -> db.pingMatch(text = text, input = text) + db.pingMatch(text = "aa", input = text, mark = text) + db.shortcutMatch(text) - "o", "m", "e" -> db.pingMatch(text = text, input = text) + db.shortcutMatch(text) - else -> db.shortcutMatch(text) + "a" -> db.pingMatch(text = text, input = text) + db.pingMatch(text = "aa", input = text, mark = text) + db.shortcutMatch(text = text, limit = 100) + "o", "m", "e" -> db.pingMatch(text = text, input = text) + db.shortcutMatch(text = text, limit = 100) + else -> db.shortcutMatch(text = text, limit = 100) } else -> { if (asap) { diff --git a/app/src/main/java/org/jyutping/jyutping/presets/PresetConstant.kt b/app/src/main/java/org/jyutping/jyutping/presets/PresetConstant.kt index fef8dbf..bfd464f 100644 --- a/app/src/main/java/org/jyutping/jyutping/presets/PresetConstant.kt +++ b/app/src/main/java/org/jyutping/jyutping/presets/PresetConstant.kt @@ -2,7 +2,7 @@ package org.jyutping.jyutping.presets @Suppress("ConstPropertyName") object PresetConstant { - const val ToolBarHeight: Float = 60f + const val ToolBarHeight: Float = 56f const val SpaceKeyLongPressHint: String = "← →" const val keyboardPackageName: String = "org.jyutping.jyutping" const val keyboardId: String = "org.jyutping.jyutping/.JyutpingInputMethodService" diff --git a/app/src/main/java/org/jyutping/jyutping/utilities/UserLexiconHelper.kt b/app/src/main/java/org/jyutping/jyutping/utilities/UserLexiconHelper.kt index ddbbd81..7035c10 100644 --- a/app/src/main/java/org/jyutping/jyutping/utilities/UserLexiconHelper.kt +++ b/app/src/main/java/org/jyutping/jyutping/utilities/UserLexiconHelper.kt @@ -22,17 +22,9 @@ class UserLexiconHelper(context: Context) : SQLiteOpenHelper(context, UserSettin db?.execSQL(createTable) } - fun handle(candidate: Candidate) { - val id: Int = (candidate.lexiconText + candidate.romanization).hashCode() - val frequency = find(id) - if (frequency != null) { - update(id = id, frequency = frequency) - } else { - val lexicon = UserLexicon.convert(candidate) - insert(lexicon) - } - } fun process(candidates: List) { + val isNotAllCantonese = candidates.count { it.type.isNotCantonese() } > 0 + if (isNotAllCantonese) return val lexicon = UserLexicon.join(candidates) val id = lexicon.id val frequency = find(id) @@ -66,6 +58,13 @@ class UserLexiconHelper(context: Context) : SQLiteOpenHelper(context, UserSettin return null } } + + fun remove(candidate: Candidate) { + if (candidate.type.isNotCantonese()) return + val id: Int = (candidate.lexiconText + candidate.romanization).hashCode() + val command: String = "DELETE FROM memory WHERE id = ${id};" + this.writableDatabase.execSQL(command) + } fun deleteAll() { val command: String = "DELETE FROM memory;" this.writableDatabase.execSQL(command)