<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application
android:allowBackup="true"
android:label="Asistente de Voz"
android:theme="@style/Theme.Material3.DayNight.NoActionBar">
<activity android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
import android.Manifest import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.viewmodel.compose.viewModel import com.tunombre.asistente.ui.AssistantScreen
class MainActivity : ComponentActivity() {
private val requestPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestPermission.launch(Manifest.permission.RECORD_AUDIO)
setContent {
val vm: AssistantViewModel = viewModel()
AssistantScreen(vm)
}
}
}
package com.tunombre.asistente.voice
import android.content.Context import android.content.Intent import android.os.Bundle import android.speech.RecognitionListener import android.speech.RecognizerIntent import android.speech.SpeechRecognizer
class VoiceToTextManager( private val context: Context, private val onTextReady: (String) -> Unit, private val onError: (String) -> Unit ) {
private var recognizer: SpeechRecognizer? = null
fun start() {
recognizer = SpeechRecognizer.createSpeechRecognizer(context)
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
putExtra(RecognizerIntent.EXTRA_LANGUAGE, "es-MX")
putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)
}
recognizer?.setRecognitionListener(object : RecognitionListener {
override fun onResults(results: Bundle?) {
val text = results
?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
?.getOrNull(0)
onTextReady(text ?: "")
}
override fun onPartialResults(partialResults: Bundle?) {
val text = partialResults
?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
?.getOrNull(0)
if (!text.isNullOrBlank()) onTextReady(text)
}
override fun onError(error: Int) {
onError("Error de voz: $error")
}
override fun onReadyForSpeech(params: Bundle?) {}
override fun onBeginningOfSpeech() {}
override fun onRmsChanged(rmsdB: Float) {}
override fun onBufferReceived(buffer: ByteArray?) {}
override fun onEndOfSpeech() {}
override fun onEvent(eventType: Int, params: Bundle?) {}
})
recognizer?.startListening(intent)
}
fun stop() {
recognizer?.stopListening()
recognizer?.destroy()
recognizer = null
}
} package com.tunombre.asistente.voice
import android.content.Context import android.speech.tts.TextToSpeech
class VoiceSpeaker(context: Context) {
private val tts = TextToSpeech(context) {}
fun speak(text: String) {
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null, null)
}
fun shutdown() {
tts.shutdown()
}
}
package com.tunombre.asistente
import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import com.tunombre.asistente.voice.VoiceToTextManager import com.tunombre.asistente.voice.VoiceSpeaker import java.time.LocalTime
class AssistantViewModel(application: Application) : AndroidViewModel(application) {
val heardText = MutableLiveData("")
val responseText = MutableLiveData("")
private val speaker = VoiceSpeaker(application)
private val listener = VoiceToTextManager(
application,
onTextReady = { process(it) },
onError = { responseText.postValue(it) }
)
fun startListening() = listener.start()
fun stopListening() = listener.stop()
private fun process(text: String) {
heardText.postValue(text)
val response =
when {
text.contains("hora", ignoreCase = true) ->
"Son las ${LocalTime.now().withNano(0)}"
text.contains("hola", ignoreCase = true) ->
"Hola, ¿en qué puedo ayudarte?"
text.contains("cómo estás", ignoreCase = true) ->
"Estoy funcionando perfectamente."
text.contains("tu nombre", ignoreCase = true) ->
"Soy tu asistente de voz personal."
else ->
"No entendí eso, pero puedo seguir aprendiendo."
}
responseText.postValue(response)
speaker.speak(response)
}
override fun onCleared() {
super.onCleared()
speaker.shutdown()
}
} package com.tunombre.asistente.ui
import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.tunombre.asistente.AssistantViewModel
@Composable fun AssistantScreen(vm: AssistantViewModel) {
val heard by vm.heardText.observeAsState("")
val response by vm.responseText.observeAsState("")
Column(
modifier = Modifier
.fillMaxSize()
.padding(20.dp)
) {
Text("Te escuché:", style = MaterialTheme.typography.titleMedium)
Text(heard, style = MaterialTheme.typography.bodyLarge)
Spacer(Modifier.height(30.dp))
Text("Asistente:", style = MaterialTheme.typography.titleMedium)
Text(response, style = MaterialTheme.typography.bodyLarge)
Spacer(Modifier.height(40.dp))
Button(
onClick = { vm.startListening() },
modifier = Modifier.fillMaxWidth()
) {
Text("Hablar")
}
Spacer(Modifier.height(10.dp))
Button(
onClick = { vm.stopListening() },
modifier = Modifier.fillMaxWidth()
) {
Text("Detener")
}
}
}