update competition

This commit is contained in:
DimazzP 2025-02-26 01:40:22 +07:00
parent 217f587965
commit 940378ce37
9 changed files with 256 additions and 44 deletions

View File

@ -58,10 +58,17 @@ class MainActivity : ComponentActivity() {
repository.createUser(newUser) { success, newUserId ->
if (success) {
Toast.makeText(this, "User Baru Dibuat! Login Sekarang...", Toast.LENGTH_SHORT).show()
Toast.makeText(
this,
"User Baru Dibuat! Login Sekarang...",
Toast.LENGTH_SHORT
).show()
repository.loginUser(email, password) { loginSuccess, loggedInUserId ->
if (loginSuccess) {
Log.d("MainActivity", "Login Berhasil setelah buat akun! User ID: $loggedInUserId")
Log.d(
"MainActivity",
"Login Berhasil setelah buat akun! User ID: $loggedInUserId"
)
} else {
Log.e("MainActivity", "Login Gagal setelah membuat akun!")
}
@ -91,6 +98,13 @@ fun MyApp() {
composable("write") { WriteScreen(navController) }
composable("screening") { ScreeningScreen(navController) }
// composable("resultscreening") { ResultScreeningScreen(navController) }
composable(
route = "resultscreening/{totalScore}",
arguments = listOf(navArgument("totalScore") { type = NavType.IntType })
) { backStackEntry ->
val totalScore = backStackEntry.arguments?.getInt("totalScore") ?: 0
ResultScreeningScreen(navController, totalScore)
}
composable("learnAlphabet") { LearnAlphabetScreen(navController) }
composable("learnNumber") { LearnNumberScreen(navController) }
// composable(
@ -115,7 +129,7 @@ fun MyApp() {
?.get<Int>("indexValue") ?: -1
if (materialList != null) {
DetailMaterialScreen(navController, materialList, indexValue)
}else{
} else {
println("masuk kondisi else")
}
}

View File

@ -0,0 +1,7 @@
package com.example.lexilearn.data.model
data class CompetitionScore(
val userId: String = "",
val name: String = "",
val score: Int = 0
)

View File

@ -9,6 +9,9 @@ import com.example.lexilearn.data.model.UserModel
import com.google.firebase.database.*
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class FirebaseHelper(private val context: Context) {
private val sharedPrefHelper = SharedPrefHelper(context)
@ -122,4 +125,35 @@ class FirebaseHelper(private val context: Context) {
}
})
}
fun fetchTop10Scores(callback: (List<Pair<String, Int>>) -> Unit) {
val todayDate = getTodayDate()
val databaseReference: DatabaseReference =
FirebaseDatabase.getInstance().getReference("competitions/$todayDate")
databaseReference.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val scoreList = mutableListOf<Pair<String, Int>>()
for (scoreSnapshot in snapshot.children) {
val name = scoreSnapshot.child("name").getValue(String::class.java) ?: "Unknown"
val score = scoreSnapshot.child("score").getValue(Int::class.java) ?: 0
scoreList.add(Pair(name, score))
}
// Urutkan berdasarkan skor tertinggi dan ambil 10 data
val top10Scores = scoreList.sortedByDescending { it.second }.take(10)
callback(top10Scores)
}
override fun onCancelled(error: DatabaseError) {
Log.e("FirebaseHelper", "Error fetching scores: ${error.message}")
callback(emptyList()) // Jika error, kembalikan list kosong
}
})
}
fun getTodayDate(): String {
val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
return dateFormat.format(Date()) // Mendapatkan tanggal hari ini (misalnya "25-02-2025")
}
}

View File

