diff --git a/app/build.gradle b/app/build.gradle
index 161057c..acec345 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -30,8 +30,9 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
- viewBinding {
- enabled = true
+ buildFeatures {
+ viewBinding true
+ buildConfig true
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 53eec17..4ea604c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,6 +29,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/OpenMoaIME.kt b/app/src/main/kotlin/pe/aioo/openmoa/OpenMoaIME.kt
index e8d0b35..fced642 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/OpenMoaIME.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/OpenMoaIME.kt
@@ -12,6 +12,7 @@ import android.inputmethodservice.InputMethodService
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
+import android.view.inputmethod.InputMethodManager
import android.text.InputType
import android.util.Size
import android.view.KeyEvent
@@ -34,11 +35,17 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import pe.aioo.openmoa.config.Config
+import pe.aioo.openmoa.config.HangulInputMode
+import pe.aioo.openmoa.config.KeyboardSkin
+import pe.aioo.openmoa.config.OneHandMode
import pe.aioo.openmoa.databinding.OpenMoaImeBinding
import pe.aioo.openmoa.hangul.HangulAssembler
+import pe.aioo.openmoa.settings.SettingsActivity
+import pe.aioo.openmoa.settings.SettingsPreferences
import pe.aioo.openmoa.view.keyboardview.*
import pe.aioo.openmoa.view.keyboardview.qwerty.QuertyView
import pe.aioo.openmoa.view.message.SpecialKey
+import pe.aioo.openmoa.view.skin.SkinApplier
import java.io.Serializable
import kotlin.math.roundToInt
@@ -46,12 +53,14 @@ class OpenMoaIME : InputMethodService(), KoinComponent {
private lateinit var binding: OpenMoaImeBinding
private lateinit var broadcastReceiver: BroadcastReceiver
- private lateinit var keyboardViews: Map
+ private lateinit var keyboardViews: MutableMap
private val config: Config by inject()
private val hangulAssembler = HangulAssembler()
private var imeMode = IMEMode.IME_KO
private var previousImeMode = IMEMode.IME_KO
private var composingText = ""
+ private var lastSpaceTime = 0L
+ private var lastAppliedSkin: KeyboardSkin = KeyboardSkin.DEFAULT
private fun finishComposing() {
currentInputConnection?.finishComposingText()
@@ -310,6 +319,18 @@ class OpenMoaIME : InputMethodService(), KoinComponent {
setKeyboard(IMEMode.IME_EMOJI)
}
}
+ SpecialKey.OPEN_SETTINGS -> {
+ startActivity(
+ Intent(this@OpenMoaIME, SettingsActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ )
+ }
+ SpecialKey.SHOW_IME_PICKER -> {
+ finishComposing()
+ val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
+ imm?.showInputMethodPicker()
+ }
}
}
is String -> {
@@ -332,7 +353,22 @@ class OpenMoaIME : InputMethodService(), KoinComponent {
} else {
// Process for another key
finishComposing()
- currentInputConnection.commitText(key, 1)
+ if (key == " ") {
+ val autoEnabled = SettingsPreferences.getAutoSpacePeriod(this@OpenMoaIME)
+ val now = SystemClock.elapsedRealtime()
+ if (autoEnabled && now - lastSpaceTime < 1000L &&
+ currentInputConnection.getTextBeforeCursor(1, 0) == " ") {
+ currentInputConnection.deleteSurroundingText(1, 0)
+ currentInputConnection.commitText(". ", 1)
+ lastSpaceTime = 0L
+ } else {
+ currentInputConnection.commitText(key, 1)
+ lastSpaceTime = if (autoEnabled) now else 0L
+ }
+ } else {
+ currentInputConnection.commitText(key, 1)
+ lastSpaceTime = 0L
+ }
}
}
}
@@ -353,7 +389,10 @@ class OpenMoaIME : InputMethodService(), KoinComponent {
when (it) {
is PunctuationView -> it.setPageOrNextPage(0)
is PhoneView -> it.setPageOrNextPage(0)
- is ArrowView -> it.setSelectingOrToggleSelecting(false)
+ is ArrowView -> {
+ it.setSelectingOrToggleSelecting(false)
+ it.refreshOneHandMode()
+ }
}
binding.keyboardFrameLayout.setKeyboardView(it)
}
@@ -361,6 +400,7 @@ class OpenMoaIME : InputMethodService(), KoinComponent {
}
private fun setShiftAutomatically() {
+ if (!config.autoCapitalizeEnglish) return
keyboardViews[imeMode]?.let { view ->
if (view is QuertyView) {
currentInputConnection?.let { inputConnection ->
@@ -391,30 +431,22 @@ class OpenMoaIME : InputMethodService(), KoinComponent {
@SuppressLint("InflateParams")
override fun onCreateInputView(): View {
super.onCreateInputView()
- window.window?.apply {
- navigationBarColor =
- ContextCompat.getColor(this@OpenMoaIME, R.color.keyboard_background)
- when (resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
- Configuration.UI_MODE_NIGHT_YES -> {
- insetsController?.apply {
- setSystemBarsAppearance(0, APPEARANCE_LIGHT_NAVIGATION_BARS)
- }
- }
- Configuration.UI_MODE_NIGHT_NO, Configuration.UI_MODE_NIGHT_UNDEFINED -> {
- insetsController?.apply {
- setSystemBarsAppearance(
- APPEARANCE_LIGHT_NAVIGATION_BARS, APPEARANCE_LIGHT_NAVIGATION_BARS
- )
- }
- }
- }
- }
+ lastAppliedSkin = SettingsPreferences.getKeyboardSkin(this)
+ keyboardViews = buildKeyboardViews()
+ val view = layoutInflater.inflate(R.layout.open_moa_ime, null)
+ binding = OpenMoaImeBinding.bind(view)
+ applyKeyboardLayout()
+ setKeyboard(imeMode)
+ return view
+ }
+
+ private fun buildKeyboardViews(): MutableMap {
val punctuationView = PunctuationView(this)
val numberView = NumberView(this)
val arrowView = ArrowView(this)
val phoneView = PhoneView(this)
val emojiView = EmojiView(this)
- keyboardViews = mapOf(
+ return mutableMapOf(
IMEMode.IME_KO to OpenMoaView(this),
IMEMode.IME_EN to QuertyView(this),
IMEMode.IME_KO_PUNCTUATION to punctuationView,
@@ -427,15 +459,31 @@ class OpenMoaIME : InputMethodService(), KoinComponent {
IMEMode.IME_EN_PHONE to phoneView,
IMEMode.IME_EMOJI to emojiView,
)
- val view = layoutInflater.inflate(R.layout.open_moa_ime, null)
- binding = OpenMoaImeBinding.bind(view)
- setKeyboard(imeMode)
- return view
+ }
+
+ private fun refreshSkinIfNeeded() {
+ val currentSkin = SettingsPreferences.getKeyboardSkin(this)
+ if (currentSkin == lastAppliedSkin) return
+ lastAppliedSkin = currentSkin
+ keyboardViews = buildKeyboardViews()
+ }
+
+ private fun refreshOpenMoaViewIfNeeded() {
+ val savedIsMoakey = SettingsPreferences.getHangulInputMode(this) == HangulInputMode.MOAKEY
+ val currentView = keyboardViews[IMEMode.IME_KO] as? OpenMoaView ?: return
+ if (currentView.isMoakeyMode != savedIsMoakey) {
+ keyboardViews[IMEMode.IME_KO] = OpenMoaView(this)
+ }
}
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
super.onStartInputView(info, restarting)
finishComposing()
+ refreshSkinIfNeeded()
+ refreshOpenMoaViewIfNeeded()
+ (keyboardViews[IMEMode.IME_KO] as? OpenMoaView)?.refreshQuickPhraseBadges()
+ (keyboardViews[IMEMode.IME_KO] as? OpenMoaView)?.refreshUserCharLabels()
+ applyKeyboardLayout()
when ((info?.inputType ?: 0) and InputType.TYPE_MASK_CLASS) {
InputType.TYPE_CLASS_NUMBER -> {
setKeyboard(
@@ -581,6 +629,40 @@ class OpenMoaIME : InputMethodService(), KoinComponent {
.build()
}
+ private fun applyKeyboardLayout() {
+ val skin = SettingsPreferences.getKeyboardSkin(this)
+ val bgColor = SkinApplier.keyboardBgColor(this, skin)
+ binding.root.setBackgroundColor(bgColor)
+ binding.keyboardContainer.setBackgroundColor(bgColor)
+ window.window?.apply {
+ navigationBarColor = bgColor
+ val isLightBg = Color.luminance(bgColor) > 0.5f
+ insetsController?.setSystemBarsAppearance(
+ if (isLightBg) APPEARANCE_LIGHT_NAVIGATION_BARS else 0,
+ APPEARANCE_LIGHT_NAVIGATION_BARS,
+ )
+ }
+ val oneHandMode = SettingsPreferences.getOneHandMode(this)
+ val displayWidth = resources.displayMetrics.widthPixels
+ val keyboardWidth = if (oneHandMode.isReduced) {
+ (displayWidth * 0.80f).toInt()
+ } else {
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT
+ }
+ val params = android.widget.FrameLayout.LayoutParams(keyboardWidth, calculateKeyboardHeight()).apply {
+ gravity = oneHandMode.gravity
+ }
+ binding.keyboardFrameLayout.layoutParams = params
+ }
+
+ private fun calculateKeyboardHeight(): Int {
+ val displayHeight = resources.displayMetrics.heightPixels
+ val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ val baseScale = if (isLandscape) 0.50f else 0.35f
+ val heightScale = SettingsPreferences.getKeypadHeight(this).heightScale
+ return (displayHeight * baseScale * heightScale).toInt()
+ }
+
private fun getHeight(): Int {
return toDp(32)
}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/config/Config.kt b/app/src/main/kotlin/pe/aioo/openmoa/config/Config.kt
index e04ef11..a76dbda 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/config/Config.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/config/Config.kt
@@ -1,9 +1,17 @@
package pe.aioo.openmoa.config
-data class Config (
- val longPressRepeatTime: Long = 50L,
- val longPressThresholdTime: Long = 500L,
- val gestureThreshold: Float = 50f,
- val hapticFeedback: Boolean = true,
- val maxSuggestionCount: Int = 10,
-)
\ No newline at end of file
+import android.content.Context
+import pe.aioo.openmoa.settings.SettingsPreferences
+
+class Config(private val context: Context) {
+ val longPressRepeatTime: Long = 50L
+ val longPressThresholdTime: Long
+ get() = SettingsPreferences.getLongPressTime(context).millis
+ val gestureThreshold: Float = 50f
+ val hapticFeedback: Boolean = true
+ val maxSuggestionCount: Int = 10
+ val keyPreviewEnabled: Boolean
+ get() = SettingsPreferences.getKeyPreviewEnabled(context)
+ val autoCapitalizeEnglish: Boolean
+ get() = SettingsPreferences.getAutoCapitalizeEnglish(context)
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/config/EnterLongPressAction.kt b/app/src/main/kotlin/pe/aioo/openmoa/config/EnterLongPressAction.kt
new file mode 100644
index 0000000..3bf3aa4
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/config/EnterLongPressAction.kt
@@ -0,0 +1,15 @@
+package pe.aioo.openmoa.config
+
+import pe.aioo.openmoa.R
+
+enum class EnterLongPressAction(val labelResId: Int) {
+ ARROW(R.string.settings_enter_long_press_arrow),
+ IME_PICKER(R.string.settings_enter_long_press_ime_picker),
+ SWITCH_LANGUAGE(R.string.settings_enter_long_press_switch_language),
+ NONE(R.string.settings_enter_long_press_none);
+
+ companion object {
+ fun fromString(value: String?): EnterLongPressAction =
+ values().find { it.name == value } ?: ARROW
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/config/HangulInputMode.kt b/app/src/main/kotlin/pe/aioo/openmoa/config/HangulInputMode.kt
new file mode 100644
index 0000000..5982f5f
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/config/HangulInputMode.kt
@@ -0,0 +1,13 @@
+package pe.aioo.openmoa.config
+
+import pe.aioo.openmoa.R
+
+enum class HangulInputMode(val labelResId: Int) {
+ MOAKEY(R.string.settings_input_mode_moakey),
+ TWO_HAND_MOAKEY(R.string.settings_input_mode_two_hand_moakey);
+
+ companion object {
+ fun fromString(value: String?): HangulInputMode =
+ values().find { it.name == value } ?: TWO_HAND_MOAKEY
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/config/KeyboardSkin.kt b/app/src/main/kotlin/pe/aioo/openmoa/config/KeyboardSkin.kt
new file mode 100644
index 0000000..7767d03
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/config/KeyboardSkin.kt
@@ -0,0 +1,68 @@
+package pe.aioo.openmoa.config
+
+import androidx.annotation.ColorRes
+import androidx.annotation.StringRes
+import pe.aioo.openmoa.R
+
+enum class KeyboardSkin(
+ @StringRes val labelResId: Int,
+ @ColorRes val keyboardBgColorRes: Int,
+ @ColorRes val keyBgColorRes: Int,
+ @ColorRes val keyBgPressedColorRes: Int,
+ @ColorRes val keyFgColorRes: Int,
+ @ColorRes val keyFgMutedColorRes: Int,
+ @ColorRes val keyFgAccentColorRes: Int,
+) {
+ WHITE(
+ R.string.settings_keyboard_skin_white,
+ R.color.skin_white_keyboard_bg,
+ R.color.skin_white_key_bg,
+ R.color.skin_white_key_bg_pressed,
+ R.color.skin_white_key_fg,
+ R.color.skin_white_key_fg_muted,
+ R.color.skin_white_key_fg_accent,
+ ),
+ DARK_GRAY(
+ R.string.settings_keyboard_skin_dark_gray,
+ R.color.skin_dark_gray_keyboard_bg,
+ R.color.skin_dark_gray_key_bg,
+ R.color.skin_dark_gray_key_bg_pressed,
+ R.color.skin_dark_gray_key_fg,
+ R.color.skin_dark_gray_key_fg_muted,
+ R.color.skin_dark_gray_key_fg_accent,
+ ),
+ BLACK(
+ R.string.settings_keyboard_skin_black,
+ R.color.skin_black_keyboard_bg,
+ R.color.skin_black_key_bg,
+ R.color.skin_black_key_bg_pressed,
+ R.color.skin_black_key_fg,
+ R.color.skin_black_key_fg_muted,
+ R.color.skin_black_key_fg_accent,
+ ),
+ BLUE(
+ R.string.settings_keyboard_skin_blue,
+ R.color.skin_blue_keyboard_bg,
+ R.color.skin_blue_key_bg,
+ R.color.skin_blue_key_bg_pressed,
+ R.color.skin_blue_key_fg,
+ R.color.skin_blue_key_fg_muted,
+ R.color.skin_blue_key_fg_accent,
+ ),
+ GREEN(
+ R.string.settings_keyboard_skin_green,
+ R.color.skin_green_keyboard_bg,
+ R.color.skin_green_key_bg,
+ R.color.skin_green_key_bg_pressed,
+ R.color.skin_green_key_fg,
+ R.color.skin_green_key_fg_muted,
+ R.color.skin_green_key_fg_accent,
+ );
+
+ companion object {
+ val DEFAULT = WHITE
+
+ fun fromString(value: String?): KeyboardSkin =
+ values().firstOrNull { it.name == value } ?: DEFAULT
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/config/KeypadHeight.kt b/app/src/main/kotlin/pe/aioo/openmoa/config/KeypadHeight.kt
new file mode 100644
index 0000000..dda9efd
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/config/KeypadHeight.kt
@@ -0,0 +1,14 @@
+package pe.aioo.openmoa.config
+
+import pe.aioo.openmoa.R
+
+enum class KeypadHeight(val labelResId: Int, val heightScale: Float) {
+ NORMAL(R.string.settings_keypad_height_normal, 1.0f),
+ LOW(R.string.settings_keypad_height_low, 0.85f),
+ VERY_LOW(R.string.settings_keypad_height_very_low, 0.70f);
+
+ companion object {
+ fun fromString(value: String?): KeypadHeight =
+ values().find { it.name == value } ?: LOW
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/config/LongPressTime.kt b/app/src/main/kotlin/pe/aioo/openmoa/config/LongPressTime.kt
new file mode 100644
index 0000000..8ed3f5d
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/config/LongPressTime.kt
@@ -0,0 +1,16 @@
+package pe.aioo.openmoa.config
+
+import pe.aioo.openmoa.R
+
+enum class LongPressTime(val labelResId: Int, val millis: Long) {
+ MS_200(R.string.settings_long_press_time_200, 200L),
+ MS_300(R.string.settings_long_press_time_300, 300L),
+ MS_500(R.string.settings_long_press_time_500, 500L),
+ MS_1000(R.string.settings_long_press_time_1000, 1000L),
+ MS_1500(R.string.settings_long_press_time_1500, 1500L);
+
+ companion object {
+ fun fromString(value: String?): LongPressTime =
+ values().find { it.name == value } ?: MS_500
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/config/OneHandMode.kt b/app/src/main/kotlin/pe/aioo/openmoa/config/OneHandMode.kt
new file mode 100644
index 0000000..be3cbdd
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/config/OneHandMode.kt
@@ -0,0 +1,18 @@
+package pe.aioo.openmoa.config
+
+import android.view.Gravity
+import pe.aioo.openmoa.R
+
+enum class OneHandMode(val labelResId: Int, val gravity: Int) {
+ NONE(R.string.settings_one_hand_mode_none, Gravity.START),
+ LEFT(R.string.settings_one_hand_mode_left, Gravity.START),
+ RIGHT(R.string.settings_one_hand_mode_right, Gravity.END),
+ CENTER(R.string.settings_one_hand_mode_center, Gravity.CENTER_HORIZONTAL);
+
+ val isReduced: Boolean get() = this != NONE
+
+ companion object {
+ fun fromString(value: String?): OneHandMode =
+ values().find { it.name == value } ?: NONE
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/config/SpaceLongPressAction.kt b/app/src/main/kotlin/pe/aioo/openmoa/config/SpaceLongPressAction.kt
new file mode 100644
index 0000000..aafdc6c
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/config/SpaceLongPressAction.kt
@@ -0,0 +1,15 @@
+package pe.aioo.openmoa.config
+
+import pe.aioo.openmoa.R
+
+enum class SpaceLongPressAction(val labelResId: Int) {
+ ARROW(R.string.settings_space_long_press_arrow),
+ IME_PICKER(R.string.settings_space_long_press_ime_picker),
+ SWITCH_LANGUAGE(R.string.settings_space_long_press_switch_language),
+ NONE(R.string.settings_space_long_press_none);
+
+ companion object {
+ fun fromString(value: String?): SpaceLongPressAction =
+ values().find { it.name == value } ?: IME_PICKER
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/hangul/HangulPreviewComposer.kt b/app/src/main/kotlin/pe/aioo/openmoa/hangul/HangulPreviewComposer.kt
new file mode 100644
index 0000000..afeabb1
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/hangul/HangulPreviewComposer.kt
@@ -0,0 +1,17 @@
+package pe.aioo.openmoa.hangul
+
+import com.github.kimkevin.hangulparser.HangulParser
+import com.github.kimkevin.hangulparser.HangulParserException
+
+object HangulPreviewComposer {
+
+ fun compose(jaum: String, moeum: String?): String {
+ moeum ?: return jaum
+ return try {
+ HangulParser.assemble(listOf(jaum, moeum))
+ } catch (_: HangulParserException) {
+ jaum
+ }
+ }
+
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/module/configModule.kt b/app/src/main/kotlin/pe/aioo/openmoa/module/configModule.kt
index d1e9d68..3f1a7ac 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/module/configModule.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/module/configModule.kt
@@ -1,8 +1,9 @@
package pe.aioo.openmoa.module
+import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
import pe.aioo.openmoa.config.Config
val configModule = module {
- single { Config() }
-}
\ No newline at end of file
+ single { Config(androidContext()) }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/PhraseKey.kt b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/PhraseKey.kt
new file mode 100644
index 0000000..62df9bf
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/PhraseKey.kt
@@ -0,0 +1,24 @@
+package pe.aioo.openmoa.quickphrase
+
+import android.content.Context
+import pe.aioo.openmoa.settings.SettingsPreferences
+
+interface PhraseKey {
+ val name: String
+ val displayName: String
+ val defaultPhrase: String
+ val prefKey: String
+
+ fun getPhrase(context: Context): String {
+ val value = context.getSharedPreferences(SettingsPreferences.PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(prefKey, null)
+ return if (value.isNullOrEmpty()) defaultPhrase else value
+ }
+
+ fun setPhrase(context: Context, phrase: String) {
+ context.getSharedPreferences(SettingsPreferences.PREFS_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putString(prefKey, phrase)
+ .apply()
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QuickPhraseKey.kt b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QuickPhraseKey.kt
new file mode 100644
index 0000000..f781adf
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QuickPhraseKey.kt
@@ -0,0 +1,19 @@
+package pe.aioo.openmoa.quickphrase
+
+enum class QuickPhraseKey(
+ val jaum: String,
+ override val defaultPhrase: String,
+ override val prefKey: String,
+) : PhraseKey {
+ KIEUK("ㅋ", "안녕하세요", "quick_phrase_kieuk"),
+ TIEUT("ㅌ", "감사합니다", "quick_phrase_tieut"),
+ CHIEUT("ㅊ", "어디세요?", "quick_phrase_chieut"),
+ PIEUP("ㅍ", "연락바랍니다", "quick_phrase_pieup"),
+ SSANGBIEUP("ㅃ", "잠시만요", "quick_phrase_ssangbieup"),
+ SSANGJIEUT("ㅉ", "알겠습니다", "quick_phrase_ssangjieut"),
+ SSANGDIGEUT("ㄸ", "수고하세요", "quick_phrase_ssangdigeut"),
+ SSANGGIYEOK("ㄲ", "죄송합니다", "quick_phrase_ssanggiyeok"),
+ SSANGSIOT("ㅆ", "확인해드릴게요", "quick_phrase_ssangsiot");
+
+ override val displayName get() = jaum
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QuickPhraseRepository.kt b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QuickPhraseRepository.kt
new file mode 100644
index 0000000..1945982
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QuickPhraseRepository.kt
@@ -0,0 +1,25 @@
+package pe.aioo.openmoa.quickphrase
+
+import android.content.Context
+import pe.aioo.openmoa.settings.SettingsPreferences
+
+object QuickPhraseRepository {
+
+ fun getPhrase(context: Context, key: QuickPhraseKey): String {
+ val value = context.getSharedPreferences(SettingsPreferences.PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(key.prefKey, null)
+ return if (value.isNullOrEmpty()) key.defaultPhrase else value
+ }
+
+ fun setPhrase(context: Context, key: QuickPhraseKey, phrase: String) {
+ context.getSharedPreferences(SettingsPreferences.PREFS_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putString(key.prefKey, phrase)
+ .apply()
+ }
+
+ fun getFirstChar(context: Context, key: QuickPhraseKey): String {
+ val phrase = getPhrase(context, key)
+ return phrase.firstOrNull()?.toString() ?: ""
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QwertyLongKey.kt b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QwertyLongKey.kt
new file mode 100644
index 0000000..035b55b
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QwertyLongKey.kt
@@ -0,0 +1,17 @@
+package pe.aioo.openmoa.quickphrase
+
+enum class QwertyLongKey(
+ val letter: String,
+ override val defaultPhrase: String,
+ override val prefKey: String,
+) : PhraseKey {
+ Z("z", "/", "qwerty_long_key_z"),
+ X("x", "'", "qwerty_long_key_x"),
+ C("c", "\"", "qwerty_long_key_c"),
+ V("v", ".", "qwerty_long_key_v"),
+ B("b", ",", "qwerty_long_key_b"),
+ N("n", "?", "qwerty_long_key_n"),
+ M("m", "!", "qwerty_long_key_m");
+
+ override val displayName get() = letter
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QwertyLongKeyRepository.kt b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QwertyLongKeyRepository.kt
new file mode 100644
index 0000000..c391e5f
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/QwertyLongKeyRepository.kt
@@ -0,0 +1,20 @@
+package pe.aioo.openmoa.quickphrase
+
+import android.content.Context
+import pe.aioo.openmoa.settings.SettingsPreferences
+
+object QwertyLongKeyRepository {
+
+ fun getPhrase(context: Context, key: QwertyLongKey): String {
+ val value = context.getSharedPreferences(SettingsPreferences.PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(key.prefKey, null)
+ return if (value.isNullOrEmpty()) key.defaultPhrase else value
+ }
+
+ fun setPhrase(context: Context, key: QwertyLongKey, phrase: String) {
+ context.getSharedPreferences(SettingsPreferences.PREFS_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putString(key.prefKey, phrase)
+ .apply()
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/UserCharKey.kt b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/UserCharKey.kt
new file mode 100644
index 0000000..47c3bc4
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/quickphrase/UserCharKey.kt
@@ -0,0 +1,17 @@
+package pe.aioo.openmoa.quickphrase
+
+enum class UserCharKey(
+ val symbol: String,
+ override val defaultPhrase: String,
+ override val prefKey: String,
+) : PhraseKey {
+ TILDE("~", "~", "user_char_tilde"),
+ CARET("^", "^", "user_char_caret"),
+ SEMICOLON(";", ";", "user_char_semicolon"),
+ ASTERISK("*", "*", "user_char_asterisk"),
+ EXCLAMATION("!", "!", "user_char_exclamation"),
+ QUESTION("?", "?", "user_char_question"),
+ DOT(".", ".", "user_char_dot");
+
+ override val displayName get() = symbol
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/settings/PhraseEditActivity.kt b/app/src/main/kotlin/pe/aioo/openmoa/settings/PhraseEditActivity.kt
new file mode 100644
index 0000000..34cd865
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/settings/PhraseEditActivity.kt
@@ -0,0 +1,88 @@
+package pe.aioo.openmoa.settings
+
+import android.os.Bundle
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import androidx.appcompat.app.AppCompatActivity
+import pe.aioo.openmoa.R
+import pe.aioo.openmoa.databinding.ActivityPhraseEditBinding
+import pe.aioo.openmoa.quickphrase.PhraseKey
+import pe.aioo.openmoa.quickphrase.QuickPhraseKey
+import pe.aioo.openmoa.quickphrase.QwertyLongKey
+import pe.aioo.openmoa.quickphrase.UserCharKey
+
+class PhraseEditActivity : AppCompatActivity() {
+
+ companion object {
+ const val EXTRA_TYPE = "extra_type"
+ const val EXTRA_KEY = "extra_key"
+ const val TYPE_KOREAN = "KOREAN"
+ const val TYPE_ENGLISH = "ENGLISH"
+ const val TYPE_USER_CHAR = "USER_CHAR"
+ }
+
+ private lateinit var binding: ActivityPhraseEditBinding
+ private lateinit var keys: Array
+ private lateinit var type: String
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityPhraseEditBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ type = intent.getStringExtra(EXTRA_TYPE) ?: TYPE_KOREAN
+ val initialKeyName = intent.getStringExtra(EXTRA_KEY)
+
+ keys = when (type) {
+ TYPE_ENGLISH -> QwertyLongKey.values()
+ TYPE_USER_CHAR -> UserCharKey.values()
+ else -> QuickPhraseKey.values()
+ }
+
+ binding.editTitle.text = when (type) {
+ TYPE_USER_CHAR -> getString(R.string.user_char_edit_title)
+ else -> getString(R.string.quick_phrase_edit_title)
+ }
+
+ setupSpinner(initialKeyName)
+ setupButtons()
+ }
+
+ private fun setupSpinner(initialKeyName: String?) {
+ val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, keys.map { it.displayName })
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ binding.keySpinner.adapter = adapter
+
+ val initialIndex = keys.indexOfFirst { it.name == initialKeyName }.takeIf { it >= 0 } ?: 0
+ binding.keySpinner.setSelection(initialIndex)
+
+ binding.keySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
+ binding.contentEditText.setText(keys[position].getPhrase(this@PhraseEditActivity))
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>) {}
+ }
+ }
+
+ private fun setupButtons() {
+ binding.confirmButton.setOnClickListener {
+ val key = keys[binding.keySpinner.selectedItemPosition]
+ val raw = binding.contentEditText.text.toString().trim()
+ val content = if (type == TYPE_USER_CHAR && raw.isNotEmpty()) {
+ raw.substring(0, raw.offsetByCodePoints(0, 1))
+ } else {
+ raw
+ }
+ key.setPhrase(this, content.ifEmpty { key.defaultPhrase })
+ finish()
+ }
+ binding.cancelButton.setOnClickListener { finish() }
+ binding.resetButton.setOnClickListener {
+ val key = keys[binding.keySpinner.selectedItemPosition]
+ key.setPhrase(this, key.defaultPhrase)
+ binding.contentEditText.setText(key.defaultPhrase)
+ }
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/settings/SettingsActivity.kt b/app/src/main/kotlin/pe/aioo/openmoa/settings/SettingsActivity.kt
new file mode 100644
index 0000000..cc12867
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/settings/SettingsActivity.kt
@@ -0,0 +1,410 @@
+package pe.aioo.openmoa.settings
+
+import android.os.Bundle
+import android.widget.EditText
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import org.json.JSONObject
+import pe.aioo.openmoa.R
+import pe.aioo.openmoa.config.EnterLongPressAction
+import pe.aioo.openmoa.config.HangulInputMode
+import pe.aioo.openmoa.config.KeyboardSkin
+import pe.aioo.openmoa.config.KeypadHeight
+import pe.aioo.openmoa.config.LongPressTime
+import pe.aioo.openmoa.config.OneHandMode
+import pe.aioo.openmoa.config.SpaceLongPressAction
+import pe.aioo.openmoa.databinding.ActivitySettingsBinding
+import pe.aioo.openmoa.quickphrase.QuickPhraseKey
+import pe.aioo.openmoa.quickphrase.QuickPhraseRepository
+import pe.aioo.openmoa.quickphrase.QwertyLongKey
+import pe.aioo.openmoa.quickphrase.QwertyLongKeyRepository
+import java.io.File
+
+class SettingsActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivitySettingsBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivitySettingsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ setupViews()
+ }
+
+ private fun setupViews() {
+ refreshAllDisplays()
+ binding.hangulInputModeItem.setOnClickListener { showInputModeDialog() }
+ binding.keyboardSkinItem.setOnClickListener { showKeyboardSkinDialog() }
+ binding.keypadHeightItem.setOnClickListener { showKeypadHeightDialog() }
+ binding.oneHandModeItem.setOnClickListener { showOneHandModeDialog() }
+ binding.longPressTimeItem.setOnClickListener { showLongPressTimeDialog() }
+ binding.spaceLongPressActionItem.setOnClickListener { showSpaceLongPressActionDialog() }
+ binding.keyPreviewItem.setOnClickListener { toggleKeyPreview() }
+ binding.autoSpacePeriodItem.setOnClickListener { toggleAutoSpacePeriod() }
+ binding.autoCapitalizeEnglishItem.setOnClickListener { toggleAutoCapitalizeEnglish() }
+ binding.enterLongPressActionItem.setOnClickListener { showEnterLongPressActionDialog() }
+ binding.quickPhraseKieukItem.setOnClickListener { showQuickPhraseEditDialog(QuickPhraseKey.KIEUK) }
+ binding.quickPhraseTieutItem.setOnClickListener { showQuickPhraseEditDialog(QuickPhraseKey.TIEUT) }
+ binding.quickPhraseChieutItem.setOnClickListener { showQuickPhraseEditDialog(QuickPhraseKey.CHIEUT) }
+ binding.quickPhrasePieupItem.setOnClickListener { showQuickPhraseEditDialog(QuickPhraseKey.PIEUP) }
+ binding.quickPhraseSsangbieupItem.setOnClickListener { showQuickPhraseEditDialog(QuickPhraseKey.SSANGBIEUP) }
+ binding.quickPhraseSsangjieutItem.setOnClickListener { showQuickPhraseEditDialog(QuickPhraseKey.SSANGJIEUT) }
+ binding.quickPhraseSsangdigeutItem.setOnClickListener { showQuickPhraseEditDialog(QuickPhraseKey.SSANGDIGEUT) }
+ binding.quickPhraseSsanggiyeokItem.setOnClickListener { showQuickPhraseEditDialog(QuickPhraseKey.SSANGGIYEOK) }
+ binding.quickPhraseSsangsiotItem.setOnClickListener { showQuickPhraseEditDialog(QuickPhraseKey.SSANGSIOT) }
+ binding.qwertyLongKeyZItem.setOnClickListener { showQwertyLongKeyEditDialog(QwertyLongKey.Z) }
+ binding.qwertyLongKeyXItem.setOnClickListener { showQwertyLongKeyEditDialog(QwertyLongKey.X) }
+ binding.qwertyLongKeyCItem.setOnClickListener { showQwertyLongKeyEditDialog(QwertyLongKey.C) }
+ binding.qwertyLongKeyVItem.setOnClickListener { showQwertyLongKeyEditDialog(QwertyLongKey.V) }
+ binding.qwertyLongKeyBItem.setOnClickListener { showQwertyLongKeyEditDialog(QwertyLongKey.B) }
+ binding.qwertyLongKeyNItem.setOnClickListener { showQwertyLongKeyEditDialog(QwertyLongKey.N) }
+ binding.qwertyLongKeyMItem.setOnClickListener { showQwertyLongKeyEditDialog(QwertyLongKey.M) }
+ binding.settingsDataExportItem.setOnClickListener { exportSettings() }
+ binding.settingsDataImportItem.setOnClickListener { importSettings() }
+ binding.settingsDataResetItem.setOnClickListener { showResetConfirmDialog() }
+ }
+
+ private fun updateQuickPhraseDisplays() {
+ binding.quickPhraseKieukValue.text = QuickPhraseRepository.getPhrase(this, QuickPhraseKey.KIEUK)
+ binding.quickPhraseTieutValue.text = QuickPhraseRepository.getPhrase(this, QuickPhraseKey.TIEUT)
+ binding.quickPhraseChieutValue.text = QuickPhraseRepository.getPhrase(this, QuickPhraseKey.CHIEUT)
+ binding.quickPhrasePieupValue.text = QuickPhraseRepository.getPhrase(this, QuickPhraseKey.PIEUP)
+ binding.quickPhraseSsangbieupValue.text = QuickPhraseRepository.getPhrase(this, QuickPhraseKey.SSANGBIEUP)
+ binding.quickPhraseSsangjieutValue.text = QuickPhraseRepository.getPhrase(this, QuickPhraseKey.SSANGJIEUT)
+ binding.quickPhraseSsangdigeutValue.text = QuickPhraseRepository.getPhrase(this, QuickPhraseKey.SSANGDIGEUT)
+ binding.quickPhraseSsanggiyeokValue.text = QuickPhraseRepository.getPhrase(this, QuickPhraseKey.SSANGGIYEOK)
+ binding.quickPhraseSsangsiotValue.text = QuickPhraseRepository.getPhrase(this, QuickPhraseKey.SSANGSIOT)
+ }
+
+ private fun showQuickPhraseEditDialog(key: QuickPhraseKey) {
+ val editText = EditText(this).apply {
+ setText(QuickPhraseRepository.getPhrase(this@SettingsActivity, key))
+ hint = getString(R.string.settings_quick_phrase_edit_hint)
+ setSingleLine(true)
+ }
+ AlertDialog.Builder(this)
+ .setTitle("${key.jaum} ${getString(R.string.settings_quick_phrase_edit_hint)}")
+ .setView(editText)
+ .setPositiveButton(R.string.settings_qwerty_long_key_save) { _, _ ->
+ val input = editText.text.toString().trim()
+ if (input.isNotEmpty()) {
+ QuickPhraseRepository.setPhrase(this, key, input)
+ } else {
+ QuickPhraseRepository.setPhrase(this, key, key.defaultPhrase)
+ }
+ updateQuickPhraseDisplays()
+ }
+ .setNeutralButton(R.string.settings_quick_phrase_reset) { _, _ ->
+ QuickPhraseRepository.setPhrase(this, key, key.defaultPhrase)
+ updateQuickPhraseDisplays()
+ }
+ .setNegativeButton(R.string.settings_qwerty_long_key_cancel, null)
+ .show()
+ }
+
+ private fun updateQwertyLongKeyDisplays() {
+ binding.qwertyLongKeyZValue.text = QwertyLongKeyRepository.getPhrase(this, QwertyLongKey.Z)
+ binding.qwertyLongKeyXValue.text = QwertyLongKeyRepository.getPhrase(this, QwertyLongKey.X)
+ binding.qwertyLongKeyCValue.text = QwertyLongKeyRepository.getPhrase(this, QwertyLongKey.C)
+ binding.qwertyLongKeyVValue.text = QwertyLongKeyRepository.getPhrase(this, QwertyLongKey.V)
+ binding.qwertyLongKeyBValue.text = QwertyLongKeyRepository.getPhrase(this, QwertyLongKey.B)
+ binding.qwertyLongKeyNValue.text = QwertyLongKeyRepository.getPhrase(this, QwertyLongKey.N)
+ binding.qwertyLongKeyMValue.text = QwertyLongKeyRepository.getPhrase(this, QwertyLongKey.M)
+ }
+
+ private fun showQwertyLongKeyEditDialog(key: QwertyLongKey) {
+ val editText = EditText(this).apply {
+ setText(QwertyLongKeyRepository.getPhrase(this@SettingsActivity, key))
+ hint = getString(R.string.settings_qwerty_long_key_edit_hint)
+ setSingleLine(true)
+ }
+ AlertDialog.Builder(this)
+ .setTitle("${key.letter} ${getString(R.string.settings_qwerty_long_key_edit_hint)}")
+ .setView(editText)
+ .setPositiveButton(R.string.settings_qwerty_long_key_save) { _, _ ->
+ val input = editText.text.toString().trim()
+ if (input.isNotEmpty()) {
+ QwertyLongKeyRepository.setPhrase(this, key, input)
+ } else {
+ QwertyLongKeyRepository.setPhrase(this, key, key.defaultPhrase)
+ }
+ updateQwertyLongKeyDisplays()
+ }
+ .setNeutralButton(R.string.settings_qwerty_long_key_reset) { _, _ ->
+ QwertyLongKeyRepository.setPhrase(this, key, key.defaultPhrase)
+ updateQwertyLongKeyDisplays()
+ }
+ .setNegativeButton(R.string.settings_qwerty_long_key_cancel, null)
+ .show()
+ }
+
+ private fun updateKeyboardSkinDisplay() {
+ binding.keyboardSkinValue.text =
+ getString(SettingsPreferences.getKeyboardSkin(this).labelResId)
+ }
+
+ private fun showKeyboardSkinDialog() {
+ val options = KeyboardSkin.values()
+ val labels = options.map { getString(it.labelResId) }.toTypedArray()
+ val currentIndex = options.indexOf(SettingsPreferences.getKeyboardSkin(this))
+
+ AlertDialog.Builder(this)
+ .setTitle(R.string.settings_keyboard_skin_title)
+ .setSingleChoiceItems(labels, currentIndex) { dialog, which ->
+ SettingsPreferences.save(this, SettingsPreferences.KEY_KEYBOARD_SKIN, options[which].name)
+ updateKeyboardSkinDisplay()
+ dialog.dismiss()
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ private fun updateInputModeDisplay() {
+ binding.hangulInputModeValue.text =
+ getString(SettingsPreferences.getHangulInputMode(this).labelResId)
+ }
+
+ private fun updateKeypadHeightDisplay() {
+ binding.keypadHeightValue.text =
+ getString(SettingsPreferences.getKeypadHeight(this).labelResId)
+ }
+
+ private fun updateOneHandModeDisplay() {
+ binding.oneHandModeValue.text =
+ getString(SettingsPreferences.getOneHandMode(this).labelResId)
+ }
+
+ private fun updateLongPressTimeDisplay() {
+ binding.longPressTimeValue.text =
+ getString(SettingsPreferences.getLongPressTime(this).labelResId)
+ }
+
+ private fun showLongPressTimeDialog() {
+ val options = LongPressTime.values()
+ val labels = options.map { getString(it.labelResId) }.toTypedArray()
+ val currentIndex = options.indexOf(SettingsPreferences.getLongPressTime(this))
+
+ AlertDialog.Builder(this)
+ .setTitle(R.string.settings_long_press_time_title)
+ .setSingleChoiceItems(labels, currentIndex) { dialog, which ->
+ SettingsPreferences.save(this, SettingsPreferences.KEY_LONG_PRESS_TIME, options[which].name)
+ updateLongPressTimeDisplay()
+ dialog.dismiss()
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ private fun updateKeyPreviewDisplay() {
+ binding.keyPreviewSwitch.isChecked = SettingsPreferences.getKeyPreviewEnabled(this)
+ }
+
+ private fun toggleKeyPreview() {
+ val newValue = !SettingsPreferences.getKeyPreviewEnabled(this)
+ SettingsPreferences.setKeyPreviewEnabled(this, newValue)
+ binding.keyPreviewSwitch.isChecked = newValue
+ }
+
+ private fun updateSpaceLongPressActionDisplay() {
+ binding.spaceLongPressActionValue.text =
+ getString(SettingsPreferences.getSpaceLongPressAction(this).labelResId)
+ }
+
+ private fun showSpaceLongPressActionDialog() {
+ val options = SpaceLongPressAction.values()
+ val labels = options.map { getString(it.labelResId) }.toTypedArray()
+ val currentIndex = options.indexOf(SettingsPreferences.getSpaceLongPressAction(this))
+
+ AlertDialog.Builder(this)
+ .setTitle(R.string.settings_space_long_press_title)
+ .setSingleChoiceItems(labels, currentIndex) { dialog, which ->
+ SettingsPreferences.save(this, SettingsPreferences.KEY_SPACE_LONG_PRESS_ACTION, options[which].name)
+ updateSpaceLongPressActionDisplay()
+ dialog.dismiss()
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ private fun updateAutoSpacePeriodDisplay() {
+ binding.autoSpacePeriodSwitch.isChecked = SettingsPreferences.getAutoSpacePeriod(this)
+ }
+
+ private fun toggleAutoSpacePeriod() {
+ val newValue = !SettingsPreferences.getAutoSpacePeriod(this)
+ SettingsPreferences.setAutoSpacePeriod(this, newValue)
+ binding.autoSpacePeriodSwitch.isChecked = newValue
+ }
+
+ private fun updateAutoCapitalizeEnglishDisplay() {
+ binding.autoCapitalizeEnglishSwitch.isChecked = SettingsPreferences.getAutoCapitalizeEnglish(this)
+ }
+
+ private fun toggleAutoCapitalizeEnglish() {
+ val newValue = !SettingsPreferences.getAutoCapitalizeEnglish(this)
+ SettingsPreferences.setAutoCapitalizeEnglish(this, newValue)
+ binding.autoCapitalizeEnglishSwitch.isChecked = newValue
+ }
+
+ private fun updateEnterLongPressActionDisplay() {
+ binding.enterLongPressActionValue.text =
+ getString(SettingsPreferences.getEnterLongPressAction(this).labelResId)
+ }
+
+ private fun showEnterLongPressActionDialog() {
+ val options = EnterLongPressAction.values()
+ val labels = options.map { getString(it.labelResId) }.toTypedArray()
+ val currentIndex = options.indexOf(SettingsPreferences.getEnterLongPressAction(this))
+
+ AlertDialog.Builder(this)
+ .setTitle(R.string.settings_enter_long_press_title)
+ .setSingleChoiceItems(labels, currentIndex) { dialog, which ->
+ SettingsPreferences.save(this, SettingsPreferences.KEY_ENTER_LONG_PRESS_ACTION, options[which].name)
+ updateEnterLongPressActionDisplay()
+ dialog.dismiss()
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ private fun showInputModeDialog() {
+ val modes = HangulInputMode.values()
+ val labels = modes.map { getString(it.labelResId) }.toTypedArray()
+ val currentIndex = modes.indexOf(SettingsPreferences.getHangulInputMode(this))
+
+ AlertDialog.Builder(this)
+ .setTitle(R.string.settings_hangul_input_mode_title)
+ .setSingleChoiceItems(labels, currentIndex) { dialog, which ->
+ SettingsPreferences.save(this, SettingsPreferences.KEY_HANGUL_INPUT_MODE, modes[which].name)
+ updateInputModeDisplay()
+ dialog.dismiss()
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ private fun showKeypadHeightDialog() {
+ val options = KeypadHeight.values()
+ val labels = options.map { getString(it.labelResId) }.toTypedArray()
+ val currentIndex = options.indexOf(SettingsPreferences.getKeypadHeight(this))
+
+ AlertDialog.Builder(this)
+ .setTitle(R.string.settings_keypad_height_title)
+ .setSingleChoiceItems(labels, currentIndex) { dialog, which ->
+ SettingsPreferences.save(this, SettingsPreferences.KEY_KEYPAD_HEIGHT, options[which].name)
+ updateKeypadHeightDisplay()
+ dialog.dismiss()
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ private fun showOneHandModeDialog() {
+ val options = OneHandMode.values()
+ val labels = options.map { getString(it.labelResId) }.toTypedArray()
+ val currentIndex = options.indexOf(SettingsPreferences.getOneHandMode(this))
+
+ AlertDialog.Builder(this)
+ .setTitle(R.string.settings_one_hand_mode_title)
+ .setSingleChoiceItems(labels, currentIndex) { dialog, which ->
+ SettingsPreferences.save(this, SettingsPreferences.KEY_ONE_HAND_MODE, options[which].name)
+ updateOneHandModeDisplay()
+ dialog.dismiss()
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ private fun getSettingsFile(): File {
+ val dir = getExternalFilesDir(null) ?: filesDir
+ return File(dir, "openmoa_settings.json")
+ }
+
+ private fun exportSettings() {
+ try {
+ val prefs = getSharedPreferences(SettingsPreferences.PREFS_NAME, MODE_PRIVATE)
+ val json = JSONObject()
+ // prefs 파일 하나에 SettingsPreferences, QuickPhraseRepository, QwertyLongKeyRepository 가 함께 저장됨
+ prefs.all.forEach { (key, value) ->
+ when (value) {
+ is Boolean -> json.put(key, value)
+ is String -> json.put(key, value)
+ is Int -> json.put(key, value)
+ is Long -> json.put(key, value)
+ is Float -> json.put(key, value.toDouble())
+ // Set 등 미지원 타입은 내보내지 않음
+ }
+ }
+ getSettingsFile().writeText(json.toString(2))
+ Toast.makeText(this, R.string.settings_data_export_success, Toast.LENGTH_SHORT).show()
+ } catch (e: Exception) {
+ Toast.makeText(this, R.string.settings_data_export_fail, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ private fun importSettings() {
+ try {
+ val file = getSettingsFile()
+ if (!file.exists()) {
+ Toast.makeText(this, R.string.settings_data_import_fail, Toast.LENGTH_SHORT).show()
+ return
+ }
+ val json = JSONObject(file.readText())
+ val allowedKeys = buildSet {
+ addAll(SettingsPreferences.ALL_KEYS)
+ QuickPhraseKey.values().forEach { add(it.prefKey) }
+ QwertyLongKey.values().forEach { add(it.prefKey) }
+ }
+ val booleanKeys = setOf(
+ SettingsPreferences.KEY_KEY_PREVIEW,
+ SettingsPreferences.KEY_AUTO_SPACE_PERIOD,
+ SettingsPreferences.KEY_AUTO_CAPITALIZE_ENGLISH,
+ )
+ val editor = getSharedPreferences(SettingsPreferences.PREFS_NAME, MODE_PRIVATE).edit()
+ json.keys().forEach { key ->
+ if (key !in allowedKeys) return@forEach
+ if (key in booleanKeys) {
+ editor.putBoolean(key, json.optBoolean(key))
+ } else if (json.has(key)) {
+ val value = json.optString(key)
+ if (value.isNotEmpty()) editor.putString(key, value)
+ }
+ }
+ editor.apply()
+ refreshAllDisplays()
+ Toast.makeText(this, R.string.settings_data_import_success, Toast.LENGTH_SHORT).show()
+ } catch (e: Exception) {
+ Toast.makeText(this, R.string.settings_data_import_fail, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ private fun showResetConfirmDialog() {
+ AlertDialog.Builder(this)
+ .setMessage(R.string.settings_data_reset_confirm)
+ .setPositiveButton(android.R.string.ok) { _, _ -> resetAllSettings() }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ private fun resetAllSettings() {
+ getSharedPreferences(SettingsPreferences.PREFS_NAME, MODE_PRIVATE).edit().clear().apply()
+ refreshAllDisplays()
+ Toast.makeText(this, R.string.settings_data_reset_success, Toast.LENGTH_SHORT).show()
+ }
+
+ private fun refreshAllDisplays() {
+ updateInputModeDisplay()
+ updateKeyboardSkinDisplay()
+ updateKeypadHeightDisplay()
+ updateOneHandModeDisplay()
+ updateLongPressTimeDisplay()
+ updateSpaceLongPressActionDisplay()
+ updateKeyPreviewDisplay()
+ updateAutoSpacePeriodDisplay()
+ updateAutoCapitalizeEnglishDisplay()
+ updateEnterLongPressActionDisplay()
+ updateQuickPhraseDisplays()
+ updateQwertyLongKeyDisplays()
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/settings/SettingsPreferences.kt b/app/src/main/kotlin/pe/aioo/openmoa/settings/SettingsPreferences.kt
new file mode 100644
index 0000000..449c956
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/settings/SettingsPreferences.kt
@@ -0,0 +1,124 @@
+package pe.aioo.openmoa.settings
+
+import android.content.Context
+import pe.aioo.openmoa.config.EnterLongPressAction
+import pe.aioo.openmoa.config.HangulInputMode
+import pe.aioo.openmoa.config.KeyboardSkin
+import pe.aioo.openmoa.config.KeypadHeight
+import pe.aioo.openmoa.config.LongPressTime
+import pe.aioo.openmoa.config.OneHandMode
+import pe.aioo.openmoa.config.SpaceLongPressAction
+import pe.aioo.openmoa.quickphrase.UserCharKey
+
+object SettingsPreferences {
+
+ const val PREFS_NAME = "openmoa_settings"
+ const val KEY_HANGUL_INPUT_MODE = "hangul_input_mode"
+ const val KEY_KEYPAD_HEIGHT = "keypad_height"
+ const val KEY_ONE_HAND_MODE = "one_hand_mode"
+ const val KEY_KEY_PREVIEW = "key_preview_enabled"
+ const val KEY_LONG_PRESS_TIME = "long_press_time"
+ const val KEY_SPACE_LONG_PRESS_ACTION = "space_long_press_action"
+ const val KEY_AUTO_SPACE_PERIOD = "auto_space_period"
+ const val KEY_KEYBOARD_SKIN = "keyboard_skin"
+ const val KEY_AUTO_CAPITALIZE_ENGLISH = "auto_capitalize_english"
+ const val KEY_ENTER_LONG_PRESS_ACTION = "enter_long_press_action"
+
+ val ALL_KEYS = setOf(
+ KEY_HANGUL_INPUT_MODE,
+ KEY_KEYPAD_HEIGHT,
+ KEY_ONE_HAND_MODE,
+ KEY_KEY_PREVIEW,
+ KEY_LONG_PRESS_TIME,
+ KEY_SPACE_LONG_PRESS_ACTION,
+ KEY_AUTO_SPACE_PERIOD,
+ KEY_KEYBOARD_SKIN,
+ KEY_AUTO_CAPITALIZE_ENGLISH,
+ KEY_ENTER_LONG_PRESS_ACTION,
+ ) + UserCharKey.values().map { it.prefKey }.toSet()
+
+ fun getKeyPreviewEnabled(context: Context): Boolean {
+ return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getBoolean(KEY_KEY_PREVIEW, true)
+ }
+
+ fun setKeyPreviewEnabled(context: Context, enabled: Boolean) {
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(KEY_KEY_PREVIEW, enabled)
+ .apply()
+ }
+
+ fun getHangulInputMode(context: Context): HangulInputMode {
+ val value = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(KEY_HANGUL_INPUT_MODE, null)
+ return HangulInputMode.fromString(value)
+ }
+
+ fun getKeypadHeight(context: Context): KeypadHeight {
+ val value = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(KEY_KEYPAD_HEIGHT, null)
+ return KeypadHeight.fromString(value)
+ }
+
+ fun getLongPressTime(context: Context): LongPressTime {
+ val value = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(KEY_LONG_PRESS_TIME, null)
+ return LongPressTime.fromString(value)
+ }
+
+ fun getOneHandMode(context: Context): OneHandMode {
+ val value = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(KEY_ONE_HAND_MODE, null)
+ return OneHandMode.fromString(value)
+ }
+
+ fun getSpaceLongPressAction(context: Context): SpaceLongPressAction {
+ val value = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(KEY_SPACE_LONG_PRESS_ACTION, null)
+ return SpaceLongPressAction.fromString(value)
+ }
+
+ fun getKeyboardSkin(context: Context): KeyboardSkin {
+ val value = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(KEY_KEYBOARD_SKIN, null)
+ return KeyboardSkin.fromString(value)
+ }
+
+ fun getAutoSpacePeriod(context: Context): Boolean {
+ return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getBoolean(KEY_AUTO_SPACE_PERIOD, false)
+ }
+
+ fun setAutoSpacePeriod(context: Context, enabled: Boolean) {
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(KEY_AUTO_SPACE_PERIOD, enabled)
+ .apply()
+ }
+
+ fun getAutoCapitalizeEnglish(context: Context): Boolean {
+ return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getBoolean(KEY_AUTO_CAPITALIZE_ENGLISH, true)
+ }
+
+ fun setAutoCapitalizeEnglish(context: Context, enabled: Boolean) {
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(KEY_AUTO_CAPITALIZE_ENGLISH, enabled)
+ .apply()
+ }
+
+ fun getEnterLongPressAction(context: Context): EnterLongPressAction {
+ val value = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(KEY_ENTER_LONG_PRESS_ACTION, null)
+ return EnterLongPressAction.fromString(value)
+ }
+
+ fun save(context: Context, key: String, value: String) {
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putString(key, value)
+ .apply()
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/KeyboardFrameLayout.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/KeyboardFrameLayout.kt
index c7f940f..6c475b2 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/KeyboardFrameLayout.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/KeyboardFrameLayout.kt
@@ -38,7 +38,7 @@ class KeyboardFrameLayout : FrameLayout {
}
}
binding.keyboardLayout.removeAllViews()
- binding.keyboardLayout.addView(view)
+ binding.keyboardLayout.addView(view, FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/ArrowView.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/ArrowView.kt
index 1254d2a..5a5e89e 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/ArrowView.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/ArrowView.kt
@@ -4,15 +4,20 @@ import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.content.ContextCompat
import pe.aioo.openmoa.R
+import pe.aioo.openmoa.config.KeyboardSkin
import pe.aioo.openmoa.databinding.ArrowViewBinding
+import pe.aioo.openmoa.view.keytouchlistener.EnterKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.FunctionalKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.LanguageKeyTouchListener
import pe.aioo.openmoa.view.message.SpecialKey
import pe.aioo.openmoa.view.keytouchlistener.RepeatKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.SimpleKeyTouchListener
+import pe.aioo.openmoa.settings.SettingsPreferences
+import pe.aioo.openmoa.view.keytouchlistener.SpaceKeyTouchListener
import pe.aioo.openmoa.view.message.SpecialKeyMessage
import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.skin.SkinApplier
class ArrowView : ConstraintLayout {
@@ -32,26 +37,42 @@ class ArrowView : ConstraintLayout {
private lateinit var binding: ArrowViewBinding
private var isSelecting = false
+ private var currentSkin: KeyboardSkin = KeyboardSkin.DEFAULT
+ private var enterKeyListener: EnterKeyTouchListener? = null
+ private var languageKeyListener: LanguageKeyTouchListener? = null
private fun init() {
inflate(context, R.layout.arrow_view, this)
binding = ArrowViewBinding.bind(this)
setOnTouchListeners()
+ currentSkin = SettingsPreferences.getKeyboardSkin(context)
+ SkinApplier.apply(this, currentSkin)
+ }
+
+ fun refreshOneHandMode() {
+ val isReduced = SettingsPreferences.getOneHandMode(context).isReduced
+ binding.copyAllKey.setText(
+ if (isReduced) R.string.key_copy_all_two_line else R.string.key_copy_all
+ )
+ binding.selectAllKey.setText(
+ if (isReduced) R.string.key_select_all_two_line else R.string.key_select_all
+ )
+ binding.cutAllKey.setText(
+ if (isReduced) R.string.key_cut_all_two_line else R.string.key_cut_all
+ )
}
fun setSelectingOrToggleSelecting(selecting: Boolean? = null) {
isSelecting = selecting ?: !isSelecting
+ val color = if (isSelecting) {
+ SkinApplier.fgAccentColor(context, currentSkin)
+ } else {
+ SkinApplier.fgColor(context, currentSkin)
+ }
listOf(
binding.areaSelectKey, binding.homeKey, binding.endKey,
binding.upKey, binding.downKey, binding.leftKey, binding.rightKey,
- ).map {
- it.setTextColor(
- ContextCompat.getColor(
- context,
- if (isSelecting) R.color.key_foreground_locked else R.color.key_foreground
- )
- )
- }
+ ).forEach { it.setTextColor(color) }
}
@SuppressLint("ClickableViewAccessibility")
@@ -129,19 +150,25 @@ class ArrowView : ConstraintLayout {
backspaceKey.setOnTouchListener(
RepeatKeyTouchListener(context, SpecialKeyMessage(SpecialKey.BACKSPACE))
)
- languageKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.LANGUAGE))
- )
+ languageKeyListener?.cancel()
+ languageKeyListener = LanguageKeyTouchListener(context)
+ languageKey.setOnTouchListener(languageKeyListener)
hanjaNumberPunctuationKey.setOnTouchListener(
SimpleKeyTouchListener(
context, SpecialKeyMessage(SpecialKey.HANJA_NUMBER_PUNCTUATION)
)
)
- spaceKey.setOnTouchListener(SimpleKeyTouchListener(context, StringKeyMessage(" ")))
- enterKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.ENTER))
- )
+ spaceKey.setOnTouchListener(SpaceKeyTouchListener(context))
+ enterKeyListener?.cancel()
+ enterKeyListener = EnterKeyTouchListener(context)
+ enterKey.setOnTouchListener(enterKeyListener)
}
}
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ enterKeyListener?.cancel()
+ languageKeyListener?.cancel()
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/EmojiView.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/EmojiView.kt
index d75f74b..8469f2f 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/EmojiView.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/EmojiView.kt
@@ -9,6 +9,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import pe.aioo.openmoa.OpenMoaIME
import pe.aioo.openmoa.R
import pe.aioo.openmoa.databinding.EmojiViewBinding
+import pe.aioo.openmoa.view.keytouchlistener.EnterKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.RepeatKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.SimpleKeyTouchListener
import pe.aioo.openmoa.view.message.SpecialKey
@@ -31,6 +32,7 @@ class EmojiView : ConstraintLayout {
}
private lateinit var binding: EmojiViewBinding
+ private var enterKeyListener: EnterKeyTouchListener? = null
@SuppressLint("ClickableViewAccessibility")
private fun init() {
@@ -52,9 +54,13 @@ class EmojiView : ConstraintLayout {
binding.backspaceKey.setOnTouchListener(
RepeatKeyTouchListener(context, SpecialKeyMessage(SpecialKey.BACKSPACE))
)
- binding.enterKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.ENTER))
- )
+ enterKeyListener = EnterKeyTouchListener(context)
+ binding.enterKey.setOnTouchListener(enterKeyListener)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ enterKeyListener?.cancel()
}
}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/NumberView.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/NumberView.kt
index 0176470..3d92661 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/NumberView.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/NumberView.kt
@@ -7,10 +7,15 @@ import androidx.constraintlayout.widget.ConstraintLayout
import pe.aioo.openmoa.R
import pe.aioo.openmoa.databinding.NumberViewBinding
import pe.aioo.openmoa.view.message.SpecialKey
+import pe.aioo.openmoa.view.keytouchlistener.EnterKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.LanguageKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.RepeatKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.SimpleKeyTouchListener
+import pe.aioo.openmoa.settings.SettingsPreferences
+import pe.aioo.openmoa.view.keytouchlistener.SpaceKeyTouchListener
import pe.aioo.openmoa.view.message.SpecialKeyMessage
import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.skin.SkinApplier
class NumberView : ConstraintLayout {
@@ -29,11 +34,14 @@ class NumberView : ConstraintLayout {
}
private lateinit var binding: NumberViewBinding
+ private var enterKeyListener: EnterKeyTouchListener? = null
+ private var languageKeyListener: LanguageKeyTouchListener? = null
private fun init() {
inflate(context, R.layout.number_view, this)
binding = NumberViewBinding.bind(this)
setOnTouchListeners()
+ SkinApplier.apply(this, SettingsPreferences.getKeyboardSkin(context))
}
@SuppressLint("ClickableViewAccessibility")
@@ -58,20 +66,26 @@ class NumberView : ConstraintLayout {
backspaceKey.setOnTouchListener(
RepeatKeyTouchListener(context, SpecialKeyMessage(SpecialKey.BACKSPACE))
)
- languageKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.LANGUAGE))
- )
+ languageKeyListener?.cancel()
+ languageKeyListener = LanguageKeyTouchListener(context)
+ languageKey.setOnTouchListener(languageKeyListener)
hanjaNumberPunctuationKey.setOnTouchListener(
SimpleKeyTouchListener(
context, SpecialKeyMessage(SpecialKey.HANJA_NUMBER_PUNCTUATION)
)
)
zeroKey.setOnTouchListener(SimpleKeyTouchListener(context, StringKeyMessage("0")))
- spaceKey.setOnTouchListener(SimpleKeyTouchListener(context, StringKeyMessage(" ")))
- enterKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.ENTER))
- )
+ spaceKey.setOnTouchListener(SpaceKeyTouchListener(context))
+ enterKeyListener?.cancel()
+ enterKeyListener = EnterKeyTouchListener(context)
+ enterKey.setOnTouchListener(enterKeyListener)
}
}
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ enterKeyListener?.cancel()
+ languageKeyListener?.cancel()
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/OpenMoaView.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/OpenMoaView.kt
index 6938fb3..c28da38 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/OpenMoaView.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/OpenMoaView.kt
@@ -7,21 +7,34 @@ import android.util.AttributeSet
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.content.ContextCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import pe.aioo.openmoa.OpenMoaIME
import pe.aioo.openmoa.R
import pe.aioo.openmoa.config.Config
+import pe.aioo.openmoa.config.HangulInputMode
+import pe.aioo.openmoa.settings.SettingsPreferences
import pe.aioo.openmoa.view.message.SpecialKey
import pe.aioo.openmoa.databinding.OpenMoaViewBinding
+import pe.aioo.openmoa.databinding.OpenMoaViewMoakeyBinding
import pe.aioo.openmoa.view.keytouchlistener.CrossKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.EnterKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.JaumKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.LanguageKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.RepeatKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.SimpleKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.SpaceKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.UserCharKeyTouchListener
+import pe.aioo.openmoa.quickphrase.UserCharKey
import pe.aioo.openmoa.view.message.SpecialKeyMessage
import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.quickphrase.QuickPhraseKey
+import pe.aioo.openmoa.quickphrase.QuickPhraseRepository
+import pe.aioo.openmoa.view.preview.KeyPreviewController
+import pe.aioo.openmoa.view.preview.QuickPhraseMenuPopup
+import pe.aioo.openmoa.config.KeyboardSkin
+import pe.aioo.openmoa.view.skin.SkinApplier
class OpenMoaView : ConstraintLayout, KoinComponent {
@@ -42,64 +55,143 @@ class OpenMoaView : ConstraintLayout, KoinComponent {
}
private val broadcastManager = LocalBroadcastManager.getInstance(context)
- private lateinit var binding: OpenMoaViewBinding
+ internal var isMoakeyMode = false
+ private set
+ private var twoHandBinding: OpenMoaViewBinding? = null
+ private var moakeyBinding: OpenMoaViewMoakeyBinding? = null
private var touchedMoeum: String? = null
- private val backgrounds = listOf(
- ContextCompat.getDrawable(context, R.drawable.key_background_pressed),
- ContextCompat.getDrawable(context, R.drawable.key_background),
- )
+ private var moeumKeyBgPressed: android.graphics.drawable.Drawable? = null
+ private var moeumKeyBgNormal: android.graphics.drawable.Drawable? = null
+ private lateinit var previewController: KeyPreviewController
+ private var enterKeyListener: EnterKeyTouchListener? = null
private fun init() {
- inflate(context, R.layout.open_moa_view, this)
- binding = OpenMoaViewBinding.bind(this)
- setOnTouchListeners()
+ val skin = SettingsPreferences.getKeyboardSkin(context)
+ previewController = KeyPreviewController({ config.keyPreviewEnabled }, skin)
+ val mode = SettingsPreferences.getHangulInputMode(context)
+ isMoakeyMode = mode == HangulInputMode.MOAKEY
+ if (isMoakeyMode) {
+ inflate(context, R.layout.open_moa_view_moakey, this)
+ moakeyBinding = OpenMoaViewMoakeyBinding.bind(this)
+ setMoakeyTouchListeners()
+ } else {
+ inflate(context, R.layout.open_moa_view, this)
+ twoHandBinding = OpenMoaViewBinding.bind(this)
+ setTwoHandTouchListeners()
+ }
+ updateQuickPhraseBadges()
+ updateUserCharLabels()
+ SkinApplier.apply(this, skin)
+ moeumKeyBgPressed = SkinApplier.buildKeyDrawable(context, skin, pressed = true)
+ moeumKeyBgNormal = SkinApplier.buildKeyDrawable(context, skin, pressed = false)
+ if (SettingsPreferences.getOneHandMode(context).isReduced) {
+ twoHandBinding?.emojiKey?.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 16f)
+ moakeyBinding?.emojiKey?.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 16f)
+ }
+ }
+
+ fun refreshQuickPhraseBadges() {
+ updateQuickPhraseBadges()
+ }
+
+ fun refreshUserCharLabels() {
+ updateUserCharLabels()
+ }
+
+ private fun updateQuickPhraseBadges() {
+ twoHandBinding?.apply {
+ ssangbieupBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGBIEUP)
+ ssangjieutBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGJIEUT)
+ ssangdigeutBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGDIGEUT)
+ ssanggiyeokBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGGIYEOK)
+ ssangsiotBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGSIOT)
+ kieukBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.KIEUK)
+ tieutBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.TIEUT)
+ chieutBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.CHIEUT)
+ pieupBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.PIEUP)
+ }
+ moakeyBinding?.apply {
+ ssangbieupBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGBIEUP)
+ ssangjieutBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGJIEUT)
+ ssangdigeutBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGDIGEUT)
+ ssanggiyeokBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGGIYEOK)
+ ssangsiotBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.SSANGSIOT)
+ kieukBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.KIEUK)
+ tieutBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.TIEUT)
+ chieutBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.CHIEUT)
+ pieupBadge.text = QuickPhraseRepository.getFirstChar(context, QuickPhraseKey.PIEUP)
+ }
+ }
+
+ private fun updateUserCharLabels() {
+ twoHandBinding?.apply {
+ tildeKey.text = UserCharKey.TILDE.getPhrase(context)
+ caretKey.text = UserCharKey.CARET.getPhrase(context)
+ semicolonKey.text = UserCharKey.SEMICOLON.getPhrase(context)
+ asteriskKey.text = UserCharKey.ASTERISK.getPhrase(context)
+ }
+ moakeyBinding?.apply {
+ tildeKey.text = UserCharKey.TILDE.getPhrase(context)
+ caretKey.text = UserCharKey.CARET.getPhrase(context)
+ semicolonKey.text = UserCharKey.SEMICOLON.getPhrase(context)
+ asteriskKey.text = UserCharKey.ASTERISK.getPhrase(context)
+ exclamationKey.text = UserCharKey.EXCLAMATION.getPhrase(context)
+ questionKey.text = UserCharKey.QUESTION.getPhrase(context)
+ dotKey.text = UserCharKey.DOT.getPhrase(context)
+ }
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ previewController.cancel()
+ enterKeyListener?.cancel()
}
@SuppressLint("ClickableViewAccessibility")
- private fun setOnTouchListeners() {
- binding.apply {
- tildeKey.setOnTouchListener(SimpleKeyTouchListener(context, StringKeyMessage("~")))
- ssangbieupKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅃ"))
- ssangjieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅉ"))
- ssangdigeutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄸ"))
- ssanggiyeokKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄲ"))
- ssangsiotKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅆ"))
+ private fun setTwoHandTouchListeners() {
+ val b = twoHandBinding ?: return
+ val quickPhraseMenuPopup = QuickPhraseMenuPopup(context)
+ b.apply {
+ tildeKey.setOnTouchListener(UserCharKeyTouchListener(context, UserCharKey.TILDE, previewController))
+ ssangbieupKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅃ", previewController, QuickPhraseKey.SSANGBIEUP, quickPhraseMenuPopup))
+ ssangjieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅉ", previewController, QuickPhraseKey.SSANGJIEUT, quickPhraseMenuPopup))
+ ssangdigeutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄸ", previewController, QuickPhraseKey.SSANGDIGEUT, quickPhraseMenuPopup))
+ ssanggiyeokKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄲ", previewController, QuickPhraseKey.SSANGGIYEOK, quickPhraseMenuPopup))
+ ssangsiotKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅆ", previewController, QuickPhraseKey.SSANGSIOT, quickPhraseMenuPopup))
emojiKey.setOnTouchListener(
SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.EMOJI))
)
- caretKey.setOnTouchListener(SimpleKeyTouchListener(context, StringKeyMessage("^")))
- bieupKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅂ"))
- jieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅈ"))
- digeutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄷ"))
- giyeokKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄱ"))
- siotKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅅ"))
+ caretKey.setOnTouchListener(UserCharKeyTouchListener(context, UserCharKey.CARET, previewController))
+ bieupKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅂ", previewController, numberChar = "1"))
+ jieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅈ", previewController, numberChar = "2"))
+ digeutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄷ", previewController, numberChar = "3"))
+ giyeokKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄱ", previewController, numberChar = "4"))
+ siotKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅅ", previewController, numberChar = "5"))
backspaceKey.setOnTouchListener(
RepeatKeyTouchListener(context, SpecialKeyMessage(SpecialKey.BACKSPACE))
)
semicolonKey.setOnTouchListener(
- SimpleKeyTouchListener(context, StringKeyMessage(";"))
+ UserCharKeyTouchListener(context, UserCharKey.SEMICOLON, previewController)
)
- mieumKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅁ"))
- nieunKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄴ"))
- ieungKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅇ"))
- rieulKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄹ"))
- hieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅎ"))
+ mieumKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅁ", previewController, numberChar = "6"))
+ nieunKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄴ", previewController, numberChar = "7"))
+ ieungKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅇ", previewController, numberChar = "8"))
+ rieulKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄹ", previewController, numberChar = "9"))
+ hieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅎ", previewController, numberChar = "0"))
asteriskKey.setOnTouchListener(
- SimpleKeyTouchListener(context, StringKeyMessage("*"))
- )
- kieukKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅋ"))
- tieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅌ"))
- chieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅊ"))
- pieupKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅍ"))
- languageKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.LANGUAGE))
+ UserCharKeyTouchListener(context, UserCharKey.ASTERISK, previewController)
)
+ kieukKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅋ", previewController, QuickPhraseKey.KIEUK, quickPhraseMenuPopup))
+ tieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅌ", previewController, QuickPhraseKey.TIEUT, quickPhraseMenuPopup))
+ chieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅊ", previewController, QuickPhraseKey.CHIEUT, quickPhraseMenuPopup))
+ pieupKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅍ", previewController, QuickPhraseKey.PIEUP, quickPhraseMenuPopup))
+ languageKey.setOnTouchListener(LanguageKeyTouchListener(context))
hanjaNumberPunctuationKey.setOnTouchListener(
SimpleKeyTouchListener(
context, SpecialKeyMessage(SpecialKey.HANJA_NUMBER_PUNCTUATION)
)
)
- spaceKey.setOnTouchListener(SimpleKeyTouchListener(context, StringKeyMessage(" ")))
+ spaceKey.setOnTouchListener(SpaceKeyTouchListener(context))
commaQuestionDotExclamationKey.setOnTouchListener(
CrossKeyTouchListener(
context,
@@ -109,94 +201,166 @@ class OpenMoaView : ConstraintLayout, KoinComponent {
StringKeyMessage("."),
StringKeyMessage("?"),
),
+ previewController,
+ )
+ )
+ enterKeyListener = EnterKeyTouchListener(context)
+ enterKey.setOnTouchListener(enterKeyListener)
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun setMoakeyTouchListeners() {
+ val b = moakeyBinding ?: return
+ val quickPhraseMenuPopup = QuickPhraseMenuPopup(context)
+ b.apply {
+ tildeKey.setOnTouchListener(UserCharKeyTouchListener(context, UserCharKey.TILDE, previewController))
+ ssangbieupKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅃ", previewController, QuickPhraseKey.SSANGBIEUP, quickPhraseMenuPopup))
+ ssangjieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅉ", previewController, QuickPhraseKey.SSANGJIEUT, quickPhraseMenuPopup))
+ ssangdigeutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄸ", previewController, QuickPhraseKey.SSANGDIGEUT, quickPhraseMenuPopup))
+ ssanggiyeokKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄲ", previewController, QuickPhraseKey.SSANGGIYEOK, quickPhraseMenuPopup))
+ ssangsiotKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅆ", previewController, QuickPhraseKey.SSANGSIOT, quickPhraseMenuPopup))
+ exclamationKey.setOnTouchListener(
+ UserCharKeyTouchListener(context, UserCharKey.EXCLAMATION, previewController)
+ )
+ caretKey.setOnTouchListener(UserCharKeyTouchListener(context, UserCharKey.CARET, previewController))
+ bieupKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅂ", previewController, numberChar = "1"))
+ jieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅈ", previewController, numberChar = "2"))
+ digeutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄷ", previewController, numberChar = "3"))
+ giyeokKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄱ", previewController, numberChar = "4"))
+ siotKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅅ", previewController, numberChar = "5"))
+ questionKey.setOnTouchListener(
+ UserCharKeyTouchListener(context, UserCharKey.QUESTION, previewController)
+ )
+ semicolonKey.setOnTouchListener(
+ UserCharKeyTouchListener(context, UserCharKey.SEMICOLON, previewController)
+ )
+ mieumKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅁ", previewController, numberChar = "6"))
+ nieunKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄴ", previewController, numberChar = "7"))
+ ieungKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅇ", previewController, numberChar = "8"))
+ rieulKey.setOnTouchListener(JaumKeyTouchListener(context, "ㄹ", previewController, numberChar = "9"))
+ hieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅎ", previewController, numberChar = "0"))
+ dotKey.setOnTouchListener(UserCharKeyTouchListener(context, UserCharKey.DOT, previewController))
+ asteriskKey.setOnTouchListener(
+ UserCharKeyTouchListener(context, UserCharKey.ASTERISK, previewController)
+ )
+ kieukKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅋ", previewController, QuickPhraseKey.KIEUK, quickPhraseMenuPopup))
+ tieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅌ", previewController, QuickPhraseKey.TIEUT, quickPhraseMenuPopup))
+ chieutKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅊ", previewController, QuickPhraseKey.CHIEUT, quickPhraseMenuPopup))
+ pieupKey.setOnTouchListener(JaumKeyTouchListener(context, "ㅍ", previewController, QuickPhraseKey.PIEUP, quickPhraseMenuPopup))
+ backspaceKey.setOnTouchListener(
+ RepeatKeyTouchListener(context, SpecialKeyMessage(SpecialKey.BACKSPACE))
+ )
+ emojiKey.setOnTouchListener(
+ SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.EMOJI))
+ )
+ languageKey.setOnTouchListener(LanguageKeyTouchListener(context))
+ hanjaNumberPunctuationKey.setOnTouchListener(
+ SimpleKeyTouchListener(
+ context, SpecialKeyMessage(SpecialKey.HANJA_NUMBER_PUNCTUATION)
)
)
- enterKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.ENTER))
+ spaceKey.setOnTouchListener(SpaceKeyTouchListener(context))
+ moeumKey.setOnTouchListener(
+ CrossKeyTouchListener(
+ context,
+ listOf(
+ StringKeyMessage("ᆢ"),
+ StringKeyMessage("ㅡ"),
+ StringKeyMessage("ㆍ"),
+ StringKeyMessage("ㅣ"),
+ ),
+ previewController,
+ )
)
+ enterKeyListener = EnterKeyTouchListener(context)
+ enterKey.setOnTouchListener(enterKeyListener)
}
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
- touchedMoeum.let { moeum ->
- when (ev.action) {
- MotionEvent.ACTION_DOWN,
- MotionEvent.ACTION_MOVE -> {
- if (ev.action == MotionEvent.ACTION_DOWN ||
- (ev.action == MotionEvent.ACTION_MOVE && touchedMoeum != null)
- ) {
- binding.iKey.apply {
- if (ev.x in x..x + width && ev.y in y..y + height) {
- if (moeum != "ㅣ") {
- background = backgrounds[0]
- binding.euKey.background = backgrounds[1]
- binding.araeaKey.background = backgrounds[1]
- if (config.hapticFeedback) {
- performHapticFeedback(
- HapticFeedbackConstants.KEYBOARD_PRESS
- )
- }
- if (moeum != null) {
- sendKeyMessage(StringKeyMessage(moeum))
+ if (!isMoakeyMode) {
+ val b = twoHandBinding ?: return super.dispatchTouchEvent(ev)
+ touchedMoeum.let { moeum ->
+ when (ev.action) {
+ MotionEvent.ACTION_DOWN,
+ MotionEvent.ACTION_MOVE -> {
+ if (ev.action == MotionEvent.ACTION_DOWN ||
+ (ev.action == MotionEvent.ACTION_MOVE && touchedMoeum != null)
+ ) {
+ b.iKey.apply {
+ if (ev.x in x..x + width && ev.y in y..y + height) {
+ if (moeum != "ㅣ") {
+ background = moeumKeyBgPressed
+ b.euKey.background = moeumKeyBgNormal
+ b.araeaKey.background = moeumKeyBgNormal
+ if (config.hapticFeedback) {
+ performHapticFeedback(
+ HapticFeedbackConstants.KEYBOARD_PRESS
+ )
+ }
+ if (moeum != null) {
+ sendKeyMessage(StringKeyMessage(moeum))
+ }
}
+ touchedMoeum = "ㅣ"
+ return true
}
- touchedMoeum = "ㅣ"
- return true
}
- }
- binding.euKey.apply {
- if (ev.x in x..x + width && ev.y in y..y + height) {
- if (moeum != "ㅡ") {
- background = backgrounds[0]
- binding.iKey.background = backgrounds[1]
- binding.araeaKey.background = backgrounds[1]
- if (config.hapticFeedback) {
- performHapticFeedback(
- HapticFeedbackConstants.KEYBOARD_PRESS
- )
- }
- if (moeum != null) {
- sendKeyMessage(StringKeyMessage(moeum))
+ b.euKey.apply {
+ if (ev.x in x..x + width && ev.y in y..y + height) {
+ if (moeum != "ㅡ") {
+ background = moeumKeyBgPressed
+ b.iKey.background = moeumKeyBgNormal
+ b.araeaKey.background = moeumKeyBgNormal
+ if (config.hapticFeedback) {
+ performHapticFeedback(
+ HapticFeedbackConstants.KEYBOARD_PRESS
+ )
+ }
+ if (moeum != null) {
+ sendKeyMessage(StringKeyMessage(moeum))
+ }
}
+ touchedMoeum = "ㅡ"
+ return true
}
- touchedMoeum = "ㅡ"
- return true
}
- }
- binding.araeaKey.apply {
- if (ev.x in x..x + width && ev.y in y..y + height) {
- if (moeum != "ㆍ") {
- background = backgrounds[0]
- binding.iKey.background = backgrounds[1]
- binding.euKey.background = backgrounds[1]
- if (config.hapticFeedback) {
- performHapticFeedback(
- HapticFeedbackConstants.KEYBOARD_PRESS
- )
- }
- if (moeum != null) {
- sendKeyMessage(StringKeyMessage(moeum))
+ b.araeaKey.apply {
+ if (ev.x in x..x + width && ev.y in y..y + height) {
+ if (moeum != "ㆍ") {
+ background = moeumKeyBgPressed
+ b.iKey.background = moeumKeyBgNormal
+ b.euKey.background = moeumKeyBgNormal
+ if (config.hapticFeedback) {
+ performHapticFeedback(
+ HapticFeedbackConstants.KEYBOARD_PRESS
+ )
+ }
+ if (moeum != null) {
+ sendKeyMessage(StringKeyMessage(moeum))
+ }
}
+ touchedMoeum = "ㆍ"
+ return true
}
- touchedMoeum = "ㆍ"
- return true
}
}
+ Unit
}
- Unit
- }
- MotionEvent.ACTION_UP -> {
- if (moeum != null) {
- binding.iKey.background = backgrounds[1]
- binding.euKey.background = backgrounds[1]
- binding.araeaKey.background = backgrounds[1]
- sendKeyMessage(StringKeyMessage(moeum))
- touchedMoeum = null
- return true
+ MotionEvent.ACTION_UP -> {
+ if (moeum != null) {
+ b.iKey.background = moeumKeyBgNormal
+ b.euKey.background = moeumKeyBgNormal
+ b.araeaKey.background = moeumKeyBgNormal
+ sendKeyMessage(StringKeyMessage(moeum))
+ touchedMoeum = null
+ return true
+ }
+ Unit
}
- Unit
+ else -> Unit
}
- else -> Unit
}
}
return super.dispatchTouchEvent(ev)
@@ -210,4 +374,4 @@ class OpenMoaView : ConstraintLayout, KoinComponent {
)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/PhoneView.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/PhoneView.kt
index a3d6d47..b267b36 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/PhoneView.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/PhoneView.kt
@@ -6,12 +6,16 @@ import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import pe.aioo.openmoa.R
import pe.aioo.openmoa.databinding.PhoneViewBinding
+import pe.aioo.openmoa.view.keytouchlistener.EnterKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.FunctionalKeyTouchListener
import pe.aioo.openmoa.view.message.SpecialKey
import pe.aioo.openmoa.view.keytouchlistener.RepeatKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.SimpleKeyTouchListener
+import pe.aioo.openmoa.settings.SettingsPreferences
+import pe.aioo.openmoa.view.keytouchlistener.SpaceKeyTouchListener
import pe.aioo.openmoa.view.message.SpecialKeyMessage
import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.skin.SkinApplier
class PhoneView : ConstraintLayout {
@@ -31,12 +35,14 @@ class PhoneView : ConstraintLayout {
private lateinit var binding: PhoneViewBinding
private var page = 0
+ private var enterKeyListener: EnterKeyTouchListener? = null
private fun init() {
inflate(context, R.layout.phone_view, this)
binding = PhoneViewBinding.bind(this)
setPageOrNextPage(0, true)
setOnTouchListeners()
+ SkinApplier.apply(this, SettingsPreferences.getKeyboardSkin(context))
}
fun setPageOrNextPage(newPage: Int? = null, isInitialize: Boolean = false) {
@@ -80,13 +86,17 @@ class PhoneView : ConstraintLayout {
null
}
)
- spaceKey.setOnTouchListener(SimpleKeyTouchListener(context, StringKeyMessage(" ")))
- enterKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.ENTER))
- )
+ spaceKey.setOnTouchListener(SpaceKeyTouchListener(context))
+ enterKeyListener = EnterKeyTouchListener(context)
+ enterKey.setOnTouchListener(enterKeyListener)
}
}
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ enterKeyListener?.cancel()
+ }
+
companion object {
private val KEY_LIST = listOf(
listOf(
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/PunctuationView.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/PunctuationView.kt
index d3673f6..e7150d8 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/PunctuationView.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/PunctuationView.kt
@@ -4,16 +4,27 @@ import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
import pe.aioo.openmoa.R
+import pe.aioo.openmoa.config.Config
import pe.aioo.openmoa.databinding.PunctuationViewBinding
import pe.aioo.openmoa.view.message.SpecialKey
+import pe.aioo.openmoa.view.keytouchlistener.EnterKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.FunctionalKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.LanguageKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.RepeatKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.SimpleKeyTouchListener
+import pe.aioo.openmoa.settings.SettingsPreferences
+import pe.aioo.openmoa.view.keytouchlistener.SpaceKeyTouchListener
import pe.aioo.openmoa.view.message.SpecialKeyMessage
import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.preview.KeyPreviewController
+import pe.aioo.openmoa.view.skin.SkinApplier
-class PunctuationView : ConstraintLayout {
+class PunctuationView : ConstraintLayout, KoinComponent {
+
+ private val config: Config by inject()
constructor(context: Context) : super(context) {
init()
@@ -30,13 +41,25 @@ class PunctuationView : ConstraintLayout {
}
private lateinit var binding: PunctuationViewBinding
+ private var previewController: KeyPreviewController? = null
+ private var enterKeyListener: EnterKeyTouchListener? = null
+ private var languageKeyListener: LanguageKeyTouchListener? = null
private var page = 0
private fun init() {
inflate(context, R.layout.punctuation_view, this)
binding = PunctuationViewBinding.bind(this)
+ previewController = KeyPreviewController({ config.keyPreviewEnabled })
setPageOrNextPage(0, true)
setOnTouchListeners()
+ SkinApplier.apply(this, SettingsPreferences.getKeyboardSkin(context))
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ previewController?.cancel()
+ enterKeyListener?.cancel()
+ languageKeyListener?.cancel()
}
fun setPageOrNextPage(newPage: Int? = null, isInitialize: Boolean = false) {
@@ -68,7 +91,7 @@ class PunctuationView : ConstraintLayout {
binding.nKey, binding.mKey,
).map {
it.apply {
- setOnTouchListener(FunctionalKeyTouchListener(context) {
+ setOnTouchListener(FunctionalKeyTouchListener(context, previewController = previewController) {
StringKeyMessage(text.toString())
})
}
@@ -83,21 +106,21 @@ class PunctuationView : ConstraintLayout {
backspaceKey.setOnTouchListener(
RepeatKeyTouchListener(context, SpecialKeyMessage(SpecialKey.BACKSPACE))
)
- languageKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.LANGUAGE))
- )
+ languageKeyListener?.cancel()
+ languageKeyListener = LanguageKeyTouchListener(context)
+ languageKey.setOnTouchListener(languageKeyListener)
hanjaNumberPunctuationKey.setOnTouchListener(
SimpleKeyTouchListener(
context, SpecialKeyMessage(SpecialKey.HANJA_NUMBER_PUNCTUATION)
)
)
- spaceKey.setOnTouchListener(SimpleKeyTouchListener(context, StringKeyMessage(" ")))
+ spaceKey.setOnTouchListener(SpaceKeyTouchListener(context))
arrowKey.setOnTouchListener(
SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.ARROW))
)
- enterKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.ENTER))
- )
+ enterKeyListener?.cancel()
+ enterKeyListener = EnterKeyTouchListener(context)
+ enterKey.setOnTouchListener(enterKeyListener)
}
}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/qwerty/HintKeyView.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/qwerty/HintKeyView.kt
new file mode 100644
index 0000000..0e2bfa6
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/qwerty/HintKeyView.kt
@@ -0,0 +1,46 @@
+package pe.aioo.openmoa.view.keyboardview.qwerty
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.util.TypedValue
+import androidx.appcompat.widget.AppCompatTextView
+
+class HintKeyView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : AppCompatTextView(context, attrs, defStyleAttr) {
+
+ var keyHint: String = ""
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ private val hintPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 9f, resources.displayMetrics)
+ color = Color.argb(153, 0, 0, 0)
+ }
+
+ override fun setTextColor(color: Int) {
+ super.setTextColor(color)
+ hintPaint.color = Color.argb(
+ (Color.alpha(color) * 0.6f).toInt(),
+ Color.red(color),
+ Color.green(color),
+ Color.blue(color),
+ )
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ if (keyHint.isEmpty()) return
+ val x = width - paddingRight - hintPaint.measureText(keyHint) - 4f
+ val y = paddingTop + hintPaint.textSize + 3f
+ canvas.drawText(keyHint, x, y, hintPaint)
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/qwerty/QuertyView.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/qwerty/QuertyView.kt
index 7568ae1..13d3883 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/qwerty/QuertyView.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keyboardview/qwerty/QuertyView.kt
@@ -2,20 +2,38 @@ package pe.aioo.openmoa.view.keyboardview.qwerty
import android.annotation.SuppressLint
import android.content.Context
+import android.content.SharedPreferences
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.content.ContextCompat
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
import pe.aioo.openmoa.R
+import pe.aioo.openmoa.config.Config
import pe.aioo.openmoa.view.message.SpecialKey
import pe.aioo.openmoa.databinding.QuertyViewBinding
+import pe.aioo.openmoa.quickphrase.QwertyLongKey
+import pe.aioo.openmoa.quickphrase.QwertyLongKeyRepository
import pe.aioo.openmoa.view.keytouchlistener.CrossKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.EnterKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.FunctionalKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.LanguageKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.QwertyKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.RepeatKeyTouchListener
import pe.aioo.openmoa.view.keytouchlistener.SimpleKeyTouchListener
+import pe.aioo.openmoa.view.keytouchlistener.SpaceKeyTouchListener
import pe.aioo.openmoa.view.message.SpecialKeyMessage
import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.config.KeyboardSkin
+import android.content.Intent
+import pe.aioo.openmoa.settings.PhraseEditActivity
+import pe.aioo.openmoa.settings.SettingsPreferences
+import pe.aioo.openmoa.view.preview.KeyPreviewController
+import pe.aioo.openmoa.view.preview.QuickPhraseMenuPopup
+import pe.aioo.openmoa.view.skin.SkinApplier
-class QuertyView : ConstraintLayout {
+class QuertyView : ConstraintLayout, KoinComponent {
+
+ private val config: Config by inject()
constructor(context: Context) : super(context) {
init()
@@ -33,12 +51,62 @@ class QuertyView : ConstraintLayout {
private var shiftKeyStatus = ShiftKeyStatus.DISABLED
private lateinit var binding: QuertyViewBinding
+ private var previewController: KeyPreviewController? = null
+ private var currentSkin: KeyboardSkin = KeyboardSkin.DEFAULT
+ private val configurableLongKeyListeners = mutableListOf()
+ private var enterKeyListener: EnterKeyTouchListener? = null
+ private var languageKeyListener: LanguageKeyTouchListener? = null
+ private val prefs by lazy {
+ context.getSharedPreferences(SettingsPreferences.PREFS_NAME, Context.MODE_PRIVATE)
+ }
+ private val prefKeySet = QwertyLongKey.values().map { it.prefKey }.toSet()
+ private val prefChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+ if (key in prefKeySet && ::binding.isInitialized) updateConfigurableKeyHints()
+ }
+ private val configurableLongKeyPairs by lazy {
+ listOf(
+ binding.zKey to QwertyLongKey.Z, binding.xKey to QwertyLongKey.X,
+ binding.cKey to QwertyLongKey.C, binding.vKey to QwertyLongKey.V,
+ binding.bKey to QwertyLongKey.B, binding.nKey to QwertyLongKey.N,
+ binding.mKey to QwertyLongKey.M,
+ )
+ }
private fun init() {
inflate(context, R.layout.querty_view, this)
binding = QuertyViewBinding.bind(this)
+ currentSkin = SettingsPreferences.getKeyboardSkin(context)
+ previewController = KeyPreviewController({ config.keyPreviewEnabled }, currentSkin)
setShiftStatus(ShiftKeyStatus.DISABLED, true)
setOnTouchListeners()
+ SkinApplier.apply(this, currentSkin)
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ prefs.registerOnSharedPreferenceChangeListener(prefChangeListener)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ prefs.unregisterOnSharedPreferenceChangeListener(prefChangeListener)
+ previewController?.cancel()
+ configurableLongKeyListeners.forEach { it.cancel() }
+ enterKeyListener?.cancel()
+ languageKeyListener?.cancel()
+ }
+
+ override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
+ super.onWindowFocusChanged(hasWindowFocus)
+ if (!hasWindowFocus) {
+ configurableLongKeyListeners.forEach { it.cancel() }
+ }
+ }
+
+ private fun updateConfigurableKeyHints() {
+ configurableLongKeyPairs.forEach { (view, longKeyEnum) ->
+ view.keyHint = QwertyLongKeyRepository.getPhrase(context, longKeyEnum).take(1)
+ }
}
private fun setShiftStatus(status: ShiftKeyStatus, isInitialize: Boolean = false) {
@@ -58,14 +126,11 @@ class QuertyView : ConstraintLayout {
view.text = KEY_LIST[if (isShiftEnabled) 1 else 0][index]
}
binding.shiftKey.setTextColor(
- ContextCompat.getColor(
- context,
- if (isShiftEnabled) {
- R.color.key_foreground_locked
- } else {
- R.color.key_foreground
- },
- )
+ if (isShiftEnabled) {
+ SkinApplier.fgAccentColor(context, currentSkin)
+ } else {
+ SkinApplier.fgColor(context, currentSkin)
+ }
)
}
binding.shiftKey.text = if (status == ShiftKeyStatus.LOCKED) "⬆︎" else "⇧"
@@ -83,14 +148,9 @@ class QuertyView : ConstraintLayout {
listOf(
binding.oneKey, binding.twoKey, binding.threeKey, binding.fourKey, binding.fiveKey,
binding.sixKey, binding.sevenKey, binding.eightKey, binding.nineKey, binding.zeroKey,
- binding.qKey, binding.wKey, binding.eKey, binding.rKey, binding.tKey, binding.yKey,
- binding.uKey, binding.iKey, binding.oKey, binding.pKey, binding.aKey, binding.sKey,
- binding.dKey, binding.fKey, binding.gKey, binding.hKey, binding.jKey, binding.kKey,
- binding.lKey, binding.zKey, binding.xKey, binding.cKey, binding.vKey, binding.bKey,
- binding.nKey, binding.mKey,
).map {
it.apply {
- setOnTouchListener(FunctionalKeyTouchListener(context) {
+ setOnTouchListener(FunctionalKeyTouchListener(context, previewController = previewController) {
val key = text.toString()
setShiftStatus(
when (shiftKeyStatus) {
@@ -102,9 +162,10 @@ class QuertyView : ConstraintLayout {
})
}
}
+ setAlphaKeyTouchListeners()
binding.apply {
shiftKey.setOnTouchListener(
- FunctionalKeyTouchListener(context, false) {
+ FunctionalKeyTouchListener(context, triggerWhenActionUp = false) {
setShiftStatus(
when (shiftKeyStatus) {
ShiftKeyStatus.DISABLED -> ShiftKeyStatus.ENABLED
@@ -119,15 +180,15 @@ class QuertyView : ConstraintLayout {
backspaceKey.setOnTouchListener(
RepeatKeyTouchListener(context, SpecialKeyMessage(SpecialKey.BACKSPACE))
)
- languageKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.LANGUAGE))
- )
+ languageKeyListener?.cancel()
+ languageKeyListener = LanguageKeyTouchListener(context)
+ languageKey.setOnTouchListener(languageKeyListener)
hanjaNumberPunctuationKey.setOnTouchListener(
SimpleKeyTouchListener(
context, SpecialKeyMessage(SpecialKey.HANJA_NUMBER_PUNCTUATION)
)
)
- spaceKey.setOnTouchListener(SimpleKeyTouchListener(context, StringKeyMessage(" ")))
+ spaceKey.setOnTouchListener(SpaceKeyTouchListener(context))
commaQuestionDotExclamationKey.setOnTouchListener(
CrossKeyTouchListener(
context,
@@ -137,11 +198,72 @@ class QuertyView : ConstraintLayout {
StringKeyMessage("."),
StringKeyMessage("?"),
),
+ previewController,
)
)
- enterKey.setOnTouchListener(
- SimpleKeyTouchListener(context, SpecialKeyMessage(SpecialKey.ENTER))
+ enterKeyListener = EnterKeyTouchListener(context)
+ enterKey.setOnTouchListener(enterKeyListener)
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun setAlphaKeyTouchListeners() {
+ val fixedLongKeyPairs = listOf(
+ binding.qKey to "#", binding.wKey to "&", binding.eKey to "%",
+ binding.rKey to "+", binding.tKey to "=", binding.yKey to "_",
+ binding.uKey to "\\", binding.iKey to "|", binding.oKey to "<", binding.pKey to ">",
+ binding.aKey to "-", binding.sKey to "@", binding.dKey to "*",
+ binding.fKey to "^", binding.gKey to ":", binding.hKey to ";",
+ binding.jKey to "(", binding.kKey to ")", binding.lKey to "~",
+ )
+ fixedLongKeyPairs.forEach { (view, longKey) ->
+ view.apply {
+ keyHint = longKey
+ setOnTouchListener(QwertyKeyTouchListener(
+ context,
+ previewController,
+ { longKey },
+ onTap = {
+ val key = text.toString()
+ setShiftStatus(when (shiftKeyStatus) {
+ ShiftKeyStatus.ENABLED -> ShiftKeyStatus.DISABLED
+ else -> shiftKeyStatus
+ })
+ StringKeyMessage(key)
+ }
+ ))
+ }
+ }
+ configurableLongKeyListeners.clear()
+ configurableLongKeyPairs.forEach { (view, longKeyEnum) ->
+ val popup = QuickPhraseMenuPopup(context)
+ val listener = QwertyKeyTouchListener(
+ context,
+ previewController,
+ { QwertyLongKeyRepository.getPhrase(context, longKeyEnum) },
+ onTap = {
+ val key = view.text.toString()
+ setShiftStatus(when (shiftKeyStatus) {
+ ShiftKeyStatus.ENABLED -> ShiftKeyStatus.DISABLED
+ else -> shiftKeyStatus
+ })
+ StringKeyMessage(key)
+ },
+ quickPhraseMenuPopup = popup,
+ onEdit = {
+ val intent = Intent(context, PhraseEditActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ putExtra(PhraseEditActivity.EXTRA_TYPE, PhraseEditActivity.TYPE_ENGLISH)
+ putExtra(PhraseEditActivity.EXTRA_KEY, longKeyEnum.name)
+ }
+ context.startActivity(intent)
+ }
)
+ configurableLongKeyListeners.add(listener)
+ view.apply {
+ keyHint = QwertyLongKeyRepository.getPhrase(context, longKeyEnum).take(1)
+ setOnTouchListener(listener)
+ }
}
}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/BaseKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/BaseKeyTouchListener.kt
index 9aa0732..c022e43 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/BaseKeyTouchListener.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/BaseKeyTouchListener.kt
@@ -6,25 +6,26 @@ import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
-import androidx.core.content.ContextCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import pe.aioo.openmoa.OpenMoaIME
-import pe.aioo.openmoa.R
import pe.aioo.openmoa.config.Config
+import pe.aioo.openmoa.settings.SettingsPreferences
import pe.aioo.openmoa.view.message.BaseKeyMessage
import pe.aioo.openmoa.view.message.SpecialKeyMessage
import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.skin.SkinApplier
open class BaseKeyTouchListener(context: Context) : OnTouchListener, KoinComponent {
protected val config: Config by inject()
private val broadcastManager = LocalBroadcastManager.getInstance(context)
+ private val skin = SettingsPreferences.getKeyboardSkin(context)
private val backgrounds = listOf(
- ContextCompat.getDrawable(context, R.drawable.key_background_pressed),
- ContextCompat.getDrawable(context, R.drawable.key_background),
+ SkinApplier.buildKeyDrawable(context, skin, pressed = true),
+ SkinApplier.buildKeyDrawable(context, skin, pressed = false),
)
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/CrossKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/CrossKeyTouchListener.kt
index bded2f5..6c82195 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/CrossKeyTouchListener.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/CrossKeyTouchListener.kt
@@ -4,44 +4,48 @@ import android.annotation.SuppressLint
import android.content.Context
import android.view.MotionEvent
import android.view.View
-import pe.aioo.openmoa.view.message.BaseKeyMessage
+import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.preview.KeyPreviewController
import kotlin.math.*
+// keyList 순서: [0]=위/왼, [1]=오른쪽, [2]=기본(탭), [3]=왼쪽/아래
class CrossKeyTouchListener(
context: Context,
- private val keyList: List,
+ private val keyList: List,
+ private val previewController: KeyPreviewController? = null,
) : BaseKeyTouchListener(context) {
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]
+ }
+ }
+
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
startX = motionEvent.x
startY = motionEvent.y
+ previewController?.show(view, keyList[2].key)
+ }
+ MotionEvent.ACTION_MOVE -> {
+ previewController?.update(view, resolveKey(motionEvent.x, motionEvent.y).key)
}
MotionEvent.ACTION_UP -> {
- val currentX = motionEvent.x
- val currentY = motionEvent.y
- val distance = sqrt(
- (currentX - startX).pow(2) + (currentY - startY).pow(2)
- )
- if (distance > config.gestureThreshold) {
- val degree = (atan2(currentY - startY, currentX - startX) * 180f) / PI
- startX = currentX
- startY = currentY
- if (abs(degree) < 45f) {
- sendKeyMessage(keyList[1])
- } else if (abs(degree) < 135f) {
- sendKeyMessage(if (degree > 0) keyList[2] else keyList[0])
- } else {
- sendKeyMessage(keyList[3])
- }
- } else {
- sendKeyMessage(keyList[2])
- }
+ previewController?.hide()
+ sendKeyMessage(resolveKey(motionEvent.x, motionEvent.y))
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ previewController?.hide()
}
}
return super.onTouch(view, motionEvent)
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/EnterKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/EnterKeyTouchListener.kt
new file mode 100644
index 0000000..d85a805
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/EnterKeyTouchListener.kt
@@ -0,0 +1,69 @@
+package pe.aioo.openmoa.view.keytouchlistener
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.view.MotionEvent
+import android.view.View
+import pe.aioo.openmoa.config.EnterLongPressAction
+import pe.aioo.openmoa.settings.SettingsPreferences
+import pe.aioo.openmoa.view.message.SpecialKey
+import pe.aioo.openmoa.view.message.SpecialKeyMessage
+
+class EnterKeyTouchListener(private val context: Context) : BaseKeyTouchListener(context) {
+
+ private var longPressTriggered = false
+ private var longPressRunnable: Runnable? = null
+ private val handler = Handler(Looper.getMainLooper())
+
+ fun cancel() {
+ longPressRunnable?.let { handler.removeCallbacks(it) }
+ longPressRunnable = null
+ longPressTriggered = false
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
+ when (motionEvent.action) {
+ MotionEvent.ACTION_DOWN -> {
+ longPressTriggered = false
+ val action = SettingsPreferences.getEnterLongPressAction(context)
+ if (action == EnterLongPressAction.NONE) return super.onTouch(view, motionEvent)
+ val runnable = Runnable {
+ when (action) {
+ EnterLongPressAction.ARROW -> {
+ longPressTriggered = true
+ sendKeyMessage(SpecialKeyMessage(SpecialKey.ARROW))
+ }
+ EnterLongPressAction.IME_PICKER -> {
+ longPressTriggered = true
+ sendKeyMessage(SpecialKeyMessage(SpecialKey.SHOW_IME_PICKER))
+ }
+ EnterLongPressAction.SWITCH_LANGUAGE -> {
+ longPressTriggered = true
+ sendKeyMessage(SpecialKeyMessage(SpecialKey.LANGUAGE))
+ }
+ EnterLongPressAction.NONE -> Unit
+ }
+ }
+ longPressRunnable = runnable
+ handler.postDelayed(runnable, config.longPressThresholdTime)
+ }
+ MotionEvent.ACTION_UP -> {
+ longPressRunnable?.let { handler.removeCallbacks(it) }
+ if (longPressTriggered) {
+ longPressTriggered = false
+ return true
+ }
+ sendKeyMessage(SpecialKeyMessage(SpecialKey.ENTER))
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ longPressRunnable?.let { handler.removeCallbacks(it) }
+ longPressRunnable = null
+ longPressTriggered = false
+ }
+ }
+ return super.onTouch(view, motionEvent)
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/FunctionalKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/FunctionalKeyTouchListener.kt
index 6d0e4c4..62bbefb 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/FunctionalKeyTouchListener.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/FunctionalKeyTouchListener.kt
@@ -4,21 +4,30 @@ import android.annotation.SuppressLint
import android.content.Context
import android.view.MotionEvent
import android.view.View
+import android.widget.TextView
import pe.aioo.openmoa.view.message.BaseKeyMessage
+import pe.aioo.openmoa.view.preview.KeyPreviewController
class FunctionalKeyTouchListener(
context: Context,
private val triggerWhenActionUp: Boolean = true,
+ private val previewController: KeyPreviewController? = null,
private val func: () -> BaseKeyMessage?,
) : BaseKeyTouchListener(context) {
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (motionEvent.action) {
- if (triggerWhenActionUp) MotionEvent.ACTION_UP else MotionEvent.ACTION_DOWN -> {
- func()?.let {
- sendKeyMessage(it)
- }
+ MotionEvent.ACTION_DOWN -> {
+ (view as? TextView)?.text?.toString()?.let { previewController?.show(view, it) }
+ if (!triggerWhenActionUp) func()?.let { sendKeyMessage(it) }
+ }
+ MotionEvent.ACTION_UP -> {
+ previewController?.hide()
+ if (triggerWhenActionUp) func()?.let { sendKeyMessage(it) }
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ previewController?.hide()
}
}
return super.onTouch(view, motionEvent)
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/JaumKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/JaumKeyTouchListener.kt
index 1b7c946..b47a4e2 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/JaumKeyTouchListener.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/JaumKeyTouchListener.kt
@@ -2,36 +2,86 @@ package pe.aioo.openmoa.view.keytouchlistener
import android.annotation.SuppressLint
import android.content.Context
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
import android.view.MotionEvent
import android.view.View
+import pe.aioo.openmoa.hangul.HangulPreviewComposer
import pe.aioo.openmoa.hangul.MoeumGestureProcessor
+import pe.aioo.openmoa.quickphrase.QuickPhraseKey
+import pe.aioo.openmoa.quickphrase.QuickPhraseRepository
+import pe.aioo.openmoa.settings.PhraseEditActivity
import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.preview.KeyPreviewController
+import pe.aioo.openmoa.view.preview.QuickPhraseMenuPopup
import kotlin.math.*
class JaumKeyTouchListener(
- context: Context,
+ private val context: Context,
private val key: String,
+ private val previewController: KeyPreviewController? = null,
+ private val quickPhraseKey: QuickPhraseKey? = null,
+ private val quickPhraseMenuPopup: QuickPhraseMenuPopup? = null,
+ private val numberChar: String? = null,
) : BaseKeyTouchListener(context) {
- private var startX: Float = 0f
- private var startY: Float = 0f
+ init {
+ require(quickPhraseKey == null || numberChar == null) {
+ "quickPhraseKey and numberChar cannot both be set"
+ }
+ }
+
+ private var startX = 0f
+ private var startY = 0f
+ private var longPressStartX = 0f
+ private var hasMoved = false
+ private var isLongPressed = false
private val moeumGestureProcessor = MoeumGestureProcessor()
+ private val handler = Handler(Looper.getMainLooper())
+ private var anchorView: View? = null
+
+ private val longPressRunnable = Runnable {
+ val view = anchorView ?: return@Runnable
+ isLongPressed = true
+ val phraseKey = quickPhraseKey
+ if (phraseKey != null) {
+ previewController?.hide()
+ val phrase = QuickPhraseRepository.getPhrase(context, phraseKey)
+ quickPhraseMenuPopup?.show(view, phrase)
+ } else if (numberChar != null) {
+ previewController?.update(view, numberChar)
+ }
+ }
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
+ anchorView = view
startX = motionEvent.x
startY = motionEvent.y
+ longPressStartX = motionEvent.x
+ hasMoved = false
+ isLongPressed = false
moeumGestureProcessor.clear()
+ previewController?.show(view, key)
+ if (quickPhraseKey != null || numberChar != null) {
+ handler.postDelayed(longPressRunnable, config.longPressThresholdTime)
+ }
}
MotionEvent.ACTION_MOVE -> {
+ if (isLongPressed && numberChar != null) {
+ return true
+ }
val currentX = motionEvent.x
val currentY = motionEvent.y
- val distance = sqrt(
- (currentX - startX).pow(2) + (currentY - startY).pow(2)
- )
+ val distance = sqrt((currentX - startX).pow(2) + (currentY - startY).pow(2))
if (distance > config.gestureThreshold) {
+ if (!hasMoved) {
+ hasMoved = true
+ handler.removeCallbacks(longPressRunnable)
+ }
val degree = (atan2(currentY - startY, currentX - startX) * 180f) / PI
startX = currentX
startY = currentY
@@ -46,16 +96,53 @@ class JaumKeyTouchListener(
} else if (abs(degree) <= 179.999f) {
moeumGestureProcessor.appendMoeum("ㅓ")
}
+ val moeum = moeumGestureProcessor.resolveMoeumList()
+ previewController?.update(view, HangulPreviewComposer.compose(key, moeum))
+ }
+ if (isLongPressed && quickPhraseKey != null) {
+ val dx = motionEvent.x - longPressStartX
+ quickPhraseMenuPopup?.updateSelectionByDelta(dx, view.width)
}
}
MotionEvent.ACTION_UP -> {
- sendKeyMessage(StringKeyMessage(key))
- moeumGestureProcessor.resolveMoeumList()?.let {
- sendKeyMessage(StringKeyMessage(it))
+ handler.removeCallbacks(longPressRunnable)
+ previewController?.hide()
+ if (isLongPressed) {
+ isLongPressed = false
+ val phraseKey = quickPhraseKey
+ if (phraseKey != null) {
+ when (quickPhraseMenuPopup?.confirmAndDismiss()) {
+ QuickPhraseMenuPopup.MenuItem.PHRASE_PREVIEW -> {
+ sendKeyMessage(StringKeyMessage(QuickPhraseRepository.getPhrase(context, phraseKey)))
+ }
+ QuickPhraseMenuPopup.MenuItem.EDIT -> {
+ val intent = Intent(context, PhraseEditActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ putExtra(PhraseEditActivity.EXTRA_TYPE, PhraseEditActivity.TYPE_KOREAN)
+ putExtra(PhraseEditActivity.EXTRA_KEY, phraseKey.name)
+ }
+ context.startActivity(intent)
+ }
+ else -> Unit
+ }
+ } else if (numberChar != null) {
+ sendKeyMessage(StringKeyMessage(numberChar))
+ }
+ } else {
+ // 탭 또는 제스처 종료: 자음 + 합성 모음 전송 (모든 자음 키 공통)
+ sendKeyMessage(StringKeyMessage(key))
+ moeumGestureProcessor.resolveMoeumList()?.let {
+ sendKeyMessage(StringKeyMessage(it))
+ }
}
}
+ MotionEvent.ACTION_CANCEL -> {
+ handler.removeCallbacks(longPressRunnable)
+ previewController?.hide()
+ quickPhraseMenuPopup?.dismiss()
+ isLongPressed = false
+ }
}
return super.onTouch(view, motionEvent)
}
-
-}
\ No newline at end of file
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/LanguageKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/LanguageKeyTouchListener.kt
new file mode 100644
index 0000000..ca26c42
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/LanguageKeyTouchListener.kt
@@ -0,0 +1,63 @@
+package pe.aioo.openmoa.view.keytouchlistener
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.view.MotionEvent
+import android.view.View
+import pe.aioo.openmoa.view.message.SpecialKey
+import pe.aioo.openmoa.view.message.SpecialKeyMessage
+import java.util.Timer
+
+class LanguageKeyTouchListener(context: Context) : BaseKeyTouchListener(context) {
+
+ @Volatile
+ private var longPressTriggered = false
+ private var timer: Timer? = null
+ private val mainHandler = Handler(Looper.getMainLooper())
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
+ when (motionEvent.action) {
+ MotionEvent.ACTION_DOWN -> {
+ longPressTriggered = false
+ var elapsed = 0L
+ timer = kotlin.concurrent.timer(period = config.longPressRepeatTime) {
+ elapsed += config.longPressRepeatTime
+ if (elapsed >= LONG_PRESS_THRESHOLD_MS) {
+ longPressTriggered = true
+ cancel()
+ mainHandler.post {
+ sendKeyMessage(SpecialKeyMessage(SpecialKey.OPEN_SETTINGS))
+ }
+ }
+ }
+ }
+ MotionEvent.ACTION_UP -> {
+ timer?.cancel()
+ timer = null
+ if (!longPressTriggered) {
+ sendKeyMessage(SpecialKeyMessage(SpecialKey.LANGUAGE))
+ }
+ longPressTriggered = false
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ timer?.cancel()
+ timer = null
+ longPressTriggered = false
+ }
+ }
+ return super.onTouch(view, motionEvent)
+ }
+
+ fun cancel() {
+ timer?.cancel()
+ timer = null
+ longPressTriggered = false
+ }
+
+ companion object {
+ private const val LONG_PRESS_THRESHOLD_MS = 300L
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/QwertyKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/QwertyKeyTouchListener.kt
new file mode 100644
index 0000000..77b4c5b
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/QwertyKeyTouchListener.kt
@@ -0,0 +1,92 @@
+package pe.aioo.openmoa.view.keytouchlistener
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.view.MotionEvent
+import android.view.View
+import android.widget.TextView
+import pe.aioo.openmoa.view.message.BaseKeyMessage
+import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.preview.KeyPreviewController
+import pe.aioo.openmoa.view.preview.QuickPhraseMenuPopup
+
+class QwertyKeyTouchListener(
+ context: Context,
+ private val previewController: KeyPreviewController? = null,
+ private val longKeyProvider: () -> String,
+ private val onTap: () -> BaseKeyMessage?,
+ private val quickPhraseMenuPopup: QuickPhraseMenuPopup? = null,
+ private val onEdit: (() -> Unit)? = null,
+) : BaseKeyTouchListener(context) {
+
+ private var isLongPressed = false
+ private val handler = Handler(Looper.getMainLooper())
+ private var anchorView: View? = null
+ private var longPressStartX = 0f
+
+ private val longPressRunnable = Runnable {
+ val view = anchorView ?: return@Runnable
+ isLongPressed = true
+ if (quickPhraseMenuPopup != null) {
+ previewController?.hide()
+ quickPhraseMenuPopup.show(view, longKeyProvider())
+ } else {
+ previewController?.update(view, longKeyProvider())
+ }
+ }
+
+ fun cancel() {
+ handler.removeCallbacks(longPressRunnable)
+ quickPhraseMenuPopup?.dismiss()
+ anchorView = null
+ isLongPressed = false
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
+ when (motionEvent.action) {
+ MotionEvent.ACTION_DOWN -> {
+ anchorView = view
+ isLongPressed = false
+ longPressStartX = motionEvent.x
+ (view as? TextView)?.text?.toString()?.let { previewController?.show(view, it) }
+ handler.postDelayed(longPressRunnable, config.longPressThresholdTime)
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (isLongPressed && quickPhraseMenuPopup != null) {
+ val dx = motionEvent.x - longPressStartX
+ quickPhraseMenuPopup.updateSelectionByDelta(dx, view.width)
+ }
+ }
+ MotionEvent.ACTION_UP -> {
+ handler.removeCallbacks(longPressRunnable)
+ previewController?.hide()
+ if (isLongPressed) {
+ isLongPressed = false
+ if (quickPhraseMenuPopup != null) {
+ when (quickPhraseMenuPopup.confirmAndDismiss()) {
+ QuickPhraseMenuPopup.MenuItem.PHRASE_PREVIEW -> {
+ sendKeyMessage(StringKeyMessage(longKeyProvider()))
+ }
+ QuickPhraseMenuPopup.MenuItem.EDIT -> onEdit?.invoke()
+ else -> Unit
+ }
+ } else {
+ sendKeyMessage(StringKeyMessage(longKeyProvider()))
+ }
+ } else {
+ onTap()?.let { sendKeyMessage(it) }
+ }
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ handler.removeCallbacks(longPressRunnable)
+ previewController?.hide()
+ quickPhraseMenuPopup?.dismiss()
+ isLongPressed = false
+ }
+ }
+ return super.onTouch(view, motionEvent)
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/SimpleKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/SimpleKeyTouchListener.kt
index 6072168..d2cbab5 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/SimpleKeyTouchListener.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/SimpleKeyTouchListener.kt
@@ -5,18 +5,28 @@ import android.content.Context
import android.view.MotionEvent
import android.view.View
import pe.aioo.openmoa.view.message.BaseKeyMessage
+import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.preview.KeyPreviewController
class SimpleKeyTouchListener(
context: Context,
private val key: BaseKeyMessage,
+ private val previewController: KeyPreviewController? = null,
) : BaseKeyTouchListener(context) {
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (motionEvent.action) {
+ MotionEvent.ACTION_DOWN -> {
+ (key as? StringKeyMessage)?.let { previewController?.show(view, it.key) }
+ }
MotionEvent.ACTION_UP -> {
+ previewController?.hide()
sendKeyMessage(key)
}
+ MotionEvent.ACTION_CANCEL -> {
+ previewController?.hide()
+ }
}
return super.onTouch(view, motionEvent)
}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/SpaceKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/SpaceKeyTouchListener.kt
new file mode 100644
index 0000000..c853233
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/SpaceKeyTouchListener.kt
@@ -0,0 +1,66 @@
+package pe.aioo.openmoa.view.keytouchlistener
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.Looper
+import android.view.MotionEvent
+import android.view.View
+import pe.aioo.openmoa.config.SpaceLongPressAction
+import pe.aioo.openmoa.settings.SettingsPreferences
+import pe.aioo.openmoa.view.message.SpecialKey
+import pe.aioo.openmoa.view.message.SpecialKeyMessage
+import pe.aioo.openmoa.view.message.StringKeyMessage
+
+class SpaceKeyTouchListener(private val context: Context) : BaseKeyTouchListener(context) {
+
+ private var longPressTriggered = false
+ private var defaultBackground: Drawable? = null
+ private var longPressRunnable: Runnable? = null
+ private val handler = Handler(Looper.getMainLooper())
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
+ when (motionEvent.action) {
+ MotionEvent.ACTION_DOWN -> {
+ longPressTriggered = false
+ defaultBackground = view.background
+ val action = SettingsPreferences.getSpaceLongPressAction(context)
+ val runnable = Runnable {
+ when (action) {
+ SpaceLongPressAction.IME_PICKER -> {
+ longPressTriggered = true
+ sendKeyMessage(SpecialKeyMessage(SpecialKey.SHOW_IME_PICKER))
+ }
+ SpaceLongPressAction.SWITCH_LANGUAGE -> {
+ longPressTriggered = true
+ sendKeyMessage(SpecialKeyMessage(SpecialKey.LANGUAGE))
+ }
+ SpaceLongPressAction.ARROW -> {
+ longPressTriggered = true
+ sendKeyMessage(SpecialKeyMessage(SpecialKey.ARROW))
+ }
+ SpaceLongPressAction.NONE -> Unit
+ }
+ }
+ longPressRunnable = runnable
+ handler.postDelayed(runnable, config.longPressThresholdTime)
+ }
+ MotionEvent.ACTION_UP -> {
+ longPressRunnable?.let { handler.removeCallbacks(it) }
+ if (longPressTriggered) {
+ longPressTriggered = false
+ view.background = defaultBackground
+ return true
+ }
+ sendKeyMessage(StringKeyMessage(" "))
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ longPressRunnable?.let { handler.removeCallbacks(it) }
+ longPressTriggered = false
+ }
+ }
+ return super.onTouch(view, motionEvent)
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/UserCharKeyTouchListener.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/UserCharKeyTouchListener.kt
new file mode 100644
index 0000000..c2ae78b
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/keytouchlistener/UserCharKeyTouchListener.kt
@@ -0,0 +1,60 @@
+package pe.aioo.openmoa.view.keytouchlistener
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.view.MotionEvent
+import android.view.View
+import pe.aioo.openmoa.quickphrase.UserCharKey
+import pe.aioo.openmoa.settings.PhraseEditActivity
+import pe.aioo.openmoa.view.message.StringKeyMessage
+import pe.aioo.openmoa.view.preview.KeyPreviewController
+
+class UserCharKeyTouchListener(
+ private val context: Context,
+ private val userCharKey: UserCharKey,
+ private val previewController: KeyPreviewController? = null,
+) : BaseKeyTouchListener(context) {
+
+ private var isLongPressed = false
+ private val handler = Handler(Looper.getMainLooper())
+ private var anchorView: View? = null
+
+ private val longPressRunnable = Runnable {
+ isLongPressed = true
+ previewController?.hide()
+ val intent = Intent(context, PhraseEditActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ putExtra(PhraseEditActivity.EXTRA_TYPE, PhraseEditActivity.TYPE_USER_CHAR)
+ putExtra(PhraseEditActivity.EXTRA_KEY, userCharKey.name)
+ }
+ context.startActivity(intent)
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
+ when (motionEvent.action) {
+ MotionEvent.ACTION_DOWN -> {
+ anchorView = view
+ isLongPressed = false
+ previewController?.show(view, userCharKey.getPhrase(context))
+ handler.postDelayed(longPressRunnable, config.longPressThresholdTime)
+ }
+ MotionEvent.ACTION_UP -> {
+ handler.removeCallbacks(longPressRunnable)
+ previewController?.hide()
+ if (!isLongPressed) {
+ sendKeyMessage(StringKeyMessage(userCharKey.getPhrase(context)))
+ }
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ handler.removeCallbacks(longPressRunnable)
+ previewController?.hide()
+ isLongPressed = false
+ }
+ }
+ return super.onTouch(view, motionEvent)
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/message/SpecialKey.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/message/SpecialKey.kt
index 5e6bc87..c86989d 100644
--- a/app/src/main/kotlin/pe/aioo/openmoa/view/message/SpecialKey.kt
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/message/SpecialKey.kt
@@ -18,6 +18,7 @@ enum class SpecialKey {
HANJA_NUMBER_PUNCTUATION,
HOME,
LANGUAGE,
+ OPEN_SETTINGS,
PASTE,
SELECT_ALL,
SELECT_ARROW_UP,
@@ -26,4 +27,5 @@ enum class SpecialKey {
SELECT_ARROW_DOWN,
SELECT_END,
SELECT_HOME,
+ SHOW_IME_PICKER,
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/preview/KeyPreviewController.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/preview/KeyPreviewController.kt
new file mode 100644
index 0000000..5df937c
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/preview/KeyPreviewController.kt
@@ -0,0 +1,36 @@
+package pe.aioo.openmoa.view.preview
+
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import pe.aioo.openmoa.config.KeyboardSkin
+
+class KeyPreviewController(private val enabled: () -> Boolean, private val skin: KeyboardSkin = KeyboardSkin.DEFAULT) {
+
+ private var popup: KeyPreviewPopup? = null
+ private val handler = Handler(Looper.getMainLooper())
+ private val hideRunnable = Runnable { popup?.hide() }
+
+ fun show(anchor: View, text: String) {
+ if (!enabled()) return
+ handler.removeCallbacks(hideRunnable)
+ val p = popup ?: KeyPreviewPopup(anchor.context, skin).also { popup = it }
+ p.show(anchor, text)
+ }
+
+ fun update(anchor: View, text: String) {
+ if (!enabled()) return
+ popup?.update(anchor, text)
+ }
+
+ fun hide() {
+ handler.removeCallbacks(hideRunnable)
+ handler.postDelayed(hideRunnable, 200L)
+ }
+
+ fun cancel() {
+ handler.removeCallbacks(hideRunnable)
+ popup?.hide()
+ }
+
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/preview/KeyPreviewPopup.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/preview/KeyPreviewPopup.kt
new file mode 100644
index 0000000..e3cc1d4
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/preview/KeyPreviewPopup.kt
@@ -0,0 +1,58 @@
+package pe.aioo.openmoa.view.preview
+
+import android.content.Context
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.PopupWindow
+import android.widget.TextView
+import pe.aioo.openmoa.R
+import pe.aioo.openmoa.config.KeyboardSkin
+import pe.aioo.openmoa.view.skin.SkinApplier
+
+class KeyPreviewPopup(context: Context, skin: KeyboardSkin) {
+
+ private val popupView: View = LayoutInflater.from(context).inflate(
+ R.layout.key_preview_popup, null
+ )
+ private val previewText: TextView = popupView.findViewById(R.id.previewText).apply {
+ setTextColor(SkinApplier.fgColor(context, skin))
+ background = SkinApplier.buildPreviewBackground(context, skin)
+ }
+ private val popup = PopupWindow(
+ popupView,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ false
+ ).apply {
+ isTouchable = false
+ }
+
+ fun show(anchor: View, text: String) {
+ if (!anchor.isAttachedToWindow) return
+ previewText.text = text
+ popupView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+ val xoff = anchor.width / 2 - popupView.measuredWidth / 2
+ val yoff = -(anchor.height + popupView.measuredHeight)
+ if (popup.isShowing) {
+ popup.update(anchor, xoff, yoff, -1, -1)
+ } else {
+ popup.showAsDropDown(anchor, xoff, yoff, Gravity.NO_GRAVITY)
+ }
+ }
+
+ fun update(anchor: View, text: String) {
+ if (!popup.isShowing) return
+ previewText.text = text
+ popupView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+ val xoff = anchor.width / 2 - popupView.measuredWidth / 2
+ val yoff = -(anchor.height + popupView.measuredHeight)
+ popup.update(anchor, xoff, yoff, -1, -1)
+ }
+
+ fun hide() {
+ if (popup.isShowing) popup.dismiss()
+ }
+
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/preview/QuickPhraseMenuPopup.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/preview/QuickPhraseMenuPopup.kt
new file mode 100644
index 0000000..8b199c2
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/preview/QuickPhraseMenuPopup.kt
@@ -0,0 +1,75 @@
+package pe.aioo.openmoa.view.preview
+
+import android.content.Context
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.PopupWindow
+import android.widget.TextView
+import pe.aioo.openmoa.R
+
+class QuickPhraseMenuPopup(context: Context) {
+
+ enum class MenuItem { PHRASE_PREVIEW, EDIT, CANCEL }
+
+ private val popupView: View = LayoutInflater.from(context).inflate(
+ R.layout.quick_phrase_menu_popup, null
+ )
+ private val phrasePreviewItem: TextView = popupView.findViewById(R.id.phrasePreviewItem)
+ private val editItem: TextView = popupView.findViewById(R.id.editItem)
+ private val cancelItem: TextView = popupView.findViewById(R.id.cancelItem)
+ private val popup = PopupWindow(
+ popupView,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ false
+ ).apply {
+ isTouchable = false
+ }
+ private var selectedItem = MenuItem.PHRASE_PREVIEW
+
+ val isShowing: Boolean get() = popup.isShowing
+
+ fun show(anchor: View, phrase: String) {
+ if (!anchor.isAttachedToWindow) return
+ phrasePreviewItem.text = if (phrase.length > 5) phrase.substring(0, 5) + "…" else phrase
+ selectedItem = MenuItem.PHRASE_PREVIEW
+ updateHighlight()
+ popupView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+ val xoff = anchor.width / 2 - popupView.measuredWidth / 2
+ val yoff = -(anchor.height + popupView.measuredHeight)
+ if (popup.isShowing) {
+ popup.update(anchor, xoff, yoff, -1, -1)
+ } else {
+ popup.showAsDropDown(anchor, xoff, yoff, Gravity.NO_GRAVITY)
+ }
+ }
+
+ fun updateSelectionByDelta(dx: Float, anchorWidth: Int) {
+ val editThreshold = anchorWidth * 0.25f
+ val cancelThreshold = anchorWidth * 0.75f
+ selectedItem = when {
+ dx < editThreshold -> MenuItem.PHRASE_PREVIEW
+ dx < cancelThreshold -> MenuItem.EDIT
+ else -> MenuItem.CANCEL
+ }
+ updateHighlight()
+ }
+
+ fun confirmAndDismiss(): MenuItem {
+ val item = selectedItem
+ dismiss()
+ return item
+ }
+
+ fun dismiss() {
+ if (popup.isShowing) popup.dismiss()
+ }
+
+ private fun updateHighlight() {
+ phrasePreviewItem.alpha = if (selectedItem == MenuItem.PHRASE_PREVIEW) 1.0f else 0.4f
+ editItem.alpha = if (selectedItem == MenuItem.EDIT) 1.0f else 0.4f
+ cancelItem.alpha = if (selectedItem == MenuItem.CANCEL) 1.0f else 0.4f
+ }
+}
diff --git a/app/src/main/kotlin/pe/aioo/openmoa/view/skin/SkinApplier.kt b/app/src/main/kotlin/pe/aioo/openmoa/view/skin/SkinApplier.kt
new file mode 100644
index 0000000..234cf2c
--- /dev/null
+++ b/app/src/main/kotlin/pe/aioo/openmoa/view/skin/SkinApplier.kt
@@ -0,0 +1,110 @@
+package pe.aioo.openmoa.view.skin
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.InsetDrawable
+import android.graphics.drawable.StateListDrawable
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import pe.aioo.openmoa.config.KeyboardSkin
+
+object SkinApplier {
+
+ fun apply(root: ViewGroup, skin: KeyboardSkin) {
+ val ctx = root.context
+ val density = ctx.resources.displayMetrics.density
+ val keyBgColor = ContextCompat.getColor(ctx, skin.keyBgColorRes)
+ val keyBgPressedColor = ContextCompat.getColor(ctx, skin.keyBgPressedColorRes)
+ val keyboardBgColor = ContextCompat.getColor(ctx, skin.keyboardBgColorRes)
+ val fgColor = ContextCompat.getColor(ctx, skin.keyFgColorRes)
+
+ root.setBackgroundColor(keyboardBgColor)
+ applyRecursive(root, keyBgColor, keyBgPressedColor, fgColor, density)
+ }
+
+ fun buildKeyDrawable(ctx: Context, skin: KeyboardSkin, pressed: Boolean): Drawable {
+ val density = ctx.resources.displayMetrics.density
+ val color = ContextCompat.getColor(
+ ctx,
+ if (pressed) skin.keyBgPressedColorRes else skin.keyBgColorRes,
+ )
+ return InsetDrawable(roundedDrawable(color, 4 * density), (2 * density).toInt())
+ }
+
+ fun fgColor(ctx: Context, skin: KeyboardSkin): Int =
+ ContextCompat.getColor(ctx, skin.keyFgColorRes)
+
+ fun fgAccentColor(ctx: Context, skin: KeyboardSkin): Int =
+ ContextCompat.getColor(ctx, skin.keyFgAccentColorRes)
+
+ fun keyboardBgColor(ctx: Context, skin: KeyboardSkin): Int =
+ ContextCompat.getColor(ctx, skin.keyboardBgColorRes)
+
+ fun buildPreviewBackground(ctx: Context, skin: KeyboardSkin): GradientDrawable {
+ val density = ctx.resources.displayMetrics.density
+ val color = ContextCompat.getColor(ctx, skin.keyBgColorRes)
+ val strokeColor = ContextCompat.getColor(ctx, skin.keyboardBgColorRes)
+ return GradientDrawable().apply {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = 8 * density
+ setColor(color)
+ setStroke((1 * density).toInt(), strokeColor)
+ }
+ }
+
+ private fun applyRecursive(
+ view: View,
+ keyBgColor: Int,
+ keyBgPressedColor: Int,
+ fgColor: Int,
+ density: Float,
+ ) {
+ val resName = if (view.id == View.NO_ID) "" else {
+ try {
+ view.resources.getResourceEntryName(view.id)
+ } catch (_: Exception) {
+ ""
+ }
+ }
+
+ if (resName.endsWith("Key")) {
+ applyKeyBackground(view, keyBgColor, keyBgPressedColor, density)
+ }
+
+ if (view is TextView) {
+ view.setTextColor(fgColor)
+ }
+
+ if (view is ViewGroup) {
+ for (i in 0 until view.childCount) {
+ applyRecursive(view.getChildAt(i), keyBgColor, keyBgPressedColor, fgColor, density)
+ }
+ }
+ }
+
+ private fun applyKeyBackground(view: View, bgColor: Int, pressedColor: Int, density: Float) {
+ val insetPx = (2 * density).toInt()
+ val cornerPx = 4 * density
+ val selector = StateListDrawable().apply {
+ addState(
+ intArrayOf(android.R.attr.state_pressed),
+ InsetDrawable(roundedDrawable(pressedColor, cornerPx), insetPx),
+ )
+ addState(
+ android.util.StateSet.WILD_CARD,
+ InsetDrawable(roundedDrawable(bgColor, cornerPx), insetPx),
+ )
+ }
+ view.background = selector
+ }
+
+ private fun roundedDrawable(color: Int, cornerPx: Float): GradientDrawable =
+ GradientDrawable().apply {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = cornerPx
+ setColor(color)
+ }
+}
diff --git a/app/src/main/res/drawable/key_preview_background.xml b/app/src/main/res/drawable/key_preview_background.xml
new file mode 100644
index 0000000..279866e
--- /dev/null
+++ b/app/src/main/res/drawable/key_preview_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_phrase_edit.xml b/app/src/main/res/layout/activity_phrase_edit.xml
new file mode 100644
index 0000000..0018346
--- /dev/null
+++ b/app/src/main/res/layout/activity_phrase_edit.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..c4c1117
--- /dev/null
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,912 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/arrow_view.xml b/app/src/main/res/layout/arrow_view.xml
index b638f45..ea11a04 100644
--- a/app/src/main/res/layout/arrow_view.xml
+++ b/app/src/main/res/layout/arrow_view.xml
@@ -211,11 +211,15 @@
-
+ app:layout_constraintRight_toLeftOf="@+id/hanjaNumberPunctuationKey">
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/number_view.xml b/app/src/main/res/layout/number_view.xml
index 3d3a57c..f5f46a0 100644
--- a/app/src/main/res/layout/number_view.xml
+++ b/app/src/main/res/layout/number_view.xml
@@ -197,11 +197,15 @@
-
+ app:layout_constraintRight_toLeftOf="@+id/hanjaNumberPunctuationKey">
+
+
+
+
+
+
-
+ android:layout_height="wrap_content">
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/open_moa_view.xml b/app/src/main/res/layout/open_moa_view.xml
index 4e10a19..950826f 100644
--- a/app/src/main/res/layout/open_moa_view.xml
+++ b/app/src/main/res/layout/open_moa_view.xml
@@ -335,13 +335,318 @@
app:layout_constraintLeft_toRightOf="@+id/euKey"
app:layout_constraintRight_toRightOf="parent" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+ app:layout_constraintRight_toLeftOf="@+id/spaceKey">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/punctuation_view.xml b/app/src/main/res/layout/punctuation_view.xml
index 41f7e43..f5fd798 100644
--- a/app/src/main/res/layout/punctuation_view.xml
+++ b/app/src/main/res/layout/punctuation_view.xml
@@ -378,11 +378,15 @@
-
+ app:layout_constraintRight_toLeftOf="@+id/hanjaNumberPunctuationKey">
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ app:layout_constraintRight_toLeftOf="@+id/hanjaNumberPunctuationKey">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors_skin.xml b/app/src/main/res/values/colors_skin.xml
new file mode 100644
index 0000000..280f81a
--- /dev/null
+++ b/app/src/main/res/values/colors_skin.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ #DDDDDD
+ #FFFFFF
+ #EEEEEE
+ #000000
+ #444444
+ #5E97EE
+
+
+ #1C1C1E
+ #3A3A3C
+ #48484A
+ #FFFFFF
+ #AEAEB2
+ #64D2FF
+
+
+ #000000
+ #1C1C1E
+ #2C2C2E
+ #FFFFFF
+ #8E8E93
+ #64D2FF
+
+
+ #1A237E
+ #283593
+ #3949AB
+ #FFFFFF
+ #90CAF9
+ #FFD740
+
+
+ #1B5E20
+ #2E7D32
+ #388E3C
+ #FFFFFF
+ #A5D6A7
+ #FFEB3B
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8ceb60c..d6963fe 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -12,8 +12,10 @@
,\n? . !
복사
전체 복사
+ 전체\n복사
자르기
전체 자르기
+ 전체\n자르기
지우기
ㄷ
.
@@ -27,7 +29,8 @@
5
4
ㄱ
- 漢\n12/기
+ 12/기
+ 漢
ㅎ
처음으로
ㅣ
@@ -50,6 +53,7 @@
ㄹ
▶
전체 선택
+ 전체\n선택
;
7
ㅅ
@@ -68,4 +72,99 @@
▲
0
+ !
+ ?
+ ㅣ·ㅡ
+
+ 설정
+ 오픈모아 통합키보드 설정
+ 한글설정
+ 한글 입력 방식 선택
+ 모아키
+ 양손 모아키(기본)
+
+ 키보드 옵션
+ 키패드 높이 조절
+ 높게
+ 보통(기본)
+ 낮게
+ 한손 입력모드 설정
+ 사용안함(기본)
+ 왼손 사용
+ 오른손 사용
+ 가운데 정렬
+ 길게 누름 시간 설정
+ 0.2초
+ 0.3초
+ 0.5초(기본)
+ 1.0초
+ 1.5초
+ 키 입력 미리보기
+ 스페이스 길게 누름 기능
+ 방향키 레이아웃 표시
+ 키보드 선택(기본)
+ 언어 전환
+ 없음
+ 키보드 스킨
+ 화이트(기본)
+ 다크그레이
+ 블랙
+ 블루
+ 그린
+ 자동 마침표 입력
+ 스페이스 바를 두 번 눌러 마침표를 자동으로 입력합니다
+ 영문 자동 대문자
+ 영문 키보드에서 문장 첫 글자를 자동으로 대문자로 입력합니다
+ 엔터 길게 누름 기능
+ 방향키 레이아웃 표시(기본)
+ 키보드 선택
+ 언어 전환
+ 없음
+
+ 한글 단축키 설정
+ ㅋ 단축키
+ ㅌ 단축키
+ ㅊ 단축키
+ ㅍ 단축키
+ ㅃ 단축키
+ ㅉ 단축키
+ ㄸ 단축키
+ ㄲ 단축키
+ ㅆ 단축키
+ 단축키를 입력하세요
+ 기본값
+ 영문 단축키 설정
+ z 단축키
+ x 단축키
+ c 단축키
+ v 단축키
+ b 단축키
+ n 단축키
+ m 단축키
+ 단축키를 입력하세요
+ 기본값
+저장
+ 취소
+
+ 설정 데이터 저장 및 초기화
+ 설정 데이터 내보내기
+ 사용자가 설정한 모아키 설정상태를 단말에 저장합니다.
+ 설정 데이터 가져오기
+ 이전에 사용자가 단말에 저장한 모아키 설정상태로 복구합니다.
+ 설정 데이터 초기화
+ 모아키 설정상태를 초기설치 상태로 초기화합니다.
+ 설정을 저장했습니다.
+ 저장에 실패했습니다.
+ 설정을 복구했습니다.
+ 불러오기에 실패했습니다. 저장된 파일이 없거나 손상되었습니다.
+ 모든 설정을 초기화하시겠습니까?
+ 설정을 초기화했습니다.
+
+ 수정
+ 취소
+ 단축키 수정
+ 단축키
+ 내용
+ 사용자 문자 변경
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..d716060
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
diff --git a/gradle.properties b/gradle.properties
index a2e90d8..543c5df 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,5 +21,5 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
-android.defaults.buildfeatures.buildconfig=true
+# android.defaults.buildfeatures.buildconfig=true (removed: deprecated in AGP 10.0)
android.nonFinalResIds=false
\ No newline at end of file