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 @@ + + + + + + + + + + + + + + + + + + + + + + + +