Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
548972e
feat: 설정 화면 및 모아키 모드 추가
verlane Apr 20, 2026
6a0ea6b
feat: 자음 키 입력 시 글자 프리뷰 팝업 추가
verlane Apr 20, 2026
64f43ef
feat: 상용구 입력 기능 추가 (ㅋ/ㅌ/ㅊ/ㅍ 키)
verlane Apr 20, 2026
6193a21
feat: 쌍자음 키(ㅃ/ㅉ/ㄸ/ㄲ/ㅆ)에 상용구 입력 기능 추가
verlane Apr 20, 2026
b3d1dce
fix: 상용구 팝업에서 내용 선택 시 왼쪽 스와이프 무시
verlane Apr 20, 2026
ee12f14
feat: ㅂㅈㄷㄱㅅ/ㅁㄴㅇㄹㅎ 롱키에 숫자(1~0) 입력 기능 추가
verlane Apr 20, 2026
a6bbc06
feat: 키 프레뷰 크기 1.5배 확대 및 문자/기호/영문 키 전체 프리뷰 적용
verlane Apr 20, 2026
2714484
feat: 롱키 시간 설정 항목 추가 (0.2~1.5초)
verlane Apr 20, 2026
c9a5375
fix: ㅋㅌㅊㅍ/ㅃㅉㄸㄲㅆ 탭 시 자음 입력 및 키 프리뷰 복원
verlane Apr 20, 2026
6f55479
feat: 스페이스바 롱키 기능 설정 및 자동 마침표 입력 추가
verlane Apr 20, 2026
eb59b40
feat: 키보드 스킨 시스템 추가 및 스킨 연동 개선
verlane Apr 20, 2026
3a2f753
fix: 롱키 시간 설정 즉시 반영 및 한손모드 이모티콘 크기 조정
verlane Apr 20, 2026
01b7485
feat: 상용구 수정 팝업 한글/영문 통합 및 UI 개선
verlane Apr 21, 2026
39b0c12
fix: 코드 리뷰 지적사항 수정
verlane Apr 21, 2026
87a33f6
fix: Handler 누수 방지 및 importSettings 타입 안전성 개선
verlane Apr 21, 2026
058d9db
feat: 한글 자판 특수키 사용자 문자 변경 기능 추가
verlane Apr 21, 2026
7f0584d
feat: 영문 자판 상용구 변경 시 키 힌트 즉시 반영
verlane Apr 21, 2026
987f4d9
feat: 방향키 키보드 한손모드에서 전체 복사/선택/자르기 2줄 표시
verlane Apr 21, 2026
7786ee7
feat: 설정창 기능 추가 및 UI 정리
verlane Apr 21, 2026
ce9c051
feat: 전체 키보드에 엔터 롱키 및 한/영 설정 롱키 확장
verlane Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
viewBinding {
enabled = true
buildFeatures {
viewBinding true
buildConfig true
}
}

Expand Down
10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@

</service>

<activity
android:name="pe.aioo.openmoa.settings.SettingsActivity"
android:exported="false"
android:theme="@style/Theme.OpenMoa.Settings" />

<activity
android:name="pe.aioo.openmoa.settings.PhraseEditActivity"
android:exported="false"
android:theme="@style/Theme.OpenMoa.QuickPhraseEdit" />

</application>

</manifest>
134 changes: 108 additions & 26 deletions app/src/main/kotlin/pe/aioo/openmoa/OpenMoaIME.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,24 +35,32 @@ 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

class OpenMoaIME : InputMethodService(), KoinComponent {

private lateinit var binding: OpenMoaImeBinding
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var keyboardViews: Map<IMEMode, View>
private lateinit var keyboardViews: MutableMap<IMEMode, View>
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()
Expand Down Expand Up @@ -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 -> {
Expand All @@ -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
}
}
}
}
Expand All @@ -353,14 +389,18 @@ 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)
}
imeMode = mode
}

private fun setShiftAutomatically() {
if (!config.autoCapitalizeEnglish) return
keyboardViews[imeMode]?.let { view ->
if (view is QuertyView) {
currentInputConnection?.let { inputConnection ->
Expand Down Expand Up @@ -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<IMEMode, View> {
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,
Expand All @@ -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(
Expand Down Expand Up @@ -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)
}
Expand Down
22 changes: 15 additions & 7 deletions app/src/main/kotlin/pe/aioo/openmoa/config/Config.kt
Original file line number Diff line number Diff line change
@@ -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,
)
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)
}
15 changes: 15 additions & 0 deletions app/src/main/kotlin/pe/aioo/openmoa/config/EnterLongPressAction.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
13 changes: 13 additions & 0 deletions app/src/main/kotlin/pe/aioo/openmoa/config/HangulInputMode.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
68 changes: 68 additions & 0 deletions app/src/main/kotlin/pe/aioo/openmoa/config/KeyboardSkin.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
14 changes: 14 additions & 0 deletions app/src/main/kotlin/pe/aioo/openmoa/config/KeypadHeight.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading