update unlock material

This commit is contained in:
DimazzP 2025-02-21 09:32:07 +07:00
parent 522e568a9e
commit 98474010d0
9 changed files with 489 additions and 251 deletions

View File

@ -7,9 +7,11 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.lexilearn.data.model.MaterialDataModel
import com.example.lexilearn.data.model.UserModel
import com.example.lexilearn.data.repository.MaterialRepository
@ -91,6 +93,15 @@ fun MyApp() {
composable("resultscreening") { ResultScreeningScreen(navController) }
composable("learnAlphabet") { LearnAlphabetScreen(navController) }
composable("learnNumber") { LearnNumberScreen(navController) }
// composable(
// "navMaterial/{materialId}",
// arguments = listOf(
// navArgument("materialId") { type = NavType.StringType }
// )
// ) { backStackEntry ->
// val materialId = backStackEntry.arguments?.getString("materialId") ?: ""
// NavMaterialScreen(navController, materialId)
// }
composable("navMaterial/{materialId}") { backStackEntry ->
val materialId = backStackEntry.arguments?.getString("materialId")
NavMaterialScreen(navController, materialId ?: "")
@ -99,9 +110,11 @@ fun MyApp() {
val materialList = navController.previousBackStackEntry
?.savedStateHandle
?.get<List<MaterialDataModel>>("materialList")
val indexValue = navController.previousBackStackEntry
?.savedStateHandle
?.get<Int>("indexValue") ?: -1
if (materialList != null) {
DetailMaterialScreen(navController, materialList)
DetailMaterialScreen(navController, materialList, indexValue)
}else{
println("masuk kondisi else")
}
@ -111,14 +124,21 @@ fun MyApp() {
?.savedStateHandle
?.get<List<MaterialDataModel>>("selectedMaterialList")
val selectedMaterial = selectedMaterialList?.firstOrNull()
val typeQuiz = navController.previousBackStackEntry
?.savedStateHandle
?.get<String>("typeQuiz") ?: "default"
if (selectedMaterial != null) {
QuizScreen(navController, selectedMaterial)
val indexValue = navController.previousBackStackEntry
?.savedStateHandle
?.get<Int>("indexValue") ?: 0
if (!selectedMaterialList.isNullOrEmpty()) {
QuizScreen(navController, selectedMaterialList, typeQuiz, indexValue)
} else {
Text("Error: Data tidak tersedia")
}
}
// composable("detailMaterial/{materialJson}") { backStackEntry ->
// val jsonMaterial = backStackEntry.arguments?.getString("materialJson")
//

View File

@ -65,24 +65,27 @@ class MaterialRepository(private val context: Context) {
}
fun updateUnlockData(idUnlock: String, value: Int, callback: (Boolean) -> Unit) {
try {
val userId = sharedPrefHelper.getUserId()
if (userId == null) {
println("printUpdateUnlock: userId is null")
callback(false)
return
}
val userRef = FirebaseDatabase.getInstance().getReference("users/$userId/unlock_data/$idUnlock")
// 🔥 Ambil data user dari Firebase
// Ambil data user dari Firebase dengan try-catch di dalam callback
userRef.get().addOnSuccessListener { snapshot ->
try {
val currentValue = snapshot.getValue(Int::class.java) ?: 0
// 🔥 Hanya update jika nilai baru lebih besar
// Hanya update jika nilai baru lebih besar
if (value > currentValue) {
userRef.setValue(value).addOnCompleteListener { task ->
try {
if (task.isSuccessful) {
// 🔥 Update juga di SharedPreferences
// Update juga di SharedPreferences
val updatedUser = sharedPrefHelper.getUserData()
updatedUser?.unlock_data?.let { unlockData ->
when (idUnlock) {
@ -92,20 +95,110 @@ class MaterialRepository(private val context: Context) {
"limb" -> unlockData.limb = value
}
}
sharedPrefHelper.saveUserData(updatedUser!!)
// Pastikan updatedUser tidak null sebelum menyimpan
if (updatedUser != null) {
println("printUpdateUnlock: successfully updated Firebase and SharedPreferences")
sharedPrefHelper.saveUserData(updatedUser)
callback(true)
} else {
println("printUpdateUnlock: updatedUser is null after updating Firebase")
callback(false)
}
} else {
println("printUpdateUnlock: task is not successful")
callback(false)
}
} catch (e: Exception) {
e.printStackTrace()
println("printUpdateUnlock: exception in onCompleteListener: $e")
callback(false)
}
}
} else {
callback(false) // 🔥 Jika nilai baru <= nilai lama, tidak update
println("printUpdateUnlock: new value $value is not greater than current value $currentValue")
callback(false) // Jika nilai baru <= nilai lama, tidak update
}
} catch (e: Exception) {
e.printStackTrace()
println("printUpdateUnlock: exception in onSuccessListener: $e")
callback(false)
}
}.addOnFailureListener {
it.printStackTrace()
println("printUpdateUnlock: failed to get data: ${it.message}")
callback(false)
}
} catch (e: Exception) {
println("printUpdateUnlock: error_update_unlock_data: $e")
e.printStackTrace()
callback(false)
}
}
// fun updateUnlockData(idUnlock: String, value: Int, callback: (Boolean) -> Unit) {
// try {
// val userId = sharedPrefHelper.getUserId()
// if (userId == null) {
// callback(false)
// return
// }
//
// val userRef = FirebaseDatabase.getInstance().getReference("users/$userId/unlock_data/$idUnlock")
//
// // Ambil data user dari Firebase dengan try-catch di dalam callback
// userRef.get().addOnSuccessListener { snapshot ->
// try {
// val currentValue = snapshot.getValue(Int::class.java) ?: 0
//
// // Hanya update jika nilai baru lebih besar
// if (value > currentValue) {
// userRef.setValue(value).addOnCompleteListener { task ->
// try {
// if (task.isSuccessful) {
// // Update juga di SharedPreferences
// val updatedUser = sharedPrefHelper.getUserData()
// updatedUser?.unlock_data?.let { unlockData ->
// when (idUnlock) {
// "animal" -> unlockData.animal = value
// "family" -> unlockData.family = value
// "house" -> unlockData.house = value
// "limb" -> unlockData.limb = value
// }
// }
// // Pastikan updatedUser tidak null sebelum menyimpan
// if (updatedUser != null) {
// sharedPrefHelper.saveUserData(updatedUser)
// callback(true)
// } else {
// callback(false)
// }
// } else {
// callback(false)
// }
// } catch (e: Exception) {
// e.printStackTrace()
// callback(false)
// }
// }
// } else {
// callback(false) // Jika nilai baru <= nilai lama, tidak update
// }
// } catch (e: Exception) {
// e.printStackTrace()
// callback(false)
// }
// }.addOnFailureListener {
// it.printStackTrace()
// callback(false)
// }
// } catch (e: Exception) {
// println("error_update_unlock_data: $e")
// e.printStackTrace()
// callback(false)
// }
// }
fun getUnlockValue(idUnlock: String, callback: (Int?) -> Unit) {
val userId = sharedPrefHelper.getUserId()

View File

@ -41,7 +41,7 @@ import com.example.lexilearn.ui.theme.ctextBlack
import com.example.lexilearn.ui.theme.ctextGray
@Composable
fun DetailMaterialScreen(navController: NavController, materialListData: List<MaterialDataModel>) {
fun DetailMaterialScreen(navController: NavController, materialListData: List<MaterialDataModel>, indexValue: Int) {
val viewModel: DetailMaterialViewModel = viewModel()
LaunchedEffect(materialListData) {
viewModel.fetchMaterial(materialListData)
@ -154,17 +154,19 @@ fun DetailMaterialScreen(navController: NavController, materialListData: List<Ma
}
ButtonNext(
onclick = {
val selectedMaterial = materialListData.firstOrNull()
if (selectedMaterial != null) {
// Simpan ke savedStateHandle sebagai List<MaterialDataModel> tapi hanya satu elemen
if (materialListData.isNotEmpty()) {
navController.currentBackStackEntry
?.savedStateHandle
?.set("selectedMaterialList", listOf(selectedMaterial))
// Navigasi ke QuizScreen
?.set("selectedMaterialList", viewModel.randomMaterialModel())
navController.currentBackStackEntry
?.savedStateHandle
?.set("typeQuiz", materialListData[0].category)
navController.currentBackStackEntry
?.savedStateHandle
?.set("indexValue", indexValue)
navController.navigate("quizScreen")
}
// navController.navigate("spell")
},
text = "Kerjakan Kuis",
painter = painterResource(id = R.drawable.ic_next),

View File

@ -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.data.repository.QuizRepository
import com.example.lexilearn.domain.models.ModelSpell
import com.example.lexilearn.utils.generateNumberList
import kotlinx.coroutines.flow.MutableStateFlow
@ -20,6 +21,7 @@ class DetailMaterialViewModel(application: Application) : AndroidViewModel(appli
private val _isTTSInitialized = MutableLiveData(false)
val isTTSInitialized: LiveData<Boolean> = _isTTSInitialized
val alphabetList = generateNumberList()
private val quizRepository = QuizRepository()
private val repository = MaterialRepository(application)
@ -46,6 +48,10 @@ class DetailMaterialViewModel(application: Application) : AndroidViewModel(appli
}
}
fun randomMaterialModel(): List<MaterialDataModel> {
return _materialList.value.shuffled()
}
init {
tts = TextToSpeech(application, this)
}

View File

@ -249,7 +249,7 @@ fun HomeScreen(navController: NavController) {
)
}
}
ButtonHome(onClick = { },
ButtonHome(onClick = { navController.navigate("navMaterial/house") },
color = Color(0xffea2b72),
modifier = Modifier
@ -304,7 +304,7 @@ fun HomeScreen(navController: NavController) {
)
}
}
ButtonHome(onClick = { navController.navigate("read") },
ButtonHome(onClick = { navController.navigate("navMaterial/family") },
color = Color(0xffedb92f),
modifier = Modifier
.constrainAs(newsRef) {

View File

@ -17,6 +17,9 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
@ -44,6 +47,16 @@ fun NavMaterialScreen(navController: NavController, materialId: String) {
viewModel.fetchMaterial(materialId)
}
val numberUnlock by navController.currentBackStackEntry
?.savedStateHandle
?.getLiveData<Int>("numberUnlock")
?.observeAsState(initial = 0) ?: remember { mutableIntStateOf(0) }
LaunchedEffect(numberUnlock) {
println("printUpdateUnlock-nav $numberUnlock")
viewModel.updateUnlockValue(materialId, numberUnlock)
}
val materialList by viewModel.materialList.collectAsState()
val textTitle by viewModel.textTitle.observeAsState("")
val sizeUnlock by viewModel.sizeUnlock.observeAsState()
@ -89,6 +102,9 @@ fun NavMaterialScreen(navController: NavController, materialId: String) {
"materialList",
chunk
)
navController.currentBackStackEntry?.savedStateHandle?.set(
"indexValue", index
)
navController.navigate("detailMaterial")
}
},

View File

@ -73,6 +73,19 @@ class NavMaterialViewModel(application: Application) : AndroidViewModel(applicat
}
}
fun updateUnlockValue(materialId: String, value: Int) {
if(value >= (_sizeUnlock.value ?: 0) && _materialList.value.size > value){
viewModelScope.launch {
repository.updateUnlockData(materialId, value){
println("numberUnlock update unlock value success $it")
fetchMaterial(materialId)
}
}
}else{
println("numberUnlock-else $value, ${_sizeUnlock.value}, ${_materialList.value.size}")
}
}
override fun onCleared() {
tts?.stop()
tts?.shutdown()

View File

@ -42,6 +42,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.runtime.key
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.ui.graphics.Color
@ -50,6 +51,7 @@ 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.compose.ui.zIndex
import androidx.constraintlayout.compose.ConstraintLayout
import com.example.lexilearn.R
import com.example.lexilearn.data.model.QuestionType
@ -67,9 +69,12 @@ import kotlin.math.roundToInt
@Composable
fun QuizScreen(
navController: NavController,
material: MaterialDataModel,
materials: List<MaterialDataModel>,
typeQuiz: String,
indexValue: Int,
quizViewModel: QuizViewModel = viewModel()
) {
println("testco tipe: ${typeQuiz}, index = ${indexValue}")
val question by quizViewModel.currentQuestion.collectAsState()
val isButtonVisible by quizViewModel.isButtonVisible
val isAnswerCorrect by quizViewModel.isAnswerCorrect.collectAsState()
@ -77,16 +82,8 @@ fun QuizScreen(
val snackbarMessage by quizViewModel.snackbarMessage.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
val indexQuiz by quizViewModel.indexQuiz.collectAsState()
LaunchedEffect(Unit) {
quizViewModel.loadRandomQuestion(material)
}
LaunchedEffect(snackbarMessage) {
snackbarMessage?.let { message ->
snackbarHostState.showSnackbar(message)
quizViewModel.onSnackbarShown()
}
}
var rectColumnAnswer by remember { mutableStateOf(Rect.Zero) }
@ -128,10 +125,50 @@ fun QuizScreen(
mutableStateMapOf<Int, Rect>()
}
fun clearOffsets() {
boxRectAnswer.clear()
boxRectDragable.clear()
cardSize.clear()
quizXOffset.clear()
quizYOffset.clear()
boxRectQuiz.clear()
answerXOffset.clear()
answerYOffset.clear()
}
LaunchedEffect(Unit) {
quizViewModel.initMaterialData(materials)
}
LaunchedEffect(indexQuiz) {
println("indexQuizValue = $indexQuiz")
if (indexQuiz < materials.size) {
clearOffsets()
quizViewModel.randomQuestion()
} else {
navController.getBackStackEntry("navMaterial/$typeQuiz")
.savedStateHandle
.set("numberUnlock", indexValue + 1)
println("printUpdateUnlock-quiz $indexValue")
// Kemudian kembali ke halaman sebelumnya
navController.popBackStack()
navController.popBackStack()
}
}
LaunchedEffect(snackbarMessage) {
snackbarMessage?.let { message ->
snackbarHostState.showSnackbar(message)
quizViewModel.onSnackbarShown()
}
}
Scaffold(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { innerPadding ->
Surface(modifier = Modifier.fillMaxSize().padding(innerPadding)) {
Surface(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
) {
GradientQuiz(
navController = navController,
headerText = stringResource(id = R.string.spelltitle),
@ -205,6 +242,7 @@ fun QuizScreen(
) {
dataQuiz.forEach { dt ->
key(dt.id) {
val id = dt.id
if (!boxRectDragable.containsKey(id))
boxRectDragable[id] = Rect.Zero
@ -217,11 +255,13 @@ fun QuizScreen(
CardQuiz(
modifier = Modifier
.zIndex(zIndex = 1f)
.padding(vertical = 10.dp)
.size(minSize)
.onGloballyPositioned { coordinates ->
if (dt.type)
boxRectQuiz[id] = coordinates.boundsInWindow()
boxRectQuiz[id] =
coordinates.boundsInWindow()
}
) {
if (dt.type) {
@ -237,8 +277,10 @@ fun QuizScreen(
item = dt.data,
modifier = Modifier
.offset {
val xOffset = quizXOffset[id] ?: 0f
val yOffset = quizYOffset[id] ?: 0f
val xOffset =
quizXOffset[id] ?: 0f
val yOffset =
quizYOffset[id] ?: 0f
IntOffset(
xOffset.roundToInt(),
yOffset.roundToInt()
@ -277,13 +319,17 @@ fun QuizScreen(
dt
)
dt.apply {
hasContent = false
showCard = false
hasContent =
false
showCard =
false
data = "?"
}
checkNull = true
quizXOffset[id] = 0f
quizYOffset[id] = 0f
quizXOffset[id] =
0f
quizYOffset[id] =
0f
break
}
}
@ -294,14 +340,19 @@ fun QuizScreen(
val emDt = dt.emp
if (emDt != null) {
dt.apply {
hasContent = false
showCard = false
hasContent =
false
showCard =
false
data = "?"
}
checkNull = true
cardSize[emDt] = maxSize
quizXOffset[id] = 0f
quizYOffset[id] = 0f
cardSize[emDt] =
maxSize
quizXOffset[id] =
0f
quizYOffset[id] =
0f
dt.data = "?"
}
}
@ -328,6 +379,7 @@ fun QuizScreen(
}
}
}
}
Spacer(modifier = Modifier.height(12.dp))
}
}
@ -353,6 +405,7 @@ fun QuizScreen(
horizontalArrangement = Arrangement.Center
) {
rowItems.forEach { item ->
key(item.id) {
val id = item.id
if (!cardSize.containsKey(id))
cardSize[id] = maxSize
@ -374,7 +427,8 @@ fun QuizScreen(
)
}
.onGloballyPositioned { coordinates ->
boxRectAnswer[id] = coordinates.boundsInWindow()
boxRectAnswer[id] =
coordinates.boundsInWindow()
}
.pointerInput(Unit) {
detectDragGestures(
@ -427,6 +481,7 @@ fun QuizScreen(
}
}
}
}
Spacer(modifier = Modifier.height(12.dp))
}

View File

@ -24,6 +24,9 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
TextToSpeech.OnInitListener {
private val quizRepository = QuizRepository()
private var materialList: List<MaterialDataModel> = emptyList()
private val _currentQuestion = MutableStateFlow<QuizQuestion?>(null)
val currentQuestion: StateFlow<QuizQuestion?> = _currentQuestion
@ -33,6 +36,14 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
private val _isAnswerCorrect = MutableStateFlow<Boolean?>(null)
val isAnswerCorrect: StateFlow<Boolean?> = _isAnswerCorrect
private val _indexQuiz = MutableStateFlow(0)
val indexQuiz: StateFlow<Int> = _indexQuiz
// Update indexQuiz saat jawaban benar
fun incrementIndexQuiz() {
_indexQuiz.value += 1
}
fun loadNewQuestion(material: MaterialDataModel, questionMode: Int, answerMode: Int) {
_currentQuestion.value = quizRepository.generateQuestion(material, questionMode, answerMode)
}
@ -63,27 +74,48 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
}
fun loadRandomQuestion(material: MaterialDataModel) {
_currentQuestion.value = quizRepository.generateQuestion(material, 2, 2)
fun initMaterialData(material: List<MaterialDataModel>) {
materialList = material;
}
val dataQuest =
_currentQuestion.value?.correctAnswer?.correctWord?.mapIndexed { index, char ->
ModelSpell(index + 1, true, "?", showCard = false)
// fun randomQuestion() {
// _currentQuestion.value = quizRepository.generateQuestion(materialList[_indexQuiz.value], 2, 2)
// val dataQuest =
// _currentQuestion.value?.correctAnswer?.correctWord?.mapIndexed { index, char ->
// ModelSpell(index + 1, true, data = "coba $char", showCard = false)
// } ?: emptyList()
// _questionShuffled.value = dataQuest
//
// val listAnswer =
// _currentQuestion.value?.correctAnswer?.correctWord?.mapIndexed { index, char ->
// ModelAnswerRead(index + 1, char.toString())
// } ?: emptyList()
// val shuflledAnswer = listAnswer.shuffled()
// _shuffledAnswerLetters.value = shuflledAnswer
// println("testlistanswer ${_shuffledAnswerLetters.value}")
// }
// Di dalam fungsi randomQuestion() pada QuizViewModel
fun randomQuestion() {
_currentQuestion.value = quizRepository.generateQuestion(materialList[_indexQuiz.value], 2, 2)
// Generate unique IDs berdasarkan indexQuiz dan indeks karakter
val dataQuest = _currentQuestion.value?.correctAnswer?.correctWord?.mapIndexed { index, char ->
ModelSpell(
id = (_indexQuiz.value * 100) + index + 1, // ID unik per pertanyaan
type = true,
data = "?",
showCard = false
)
} ?: emptyList()
_questionShuffled.value = dataQuest
val listAnswer =
_currentQuestion.value?.correctAnswer?.correctWord?.mapIndexed { index, char ->
ModelAnswerRead(index + 1, char.toString())
val listAnswer = _currentQuestion.value?.correctAnswer?.correctWord?.mapIndexed { index, char ->
ModelAnswerRead(
id = (_indexQuiz.value * 100) + index + 1, // ID unik per pertanyaan
data = char.toString()
)
} ?: emptyList()
val shuflledAnswer = listAnswer.shuffled()
_shuffledAnswerLetters.value = shuflledAnswer
// Menambahkan delay 1 detik sebelum memanggil observeData()
// viewModelScope.launch {
// delay(1000L) // delay 1 detik
//
// }
_shuffledAnswerLetters.value = listAnswer.shuffled()
}
@ -139,6 +171,7 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
val checkData = checkAnswer(answerString);
if (checkData) {
triggerSnackbar("Jawaban Benar")
incrementIndexQuiz()
} else {
triggerSnackbar("Jawaban Salah")
}