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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ dependencies {

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.emoji2.emojipicker)
implementation(libs.material)
implementation(libs.guava)
implementation(libs.protobuf.java)
Expand Down
42 changes: 42 additions & 0 deletions app/src/main/java/ee/oyatl/ime/fusion/DimensionUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ee.oyatl.ime.fusion

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.util.TypedValue
import androidx.preference.PreferenceManager
import kotlin.math.roundToInt

object DimensionUtil {
fun getOrientationSuffix(context: Context): String {
val landscape = context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
val suffix = if(landscape) "_landscape" else "_portrait"
return suffix
}

fun getOrientationInteger(context: Context, key: String): Float {
val preference = PreferenceManager.getDefaultSharedPreferences(context)
val suffix = getOrientationSuffix(context)
@SuppressLint("DiscouragedApi")
val defaultId = context.resources.getIdentifier("${key}${suffix}_default", "integer", context.packageName)
val defaultValue = context.resources.getInteger(defaultId).toFloat()
val value = preference.getFloat("${key}${suffix}", defaultValue)
return value
}

fun getOrientationBoolean(context: Context, key: String): Boolean {
val preference = PreferenceManager.getDefaultSharedPreferences(context)
val suffix = getOrientationSuffix(context)
@SuppressLint("DiscouragedApi")
val defaultId = context.resources.getIdentifier("${key}${suffix}_default", "bool", context.packageName)
val defaultValue = context.resources.getBoolean(defaultId)
val value = preference.getBoolean("${key}${suffix}", defaultValue)
return value
}

fun getKeyboardHeight(context: Context): Int {
val rowHeightDIP = getOrientationInteger(context, "keyboard_height")
val height = (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rowHeightDIP, context.resources.displayMetrics) * 4).roundToInt()
return height
}
}
8 changes: 8 additions & 0 deletions app/src/main/java/ee/oyatl/ime/fusion/KeyEventUtil.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ee.oyatl.ime.fusion

import android.os.Build
import android.os.SystemClock
import android.view.KeyCharacterMap
import android.view.KeyEvent
Expand Down Expand Up @@ -115,4 +116,11 @@ class KeyEventUtil(
}
}

fun deleteSurroundingText(beforeLength: Int, afterLength: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
currentInputConnection.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
} else {
currentInputConnection.deleteSurroundingText(beforeLength, afterLength)
}
}
}
36 changes: 4 additions & 32 deletions app/src/main/java/ee/oyatl/ime/fusion/mode/CommonIMEMode.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package ee.oyatl.ime.fusion.mode

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.util.TypedValue
import android.view.KeyCharacterMap
import android.view.KeyEvent
Expand All @@ -12,6 +10,7 @@ import android.view.inputmethod.InputConnection
import androidx.preference.PreferenceManager
import ee.oyatl.ime.candidate.CandidateView
import ee.oyatl.ime.candidate.ScrollingCandidateView
import ee.oyatl.ime.fusion.DimensionUtil
import ee.oyatl.ime.fusion.Feature
import ee.oyatl.ime.fusion.FlickAction
import ee.oyatl.ime.fusion.KeyEventUtil
Expand Down Expand Up @@ -155,41 +154,14 @@ abstract class CommonIMEMode(
submitCandidates(emptyList())
}

private fun getOrientationSuffix(context: Context): String {
val landscape = context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
val suffix = if(landscape) "_landscape" else "_portrait"
return suffix
}

private fun getOrientationInteger(context: Context, key: String): Float {
val preference = PreferenceManager.getDefaultSharedPreferences(context)
val suffix = getOrientationSuffix(context)
@SuppressLint("DiscouragedApi")
val defaultId = context.resources.getIdentifier("${key}${suffix}_default", "integer", context.packageName)
val defaultValue = context.resources.getInteger(defaultId).toFloat()
val value = preference.getFloat("${key}${suffix}", defaultValue)
return value
}

private fun getOrientationBoolean(context: Context, key: String): Boolean {
val preference = PreferenceManager.getDefaultSharedPreferences(context)
val suffix = getOrientationSuffix(context)
@SuppressLint("DiscouragedApi")
val defaultId = context.resources.getIdentifier("${key}${suffix}_default", "bool", context.packageName)
val defaultValue = context.resources.getBoolean(defaultId)
val value = preference.getBoolean("${key}${suffix}", defaultValue)
return value
}

override fun createInputView(context: Context): View {
val preference = PreferenceManager.getDefaultSharedPreferences(context)

val defaultScreenMode = context.resources.getString(R.string.screen_mode_default)
val screenMode = KeyboardState.ScreenMode.valueOf(preference.getString("screen_mode", null) ?: defaultScreenMode)
val rowHeightDIP = getOrientationInteger(context, "keyboard_height")
val height = (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, rowHeightDIP, context.resources.displayMetrics) * 4).roundToInt()
val split = Feature.SplitKeyboard.availableInCurrentVersion && getOrientationBoolean(context, "split_keyboard")
val splitRatio = if(split) getOrientationInteger(context, "split_ratio") else 0f
val height = DimensionUtil.getKeyboardHeight(context)
val split = Feature.SplitKeyboard.availableInCurrentVersion && DimensionUtil.getOrientationBoolean(context, "split_keyboard")
val splitRatio = if(split) DimensionUtil.getOrientationInteger(context, "split_ratio") else 0f
val splitWidthDIP = context.resources.configuration.screenWidthDp / 100f * splitRatio
val splitWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, splitWidthDIP, context.resources.displayMetrics).roundToInt()
val showPreviewPopup = preference.getBoolean("preview_popup", true)
Expand Down
136 changes: 136 additions & 0 deletions app/src/main/java/ee/oyatl/ime/fusion/mode/EmojiIMEMode.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package ee.oyatl.ime.fusion.mode

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import androidx.core.util.Consumer
import androidx.emoji2.emojipicker.EmojiViewItem
import ee.oyatl.ime.candidate.CandidateView
import ee.oyatl.ime.candidate.ScrollingCandidateView
import ee.oyatl.ime.fusion.DimensionUtil
import ee.oyatl.ime.fusion.KeyEventUtil
import ee.oyatl.ime.fusion.R
import ee.oyatl.ime.fusion.databinding.EmojiImeModeBinding

class EmojiIMEMode(
private val listener: IMEMode.Listener
): IMEMode, Consumer<EmojiViewItem>, CandidateView.Listener {

private var inputView: View? = null
private var candidateView: CandidateView? = null
private var util: KeyEventUtil? = null
private val currentInputConnection: InputConnection? get() = util?.currentInputConnection

override suspend fun onLoad(context: Context) {
}

override fun onStart(
inputConnection: InputConnection,
editorInfo: EditorInfo
) {
util = KeyEventUtil(inputConnection, editorInfo)
currentInputConnection?.finishComposingText()
}

override fun onFinish() {
currentInputConnection?.finishComposingText()
}

override fun createInputView(context: Context): View {
val height = DimensionUtil.getKeyboardHeight(context)
val binding = EmojiImeModeBinding.inflate(LayoutInflater.from(context))
binding.emojiPickerView.setOnEmojiPickedListener(this)
binding.buttonDelete.setOnClickListener {
onDelete()
}
binding.root.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
height
)
this.inputView = binding.root
return binding.root
}

override fun createCandidateView(context: Context): View {
candidateView = ScrollingCandidateView(context, null).apply {
listener = this@EmojiIMEMode
}
return candidateView as View
}

override fun getInputView(): View? {
return inputView
}

override fun accept(value: EmojiViewItem) {
currentInputConnection?.finishComposingText()
currentInputConnection?.setComposingText(value.emoji, 1)
val candidates = value.variants.map { Candidate(it) }
submitCandidates(candidates)
}

private fun submitCandidates(candidates: List<Candidate>) {
candidateView?.submitList(candidates)
listener.onCandidateViewVisibilityChange(candidates.isNotEmpty())
}

override fun onCandidateSelected(candidate: CandidateView.Candidate) {
currentInputConnection?.setComposingText(candidate.text, 1)
currentInputConnection?.finishComposingText()
submitCandidates(emptyList())
}

private fun onDelete() {
currentInputConnection?.finishComposingText()
util?.deleteSurroundingText(1, 0)
}

override fun onKeyDown(keyCode: Int, metaState: Int) = Unit

override fun onKeyUp(keyCode: Int, metaState: Int) = Unit

override fun updateSelection(
oldSelStart: Int,
oldSelEnd: Int,
newSelStart: Int,
newSelEnd: Int,
candidatesStart: Int,
candidatesEnd: Int
) = Unit

data class Candidate(
override val text: CharSequence
): CandidateView.Candidate

class Params: IMEMode.Params {
override val type: String = TYPE

override fun create(listener: IMEMode.Listener): IMEMode {
return EmojiIMEMode(listener)
}

override fun getLabel(context: Context): String {
return context.getString(R.string.input_mode_emoji)
}

override fun getShortLabel(
context: Context,
params: List<IMEMode.Params>
): String {
return "\uD83D\uDE00"
}

companion object {
fun parse(map: Map<String, String>): Params {
return Params()
}
}
}

companion object {
const val TYPE: String = "emoji"
}
}
1 change: 1 addition & 0 deletions app/src/main/java/ee/oyatl/ime/fusion/mode/IMEMode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface IMEMode {
ZhuyinIMEMode.TYPE -> ZhuyinIMEMode.Params.parse(map)
CangjieIMEMode.TYPE -> CangjieIMEMode.Params.parse(map)
VietIMEMode.TYPE -> VietIMEMode.Params.parse(map)
EmojiIMEMode.TYPE -> EmojiIMEMode.Params.parse(map)
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ abstract class KoreanIMEMode(
} else if(wordComposer.composingText.isNotEmpty()) {
wordComposer.delete(1)
} else {
currentInputConnection?.deleteSurroundingText(1, 0)
util?.deleteSurroundingText(1, 0)
}
renderInputView()
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/ee/oyatl/ime/fusion/mode/PinyinIMEMode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ class PinyinIMEMode(
if (SIMULATE_KEY_DELETE) {
simulateKeyEventDownUp(keyCode)
} else {
currentInputConnection?.deleteSurroundingText(1, 0)
util?.deleteSurroundingText(1, 0)
}
return true
}
Expand Down Expand Up @@ -360,7 +360,7 @@ class PinyinIMEMode(
if (SIMULATE_KEY_DELETE) {
simulateKeyEventDownUp(keyCode)
} else {
currentInputConnection?.deleteSurroundingText(1, 0)
util?.deleteSurroundingText(1, 0)
}
return true
} else if (keyCode == KeyEvent.KEYCODE_ENTER) {
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/java/ee/oyatl/ime/fusion/mode/ZhuyinIMEMode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,13 @@ class ZhuyinIMEMode(
}

private fun handleBackspace() {
val ic = currentInputConnection ?: return
var deleteChar = false
if (wordComposer.typedWord?.isNotEmpty() == true) {
val length: Int = wordComposer.typedWord.length
if (length > 0) {
wordComposer.deleteLast()
} else {
ic.deleteSurroundingText(1, 0)
util?.deleteSurroundingText(1, 0)
}
} else {
deleteChar = true
Expand Down
35 changes: 35 additions & 0 deletions app/src/main/res/layout/emoji_ime_mode.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:theme="@style/Theme.FusionIME"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.emoji2.emojipicker.EmojiPickerView
android:id="@+id/emoji_picker_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:elevation="0dp">

<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/button_delete"
android:src="@drawable/keyic_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|end"
android:layout_margin="16dp" />
</FrameLayout>
</com.google.android.material.appbar.AppBarLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>
2 changes: 2 additions & 0 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<item>@string/input_mode_zhuyin</item>
<item>@string/input_mode_cangjie</item>
<item>@string/input_mode_viet</item>
<item>@string/input_mode_emoji</item>
</string-array>

<string-array name="settings_input_mode_type_values">
Expand All @@ -55,6 +56,7 @@
<item>zhuyin</item>
<item>cangjie</item>
<item>viet</item>
<item>emoji</item>
</string-array>

<string-array name="settings_input_mode_latin_locale_entries">
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
<string name="input_mode_zhuyin">Bopomofo (Traditional Chinese)</string>
<string name="input_mode_cangjie">Cangjie</string>
<string name="input_mode_viet">Vietnamese</string>
<string name="input_mode_emoji">Emoji</string>

<string name="latin_locale_de">German</string>
<string name="latin_locale_en">English</string>
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ legacySupportV4 = "1.0.0"
annotation = "1.10.0"
preference = "1.2.1"
desugarJdkLibs = "2.1.5"
emoji2Emojipicker = "1.5.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
Expand All @@ -33,6 +34,7 @@ androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support
androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
desugar-jdk-libs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugarJdkLibs" }
androidx-emoji2-emojipicker = { group = "androidx.emoji2", name = "emoji2-emojipicker", version.ref = "emoji2Emojipicker" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
Expand Down