This commit is contained in:
polwex 2024-11-24 19:19:17 +07:00
parent de9592764a
commit c06aff7133
6 changed files with 286 additions and 140 deletions

View File

@ -3,7 +3,20 @@
<component name="deploymentTargetDropDown"> <component name="deploymentTargetDropDown">
<value> <value>
<entry key="app"> <entry key="app">
<State /> <State>
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="SERIAL_NUMBER" />
<value value="8cb784d4" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2024-11-24T11:06:07.206200396Z" />
</State>
</entry> </entry>
</value> </value>
</component> </component>

View File

@ -1,8 +1,14 @@
package com.sortug.thaiinput package com.sortug.thaiinput
import android.util.Log
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
typealias InputToolsResponse = List<Any> sealed interface InputToolsResponse{
data class Success(
val results: List<TransliterationResult>
): InputToolsResponse
data object FailedToParse: InputToolsResponse
}
data class ParsedResponse( data class ParsedResponse(
val status: String, val status: String,
@ -14,3 +20,22 @@ data class TransliterationResult(
val options: List<String>, val options: List<String>,
val metadata: Map<String, Any>? val metadata: Map<String, Any>?
) )
public fun parseResponse(response: List<Any>): InputToolsResponse {
Log.d("THAI_IME", "parsing API response: $response")
return when (response[0] as String) {
"SUCCESS" -> {
val dataList = response[1] as List<*>
val results = dataList.map { item ->
val itemList = item as List<*>
TransliterationResult(
wholeString = itemList[0] as String,
options = (itemList[1] as List<*>).filterIsInstance<String>(),
metadata = if (itemList.size > 3) itemList[3] as? Map<String, Any> else null
)
}
InputToolsResponse.Success(results)
}
"FAILED_TO_PARSE_REQUEST_BODY" -> InputToolsResponse.FailedToParse
else -> throw IllegalStateException("Unknown response status: ${response[0]}")
}
}

View File

@ -19,7 +19,7 @@ interface GoogleInputToolsApiService {
@Query("ie") ie: String = "utf-8", @Query("ie") ie: String = "utf-8",
@Query("oe") oe: String = "utf-8", @Query("oe") oe: String = "utf-8",
@Query("app") app: String = "demopage" @Query("app") app: String = "demopage"
): InputToolsResponse ): List<Any>
@GET("request") @GET("request")
suspend fun getRawInputSuggestions( suspend fun getRawInputSuggestions(
@Query("text") text: String, @Query("text") text: String,

View File

@ -1,8 +1,9 @@
package com.sortug.thaiinput package com.sortug.thaiinput
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface
import android.inputmethodservice.InputMethodService import android.inputmethodservice.InputMethodService
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputConnection import android.view.inputmethod.InputConnection
import kotlinx.coroutines.* import kotlinx.coroutines.*
import android.util.Log import android.util.Log
@ -21,6 +22,7 @@ class MyKeyboardService : InputMethodService() {
updateCurrentInputDisplay(newValue) updateCurrentInputDisplay(newValue)
} }
private val gson = Gson() private val gson = Gson()
private lateinit var keyboardContainer: LinearLayout
private lateinit var suggestionsContainer: LinearLayout private lateinit var suggestionsContainer: LinearLayout
private lateinit var currentInputDisplay: TextView private lateinit var currentInputDisplay: TextView
@ -28,87 +30,97 @@ class MyKeyboardService : InputMethodService() {
private fun updateCurrentInputDisplay(text: String) { private fun updateCurrentInputDisplay(text: String) {
currentInputDisplay.text = text currentInputDisplay.text = text
} }
private val syms = listOf(
"1234567890".toList(),
"!@#$%^&*()".toList(),
"\".:?/-=+".toList()
)
private val letters = listOf(
"QWERTYUIOP".toList(),
"ASDFGHJKL".toList(),
"ZXCVBNM".toList()
)
private val fonts = listOf(
Typeface.NORMAL,
// Typeface.createFromAsset(assets, "fonts/round.ttf")
)
private var currentFontIndex = 0
override fun onCreateInputView(): View { override fun onCreateInputView(): View {
val inputView = layoutInflater.inflate(R.layout.keyboard_layout, null) as LinearLayout Log.d("THAI_IME", "onCreateInputView called") // Add this debug log
// options box val inputView = layoutInflater.inflate(R.layout.keyboard_layout, null)
val scrollView = HorizontalScrollView(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
}
// Create suggestions container // Initialize views using findViewById
suggestionsContainer = LinearLayout(this).apply { currentInputDisplay = inputView.findViewById(R.id.current_input_display)
orientation = LinearLayout.HORIZONTAL suggestionsContainer = inputView.findViewById(R.id.suggestions_container)
layoutParams = LinearLayout.LayoutParams( keyboardContainer = inputView.findViewById(R.id.keyboard_container)
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
}
currentInputDisplay = TextView(this).apply{
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
setMargins(10, 10, 10, 10)
}
setTextColor(Color.BLACK)
setBackgroundColor(Color.LTGRAY)
textSize = 18f
setPadding(10, 10, 10, 10)
}
inputView.addView(currentInputDisplay) // Set up the keyboard
scrollView.addView(suggestionsContainer) setKeyboard(letters)
inputView.addView(scrollView)
setupKeyboardKeys(inputView) Log.d("THAI_IME", "Keyboard container child count: ${keyboardContainer.childCount}")
return inputView return inputView
} }
private fun setupKeyboardKeys(keyboardView: LinearLayout){
val keys = listOf(
"qwertyuiop".toList(),
"asdfghjkl".toList(),
"zkcvbnm".toList()
)
var rowLayout: LinearLayout? = null
keys.forEachIndexed { rowIndex, row ->
val rowLayout = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
}
if (rowIndex == 2) { // Third row
// Add Shift key
val shiftButton = createSpecialButton("") { onShiftPressed() }
rowLayout.addView(shiftButton)
}
private fun setSpecialKey(row: LinearLayout, txt: String, onClick: (String) -> Unit, width: Float = 1.5f){
val button = Button(this).apply{
text = txt
layoutParams = LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.WRAP_CONTENT,
width
).apply {
setMargins(4, 4, 4, 4)
}
setTextColor(Color.LTGRAY)
setBackgroundColor(Color.BLACK)
setOnClickListener {onClick(txt)}
}
row.addView(button)
}
private fun setKeyboard(rows: List<List<Char>>) {
rows.forEachIndexed { rowIndex, row ->
Log.d("THAI_IME", "setting row :$row $rowIndex")
val rowLayout = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
}
if (rowIndex == 2){setSpecialKey(rowLayout, "1!", ::handleShift)}
// set keys proper
row.forEach { char -> row.forEach { char ->
Log.d("THAI_IME", "setting char :$char")
val button = Button(this).apply { val button = Button(this).apply {
text = char.toString() text = char.toString()
layoutParams = LinearLayout.LayoutParams( layoutParams = LinearLayout.LayoutParams(
0, 0,
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT,
1f 1f
) ).apply{
setOnClickListener { onKeyPressed(if (isShiftActive) char.toString() else char.lowercaseChar().toString()) } setMargins(4, 4, 4, 4)
}
setTextColor(Color.LTGRAY)
textSize = 18f
setBackgroundColor(Color.BLACK)
setOnClickListener {
if (isShiftActive) {
noApiCall(char.toString())
}else{
onKeyPressed(char.lowercaseChar().toString())
}
}
} }
rowLayout.addView(button) rowLayout.addView(button)
} }
if (rowIndex == 2) { // Third row
// Add Backspace key
val backspaceButton = createSpecialButton("") { onBackspace() }
rowLayout.addView(backspaceButton)
}
keyboardView.addView(rowLayout) if (rowIndex == 2) {setSpecialKey(rowLayout,"", ::onBackspace)}
keyboardContainer.addView(rowLayout)
} }
// Add a row for space
val spaceRow = LinearLayout(this).apply { val spaceRow = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams( layoutParams = LinearLayout.LayoutParams(
@ -116,21 +128,10 @@ class MyKeyboardService : InputMethodService() {
LinearLayout.LayoutParams.WRAP_CONTENT LinearLayout.LayoutParams.WRAP_CONTENT
) )
} }
setSpecialKey(spaceRow, "F", ::changeFont, 0.4f)
val spaceButton = Button(this).apply { setSpecialKey(spaceRow, "", {_ -> noApiCall(" ")}, 1f)
text = "Space" setSpecialKey(spaceRow, "", {_ -> noApiCall("\n")}, 0.4f)
layoutParams = LinearLayout.LayoutParams( keyboardContainer.addView(spaceRow)
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
setOnClickListener { onSpace() }
}
spaceRow.addView(spaceButton)
keyboardView.addView(spaceRow)
Log.d("MyKeyboardService", "Keyboard keys set up")
} }
private fun createSpecialButton(text: String, onClickListener: () -> Unit): Button { private fun createSpecialButton(text: String, onClickListener: () -> Unit): Button {
@ -145,35 +146,15 @@ class MyKeyboardService : InputMethodService() {
} }
} }
private fun onShiftPressed() {
isShiftActive = !isShiftActive
Log.d("MyKeyboardService", "Shift pressed. Active: $isShiftActive")
// You might want to update the appearance of the Shift key here
}
private fun onBackspace() { private fun onBackspace(_thing: String) {
Log.d("MyKeyboardService", "onBackspace called") Log.d("MyKeyboardService", "onBackspace called")
val inputConnection = currentInputConnection ?: return val inputConnection = currentInputConnection ?: return
// Only make API request if we still have text // Only make API request if we still have text
if (currentInput.isNotEmpty()) { if (currentInput.isNotEmpty()) {
currentInput = currentInput.substring(0, currentInput.length - 1) currentInput = currentInput.substring(0, currentInput.length - 1)
coroutineScope.launch { apiCall()
try {
val response = withContext(Dispatchers.IO) {
RetrofitClient.googleInputToolsApiService.getInputSuggestions(currentInput)
}
val parsedResponse = parseResponse(response)
val options = parsedResponse.results.flatMap { it.options }
Log.d("MyKeyboardService", "Parsed options after backspace: $options")
displaySuggestions(options)
} catch (e: Exception) {
Log.e("MyKeyboardService", "Error in API call after backspace", e)
displaySuggestions(listOf("Error: ${e.message}"))
}
}
} else { } else {
inputConnection.deleteSurroundingText(1, 0) inputConnection.deleteSurroundingText(1, 0)
// Clear suggestions if we've deleted all text // Clear suggestions if we've deleted all text
@ -181,64 +162,56 @@ class MyKeyboardService : InputMethodService() {
} }
Log.d("MyKeyboardService", "Current input after backspace: $currentInput") Log.d("MyKeyboardService", "Current input after backspace: $currentInput")
} }
private fun onSpace(){
val inputConnection = currentInputConnection ?: return
inputConnection.commitText(" ", 1)
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
coroutineScope.cancel() coroutineScope.cancel()
} }
private fun onKeyPressed(text: String) { private fun onKeyPressed(text: String) {
Log.d("MyKeyboardService", "onKeyPressed called with text: $text") Log.d("MyKeyboardService", "onKeyPressed called with text: $text")
val inputConnection: InputConnection = currentInputConnection ?: return
currentInput += text currentInput += text
Log.d("MyKeyboardService", "Current input: $currentInput") Log.d("MyKeyboardService", "Current input: $currentInput")
Log.d("MyKeyboardService", "Key pressed: $text") Log.d("MyKeyboardService", "Key pressed: $text")
apiCall()
}
private fun noApiCall(text: String){
val inputConnection: InputConnection = currentInputConnection ?: return
inputConnection.commitText(text, 1)
}
private fun apiCall(){
if (currentInput.isNotEmpty()){
coroutineScope.launch { coroutineScope.launch {
try { try {
// val rawResponse = withContext(Dispatchers.IO) { Log.d("THAI_IME", "API request input: $currentInput")
// RetrofitClient.googleInputToolsApiService.getRawInputSuggestions(currentInput.toString()) val response = withContext(Dispatchers.IO) {
// } RetrofitClient.googleInputToolsApiService.getInputSuggestions(currentInput)
// val rawJson = rawResponse.body()?.string() ?: "Empty response" }
// Log.d("MyKeyboardService", "Raw API Response: $rawJson") Log.d("THAI_IME", "raw API response: $response")
when (val apiResponse = parseResponse(response)){
val result = withContext(Dispatchers.IO) { is InputToolsResponse.Success -> {
RetrofitClient.googleInputToolsApiService.getInputSuggestions(currentInput.toString()) val options = apiResponse.results.flatMap{it.options}
Log.d("MyKeyboardService", "Parsed options after backspace: $options")
displaySuggestions((options))
}
is InputToolsResponse.FailedToParse -> {
Log.e("THAI_IME", "Failed to parse request body: $currentInput")
}
} }
val parsedResponse = parseResponse(result)
val options = parsedResponse.results.flatMap { it.options }
displaySuggestions(options)
} catch (e: Exception) { } catch (e: Exception) {
Log.e("MyKeyboardService", "Unhandled error in HTTP request", e) Log.e("MyKeyboardService", "Error in API call after backspace", e)
displaySuggestions(listOf("Error: ${e.message}"))
// Handle exceptions
} }
} }
}
private fun parseResponse(response: InputToolsResponse): ParsedResponse {
val status = response[0] as String
val dataList = response[1] as List<*>
val results = dataList.map { item ->
val itemList = item as List<*>
TransliterationResult(
wholeString = itemList[0] as String,
options = (itemList[1] as List<*>).filterIsInstance<String>(),
metadata = if (itemList.size > 3) itemList[3] as? Map<String, Any> else null
)
} }
return ParsedResponse(status, results)
} }
private fun displaySuggestions(options: List<String>){ private fun displaySuggestions(options: List<String>){
suggestionsContainer.removeAllViews() suggestionsContainer.removeAllViews()
options.forEach{ option -> options.forEach{ option ->
val button = Button(this).apply{ val button = Button(this).apply{
text = option text = option
textSize = 20f
// typeface = fonts[currentFontIndex] // Set the current font
setOnClickListener { onSuggestionClicked(option) } setOnClickListener { onSuggestionClicked(option) }
layoutParams = LinearLayout.LayoutParams( layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT,
@ -261,7 +234,88 @@ class MyKeyboardService : InputMethodService() {
suggestionsContainer.removeAllViews() suggestionsContainer.removeAllViews()
} }
private fun handleShift(_thing: String){
Log.d("THAI_IME", "shift pressed")
isShiftActive = !isShiftActive
val keys = if (isShiftActive) syms else letters
keyboardContainer.removeAllViews()
setKeyboard(keys)
}
private fun changeFont(_thing: String){
currentFontIndex = (currentFontIndex + 1) % fonts.size
Log.d("THAI_IME", "🔤 Changed font to index: $currentFontIndex")
// Refresh suggestions with new font
redrawCurrentSuggestions()
}
private fun redrawCurrentSuggestions() {
val currentButtons = mutableListOf<String>()
for (i in 0 until suggestionsContainer.childCount) {
val button = suggestionsContainer.getChildAt(i) as? Button
button?.text?.toString()?.let { currentButtons.add(it) }
}
displaySuggestions(currentButtons)
}
// TODO: Add methods for handling shift and number toggle // TODO: Add methods for handling shift and number toggle
} }
object ViewDebugUtils {
fun getViewHierarchy(view: View?, level: Int = 0): String {
if (view == null) return "null"
val indent = " ".repeat(level)
val builder = StringBuilder()
// Basic view information
builder.append("$indent${view.javaClass.simpleName} {")
builder.append("\n${indent} id: ${getViewId(view)}")
builder.append("\n${indent} width: ${view.width}, height: ${view.height}")
builder.append("\n${indent} visibility: ${getVisibilityString(view.visibility)}")
// Layout parameters
val params = view.layoutParams
if (params != null) {
builder.append("\n${indent} layout_width: ${getLayoutParamSize(params.width)}")
builder.append("\n${indent} layout_height: ${getLayoutParamSize(params.height)}")
}
// For ViewGroups, recursively print children
if (view is ViewGroup) {
builder.append("\n${indent} children: [")
for (i in 0 until view.childCount) {
builder.append("\n${getViewHierarchy(view.getChildAt(i), level + 2)}")
}
if (view.childCount > 0) builder.append("\n$indent ]")
else builder.append("]")
}
builder.append("\n$indent}")
return builder.toString()
}
private fun getViewId(view: View): String {
val id = view.id
return when {
id == View.NO_ID -> "NO_ID"
else -> try {
view.resources.getResourceEntryName(id)
} catch (e: Exception) {
id.toString()
}
}
}
private fun getVisibilityString(visibility: Int): String = when (visibility) {
View.VISIBLE -> "VISIBLE"
View.INVISIBLE -> "INVISIBLE"
View.GONE -> "GONE"
else -> visibility.toString()
}
private fun getLayoutParamSize(size: Int): String = when (size) {
ViewGroup.LayoutParams.MATCH_PARENT -> "MATCH_PARENT"
ViewGroup.LayoutParams.WRAP_CONTENT -> "WRAP_CONTENT"
else -> size.toString()
}
}

View File

@ -1,8 +1,54 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keyboard_layout" android:id="@+id/keyboard_main_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
> android:background="@android:color/darker_gray">
<!-- Top Bar -->
<TextView
android:id="@+id/keyboard_title"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="SORKB"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:textColor="@android:color/white"
android:background="@android:color/black"
android:elevation="4dp" />
<!-- Current Input Display -->
<TextView
android:id="@+id/current_input_display"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="4dp"
android:padding="4dp"
android:textSize="18sp"
android:textColor="@android:color/darker_gray"
android:background="@android:color/black" />
<!-- Suggestions Scroll View -->
<HorizontalScrollView
android:id="@+id/suggestions_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/suggestions_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</HorizontalScrollView>
<!-- Keyboard Container -->
<LinearLayout
android:id="@+id/keyboard_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keyboard_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
</LinearLayout>