@ -23,6 +23,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -52,10 +53,9 @@ import com.example.lexilearn.ui.theme.cwhite
@Composable
fun HomeScreen(navController: NavController) {
val viewModel: HomeViewModel = viewModel()
fun HomeScreen(navController: NavController, viewModel: HomeViewModel = viewModel()) {
val scrollState = rememberScrollState()
val leaderBoard = listOf("Tono (987)", "Budi (965)")
val competitionScore = viewModel.topScores.collectAsState()
Column(
modifier = Modifier
@ -139,9 +139,9 @@ fun HomeScreen(navController: NavController) {
)
Spacer(modifier = Modifier.height(10.dp))
LazyColumn {
itemsIndexed(leaderBoard) { index, player ->
itemsIndexed(competitionScore.value) { index, player ->
Text(
text = "${index + 1}. $player",
text = "${index + 1}. ${player.name} (${player.score})",
color = cAccent,
fontWeight = FontWeight.Bold,
fontSize = 14.sp
@ -157,7 +157,7 @@ fun HomeScreen(navController: NavController) {
)
.width(160.dp)
.clickable {
viewModel.prepareCompetitionQuiz {dataMaterial->
viewModel.prepareCompetitionQuiz { dataMaterial ->
navController.currentBackStackEntry
?.savedStateHandle
?.set("selectedMaterialList", dataMaterial)

View File

@ -1,22 +1,78 @@
package com.example.lexilearn.ui.views.pHome
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.lexilearn.data.model.CompetitionScore
import com.example.lexilearn.data.model.MaterialDataModel
import com.example.lexilearn.data.model.QuizState
import com.example.lexilearn.data.repository.MaterialRepository
import com.example.lexilearn.data.repository.QuizRepository
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class HomeViewModel(application: Application) : AndroidViewModel(application) {
private val totalDataCompetition = 3
private val _materialRepository = MaterialRepository(application)
private val _quizRepository = QuizRepository()
private val database: DatabaseReference = FirebaseDatabase.getInstance().getReference("competitions")
private val _topScores = MutableStateFlow<List<CompetitionScore>>(emptyList())
val topScores: StateFlow<List<CompetitionScore>> get() = _topScores.asStateFlow()
// 🔥 Mendapatkan tanggal hari ini dalam format "dd-MM-yyyy"
private fun getTodayDate(): String {
val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
return dateFormat.format(Date())
}
init {
fetchTop10Scores()
}
// 🔥 Fungsi untuk mengambil 10 skor tertinggi secara real-time dari Firebase
fun fetchTop10Scores() {
val todayDate = getTodayDate()
val dateRef = database.child(todayDate)
dateRef.orderByChild("score").limitToLast(10) // Ambil 10 skor tertinggi
.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 }
}
override fun onCancelled(error: DatabaseError) {
Log.e("CompetitionViewModel", "Error fetching scores: ${error.message}")
}
})
}
fun prepareCompetitionQuiz(callback: (List<MaterialDataModel>) -> Unit) {
_materialRepository.getAllMaterialData("all") { materials ->
_quizRepository.getRandomMaterials(3, materials) { randomData ->
_quizRepository.getRandomMaterials(totalDataCompetition, materials) { randomData ->
randomData.forEach { println(it) }
callback(randomData)
}

View File

@ -169,6 +169,14 @@ fun QuizScreen(
quizViewModel.onSnackbarShown()
}
}
val finalScore by quizViewModel.finalScore.collectAsState()
LaunchedEffect(finalScore) {
finalScore?.let { scoreFin ->
navController.navigate("resultscreening/$scoreFin") {
popUpTo("quizscreen") { inclusive = true }
}
}
}
Scaffold(
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { innerPadding ->

View File

@ -284,6 +284,8 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
val liveCurrentScore: StateFlow<Int> get() = _liveCurrentScore.asStateFlow()
private val _formattedResponseTime = MutableStateFlow("0.0")
val formattedResponseTime: StateFlow<String> get() = _formattedResponseTime.asStateFlow()
private val _finalScore = MutableStateFlow<Int?>(null) // Menyimpan skor akhir untuk navigasi
val finalScore: StateFlow<Int?> get() = _finalScore.asStateFlow()
private var questionList = listOf<MaterialDataModel>()
private var currentQuestionIndex = 0
@ -361,7 +363,7 @@ class QuizViewModel(application: Application) : AndroidViewModel(application),
_liveCurrentScore.value = baseScore
startStopwatch() // Restart stopwatch
}else{
_finalScore.value = _totalScore.value
}
}

View File

@ -12,13 +12,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
@ -30,21 +27,30 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.graphics.Path
import androidx.constraintlayout.compose.Dimension
import com.example.lexilearn.ui.components.CustomButton
import com.example.lexilearn.ui.theme.cprimary
import com.example.lexilearn.ui.theme.ctextBlack
import com.example.lexilearn.ui.theme.cwhite
import com.example.lexilearn.ui.views.pScreening.ScreeningViewModel
@Composable
fun ResultScreeningScreen(navController: NavController, totalScore: Int, viewModel: ScreeningViewModel) {
fun ResultScreeningScreen(
navController: NavController,
totalScore: Int,
viewModel: ResultScreeningViewModel = viewModel()
) {
val scorePosition = viewModel.positionScore.collectAsState()
LaunchedEffect(Unit) {
viewModel.submitScore(totalScore)
}
GradientScreening(
backButton = { navController.popBackStack() },
headerText = stringResource(id = R.string.rescreentitle),
headerText = "Skor Kompetisi",
modifier = Modifier.fillMaxSize()
) {
ConstraintLayout(
@ -86,13 +92,13 @@ fun ResultScreeningScreen(navController: NavController, totalScore: Int, viewMod
) {
Spacer(modifier = Modifier.height(30.dp))
Text(
text = "Dyslexia",
text = "Skor",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = ctextBlack
)
Text(
text = "66%",
text = "$totalScore",
fontSize = 50.sp,
fontWeight = FontWeight.Bold,
color = cprimary,
@ -112,33 +118,37 @@ fun ResultScreeningScreen(navController: NavController, totalScore: Int, viewMod
end.linkTo(parent.end)
}) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "Anda menjawab \"ya\" pada 2 dari 3 pertanyaan yang menunjukkan 66% kemungkinan Anda menderita disleksia.",
fontSize = 16.sp,
color = ctextBlack,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 24.dp)
)
Text(
text = stringResource(id = R.string.rescreendesc),
fontSize = 14.sp,
color = ctextGray,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 16.dp, bottom = 20.dp)
)
Text(
text = "Wow, hebat sekali! 🎉 Kamu menduduki peringkat ${scorePosition.value} hari ini! 🚀",
fontSize = 16.sp,
color = ctextBlack,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 24.dp)
)
Text(
text = "Petualanganmu belum berakhir! 🔥 Coba lagi dan naikkan peringkatmu! 🏆",
fontSize = 14.sp,
color = ctextGray,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 16.dp, bottom = 20.dp)
)
}
}
}
}
CustomButton(
text = stringResource(id = R.string.close),
onClick = { },
modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp).constrainAs(buttonRef) {
bottom.linkTo(parent.bottom, margin = 12.dp)
end.linkTo(parent.end, margin = 12.dp)
start.linkTo(parent.start, margin = 12.dp)
width = Dimension.fillToConstraints
})
onClick = {
navController.popBackStack()
},
modifier = Modifier
.padding(horizontal = 24.dp, vertical = 12.dp)
.constrainAs(buttonRef) {
bottom.linkTo(parent.bottom, margin = 12.dp)
end.linkTo(parent.end, margin = 12.dp)
start.linkTo(parent.start, margin = 12.dp)
width = Dimension.fillToConstraints
})
}
}
}

