diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a082763..48fdd30 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -71,6 +71,7 @@ dependencies { implementation("com.google.accompanist:accompanist-navigation-material:0.24.13-rc") implementation("io.coil-kt:coil-compose:2.1.0") implementation ("com.google.code.gson:gson:2.10.1") + implementation("com.google.accompanist:accompanist-flowlayout:0.30.1") implementation(platform("com.google.firebase:firebase-bom:32.0.0")) implementation("com.google.firebase:firebase-analytics") diff --git a/app/src/main/java/com/example/lexilearn/MainActivity.kt b/app/src/main/java/com/example/lexilearn/MainActivity.kt index c4a4513..504c66c 100644 --- a/app/src/main/java/com/example/lexilearn/MainActivity.kt +++ b/app/src/main/java/com/example/lexilearn/MainActivity.kt @@ -5,6 +5,7 @@ import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -18,6 +19,7 @@ import com.example.lexilearn.ui.views.pLearAlphabet.LearnAlphabetScreen import com.example.lexilearn.ui.views.pNavMaterial.NavMaterialScreen import com.example.lexilearn.ui.views.pLearnNumber.LearnNumberScreen import com.example.lexilearn.ui.views.pLogin.LoginScreen +import com.example.lexilearn.ui.views.pQuiz.QuizScreen import com.example.lexilearn.ui.views.pQuiz.pRead.ReadScreen import com.example.lexilearn.ui.views.pQuiz.pSpell.SpellScreen import com.example.lexilearn.ui.views.pQuiz.pWrite.WriteScreen @@ -104,7 +106,19 @@ fun MyApp() { println("masuk kondisi else") } } + composable("quizScreen") { backStackEntry -> + val selectedMaterialList = navController.previousBackStackEntry + ?.savedStateHandle + ?.get>("selectedMaterialList") + val selectedMaterial = selectedMaterialList?.firstOrNull() + + if (selectedMaterial != null) { + QuizScreen(navController, selectedMaterial) + } else { + Text("Error: Data tidak tersedia") + } + } // composable("detailMaterial/{materialJson}") { backStackEntry -> // val jsonMaterial = backStackEntry.arguments?.getString("materialJson") // diff --git a/app/src/main/java/com/example/lexilearn/data/model/AnswerType.kt b/app/src/main/java/com/example/lexilearn/data/model/AnswerType.kt new file mode 100644 index 0000000..65558fe --- /dev/null +++ b/app/src/main/java/com/example/lexilearn/data/model/AnswerType.kt @@ -0,0 +1,5 @@ +package com.example.lexilearn.data.model + +enum class AnswerType { + FULL_WORD, SHUFFLED_LETTERS +} \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/data/model/MaterialDataModel.kt b/app/src/main/java/com/example/lexilearn/data/model/MaterialDataModel.kt index 337d2d8..a3b9d69 100644 --- a/app/src/main/java/com/example/lexilearn/data/model/MaterialDataModel.kt +++ b/app/src/main/java/com/example/lexilearn/data/model/MaterialDataModel.kt @@ -10,12 +10,4 @@ data class MaterialDataModel ( @SerializedName("enDescription") val enDescription: String = "", @SerializedName("image") val image: String = "", @SerializedName("category") val category: String = "" -// val id: String = "", -// val idName: String = "", -// val enName: String = "", -// val idDescription: String = "", -// val enDescription: String = "", -// val image: String = "", -// val category: String = "" - ) \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/data/model/QuestionType.kt b/app/src/main/java/com/example/lexilearn/data/model/QuestionType.kt new file mode 100644 index 0000000..02f1ee6 --- /dev/null +++ b/app/src/main/java/com/example/lexilearn/data/model/QuestionType.kt @@ -0,0 +1,5 @@ +package com.example.lexilearn.data.model + +enum class QuestionType { + TEXT, IMAGE, AUDIO +} \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/data/model/QuizAnswer.kt b/app/src/main/java/com/example/lexilearn/data/model/QuizAnswer.kt new file mode 100644 index 0000000..262adab --- /dev/null +++ b/app/src/main/java/com/example/lexilearn/data/model/QuizAnswer.kt @@ -0,0 +1,6 @@ +package com.example.lexilearn.data.model + +data class QuizAnswer( + val answerType: AnswerType, // FULL_WORD atau SHUFFLED_LETTERS + val correctWord: String // Kata yang benar dalam bahasa Inggris +) \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/data/model/QuizQuestion.kt b/app/src/main/java/com/example/lexilearn/data/model/QuizQuestion.kt new file mode 100644 index 0000000..f1a5e75 --- /dev/null +++ b/app/src/main/java/com/example/lexilearn/data/model/QuizQuestion.kt @@ -0,0 +1,7 @@ +package com.example.lexilearn.data.model + +data class QuizQuestion( + val questionType: QuestionType, // TEXT, IMAGE, AUDIO + val question: String, // Bisa teks, URL gambar, atau teks untuk TTS + val correctAnswer: QuizAnswer // Jawaban yang benar +) \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/data/repository/QuizRepository.kt b/app/src/main/java/com/example/lexilearn/data/repository/QuizRepository.kt new file mode 100644 index 0000000..b45f520 --- /dev/null +++ b/app/src/main/java/com/example/lexilearn/data/repository/QuizRepository.kt @@ -0,0 +1,54 @@ +package com.example.lexilearn.data.repository + +import com.example.lexilearn.data.model.AnswerType +import com.example.lexilearn.data.model.MaterialDataModel +import com.example.lexilearn.data.model.QuestionType +import com.example.lexilearn.data.model.QuizAnswer +import com.example.lexilearn.data.model.QuizQuestion +import kotlin.random.Random + + +class QuizRepository { + + /** + * Fungsi ini akan membuat soal dengan mode soal & jawaban yang DITENTUKAN oleh parameter. + */ + fun generateQuestion(material: MaterialDataModel, questionMode: Int, answerMode: Int): QuizQuestion { + val questionType = when (questionMode) { + 1 -> listOf(QuestionType.TEXT, QuestionType.IMAGE).random() // Text atau Image + 2 -> QuestionType.AUDIO // Hanya Audio + else -> QuestionType.TEXT // Default ke Text + } + + val question = when (questionType) { + QuestionType.TEXT -> listOf(material.idName, material.idDescription).random() + QuestionType.IMAGE -> material.image + QuestionType.AUDIO -> material.enName + } ?: "Soal tidak tersedia" + + val answerType = when (answerMode) { + 1 -> AnswerType.FULL_WORD + 2 -> AnswerType.SHUFFLED_LETTERS + else -> AnswerType.FULL_WORD + } + + return QuizQuestion( + questionType = questionType, + question = question, + correctAnswer = QuizAnswer( + answerType = answerType, + correctWord = material.enName + ) + ) + } + + /** + * Fungsi ini akan membuat soal dengan mode soal & jawaban yang DIPILIH SECARA ACAK. + */ + fun generateRandomQuestion(material: MaterialDataModel): QuizQuestion { + val randomQuestionMode = Random.nextInt(1, 3) // Acak antara 1 (Text/Image) atau 2 (Audio) + val randomAnswerMode = Random.nextInt(1, 3) // Acak antara 1 (FULL_WORD) atau 2 (SHUFFLED_LETTERS) + + return generateQuestion(material, randomQuestionMode, randomAnswerMode) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pDetailMaterial/DetailMaterialScreen.kt b/app/src/main/java/com/example/lexilearn/ui/views/pDetailMaterial/DetailMaterialScreen.kt index 6669f3b..5d07318 100644 --- a/app/src/main/java/com/example/lexilearn/ui/views/pDetailMaterial/DetailMaterialScreen.kt +++ b/app/src/main/java/com/example/lexilearn/ui/views/pDetailMaterial/DetailMaterialScreen.kt @@ -1,8 +1,6 @@ package com.example.lexilearn.ui.views.pDetailMaterial -import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -26,7 +24,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -40,10 +37,8 @@ import com.example.lexilearn.ui.components.FirebaseImage import com.example.lexilearn.ui.components.GradientQuiz import com.example.lexilearn.ui.components.HorizontalLine import com.example.lexilearn.ui.theme.cAccent -import com.example.lexilearn.ui.theme.cGray import com.example.lexilearn.ui.theme.ctextBlack import com.example.lexilearn.ui.theme.ctextGray -import com.example.lexilearn.ui.theme.cwhite @Composable fun DetailMaterialScreen(navController: NavController, materialListData: List) { @@ -159,7 +154,17 @@ fun DetailMaterialScreen(navController: NavController, materialListData: List tapi hanya satu elemen + navController.currentBackStackEntry + ?.savedStateHandle + ?.set("selectedMaterialList", listOf(selectedMaterial)) + + // Navigasi ke QuizScreen + navController.navigate("quizScreen") + } +// navController.navigate("spell") }, text = "Kerjakan Kuis", painter = painterResource(id = R.drawable.ic_next), diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pDetailMaterial/DetailMaterialViewModel.kt b/app/src/main/java/com/example/lexilearn/ui/views/pDetailMaterial/DetailMaterialViewModel.kt index b9225ca..aca5322 100644 --- a/app/src/main/java/com/example/lexilearn/ui/views/pDetailMaterial/DetailMaterialViewModel.kt +++ b/app/src/main/java/com/example/lexilearn/ui/views/pDetailMaterial/DetailMaterialViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.example.lexilearn.data.model.MaterialDataModel import com.example.lexilearn.data.repository.MaterialRepository +import com.example.lexilearn.domain.models.ModelSpell import com.example.lexilearn.utils.generateNumberList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -22,12 +23,14 @@ class DetailMaterialViewModel(application: Application) : AndroidViewModel(appli private val repository = MaterialRepository(application) + private val _materialList = MutableStateFlow>(emptyList()) val materialList: StateFlow> = _materialList private val _textTitle = MutableLiveData("Hello, Jetpack Compose!") val textTitle: LiveData = _textTitle + fun fetchMaterial(materialListData: List) { _materialList.value = materialListData if (materialListData.isNotEmpty()) { @@ -35,9 +38,9 @@ class DetailMaterialViewModel(application: Application) : AndroidViewModel(appli _textTitle.value = "hewan" } else if (materialListData[0].category == "limb") { _textTitle.value = "anggota tubuh" - }else if (materialListData[0].category == "house") { + } else if (materialListData[0].category == "house") { _textTitle.value = "bagian rumah" - }else if (materialListData[0].category == "family") { + } else if (materialListData[0].category == "family") { _textTitle.value = "anggota keluarga" } } diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/QuizScreen.kt b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/QuizScreen.kt new file mode 100644 index 0000000..2655bbd --- /dev/null +++ b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/QuizScreen.kt @@ -0,0 +1,668 @@ +package com.example.lexilearn.ui.views.pQuiz + +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.boundsInWindow +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import com.example.lexilearn.data.model.MaterialDataModel +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.VolumeUp +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Surface +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.constraintlayout.compose.ConstraintLayout +import com.example.lexilearn.R +import com.example.lexilearn.data.model.QuestionType +import com.example.lexilearn.domain.models.ModelAnswerRead +import com.example.lexilearn.ui.components.ButtonNext +import com.example.lexilearn.ui.components.CardQuiz +import com.example.lexilearn.ui.components.DraggableAnswerCard +import com.example.lexilearn.ui.components.FirebaseImage +import com.example.lexilearn.ui.components.GradientQuiz +import com.example.lexilearn.ui.components.MyShadowCard +import com.example.lexilearn.ui.theme.ctextGray +import com.example.lexilearn.ui.theme.ctextWhite +import com.google.accompanist.flowlayout.FlowRow +import com.google.accompanist.flowlayout.MainAxisAlignment +import kotlin.math.roundToInt + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun QuizScreen( + navController: NavController, + material: MaterialDataModel, + quizViewModel: QuizViewModel = viewModel() +) { + val question by quizViewModel.currentQuestion.collectAsState() + val isAnswerCorrect by quizViewModel.isAnswerCorrect.collectAsState() + val isTTSInitialized = quizViewModel.isTTSInitialized.observeAsState(false) + + LaunchedEffect(Unit) { + quizViewModel.loadRandomQuestion(material) + } + + var rectColumnAnswer by remember { mutableStateOf(Rect.Zero) } + + val cardSize = remember { + mutableStateMapOf() + } + + val maxSize = 120.dp + + val minSize = 70.dp + val dataQuiz by quizViewModel.questionShuffled.collectAsState() + val listAnswer by quizViewModel.shuffledAnswerLetters.collectAsState() + +// var dataQuiz = remember { +// mutableStateListOf( +// ModelSpell(1, true, "? ", showCard = false), +// ModelSpell(2, true, "?", showCard = false), +// ModelSpell(3, true, "?", showCard = false), +// ModelSpell(4, true, "?", showCard = false), +// ) +// } + +// val listAnswer = +// remember { +// mutableStateListOf( +// ModelAnswerRead(1, "a"), +// ModelAnswerRead(2, "c"), +// ModelAnswerRead(3, "d"), +// ModelAnswerRead(4, "k") +// ) +// } + + val quizXOffset = remember { + mutableStateMapOf() + } + + val quizYOffset = remember { + mutableStateMapOf() + } + + val boxRectDragable = remember { + mutableStateMapOf() + } + + val boxRectQuiz = remember { + mutableStateMapOf() + } + + val answerXOffset = remember { + mutableStateMapOf() + } + + val answerYOffset = remember { + mutableStateMapOf() + } + + val boxRectAnswer = remember { + mutableStateMapOf() + } + + Surface(modifier = Modifier.fillMaxSize()) { + GradientQuiz( + navController = navController, + headerText = stringResource(id = R.string.spelltitle), + modifier = Modifier.fillMaxSize() + ) { + ConstraintLayout { + val buttonRef = createRef() + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + Text( + "Level: 3", + modifier = Modifier.padding(22.dp), + fontWeight = FontWeight.SemiBold, + fontSize = 18.sp, + color = ctextWhite + ) + } + MyShadowCard( + modifier = Modifier + .padding(12.dp) + .fillMaxWidth() + ) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(12.dp)) +// Text( +// text = "Susun Kata", +// fontSize = 20.sp, +// fontWeight = FontWeight.Bold +// ) +// Spacer(modifier = Modifier.height(12.dp)) +// Image( +// painter = painterResource(id = R.drawable.ic_news), +// contentDescription = "image", +// modifier = Modifier.size(120.dp) +// ) + if (question != null) { + if (question?.questionType == QuestionType.IMAGE) { + FirebaseImage( + path = question!!.question, + contentScale = ContentScale.Crop, + modifier = Modifier.size(200.dp) + ) + + } else if (question?.questionType == QuestionType.TEXT) { + Text( + text = question!!.question, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(12.dp) + ) + } else if (question?.questionType == QuestionType.AUDIO) { + IconButton( + onClick = { + quizViewModel.speakLetter(question!!.question) + }, + modifier = Modifier + .size(160.dp) + + ) { + Icon( + imageVector = Icons.Filled.VolumeUp, // 🔥 Ikon Speaker dari Material Icons + contentDescription = "Speaker Icon", + tint = Color.Black // 🔥 Warna ikon + ) + } + } + } + + Spacer(modifier = Modifier.height(12.dp)) +// Row( +// modifier = Modifier.fillMaxWidth(), +// horizontalArrangement = Arrangement.SpaceAround +// ) { +// dataQuiz.forEach { dt -> +// val id = dt.id +// if (!boxRectDragable.containsKey(id)) +// boxRectDragable[id] = Rect.Zero +// if (!boxRectQuiz.containsKey(id)) +// boxRectQuiz[id] = Rect.Zero +// if (!quizXOffset.containsKey(id)) +// quizXOffset[id] = 0f +// if (!quizYOffset.containsKey(id)) +// quizYOffset[id] = 0f +// CardQuiz( +// modifier = Modifier +// .padding(vertical = 10.dp) +// .size(minSize) +// .onGloballyPositioned { coordinates -> +// if (dt.type) +// boxRectQuiz[id] = coordinates.boundsInWindow() +// } +// ) { +// if (dt.type) { +// Text( +// text = dt.data, // Use the state to display text +// color = ctextWhite, +// fontWeight = FontWeight.Bold, +// textAlign = TextAlign.Center, +// modifier = Modifier.fillMaxWidth() +// ) +// if (dt.showCard) { +// DraggableAnswerCard( +// item = dt.data, +// modifier = Modifier +// .offset { +// val xOffset = quizXOffset[id] ?: 0f +// val yOffset = quizYOffset[id] ?: 0f +// IntOffset( +// xOffset.roundToInt(), +// yOffset.roundToInt() +// ) +// } +// .onGloballyPositioned { +// boxRectDragable[id] = +// it.boundsInWindow() +// } +// .fillMaxSize() +// .pointerInput(Unit) { +// detectDragGestures( +// onDrag = { change, dragAmount -> +// change.consume() +// quizXOffset[id] = +// quizXOffset[id]!! + dragAmount.x +// quizYOffset[id] = +// quizYOffset[id]!! + dragAmount.y +// }, +// onDragEnd = { +// var checkNull = false +// for ((ind, entry) in boxRectQuiz.entries.withIndex()) { +// val (key, rect) = entry +// if (key == id) +// continue +// +// if (dataQuiz[ind].hasContent) +// continue +// +// if (boxRectDragable[id]!!.overlaps( +// rect +// ) +// ) { +// quizViewModel.updateQuestionShuffled( +// ind, +// dt +// ) +//// dataQuiz = +//// dataQuiz.apply { +//// this[ind] = +//// this[ind].copy( +//// data = dt.data, +//// showCard = true, +//// emp = dt.emp, +//// hasContent = true +//// ) +//// } +// dt.apply { +// hasContent = false +// showCard = false +// data = "?" +// } +// checkNull = true +// quizXOffset[id] = 0f +// quizYOffset[id] = 0f +// break +// } +// +// } +// if (boxRectDragable[id]!!.overlaps( +// rectColumnAnswer +// ) +// ) { +// val emDt = dt.emp +// if (emDt != null) { +// dt.apply { +// hasContent = false +// showCard = false +// data = "?" +// } +// checkNull = true +// cardSize[emDt] = maxSize +// quizXOffset[id] = 0f +// quizYOffset[id] = 0f +// dt.data = "?" +// } +// } +// if (!checkNull) { +// quizXOffset[id] = 0f +// quizYOffset[id] = 0f +// } +// } +// ) +// } +// ) +// } +// } else { +// Box(modifier = Modifier.align(Alignment.CenterVertically)) { +// Text( +// text = dt.data, +// color = ctextWhite, +// fontSize = 20.sp, +// fontWeight = FontWeight.Bold, +// textAlign = TextAlign.Center, +// ) +// } +// +// } +// } +// } +// } + FlowRow( + modifier = Modifier.fillMaxWidth().padding(12.dp), + mainAxisSpacing = 8.dp, // Jarak horizontal antar item + crossAxisSpacing = 8.dp, // Jarak vertikal antar item + mainAxisAlignment = MainAxisAlignment.Center + ) { + dataQuiz.forEach { dt -> + val id = dt.id + if (!boxRectDragable.containsKey(id)) + boxRectDragable[id] = Rect.Zero + if (!boxRectQuiz.containsKey(id)) + boxRectQuiz[id] = Rect.Zero + if (!quizXOffset.containsKey(id)) + quizXOffset[id] = 0f + if (!quizYOffset.containsKey(id)) + quizYOffset[id] = 0f + + CardQuiz( + modifier = Modifier + .padding(vertical = 10.dp) + .size(minSize) + .onGloballyPositioned { coordinates -> + if (dt.type) + boxRectQuiz[id] = coordinates.boundsInWindow() + } + ) { + if (dt.type) { + Text( + text = dt.data, + color = ctextWhite, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + if (dt.showCard) { + DraggableAnswerCard( + item = dt.data, + modifier = Modifier + .offset { + val xOffset = quizXOffset[id] ?: 0f + val yOffset = quizYOffset[id] ?: 0f + IntOffset( + xOffset.roundToInt(), + yOffset.roundToInt() + ) + } + .onGloballyPositioned { + boxRectDragable[id] = it.boundsInWindow() + } + .fillMaxSize() + .pointerInput(Unit) { + detectDragGestures( + onDrag = { change, dragAmount -> + change.consume() + quizXOffset[id] = + quizXOffset[id]!! + dragAmount.x + quizYOffset[id] = + quizYOffset[id]!! + dragAmount.y + }, + onDragEnd = { + var checkNull = false + for ((ind, entry) in boxRectQuiz.entries.withIndex()) { + val (key, rect) = entry + if (key == id) + continue + + if (dataQuiz[ind].hasContent) + continue + + if (boxRectDragable[id]!!.overlaps(rect)) { + quizViewModel.updateQuestionShuffled(ind, dt) + dt.apply { + hasContent = false + showCard = false + data = "?" + } + checkNull = true + quizXOffset[id] = 0f + quizYOffset[id] = 0f + break + } + } + if (boxRectDragable[id]!!.overlaps(rectColumnAnswer)) { + val emDt = dt.emp + if (emDt != null) { + dt.apply { + hasContent = false + showCard = false + data = "?" + } + checkNull = true + cardSize[emDt] = maxSize + quizXOffset[id] = 0f + quizYOffset[id] = 0f + dt.data = "?" + } + } + if (!checkNull) { + quizXOffset[id] = 0f + quizYOffset[id] = 0f + } + } + ) + } + ) + } + } else { + Box(modifier = Modifier) { + Text( + text = dt.data, + color = ctextWhite, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + ) + } + } + } + } + } + Spacer(modifier = Modifier.height(12.dp)) + } + } + Spacer(modifier = Modifier.height(12.dp)) + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .background(ctextGray) + ) + Spacer(modifier = Modifier.height(12.dp)) + Column( + modifier = Modifier + .fillMaxWidth() + .onGloballyPositioned { + rectColumnAnswer = it.boundsInWindow() + }, + ) { + listAnswer.chunked(2).forEach { rowItems -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + rowItems.forEach { item -> + val id = item.id + if (!cardSize.containsKey(id)) + cardSize[id] = maxSize + if (!boxRectAnswer.containsKey(id)) + boxRectAnswer[id] = Rect.Zero + if (!answerXOffset.containsKey(id)) + answerXOffset[id] = 0f + if (!answerYOffset.containsKey(id)) + answerYOffset[id] = 0f + DraggableAnswerCard( + item = item.data, + modifier = Modifier + .padding(10.dp) + .size(cardSize[id]!!) + .offset { + IntOffset( + answerXOffset[id]!!.roundToInt(), + answerYOffset[id]!!.roundToInt() + ) + } + .onGloballyPositioned { coordinates -> + boxRectAnswer[id] = coordinates.boundsInWindow() + } + .pointerInput(Unit) { + detectDragGestures( + onDrag = { change, dragAmount -> + change.consume() + answerXOffset[id] = + answerXOffset[id]!! + dragAmount.x + answerYOffset[id] = + answerYOffset[id]!! + dragAmount.y + cardSize[id] = minSize + }, + onDragEnd = { + var checkNull = false + for ((ind, entry) in boxRectQuiz.entries.withIndex()) { + val (_, rect) = entry + if (dataQuiz[ind].hasContent) + continue + + if (boxRectAnswer[id]!!.overlaps( + rect + ) + ) { + cardSize[id] = minSize + quizViewModel.updateQuestionShuffled( + ind, + dataQuiz[ind].copy(data = item.data, showCard = true, emp = id, hasContent = true) + ) +// dataQuiz = dataQuiz +// .apply { +// this[ind] = +// this[ind].copy( +// data = item.data, +// showCard = true, +// emp = id, +// hasContent = true +// ) +// } + checkNull = true + cardSize[id] = 0.dp + answerXOffset[id] = 0f + answerYOffset[id] = 0f + break + } + } + if (!checkNull) { + cardSize[id] = maxSize + answerXOffset[id] = 0f + answerYOffset[id] = 0f + } + } + ) + } + ) + } + } + } + } + Spacer(modifier = Modifier.height(12.dp)) + } + + ButtonNext( + onclick = { + navController.navigate("write") + }, + text = stringResource(id = R.string.next), + painter = painterResource(id = R.drawable.ic_next), + modifier = Modifier + .padding(vertical = 30.dp, horizontal = 50.dp) + .fillMaxWidth() + .constrainAs(buttonRef) { + bottom.linkTo(parent.bottom) + } + ) + } + + } + } +} + +// +//@Composable +//fun DragAndDropAnswer(correctAnswer: String, onAnswerSubmitted: (String) -> Unit) { +// val shuffledLetters = correctAnswer.toList().shuffled() +// +// var selectedLetters by remember { mutableStateOf("") } +// val answerBoxRect = remember { mutableStateOf(Rect.Zero) } +// +// Column( +// modifier = Modifier.fillMaxWidth(), +// horizontalAlignment = Alignment.CenterHorizontally +// ) { +// Box( +// modifier = Modifier +// .size(150.dp, 50.dp) +// .background(Color.LightGray) +// .onGloballyPositioned { answerBoxRect.value = it.boundsInWindow() }, +// contentAlignment = Alignment.Center +// ) { +// Text(text = selectedLetters, fontSize = 20.sp) +// } +// +// Spacer(modifier = Modifier.height(20.dp)) +// +// Row { +// shuffledLetters.forEach { letter -> +// DraggableLetter( +// letter = letter.toString(), +// onDrop = { +// selectedLetters += letter +// if (selectedLetters.length == correctAnswer.length) { +// onAnswerSubmitted(selectedLetters) +// } +// } +// ) +// } +// } +// } +//} +// +//@Composable +//fun DraggableLetter(letter: String, onDrop: () -> Unit) { +// var offsetX by remember { mutableStateOf(0f) } +// var offsetY by remember { mutableStateOf(0f) } +// +// Box( +// modifier = Modifier +// .size(50.dp) +// .background(Color.Blue) +// .pointerInput(Unit) { +// detectDragGestures( +// onDrag = { change, dragAmount -> +// change.consume() +// offsetX += dragAmount.x +// offsetY += dragAmount.y +// }, +// onDragEnd = { onDrop() } +// ) +// }, +// contentAlignment = Alignment.Center +// ) { +// Text(text = letter, fontSize = 20.sp, color = Color.White) +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/QuizViewModel.kt b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/QuizViewModel.kt new file mode 100644 index 0000000..f2655dc --- /dev/null +++ b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/QuizViewModel.kt @@ -0,0 +1,120 @@ +package com.example.lexilearn.ui.views.pQuiz + +import android.app.Application +import android.speech.tts.TextToSpeech +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.example.lexilearn.data.model.MaterialDataModel +import com.example.lexilearn.data.model.QuizQuestion +import com.example.lexilearn.data.repository.QuizRepository +import com.example.lexilearn.domain.models.ModelAnswerRead +import com.example.lexilearn.domain.models.ModelSpell +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import java.util.Locale + +class QuizViewModel(application: Application) : AndroidViewModel(application), + TextToSpeech.OnInitListener { + private val quizRepository = QuizRepository() + + private val _currentQuestion = MutableStateFlow(null) + val currentQuestion: StateFlow = _currentQuestion + + private val _userAnswer = MutableStateFlow(null) + val userAnswer: StateFlow = _userAnswer + + private val _isAnswerCorrect = MutableStateFlow(null) + val isAnswerCorrect: StateFlow = _isAnswerCorrect + + fun loadNewQuestion(material: MaterialDataModel, questionMode: Int, answerMode: Int) { + _currentQuestion.value = quizRepository.generateQuestion(material, questionMode, answerMode) + } + + private val _shuffledAnswerLetters = MutableStateFlow>(emptyList()) + val shuffledAnswerLetters: StateFlow> = _shuffledAnswerLetters +// fun updateShuffledLetter(index: Int, newData: ModelAnswerRead) { +// _shuffledAnswerLetters.value = _shuffledAnswerLetters.value.mapIndexed { i, item -> +// if (i == index) { +// item.copy( +// data = newData.data, +// showCard = true, +// emp = newData.emp, +// hasContent = true +// ) +// } else { +// item +// } +// } +// } + + + private val _questionShuffled = MutableStateFlow>(emptyList()) + val questionShuffled: StateFlow> = _questionShuffled + + fun updateQuestionShuffled(index: Int, newData: ModelSpell) { + _questionShuffled.value = _questionShuffled.value.mapIndexed { i, item -> + if (i == index) { + item.copy( + data = newData.data, + showCard = true, + emp = newData.emp, + hasContent = true + ) + } else { + item + } + } + } + + + fun loadRandomQuestion(material: MaterialDataModel) { +// _currentQuestion.value = quizRepository.generateRandomQuestion(material) + _currentQuestion.value = quizRepository.generateQuestion(material, 1, 2) + val dataQuest = _currentQuestion.value?.correctAnswer?.correctWord?.mapIndexed { index, char -> + ModelSpell(index + 1, true, "?", showCard = false) + } ?: emptyList() + _questionShuffled.value = dataQuest + val listAnswer = _currentQuestion.value?.correctAnswer?.correctWord?.mapIndexed { index, char -> + ModelAnswerRead(index + 1, char.toString()) + } ?: emptyList() + _shuffledAnswerLetters.value = listAnswer + println("testing_question ${_currentQuestion.value}") + println("testing_question ${material}") + } + + fun submitAnswer(answer: String) { + _userAnswer.value = answer + _isAnswerCorrect.value = + _currentQuestion.value?.correctAnswer?.correctWord?.equals(answer, ignoreCase = true) + } + + private var tts: TextToSpeech? = null + private val _isTTSInitialized = MutableLiveData(false) + val isTTSInitialized: LiveData = _isTTSInitialized + + init { + tts = TextToSpeech(application, this) + } + + override fun onInit(status: Int) { + if (status == TextToSpeech.SUCCESS) { + tts?.language = Locale.ENGLISH + _isTTSInitialized.value = true + } else { + _isTTSInitialized.value = false + } + } + + fun speakLetter(letter: String) { + if (_isTTSInitialized.value == true) { + tts?.speak(letter, TextToSpeech.QUEUE_FLUSH, null, null) + } + } + + override fun onCleared() { + tts?.stop() + tts?.shutdown() + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pComponentsQuiz/AnswerShuffled.kt b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pComponentsQuiz/AnswerShuffled.kt new file mode 100644 index 0000000..c219233 --- /dev/null +++ b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pComponentsQuiz/AnswerShuffled.kt @@ -0,0 +1,4 @@ +package com.example.lexilearn.ui.views.pQuiz.pComponentsQuiz + +class AnswerShuffled { +} \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pRead/ReadScreen.kt b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pRead/ReadScreen.kt index 281a5ec..31b1283 100644 --- a/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pRead/ReadScreen.kt +++ b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pRead/ReadScreen.kt @@ -1,9 +1,6 @@ package com.example.lexilearn.ui.views.pQuiz.pRead -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint + import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pSpell/SpellScreen.kt b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pSpell/SpellScreen.kt index 79a1dfb..db2cfc6 100644 --- a/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pSpell/SpellScreen.kt +++ b/app/src/main/java/com/example/lexilearn/ui/views/pQuiz/pSpell/SpellScreen.kt @@ -1,31 +1,19 @@ package com.example.lexilearn.ui.views.pQuiz.pSpell -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable