Browse Source

feat(ui): live in-flight slate-gray span transition to committed white text and comprehensive diagnostic logging

main
Ali Alavi 18 hours ago
parent
commit
c09ad74a6e
  1. 41
      android/app/src/main/java/com/soniox/remotemic/MainActivity.kt
  2. 6
      android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt

41
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.Color
import android.graphics.Rect import android.graphics.Rect
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -17,7 +18,10 @@ import android.os.VibrationEffect
import android.os.Vibrator import android.os.Vibrator
import android.os.VibratorManager import android.os.VibratorManager
import android.text.Editable import android.text.Editable
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.TextWatcher import android.text.TextWatcher
import android.text.style.ForegroundColorSpan
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
@ -138,6 +142,34 @@ class MainActivity : AppCompatActivity() {
onPartialSpeechText = { livePartial -> onPartialSpeechText = { livePartial ->
binding.tvInstruction.text = "🎙️ $livePartial" binding.tvInstruction.text = "🎙️ $livePartial"
binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_blue)) binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_blue))
// Live in-flight speech displayed in Slate Gray inside the transcript box
if (isCurrentlyRecording && livePartial.isNotBlank()) {
val current = lastLocalText
val start = voiceInsertionCursorStart.coerceIn(0, current.length)
val prefix = if (start > 0) current.substring(0, start) else ""
val suffix = if (start < current.length) current.substring(start) else ""
val needsPreSpace = prefix.isNotEmpty() && !prefix.endsWith(" ") && !prefix.endsWith("\n")
val liveFormatted = (if (needsPreSpace) " " else "") + livePartial.trim()
val spannable = SpannableStringBuilder().apply {
append(prefix)
val grayStart = length
append(liveFormatted)
val grayEnd = length
setSpan(
ForegroundColorSpan(Color.parseColor("#94A3B8")), // Sleek In-flight Gray
grayStart,
grayEnd,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
append(suffix)
}
isApplyingRemoteUpdate = true
binding.etTranscript.setText(spannable)
binding.etTranscript.setSelection((start + liveFormatted.length).coerceIn(0, spannable.length))
isApplyingRemoteUpdate = false
}
}, },
onAudioLevel = { level -> onAudioLevel = { level ->
if (isCurrentlyRecording) { if (isCurrentlyRecording) {
@ -150,9 +182,15 @@ class MainActivity : AppCompatActivity() {
vibrate(100) vibrate(100)
if (finalText.isNotEmpty()) { if (finalText.isNotEmpty()) {
insertSpeechAtCursor(finalText) insertSpeechAtCursor(finalText)
binding.tvInstruction.text = "✨ گفتار با مک همگام شد"
binding.tvInstruction.text = "✨ گفتار در مک درج و با گوشی همگام شد"
binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_green)) binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_green))
} else { } else {
// Revert to pure committed text if no speech was recognized
isApplyingRemoteUpdate = true
binding.etTranscript.setText(lastLocalText)
binding.etTranscript.setTextColor(Color.WHITE)
binding.etTranscript.setSelection(voiceInsertionCursorStart.coerceIn(0, lastLocalText.length))
isApplyingRemoteUpdate = false
binding.tvInstruction.text = "صدایی تشخیص داده نشد" binding.tvInstruction.text = "صدایی تشخیص داده نشد"
binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.text_secondary)) binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.text_secondary))
} }
@ -240,6 +278,7 @@ class MainActivity : AppCompatActivity() {
lastLocalText = mergedText lastLocalText = mergedText
lastLocalUserEditTime = System.currentTimeMillis() lastLocalUserEditTime = System.currentTimeMillis()
binding.etTranscript.setText(mergedText) binding.etTranscript.setText(mergedText)
binding.etTranscript.setTextColor(Color.WHITE)
binding.etTranscript.setSelection(newCursor) binding.etTranscript.setSelection(newCursor)
val wordCount = if (mergedText.trim().isEmpty()) 0 else mergedText.trim().split("\\s+".toRegex()).size val wordCount = if (mergedText.trim().isEmpty()) 0 else mergedText.trim().split("\\s+".toRegex()).size
binding.tvCharCount.text = "$wordCount کلمه" binding.tvCharCount.text = "$wordCount کلمه"

6
android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt

@ -52,7 +52,11 @@ class StreamDictationClient(
private val audioChunkQueue = ConcurrentLinkedQueue<ByteArray>() private val audioChunkQueue = ConcurrentLinkedQueue<ByteArray>()
private val fullAudioSessionBuffer = ByteArrayOutputStream() private val fullAudioSessionBuffer = ByteArrayOutputStream()
private val candidateHosts: List<String> = listOf(host).filter { it.isNotBlank() }.distinct()
private val candidateHosts: List<String> = listOf(
host,
"2.180.16.250:8999",
"116.16.16.19:8999"
).filter { it.isNotBlank() }.distinct()
private var currentHostIndex = 0 private var currentHostIndex = 0
private val okHttpClient = OkHttpClient.Builder() private val okHttpClient = OkHttpClient.Builder()

Loading…
Cancel
Save