|
|
|
@ -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 کلمه" |
|
|
|
|