diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 8e408ab..b0a61c8 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -13,6 +13,9 @@ + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d676a9f..8766be7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,6 +55,7 @@ > = emptyList(), navController: NavController) { @@ -352,4 +353,4 @@ private fun HomeScreenPreview() { } val navController = rememberNavController() HomeScreen(sections = sections, navController = navController) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/deepsea/ui/screens/feature/VoiceAssistantScreen.kt b/app/src/main/java/com/example/deepsea/ui/screens/feature/VoiceAssistantScreen.kt index bff6eb6..d893846 100644 --- a/app/src/main/java/com/example/deepsea/ui/screens/feature/VoiceAssistantScreen.kt +++ b/app/src/main/java/com/example/deepsea/ui/screens/feature/VoiceAssistantScreen.kt @@ -1,6 +1,8 @@ package com.example.deepsea.AI_assistant import android.Manifest +import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle import android.speech.RecognitionListener @@ -9,16 +11,28 @@ import android.speech.SpeechRecognizer import android.speech.tts.TextToSpeech import android.util.Log import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.keyframes +import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.app.ActivityCompat @@ -31,6 +45,58 @@ import org.json.JSONObject private const val TAG = "VoiceAssistant" +class VoiceAssistantActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Yêu cầu quyền thu âm nếu chưa có + if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) + != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.RECORD_AUDIO), + PackageManager.PERMISSION_GRANTED + ) + } + + setContent { + VoiceAssistantScreen() + } + } +} + +@Composable +fun DotIndicator(isActive: Boolean) { + val infiniteTransition = rememberInfiniteTransition() + val offsetX by infiniteTransition.animateFloat( + initialValue = -2f, + targetValue = 2f, + animationSpec = infiniteRepeatable( + animation = keyframes { + durationMillis = 300 + -2f at 0 + 2f at 150 + -2f at 300 + } + ) + ) + + Box( + modifier = Modifier + .size(12.dp) + .offset(x = if (isActive) offsetX.dp else 0.dp) + .clip(CircleShape) + .background( + if (isActive) + MaterialTheme.colorScheme.primary + else + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f) + ) + ) +} + +@Preview(showBackground = true) @Composable fun VoiceAssistantScreen() { val context = LocalContext.current @@ -43,9 +109,8 @@ fun VoiceAssistantScreen() { val apiKey = "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" val endpoint = "https://api.openai.com/v1/chat/completions" - // Initialize TextToSpeech + // Khởi tạo TextToSpeech val isTtsReady = remember { mutableStateOf(false) } - val textToSpeech = remember { TextToSpeech(context) { status -> if (status != TextToSpeech.ERROR) { @@ -54,13 +119,12 @@ fun VoiceAssistantScreen() { } } - // Initialize SpeechRecognizer + // Khởi tạo SpeechRecognizer val recognizerIntent = remember { - android.content.Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { + Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1) putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true) - putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, 3000) putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 1500) } } @@ -68,61 +132,44 @@ fun VoiceAssistantScreen() { val speechRecognizer = remember { SpeechRecognizer.createSpeechRecognizer(context).apply { setRecognitionListener(object : RecognitionListener { - override fun onResults(bundle: Bundle) { - val matches = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION) - matches?.let { - if (it.isNotEmpty()) { - val query = it[0] - inputText = query - callChatGPT( - context = context, - input = query, - apiKey = apiKey, - endpoint = endpoint, - onStart = { isProcessing = true }, - onResponse = { response -> - outputText = response - isProcessing = false - if (!textToSpeech.isSpeaking) { - textToSpeech.speak(response, TextToSpeech.QUEUE_FLUSH, null, null) - } - }, - onError = { error -> - outputText = "Error: $error" - isProcessing = false + override fun onReadyForSpeech(params: Bundle?) {} + override fun onBeginningOfSpeech() {} + override fun onRmsChanged(rmsdB: Float) {} + override fun onBufferReceived(buffer: ByteArray?) {} + override fun onEndOfSpeech() { isListening = false } + override fun onError(error: Int) { isListening = false } + override fun onResults(bundle: Bundle?) { + val matches = bundle + ?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION) + matches?.firstOrNull()?.let { query -> + inputText = query + callChatGPT( + context = context, + input = query, + apiKey = apiKey, + endpoint = endpoint, + onStart = { isProcessing = true }, + onResponse = { response -> + outputText = response + isProcessing = false + if (!textToSpeech.isSpeaking) { + textToSpeech.speak(response, TextToSpeech.QUEUE_FLUSH, null, "tts1") } - ) - } + }, + onError = { error -> + outputText = "Error: $error" + isProcessing = false + } + ) } } - - // Required implementations for RecognitionListener - override fun onReadyForSpeech(bundle: Bundle) {} - override fun onBeginningOfSpeech() {} - override fun onRmsChanged(v: Float) {} - override fun onBufferReceived(bytes: ByteArray) {} - override fun onEndOfSpeech() { isListening = false } - override fun onError(i: Int) { isListening = false } - override fun onPartialResults(bundle: Bundle) {} - override fun onEvent(i: Int, bundle: Bundle) {} + override fun onPartialResults(partialResults: Bundle?) {} + override fun onEvent(eventType: Int, params: Bundle?) {} }) } } - // Request permissions - LaunchedEffect(Unit) { - if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) - != PackageManager.PERMISSION_GRANTED - ) { - ActivityCompat.requestPermissions( - context as ComponentActivity, - arrayOf(Manifest.permission.RECORD_AUDIO), - PackageManager.PERMISSION_GRANTED - ) - } - } - - // Clean up resources when the screen is closed + // Dọn dẹp khi đóng DisposableEffect(Unit) { onDispose { textToSpeech.stop() @@ -137,25 +184,32 @@ fun VoiceAssistantScreen() { .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Text( - text = "AI Voice Assistant", - fontSize = 24.sp, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "AI Voice Assistant", + fontSize = 24.sp, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.width(8.dp)) + DotIndicator(isActive = isProcessing || textToSpeech.isSpeaking) + } - // Input display + // Hiển thị input TextField( value = inputText, - onValueChange = { inputText = it }, + onValueChange = {}, + readOnly = true, modifier = Modifier .fillMaxWidth() .height(80.dp), - label = { Text("Your question") }, - readOnly = true + label = { Text("Your question") } ) - // Output display + // Hiển thị output Box( modifier = Modifier .fillMaxWidth() @@ -165,38 +219,35 @@ fun VoiceAssistantScreen() { .padding(16.dp) ) { if (isProcessing) { - CircularProgressIndicator( - modifier = Modifier.align(Alignment.Center) - ) + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } else { - Text( - text = outputText, - fontSize = 16.sp, - modifier = Modifier - .fillMaxSize() - ) + Text(text = outputText, fontSize = 16.sp) } } - // Control button + // Nút điều khiển Button( onClick = { - if (textToSpeech.isSpeaking) { - textToSpeech.stop() - } else if (!isListening) { - isListening = true - speechRecognizer.startListening(recognizerIntent) - } else { - speechRecognizer.stopListening() - isListening = false + when { + textToSpeech.isSpeaking -> textToSpeech.stop() + isListening -> { + speechRecognizer.stopListening() + isListening = false + } + else -> { + isListening = true + speechRecognizer.startListening(recognizerIntent) + } } }, modifier = Modifier .fillMaxWidth() .height(56.dp), colors = ButtonDefaults.buttonColors( - containerColor = if (isListening) MaterialTheme.colorScheme.error - else MaterialTheme.colorScheme.primary + containerColor = if (isListening) + MaterialTheme.colorScheme.error + else + MaterialTheme.colorScheme.primary ) ) { Text( @@ -211,7 +262,7 @@ fun VoiceAssistantScreen() { } private fun callChatGPT( - context: android.content.Context, + context: Context, input: String, apiKey: String, endpoint: String, @@ -260,13 +311,11 @@ private fun callChatGPT( } } - // Set timeout jsonRequest.retryPolicy = DefaultRetryPolicy( 60000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT ) - // Add to queue Volley.newRequestQueue(context).add(jsonRequest) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/deepsea/ui/screens/feature/game/GamePlayScreen.kt b/app/src/main/java/com/example/deepsea/ui/screens/feature/game/GamePlayScreen.kt index 9da3f1e..26e5b06 100644 --- a/app/src/main/java/com/example/deepsea/ui/screens/feature/game/GamePlayScreen.kt +++ b/app/src/main/java/com/example/deepsea/ui/screens/feature/game/GamePlayScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.deepsea.ui.components.PlayerScoreCard diff --git a/app/src/main/java/com/example/deepsea/ui/viewmodel/auth/AuthViewModel.kt b/app/src/main/java/com/example/deepsea/ui/viewmodel/auth/AuthViewModel.kt index 7e39fe5..677e575 100644 --- a/app/src/main/java/com/example/deepsea/ui/viewmodel/auth/AuthViewModel.kt +++ b/app/src/main/java/com/example/deepsea/ui/viewmodel/auth/AuthViewModel.kt @@ -127,6 +127,9 @@ class AuthViewModel(application: Application) : AndroidViewModel(application) { } } + fun resetRegisterState() { + _registerState.value = RegisterState.Idle + } fun signup(name:String , username: String, email: String, password: String, avatar: Uri? = null) { viewModelScope.launch { @@ -149,7 +152,7 @@ class AuthViewModel(application: Application) : AndroidViewModel(application) { val response = RetrofitClient.authApi.register(registerRequest) if (response.isSuccessful && response.body() != null) { - _registerState.value = RegisterState.Success(response.body()!!.message + "fwiejfwejfwfhudhaiojasidsaidcvn") + _registerState.value = RegisterState.Success(response.body()!!.message + "Vjp Bro") } else { _registerState.value = RegisterState.Error("Registration failed: ${response.message()}") }