feat: Update UI

This commit is contained in:
Lucàs
2025-01-14 01:15:43 +01:00
parent 5f027ce683
commit 13a95f2d55
11 changed files with 407 additions and 412 deletions
@@ -7,6 +7,7 @@ data class Settings(
var numberOfTitles: Int? = 5, var numberOfTitles: Int? = 5,
var playlistUrl: String = "https://api.deezer.com/playlist/13279914183", var playlistUrl: String = "https://api.deezer.com/playlist/13279914183",
) { ) {
fun validate(context: android.content.Context) { fun validate(context: android.content.Context) {
if (playlistUrl.isEmpty()) { if (playlistUrl.isEmpty()) {
throw IllegalArgumentException(context.resources.getString(R.string.error_playlist_url_empty)) throw IllegalArgumentException(context.resources.getString(R.string.error_playlist_url_empty))
@@ -1,5 +1,6 @@
package fr.univpau.queezer.manager package fr.univpau.queezer.manager
import android.content.Context
import android.os.CountDownTimer import android.os.CountDownTimer
class CountdownManager (val duration: Long, val onTickTimer: () -> Unit, val onFinishTimer: () -> Unit) { class CountdownManager (val duration: Long, val onTickTimer: () -> Unit, val onFinishTimer: () -> Unit) {
@@ -3,6 +3,10 @@ package fr.univpau.queezer.manager
import android.content.Context import android.content.Context
import android.media.MediaPlayer import android.media.MediaPlayer
import android.util.Log import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import fr.univpau.queezer.data.Answer import fr.univpau.queezer.data.Answer
import fr.univpau.queezer.data.Game import fr.univpau.queezer.data.Game
import fr.univpau.queezer.data.GameMode import fr.univpau.queezer.data.GameMode
@@ -18,23 +22,23 @@ import java.util.Locale
class GameManager() { class GameManager() {
private lateinit var databaseService: DatabaseService private lateinit var databaseService: DatabaseService
private var mediaPlayer: MediaPlayer = MediaPlayer() private var mediaPlayer: MediaPlayer = MediaPlayer()
var countDownManager = CountdownManager(30000L, {}, {}) var countDownManager = CountdownManager(30000L, {}, {})
var settings: Settings = Settings() var settings: Settings = Settings()
// var tracks: List<Track> = emptyList()
var playlist: Playlist = Playlist() var playlist: Playlist = Playlist()
var gameFinished : Boolean = false;
var currentTrackIndex: Int = 0; var gameFinished by mutableStateOf(false)
var score = 0 var currentTrackIndex by mutableIntStateOf(0)
var score by mutableIntStateOf(0)
constructor(settings: Settings, playlist: Playlist, onTick: () -> Unit, databaseService: DatabaseService) : this() { constructor(settings: Settings, playlist: Playlist, onTickTrack: () -> Unit, onFinishTrack: () -> Unit, databaseService: DatabaseService) : this() {
this.databaseService = databaseService
this.settings = settings this.settings = settings
this.databaseService = databaseService
this.playlist = Playlist(playlist.title, playlist.tracks.subList(0, settings.numberOfTitles!!)) this.playlist = Playlist(playlist.title, playlist.tracks.subList(0, settings.numberOfTitles!!))
this.countDownManager = CountdownManager(30000L, onTickTimer = onTick, onFinishTimer = { nextTrack() }) this.countDownManager = CountdownManager(duration = 30000L, onTickTimer = onTickTrack, onFinishTimer = { onFinishTrack() })
for (track in playlist.tracks) { for (track in playlist.tracks) {
if (settings.gameMode == GameMode.TITLE) { if (settings.gameMode == GameMode.TITLE) {
@@ -53,11 +57,8 @@ class GameManager() {
fun checkTitleAnswer(currentTrack: Track?, answerTitle: String) { fun checkTitleAnswer(currentTrack: Track?, answerTitle: String) {
if (currentTrack == null) return; if (currentTrack == null) return;
Log.i("GameManager", "Current track: ${currentTrack.title.value}") val simpleTitleAnswer = formatString(answerTitle)
Log.i("GameManager", "Answer: $answerTitle") val simpleTitle = formatString(currentTrack.title.value)
var simpleTitleAnswer = formatString(answerTitle)
var simpleTitle = formatString(currentTrack.title.value)
if (simpleTitle.equals(simpleTitleAnswer, ignoreCase = true)) { if (simpleTitle.equals(simpleTitleAnswer, ignoreCase = true)) {
currentTrack.title.answer = Answer.CORRECT currentTrack.title.answer = Answer.CORRECT
@@ -70,11 +71,8 @@ class GameManager() {
fun checkArtistAnswer(currentTrack: Track?, answerArtist: String) { fun checkArtistAnswer(currentTrack: Track?, answerArtist: String) {
if (currentTrack == null) return; if (currentTrack == null) return;
Log.i("GameManager", "Current track: ${currentTrack.artist.value}") val simpleArtistAnswer = formatString(answerArtist)
Log.i("GameManager", "Answer: $answerArtist") val simpleArtist = formatString(currentTrack.artist.value)
var simpleArtistAnswer = formatString(answerArtist)
var simpleArtist = formatString(currentTrack.artist.value)
if (simpleArtist.equals(simpleArtistAnswer, ignoreCase = true)) { if (simpleArtist.equals(simpleArtistAnswer, ignoreCase = true)) {
currentTrack.artist.answer = Answer.CORRECT currentTrack.artist.answer = Answer.CORRECT
@@ -84,7 +82,7 @@ class GameManager() {
} }
} }
fun formatString(input: String): String { private fun formatString(input: String): String {
return input return input
.trim() .trim()
.lowercase(Locale.ROOT) .lowercase(Locale.ROOT)
@@ -109,8 +107,11 @@ class GameManager() {
fun nextTrack() { fun nextTrack() {
mediaPlayer.release() mediaPlayer.release()
Log.i("GameManager", "Current track index: $currentTrackIndex")
Log.i("GameManager", "Playlist size: ${playlist.tracks.size}")
if (currentTrackIndex + 1 >= playlist.tracks.size) { if (currentTrackIndex + 1 >= playlist.tracks.size) {
gameFinished = true this.gameFinished = true
return; return;
} }
@@ -128,7 +129,6 @@ class GameManager() {
fun start() { fun start() {
countDownManager.start() countDownManager.start()
Log.i("GameManager", "Next track: ${playlist.tracks[currentTrackIndex].preview}")
mediaPlayer = MediaPlayer().apply { mediaPlayer = MediaPlayer().apply {
setDataSource(playlist.tracks[currentTrackIndex].preview) setDataSource(playlist.tracks[currentTrackIndex].preview)
prepare() prepare()
@@ -137,8 +137,6 @@ class GameManager() {
} }
fun save(context: Context) { fun save(context: Context) {
// TODO Sauvegarder tout le jeu en base de donnée locale (Room) dans un objet Game
// Créer un objet Game // Créer un objet Game
val game = Game( val game = Game(
id = 0, id = 0,
@@ -153,4 +151,9 @@ class GameManager() {
databaseService.gameDao().insert(game) databaseService.gameDao().insert(game)
} }
} }
fun pause() {
mediaPlayer.pause()
countDownManager.stop()
}
} }
@@ -31,28 +31,4 @@ suspend fun fetchPlaylist(apiUrl: String) : Playlist? {
null; null;
} }
} }
}
suspend fun fetchAndFormatPlaylist(apiUrl: String): List<Track> {
val deezerApiService = createDeezerApiService()
return withContext(Dispatchers.IO) {
try {
// Récupérer la réponse de la playlist
val playlist = deezerApiService.getPlaylist(apiUrl)
// Transformer les données en liste de `Track`
playlist.tracks.data.map { track ->
Track(
preview = track.preview,
album = track.album.cover,
title = Input(value = track.title),
artist = Input(value = track.artist.name)
)
}
} catch (e: Exception) {
e.printStackTrace()
emptyList()
}
}
} }
@@ -14,7 +14,7 @@ import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme( private val DarkColorScheme = darkColorScheme(
primary = Purple80, primary = Purple80,
secondary = PurpleGrey80, secondary = PurpleGrey80,
tertiary = Pink80 tertiary = Pink80,
) )
private val LightColorScheme = lightColorScheme( private val LightColorScheme = lightColorScheme(
@@ -8,6 +8,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -16,44 +17,25 @@ import fr.univpau.queezer.data.Track
@Composable @Composable
fun TrackCardItem(track: Track) { fun TrackCardItem(track: Track) {
val titleColor = when (track.title.answer) { Card(modifier = Modifier.fillMaxWidth()) {
Answer.CORRECT -> { Column(modifier = Modifier.padding(16.dp)) {
MaterialTheme.colorScheme.primary
}
Answer.INCORRECT -> {
MaterialTheme.colorScheme.error
}
else -> {
MaterialTheme.colorScheme.onSurface
}
}
val artistColor = when (track.artist.answer) {
Answer.CORRECT -> {
MaterialTheme.colorScheme.primary
}
Answer.INCORRECT -> {
MaterialTheme.colorScheme.error
}
else -> {
MaterialTheme.colorScheme.onSurface
}
}
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text( Text(
text = track.title.value, text = track.title.value,
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
color = titleColor color = getColor(track.title.answer)
) )
Text(text = track.artist.value, color = artistColor) Text(text = track.artist.value, color = getColor(track.artist.answer))
} }
} }
}
@Composable
fun getColor(answer: Answer): Color {
return when (answer) {
Answer.CORRECT -> Color(0xFF4CAF50)
Answer.INCORRECT -> MaterialTheme.colorScheme.error
else -> MaterialTheme.colorScheme.onSurface
}
} }
@@ -1,6 +1,6 @@
package fr.univpau.queezer.view.screens package fr.univpau.queezer.view.screens
import android.util.Log import android.content.Context
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -9,14 +9,19 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -28,7 +33,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.blur
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@@ -41,7 +46,7 @@ import fr.univpau.queezer.data.Playlist
import fr.univpau.queezer.manager.GameManager import fr.univpau.queezer.manager.GameManager
import fr.univpau.queezer.manager.fetchPlaylist import fr.univpau.queezer.manager.fetchPlaylist
import fr.univpau.queezer.service.DatabaseService import fr.univpau.queezer.service.DatabaseService
import kotlinx.coroutines.launch import kotlinx.coroutines.CoroutineScope
@Composable @Composable
fun GameScreen(navController: NavHostController, database: DatabaseService) { fun GameScreen(navController: NavHostController, database: DatabaseService) {
@@ -52,118 +57,213 @@ fun GameScreen(navController: NavHostController, database: DatabaseService) {
var gameManager by remember { mutableStateOf(GameManager()) } var gameManager by remember { mutableStateOf(GameManager()) }
var countdown by remember { mutableIntStateOf(30) } var countdown by remember { mutableIntStateOf(30) }
LaunchedEffect(settings.playlistUrl) {
val playlist = fetchPlaylist(settings.playlistUrl)
gameManager = GameManager(settings, playlist ?: Playlist(), { countdown = gameManager.countDownManager.timeLeft.toInt() }, database)
gameManager.start()
Log.i("GameScreen", gameManager.getCurrentTrack().toString())
}
// État de l'utilisateur et des éléments du jeu
val titleInput = remember { mutableStateOf("") } val titleInput = remember { mutableStateOf("") }
val artistInput = remember { mutableStateOf("") } val artistInput = remember { mutableStateOf("") }
// Affichage de l'interface LaunchedEffect(settings.playlistUrl) {
Column( val playlist = fetchPlaylist(settings.playlistUrl)
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (gameManager.getCurrentTrack() == null) {
CircularProgressIndicator(
modifier = Modifier.width(64.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
} else if (gameManager.gameFinished) {
Text("Partie terminée !", fontSize = 24.sp)
Text("Score : ${gameManager.score}", fontSize = 20.sp)
Button(onClick = {
coroutineScope.launch { gameManager = GameManager(
gameManager.save(context) settings = settings,
playlist = playlist ?: Playlist(),
onTickTrack = {
countdown = gameManager.countDownManager.timeLeft.toInt()
},
onFinishTrack = {
titleInput.value = ""
artistInput.value = ""
gameManager.nextTrack()
},
databaseService = database
)
gameManager.start()
}
if (gameManager.getCurrentTrack() == null) {
LoadingScreen()
} else if (gameManager.gameFinished) {
FinishScreen(gameManager, context, navController)
} else {
InGameScreen(
gameManager,
context,
coroutineScope,
navController,
titleInput,
artistInput,
countdown
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun InGameScreen(
gameManager: GameManager,
context: Context,
coroutineScope: CoroutineScope,
navController: NavHostController,
titleInput: MutableState<String>,
artistInput: MutableState<String>,
countdown: Int
) {
Scaffold(
topBar = {
CenterAlignedTopAppBar(
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
Text(
text = "${countdown}sec",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
navigationIcon = { Text("${gameManager.currentTrackIndex + 1}/${gameManager.playlist.tracks.size}") },
actions = { Text("${gameManager.score}pts") }
)
},
bottomBar = {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedButton(onClick = {
gameManager.stop() gameManager.stop()
navController.popBackStack() navController.popBackStack()
} })
}) { Text(context.resources.getString(R.string.back)) } { Text(context.resources.getString(R.string.give_up)) }
} else {
Text("Score : ${gameManager.score}", fontSize = 24.sp)
Text("Temps restant : ${countdown}sec", fontSize = 20.sp)
if (gameManager.getCurrentTrack()!!.title.answer == Answer.CORRECT && gameManager.getCurrentTrack()!!.artist.answer == Answer.UNKNOWN
|| gameManager.getCurrentTrack()!!.title.answer == Answer.UNKNOWN && gameManager.getCurrentTrack()!!.artist.answer == Answer.CORRECT
|| gameManager.getCurrentTrack()!!.title.answer == Answer.CORRECT && gameManager.getCurrentTrack()!!.artist.answer == Answer.CORRECT) {
AsyncImage(
model = gameManager.getCurrentTrack()!!.album,
contentDescription = "Image from URL",
modifier = Modifier
.width(200.dp)
.height(200.dp)
,
contentScale = ContentScale.Crop
)
} else {
AsyncImage(
model = gameManager.getCurrentTrack()!!.album,
contentDescription = "Image from URL",
modifier = Modifier
.width(200.dp)
.height(200.dp)
.blur(50.dp)
,
contentScale = ContentScale.Crop
)
}
Row {
if (gameManager.getCurrentTrack()!!.title.answer == Answer.CORRECT || gameManager.getCurrentTrack()!!.title.answer == Answer.UNKNOWN) {
Text("Titre : ${gameManager.getCurrentTrack()!!.title.value}", fontSize = 20.sp)
} else {
TextField(
value = titleInput.value,
onValueChange = {
titleInput.value = it;
gameManager.checkTitleAnswer(gameManager.getCurrentTrack(), titleInput.value)
},
label = { Text("Titre") },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text),
modifier = Modifier.fillMaxWidth()
)
}
}
Row {
if (gameManager.getCurrentTrack()!!.artist.answer == Answer.CORRECT || gameManager.getCurrentTrack()!!.artist.answer == Answer.UNKNOWN) {
Text("Artiste : ${gameManager.getCurrentTrack()!!.artist.value}", fontSize = 20.sp)
} else {
TextField(
value = artistInput.value,
onValueChange = { artistInput.value = it; gameManager.checkArtistAnswer(gameManager.getCurrentTrack(), artistInput.value) },
label = { Text("Artiste") },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text),
modifier = Modifier.fillMaxWidth()
)
}
}
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button( Button(
onClick = { onClick = {
titleInput.value = "" titleInput.value = ""
artistInput.value = "" artistInput.value = ""
gameManager.nextTrack() gameManager.nextTrack()
}, },
) { Text(context.resources.getString(R.string.skip)) } ) { Text(context.resources.getString(R.string.next)) }
}
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Button(onClick = { Column(
gameManager.stop() modifier = Modifier
navController.popBackStack() .fillMaxWidth()
}) .padding(16.dp),
{ Text(context.resources.getString(R.string.give_up)) } horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (gameManager.getCurrentTrack()!!.title.answer == Answer.CORRECT && gameManager.getCurrentTrack()!!.artist.answer == Answer.UNKNOWN
|| gameManager.getCurrentTrack()!!.title.answer == Answer.UNKNOWN && gameManager.getCurrentTrack()!!.artist.answer == Answer.CORRECT
|| gameManager.getCurrentTrack()!!.title.answer == Answer.CORRECT && gameManager.getCurrentTrack()!!.artist.answer == Answer.CORRECT
) {
AsyncImage(
model = gameManager.getCurrentTrack()!!.album,
contentDescription = "Image from URL",
modifier = Modifier
.width(200.dp)
.height(200.dp),
contentScale = ContentScale.Crop
)
} else {
AsyncImage(
model = gameManager.getCurrentTrack()!!.album,
contentDescription = "Image from URL",
modifier = Modifier
.width(200.dp)
.height(200.dp)
.blur(50.dp),
contentScale = ContentScale.Crop
)
}
Row {
if (gameManager.getCurrentTrack()!!.title.answer == Answer.CORRECT || gameManager.getCurrentTrack()!!.title.answer == Answer.UNKNOWN) {
Text(
"Titre : ${gameManager.getCurrentTrack()!!.title.value}",
fontSize = 20.sp
)
} else {
TextField(
value = titleInput.value,
onValueChange = {
titleInput.value = it;
gameManager.checkTitleAnswer(
gameManager.getCurrentTrack(),
titleInput.value
)
},
label = { Text("Titre") },
modifier = Modifier.fillMaxWidth()
)
}
}
Row {
if (gameManager.getCurrentTrack()!!.artist.answer == Answer.CORRECT || gameManager.getCurrentTrack()!!.artist.answer == Answer.UNKNOWN) {
Text(
gameManager.getCurrentTrack()!!.artist.value,
fontSize = 20.sp
)
} else {
TextField(
value = artistInput.value,
onValueChange = {
artistInput.value = it;
gameManager.checkArtistAnswer(
gameManager.getCurrentTrack(),
artistInput.value
)
},
label = { Text("Artiste") },
modifier = Modifier.fillMaxWidth()
)
}
}
} }
} }
} }
} }
@Composable
fun LoadingScreen() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(
modifier = Modifier.width(64.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
}
}
@Composable
fun FinishScreen(gameManager: GameManager, context: Context, navController: NavHostController) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Partie terminée !", fontSize = 24.sp)
Text("Score : ${gameManager.score}", fontSize = 20.sp)
Button(onClick = {
gameManager.save(context)
gameManager.stop()
navController.popBackStack()
}) { Text(context.resources.getString(R.string.back)) }
}
}
@@ -47,7 +47,6 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel)
val averageSuccessRate = games.sumOf { it.score }.div(max(games.size, 1)) val averageSuccessRate = games.sumOf { it.score }.div(max(games.size, 1))
val filteredGames = filterGames(filter.value, games) val filteredGames = filterGames(filter.value, games)
Log.i("ScoreScreen", "filteredGames: $filteredGames")
Scaffold( Scaffold(
topBar = { topBar = {
@@ -79,7 +78,6 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel)
.padding(innerPadding), .padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -100,146 +98,16 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel)
// Todo add filters // Todo add filters
GameCardItemList(filteredGames) if (filteredGames.isEmpty()) {
Column (
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Aucune partie trouvée... (。•́︿•̀。)", fontSize = 16.sp, color = MaterialTheme.colorScheme.onSurface)
}
} else {
GameCardItemList(filteredGames)
}
} }
} }
// Column(
// modifier = Modifier
// .fillMaxSize()
// .padding(16.dp),
// horizontalAlignment = Alignment.CenterHorizontally,
// verticalArrangement = Arrangement.spacedBy(16.dp)
// ) {
// // Titre des paramètres
// Text(
// text = context.resources.getString(R.string.score),
// fontSize = 32.sp,
// modifier = Modifier.padding(bottom = 32.dp)
// )
//
// // Nombre de parties jouées
// Row(
// modifier = Modifier.fillMaxWidth(),
// horizontalArrangement = Arrangement.SpaceBetween
// ) {
// Text(
// text = context.resources.getString(R.string.games_played),
// fontSize = 24.sp,
// )
// if (gameList?.isNotEmpty() == true) {
// Text(
// text = gameList.size.toString(),
// fontSize = 24.sp,
// )
// } else {
// Text(
// text = "0",
// fontSize = 24.sp,
// )
// }
//
// }
//
// // pourcentage de réussite moyen
// Row(
// modifier = Modifier.fillMaxWidth(),
// horizontalArrangement = Arrangement.SpaceBetween
// ) {
// Text(
// text = context.resources.getString(R.string.average_success_rate),
// fontSize = 24.sp,
// )
// if (gameList?.isNotEmpty() == true) {
// Text(
// text = (gameList.sumOf { it.score }.div(gameList.size).toString()) + "%",
// fontSize = 24.sp,
// )
// } else {
// Text(
// text = "0%",
// fontSize = 24.sp,
// )
// }
// }
// filtres
// - date
// - mode de jeu titre/artiste/les deux
// - nombre de titres
// Row(
// modifier = Modifier.fillMaxWidth(),
// horizontalArrangement = Arrangement.SpaceBetween
// ) {
// Text(
// text = context.resources.getString(R.string.filters),
// fontSize = 24.sp,
// )
//
// Row(
// horizontalArrangement = Arrangement.SpaceBetween
// ){
// Button(
// onClick = { /* TODO: Filtre par date */ },
// shape = RoundedCornerShape(8.dp),
// modifier = Modifier
// .padding(vertical = 8.dp)
// ) {
// Text(context.resources.getString(R.string.date))
// }
//
// Button(
// onClick = { /* TODO: Filtre par date */ },
// shape = RoundedCornerShape(8.dp),
// modifier = Modifier
// .padding(vertical = 8.dp)
// ) {
// Text("Mode de jeu")
// }
//
// Button(
// onClick = { /* TODO: Filtre par date */ },
// shape = RoundedCornerShape(8.dp),
// modifier = Modifier
// .padding(vertical = 8.dp)
// ) {
// Text("Nombre de titres")
// }
// }
// }
// Historique des parties (score, nom)
// - cliquer sur une partie pour voir les détails
// gameList?.forEach {
// Row(
// modifier = Modifier.fillMaxWidth(),
// horizontalArrangement = Arrangement.SpaceBetween
// ) {
// Text(
// text = it.playlist.title,
// fontSize = 24.sp,
// )
// Text(
// text = it.score.toString(),
// fontSize = 24.sp,
// )
// }
// }
// if (gameList !== null) {
// GameList(gameList)
// }
//
// Button(
// onClick = { navController.navigate("home") },
// shape = RoundedCornerShape(8.dp),
// modifier = Modifier
// .fillMaxWidth()
// .padding(vertical = 8.dp)
// ) {
// Text(context.resources.getString(R.string.back))
// }
//}
} }
@@ -7,19 +7,30 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
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.foundation.selection.selectable
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
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.semantics.Role
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
@@ -28,104 +39,120 @@ import fr.univpau.queezer.data.GameMode
import fr.univpau.queezer.manager.loadSettings import fr.univpau.queezer.manager.loadSettings
import fr.univpau.queezer.manager.saveSettings import fr.univpau.queezer.manager.saveSettings
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsScreen(navController: NavHostController) { fun SettingsScreen(navController: NavHostController) {
val context = LocalContext.current val context = LocalContext.current
val settings = remember { mutableStateOf(loadSettings(context)) } val settings = remember { mutableStateOf(loadSettings(context)) }
Column( Scaffold(
modifier = Modifier topBar = {
.fillMaxSize() CenterAlignedTopAppBar(
.padding(16.dp), colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
horizontalAlignment = Alignment.CenterHorizontally, containerColor = MaterialTheme.colorScheme.primaryContainer,
verticalArrangement = Arrangement.spacedBy(16.dp) titleContentColor = MaterialTheme.colorScheme.primary,
) { ),
// Titre des paramètres title = {
Text( Text(
text = context.resources.getString(R.string.settings), text = context.resources.getString(R.string.settings),
fontSize = 32.sp, maxLines = 1,
modifier = Modifier.padding(bottom = 32.dp) overflow = TextOverflow.Ellipsis
) )
// Paramètre : Playlist à utiliser
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
TextField(
label = { Text(context.resources.getString(R.string.playlist_url_label)) },
placeholder = { Text(context.resources.getString(R.string.playlist_url_hint)) },
modifier = Modifier.fillMaxWidth(),
value = settings.value.playlistUrl,
onValueChange = { settings.value = settings.value.copy(playlistUrl = it) },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri),
)
}
// Paramètre : Nombre de titres dans une partie
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
TextField(
label = { Text(context.resources.getString(R.string.tracks_count_label)) },
modifier = Modifier.fillMaxWidth(1f),
value = settings.value.numberOfTitles?.toString() ?: "",
onValueChange = {
settings.value = settings.value.copy(numberOfTitles = it.toIntOrNull())
}, },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), navigationIcon = {
) IconButton(onClick = { navController.popBackStack() }) {
} Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
// Paramètre : Mode de jeu contentDescription = context.resources.getString(R.string.back)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(text = "Mode de jeu", fontSize = 18.sp)
val gameModes = context.resources.getStringArray(R.array.game_modes)
gameModes.forEachIndexed { index, label ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(end = 16.dp)
) {
RadioButton(
selected = settings.value.gameMode.ordinal == index,
onClick = {
settings.value =
settings.value.copy(gameMode = GameMode.entries[index])
}
) )
Text(text = label) }
},
)
},
bottomBar = {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(
onClick = {
try {
settings.value.validate(context)
saveSettings(context, settings.value)
navController.popBackStack()
} catch (e: Exception) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
},
modifier = Modifier.fillMaxWidth()
) { Text(context.resources.getString(R.string.submit)) }
}
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
TextField(
label = { Text(context.resources.getString(R.string.playlist_url_label)) },
placeholder = { Text(context.resources.getString(R.string.playlist_url_hint)) },
modifier = Modifier.fillMaxWidth(),
value = settings.value.playlistUrl,
onValueChange = { settings.value = settings.value.copy(playlistUrl = it) },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri),
)
TextField(
label = { Text(context.resources.getString(R.string.tracks_count_label)) },
modifier = Modifier.fillMaxWidth(1f),
value = settings.value.numberOfTitles?.toString() ?: "",
onValueChange = {
settings.value = settings.value.copy(numberOfTitles = it.toIntOrNull())
},
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
)
Column (
modifier = Modifier.fillMaxWidth(),
) {
Text(text = "Mode de jeu", fontSize = 18.sp)
val gameModes = context.resources.getStringArray(R.array.game_modes)
gameModes.forEachIndexed { index, label ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.selectable(
selected = (settings.value.gameMode.ordinal == index),
onClick = {
settings.value =
settings.value.copy(gameMode = GameMode.entries[index])
},
role = Role.RadioButton
)
) {
RadioButton(
selected = settings.value.gameMode.ordinal == index,
onClick = {
settings.value =
settings.value.copy(gameMode = GameMode.entries[index])
}
)
Text(text = label)
}
} }
} }
} }
} }
// Bouton pour revenir à l'écran d'accueil
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedButton(
onClick = { navController.popBackStack() },
) { Text(context.resources.getString(R.string.back)) }
Button(
onClick = {
try {
settings.value.validate(context)
saveSettings(context, settings.value)
navController.popBackStack()
} catch (e: Exception) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
},
) {
Text(context.resources.getString(R.string.submit))
}
}
} }
} }
@@ -0,0 +1,35 @@
package fr.univpau.queezer.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import fr.univpau.queezer.data.Input
import fr.univpau.queezer.data.Playlist
import fr.univpau.queezer.data.Track
import fr.univpau.queezer.service.PlaylistResponse
import fr.univpau.queezer.service.createDeezerApiService
import kotlinx.coroutines.launch
class PlaylistViewModel(private val url: String) : ViewModel() {
var playlist: Playlist? = null
init {
val deezerApiService = createDeezerApiService()
viewModelScope.launch {
val playlistResponse : PlaylistResponse = deezerApiService.getPlaylist(url)
playlist = Playlist(
title = playlistResponse.title,
tracks = playlistResponse.tracks.data.map { track ->
Track(
preview = track.preview,
album = track.album.cover,
title = Input(value = track.title),
artist = Input(value = track.artist.name)
)
}.shuffled()
)
}
}
}
+3 -1
View File
@@ -25,7 +25,7 @@
</string-array> </string-array>
<string name="back">Retour</string> <string name="back">Retour</string>
<string name="skip">Passer</string> <string name="next">Suivant</string>
<string name="give_up">Abandonner</string> <string name="give_up">Abandonner</string>
<string name="submit">Valider</string> <string name="submit">Valider</string>
@@ -33,5 +33,7 @@
<string name="average_success_rate">Taux de réussite moyen</string> <string name="average_success_rate">Taux de réussite moyen</string>
<string name="filters">Filtrer par</string> <string name="filters">Filtrer par</string>
<string name="date">Date</string> <string name="date">Date</string>
<string name="error_playlist_not_found">La playlist n\'as pas été trouvée</string>
<string name="error_tracks_count_too_high">Le nombre de titres et trop grand</string>
</resources> </resources>