diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt index 05736bda9..942681618 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/KenjinxNative.kt @@ -97,7 +97,8 @@ val jnaInstance: KenjinxNativeJna = Native.load( object KenjinxNative : KenjinxNativeJna by jnaInstance { - fun loggingSetEnabled(logLevel: LogLevel, enabled: Boolean) = loggingSetEnabled(logLevel.ordinal, enabled) + fun loggingSetEnabled(logLevel: LogLevel, enabled: Boolean) = + loggingSetEnabled(logLevel.ordinal, enabled) @JvmStatic fun frameEnded() = MainActivity.frameEnded() @@ -116,6 +117,10 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance { progress ) + /** + * Variant A (Pointer → Strings via NativeHelpers). + * Used by older JNI/Interop paths. + */ @JvmStatic fun updateUiHandler( newTitlePointer: Long, @@ -127,15 +132,54 @@ object KenjinxNative : KenjinxNativeJna by jnaInstance { nMode: Int, newSubtitlePointer: Long, newInitialTextPointer: Long - ) = MainActivity.mainViewModel?.activity?.uiHandler?.update( - newTitle = NativeHelpers.instance.getStringJava(newTitlePointer), - newMessage = NativeHelpers.instance.getStringJava(newMessagePointer), - newWatermark = NativeHelpers.instance.getStringJava(newWatermarkPointer), - newType, - min, - max, - newMode = KeyboardMode.entries[nMode], - newSubtitle = NativeHelpers.instance.getStringJava(newSubtitlePointer), - NativeHelpers.instance.getStringJava(newInitialTextPointer) - ) + ) { + val title = NativeHelpers.instance.getStringJava(newTitlePointer) + val message = NativeHelpers.instance.getStringJava(newMessagePointer) + val watermark = NativeHelpers.instance.getStringJava(newWatermarkPointer) + val subtitle = NativeHelpers.instance.getStringJava(newSubtitlePointer) + val initialText = NativeHelpers.instance.getStringJava(newInitialTextPointer) + val mode = KeyboardMode.entries.getOrNull(nMode) ?: KeyboardMode.Default + + MainActivity.mainViewModel?.activity?.uiHandler?.update( + newTitle = title, + newMessage = message, + newWatermark = watermark, + newType = newType, + min = min, + max = max, + newMode = mode, + newSubtitle = subtitle, + newInitialText = initialText + ) + } + + /** + * Variant B (strings directly). Used by newer JNI/Interop paths. + * Signature exactly matches the C# call in AndroidUIHandler.cs / Interop.UpdateUiHandler(...). + */ + @JvmStatic + fun uiHandlerUpdate( + title: String, + message: String, + watermark: String, + type: Int, + min: Int, + max: Int, + nMode: Int, + subtitle: String, + initialText: String + ) { + val mode = KeyboardMode.entries.getOrNull(nMode) ?: KeyboardMode.Default + MainActivity.mainViewModel?.activity?.uiHandler?.update( + newTitle = title, + newMessage = message, + newWatermark = watermark, + newType = type, + min = min, + max = max, + newMode = mode, + newSubtitle = subtitle, + newInitialText = initialText + ) + } } diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt index 7bdc9c0b9..7b200235c 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/MainActivity.kt @@ -28,7 +28,6 @@ import org.kenjinx.android.viewmodels.GameModel import org.kenjinx.android.views.MainView import androidx.core.net.toUri - class MainActivity : BaseActivity() { private var physicalControllerManager: PhysicalControllerManager = PhysicalControllerManager(this) @@ -123,8 +122,7 @@ class MainActivity : BaseActivity() { motionSensorManager = MotionSensorManager(this) Thread.setDefaultUncaughtExceptionHandler(crashHandler) - if ( - !Environment.isExternalStorageManager() + if (!Environment.isExternalStorageManager() ) { storageHelper?.storage?.requestFullStorageAccess() } @@ -143,6 +141,9 @@ class MainActivity : BaseActivity() { controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } + // >>> Important: Initialize UI handler (for software keyboard/dialog) + uiHandler = UiHandler() + mainViewModel = MainViewModel(this) mainViewModel!!.physicalControllerManager = physicalControllerManager mainViewModel!!.motionSensorManager = motionSensorManager @@ -152,7 +153,6 @@ class MainActivity : BaseActivity() { mainViewModel?.apply { setContent { KenjinxAndroidTheme { - // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background @@ -219,7 +219,7 @@ class MainActivity : BaseActivity() { override fun onPause() { super.onPause() - isActive = true + isActive = false if (isGameRunning) { mainViewModel?.performanceManager?.setTurboMode(false) @@ -232,7 +232,7 @@ class MainActivity : BaseActivity() { when (storedIntent.action) { Intent.ACTION_VIEW, "org.kenjinx.android.LAUNCH_GAME" -> { val bootPath = storedIntent.getStringExtra("bootPath") - val forceNceAndPptc = storedIntent.getBooleanExtra("forceNceAndPptc",false) + val forceNceAndPptc = storedIntent.getBooleanExtra("forceNceAndPptc", false) if (bootPath != null) { val uri = bootPath.toUri() @@ -260,7 +260,6 @@ class MainActivity : BaseActivity() { // Clean up resources if needed mainViewModel?.let { - // Perform any critical cleanup it.performanceManager?.setTurboMode(false) } diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/UiHandler.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/UiHandler.kt index 9fc40eea8..e10ffe3a9 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/UiHandler.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/UiHandler.kt @@ -17,8 +17,12 @@ import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign @@ -27,6 +31,7 @@ import androidx.compose.ui.window.DialogProperties import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.ui.material3.RichText import org.kenjinx.android.widgets.SimpleAlertDialog +import kotlinx.coroutines.delay enum class KeyboardMode { Default, Numeric, ASCII, FullLatin, Alphabet, SimplifiedChinese, TraditionalChinese, Korean, LanguageSet2, LanguageSet2Latin @@ -46,6 +51,7 @@ class UiHandler { var message: String = "" init { + // 2.0.3 compatible: no parameters KenjinxNative.uiHandlerSetup() } @@ -77,15 +83,21 @@ class UiHandler { @OptIn(ExperimentalMaterial3Api::class) @Composable fun Compose() { - val showMessageListener = remember { - showMessage - } + val showMessageListener = remember { showMessage } + val inputListener = remember { inputText } + val validation = remember { mutableStateOf("") } - val inputListener = remember { - inputText - } - val validation = remember { - mutableStateOf("") + // Focus & keyboard control so the popup can be typed immediately like in 2.0.3 + val focusRequester = remember { FocusRequester() } + val keyboard = LocalSoftwareKeyboardController.current + + LaunchedEffect(showMessageListener.value, type) { + if (showMessageListener.value && type == 2) { + // small delay until the dialog is mounted + delay(100) + focusRequester.requestFocus() + keyboard?.show() + } } fun validate(): Boolean { @@ -94,7 +106,6 @@ class UiHandler { } else { return inputText.value.length < minLength || inputText.value.length > maxLength } - return false } @@ -103,9 +114,7 @@ class UiHandler { KeyboardMode.Default -> KeyboardType.Text KeyboardMode.Numeric -> KeyboardType.Decimal KeyboardMode.ASCII -> KeyboardType.Ascii - else -> { - KeyboardType.Text - } + else -> KeyboardType.Text } } @@ -160,10 +169,9 @@ class UiHandler { onValueChange = { inputListener.value = it }, modifier = Modifier .fillMaxWidth() - .padding(4.dp), - label = { - Text(text = watermark) - }, + .padding(4.dp) + .focusRequester(focusRequester), + label = { Text(text = watermark) }, keyboardOptions = KeyboardOptions(keyboardType = getInputType()), isError = validate() ) @@ -173,7 +181,8 @@ class UiHandler { onValueChange = { inputListener.value = it }, modifier = Modifier .fillMaxWidth() - .padding(4.dp), + .padding(4.dp) + .focusRequester(focusRequester), keyboardOptions = KeyboardOptions( keyboardType = getInputType(), imeAction = ImeAction.Done @@ -211,4 +220,3 @@ class UiHandler { } } } - diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/GameViews.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/GameViews.kt index 5dc0d4c66..96c764508 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/GameViews.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/GameViews.kt @@ -250,6 +250,13 @@ class GameViews { mutableStateOf(false) } + // NEW: If the software keyboard is open, catch Back and close ONLY the dialog. + val uiHandler = mainViewModel.activity.uiHandler + BackHandler(enabled = uiHandler.showMessage.value) { + KenjinxNative.uiHandlerSetResponse(false, "") + uiHandler.showMessage.value = false + } + BackHandler { showBackNotice.value = true }