mirror of
https://github.com/LucasVbr/Queezer.git
synced 2026-05-13 17:11:55 +00:00
feat: Update UI
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Track> = 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()
|
||||
}
|
||||
}
|
||||
@@ -32,27 +32,3 @@ suspend fun fetchPlaylist(apiUrl: String) : Playlist? {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
tertiary = Pink80,
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,57 +57,123 @@ 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
|
||||
LaunchedEffect(settings.playlistUrl) {
|
||||
val playlist = fetchPlaylist(settings.playlistUrl)
|
||||
|
||||
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<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()
|
||||
navController.popBackStack()
|
||||
})
|
||||
{ Text(context.resources.getString(R.string.give_up)) }
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
titleInput.value = ""
|
||||
artistInput.value = ""
|
||||
gameManager.nextTrack()
|
||||
},
|
||||
) { Text(context.resources.getString(R.string.next)) }
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.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.save(context)
|
||||
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)
|
||||
|
||||
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) {
|
||||
|| 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)
|
||||
,
|
||||
.height(200.dp),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
} else {
|
||||
@@ -112,24 +183,28 @@ fun GameScreen(navController: NavHostController, database: DatabaseService) {
|
||||
modifier = Modifier
|
||||
.width(200.dp)
|
||||
.height(200.dp)
|
||||
.blur(50.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)
|
||||
Text(
|
||||
"Titre : ${gameManager.getCurrentTrack()!!.title.value}",
|
||||
fontSize = 20.sp
|
||||
)
|
||||
} else {
|
||||
TextField(
|
||||
value = titleInput.value,
|
||||
onValueChange = {
|
||||
titleInput.value = it;
|
||||
gameManager.checkTitleAnswer(gameManager.getCurrentTrack(), titleInput.value)
|
||||
gameManager.checkTitleAnswer(
|
||||
gameManager.getCurrentTrack(),
|
||||
titleInput.value
|
||||
)
|
||||
},
|
||||
label = { Text("Titre") },
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
@@ -137,33 +212,58 @@ fun GameScreen(navController: NavHostController, database: DatabaseService) {
|
||||
|
||||
Row {
|
||||
if (gameManager.getCurrentTrack()!!.artist.answer == Answer.CORRECT || gameManager.getCurrentTrack()!!.artist.answer == Answer.UNKNOWN) {
|
||||
Text("Artiste : ${gameManager.getCurrentTrack()!!.artist.value}", fontSize = 20.sp)
|
||||
Text(
|
||||
gameManager.getCurrentTrack()!!.artist.value,
|
||||
fontSize = 20.sp
|
||||
)
|
||||
} else {
|
||||
TextField(
|
||||
value = artistInput.value,
|
||||
onValueChange = { artistInput.value = it; gameManager.checkArtistAnswer(gameManager.getCurrentTrack(), 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)) }
|
||||
|
||||
Button(onClick = {
|
||||
gameManager.stop()
|
||||
navController.popBackStack()
|
||||
})
|
||||
{ Text(context.resources.getString(R.string.give_up)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 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
|
||||
|
||||
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.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,91 +39,43 @@ 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
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
title = {
|
||||
Text(
|
||||
text = context.resources.getString(R.string.settings),
|
||||
fontSize = 32.sp,
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
maxLines = 1,
|
||||
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,
|
||||
contentDescription = context.resources.getString(R.string.back)
|
||||
)
|
||||
}
|
||||
|
||||
// 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])
|
||||
}
|
||||
},
|
||||
)
|
||||
Text(text = label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bouton pour revenir à l'écran d'accueil
|
||||
},
|
||||
bottomBar = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = { navController.popBackStack() },
|
||||
) { Text(context.resources.getString(R.string.back)) }
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
try {
|
||||
@@ -123,8 +86,72 @@ fun SettingsScreen(navController: NavHostController) {
|
||||
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),
|
||||
) {
|
||||
Text(context.resources.getString(R.string.submit))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
</string-array>
|
||||
|
||||
<string name="back">Retour</string>
|
||||
<string name="skip">Passer</string>
|
||||
<string name="next">Suivant</string>
|
||||
<string name="give_up">Abandonner</string>
|
||||
<string name="submit">Valider</string>
|
||||
|
||||
@@ -33,5 +33,7 @@
|
||||
<string name="average_success_rate">Taux de réussite moyen</string>
|
||||
<string name="filters">Filtrer par</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>
|
||||
Reference in New Issue
Block a user