fix: Custom Party + Success rate

This commit is contained in:
Lucàs
2025-01-14 14:25:15 +01:00
parent edcf1b786f
commit 0befbc4ef0
7 changed files with 195 additions and 38 deletions
+1
View File
@@ -46,6 +46,7 @@ apply(
dependencies {
implementation(libs.androidx.runtime.livedata)
implementation(libs.material)
val roomVersion = "2.5.2"
@@ -1,23 +1,32 @@
package fr.univpau.queezer.data
import android.util.Log
data class Filter(
val date: DateFilter = DateFilter.DESCENDING,
val mode : List<GameMode> = listOf(GameMode.TITLE, GameMode.ARTIST, GameMode.ALL),
val nbTitle : Int? = null,
var dateOrderIsAscending: Boolean = true,
val mode : Map<GameMode, Boolean> = mapOf(
GameMode.TITLE to true,
GameMode.ARTIST to true,
GameMode.ALL to true
),
val nbTitleIsAscending : Boolean = true,
)
fun filterGames(filter: Filter, games: List<Game>): List<Game> {
return games.filter { game ->
filter.mode.contains(game.settings.gameMode) && (filter.nbTitle == null || game.playlist.tracks.size == filter.nbTitle)
}.sortedBy { game ->
when (filter.date) {
DateFilter.ASCENDING -> game.date.time
DateFilter.DESCENDING -> -game.date.time
}
}
}
Log.i("Filter", "Filtering games with $filter")
enum class DateFilter {
ASCENDING,
DESCENDING
return games
.filter { game ->
// Filtrer uniquement par les modes de jeu activés
filter.mode.filter { it.value }.keys.contains(game.settings.gameMode)
}
.sortedWith(compareBy<Game> { game ->
// Tri par date
if (filter.dateOrderIsAscending) -game.date.time else game.date.time
}.thenBy { game ->
// Tri par nombre de titres
if (filter.nbTitleIsAscending) game.settings.numberOfTitles ?: 0 else -(game.settings.numberOfTitles ?: 0)
})
}
@@ -11,4 +11,4 @@ data class Game(
val playlist: Playlist = Playlist(),
val score: Int = 0,
val date: Date = Date()
)
)
@@ -1,10 +1,13 @@
package fr.univpau.queezer.view.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
@@ -15,9 +18,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.univpau.queezer.R
import fr.univpau.queezer.data.Game
import fr.univpau.queezer.data.GameMode
import java.text.SimpleDateFormat
@@ -26,7 +31,8 @@ import java.util.Locale
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GameCardItem(game: Game) {
val formatter = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault())
val context = LocalContext.current
val formatter = SimpleDateFormat("dd MMMM yyyy - HH:mm", Locale.getDefault())
val showBottomSheet = remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
@@ -36,6 +42,8 @@ fun GameCardItem(game: Game) {
(game.settings.numberOfTitles ?: 1)
}
val gameModesLabels = context.resources.getStringArray(R.array.game_modes)
Card(
onClick = { showBottomSheet.value = true },
modifier = Modifier.fillMaxWidth()
@@ -43,14 +51,29 @@ fun GameCardItem(game: Game) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = game.playlist.title,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold
)
Text(text = formatter.format(game.date))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = game.playlist.title,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold
)
Text(text = "${game.score}/$maxScore")
Badge(
containerColor = MaterialTheme.colorScheme.primary,
content = { Text("${game.score}/${maxScore}") }
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = formatter.format(game.date))
Text(text = "Mode: ${gameModesLabels[game.settings.gameMode.ordinal]}")
}
}
}
@@ -76,14 +99,24 @@ fun GameCardItem(game: Game) {
text = game.playlist.title,
style = MaterialTheme.typography.titleMedium
)
Text(
text = "${game.score}/$maxScore",
style = MaterialTheme.typography.titleMedium
Badge(
containerColor = MaterialTheme.colorScheme.primary,
content = {
Text(
text = "${game.score}/${maxScore}pts",
style = MaterialTheme.typography.titleMedium
)
}
)
}
Text(
text = formatter.format(game.date),
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(formatter.format(game.date))
Text("Mode: ${gameModesLabels[game.settings.gameMode.ordinal]}")
}
TrackCardItemList(game.playlist.tracks)
}
@@ -3,13 +3,21 @@ package fr.univpau.queezer.view.screens
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -18,9 +26,12 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -31,22 +42,23 @@ import androidx.navigation.NavHostController
import fr.univpau.queezer.R
import fr.univpau.queezer.data.Filter
import fr.univpau.queezer.data.Game
import fr.univpau.queezer.data.GameMode
import fr.univpau.queezer.data.filterGames
import fr.univpau.queezer.view.components.GameCardItemList
import fr.univpau.queezer.viewmodel.GameViewModel
import kotlin.math.max
import java.util.Locale
import kotlin.math.round
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel) {
val context = LocalContext.current
val filter = remember { mutableStateOf(Filter()) }
var filter by remember { mutableStateOf(Filter()) }
val games: List<Game> = gameViewModel.games.observeAsState().value ?: emptyList()
val nbGames = games.size;
val averageSuccessRate = games.sumOf { it.score }.div(max(games.size, 1))
val filteredGames = filterGames(filter.value, games)
val filteredGames by remember(filter, games) { derivedStateOf { filterGames(filter, games) } }
val averageSuccessRate by remember(filteredGames) { derivedStateOf { calculateAverageSuccessRate(filteredGames) } }
val nbGames by remember(filteredGames) { derivedStateOf { filteredGames.size } }
Scaffold(
topBar = {
@@ -89,7 +101,7 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel)
Text(text = "Parties jouées", fontSize = 14.sp)
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "$averageSuccessRate%", fontSize = 24.sp, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
Text(text = "${String.format(Locale.getDefault(), "%.02f", averageSuccessRate)}%", fontSize = 24.sp, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
Text(text = "De réussite", fontSize = 14.sp)
}
}
@@ -98,6 +110,89 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel)
// Todo add filters
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
FilterChip(
selected = true,
onClick = {
// Inverser l'ordre de tri
filter = filter.copy(dateOrderIsAscending = !filter.dateOrderIsAscending)
},
label = { Text("Date") },
leadingIcon = {
if (filter.dateOrderIsAscending) {
Icon(
imageVector = Icons.Filled.KeyboardArrowUp,
contentDescription = "Tri Ascendant",
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
} else {
Icon(
imageVector = Icons.Filled.KeyboardArrowDown,
contentDescription = "Tri Descendant",
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
}
},
)
val gameModesLabels = context.resources.getStringArray(R.array.game_modes)
filter.mode.entries.forEachIndexed { index, entry ->
FilterChip(
selected = entry.value,
onClick = {
filter = filter.copy(mode = filter.mode.toMutableMap().apply {
this[entry.key] = !entry.value
})
},
leadingIcon = {
if (entry.value) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Filtre activé",
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
}
},
label = { Text(
gameModesLabels[index],
maxLines = 1,
) },
)
}
// Filtrer par nombre de titres
FilterChip(
selected = true,
onClick = {
// Inverser l'ordre de tri
filter = filter.copy(nbTitleIsAscending = !filter.nbTitleIsAscending)
},
label = { Text("Nombre de titres") },
leadingIcon = {
if (filter.nbTitleIsAscending) {
Icon(
imageVector = Icons.Filled.KeyboardArrowUp,
contentDescription = "Tri Ascendant",
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
} else {
Icon(
imageVector = Icons.Filled.KeyboardArrowDown,
contentDescription = "Tri Descendant",
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
}
},
)
}
if (filteredGames.isEmpty()) {
Column (
modifier = Modifier.fillMaxWidth(),
@@ -110,4 +205,18 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel)
}
}
}
}
fun calculateAverageSuccessRate(games: List<Game>): Double {
if (games.isEmpty()) return 0.0
val averageGameScore = games.sumOf {
if (it.settings.gameMode == GameMode.ALL) {
it.score.toDouble() / (it.settings.numberOfTitles!! * 2)
} else {
it.score.toDouble() / (it.settings.numberOfTitles ?: 1)
}
}
return (averageGameScore / games.size) * 100
}
@@ -82,6 +82,9 @@ fun SettingsScreen(navController: NavHostController, saveLocation: String = "set
settings.value.validate(context)
saveSettings(context, settings.value, saveLocation)
navController.popBackStack()
if (saveLocation != "settings") {
navController.navigate("custom_game")
}
} catch (e: Exception) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}