Browse Source

feat(ui): 100% full-screen immersive input box when keyboard expands (hide mic & action buttons completely)

main
Ali Alavi 1 day ago
parent
commit
91ae022764
  1. 61
      android/app/src/main/java/com/soniox/remotemic/MainActivity.kt
  2. 25
      android/app/src/main/res/layout/activity_main.xml

61
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.ClipboardManager
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Rect
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
@ -20,6 +21,7 @@ import android.text.TextWatcher
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewTreeObserver
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.ScrollView import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
@ -56,6 +58,7 @@ class MainActivity : AppCompatActivity() {
private var isApplyingRemoteUpdate = false private var isApplyingRemoteUpdate = false
private var lastLocalText = "" private var lastLocalText = ""
private var lastLocalUserEditTime = 0L private var lastLocalUserEditTime = 0L
private var isKeyboardCurrentlyVisible = false
private val debounceHandler = Handler(Looper.getMainLooper()) private val debounceHandler = Handler(Looper.getMainLooper())
private var pendingSyncRunnable: Runnable? = null private var pendingSyncRunnable: Runnable? = null
@ -81,11 +84,11 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
setupKeyboardInsetHandling()
setupKeyboardVisibilityDetection()
setupUI() setupUI()
checkPermissions() checkPermissions()
AppLogger.log("Main", "اپلیکیشن راه‌اندازی شد (همگام‌سازی بلادرنگ بدون باگ پاک‌کردن)")
AppLogger.log("Main", "اپلیکیشن راه‌اندازی شد (طراحی ریسپانسیو و فوکوس کامل اینپوت‌باکس روی کیبورد)")
// Initialize Collaborative WebSocket Client // Initialize Collaborative WebSocket Client
streamDictationClient = StreamDictationClient( streamDictationClient = StreamDictationClient(
@ -156,21 +159,48 @@ 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 -> ViewCompat.setOnApplyWindowInsetsListener(binding.rootLayout) { _, insets ->
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
updateKeyboardUIMode(imeVisible)
insets
}
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
// 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)
} }
})
}
insets
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
} }
} }
@ -287,11 +317,6 @@ class MainActivity : AppCompatActivity() {
binding.btnMic.setOnTouchListener { _, event -> binding.btnMic.setOnTouchListener { _, event ->
handleMicTouch(event) handleMicTouch(event)
} }
// Touch listener for Compact Mini Mic Button (above keyboard)
binding.btnMiniMic.setOnTouchListener { _, event ->
handleMicTouch(event)
}
} }
private fun handleMicTouch(event: MotionEvent): Boolean { private fun handleMicTouch(event: MotionEvent): Boolean {
@ -413,7 +438,6 @@ class MainActivity : AppCompatActivity() {
binding.tvInstruction.text = getString(R.string.release_to_type) binding.tvInstruction.text = getString(R.string.release_to_type)
binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_red)) binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_red))
binding.btnMic.setBackgroundResource(R.drawable.bg_mic_button_active) binding.btnMic.setBackgroundResource(R.drawable.bg_mic_button_active)
binding.btnMiniMic.setBackgroundColor(ContextCompat.getColor(this, R.color.accent_red))
startPulseAnimation() startPulseAnimation()
streamDictationClient?.startRecording(voiceInsertionCursorStart) streamDictationClient?.startRecording(voiceInsertionCursorStart)
@ -425,7 +449,6 @@ class MainActivity : AppCompatActivity() {
stopPulseAnimation() stopPulseAnimation()
binding.btnMic.setBackgroundResource(R.drawable.bg_mic_button) binding.btnMic.setBackgroundResource(R.drawable.bg_mic_button)
binding.btnMiniMic.setBackgroundColor(ContextCompat.getColor(this, R.color.accent_blue))
binding.tvInstruction.text = "⏳ در حال درج و همگام‌سازی..." binding.tvInstruction.text = "⏳ در حال درج و همگام‌سازی..."
binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_yellow)) binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_yellow))

25
android/app/src/main/res/layout/activity_main.xml

@ -98,6 +98,7 @@
<!-- Top Row: Box Title + Word Count + Clear Button --> <!-- Top Row: Box Title + Word Count + Clear Button -->
<LinearLayout <LinearLayout
android:id="@+id/topBoxBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
@ -132,7 +133,7 @@
android:textSize="11sp" /> android:textSize="11sp" />
</LinearLayout> </LinearLayout>
<!-- Fully Editable Multi-line EditText -->
<!-- Fully Editable Multi-line EditText (Expands to fill entire space) -->
<EditText <EditText
android:id="@+id/etTranscript" android:id="@+id/etTranscript"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -141,7 +142,7 @@
android:layout_weight="1" android:layout_weight="1"
android:background="@null" android:background="@null"
android:gravity="top|start" android:gravity="top|start"
android:hint="متن مک در اینجا نمایش داده می‌شود؛ صحبت کنید یا بنویسید..."
android:hint="متن مک در اینجا نمایش داده می‌شود؛ با کیبورد ویرایش کنید یا صحبت کنید..."
android:inputType="textMultiLine|textCapSentences" android:inputType="textMultiLine|textCapSentences"
android:lineSpacingExtra="4dp" android:lineSpacingExtra="4dp"
android:scrollbars="vertical" android:scrollbars="vertical"
@ -150,12 +151,13 @@
android:textSize="16sp" /> android:textSize="16sp" />
<View <View
android:id="@+id/actionDivider"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginVertical="6dp" android:layout_marginVertical="6dp"
android:background="@color/card_border" /> android:background="@color/card_border" />
<!-- Action Buttons Row (sits directly above keyboard when open) -->
<!-- Action Buttons Row (Hidden when keyboard opens) -->
<LinearLayout <LinearLayout
android:id="@+id/actionButtonsRow" android:id="@+id/actionButtonsRow"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -173,23 +175,6 @@
android:textSize="11sp" android:textSize="11sp"
app:cornerRadius="8dp" /> app:cornerRadius="8dp" />
<!-- Compact Mini Mic Button (visible when keyboard is open) -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnMiniMic"
style="@style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="38dp"
android:layout_marginStart="8dp"
android:backgroundTint="@color/accent_blue"
android:text="🎙️ گفتار (نگه‌دارید)"
android:textColor="@color/text_primary"
android:textSize="11sp"
android:textStyle="bold"
android:visibility="gone"
app:cornerRadius="8dp"
app:icon="@drawable/ic_mic"
app:iconSize="16dp" />
<View <View
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="1dp" android:layout_height="1dp"

Loading…
Cancel
Save