From 940378ce37a018c752ac7c170c1f94ebcc90cd71 Mon Sep 17 00:00:00 2001 From: DimazzP Date: Wed, 26 Feb 2025 01:40:22 +0700 Subject: [PATCH] update competition --- .../com/example/lexilearn/MainActivity.kt | 20 ++++- .../lexilearn/data/model/CompetitionScore.kt | 7 ++ .../lexilearn/data/remote/FirebaseHelper.kt | 34 ++++++++ .../lexilearn/ui/views/pHome/HomeScreen.kt | 12 +-- .../lexilearn/ui/views/pHome/HomeViewModel.kt | 58 ++++++++++++- .../lexilearn/ui/views/pQuiz/QuizScreen.kt | 8 ++ .../lexilearn/ui/views/pQuiz/QuizViewModel.kt | 4 +- .../pResultScreening/ResultScreeningScreen.kt | 74 ++++++++++------- .../ResultScreeningViewModel.kt | 83 ++++++++++++++++++- 9 files changed, 256 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/com/example/lexilearn/data/model/CompetitionScore.kt diff --git a/app/src/main/java/com/example/lexilearn/MainActivity.kt b/app/src/main/java/com/example/lexilearn/MainActivity.kt index 3a88de8..64f8c09 100644 --- a/app/src/main/java/com/example/lexilearn/MainActivity.kt +++ b/app/src/main/java/com/example/lexilearn/MainActivity.kt @@ -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("indexValue") ?: -1 if (materialList != null) { DetailMaterialScreen(navController, materialList, indexValue) - }else{ + } else { println("masuk kondisi else") } } diff --git a/app/src/main/java/com/example/lexilearn/data/model/CompetitionScore.kt b/app/src/main/java/com/example/lexilearn/data/model/CompetitionScore.kt new file mode 100644 index 0000000..2cc6263 --- /dev/null +++ b/app/src/main/java/com/example/lexilearn/data/model/CompetitionScore.kt @@ -0,0 +1,7 @@ +package com.example.lexilearn.data.model + +data class CompetitionScore( + val userId: String = "", + val name: String = "", + val score: Int = 0 +) diff --git a/app/src/main/java/com/example/lexilearn/data/remote/FirebaseHelper.kt b/app/src/main/java/com/example/lexilearn/data/remote/FirebaseHelper.kt index 8f6f614..7a8a21e 100644 --- a/app/src/main/java/com/example/lexilearn/data/remote/FirebaseHelper.kt +++ b/app/src/main/java/com/example/lexilearn/data/remote/FirebaseHelper.kt @@ -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>) -> Unit) { + val todayDate = getTodayDate() + val databaseReference: DatabaseReference = + FirebaseDatabase.getInstance().getReference("competitions/$todayDate") + + databaseReference.addListenerForSingleValueEvent(object : ValueEventListener { + override fun onDataChange(snapshot: DataSnapshot) { + val scoreList = mutableListOf>() + + 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") + } } diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pHome/HomeScreen.kt b/app/src/main/java/com/example/lexilearn/ui/views/pHome/HomeScreen.kt index c1c2301..90ab3ee 100644 --- a/app/src/main/java/com/example/lexilearn/ui/views/pHome/HomeScreen.kt +++ b/app/src/main/java/com/example/lexilearn/ui/views/pHome/HomeScreen.kt @@ -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) diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pHome/HomeViewModel.kt b/app/src/main/java/com/example/lexilearn/ui/views/pHome/HomeViewModel.kt index 5446e43..2577c40 100644 --- a/app/src/main/java/com/example/lexilearn/ui/views/pHome/HomeViewModel.kt +++ b/app/src/main/java/com/example/lexilearn/ui/views/pHome/HomeViewModel.kt @@ -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>(emptyList()) + val topScores: StateFlow> 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() + + 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) -> Unit) { _materialRepository.getAllMaterialData("all") { materials -> - _quizRepository.getRandomMaterials(3, materials) { randomData -> + _quizRepository.getRandomMaterials(totalDataCompetition, materials) { randomData -> randomData.forEach { println(it) } callback(randomData) } 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 index bd89546..bb7b695 100644 --- 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 @@ -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 -> 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 index ffbb8de..a25c08d 100644 --- 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 @@ -284,6 +284,8 @@ class QuizViewModel(application: Application) : AndroidViewModel(application), val liveCurrentScore: StateFlow get() = _liveCurrentScore.asStateFlow() private val _formattedResponseTime = MutableStateFlow("0.0") val formattedResponseTime: StateFlow get() = _formattedResponseTime.asStateFlow() + private val _finalScore = MutableStateFlow(null) // Menyimpan skor akhir untuk navigasi + val finalScore: StateFlow get() = _finalScore.asStateFlow() private var questionList = listOf() 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 } } diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pResultScreening/ResultScreeningScreen.kt b/app/src/main/java/com/example/lexilearn/ui/views/pResultScreening/ResultScreeningScreen.kt index 3bcbd06..b0465a5 100644 --- a/app/src/main/java/com/example/lexilearn/ui/views/pResultScreening/ResultScreeningScreen.kt +++ b/app/src/main/java/com/example/lexilearn/ui/views/pResultScreening/ResultScreeningScreen.kt @@ -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 + }) } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/lexilearn/ui/views/pResultScreening/ResultScreeningViewModel.kt b/app/src/main/java/com/example/lexilearn/ui/views/pResultScreening/ResultScreeningViewModel.kt index 73bfb45..135b283 100644 --- a/app/src/main/java/com/example/lexilearn/ui/views/pResultScreening/ResultScreeningViewModel.kt +++ b/app/src/main/java/com/example/lexilearn/ui/views/pResultScreening/ResultScreeningViewModel.kt @@ -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 = _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>() // (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}") + } + }) + } + } \ No newline at end of file