View File

@ -1,8 +1,89 @@
package com.example.lexilearn.ui.views.pResultScreening
import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import com.example.lexilearn.data.model.CompetitionScore
import com.example.lexilearn.data.repository.MaterialRepository
import com.example.lexilearn.data.repository.QuizRepository
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.UUID
class ResultScreeningViewModel(application: Application) : AndroidViewModel(application) {
private val database: DatabaseReference = FirebaseDatabase.getInstance().getReference("competitions")
private val materialRepo = MaterialRepository(application)
private val _positionScore = MutableStateFlow(0)
val positionScore: StateFlow<Int> = _positionScore
private fun getTodayDate(): String {
val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
return dateFormat.format(Date())
}
fun submitScore(score: Int) {
materialRepo.getUserData {dataUser->
val todayDate = getTodayDate()
val dateRef = database.child(todayDate)
// Buat ID unik untuk skor
val scoreId = dateRef.push().key ?: UUID.randomUUID().toString()
val scoreData = mapOf("score" to score, "name" to "${dataUser?.name}")
dateRef.child(scoreId).setValue(scoreData)
.addOnSuccessListener {
Log.d("CompetitionViewModel", "Score added successfully!")
checkUserRanking(todayDate, scoreId, score)
}
.addOnFailureListener {
Log.e("CompetitionViewModel", "Error saving score: ${it.message}")
}
}
}
private fun checkUserRanking(todayDate: String, scoreId: String, userScore: Int) {
val dateRef = database.child(todayDate)
dateRef.orderByChild("score").addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val scoreList = mutableListOf<Pair<String, Int>>() // (scoreId, score)
for (scoreSnapshot in snapshot.children) {
val id = scoreSnapshot.key ?: continue
val score = scoreSnapshot.child("score").getValue(Int::class.java) ?: 0
scoreList.add(id to score)
}
// Urutkan skor dari tertinggi ke terendah
val sortedScores = scoreList.sortedByDescending { it.second }
// Cari posisi user
// val userRank = sortedScores.indexOfFirst { it.first == scoreId } + 1
val userRank = maxOf(1, sortedScores.indexOfFirst { it.first == scoreId } + 1)
_positionScore.value = userRank
if (userRank > 0) {
Log.d("ResultScreeningVM", "User is ranked: #$userRank out of ${sortedScores.size}")
} else {
Log.d("ResultScreeningVM", "User rank not found.")
}
}
override fun onCancelled(error: DatabaseError) {
Log.e("ResultScreeningVM", "Error fetching scores: ${error.message}")
}
})
}
}