mirror of
https://github.com/LucasVbr/Queezer.git
synced 2026-05-13 17:11:55 +00:00
refactor: Use data classes for Settings + Game
This commit is contained in:
@@ -51,6 +51,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation(libs.retrofit)
|
implementation(libs.retrofit)
|
||||||
implementation(libs.converter.gson)
|
implementation(libs.converter.gson)
|
||||||
|
implementation("io.coil-kt:coil-compose:2.4.0")
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
package fr.univpau.queezer
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.JsonObject
|
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
data class Track(
|
|
||||||
val title: String,
|
|
||||||
val artist: Artist,
|
|
||||||
val preview: String,
|
|
||||||
val album: Album
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Artist(
|
|
||||||
val name: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Album(
|
|
||||||
val cover: String
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun fetchTracks(apiUrl: String): List<Track> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
val url = URL(apiUrl)
|
|
||||||
val connection = url.openConnection() as HttpURLConnection
|
|
||||||
connection.requestMethod = "GET"
|
|
||||||
|
|
||||||
if (connection.responseCode == HttpURLConnection.HTTP_OK) {
|
|
||||||
val response = connection.inputStream.bufferedReader().use { it.readText() }
|
|
||||||
val json = Gson().fromJson(response, JsonObject::class.java)
|
|
||||||
val tracksJson = json["tracks"].asJsonObject["data"].toString()
|
|
||||||
val trackListType = object : TypeToken<List<Track>>() {}.type
|
|
||||||
Gson().fromJson<List<Track>>(tracksJson, trackListType)
|
|
||||||
} else {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun displayTracks(apiUrl: String) {
|
|
||||||
val tracks = fetchTracks(apiUrl)
|
|
||||||
|
|
||||||
tracks?.forEach { track ->
|
|
||||||
Log.d("Track", track.title)
|
|
||||||
Log.d("Artist", track.artist.name)
|
|
||||||
Log.d("Preview", track.preview)
|
|
||||||
Log.d("Album", track.album.cover)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
package fr.univpau.queezer
|
|
||||||
|
|
||||||
import android.os.CountDownTimer
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
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.text.KeyboardOptions
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextField
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GameScreen(navController: NavHostController) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val loadedSettings = loadSettings(context)
|
|
||||||
|
|
||||||
var selectedGameMode by remember { mutableStateOf(loadedSettings.gameMode) }
|
|
||||||
val numberOfTitles by remember { mutableIntStateOf(loadedSettings.numberOfTitles.toInt()) }
|
|
||||||
val playlistUrl by remember { mutableStateOf(loadedSettings.playlistUrl) }
|
|
||||||
var tracks by remember { mutableStateOf(emptyList<Track>()) }
|
|
||||||
|
|
||||||
LaunchedEffect(playlistUrl) {
|
|
||||||
tracks = fetchTracks(playlistUrl)
|
|
||||||
if (tracks.isEmpty()) {
|
|
||||||
// Affiche un message d'erreur en toast
|
|
||||||
Toast.makeText(context, "Impossible de charger les titres, veuillez vérifier la validité de l'URL.", Toast.LENGTH_SHORT).show()
|
|
||||||
|
|
||||||
// Retourn à l'écran d'accueil
|
|
||||||
navController.popBackStack()
|
|
||||||
}
|
|
||||||
|
|
||||||
tracks = tracks.shuffled() // On mélange les titres
|
|
||||||
Log.i("Tracks", tracks.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
val score = remember { mutableIntStateOf(0) }
|
|
||||||
val remainingTitles = remember { mutableIntStateOf(numberOfTitles) } // Exemple avec 5 titres restants
|
|
||||||
val userInput = remember { mutableStateOf("") }
|
|
||||||
// val albumCover: Painter = painterResource(id = R.drawable.album_cover) // Remplacez par une ressource valide d'album
|
|
||||||
val isCoverVisible = remember { mutableStateOf(false) }
|
|
||||||
val totalTime = 30000L // 30 secondes
|
|
||||||
var timeLeft by remember { mutableStateOf(totalTime / 1000) }
|
|
||||||
|
|
||||||
var currentTrackIndex by remember { mutableIntStateOf(0) }
|
|
||||||
|
|
||||||
// Timer de 30 secondes
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
object : CountDownTimer(totalTime, 1000) { // Tick toutes les secondes
|
|
||||||
override fun onTick(millisUntilFinished: Long) {
|
|
||||||
timeLeft = millisUntilFinished / 1000 // Mettre à jour en secondes
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFinish() {
|
|
||||||
timeLeft = 0 // Compte à rebours terminé
|
|
||||||
currentTrackIndex += 1 // Passer à la chanson suivante
|
|
||||||
}
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
// Score
|
|
||||||
Text("Score : ${score.intValue}", fontSize = 24.sp)
|
|
||||||
|
|
||||||
// Nombre de titres restants
|
|
||||||
Text("Titres restants : ${remainingTitles.intValue}", fontSize = 20.sp)
|
|
||||||
|
|
||||||
// Timer
|
|
||||||
Text("Temps restant : $timeLeft s", fontSize = 20.sp)
|
|
||||||
|
|
||||||
// Affichage de la couverture de l'album
|
|
||||||
if (isCoverVisible.value) {
|
|
||||||
// Image(painter = albumCover, contentDescription = "Cover", modifier = Modifier.fillMaxWidth())
|
|
||||||
} else {
|
|
||||||
Text("Couverture cachée", fontSize = 18.sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Champ de texte pour entrer la proposition
|
|
||||||
TextField(
|
|
||||||
value = userInput.value,
|
|
||||||
onValueChange = { userInput.value = it },
|
|
||||||
label = { Text("Titre / Artiste") },
|
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text),
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Bouton Valider la réponse
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
// Logique pour valider la réponse, en vérifiant la casse et en ajustant le score
|
|
||||||
val correctAnswer = "Titre Correct" // Exemple, il faut remplacer par la bonne réponse
|
|
||||||
if (userInput.value.trim().equals(correctAnswer, ignoreCase = true)) {
|
|
||||||
score.value += 1 // Ajouter 10 points pour une bonne réponse
|
|
||||||
}
|
|
||||||
remainingTitles.value -= 1
|
|
||||||
userInput.value = "" // Réinitialiser le champ de texte
|
|
||||||
// Réinitialiser ou ajuster le timer si nécessaire
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text("Valider")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bouton Passer
|
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
// Logique pour passer la chanson
|
|
||||||
remainingTitles.value -= 1
|
|
||||||
// Vous pouvez réinitialiser le timer, ou passer à la chanson suivante
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text("Passer")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bouton Abandonner
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
// Logique pour abandonner, peut-être retour à l'écran d'accueil
|
|
||||||
navController.popBackStack()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text("Abandonner")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,12 @@ import android.os.Bundle
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.lifecycle.lifecycleScope
|
import fr.univpau.queezer.screen.GameScreen
|
||||||
import kotlinx.coroutines.launch
|
import fr.univpau.queezer.screen.HomeScreen
|
||||||
|
import fr.univpau.queezer.screen.SettingsScreen
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -27,14 +26,8 @@ fun QueezerApp() {
|
|||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
NavHost(navController = navController, startDestination = "home") {
|
NavHost(navController = navController, startDestination = "home") {
|
||||||
composable("home") {
|
composable("home") { HomeScreen(navController) }
|
||||||
HomeScreen(navController)
|
composable("game") { GameScreen(navController) }
|
||||||
}
|
composable("settings") { SettingsScreen(navController) }
|
||||||
composable("game") {
|
|
||||||
GameScreen(navController)
|
|
||||||
}
|
|
||||||
composable("settings") {
|
|
||||||
SettingsScreen(navController)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package fr.univpau.queezer
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
|
|
||||||
val gameModes = listOf("Titre Uniquement", "Artiste Uniquement", "Titre et Artiste")
|
|
||||||
|
|
||||||
fun saveSettings(context: Context, gameMode: String, numberOfTitles: String, playlistUrl: String) {
|
|
||||||
val sharedPreferences: SharedPreferences =
|
|
||||||
context.getSharedPreferences("AppSettings", Context.MODE_PRIVATE)
|
|
||||||
with(sharedPreferences.edit()) {
|
|
||||||
putString("gameMode", gameMode)
|
|
||||||
putString("numberOfTitles", numberOfTitles)
|
|
||||||
putString("playlistUrl", playlistUrl)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadSettings(context: Context): Settings {
|
|
||||||
val sharedPreferences: SharedPreferences =
|
|
||||||
context.getSharedPreferences("AppSettings", Context.MODE_PRIVATE)
|
|
||||||
val gameMode = sharedPreferences.getString("gameMode", gameModes[0]) ?: gameModes[0]
|
|
||||||
val numberOfTitles = sharedPreferences.getString("numberOfTitles", "30") ?: "30"
|
|
||||||
val playlistUrl = sharedPreferences.getString("playlistUrl", "") ?: ""
|
|
||||||
return Settings(gameMode, numberOfTitles, playlistUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Settings(val gameMode: String, val numberOfTitles: String, val playlistUrl: String)
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package fr.univpau.queezer.data
|
||||||
|
|
||||||
|
data class Album(
|
||||||
|
val cover: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package fr.univpau.queezer.data
|
||||||
|
|
||||||
|
data class Artist(
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package fr.univpau.queezer.data
|
||||||
|
|
||||||
|
import java.sql.Date
|
||||||
|
|
||||||
|
data class Game(
|
||||||
|
val settings: Settings,
|
||||||
|
val tracks: List<Track>,
|
||||||
|
val score: Int,
|
||||||
|
val date: Date
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package fr.univpau.queezer.data
|
||||||
|
|
||||||
|
enum class GameMode {
|
||||||
|
TITLE,
|
||||||
|
ARTIST,
|
||||||
|
ALL
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package fr.univpau.queezer.data
|
||||||
|
|
||||||
|
import fr.univpau.queezer.R
|
||||||
|
|
||||||
|
data class Settings(
|
||||||
|
var gameMode: GameMode = GameMode.TITLE,
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playlistUrl.startsWith("https://api.deezer.com/playlist/")) {
|
||||||
|
throw IllegalArgumentException(context.resources.getString(R.string.error_playlist_url_invalid))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numberOfTitles == null) {
|
||||||
|
throw IllegalArgumentException(context.resources.getString(R.string.error_tracks_count_empty))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numberOfTitles!! <= 0) {
|
||||||
|
throw IllegalArgumentException(context.resources.getString(R.string.error_tracks_count_negative))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package fr.univpau.queezer.data
|
||||||
|
|
||||||
|
data class Track(
|
||||||
|
val title: String,
|
||||||
|
val artist: Artist,
|
||||||
|
val preview: String,
|
||||||
|
val album: Album
|
||||||
|
)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package fr.univpau.queezer.manager
|
||||||
|
|
||||||
|
import android.media.MediaPlayer
|
||||||
|
|
||||||
|
class AudioManager {
|
||||||
|
private var mediaPlayer: MediaPlayer = MediaPlayer()
|
||||||
|
|
||||||
|
fun play(url: String) {
|
||||||
|
mediaPlayer.apply {
|
||||||
|
setDataSource(url)
|
||||||
|
prepare()
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
mediaPlayer.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pause() {
|
||||||
|
mediaPlayer.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resume() {
|
||||||
|
mediaPlayer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isPlaying(): Boolean {
|
||||||
|
return mediaPlayer.isPlaying
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package fr.univpau.queezer.manager
|
||||||
|
|
||||||
|
import android.os.CountDownTimer
|
||||||
|
|
||||||
|
class CountdownManager (val duration: Long, val onFinish: () -> Unit) {
|
||||||
|
|
||||||
|
var timeLeft = duration;
|
||||||
|
var interval = 1000L;
|
||||||
|
var timer: CountDownTimer? = null
|
||||||
|
|
||||||
|
private fun create() {
|
||||||
|
timer = object : CountDownTimer(duration, interval) {
|
||||||
|
override fun onTick(millisUntilFinished: Long) {
|
||||||
|
timeLeft = millisUntilFinished / 1000;
|
||||||
|
}
|
||||||
|
override fun onFinish() { onFinish() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
create()
|
||||||
|
timer?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
timer?.cancel()
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restart() {
|
||||||
|
stop()
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package fr.univpau.queezer.manager
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import fr.univpau.queezer.data.Settings
|
||||||
|
import fr.univpau.queezer.data.Track
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
class GameManager(var settings: Settings) {
|
||||||
|
|
||||||
|
val audioManager = AudioManager()
|
||||||
|
val countDownManager = CountdownManager(30000L, onFinish = ::nextTrack)
|
||||||
|
|
||||||
|
var tracks: List<Track> = mutableListOf()
|
||||||
|
|
||||||
|
var currentTrackIndex: Int = 0;
|
||||||
|
var score = 0
|
||||||
|
|
||||||
|
suspend fun loadTracks() {
|
||||||
|
val url = URL(settings.playlistUrl)
|
||||||
|
val connection = url.openConnection() as HttpURLConnection
|
||||||
|
connection.requestMethod = "GET"
|
||||||
|
|
||||||
|
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
throw Exception("Failed to load tracks")
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = connection.inputStream.bufferedReader().use { it.readText() }
|
||||||
|
val json = Gson().fromJson(response, JsonObject::class.java)
|
||||||
|
val tracksJson = json["tracks"].asJsonObject["data"].toString()
|
||||||
|
|
||||||
|
// Assurez-vous d'utiliser un TypeToken explicite pour une liste de Track
|
||||||
|
tracks = Gson().fromJson(tracksJson, object : TypeToken<List<Track>>() {}.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun nextTrack() {
|
||||||
|
// Stop the current track
|
||||||
|
audioManager.stop()
|
||||||
|
|
||||||
|
// Play the next track
|
||||||
|
if (currentTrackIndex >= tracks.size - 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTrackIndex++
|
||||||
|
audioManager.play(getCurrentTrack().preview)
|
||||||
|
|
||||||
|
// Restart the countdown
|
||||||
|
countDownManager.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentTrack(): Track {
|
||||||
|
if (tracks.isEmpty()) {
|
||||||
|
throw IllegalStateException("La liste des pistes est vide")
|
||||||
|
}
|
||||||
|
return tracks[currentTrackIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
if (tracks.isEmpty()) {
|
||||||
|
throw IllegalStateException("Aucune piste n'a été trouvée dans la playlist")
|
||||||
|
}
|
||||||
|
countDownManager.start()
|
||||||
|
audioManager.play(tracks[currentTrackIndex].preview)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package fr.univpau.queezer.manager
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import fr.univpau.queezer.data.Settings
|
||||||
|
|
||||||
|
fun saveSettings(context: Context, settings: Settings) {
|
||||||
|
|
||||||
|
val sharedPreferences = context.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||||
|
val editor = sharedPreferences.edit()
|
||||||
|
|
||||||
|
// Sérialiser l'objet Settings en JSON
|
||||||
|
val json = Gson().toJson(settings)
|
||||||
|
|
||||||
|
// Sauvegarder le JSON dans les SharedPreferences
|
||||||
|
editor.putString("settings", json)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadSettings(context: Context): Settings {
|
||||||
|
val sharedPreferences = context.getSharedPreferences("app_preferences", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
// Récupérer le JSON depuis SharedPreferences
|
||||||
|
val json = sharedPreferences.getString("settings", null)
|
||||||
|
|
||||||
|
// Si le JSON n'est pas null, le convertir en objet Settings
|
||||||
|
return if (json != null) {
|
||||||
|
Gson().fromJson(json, Settings::class.java)
|
||||||
|
} else {
|
||||||
|
Settings()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package fr.univpau.queezer.screen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
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.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
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.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.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import fr.univpau.queezer.R
|
||||||
|
import fr.univpau.queezer.data.Settings
|
||||||
|
import fr.univpau.queezer.manager.loadSettings
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GameScreen(navController: NavHostController) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val settings: Settings = loadSettings(context)
|
||||||
|
|
||||||
|
// val gameManager: GameManager = remember { GameManager(settings) }
|
||||||
|
|
||||||
|
// LaunchedEffect(gameManager) {
|
||||||
|
// gameManager.loadTracks()
|
||||||
|
// gameManager.start()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// État de l'utilisateur et des éléments du jeu
|
||||||
|
val userInput = remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
// Affichage de l'interface
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text("Score : 0", fontSize = 24.sp)
|
||||||
|
|
||||||
|
Text("Temps restant : 30sec", fontSize = 20.sp)
|
||||||
|
|
||||||
|
// Affiche une image a partir d'une url
|
||||||
|
AsyncImage(
|
||||||
|
model = "https://api.deezer.com/album/382921287/image",
|
||||||
|
contentDescription = "Image from URL",
|
||||||
|
modifier = Modifier
|
||||||
|
.width(200.dp)
|
||||||
|
.height(200.dp)
|
||||||
|
.blur(30.dp)
|
||||||
|
,
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
|
||||||
|
Text("Titre : Légende Vivante", fontSize = 20.sp)
|
||||||
|
Text("Artiste : Lorenzo", fontSize = 20.sp)
|
||||||
|
|
||||||
|
// Champ de texte pour entrer la proposition
|
||||||
|
TextField(
|
||||||
|
value = userInput.value,
|
||||||
|
onValueChange = { userInput.value = it },
|
||||||
|
label = { Text("Titre / Artiste") },
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bouton Valider la réponse
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
userInput.value = "" // Réinitialiser le champ de texte
|
||||||
|
}
|
||||||
|
) { Text(context.resources.getString(R.string.submit)) }
|
||||||
|
|
||||||
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
userInput.value = "" // Réinitialiser le champ de texte
|
||||||
|
},
|
||||||
|
) { Text(context.resources.getString(R.string.skip)) }
|
||||||
|
|
||||||
|
Button(onClick = { navController.popBackStack() })
|
||||||
|
{ Text(context.resources.getString(R.string.give_up)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package fr.univpau.queezer
|
package fr.univpau.queezer.screen
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
+37
-26
@@ -1,5 +1,6 @@
|
|||||||
package fr.univpau.queezer
|
package fr.univpau.queezer.screen
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -13,10 +14,8 @@ import androidx.compose.material3.RadioButton
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -24,15 +23,16 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import fr.univpau.queezer.R
|
||||||
|
import fr.univpau.queezer.data.GameMode
|
||||||
|
import fr.univpau.queezer.manager.loadSettings
|
||||||
|
import fr.univpau.queezer.manager.saveSettings
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(navController: NavHostController) {
|
fun SettingsScreen(navController: NavHostController) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val loadedSettings = loadSettings(context)
|
val settings = remember { mutableStateOf(loadSettings(context)) }
|
||||||
|
|
||||||
var selectedGameMode by remember { mutableStateOf(loadedSettings.gameMode) }
|
|
||||||
var numberOfTitles by remember { mutableStateOf(loadedSettings.numberOfTitles) }
|
|
||||||
var playlistUrl by remember { mutableStateOf(loadedSettings.playlistUrl) }
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -43,7 +43,7 @@ fun SettingsScreen(navController: NavHostController) {
|
|||||||
) {
|
) {
|
||||||
// Titre des paramètres
|
// Titre des paramètres
|
||||||
Text(
|
Text(
|
||||||
text = "Paramètres",
|
text = context.resources.getString(R.string.settings),
|
||||||
fontSize = 32.sp,
|
fontSize = 32.sp,
|
||||||
modifier = Modifier.padding(bottom = 32.dp)
|
modifier = Modifier.padding(bottom = 32.dp)
|
||||||
)
|
)
|
||||||
@@ -54,10 +54,12 @@ fun SettingsScreen(navController: NavHostController) {
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
TextField(
|
TextField(
|
||||||
value = playlistUrl,
|
label = { Text(context.resources.getString(R.string.playlist_url_label)) },
|
||||||
onValueChange = { playlistUrl = it },
|
placeholder = { Text(context.resources.getString(R.string.playlist_url_hint)) },
|
||||||
label = { Text("URL de la playlist") },
|
modifier = Modifier.fillMaxWidth(),
|
||||||
modifier = Modifier.fillMaxWidth()
|
value = settings.value.playlistUrl,
|
||||||
|
onValueChange = { settings.value = settings.value.copy(playlistUrl = it) },
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,11 +69,13 @@ fun SettingsScreen(navController: NavHostController) {
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
TextField(
|
TextField(
|
||||||
value = numberOfTitles,
|
label = { Text(context.resources.getString(R.string.tracks_count_label)) },
|
||||||
onValueChange = { numberOfTitles = it },
|
modifier = Modifier.fillMaxWidth(1f),
|
||||||
label = { Text("Nombre de titres dans une partie") },
|
value = settings.value.numberOfTitles?.toString() ?: "",
|
||||||
|
onValueChange = {
|
||||||
|
settings.value = settings.value.copy(numberOfTitles = it.toIntOrNull())
|
||||||
|
},
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
|
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
|
||||||
modifier = Modifier.fillMaxWidth(1f)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,16 +86,20 @@ fun SettingsScreen(navController: NavHostController) {
|
|||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Text(text = "Mode de jeu", fontSize = 18.sp)
|
Text(text = "Mode de jeu", fontSize = 18.sp)
|
||||||
gameModes.forEach { option ->
|
val gameModes = context.resources.getStringArray(R.array.game_modes)
|
||||||
|
gameModes.forEachIndexed { index, label ->
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(end = 16.dp)
|
modifier = Modifier.padding(end = 16.dp)
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
selected = selectedGameMode == option,
|
selected = settings.value.gameMode.ordinal == index,
|
||||||
onClick = { selectedGameMode = option }
|
onClick = {
|
||||||
|
settings.value =
|
||||||
|
settings.value.copy(gameMode = GameMode.entries[index])
|
||||||
|
}
|
||||||
)
|
)
|
||||||
Text(text = option)
|
Text(text = label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,17 +112,20 @@ fun SettingsScreen(navController: NavHostController) {
|
|||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { navController.popBackStack() },
|
onClick = { navController.popBackStack() },
|
||||||
) {
|
) { Text(context.resources.getString(R.string.back)) }
|
||||||
Text("Retour")
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
saveSettings(context, selectedGameMode, numberOfTitles, playlistUrl)
|
try {
|
||||||
|
settings.value.validate(context)
|
||||||
|
saveSettings(context, settings.value)
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text("Valider")
|
Text(context.resources.getString(R.string.submit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,32 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Queezer</string>
|
<string name="app_name">Queezer</string>
|
||||||
|
|
||||||
|
<string name="quick_play">Partie rapide</string>
|
||||||
|
<string name="custom_play">Partie personnalisée</string>
|
||||||
|
<string name="settings">Paramètre</string>
|
||||||
|
<string name="score">Score</string>
|
||||||
|
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="playlist_url_label">URL de la playlist</string>
|
||||||
|
<string name="playlist_url_hint">https://api.deezer.com/playlist/...</string>
|
||||||
|
<string name="error_playlist_url_empty">L\'URL de la playlist ne peut pas être vide</string>
|
||||||
|
<string name="error_playlist_url_invalid">L\'URL de la playlist est invalide</string>
|
||||||
|
|
||||||
|
<string name="tracks_count_label">Nombre de titres dans une partie</string>
|
||||||
|
<string name="tracks_count_hint">10</string>
|
||||||
|
<string name="error_tracks_count_empty">Le nombre de titres ne peut pas être vide</string>
|
||||||
|
<string name="error_tracks_count_negative">Le nombre de titres doit être supérieur à 0</string>
|
||||||
|
|
||||||
|
<string name="game_mode_label">Mode de jeu</string>
|
||||||
|
<string-array name="game_modes">
|
||||||
|
<item>Titres</item>
|
||||||
|
<item>Artistes</item>
|
||||||
|
<item>Tout</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string name="back">Retour</string>
|
||||||
|
<string name="skip">Passer</string>
|
||||||
|
<string name="give_up">Abandonner</string>
|
||||||
|
<string name="submit">Valider</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user