Initial commit

This commit is contained in:
Lucàs
2025-01-02 14:44:39 +01:00
commit 5a592f3e43
54 changed files with 1564 additions and 0 deletions
@@ -0,0 +1,61 @@
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): MutableState<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 {
null
}
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
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)
}
}
@@ -0,0 +1,148 @@
package fr.univpau.queezer
import android.os.CountDownTimer
import android.util.Log
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.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.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
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.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import androidx.compose.ui.res.painterResource
import kotlinx.coroutines.runBlocking
@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 = remember { mutableStateOf(emptyList<Track>()) }
LaunchedEffect(playlistUrl) {
tracks = fetchTracks(playlistUrl)
}
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")
}
}
}
}
@@ -0,0 +1,90 @@
package fr.univpau.queezer
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
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.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import fr.univpau.queezer.ui.theme.Purple40
@Composable
fun HomeScreen(navController: NavHostController) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// Logo et Titre
// Image(
// painter = painterResource(id = R.drawable.logo),
// contentDescription = "Logo Queezer",
// modifier = Modifier
// .size(120.dp)
// .padding(16.dp),
// contentScale = ContentScale.Crop
// )
Text(
text = "Queezer",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = Purple40
)
Spacer(modifier = Modifier.height(24.dp))
// Boutons
Button(
onClick = { navController.navigate("game") },
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
Text("Partie rapide")
}
Button(
onClick = { /* TODO: Partie personnalisée */ },
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
Text("Partie personnalisée")
}
Button(
onClick = { navController.navigate("settings") },
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
Text("Paramètres")
}
Button(
onClick = { /* TODO: Scores */ },
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
Text("Scores")
}
}
}
@@ -0,0 +1,40 @@
package fr.univpau.queezer
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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.composable
import androidx.navigation.compose.rememberNavController
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
QueezerApp()
}
}
}
@Composable
fun QueezerApp() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(navController)
}
composable("game") {
GameScreen(navController)
}
composable("settings") {
SettingsScreen(navController)
}
}
}
@@ -0,0 +1,121 @@
package fr.univpau.queezer
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.OutlinedButton
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
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.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 SettingsScreen(navController: NavHostController) {
val context = LocalContext.current
val loadedSettings = loadSettings(context)
var selectedGameMode by remember { mutableStateOf(loadedSettings.gameMode) }
var numberOfTitles by remember { mutableStateOf(loadedSettings.numberOfTitles) }
var playlistUrl by remember { mutableStateOf(loadedSettings.playlistUrl) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Titre des paramètres
Text(
text = "Paramètres",
fontSize = 32.sp,
modifier = Modifier.padding(bottom = 32.dp)
)
// Paramètre : Playlist à utiliser
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
TextField(
value = playlistUrl,
onValueChange = { playlistUrl = it },
label = { Text("URL de la playlist") },
modifier = Modifier.fillMaxWidth()
)
}
// Paramètre : Nombre de titres dans une partie
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
TextField(
value = numberOfTitles,
onValueChange = { numberOfTitles = it },
label = { Text("Nombre de titres dans une partie") },
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
modifier = Modifier.fillMaxWidth(1f)
)
}
// Paramètre : Mode de jeu
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(text = "Mode de jeu", fontSize = 18.sp)
gameModes.forEach { option ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(end = 16.dp)
) {
RadioButton(
selected = selectedGameMode == option,
onClick = { selectedGameMode = option }
)
Text(text = option)
}
}
}
}
// Bouton pour revenir à l'écran d'accueil
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedButton(
onClick = { navController.popBackStack() },
) {
Text("Retour")
}
Button(
onClick = {
saveSettings(context, selectedGameMode, numberOfTitles, playlistUrl)
navController.popBackStack()
},
) {
Text("Valider")
}
}
}
}
@@ -0,0 +1,28 @@
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,11 @@
package fr.univpau.queezer.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
@@ -0,0 +1,58 @@
package fr.univpau.queezer.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun QueezerTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
@@ -0,0 +1,34 @@
package fr.univpau.queezer.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)