mirror of
https://github.com/LucasVbr/Queezer.git
synced 2026-05-13 17:11:55 +00:00
feat: Fetch tracks, update interface and music
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
package fr.univpau.queezer.data
|
|
||||||
|
|
||||||
data class Album(
|
|
||||||
val cover: String
|
|
||||||
)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package fr.univpau.queezer.data
|
|
||||||
|
|
||||||
data class Artist(
|
|
||||||
val name: String
|
|
||||||
)
|
|
||||||
@@ -1,8 +1,19 @@
|
|||||||
package fr.univpau.queezer.data
|
package fr.univpau.queezer.data
|
||||||
|
|
||||||
data class Track(
|
data class Track(
|
||||||
val title: String,
|
val preview: String, // Music preview URL
|
||||||
val artist: Artist,
|
val album: String, // Album picture URL
|
||||||
val preview: String,
|
val title: Input, // Title of the track
|
||||||
val album: Album
|
val artist: Input, // Artist of the track
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class Input(
|
||||||
|
val value: String,
|
||||||
|
val answer: Answer = Answer.UNKNOWN
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class Answer {
|
||||||
|
CORRECT,
|
||||||
|
INCORRECT,
|
||||||
|
UNKNOWN
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ import android.os.CountDownTimer
|
|||||||
|
|
||||||
class CountdownManager (val duration: Long, val onFinish: () -> Unit) {
|
class CountdownManager (val duration: Long, val onFinish: () -> Unit) {
|
||||||
|
|
||||||
var timeLeft = duration;
|
var timeLeft = duration / 1000;
|
||||||
var interval = 1000L;
|
var interval = 1000L;
|
||||||
var timer: CountDownTimer? = null
|
var timer: CountDownTimer? = null
|
||||||
|
|
||||||
|
|||||||
@@ -1,71 +1,59 @@
|
|||||||
package fr.univpau.queezer.manager
|
package fr.univpau.queezer.manager
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import android.media.MediaPlayer
|
||||||
import com.google.gson.JsonObject
|
import android.util.Log
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import fr.univpau.queezer.data.Settings
|
import fr.univpau.queezer.data.Settings
|
||||||
import fr.univpau.queezer.data.Track
|
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<Track>) {
|
||||||
|
|
||||||
val audioManager = AudioManager()
|
private var mediaPlayer: MediaPlayer = MediaPlayer()
|
||||||
val countDownManager = CountdownManager(30000L, onFinish = ::nextTrack)
|
val countDownManager = CountdownManager(30000L, onFinish = ::nextTrack)
|
||||||
|
|
||||||
var tracks: List<Track> = mutableListOf()
|
|
||||||
|
|
||||||
var currentTrackIndex: Int = 0;
|
var currentTrackIndex: Int = 0;
|
||||||
var score = 0
|
var score = 0
|
||||||
|
|
||||||
suspend fun loadTracks() {
|
fun getCurrentTrack(): Track? {
|
||||||
val url = URL(settings.playlistUrl)
|
if (tracks.isEmpty()) return null;
|
||||||
val connection = url.openConnection() as HttpURLConnection
|
return tracks[currentTrackIndex]
|
||||||
connection.requestMethod = "GET"
|
|
||||||
|
|
||||||
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
|
||||||
throw Exception("Failed to load tracks")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = connection.inputStream.bufferedReader().use { it.readText() }
|
fun stop() {
|
||||||
val json = Gson().fromJson(response, JsonObject::class.java)
|
mediaPlayer.release()
|
||||||
val tracksJson = json["tracks"].asJsonObject["data"].toString()
|
// countDownManager.stop()
|
||||||
|
|
||||||
// Assurez-vous d'utiliser un TypeToken explicite pour une liste de Track
|
|
||||||
tracks = Gson().fromJson(tracksJson, object : TypeToken<List<Track>>() {}.type)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun nextTrack() {
|
fun nextTrack() {
|
||||||
// Stop the current track
|
mediaPlayer.release()
|
||||||
audioManager.stop()
|
|
||||||
|
|
||||||
// Play the next track
|
|
||||||
if (currentTrackIndex >= tracks.size - 1) {
|
if (currentTrackIndex >= tracks.size - 1) {
|
||||||
return
|
return; // TODO vérifier avant meme de lancer la partie si le nombre de titres est suffisant
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTrackIndex >= settings.numberOfTitles!! - 1) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTrackIndex++
|
currentTrackIndex++
|
||||||
audioManager.play(getCurrentTrack().preview)
|
|
||||||
|
|
||||||
// Restart the countdown
|
mediaPlayer = MediaPlayer().apply {
|
||||||
countDownManager.restart()
|
setDataSource(tracks[currentTrackIndex].preview)
|
||||||
|
prepare()
|
||||||
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentTrack(): Track {
|
// countDownManager.restart()
|
||||||
if (tracks.isEmpty()) {
|
|
||||||
throw IllegalStateException("La liste des pistes est vide")
|
|
||||||
}
|
|
||||||
return tracks[currentTrackIndex]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if (tracks.isEmpty()) {
|
// countDownManager.start()
|
||||||
throw IllegalStateException("Aucune piste n'a été trouvée dans la playlist")
|
Log.i("GameManager", "Next track: ${tracks[currentTrackIndex].preview}")
|
||||||
|
mediaPlayer = MediaPlayer().apply {
|
||||||
|
setDataSource(tracks[currentTrackIndex].preview)
|
||||||
|
prepare()
|
||||||
|
start()
|
||||||
}
|
}
|
||||||
countDownManager.start()
|
|
||||||
audioManager.play(tracks[currentTrackIndex].preview)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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<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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
package fr.univpau.queezer.screen
|
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.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
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.layout.width
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
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.LaunchedEffect
|
||||||
|
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.draw.blur
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
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.data.Settings
|
||||||
import fr.univpau.queezer.manager.loadSettings
|
import fr.univpau.queezer.manager.loadSettings
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
|
import fr.univpau.queezer.manager.GameManager
|
||||||
|
import fr.univpau.queezer.manager.fetchAndFormatPlaylist
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GameScreen(navController: NavHostController) {
|
fun GameScreen(navController: NavHostController) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val settings: Settings = loadSettings(context)
|
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) {
|
LaunchedEffect(settings.playlistUrl) {
|
||||||
// gameManager.loadTracks()
|
val tracks = fetchAndFormatPlaylist(settings.playlistUrl).shuffled()
|
||||||
// gameManager.start()
|
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
|
// État de l'utilisateur et des éléments du jeu
|
||||||
val userInput = remember { mutableStateOf("") }
|
val userInput = remember { mutableStateOf("") }
|
||||||
@@ -54,25 +64,27 @@ fun GameScreen(navController: NavHostController) {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
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)
|
|
||||||
|
|
||||||
Text("Temps restant : 30sec", fontSize = 20.sp)
|
|
||||||
|
|
||||||
// Affiche une image a partir d'une url
|
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = "https://api.deezer.com/album/382921287/image",
|
model = currentTrack!!.album,
|
||||||
contentDescription = "Image from URL",
|
contentDescription = "Image from URL",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(200.dp)
|
.width(200.dp)
|
||||||
.height(200.dp)
|
.height(200.dp),
|
||||||
.blur(30.dp)
|
|
||||||
,
|
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
|
|
||||||
Text("Titre : Légende Vivante", fontSize = 20.sp)
|
Text("Titre : ${currentTrack!!.title.value}", fontSize = 20.sp)
|
||||||
Text("Artiste : Lorenzo", fontSize = 20.sp)
|
Text("Artiste : ${currentTrack!!.artist.value}", fontSize = 20.sp)
|
||||||
|
|
||||||
// Champ de texte pour entrer la proposition
|
// Champ de texte pour entrer la proposition
|
||||||
TextField(
|
TextField(
|
||||||
@@ -94,11 +106,17 @@ fun GameScreen(navController: NavHostController) {
|
|||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
userInput.value = "" // Réinitialiser le champ de texte
|
userInput.value = "" // Réinitialiser le champ de texte
|
||||||
|
gameManager.nextTrack()
|
||||||
|
currentTrack = gameManager.getCurrentTrack()
|
||||||
},
|
},
|
||||||
) { Text(context.resources.getString(R.string.skip)) }
|
) { Text(context.resources.getString(R.string.skip)) }
|
||||||
|
|
||||||
Button(onClick = { navController.popBackStack() })
|
Button(onClick = {
|
||||||
|
gameManager.stop()
|
||||||
|
navController.popBackStack()
|
||||||
|
})
|
||||||
{ Text(context.resources.getString(R.string.give_up)) }
|
{ Text(context.resources.getString(R.string.give_up)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<ApiTrack>
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user