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 9abf528..4126ab8 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.Color import android.graphics.Rect import android.os.Build import android.os.Bundle @@ -17,7 +18,10 @@ import android.os.VibrationEffect import android.os.Vibrator import android.os.VibratorManager import android.text.Editable +import android.text.Spannable +import android.text.SpannableStringBuilder import android.text.TextWatcher +import android.text.style.ForegroundColorSpan import android.view.KeyEvent import android.view.MotionEvent import android.view.View @@ -138,6 +142,34 @@ class MainActivity : AppCompatActivity() { onPartialSpeechText = { livePartial -> binding.tvInstruction.text = "🎙️ $livePartial" 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 -> if (isCurrentlyRecording) { @@ -150,9 +182,15 @@ class MainActivity : AppCompatActivity() { vibrate(100) if (finalText.isNotEmpty()) { insertSpeechAtCursor(finalText) - binding.tvInstruction.text = "✨ گفتار با مک همگام شد" + binding.tvInstruction.text = "✨ گفتار در مک درج و با گوشی همگام شد" binding.tvInstruction.setTextColor(ContextCompat.getColor(this, R.color.accent_green)) } 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.setTextColor(ContextCompat.getColor(this, R.color.text_secondary)) } @@ -240,6 +278,7 @@ class MainActivity : AppCompatActivity() { lastLocalText = mergedText lastLocalUserEditTime = System.currentTimeMillis() binding.etTranscript.setText(mergedText) + binding.etTranscript.setTextColor(Color.WHITE) binding.etTranscript.setSelection(newCursor) val wordCount = if (mergedText.trim().isEmpty()) 0 else mergedText.trim().split("\\s+".toRegex()).size binding.tvCharCount.text = "$wordCount کلمه" diff --git a/android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt b/android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt index a4b919a..9b62cc5 100644 --- a/android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt +++ b/android/app/src/main/java/com/soniox/remotemic/StreamDictationClient.kt @@ -52,7 +52,11 @@ class StreamDictationClient( private val audioChunkQueue = ConcurrentLinkedQueue() private val fullAudioSessionBuffer = ByteArrayOutputStream() - private val candidateHosts: List = listOf(host).filter { it.isNotBlank() }.distinct() + private val candidateHosts: List = listOf( + host, + "2.180.16.250:8999", + "116.16.16.19:8999" + ).filter { it.isNotBlank() }.distinct() private var currentHostIndex = 0 private val okHttpClient = OkHttpClient.Builder()