From 13a95f2d55a3464b5c1cd17960dc08c422cfbaa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luc=C3=A0s?= <86352901+LucasVbr@users.noreply.github.com> Date: Tue, 14 Jan 2025 01:15:43 +0100 Subject: [PATCH] feat: Update UI --- .../java/fr/univpau/queezer/data/Settings.kt | 1 + .../queezer/manager/CountdownManager.kt | 1 + .../fr/univpau/queezer/manager/GameManager.kt | 47 +-- .../univpau/queezer/manager/TrackManager.kt | 24 -- .../java/fr/univpau/queezer/ui/theme/Theme.kt | 2 +- .../queezer/view/components/TrackCardItem.kt | 46 +-- .../queezer/view/screens/GameScreen.kt | 302 ++++++++++++------ .../queezer/view/screens/ScoreScreen.kt | 152 +-------- .../queezer/view/screens/SettingsScreen.kt | 205 ++++++------ .../queezer/viewmodel/PlaylistViewModel.kt | 35 ++ app/src/main/res/values/strings.xml | 4 +- 11 files changed, 407 insertions(+), 412 deletions(-) create mode 100644 app/src/main/java/fr/univpau/queezer/viewmodel/PlaylistViewModel.kt diff --git a/app/src/main/java/fr/univpau/queezer/data/Settings.kt b/app/src/main/java/fr/univpau/queezer/data/Settings.kt index a28fc46..ed390f3 100644 --- a/app/src/main/java/fr/univpau/queezer/data/Settings.kt +++ b/app/src/main/java/fr/univpau/queezer/data/Settings.kt @@ -7,6 +7,7 @@ data class Settings( var numberOfTitles: Int? = 5, var playlistUrl: String = "https://api.deezer.com/playlist/13279914183", ) { + fun validate(context: android.content.Context) { if (playlistUrl.isEmpty()) { throw IllegalArgumentException(context.resources.getString(R.string.error_playlist_url_empty)) diff --git a/app/src/main/java/fr/univpau/queezer/manager/CountdownManager.kt b/app/src/main/java/fr/univpau/queezer/manager/CountdownManager.kt index 6520f25..e224a01 100644 --- a/app/src/main/java/fr/univpau/queezer/manager/CountdownManager.kt +++ b/app/src/main/java/fr/univpau/queezer/manager/CountdownManager.kt @@ -1,5 +1,6 @@ package fr.univpau.queezer.manager +import android.content.Context import android.os.CountDownTimer class CountdownManager (val duration: Long, val onTickTimer: () -> Unit, val onFinishTimer: () -> Unit) { diff --git a/app/src/main/java/fr/univpau/queezer/manager/GameManager.kt b/app/src/main/java/fr/univpau/queezer/manager/GameManager.kt index 33f6d10..7314529 100644 --- a/app/src/main/java/fr/univpau/queezer/manager/GameManager.kt +++ b/app/src/main/java/fr/univpau/queezer/manager/GameManager.kt @@ -3,6 +3,10 @@ package fr.univpau.queezer.manager import android.content.Context import android.media.MediaPlayer 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.Game import fr.univpau.queezer.data.GameMode @@ -18,23 +22,23 @@ import java.util.Locale class GameManager() { private lateinit var databaseService: DatabaseService + private var mediaPlayer: MediaPlayer = MediaPlayer() var countDownManager = CountdownManager(30000L, {}, {}) var settings: Settings = Settings() - // var tracks: List = emptyList() var playlist: Playlist = Playlist() - var gameFinished : Boolean = false; - var currentTrackIndex: Int = 0; - var score = 0 + var gameFinished by mutableStateOf(false) + var currentTrackIndex by mutableIntStateOf(0) + var score by mutableIntStateOf(0) - constructor(settings: Settings, playlist: Playlist, onTick: () -> Unit, databaseService: DatabaseService) : this() { - this.databaseService = databaseService + constructor(settings: Settings, playlist: Playlist, onTickTrack: () -> Unit, onFinishTrack: () -> Unit, databaseService: DatabaseService) : this() { this.settings = settings + this.databaseService = databaseService 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) { if (settings.gameMode == GameMode.TITLE) { @@ -53,11 +57,8 @@ class GameManager() { fun checkTitleAnswer(currentTrack: Track?, answerTitle: String) { if (currentTrack == null) return; - Log.i("GameManager", "Current track: ${currentTrack.title.value}") - Log.i("GameManager", "Answer: $answerTitle") - - var simpleTitleAnswer = formatString(answerTitle) - var simpleTitle = formatString(currentTrack.title.value) + val simpleTitleAnswer = formatString(answerTitle) + val simpleTitle = formatString(currentTrack.title.value) if (simpleTitle.equals(simpleTitleAnswer, ignoreCase = true)) { currentTrack.title.answer = Answer.CORRECT @@ -70,11 +71,8 @@ class GameManager() { fun checkArtistAnswer(currentTrack: Track?, answerArtist: String) { if (currentTrack == null) return; - Log.i("GameManager", "Current track: ${currentTrack.artist.value}") - Log.i("GameManager", "Answer: $answerArtist") - - var simpleArtistAnswer = formatString(answerArtist) - var simpleArtist = formatString(currentTrack.artist.value) + val simpleArtistAnswer = formatString(answerArtist) + val simpleArtist = formatString(currentTrack.artist.value) if (simpleArtist.equals(simpleArtistAnswer, ignoreCase = true)) { currentTrack.artist.answer = Answer.CORRECT @@ -84,7 +82,7 @@ class GameManager() { } } - fun formatString(input: String): String { + private fun formatString(input: String): String { return input .trim() .lowercase(Locale.ROOT) @@ -109,8 +107,11 @@ class GameManager() { fun nextTrack() { mediaPlayer.release() + Log.i("GameManager", "Current track index: $currentTrackIndex") + Log.i("GameManager", "Playlist size: ${playlist.tracks.size}") + if (currentTrackIndex + 1 >= playlist.tracks.size) { - gameFinished = true + this.gameFinished = true return; } @@ -128,7 +129,6 @@ class GameManager() { fun start() { countDownManager.start() - Log.i("GameManager", "Next track: ${playlist.tracks[currentTrackIndex].preview}") mediaPlayer = MediaPlayer().apply { setDataSource(playlist.tracks[currentTrackIndex].preview) prepare() @@ -137,8 +137,6 @@ class GameManager() { } 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 val game = Game( id = 0, @@ -153,4 +151,9 @@ class GameManager() { databaseService.gameDao().insert(game) } } + + fun pause() { + mediaPlayer.pause() + countDownManager.stop() + } } \ No newline at end of file diff --git a/app/src/main/java/fr/univpau/queezer/manager/TrackManager.kt b/app/src/main/java/fr/univpau/queezer/manager/TrackManager.kt index 852dd2b..af15ead 100644 --- a/app/src/main/java/fr/univpau/queezer/manager/TrackManager.kt +++ b/app/src/main/java/fr/univpau/queezer/manager/TrackManager.kt @@ -31,28 +31,4 @@ suspend fun fetchPlaylist(apiUrl: String) : Playlist? { null; } } -} - -suspend fun fetchAndFormatPlaylist(apiUrl: String): List { - 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() - } - } } \ No newline at end of file diff --git a/app/src/main/java/fr/univpau/queezer/ui/theme/Theme.kt b/app/src/main/java/fr/univpau/queezer/ui/theme/Theme.kt index c4a70bc..85e422e 100644 --- a/app/src/main/java/fr/univpau/queezer/ui/theme/Theme.kt +++ b/app/src/main/java/fr/univpau/queezer/ui/theme/Theme.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.platform.LocalContext private val DarkColorScheme = darkColorScheme( primary = Purple80, secondary = PurpleGrey80, - tertiary = Pink80 + tertiary = Pink80, ) private val LightColorScheme = lightColorScheme( diff --git a/app/src/main/java/fr/univpau/queezer/view/components/TrackCardItem.kt b/app/src/main/java/fr/univpau/queezer/view/components/TrackCardItem.kt index a0238d8..421755f 100644 --- a/app/src/main/java/fr/univpau/queezer/view/components/TrackCardItem.kt +++ b/app/src/main/java/fr/univpau/queezer/view/components/TrackCardItem.kt @@ -8,6 +8,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -16,44 +17,25 @@ import fr.univpau.queezer.data.Track @Composable fun TrackCardItem(track: Track) { - val titleColor = when (track.title.answer) { - Answer.CORRECT -> { - 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) - ) { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { Text( text = track.title.value, fontSize = 14.sp, 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 + } } \ No newline at end of file diff --git a/app/src/main/java/fr/univpau/queezer/view/screens/GameScreen.kt b/app/src/main/java/fr/univpau/queezer/view/screens/GameScreen.kt index 7a53f0e..0e994ee 100644 --- a/app/src/main/java/fr/univpau/queezer/view/screens/GameScreen.kt +++ b/app/src/main/java/fr/univpau/queezer/view/screens/GameScreen.kt @@ -1,6 +1,6 @@ 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.Column 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.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button +import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -28,7 +33,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.blur import androidx.compose.ui.layout.ContentScale 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.sp 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.fetchPlaylist import fr.univpau.queezer.service.DatabaseService -import kotlinx.coroutines.launch +import kotlinx.coroutines.CoroutineScope @Composable fun GameScreen(navController: NavHostController, database: DatabaseService) { @@ -52,118 +57,213 @@ fun GameScreen(navController: NavHostController, database: DatabaseService) { var gameManager by remember { mutableStateOf(GameManager()) } 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 artistInput = remember { mutableStateOf("") } - // Affichage de l'interface - Column( - 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 = { + LaunchedEffect(settings.playlistUrl) { + val playlist = fetchPlaylist(settings.playlistUrl) - coroutineScope.launch { - gameManager.save(context) + gameManager = GameManager( + 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, + artistInput: MutableState, + 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() navController.popBackStack() - } - }) { Text(context.resources.getString(R.string.back)) } - } else { - Text("Score : ${gameManager.score}", fontSize = 24.sp) - Text("Temps restant : ${countdown}sec", fontSize = 20.sp) + }) + { Text(context.resources.getString(R.string.give_up)) } - 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( onClick = { titleInput.value = "" artistInput.value = "" 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 = { - gameManager.stop() - navController.popBackStack() - }) - { Text(context.resources.getString(R.string.give_up)) } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + 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)) } + } +} diff --git a/app/src/main/java/fr/univpau/queezer/view/screens/ScoreScreen.kt b/app/src/main/java/fr/univpau/queezer/view/screens/ScoreScreen.kt index 53d9193..91dda65 100644 --- a/app/src/main/java/fr/univpau/queezer/view/screens/ScoreScreen.kt +++ b/app/src/main/java/fr/univpau/queezer/view/screens/ScoreScreen.kt @@ -47,7 +47,6 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel) val averageSuccessRate = games.sumOf { it.score }.div(max(games.size, 1)) val filteredGames = filterGames(filter.value, games) - Log.i("ScoreScreen", "filteredGames: $filteredGames") Scaffold( topBar = { @@ -79,7 +78,6 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel) .padding(innerPadding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - Row( modifier = Modifier .fillMaxWidth() @@ -100,146 +98,16 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel) // 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)) -// } - - //} } \ No newline at end of file diff --git a/app/src/main/java/fr/univpau/queezer/view/screens/SettingsScreen.kt b/app/src/main/java/fr/univpau/queezer/view/screens/SettingsScreen.kt index acaf7a6..e39beda 100644 --- a/app/src/main/java/fr/univpau/queezer/view/screens/SettingsScreen.kt +++ b/app/src/main/java/fr/univpau/queezer/view/screens/SettingsScreen.kt @@ -7,19 +7,30 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.selection.selectable 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.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.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.Role 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.sp 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.saveSettings +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen(navController: NavHostController) { val context = LocalContext.current val settings = remember { mutableStateOf(loadSettings(context)) } - 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.settings), - fontSize = 32.sp, - modifier = Modifier.padding(bottom = 32.dp) - ) - - // 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()) + Scaffold( + topBar = { + CenterAlignedTopAppBar( + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.primary, + ), + title = { + Text( + text = context.resources.getString(R.string.settings), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) }, - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), - ) - } - - // Paramètre : Mode de jeu - 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]) - } + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = context.resources.getString(R.string.back) ) - 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)) - } - } } } diff --git a/app/src/main/java/fr/univpau/queezer/viewmodel/PlaylistViewModel.kt b/app/src/main/java/fr/univpau/queezer/viewmodel/PlaylistViewModel.kt new file mode 100644 index 0000000..c9fae88 --- /dev/null +++ b/app/src/main/java/fr/univpau/queezer/viewmodel/PlaylistViewModel.kt @@ -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() + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52d4d5b..55dba8f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,7 +25,7 @@ Retour - Passer + Suivant Abandonner Valider @@ -33,5 +33,7 @@ Taux de réussite moyen Filtrer par Date + La playlist n\'as pas été trouvée + Le nombre de titres et trop grand \ No newline at end of file