update quiz
This commit is contained in:
parent
afb61f8cac
commit
133fa954fd
|
@ -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")
|
||||
|
|
|
@ -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<List<MaterialDataModel>>("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")
|
||||
//
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package com.example.lexilearn.data.model
|
||||
|
||||
enum class AnswerType {
|
||||
FULL_WORD, SHUFFLED_LETTERS
|
||||
}
|
|
@ -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 = ""
|
||||
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
package com.example.lexilearn.data.model
|
||||
|
||||
enum class QuestionType {
|
||||
TEXT, IMAGE, AUDIO
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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<MaterialDataModel>) {
|
||||
|
@ -159,7 +154,17 @@ fun DetailMaterialScreen(navController: NavController, materialListData: List<Ma
|
|||
}
|
||||
ButtonNext(
|
||||
onclick = {
|
||||
navController.navigate("spell")
|
||||
val selectedMaterial = materialListData.firstOrNull()
|
||||
if (selectedMaterial != null) {
|
||||
// Simpan ke savedStateHandle sebagai List<MaterialDataModel> 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),
|
||||
|
|
|
@ -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<List<MaterialDataModel>>(emptyList())
|
||||
val materialList: StateFlow<List<MaterialDataModel>> = _materialList
|
||||
|
||||
private val _textTitle = MutableLiveData("Hello, Jetpack Compose!")
|
||||
val textTitle: LiveData<String> = _textTitle
|
||||
|
||||
|
||||
fun fetchMaterial(materialListData: List<MaterialDataModel>) {
|
||||
_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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Int, Dp>()
|
||||
}
|
||||
|
||||
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<Int, Float>()
|
||||
}
|
||||
|
||||
val quizYOffset = remember {
|
||||
mutableStateMapOf<Int, Float>()
|
||||
}
|
||||
|
||||
val boxRectDragable = remember {
|
||||
mutableStateMapOf<Int, Rect>()
|
||||
}
|
||||
|
||||
val boxRectQuiz = remember {
|
||||
mutableStateMapOf<Int, Rect>()
|
||||
}
|
||||
|
||||
val answerXOffset = remember {
|
||||
mutableStateMapOf<Int, Float>()
|
||||
}
|
||||
|
||||
val answerYOffset = remember {
|
||||
mutableStateMapOf<Int, Float>()
|
||||
}
|
||||
|
||||
val boxRectAnswer = remember {
|
||||
mutableStateMapOf<Int, Rect>()
|
||||
}
|
||||
|
||||
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)
|
||||
// }
|
||||
//}
|
|
@ -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<QuizQuestion?>(null)
|
||||
val currentQuestion: StateFlow<QuizQuestion?> = _currentQuestion
|
||||
|
||||
private val _userAnswer = MutableStateFlow<String?>(null)
|
||||
val userAnswer: StateFlow<String?> = _userAnswer
|
||||
|
||||
private val _isAnswerCorrect = MutableStateFlow<Boolean?>(null)
|
||||
val isAnswerCorrect: StateFlow<Boolean?> = _isAnswerCorrect
|
||||
|
||||
fun loadNewQuestion(material: MaterialDataModel, questionMode: Int, answerMode: Int) {
|
||||
_currentQuestion.value = quizRepository.generateQuestion(material, questionMode, answerMode)
|
||||
}
|
||||
|
||||
private val _shuffledAnswerLetters = MutableStateFlow<List<ModelAnswerRead>>(emptyList())
|
||||
val shuffledAnswerLetters: StateFlow<List<ModelAnswerRead>> = _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<List<ModelSpell>>(emptyList())
|
||||
val questionShuffled: StateFlow<List<ModelSpell>> = _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<Boolean> = _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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package com.example.lexilearn.ui.views.pQuiz.pComponentsQuiz
|
||||
|
||||
class AnswerShuffled {
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue