diff --git a/README.md b/README.md index e755cb9..aa2e98d 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,27 @@ ## A faire ? -- Jeu de question +- [x] Jeu de question - [x] Ajouter / Importer - [x] Fichier local - [x] Lien HTTP - [x] Supprimer - - Modifier + - [x] Modifier - [x] Créer des questions - [x] Supprimer des questions - [x] Choisir / Sélection - [x] Commencer un jeu - [ ] Reprendre la progression d'un jeu - - [ ] Afficher les statistiques de tous les jeux -- Dans un jeu + - [x] Afficher les statistiques de tous les jeux +- [x] Dans un jeu - [x] Consulter la réponse à une question trop difficile - [ ] Choisir le statut d'une question - [x] Modifier la question - [x] Supprimer la question - [ ] Afficher les statistiques du jeu de question en cours - [ ] Notification - - une fois par jour -- [ ] Paramètres + - [ ] une fois par jour +- [x] Paramètres - [ ] Temps de réponse aux questions - [ ] Thème - [ ] Taille police diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f13875c..05f3716 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -72,6 +72,8 @@ dependencies { implementation("androidx.navigation:navigation-compose:$navVersion") implementation("androidx.compose.material:material:1.3.1") + implementation("androidx.datastore:datastore-preferences:1.0.0") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/DataStore.kt b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/DataStore.kt new file mode 100644 index 0000000..22d3187 --- /dev/null +++ b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/DataStore.kt @@ -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" diff --git a/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/MainActivity.kt b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/MainActivity.kt index c6e7e28..2b00f50 100644 --- a/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/MainActivity.kt +++ b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/MainActivity.kt @@ -1,5 +1,6 @@ package fr.uparis.diamantkennel.memorisationapplication +import android.content.Context import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -25,6 +26,9 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext 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.compose.NavHost import androidx.navigation.compose.composable @@ -32,6 +36,8 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import fr.uparis.diamantkennel.memorisationapplication.ui.theme.MemorisationApplicationTheme +val Context.dataStore: DataStore by preferencesDataStore(name = STATS) + class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/SettingsScreen.kt b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/SettingsScreen.kt index 6bf5578..df7d4ff 100644 --- a/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/SettingsScreen.kt +++ b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/SettingsScreen.kt @@ -4,19 +4,25 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Divider import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext 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 fr.uparis.diamantkennel.memorisationapplication.ui.SettingsViewModel @@ -25,23 +31,44 @@ fun SettingsScreen(padding: PaddingValues, model: SettingsViewModel = viewModel( val context = LocalContext.current var deletionDBRequest by model.deletionDB + var cleanStatRequest by model.deletionStat if (deletionDBRequest) { DeletionDBDialog(model::deleteDb) { deletionDBRequest = false } } + if (cleanStatRequest) { + CleanStatDialog(model::cleanStats) { cleanStatRequest = false } + } + Column( modifier = Modifier.padding(padding), 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( onClick = { deletionDBRequest = true }, colors = ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.red)) ) { 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)) } }, 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)) } }) diff --git a/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/ui/PlayViewModel.kt b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/ui/PlayViewModel.kt index 92c8857..f9e6c5a 100644 --- a/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/ui/PlayViewModel.kt +++ b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/ui/PlayViewModel.kt @@ -2,10 +2,17 @@ package fr.uparis.diamantkennel.memorisationapplication.ui import android.app.Application import androidx.compose.runtime.mutableStateOf +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope 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.dataStore import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -13,6 +20,12 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) { private val dao = (application as MemoApplication).database.memoDao() private var questions = mutableStateOf>(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(null) private var index = mutableStateOf(0) var proposedAnswer = mutableStateOf("") @@ -28,6 +41,9 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) { viewModelScope.launch(Dispatchers.Main) { dao.loadQuestions(setId).collect { questionList -> questions.value = questionList.shuffled() + if (questions.value.isNotEmpty()) { + stats.edit { it[statsKeyTotal] = (it[statsKeyTotal] ?: 0) + 1 } + } updateQuestion() } } @@ -41,6 +57,11 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) { if (index.value >= questions.value.size) { /* Fin des questions */ end.value = true + viewModelScope.launch { + stats.edit { + it[statsKeyTotalDone] = (it[statsKeyTotalDone] ?: 0) + 1 + } + } } else { currentQuestion.value = questions.value[index.value] } @@ -81,7 +102,7 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) { fun checkAnswer() { val probaReponse = calcSimilarite(currentQuestion.value!!.reponse, proposedAnswer.value) - if (probaReponse >= .60f) { + if (probaReponse >= .70f) { evaluatedAnswer.value = AnswerType.GOOD } else { evaluatedAnswer.value = AnswerType.BAD @@ -89,17 +110,23 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) { } fun sbUpdate() { - /* TODO: Statistiques à sauvegarder : - * - temps de réponse - * - taux réussite (ratio bonne/mauvaise réponse - * - * Tout ça va être récupérer depuis ici */ + /* TODO: Statistiques à sauvegarder : temps de réponse */ when (evaluatedAnswer.value!!) { AnswerType.GOOD -> { + viewModelScope.launch { + stats.edit { + it[statsKeyTotalGood] = (it[statsKeyTotalGood] ?: 0) + 1 + } + } newQuestion() } AnswerType.BAD -> { + viewModelScope.launch { + stats.edit { + it[statsKeyTotalBad] = (it[statsKeyTotalBad] ?: 0) + 1 + } + } reset() } } diff --git a/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/ui/SettingsViewModel.kt b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/ui/SettingsViewModel.kt index c387c6f..4e95763 100644 --- a/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/ui/SettingsViewModel.kt +++ b/app/src/main/java/fr/uparis/diamantkennel/memorisationapplication/ui/SettingsViewModel.kt @@ -2,15 +2,36 @@ package fr.uparis.diamantkennel.memorisationapplication.ui import android.app.Application import androidx.compose.runtime.mutableStateOf +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope 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.flow.map import kotlinx.coroutines.launch class SettingsViewModel(application: Application) : AndroidViewModel(application) { 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 deletionStat = mutableStateOf(false) fun deleteDb() { deletionDB.value = false @@ -18,4 +39,24 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application 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() + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 609afb9..2fbdd74 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -41,4 +41,13 @@ Non Bravo Le set de questions est terminé ! + Statistiques + Set lancés + Set terminés + Réponses bonnes + Réponses mauvaises + Taux de victoire + Réinitialiser stats + Réinitialiser les statistiques + Voulez-vous réinitialiser les statistiques ?