This commit is contained in:
DimazzP 2025-05-03 23:24:06 +07:00
parent 8a328e7dd4
commit 9f049952e8
12 changed files with 127 additions and 135 deletions

View File

@ -15,6 +15,7 @@ import androidx.navigation.navArgument
import com.example.lexilearn.data.model.MaterialDataModel
import com.example.lexilearn.data.model.UserDataModel
import com.example.lexilearn.data.repository.UserRepository
import com.example.lexilearn.ui.theme.LexiLearnTheme
import com.example.lexilearn.ui.views.pDetailMaterial.DetailMaterialScreen
import com.example.lexilearn.ui.views.pHome.HomeScreen
import com.example.lexilearn.ui.views.pLearAlphabet.LearnAlphabetScreen
@ -75,7 +76,9 @@ class MainActivity : ComponentActivity() {
}
}
setContent {
MyApp()
LexiLearnTheme {
MyApp() // Menggunakan MyApp dengan tema LexiLearn
}
}
}
}

View File

@ -40,7 +40,8 @@ class FirebaseHelper(private val context: Context) {
}
fun fetchAllMaterials(callback: (List<MaterialDataModel>) -> Unit) {
val databaseReference: DatabaseReference = FirebaseDatabase.getInstance().getReference("data")
val databaseReference: DatabaseReference =
FirebaseDatabase.getInstance().getReference("data")
databaseReference.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
@ -102,8 +103,10 @@ class FirebaseHelper(private val context: Context) {
// })
// }
fun loginUser(callback: (Boolean, String?) -> Unit) {
val databaseReference: DatabaseReference = FirebaseDatabase.getInstance().getReference("users")
val userId = sharedPrefHelper.getUserId() // Ambil ID pengguna yang disimpan di SharedPreferences
val databaseReference: DatabaseReference =
FirebaseDatabase.getInstance().getReference("users")
val userId =
sharedPrefHelper.getUserId() // Ambil ID pengguna yang disimpan di SharedPreferences
if (userId.isNullOrEmpty()) {
callback(false, null) // Jika tidak ada ID tersimpan
@ -146,8 +149,7 @@ class FirebaseHelper(private val context: Context) {
}
fun updateUser(updatedUser: UserDataModel, callback: (Boolean) -> Unit) {
val getUserData = sharedPrefHelper.getUserData()
val userId = sharedPrefHelper.getUserId() // Ambil ID pengguna yang disimpan di SharedPreferences
val userId = sharedPrefHelper.getUserId()
val databaseReference = FirebaseDatabase.getInstance().getReference("users")
val newData = mapOf(
"name" to updatedUser.name,
@ -169,7 +171,8 @@ class FirebaseHelper(private val context: Context) {
return
}
val databaseReference: DatabaseReference = FirebaseDatabase.getInstance().getReference("users/$userId")
val databaseReference: DatabaseReference =
FirebaseDatabase.getInstance().getReference("users/$userId")
databaseReference.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
@ -183,6 +186,7 @@ class FirebaseHelper(private val context: Context) {
}
})
}
fun fetchTop10Scores(callback: (List<Pair<String, Int>>) -> Unit) {
val todayDate = getTodayDate()
val databaseReference: DatabaseReference =

View File

@ -10,18 +10,15 @@ 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
1 -> listOf(QuestionType.TEXT, QuestionType.IMAGE).random()
2 -> QuestionType.AUDIO
else -> QuestionType.TEXT
}
val question = when (questionType) {
@ -46,17 +43,6 @@ class QuizRepository {
)
}
/**
* 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)
}
fun getRandomMaterials(
nData: Int,
materials: List<MaterialDataModel>,

View File

@ -40,7 +40,8 @@ class UserRepository(private val context: Context) {
return
}
val userRef = FirebaseDatabase.getInstance().getReference("users/$userId/unlock_data/$idUnlock")
val userRef =
FirebaseDatabase.getInstance().getReference("users/$userId/unlock_data/$idUnlock")
userRef.get().addOnSuccessListener { snapshot ->
val currentValue = snapshot.getValue(Int::class.java) ?: 0
@ -85,7 +86,8 @@ class UserRepository(private val context: Context) {
return
}
val userRef = FirebaseDatabase.getInstance().getReference("users/$userId/unlock_data/$idUnlock")
val userRef =
FirebaseDatabase.getInstance().getReference("users/$userId/unlock_data/$idUnlock")
userRef.get().addOnSuccessListener { snapshot ->
callback(snapshot.getValue(Int::class.java) ?: 0)
}.addOnFailureListener {

View File

@ -7,31 +7,31 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.example.lexilearn.R
val _fredokaOne = FontFamily(
Font(R.font.fredoka) // Pastikan nama file sesuai
)
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily(Font(R.font.inter)),
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
displayLarge = TextStyle(fontFamily = _fredokaOne, fontSize = 57.sp), // Gaya displayLarge
displayMedium = TextStyle(fontFamily = _fredokaOne, fontSize = 45.sp), // Gaya displayMedium
displaySmall = TextStyle(fontFamily = _fredokaOne, fontSize = 36.sp), // Gaya displaySmall
headlineLarge = TextStyle(fontFamily = _fredokaOne, fontSize = 32.sp), // Gaya headlineLarge
headlineMedium = TextStyle(fontFamily = _fredokaOne, fontSize = 28.sp), // Gaya headlineMedium
headlineSmall = TextStyle(fontFamily = _fredokaOne, fontSize = 24.sp), // Gaya headlineSmall
titleLarge = TextStyle(fontFamily = _fredokaOne, fontSize = 22.sp), // Gaya titleLarge
titleMedium = TextStyle(fontFamily = _fredokaOne, fontSize = 20.sp), // Gaya titleMedium
titleSmall = TextStyle(fontFamily = _fredokaOne, fontSize = 18.sp), // Gaya titleSmall
bodyLarge = TextStyle(fontFamily = _fredokaOne, fontSize = 16.sp), // Gaya bodyLarge
bodyMedium = TextStyle(fontFamily = _fredokaOne, fontSize = 14.sp), // Gaya bodyMedium
bodySmall = TextStyle(fontFamily = _fredokaOne, fontSize = 12.sp), // Gaya bodySmall
labelLarge = TextStyle(fontFamily = _fredokaOne, fontSize = 14.sp), // Gaya labelLarge
labelMedium = TextStyle(fontFamily = _fredokaOne, fontSize = 12.sp), // Gaya labelMedium
labelSmall = TextStyle(fontFamily = _fredokaOne, fontSize = 10.sp) // Gaya labelSm
)

View File

@ -41,7 +41,11 @@ import com.example.lexilearn.ui.theme.ctextBlack
import com.example.lexilearn.ui.theme.ctextGray
@Composable
fun DetailMaterialScreen(navController: NavController, materialListData: List<MaterialDataModel>, indexValue: Int) {
fun DetailMaterialScreen(
navController: NavController,
materialListData: List<MaterialDataModel>,
indexValue: Int
) {
val viewModel: DetailMaterialViewModel = viewModel()
LaunchedEffect(materialListData) {
viewModel.fetchMaterial(materialListData)
@ -98,8 +102,8 @@ fun DetailMaterialScreen(navController: NavController, materialListData: List<Ma
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(120.dp) // 🔥 Ukuran gambar
.clip(RoundedCornerShape(12.dp)) // 🔥 Memberikan sudut lengkung (radius)
.size(120.dp)
.clip(RoundedCornerShape(12.dp))
) {
FirebaseImage(
path = dataMat.image,
@ -144,7 +148,7 @@ fun DetailMaterialScreen(navController: NavController, materialListData: List<Ma
} // 🔥 Ukuran tombol bisa disesuaikan
) {
Icon(
imageVector = Icons.Filled.VolumeUp, // 🔥 Ikon Speaker dari Material Icons
imageVector = Icons.Filled.VolumeUp,
contentDescription = "Speaker Icon",
tint = Color.Black // 🔥 Warna ikon
)

View File

@ -26,7 +26,7 @@ import java.util.Locale
class HomeViewModel(application: Application) : AndroidViewModel(application) {
private val totalDataCompetition = 3
private val totalDataCompetition = 5
val materialRepository = MaterialRepository(application)
val userRepository = UserRepository(application)
@ -67,21 +67,18 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
// 🔥 Fungsi untuk mengambil 10 skor tertinggi secara real-time dari Firebase
fun fetchTop10Scores() {
private fun fetchTop10Scores() {
val todayDate = getTodayDate()
val dateRef = database.child(todayDate)
dateRef.orderByChild("score").limitToLast(10) // Ambil 10 skor tertinggi
dateRef.orderByChild("score").limitToLast(10)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val scoreList = mutableListOf<CompetitionScore>()
for (scoreSnapshot in snapshot.children) {
val scoreData = scoreSnapshot.getValue(CompetitionScore::class.java)
scoreData?.let { scoreList.add(it) }
}
// Urutkan dari yang tertinggi ke terendah
_topScores.value = scoreList.sortedByDescending { it.score }
}

View File

@ -30,7 +30,7 @@ fun LearnNumberScreen(navController: NavController) {
Surface(modifier = Modifier.fillMaxSize()) {
GradientQuiz(
navController = navController,
headerText = "Belajar Alfabet Inggris",
headerText = "Belajar Angka Inggris",
modifier = Modifier.fillMaxSize()
) {
Column(

View File

@ -45,7 +45,6 @@ class NavMaterialViewModel(application: Application) : AndroidViewModel(applicat
_sizeUnlock.value = value
}
repository.getMaterialData(materialId) { materials ->
// val filteredMaterials = materials.filter { it.category == materialId }
_materialList.value = materials.chunked(3)
if (materials[0].category == "animal") {
_textTitle.value = "hewan"

View File

@ -53,12 +53,14 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.zIndex
import androidx.constraintlayout.compose.ConstraintLayout
import com.example.lexilearn.data.model.QuestionType
import com.example.lexilearn.domain.models.ModelQuestion
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.cAccent
import com.example.lexilearn.ui.theme.ctextBlack
import com.example.lexilearn.ui.theme.ctextGray
import com.example.lexilearn.ui.theme.ctextWhite
import com.google.accompanist.flowlayout.FlowRow
@ -73,7 +75,6 @@ fun QuizScreen(
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()
@ -222,7 +223,7 @@ fun QuizScreen(
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Score: $liveCurrentScore",
text = "Point: $liveCurrentScore",
color = cAccent,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
@ -256,6 +257,7 @@ fun QuizScreen(
Text(
text = question!!.question,
fontSize = 20.sp,
color = ctextBlack,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(12.dp)
)
@ -366,10 +368,7 @@ fun QuizScreen(
rect
)
) {
quizViewModel.updateQuestionShuffled(
ind,
dt
)
val newDt = dt.copy()
dt.apply {
hasContent =
false
@ -377,6 +376,10 @@ fun QuizScreen(
false
data = "?"
}
quizViewModel.updateQuestionShuffled(
ind,
newDt
)
checkNull = true
quizXOffset[id] =
0f
@ -431,6 +434,7 @@ fun QuizScreen(
}
}
}
println("--------")
}
Spacer(modifier = Modifier.height(12.dp))
}

View File

@ -2,7 +2,6 @@ package com.example.lexilearn.ui.views.pQuiz
import android.app.Application
import android.speech.tts.TextToSpeech
import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
@ -46,10 +45,6 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
_indexQuiz.value += 1
}
fun loadNewQuestion(material: MaterialDataModel, questionMode: Int, answerMode: Int) {
_currentQuestion.value = quizRepository.generateQuestion(material, questionMode, answerMode)
}
private val _shuffledAnswerLetters = MutableStateFlow<List<ModelAnswer>>(emptyList())
val shuffledAnswerLetters: StateFlow<List<ModelAnswer>> = _shuffledAnswerLetters
@ -101,7 +96,7 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
val listAnswer =
_currentQuestion.value?.correctAnswer?.correctWord?.mapIndexed { index, char ->
ModelAnswer(
id = (_indexQuiz.value * 100) + index + 1, // ID unik per pertanyaan
id = (_indexQuiz.value * 100) + index + 1,
data = char.toString().lowercase()
)
} ?: emptyList()
@ -135,24 +130,21 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
}
fun checkAnswer(answerString: String): Boolean {
if (answerString.lowercase() == currentQuestion.value?.correctAnswer?.correctWord?.lowercase()) {
return true
}
return false
return (answerString.lowercase() == currentQuestion.value?.correctAnswer?.correctWord?.lowercase())
}
fun observeData() {
viewModelScope.launch {
_questionShuffled.collect { newList ->
// Lakukan aksi setiap kali data berubah
var answerString = ""
newList.forEach { text ->
answerString = answerString + text.data
}
if (answerString.contains("?") || answerString.isEmpty() || answerString.contains("null")) {
println("if-$answerString")
_isButtonVisible.value = false
} else {
Log.d("cobaaja", answerString)
println("else-$answerString")
val checkData = checkAnswer(answerString);
if (checkData) {
triggerSnackbar("Jawaban Benar")
@ -185,6 +177,8 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
super.onCleared()
}
// competition
private val _totalScore = MutableStateFlow(0)
val totalScore: StateFlow<Int> get() = _totalScore.asStateFlow()
@ -288,12 +282,11 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
val penaltyTime = max(0, responseTime - safeTime) * timePenalty
val penaltyWrong = wrongAttempts * wrongPenalty
var score = baseScore - penaltyTime - penaltyWrong
if (responseTime <= 3) {
if (responseTime <= 5) {
// println("responset: $responseTime")
score += bonusQuick
}
return max(minScore, score)
}
}

Binary file not shown.