diff --git a/app/src/main/java/mx/joykoo/scanner/AppConfig.kt b/app/src/main/java/mx/joykoo/scanner/AppConfig.kt index f72ecda..098ab68 100644 --- a/app/src/main/java/mx/joykoo/scanner/AppConfig.kt +++ b/app/src/main/java/mx/joykoo/scanner/AppConfig.kt @@ -12,6 +12,9 @@ object AppConfig { "joykoo.mx", ) + /** Size in dp of the corner regions used for hidden tap gestures. */ + const val GESTURE_CORNER_DP = 60 + /** Number of rapid Back presses required to exit the app when there is no WebView history. */ const val BACK_PRESS_EXIT_COUNT = 3 @@ -23,4 +26,10 @@ object AppConfig { /** Time window (ms) for admin tap sequence. */ const val ADMIN_TAP_WINDOW_MS = 3000L + + /** Number of taps on the top-right corner to toggle the on-screen keyboard. */ + const val KEYBOARD_TOGGLE_TAP_COUNT = 5 + + /** Time window (ms) for keyboard toggle tap sequence. */ + const val KEYBOARD_TOGGLE_TAP_WINDOW_MS = 3000L } diff --git a/app/src/main/java/mx/joykoo/scanner/MainActivity.kt b/app/src/main/java/mx/joykoo/scanner/MainActivity.kt index 746375f..b7ca678 100644 --- a/app/src/main/java/mx/joykoo/scanner/MainActivity.kt +++ b/app/src/main/java/mx/joykoo/scanner/MainActivity.kt @@ -41,6 +41,10 @@ class MainActivity : AppCompatActivity() { // Admin exit: rapid taps in the top-left corner private var adminTapTimestamps = mutableListOf() + // Keyboard toggle: rapid taps in the top-right corner + private var keyboardToggleTapTimestamps = mutableListOf() + private var isKeyboardSuppressed = true + // Back-press exit: rapid presses when no WebView history private var backPressTimestamps = mutableListOf() @@ -213,11 +217,14 @@ class MainActivity : AppCompatActivity() { override fun dispatchTouchEvent(ev: MotionEvent): Boolean { if (ev.actionMasked == MotionEvent.ACTION_DOWN) { - // Top-left 60x60 dp region as the hidden admin area - val threshold = (60 * resources.displayMetrics.density).toInt() + val threshold = (AppConfig.GESTURE_CORNER_DP * resources.displayMetrics.density).toInt() if (ev.x < threshold && ev.y < threshold) { handleAdminTap() } + val screenWidth = resources.displayMetrics.widthPixels + if (ev.x > screenWidth - threshold && ev.y < threshold) { + handleKeyboardToggleTap() + } } return super.dispatchTouchEvent(ev) } @@ -234,7 +241,6 @@ class MainActivity : AppCompatActivity() { } private fun showAdminExitDialog() { - // Structured so a PIN check can be added here before calling finish() AlertDialog.Builder(this) .setTitle("Salir de la aplicación") .setMessage("¿Desea cerrar la aplicación del escáner?") @@ -243,6 +249,35 @@ class MainActivity : AppCompatActivity() { .show() } + // ── Keyboard Toggle (hidden tap gesture, top-right corner) ────────── + + private fun handleKeyboardToggleTap() { + val now = System.currentTimeMillis() + keyboardToggleTapTimestamps.add(now) + keyboardToggleTapTimestamps.removeAll { + now - it > AppConfig.KEYBOARD_TOGGLE_TAP_WINDOW_MS + } + + if (keyboardToggleTapTimestamps.size >= AppConfig.KEYBOARD_TOGGLE_TAP_COUNT) { + keyboardToggleTapTimestamps.clear() + toggleKeyboard() + } + } + + private fun toggleKeyboard() { + isKeyboardSuppressed = !isKeyboardSuppressed + webView.keyboardSuppressed = isKeyboardSuppressed + + if (isKeyboardSuppressed) { + injectKeyboardSuppression(webView) + hideKeyboard() + Toast.makeText(this, R.string.keyboard_disabled, Toast.LENGTH_SHORT).show() + } else { + removeKeyboardSuppression(webView) + Toast.makeText(this, R.string.keyboard_enabled, Toast.LENGTH_SHORT).show() + } + } + // ── Lifecycle ─────────────────────────────────────────────────────── override fun onResume() { @@ -291,15 +326,15 @@ class MainActivity : AppCompatActivity() { } private fun hideKeyboard() { + if (!isKeyboardSuppressed) return val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(webView.windowToken, 0) } private fun injectKeyboardSuppression(view: WebView) { + if (!isKeyboardSuppressed) return view.evaluateJavascript(""" (function() { - if (window.__jkKbSuppressed) return; - window.__jkKbSuppressed = true; function suppress(el) { if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable) { @@ -309,7 +344,8 @@ class MainActivity : AppCompatActivity() { } document.querySelectorAll('input, textarea, [contenteditable]') .forEach(suppress); - new MutationObserver(function(muts) { + if (window.__jkKbObserver) window.__jkKbObserver.disconnect(); + window.__jkKbObserver = new MutationObserver(function(muts) { muts.forEach(function(m) { m.addedNodes.forEach(function(n) { if (n.nodeType === 1) { @@ -320,12 +356,27 @@ class MainActivity : AppCompatActivity() { } }); }); - }).observe(document.body || document.documentElement, + }); + window.__jkKbObserver.observe(document.body || document.documentElement, { childList: true, subtree: true }); })(); """.trimIndent(), null) } + private fun removeKeyboardSuppression(view: WebView) { + view.evaluateJavascript(""" + (function() { + if (window.__jkKbObserver) { + window.__jkKbObserver.disconnect(); + window.__jkKbObserver = null; + } + document.querySelectorAll('[inputmode="none"]').forEach(function(el) { + el.removeAttribute('inputmode'); + }); + })(); + """.trimIndent(), null) + } + // ── WebViewClient ─────────────────────────────────────────────────── private inner class ScannerWebViewClient : WebViewClient() { diff --git a/app/src/main/java/mx/joykoo/scanner/NoSoftKeyboardWebView.kt b/app/src/main/java/mx/joykoo/scanner/NoSoftKeyboardWebView.kt index ad78881..0a6efff 100644 --- a/app/src/main/java/mx/joykoo/scanner/NoSoftKeyboardWebView.kt +++ b/app/src/main/java/mx/joykoo/scanner/NoSoftKeyboardWebView.kt @@ -26,6 +26,8 @@ import android.webkit.WebView */ class NoSoftKeyboardWebView : WebView { + var keyboardSuppressed = true + constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : @@ -33,17 +35,20 @@ class NoSoftKeyboardWebView : WebView { override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? { val ic = super.onCreateInputConnection(outAttrs) - outAttrs.imeOptions = outAttrs.imeOptions or - EditorInfo.IME_FLAG_NO_EXTRACT_UI or - EditorInfo.IME_FLAG_NO_FULLSCREEN - post { - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(windowToken, 0) + if (keyboardSuppressed) { + outAttrs.imeOptions = outAttrs.imeOptions or + EditorInfo.IME_FLAG_NO_EXTRACT_UI or + EditorInfo.IME_FLAG_NO_FULLSCREEN + post { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) + as InputMethodManager + imm.hideSoftInputFromWindow(windowToken, 0) + } } return ic } override fun onCheckIsTextEditor(): Boolean { - return false + return !keyboardSuppressed } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e0972e3..2a5a954 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,4 +4,6 @@ No se puede conectar Verifique la conexión Wi-Fi e intente de nuevo. Reintentar + Teclado en pantalla activado + Teclado en pantalla desactivado