Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ android {
minSdk = 30
targetSdk = 35
versionCode = 1
versionName = "1.0"
versionName = "1.0.12"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/kotlin/dev/bsb/moakeyvim/OpenMoaIME.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1135,8 +1135,9 @@ class OpenMoaIME : InputMethodService(), KoinComponent {
return KoLayout(useQwerty = true, simpleQwerty = savedMode.isSimpleQwerty)
}
val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
if (isLandscape && SettingsPreferences.getLandscapeQwerty(this)) {
return KoLayout(useQwerty = true, simpleQwerty = false)
val landscapeLayout = SettingsPreferences.getLandscapeKoLayout(this)
if (isLandscape && landscapeLayout.isQwerty) {
return KoLayout(useQwerty = true, simpleQwerty = landscapeLayout.isSimple)
}
return KoLayout(useQwerty = false, simpleQwerty = false)
}
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/kotlin/dev/bsb/moakeyvim/config/LandscapeKoLayout.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.bsb.moakeyvim.config

import dev.bsb.moakeyvim.R

enum class LandscapeKoLayout(val labelResId: Int) {
NONE(R.string.settings_landscape_ko_layout_none),
QWERTY(R.string.settings_input_mode_qwerty),
QWERTY_SIMPLE(R.string.settings_input_mode_qwerty_simple);

val isQwerty: Boolean get() = this != NONE
val isSimple: Boolean get() = this == QWERTY_SIMPLE

companion object {
fun fromString(value: String?): LandscapeKoLayout =
values().find { it.name == value } ?: NONE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import dev.bsb.moakeyvim.hotstring.HotstringRule
import dev.bsb.moakeyvim.hotstring.HotstringRepository
import dev.bsb.moakeyvim.config.EnterLongPressAction
import dev.bsb.moakeyvim.config.HangulInputMode
import dev.bsb.moakeyvim.config.LandscapeKoLayout
import dev.bsb.moakeyvim.config.HapticStrength
import dev.bsb.moakeyvim.config.KeyboardSkin
import dev.bsb.moakeyvim.config.KeypadHeight
Expand Down Expand Up @@ -62,7 +63,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
SettingsPreferences.KEY_WORD_SUGGESTION_ENABLED,
SettingsPreferences.KEY_KOREAN_WORD_SUGGESTION_ENABLED,
SettingsPreferences.KEY_CLIPBOARD_ENABLED,
SettingsPreferences.KEY_LANDSCAPE_QWERTY,
SettingsPreferences.KEY_FLOATING_INDICATOR_ENABLED,
SettingsPreferences.KEY_OVERLAY_PERMISSION_NOTIFIED,
SettingsPreferences.KEY_HW_CAPSLOCK_TO_CTRL,
Expand Down Expand Up @@ -124,6 +124,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
setupClipboardListPreferences()
pref<ListPreference>(SettingsPreferences.KEY_HANGUL_INPUT_MODE)
?.setupEnum(HangulInputMode.values(), { it.labelResId }, HangulInputMode.TWO_HAND_MOAKEY)
pref<ListPreference>(SettingsPreferences.KEY_LANDSCAPE_KO_LAYOUT)
?.setupEnum(LandscapeKoLayout.values(), { it.labelResId }, LandscapeKoLayout.NONE)
Comment on lines +127 to +128

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve migrated legacy value instead of hard-defaulting to NONE.

Line 127~128 currently initializes landscape_ko_layout to LandscapeKoLayout.NONE when unset, which can overwrite users’ legacy landscape_qwerty=true before migration logic is applied.

Suggested fix
-        pref<ListPreference>(SettingsPreferences.KEY_LANDSCAPE_KO_LAYOUT)
-            ?.setupEnum(LandscapeKoLayout.values(), { it.labelResId }, LandscapeKoLayout.NONE)
+        pref<ListPreference>(SettingsPreferences.KEY_LANDSCAPE_KO_LAYOUT)
+            ?.setupEnum(
+                LandscapeKoLayout.values(),
+                { it.labelResId },
+                SettingsPreferences.getLandscapeKoLayout(requireContext())
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pref<ListPreference>(SettingsPreferences.KEY_LANDSCAPE_KO_LAYOUT)
?.setupEnum(LandscapeKoLayout.values(), { it.labelResId }, LandscapeKoLayout.NONE)
pref<ListPreference>(SettingsPreferences.KEY_LANDSCAPE_KO_LAYOUT)
?.setupEnum(
LandscapeKoLayout.values(),
{ it.labelResId },
SettingsPreferences.getLandscapeKoLayout(requireContext())
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/kotlin/dev/bsb/moakeyvim/settings/SettingsFragment.kt` around
lines 127 - 128, The current call to
pref<ListPreference>(SettingsPreferences.KEY_LANDSCAPE_KO_LAYOUT)?.setupEnum(...,
LandscapeKoLayout.NONE) forces a default of NONE and can overwrite legacy
landscape_qwerty before migration; change it to preserve any existing/migrated
value by removing the hard-default (do not pass LandscapeKoLayout.NONE) and
instead pass null or the current stored value retrieved from SettingsPreferences
(or run migration first) so setupEnum uses the existing preference; update the
call site (pref<ListPreference>(SettingsPreferences.KEY_LANDSCAPE_KO_LAYOUT) and
setupEnum) to avoid forcing NONE.

pref<ListPreference>(SettingsPreferences.KEY_KEYBOARD_SKIN)
?.setupEnum(KeyboardSkin.values(), { it.labelResId }, KeyboardSkin.WHITE)
pref<ListPreference>(SettingsPreferences.KEY_KEYPAD_HEIGHT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dev.bsb.moakeyvim.config.EnterLongPressAction
import dev.bsb.moakeyvim.config.GestureAnglePreset
import dev.bsb.moakeyvim.config.GestureAngles
import dev.bsb.moakeyvim.config.HangulInputMode
import dev.bsb.moakeyvim.config.LandscapeKoLayout
import dev.bsb.moakeyvim.config.HapticStrength
import dev.bsb.moakeyvim.config.KeyboardSkin
import dev.bsb.moakeyvim.config.SoundType
Expand Down Expand Up @@ -45,6 +46,7 @@ object SettingsPreferences {
const val KEY_CLIPBOARD_EXPIRY_MINUTES = "clipboard_expiry_minutes"
const val KEY_MIN_LEARN_COUNT = "min_learn_count"
const val KEY_LANDSCAPE_QWERTY = "landscape_qwerty"
const val KEY_LANDSCAPE_KO_LAYOUT = "landscape_ko_layout"
const val KEY_FLOATING_INDICATOR_ENABLED = "floating_indicator_enabled"
const val KEY_FLOATING_INDICATOR_X = "floating_indicator_x"
const val KEY_FLOATING_INDICATOR_Y = "floating_indicator_y"
Expand Down Expand Up @@ -79,6 +81,7 @@ object SettingsPreferences {
KEY_CLIPBOARD_EXPIRY_MINUTES,
KEY_MIN_LEARN_COUNT,
KEY_LANDSCAPE_QWERTY,
KEY_LANDSCAPE_KO_LAYOUT,
KEY_FLOATING_INDICATOR_ENABLED,
KEY_FLOATING_INDICATOR_X,
KEY_FLOATING_INDICATOR_Y,
Expand Down Expand Up @@ -197,9 +200,21 @@ object SettingsPreferences {
prefs(context).edit().putBoolean(KEY_CLIPBOARD_ENABLED, enabled).apply()
}

fun getLandscapeQwerty(context: Context): Boolean =
prefs(context).getBoolean(KEY_LANDSCAPE_QWERTY, false)
fun getLandscapeKoLayout(context: Context): LandscapeKoLayout {
val p = prefs(context)
val saved = p.getString(KEY_LANDSCAPE_KO_LAYOUT, null)
if (saved != null) return LandscapeKoLayout.fromString(saved)
// 기존 boolean 설정 마이그레이션
return if (p.getBoolean(KEY_LANDSCAPE_QWERTY, false)) {
LandscapeKoLayout.QWERTY
} else {
LandscapeKoLayout.NONE
}
}

fun getLandscapeQwerty(context: Context): Boolean = getLandscapeKoLayout(context).isQwerty

@Deprecated("Use getLandscapeKoLayout() instead", ReplaceWith("getLandscapeKoLayout(context)"))
fun setLandscapeQwerty(context: Context, enabled: Boolean) {
prefs(context).edit().putBoolean(KEY_LANDSCAPE_QWERTY, enabled).apply()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ class OpenMoaView : ConstraintLayout, KoinComponent {
CrossKeyTouchListener(
context,
listOf(
StringKeyMessage(""),
StringKeyMessage(""),
StringKeyMessage("ㅡ"),
StringKeyMessage("ㆍ"),
StringKeyMessage("ㅣ"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,27 @@ import dev.bsb.moakeyvim.view.message.StringKeyMessage
import dev.bsb.moakeyvim.view.preview.KeyPreviewController
import kotlin.math.*

// keyList 순서: [0]=위/왼, [1]=오른쪽, [2]=기본(탭), [3]=왼쪽/아래
// keyList 순서: [0]=위, [1]=오른쪽, [2]=기본(탭/아래), [3]=왼쪽
internal fun resolveKeyFromGesture(
startX: Float,
startY: Float,
currentX: Float,
currentY: Float,
gestureThreshold: Float,
keyList: List<StringKeyMessage>,
): StringKeyMessage {
require(keyList.size == 4) { "keyList must have exactly 4 entries: [up, right, center, left]" }
val distance = sqrt((currentX - startX).pow(2) + (currentY - startY).pow(2))
if (distance <= gestureThreshold) return keyList[2]
val degree = ((atan2(currentY - startY, currentX - startX) * 180.0) / PI).toFloat()
// atan2 범위: (-180, 180]. abs >= 135도 = 왼쪽(keyList[3])
return when {
abs(degree) < 45f -> keyList[1]
abs(degree) < 135f -> if (degree > 0) keyList[2] else keyList[0]
else -> keyList[3]
}
}

class CrossKeyTouchListener(
context: Context,
private val keyList: List<StringKeyMessage>,
Expand All @@ -18,16 +38,8 @@ class CrossKeyTouchListener(
private var startX: Float = 0f
private var startY: Float = 0f

private fun resolveKey(currentX: Float, currentY: Float): StringKeyMessage {
val distance = sqrt((currentX - startX).pow(2) + (currentY - startY).pow(2))
if (distance <= config.gestureThreshold) return keyList[2]
val degree = (atan2(currentY - startY, currentX - startX) * 180f) / PI
return when {
abs(degree) < 45f -> keyList[1]
abs(degree) < 135f -> if (degree > 0) keyList[2] else keyList[0]
else -> keyList[3]
}
}
private fun resolveKey(currentX: Float, currentY: Float): StringKeyMessage =
resolveKeyFromGesture(startX, startY, currentX, currentY, config.gestureThreshold, keyList)

@SuppressLint("ClickableViewAccessibility")
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ class SuggestionBarView @JvmOverloads constructor(
val showFunctionKeys = words.isEmpty()
leftActions.visibility = if (showFunctionKeys) VISIBLE else GONE
rightActions.visibility = if (showFunctionKeys) VISIBLE else GONE
words.forEach { word -> container.addView(buildWordView(word, word in hotstringExpansions)) }
if (words.isNotEmpty()) {
val skin = SettingsPreferences.getKeyboardSkin(context)
val wordColor = dimTextColor(SkinApplier.fgColor(context, skin))
words.forEach { word -> container.addView(buildWordView(word, word in hotstringExpansions, wordColor)) }
}
}

fun showClipboard(text: String, onPaste: (String) -> Unit) {
Expand Down Expand Up @@ -171,12 +175,14 @@ class SuggestionBarView @JvmOverloads constructor(
scrollView.scrollTo(0, 0)
leftActions.visibility = VISIBLE
rightActions.visibility = VISIBLE
container.addView(buildActionView(context.getString(R.string.key_cut), onCut))
container.addView(buildActionView(context.getString(R.string.key_copy), onCopy))
val skin = SettingsPreferences.getKeyboardSkin(context)
val actionColor = dimTextColor(SkinApplier.fgColor(context, skin))
container.addView(buildActionView(context.getString(R.string.key_cut), onCut, actionColor))
container.addView(buildActionView(context.getString(R.string.key_copy), onCopy, actionColor))
}

fun applyColors(textColor: Int, bgColor: Int, keyBgColor: Int = Color.WHITE) {
currentTextColor = textColor
currentTextColor = dimTextColor(textColor)
currentBgColor = bgColor
currentKeyBgColor = keyBgColor
setBackgroundColor(bgColor)
Expand All @@ -193,17 +199,17 @@ class SuggestionBarView @JvmOverloads constructor(
(child.getChildAt(1) as? TextView)?.setTextColor(textColor)
child.background = buildChipBackground()
}
child is TextView -> child.setTextColor(textColor)
child is TextView -> child.setTextColor(currentTextColor)
child is ImageButton -> child.imageTintList = tintList
}
}
}

private fun buildWordView(word: String, isHotstring: Boolean): TextView {
private fun buildWordView(word: String, isHotstring: Boolean, wordColor: Int): TextView {
return TextView(context).apply {
text = word
text = word.trim()
textSize = TEXT_SIZE_SP
setTextColor(currentTextColor)
setTextColor(wordColor)
setPadding(hPad, vPad, hPad, vPad)
gravity = Gravity.CENTER
isClickable = true
Expand Down Expand Up @@ -271,11 +277,11 @@ class SuggestionBarView @JvmOverloads constructor(
return buildIconButton(R.drawable.ic_content_paste) { onOpenClipboardPanel?.invoke() }
}

private fun buildActionView(label: String, action: () -> Unit): TextView {
private fun buildActionView(label: String, action: () -> Unit, actionColor: Int): TextView {
return TextView(context).apply {
text = label
textSize = TEXT_SIZE_SP
setTextColor(currentTextColor)
setTextColor(actionColor)
setPadding(hPad, vPad, hPad, vPad)
gravity = Gravity.CENTER
isClickable = true
Expand Down Expand Up @@ -336,6 +342,13 @@ class SuggestionBarView @JvmOverloads constructor(
}
}

private fun dimTextColor(color: Int): Int {
val hsv = FloatArray(3)
Color.colorToHSV(color, hsv)
hsv[2] *= 0.88f
return Color.HSVToColor(hsv)
}

private fun buildRipple(): RippleDrawable {
return RippleDrawable(ColorStateList.valueOf(0x22000000), null, ColorDrawable(Color.WHITE))
}
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@
<string name="settings_input_mode_qwerty">두벌식</string>
<string name="settings_input_mode_qwerty_simple">두벌식 단모음</string>
<string name="settings_landscape_qwerty_title">가로 모드 두벌식</string>
<string name="settings_landscape_qwerty_description">가로 모드일 때 한글 자판을 쿼티로 표시합니다</string>
<string name="settings_landscape_ko_layout_title">가로 모드 입력 방식</string>
<string name="settings_landscape_ko_layout_none">세로와 동일(기본)</string>

<string name="settings_keyboard_section">키보드 외형</string>
<string name="settings_keypad_height_title">키패드 높이 조절</string>
Expand Down
10 changes: 5 additions & 5 deletions app/src/main/res/xml/preferences_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
android:negativeButtonText="@string/dialog_close"
app:icon="@drawable/ic_pref_keyboard" />

<SwitchPreferenceCompat
app:key="landscape_qwerty"
app:title="@string/settings_landscape_qwerty_title"
app:summary="@string/settings_landscape_qwerty_description"
app:defaultValue="false"
<ListPreference
app:key="landscape_ko_layout"
app:title="@string/settings_landscape_ko_layout_title"
app:useSimpleSummaryProvider="true"
android:negativeButtonText="@string/dialog_close"
app:icon="@drawable/ic_pref_keyboard" />

<Preference
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package dev.bsb.moakeyvim.view.keytouchlistener

import dev.bsb.moakeyvim.view.message.StringKeyMessage
import org.junit.Assert.assertEquals
import org.junit.Test

class CrossKeyTouchListenerTest {

private val threshold = 50f

private fun testKeyList() = listOf(
StringKeyMessage("UP"),
StringKeyMessage("RIGHT"),
StringKeyMessage("CENTER"),
StringKeyMessage("LEFT"),
)

private fun moeumKeyList() = listOf(
StringKeyMessage("ㆍ"),
StringKeyMessage("ㅡ"),
StringKeyMessage("ㆍ"),
StringKeyMessage("ㅣ"),
)

@Test
fun `임계값 이하 이동은 CENTER키 반환`() {
val result = resolveKeyFromGesture(0f, 0f, 10f, 10f, threshold, testKeyList())
assertEquals("CENTER", result.key)
}

@Test
fun `위로 스와이프하면 keyList-0 반환`() {
val result = resolveKeyFromGesture(0f, 100f, 0f, 0f, threshold, testKeyList())
assertEquals("UP", result.key)
}

@Test
fun `오른쪽으로 스와이프하면 keyList-1 반환`() {
val result = resolveKeyFromGesture(0f, 0f, 100f, 0f, threshold, testKeyList())
assertEquals("RIGHT", result.key)
}

@Test
fun `아래로 스와이프하면 keyList-2 반환`() {
val result = resolveKeyFromGesture(0f, 0f, 0f, 100f, threshold, testKeyList())
assertEquals("CENTER", result.key)
}

@Test
fun `왼쪽으로 스와이프하면 keyList-3 반환`() {
val result = resolveKeyFromGesture(100f, 0f, 0f, 0f, threshold, testKeyList())
assertEquals("LEFT", result.key)
}

@Test
fun `임계값 경계값 - threshold 초과 시 방향 감지`() {
val justOver = resolveKeyFromGesture(0f, 100f, 0f, 49f, threshold, testKeyList())
assertEquals("UP", justOver.key)
}

@Test
fun `임계값 경계값 - threshold 이하 시 CENTER 반환`() {
val justUnder = resolveKeyFromGesture(0f, 0f, 0f, 50f, threshold, testKeyList())
assertEquals("CENTER", justUnder.key)
}

@Test
fun `모아키 모음키 위로 스와이프는 아래아 단일 반환 - 이중 아래아 아님`() {
val result = resolveKeyFromGesture(0f, 100f, 0f, 0f, threshold, moeumKeyList())
assertEquals("ㆍ", result.key)
assert(result.key != "ᆢ") { "위로 스와이프에 아래아 이중(ᆢ)이 반환되면 안 됨" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether JVM assertions are enabled for tests and find Kotlin assert usage in test sources.
rg -n --iglob '*gradle*' 'jvmArgs|enableAssertions|-ea'
rg -n --iglob '*Test.kt' '\bassert\s*\('

Repository: verlane/moakey-vim

Length of output: 866


🏁 Script executed:

#!/bin/bash
# Find all gradle configuration files
find . -name "build.gradle*" -o -name "settings.gradle*" | head -20

# Check for gradle wrapper properties that might affect JVM args
cat gradle/wrapper/gradle-wrapper.properties 2>/dev/null || echo "No gradle wrapper properties found"

# Search more broadly for test task configuration and JVM argument settings
rg -U 'test\s*\{|jvmArgs|java\s*\{' --type kotlin --type gradle -A 5 | head -50

Repository: verlane/moakey-vim

Length of output: 365


🏁 Script executed:

#!/bin/bash
# Check the main build.gradle and app/build.gradle for test configuration
echo "=== ./build.gradle ==="
cat ./build.gradle

echo -e "\n=== ./app/build.gradle ==="
cat ./app/build.gradle

# Also check if JUnit is in dependencies
echo -e "\n=== Checking for JUnit in dependencies ==="
rg 'junit|jUnit' build.gradle app/build.gradle

Repository: verlane/moakey-vim

Length of output: 2009


🏁 Script executed:

#!/bin/bash
# Check the test file to understand context and verify assert usage
cat -n app/src/test/kotlin/dev/bsb/moakeyvim/view/keytouchlistener/CrossKeyTouchListenerTest.kt | sed -n '65,80p'

# Check what JUnit imports are used in the test
head -30 app/src/test/kotlin/dev/bsb/moakeyvim/view/keytouchlistener/CrossKeyTouchListenerTest.kt

Repository: verlane/moakey-vim

Length of output: 1169


Replace Kotlin assert with JUnit assertion.

Line 71 uses Kotlin's assert(...) which does not execute unless JVM is started with -ea flag. The gradle configuration has no such flag enabled, so this check will silently pass without validation. Use JUnit's assertTrue(result.key != "ᆢ") or assertNotEquals("ᆢ", result.key) instead, which are already imported in the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/test/kotlin/dev/bsb/moakeyvim/view/keytouchlistener/CrossKeyTouchListenerTest.kt`
at line 71, The test in CrossKeyTouchListenerTest.kt uses Kotlin's
assert(result.key != "ᆢ") which is ignored without -ea; replace it with a JUnit
assertion such as assertNotEquals("ᆢ", result.key) (or assertTrue(result.key !=
"ᆢ")) to ensure the check always runs; update the assertion call in the test
method (referencing result.key) using the existing JUnit imports.

}
}
Loading