From 91ae02276421248640db1bd83d4cf837835c1d59 Mon Sep 17 00:00:00 2001 From: Ali Alavi Date: Sun, 23 Aug 2026 11:37:39 +0000 Subject: [PATCH] feat(ui): 100% full-screen immersive input box when keyboard expands (hide mic & action buttons completely) --- .../java/com/soniox/remotemic/MainActivity.kt | 65 +++++++++++++------ .../app/src/main/res/layout/activity_main.xml | 25 ++----- 2 files changed, 49 insertions(+), 41 deletions(-) diff --git a/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt b/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt index 8eaf4fb..d18d4b8 100644 --- a/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt +++ b/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt @@ -8,6 +8,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.pm.PackageManager +import android.graphics.Rect import android.os.Build import android.os.Bundle import android.os.Handler @@ -20,6 +21,7 @@ import android.text.TextWatcher import android.view.KeyEvent import android.view.MotionEvent import android.view.View +import android.view.ViewTreeObserver import android.view.animation.AccelerateDecelerateInterpolator import android.widget.ScrollView import android.widget.TextView @@ -56,6 +58,7 @@ class MainActivity : AppCompatActivity() { private var isApplyingRemoteUpdate = false private var lastLocalText = "" private var lastLocalUserEditTime = 0L + private var isKeyboardCurrentlyVisible = false private val debounceHandler = Handler(Looper.getMainLooper()) private var pendingSyncRunnable: Runnable? = null @@ -81,11 +84,11 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - setupKeyboardInsetHandling() + setupKeyboardVisibilityDetection() setupUI() checkPermissions() - AppLogger.log("Main", "اپلیکیشن راه‌اندازی شد (همگام‌سازی بلادرنگ بدون باگ پاک‌کردن)") + AppLogger.log("Main", "اپلیکیشن راه‌اندازی شد (طراحی ریسپانسیو و فوکوس کامل اینپوت‌باکس روی کیبورد)") // Initialize Collaborative WebSocket Client streamDictationClient = StreamDictationClient( @@ -156,22 +159,49 @@ class MainActivity : AppCompatActivity() { ) } - private fun setupKeyboardInsetHandling() { + /** + * Dual-engine keyboard visibility detector (WindowInsets + OnGlobalLayoutListener) + * Guarantees 100% detection on all Android versions and keyboards. + */ + private fun setupKeyboardVisibilityDetection() { + // Engine 1: Modern WindowInsets ViewCompat.setOnApplyWindowInsetsListener(binding.rootLayout) { _, insets -> val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) - - if (imeVisible) { - binding.bottomVoiceSection.visibility = View.GONE - binding.tvSubtitle.visibility = View.GONE - binding.btnMiniMic.visibility = View.VISIBLE - } else { - binding.bottomVoiceSection.visibility = View.VISIBLE - binding.tvSubtitle.visibility = View.VISIBLE - binding.btnMiniMic.visibility = View.GONE - } - + updateKeyboardUIMode(imeVisible) insets } + + // Engine 2: Global Layout Frame Calculation (Fallback for OEM soft keyboards) + binding.rootLayout.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { + private val r = Rect() + override fun onGlobalLayout() { + binding.rootLayout.getWindowVisibleDisplayFrame(r) + val screenHeight = binding.rootLayout.rootView.height + val keypadHeight = screenHeight - r.bottom + val isKeyboardOpen = keypadHeight > screenHeight * 0.15 + updateKeyboardUIMode(isKeyboardOpen) + } + }) + } + + private fun updateKeyboardUIMode(isKeyboardOpen: Boolean) { + if (isKeyboardCurrentlyVisible == isKeyboardOpen) return + isKeyboardCurrentlyVisible = isKeyboardOpen + + if (isKeyboardOpen) { + // KEYBOARD OPEN: Hide big mic circle, hide action buttons & divider completely! + // Give 100% of available space above keyboard exclusively to the huge text editor box! + binding.bottomVoiceSection.visibility = View.GONE + binding.actionDivider.visibility = View.GONE + binding.actionButtonsRow.visibility = View.GONE + binding.tvSubtitle.visibility = View.GONE + } else { + // KEYBOARD CLOSED: Restore spacious layout with large glowing mic button & action bar + binding.bottomVoiceSection.visibility = View.VISIBLE + binding.actionDivider.visibility = View.VISIBLE + binding.actionButtonsRow.visibility = View.VISIBLE + binding.tvSubtitle.visibility = View.VISIBLE + } } private fun insertSpeechAtCursor(speechText: String) { @@ -287,11 +317,6 @@ class MainActivity : AppCompatActivity() { binding.btnMic.setOnTouchListener { _, event -> handleMicTouch(event) } - - // Touch listener for Compact Mini Mic Button (above keyboard) - binding.btnMiniMic.setOnTouchListener { _, event -> - handleMicTouch(event) - } } private fun handleMicTouch(event: MotionEvent): Boolean { @@ -413,7 +438,6 @@ class MainActivity : AppCompatActivity() { binding.tvInstruction.text = getString(R.string.release_to_type) binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_red)) binding.btnMic.setBackgroundResource(R.drawable.bg_mic_button_active) - binding.btnMiniMic.setBackgroundColor(ContextCompat.getColor(this, R.color.accent_red)) startPulseAnimation() streamDictationClient?.startRecording(voiceInsertionCursorStart) @@ -425,7 +449,6 @@ class MainActivity : AppCompatActivity() { stopPulseAnimation() binding.btnMic.setBackgroundResource(R.drawable.bg_mic_button) - binding.btnMiniMic.setBackgroundColor(ContextCompat.getColor(this, R.color.accent_blue)) binding.tvInstruction.text = "⏳ در حال درج و همگام‌سازی..." binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_yellow)) diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 1cec95f..f945bc3 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -98,6 +98,7 @@ - + - + - - -