add stats

This commit is contained in:
Mylloon 2024-01-04 20:07:10 +01:00
parent b347bf01f1
commit 6a7080766c
Signed by: Anri
GPG key ID: A82D63DFF8D1317F
8 changed files with 188 additions and 13 deletions

View file

@ -2,27 +2,27 @@
## A faire ? ## A faire ?
- Jeu de question - [x] Jeu de question
- [x] Ajouter / Importer - [x] Ajouter / Importer
- [x] Fichier local - [x] Fichier local
- [x] Lien HTTP - [x] Lien HTTP
- [x] Supprimer - [x] Supprimer
- Modifier - [x] Modifier
- [x] Créer des questions - [x] Créer des questions
- [x] Supprimer des questions - [x] Supprimer des questions
- [x] Choisir / Sélection - [x] Choisir / Sélection
- [x] Commencer un jeu - [x] Commencer un jeu
- [ ] Reprendre la progression d'un jeu - [ ] Reprendre la progression d'un jeu
- [ ] Afficher les statistiques de tous les jeux - [x] Afficher les statistiques de tous les jeux
- Dans un jeu - [x] Dans un jeu
- [x] Consulter la réponse à une question trop difficile - [x] Consulter la réponse à une question trop difficile
- [ ] Choisir le statut d'une question - [ ] Choisir le statut d'une question
- [x] Modifier la question - [x] Modifier la question
- [x] Supprimer la question - [x] Supprimer la question
- [ ] Afficher les statistiques du jeu de question en cours - [ ] Afficher les statistiques du jeu de question en cours
- [ ] Notification - [ ] Notification
- une fois par jour - [ ] une fois par jour
- [ ] Paramètres - [x] Paramètres
- [ ] Temps de réponse aux questions - [ ] Temps de réponse aux questions
- [ ] Thème - [ ] Thème
- [ ] Taille police - [ ] Taille police

View file

@ -72,6 +72,8 @@ dependencies {
implementation("androidx.navigation:navigation-compose:$navVersion") implementation("androidx.navigation:navigation-compose:$navVersion")
implementation("androidx.compose.material:material:1.3.1") implementation("androidx.compose.material:material:1.3.1")
implementation("androidx.datastore:datastore-preferences:1.0.0")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

View file

@ -0,0 +1,25 @@
package fr.uparis.diamantkennel.memorisationapplication
/** DataStore nom */
const val STATS = "stats"
/** DataStore clef lié au stockage temporaire
* du nombre de bonne réponse pour la session en cours */
// const val STATS_GOOD_ANSWER = "current_good"
/** DataStore clef lié au stockage temporaire
* du nombre de mauvaise réponse pour la session en cours */
// const val STATS_BAD_ANSWER = "current_bad"
/** DataStore clef lié au nombre de parties lancés */
const val STATS_TOTAL_TRIED = "total"
/** DataStore clef lié au nombre de partie gagnés (terminés) */
const val STATS_TOTAL_DONE = "total_done"
/** DataStore clef lié au nombre de questions bien répondu */
const val STATS_TOTAL_GOOD = "total_good"
/** DataStore clef lié au nombre de questions mal répondu */
const val STATS_TOTAL_BAD = "total_bad"

View file

@ -1,5 +1,6 @@
package fr.uparis.diamantkennel.memorisationapplication package fr.uparis.diamantkennel.memorisationapplication
import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@ -25,6 +26,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@ -32,6 +36,8 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import fr.uparis.diamantkennel.memorisationapplication.ui.theme.MemorisationApplicationTheme import fr.uparis.diamantkennel.memorisationapplication.ui.theme.MemorisationApplicationTheme
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = STATS)
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -4,19 +4,25 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import fr.uparis.diamantkennel.memorisationapplication.ui.SettingsViewModel import fr.uparis.diamantkennel.memorisationapplication.ui.SettingsViewModel
@ -25,23 +31,44 @@ fun SettingsScreen(padding: PaddingValues, model: SettingsViewModel = viewModel(
val context = LocalContext.current val context = LocalContext.current
var deletionDBRequest by model.deletionDB var deletionDBRequest by model.deletionDB
var cleanStatRequest by model.deletionStat
if (deletionDBRequest) { if (deletionDBRequest) {
DeletionDBDialog(model::deleteDb) { deletionDBRequest = false } DeletionDBDialog(model::deleteDb) { deletionDBRequest = false }
} }
if (cleanStatRequest) {
CleanStatDialog(model::cleanStats) { cleanStatRequest = false }
}
Column( Column(
modifier = Modifier.padding(padding), modifier = Modifier.padding(padding),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Stats(model)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { Spacer(modifier = Modifier.padding(top = 10.dp))
Divider(color = Color.Gray)
Spacer(modifier = Modifier.padding(top = 10.dp))
Text(text = "Gestion", fontSize = 30.sp)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button( Button(
onClick = { deletionDBRequest = true }, onClick = { deletionDBRequest = true },
colors = ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.red)) colors = ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.red))
) { ) {
Text(text = context.getString(R.string.main_button_deletebase)) Text(text = context.getString(R.string.main_button_deletebase))
} }
Button(
onClick = { cleanStatRequest = true },
colors = ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.red))
) {
Text(text = context.getString(R.string.clean_stat_button))
}
} }
} }
} }
@ -55,3 +82,41 @@ fun DeletionDBDialog(confirm: () -> Unit, dismiss: () -> Unit) =
Button(onClick = confirm) { Text(text = LocalContext.current.getString(R.string.yes)) } Button(onClick = confirm) { Text(text = LocalContext.current.getString(R.string.yes)) }
}, },
dismissButton = { Button(onClick = dismiss) { Text(text = LocalContext.current.getString(R.string.no)) } }) dismissButton = { Button(onClick = dismiss) { Text(text = LocalContext.current.getString(R.string.no)) } })
@Composable
fun Stats(model: SettingsViewModel) {
val context = LocalContext.current
val games by model.statTotal.collectAsState(0)
val gamesDone by model.statTotalDone.collectAsState(0)
val goodAnswers by model.statTotalGood.collectAsState(0)
val badAnswers by model.statTotalBad.collectAsState(0)
Text(text = context.getString(R.string.stats), fontSize = 30.sp)
Column {
Text(text = "${context.getString(R.string.stats_all_games)} : $games")
Text(text = "${context.getString(R.string.stats_games_done)} : $gamesDone")
Text(text = "${context.getString(R.string.stats_good_answer)} : $goodAnswers")
Text(text = "${context.getString(R.string.stats_bad_answer)} : $badAnswers")
Text(
text = "${context.getString(R.string.stats_winrate)} : ${
model.winrate(
goodAnswers,
badAnswers
)
}%"
)
}
}
@Composable
fun CleanStatDialog(confirm: () -> Unit, dismiss: () -> Unit) =
AlertDialog(onDismissRequest = dismiss,
title = { Text(text = LocalContext.current.getString(R.string.clean_stat)) },
text = { Text(text = LocalContext.current.getString(R.string.clean_stat_desc)) },
confirmButton = {
Button(onClick = confirm) { Text(text = LocalContext.current.getString(R.string.yes)) }
},
dismissButton = { Button(onClick = dismiss) { Text(text = LocalContext.current.getString(R.string.no)) } })

View file

@ -2,10 +2,17 @@ package fr.uparis.diamantkennel.memorisationapplication.ui
import android.app.Application import android.app.Application
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import fr.uparis.diamantkennel.memorisationapplication.MemoApplication import fr.uparis.diamantkennel.memorisationapplication.MemoApplication
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_BAD
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_DONE
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_GOOD
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_TRIED
import fr.uparis.diamantkennel.memorisationapplication.data.Question import fr.uparis.diamantkennel.memorisationapplication.data.Question
import fr.uparis.diamantkennel.memorisationapplication.dataStore
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -13,6 +20,12 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
private val dao = (application as MemoApplication).database.memoDao() private val dao = (application as MemoApplication).database.memoDao()
private var questions = mutableStateOf<List<Question>>(listOf()) private var questions = mutableStateOf<List<Question>>(listOf())
private val stats = application.dataStore
private val statsKeyTotal = intPreferencesKey(STATS_TOTAL_TRIED)
private val statsKeyTotalDone = intPreferencesKey(STATS_TOTAL_DONE)
private val statsKeyTotalGood = intPreferencesKey(STATS_TOTAL_GOOD)
private val statsKeyTotalBad = intPreferencesKey(STATS_TOTAL_BAD)
var currentQuestion = mutableStateOf<Question?>(null) var currentQuestion = mutableStateOf<Question?>(null)
private var index = mutableStateOf(0) private var index = mutableStateOf(0)
var proposedAnswer = mutableStateOf("") var proposedAnswer = mutableStateOf("")
@ -28,6 +41,9 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
viewModelScope.launch(Dispatchers.Main) { viewModelScope.launch(Dispatchers.Main) {
dao.loadQuestions(setId).collect { questionList -> dao.loadQuestions(setId).collect { questionList ->
questions.value = questionList.shuffled() questions.value = questionList.shuffled()
if (questions.value.isNotEmpty()) {
stats.edit { it[statsKeyTotal] = (it[statsKeyTotal] ?: 0) + 1 }
}
updateQuestion() updateQuestion()
} }
} }
@ -41,6 +57,11 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
if (index.value >= questions.value.size) { if (index.value >= questions.value.size) {
/* Fin des questions */ /* Fin des questions */
end.value = true end.value = true
viewModelScope.launch {
stats.edit {
it[statsKeyTotalDone] = (it[statsKeyTotalDone] ?: 0) + 1
}
}
} else { } else {
currentQuestion.value = questions.value[index.value] currentQuestion.value = questions.value[index.value]
} }
@ -81,7 +102,7 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
fun checkAnswer() { fun checkAnswer() {
val probaReponse = calcSimilarite(currentQuestion.value!!.reponse, proposedAnswer.value) val probaReponse = calcSimilarite(currentQuestion.value!!.reponse, proposedAnswer.value)
if (probaReponse >= .60f) { if (probaReponse >= .70f) {
evaluatedAnswer.value = AnswerType.GOOD evaluatedAnswer.value = AnswerType.GOOD
} else { } else {
evaluatedAnswer.value = AnswerType.BAD evaluatedAnswer.value = AnswerType.BAD
@ -89,17 +110,23 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
} }
fun sbUpdate() { fun sbUpdate() {
/* TODO: Statistiques à sauvegarder : /* TODO: Statistiques à sauvegarder : temps de réponse */
* - temps de réponse
* - taux réussite (ratio bonne/mauvaise réponse
*
* Tout ça va être récupérer depuis ici */
when (evaluatedAnswer.value!!) { when (evaluatedAnswer.value!!) {
AnswerType.GOOD -> { AnswerType.GOOD -> {
viewModelScope.launch {
stats.edit {
it[statsKeyTotalGood] = (it[statsKeyTotalGood] ?: 0) + 1
}
}
newQuestion() newQuestion()
} }
AnswerType.BAD -> { AnswerType.BAD -> {
viewModelScope.launch {
stats.edit {
it[statsKeyTotalBad] = (it[statsKeyTotalBad] ?: 0) + 1
}
}
reset() reset()
} }
} }

View file

@ -2,15 +2,36 @@ package fr.uparis.diamantkennel.memorisationapplication.ui
import android.app.Application import android.app.Application
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import fr.uparis.diamantkennel.memorisationapplication.MemoApplication import fr.uparis.diamantkennel.memorisationapplication.MemoApplication
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_BAD
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_DONE
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_GOOD
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_TRIED
import fr.uparis.diamantkennel.memorisationapplication.dataStore
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SettingsViewModel(application: Application) : AndroidViewModel(application) { class SettingsViewModel(application: Application) : AndroidViewModel(application) {
private val dao = (application as MemoApplication).database.memoDao() private val dao = (application as MemoApplication).database.memoDao()
private val stats = application.dataStore
private val statsKeyTotal = intPreferencesKey(STATS_TOTAL_TRIED)
private val statsKeyTotalDone = intPreferencesKey(STATS_TOTAL_DONE)
private val statsKeyTotalGood = intPreferencesKey(STATS_TOTAL_GOOD)
private val statsKeyTotalBad = intPreferencesKey(STATS_TOTAL_BAD)
val statTotal = stats.data.map { it[statsKeyTotal] ?: 0 }
val statTotalDone = stats.data.map { it[statsKeyTotalDone] ?: 0 }
val statTotalGood = stats.data.map { it[statsKeyTotalGood] ?: 0 }
val statTotalBad = stats.data.map { it[statsKeyTotalBad] ?: 0 }
val deletionDB = mutableStateOf(false) val deletionDB = mutableStateOf(false)
val deletionStat = mutableStateOf(false)
fun deleteDb() { fun deleteDb() {
deletionDB.value = false deletionDB.value = false
@ -18,4 +39,24 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
dao.deleteTable() dao.deleteTable()
} }
} }
fun cleanStats() {
deletionStat.value = false
viewModelScope.launch {
stats.edit {
it[statsKeyTotal] = 0
it[statsKeyTotalDone] = 0
it[statsKeyTotalGood] = 0
it[statsKeyTotalBad] = 0
}
}
}
fun winrate(good: Int, bad: Int): Int {
val total = good + bad
if (total == 0) {
return 0
}
return ((good.toFloat() / total.toFloat()) * 100).toInt()
}
} }

View file

@ -41,4 +41,13 @@
<string name="no">Non</string> <string name="no">Non</string>
<string name="bravo">Bravo</string> <string name="bravo">Bravo</string>
<string name="set_ended">Le set de questions est terminé !</string> <string name="set_ended">Le set de questions est terminé !</string>
<string name="stats">Statistiques</string>
<string name="stats_all_games">Set lancés</string>
<string name="stats_games_done">Set terminés</string>
<string name="stats_good_answer">Réponses bonnes</string>
<string name="stats_bad_answer">Réponses mauvaises</string>
<string name="stats_winrate">Taux de victoire</string>
<string name="clean_stat_button">Réinitialiser stats</string>
<string name="clean_stat">Réinitialiser les statistiques</string>
<string name="clean_stat_desc">Voulez-vous réinitialiser les statistiques ?</string>
</resources> </resources>