diff --git a/app/src/main/java/fr/univpau/queezer/data/Album.kt b/app/src/main/java/fr/univpau/queezer/data/Album.kt deleted file mode 100644 index 1069977..0000000 --- a/app/src/main/java/fr/univpau/queezer/data/Album.kt +++ /dev/null @@ -1,5 +0,0 @@ -package fr.univpau.queezer.data - -data class Album( - val cover: String -) diff --git a/app/src/main/java/fr/univpau/queezer/data/Artist.kt b/app/src/main/java/fr/univpau/queezer/data/Artist.kt deleted file mode 100644 index a945ba6..0000000 --- a/app/src/main/java/fr/univpau/queezer/data/Artist.kt +++ /dev/null @@ -1,5 +0,0 @@ -package fr.univpau.queezer.data - -data class Artist( - val name: String -) diff --git a/app/src/main/java/fr/univpau/queezer/data/Track.kt b/app/src/main/java/fr/univpau/queezer/data/Track.kt index 8a5a1e0..c40c52d 100644 --- a/app/src/main/java/fr/univpau/queezer/data/Track.kt +++ b/app/src/main/java/fr/univpau/queezer/data/Track.kt @@ -1,8 +1,19 @@ package fr.univpau.queezer.data data class Track( - val title: String, - val artist: Artist, - val preview: String, - val album: Album + val preview: String, // Music preview URL + val album: String, // Album picture URL + val title: Input, // Title of the track + val artist: Input, // Artist of the track ) + +data class Input( + val value: String, + val answer: Answer = Answer.UNKNOWN +) + +enum class Answer { + CORRECT, + INCORRECT, + UNKNOWN +} \ No newline at end of file diff --git a/app/src/main/java/fr/univpau/queezer/manager/AudioManager.kt b/app/src/main/java/fr/univpau/queezer/manager/AudioManager.kt deleted file mode 100644 index 44e1316..0000000 --- a/app/src/main/java/fr/univpau/queezer/manager/AudioManager.kt +++ /dev/null @@ -1,31 +0,0 @@ -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 - } -} \ No newline at end of file diff --git a/app/src/main/java/fr/univpau/queezer/manager/CountdownManager.kt b/app/src/main/java/fr/univpau/queezer/manager/CountdownManager.kt index 60a2faf..6a2ae89 100644 --- a/app/src/main/java/fr/univpau/queezer/manager/CountdownManager.kt +++ b/app/src/main/java/fr/univpau/queezer/manager/CountdownManager.kt @@ -4,7 +4,7 @@ import android.os.CountDownTimer class CountdownManager (val duration: Long, val onFinish: () -> Unit) { - var timeLeft = duration; + var timeLeft = duration / 1000; var interval = 1000L; var timer: CountDownTimer? = null diff --git a/app/src/main/java/fr/univpau/queezer/manager/GameManager.kt b/app/src/main/java/fr/univpau/queezer/manager/GameManager.kt index 20a1b50..b835c07 100644 --- a/app/src/main/java/fr/univpau/queezer/manager/GameManager.kt +++ b/app/src/main/java/fr/univpau/queezer/manager/GameManager.kt @@ -1,71 +1,59 @@ package fr.univpau.queezer.manager -import com.google.gson.Gson -import com.google.gson.JsonObject -import com.google.gson.reflect.TypeToken +import android.media.MediaPlayer +import android.util.Log 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) { +class GameManager(var settings: Settings, val tracks: List) { - val audioManager = AudioManager() + private var mediaPlayer: MediaPlayer = MediaPlayer() val countDownManager = CountdownManager(30000L, onFinish = ::nextTrack) - var tracks: List = 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>() {}.type) + fun getCurrentTrack(): Track? { + if (tracks.isEmpty()) return null; + return tracks[currentTrackIndex] } + fun stop() { + mediaPlayer.release() + // countDownManager.stop() + } fun nextTrack() { - // Stop the current track - audioManager.stop() + mediaPlayer.release() - // Play the next track - if (currentTrackIndex >= tracks.size - 1) { - return - } + + if (currentTrackIndex >= tracks.size - 1) { + return; // TODO vérifier avant meme de lancer la partie si le nombre de titres est suffisant + } + + if (currentTrackIndex >= settings.numberOfTitles!! - 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") + mediaPlayer = MediaPlayer().apply { + setDataSource(tracks[currentTrackIndex].preview) + prepare() + start() } - return tracks[currentTrackIndex] + + // countDownManager.restart() } fun start() { - if (tracks.isEmpty()) { - throw IllegalStateException("Aucune piste n'a été trouvée dans la playlist") + // countDownManager.start() + Log.i("GameManager", "Next track: ${tracks[currentTrackIndex].preview}") + mediaPlayer = MediaPlayer().apply { + setDataSource(tracks[currentTrackIndex].preview) + prepare() + start() } - countDownManager.start() - audioManager.play(tracks[currentTrackIndex].preview) - } } \ No newline at end of file diff --git a/app/src/main/java/fr/univpau/queezer/manager/TrackManager.kt b/app/src/main/java/fr/univpau/queezer/manager/TrackManager.kt new file mode 100644 index 0000000..18ba01a --- /dev/null +++ b/app/src/main/java/fr/univpau/queezer/manager/TrackManager.kt @@ -0,0 +1,31 @@ +package fr.univpau.queezer.manager + +import fr.univpau.queezer.data.Input +import fr.univpau.queezer.data.Track +import fr.univpau.queezer.service.createDeezerApiService +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +suspend fun fetchAndFormatPlaylist(apiUrl: String): List { + val deezerApiService = createDeezerApiService() + + return withContext(Dispatchers.IO) { + try { + // Récupérer la réponse de la playlist + val playlist = deezerApiService.getPlaylist(apiUrl) + + // Transformer les données en liste de `Track` + playlist.tracks.data.map { track -> + Track( + preview = track.preview, + album = track.album.cover, + title = Input(value = track.title), + artist = Input(value = track.artist.name) + ) + } + } catch (e: Exception) { + e.printStackTrace() + emptyList() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/univpau/queezer/screen/GameScreen.kt b/app/src/main/java/fr/univpau/queezer/screen/GameScreen.kt index 19e1aec..664e317 100644 --- a/app/src/main/java/fr/univpau/queezer/screen/GameScreen.kt +++ b/app/src/main/java/fr/univpau/queezer/screen/GameScreen.kt @@ -1,10 +1,9 @@ package fr.univpau.queezer.screen -import androidx.compose.foundation.Image +import android.util.Log 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 @@ -12,14 +11,18 @@ 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.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme 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.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.draw.blur import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.KeyboardType @@ -30,18 +33,25 @@ import fr.univpau.queezer.R import fr.univpau.queezer.data.Settings import fr.univpau.queezer.manager.loadSettings import coil.compose.AsyncImage +import fr.univpau.queezer.manager.GameManager +import fr.univpau.queezer.manager.fetchAndFormatPlaylist @Composable fun GameScreen(navController: NavHostController) { val context = LocalContext.current val settings: Settings = loadSettings(context) - // val gameManager: GameManager = remember { GameManager(settings) } + var gameManager by remember { mutableStateOf(GameManager(settings, emptyList())) } + var currentTrack by remember { mutableStateOf(gameManager.getCurrentTrack()) } - // LaunchedEffect(gameManager) { - // gameManager.loadTracks() - // gameManager.start() - // } + LaunchedEffect(settings.playlistUrl) { + val tracks = fetchAndFormatPlaylist(settings.playlistUrl).shuffled() + gameManager = GameManager(settings, tracks) + + currentTrack = gameManager.getCurrentTrack() + gameManager.start() + Log.i("GameScreen", gameManager.getCurrentTrack().toString()) + } // État de l'utilisateur et des éléments du jeu val userInput = remember { mutableStateOf("") } @@ -54,51 +64,59 @@ fun GameScreen(navController: NavHostController) { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { + if (currentTrack == null) { + CircularProgressIndicator( + modifier = Modifier.width(64.dp), + color = MaterialTheme.colorScheme.secondary, + trackColor = MaterialTheme.colorScheme.surfaceVariant, + ) + } else { + Text("Score : ${gameManager.score}", fontSize = 24.sp) + Text("Temps restant : ${gameManager.countDownManager.timeLeft}sec", fontSize = 20.sp) - Text("Score : 0", fontSize = 24.sp) + AsyncImage( + model = currentTrack!!.album, + contentDescription = "Image from URL", + modifier = Modifier + .width(200.dp) + .height(200.dp), + contentScale = ContentScale.Crop + ) - Text("Temps restant : 30sec", fontSize = 20.sp) + Text("Titre : ${currentTrack!!.title.value}", fontSize = 20.sp) + Text("Artiste : ${currentTrack!!.artist.value}", 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 - ) + // 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() + ) - 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) { + // Bouton Valider la réponse Button( onClick = { userInput.value = "" // Réinitialiser le champ de texte - }, - ) { Text(context.resources.getString(R.string.skip)) } + } + ) { Text(context.resources.getString(R.string.submit)) } - Button(onClick = { navController.popBackStack() }) - { Text(context.resources.getString(R.string.give_up)) } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Button( + onClick = { + userInput.value = "" // Réinitialiser le champ de texte + gameManager.nextTrack() + currentTrack = gameManager.getCurrentTrack() + }, + ) { Text(context.resources.getString(R.string.skip)) } + + Button(onClick = { + gameManager.stop() + navController.popBackStack() + }) + { Text(context.resources.getString(R.string.give_up)) } + } } } } diff --git a/app/src/main/java/fr/univpau/queezer/service/DeezerApiService.kt b/app/src/main/java/fr/univpau/queezer/service/DeezerApiService.kt new file mode 100644 index 0000000..da7cc2b --- /dev/null +++ b/app/src/main/java/fr/univpau/queezer/service/DeezerApiService.kt @@ -0,0 +1,43 @@ +package fr.univpau.queezer.service + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.GET +import retrofit2.http.Url + +data class PlaylistResponse( + val tracks: TrackList +) + +data class TrackList( + val data: List +) + +data class ApiTrack( + val preview: String, // URL d'aperçu de la musique + val album: ApiAlbum, // Informations sur l'album + val title: String, // Titre de la piste + val artist: Artist // Informations sur l'artiste +) + +data class ApiAlbum( + val cover: String // URL de la couverture de l'album +) + +data class Artist( + val name: String // Nom de l'artiste +) + + +interface DeezerApiService { + @GET + suspend fun getPlaylist(@Url url: String): PlaylistResponse +} + +fun createDeezerApiService(): DeezerApiService { + return Retrofit.Builder() + .baseUrl("https://api.deezer.com/") // Base URL par défaut + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(DeezerApiService::class.java) +} \ No newline at end of file