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 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()
)
}
}
}
+3 -1
View File
@@ -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>