USER
package com.example.musicapp.Data.Repository
import com.example.musicapp.Data.*
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Path
interface DeezerApiService {
@GET("genre?output=json") // json endpoint instead of “/”
suspend fun getGenres(): GenreResponse
@GET("genre/{genre_id}/artists")
suspend fun getArtists(@Path("genre_id") genreId: Int): ArtistResponse
@GET("artist/{artist_id}")
suspend fun getArtistDetail(@Path("artist_id") artistId: Int): ArtistDetailResponse
@GET("artist/{artist_id}/albums")
suspend fun getArtistAlbums(@Path("artist_id") artistId: Int): AlbumResponse
data class AlbumResponse(val data: List<Album>) {
fun toAlbumList(): List<Album> {
return data.map { albumData ->
Album(
id = albumData.id,
title = albumData.title,
link = albumData.link,
cover = albumData.cover,
cover_small = albumData.cover_small,
cover_medium = albumData.cover_medium,
cover_big = albumData.cover_big,
cover_xl = albumData.cover_xl,
release_date = albumData.release_date,
tracklist = albumData.tracklist,
type = albumData.type
)
}
}
}
@GET("album/{album_id}")
suspend fun getAlbumDetails(@Path("album_id") albumId: Int): AlbumDetailResponse
data class AlbumDetailResponse(
val id: Int,
val title: String,
val cover_medium: String,
val tracks: TracksResponse
)
data class TracksResponse(
val data: List<Song>
)
// Create a new class for storing response fields.
data class GenreResponse(val data: List<Category>)
data class ArtistResponse(val data: List<Artist>)
data class ArtistDetailResponse(val id: Int, val name: String, val picture_big: String, val albums: List<Album>?)
companion object {
private const val BASE_URL = "https://api.deezer.com/"
fun create(): DeezerApiService {
val client = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(DeezerApiService::class.java)
}
},package com.example.musicapp.Data.Repository
import com.example.musicapp.Data.Album
import com.example.musicapp.Data.Artist
import com.example.musicapp.Data.ArtistDetail
import com.example.musicapp.Data.Category
import com.example.musicapp.Data.AlbumDetails
class DeezerRepository {
private val deezerApiService = DeezerApiService.create()
suspend fun getCategories(): List<Category> {
val response = deezerApiService.getGenres()
return response.data.map { category ->
Category(category.id, category.name, category.picture_medium)
}
}
suspend fun getArtists(genreId: Int): List<Artist> {
val response = deezerApiService.getArtists(genreId)
return response.data
}
suspend fun getArtistDetail(artistId: Int): ArtistDetail {
val response = deezerApiService.getArtistDetail(artistId)
return ArtistDetail(
id = response.id,
name = response.name,
pictureBig = response.picture_big,
albums = response.albums
)
}
suspend fun getArtistAlbums(artistId: Int): List<Album> {
val response = deezerApiService.getArtistAlbums(artistId)
return response.toAlbumList()
}
suspend fun getAlbumDetails(albumId: Int): AlbumDetails {
val response = deezerApiService.getAlbumDetails(albumId)
return AlbumDetails(
id = response.id,
title = response.title,
cover_medium = response.cover_medium,
songs = response.tracks.data
)
}
},package com.example.musicapp.Data
data class Album(
val id: Int,
val title: String,
val link: String,
val cover: String,
val cover_small: String,
val cover_medium: String,
val cover_big: String,
val cover_xl: String,
val release_date: String,
val tracklist: String,
val type: String
)
,package com.example.musicapp.Data
data class AlbumDetails(
val id: Int,
val title: String,
val cover_medium: String,
val songs: List<Song>
),package com.example.musicapp.Data
data class Artist(
val id: Int,
val name: String,
val picture_medium: String
)
,package com.example.musicapp.Data
data class ArtistDetail(
val id: Int,
val name: String,
val pictureBig: String,
val albums: List<Album>?,
)
,package com.example.musicapp.Data
data class Category(
val id: Int,
val name: String,
val picture_medium: String
)
,package com.example.musicapp.Data
data class Song(
val id: Int,
val title: String,
val duration: Int, // in seconds
val album: Album,
val cover_medium: String,
val preview: String // 30 seconds preview URL
),package com.example.musicapp.Interface
import TopBar
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.musicapp.Data.Song
import com.example.musicapp.ViewModel.AlbumDetailsViewModel
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import com.google.android.exoplayer2.MediaItem
import androidx.compose.runtime.remember
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import coil.compose.rememberAsyncImagePainter
import com.google.android.exoplayer2.SimpleExoPlayer
import com.example.musicapp.ViewModel.PlayerViewModel
import com.example.musicapp.R
@Composable
fun AlbumDetailScreen(albumId: Int, navController: NavController) {
val albumDetailsViewModel: AlbumDetailsViewModel = viewModel()
val playerViewModel: PlayerViewModel = viewModel()
albumDetailsViewModel.fetchAlbumDetails(albumId)
val albumDetails by albumDetailsViewModel.albumDetails.collectAsState()
albumDetails?.let { details ->
Scaffold(
topBar = {
TopBar(title = details.title)
},
content = { padding ->
Column(
modifier = Modifier.padding(padding)
) {
LazyColumn {
items(details.songs) { song ->
AlbumDetailItem(song, navController, playerViewModel)
}
}
}
}
)
}
}
@Composable
fun AlbumDetailItem(song: Song, navController: NavController, playerViewModel: PlayerViewModel) {
val context = LocalContext.current
Row(
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.background(
color = Color.Black.copy(alpha = 0.5f),
shape = RoundedCornerShape(8.dp)
)
.border(
width = 1.dp,
color = Color.LightGray,
shape = RoundedCornerShape(8.dp)
)
) {
val painter = rememberAsyncImagePainter(model = song.cover_medium)
Image(
painter = painter,
contentDescription = song.title,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(100.dp)
.align(Alignment.CenterVertically)
)
Column(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp)
.align(Alignment.CenterVertically)
) {
Text(
text = song.title ?: "Unknown title",
style = MaterialTheme.typography.subtitle1,
color = Color.White
)
Text(
text = "${song.duration}" ?: "Unknown duration",
style = MaterialTheme.typography.caption,
color = Color.White
)
}
IconButton(
onClick = {
if (playerViewModel.currentSong.value?.id == song.id) {
if (playerViewModel.isPlaying.value) {
playerViewModel.pauseSong()
} else {
playerViewModel.resumeSong()
}
} else {
playerViewModel.playSong(context, song)
}
},
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(8.dp)
) {
Icon(
painter = painterResource(id = if (playerViewModel.currentSong.value?.id == song.id && playerViewModel.isPlaying.value) R.drawable.pause else R.drawable.play_buttton),
contentDescription = if (playerViewModel.currentSong.value?.id == song.id && playerViewModel.isPlaying.value) "Pause" else "Play",
modifier = Modifier.size(24.dp)
)
}
}
},package com.example.musicapp.Interface
import TopBar
import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import coil.compose.rememberAsyncImagePainter
import com.example.musicapp.Data.Album
import com.example.musicapp.ViewModel.ArtistsViewModel
/*
@Composable
fun ArtistDetailScreen(artistId: Int) {
val artistsViewModel: ArtistsViewModel = viewModel()
artistsViewModel.fetchArtistDetails(artistId)
val artistDetails by artistsViewModel.artistDetails.collectAsState()
artistDetails?.let { details ->
val artistDetail = details.firstOrNull()
artistDetail?.let {
Column {
Text(
text = it.name,
style = MaterialTheme.typography.h6,
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp)
)
val painter = rememberAsyncImagePainter(model = it.pictureBig)
Image(
painter = painter,
contentDescription = it.name,
contentScale = ContentScale.FillWidth,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
if (it.albums.isNullOrEmpty()) {
Log.d("ArtistDetailScreen", "No albums found for artist ${it.name}")
}
LazyColumn {
items(it.albums ?: emptyList()) { album ->
ArtistDetailItem(album)
}
}
}
}
}
}
*/
@Composable
fun ArtistDetailScreen(artistId: Int,navController: NavController) {
val artistsViewModel: ArtistsViewModel = viewModel()
artistsViewModel.fetchArtistDetails(artistId)
val artistDetails by artistsViewModel.artistDetails.collectAsState()
artistDetails?.let { details ->
val artistDetail = details.firstOrNull()
artistDetail?.let {
Scaffold(
topBar = {
TopBar(title = it.name)
},
content = { padding ->
Column(modifier = Modifier.padding(padding)) {
val painter = rememberAsyncImagePainter(model = it.pictureBig)
Image(
painter = painter,
contentDescription = it.name,
contentScale = ContentScale.FillWidth,
modifier = Modifier
.fillMaxWidth(0.5f) // Change this line to set the image width to half
.padding(horizontal = 16.dp)
.align(Alignment.CenterHorizontally) // Center the image horizontally
)
if (it.albums.isNullOrEmpty()) {
Log.d("ArtistDetailScreen", "No albums found for artist ${it.name}")
}
LazyColumn {
items(it.albums ?: emptyList()) { album ->
ArtistDetailItem(album, navController) // Pass the navController to ArtistDetailItem
}
}
}
}
)
}
}
}
@Composable
fun ArtistDetailItem(album: Album, navController: NavController, // Add the navController parameter
) {
Row(
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.background(
color = Color.Black.copy(alpha = 0.5f), // Change the alpha value here
shape = RoundedCornerShape(8.dp)
)
.border( // Add this line to draw an outline around the item
width = 1.dp,
color = Color.LightGray,
shape = RoundedCornerShape(8.dp)
)
.clickable(onClick = { navController.navigate("albumDetail/${album.id}") }) // Add this line for navigation
) {
val painter = rememberAsyncImagePainter(model = album.cover_medium)
Image(
painter = painter,
contentDescription = album.title,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(100.dp)
.align(Alignment.CenterVertically)
)
Column(
modifier = Modifier
.weight(1f) // 2/3 of the width
.padding(start = 16.dp)
.align(Alignment.CenterVertically)
) {
Text(text = album.title ?: "Unknown title", style = MaterialTheme.typography.subtitle1, color = Color.White)
Text(text = album.release_date ?: "Unknown release date", style = MaterialTheme.typography.caption, color = Color.White)
}
}
},package com.example.musicapp.Interface
import MusicCategoriesScreen
import TopBar
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.example.musicapp.ui.theme.MusicAppTheme
import com.example.musicapp.R
import com.example.musicapp.ViewModel.CategoriesViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MusicAppTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainScreen()
}
}
}
}
}
@Composable
fun FirstScreen(modifier: Modifier = Modifier) {
val categoriesViewModel: CategoriesViewModel = viewModel()
val categories by categoriesViewModel.categories.collectAsState()
Scaffold(
content = { padding ->
Column(
modifier = modifier.padding(padding)
) {
// Add your UI components here
MusicCategoriesScreen(categories = categories, topBar = { TopBar(title = "Music Categories") })
}
}
)
}
@Composable
fun SecondScreen(modifier: Modifier = Modifier) {
Scaffold(
topBar = {
// No top bar title
},
content = { padding ->
Column(
modifier = modifier.padding(padding)
) {
Text(text = "This is the second screen")
}
}
)
}
/*
@Composable
fun MainScreen() {
val navController = rememberNavController()
NavHost(navController, startDestination = "musicCategories") {
composable("musicCategories") {
val categoriesViewModel: CategoriesViewModel = viewModel()
val categories by categoriesViewModel.categories.collectAsState()
MusicCategoriesScreen(
categories = categories,
onCategorySelected = { category ->
navController.navigate("artistsDetail/${category.id}")
},
topBar = { TopBar(title = "Music Categories") }
)
}
composable("artistsDetail/{categoryId}") { backStackEntry ->
val categoryId = backStackEntry.arguments?.getString("categoryId")?.toIntOrNull()
if (categoryId != null) {
ArtistsScreen(navController, categoryId)
}
}
}
}
*/
@Composable
fun MainScreen() {
val navController = rememberNavController()
NavHost(navController, startDestination = "musicCategories") {
composable("musicCategories") {
val categoriesViewModel: CategoriesViewModel = viewModel()
val categories by categoriesViewModel.categories.collectAsState()
MusicCategoriesScreen(
categories = categories,
onCategorySelected = { category ->
navController.navigate("artistsDetail/${category.id}")
},
topBar = { TopBar(title = "Music Categories") }
)
}
composable("artistsDetail/{categoryId}") { backStackEntry ->
val categoryId = backStackEntry.arguments?.getString("categoryId")?.toIntOrNull()
if (categoryId != null) {
ArtistsScreen(navController, categoryId)
}
}
composable("artistDetail/{artistId}") { backStackEntry ->
val artistId = backStackEntry.arguments?.getString("artistId")?.toIntOrNull()
if (artistId != null) {
ArtistDetailScreen(artistId, navController)
}
}
composable("albumDetail/{albumId}") { backStackEntry ->
val albumId = backStackEntry.arguments?.getString("albumId")?.toIntOrNull()
if (albumId != null) {
AlbumDetailScreen(albumId,navController)
}
}
}
}
@Composable
fun BottomBar(selectedScreen: Int, onItemSelected: (Int) -> Unit) {
BottomAppBar {
BottomNavigationItem(
icon = { Icon(painterResource(R.drawable.music_symbol), contentDescription = null) },
selected = selectedScreen == 0,
onClick = { onItemSelected(0) }
)
BottomNavigationItem(
icon = { Icon(painterResource(R.drawable.heart_empty), contentDescription = null) },
selected = selectedScreen == 1,
onClick = { onItemSelected(1) }
)
}
}
/*
@Composable
fun MainScreen() {
val selectedScreen = remember { mutableStateOf(0) }
Scaffold(
bottomBar = { BottomBar(selectedScreen.value, onItemSelected = { index -> selectedScreen.value = index }) },
content = { padding ->
when (selectedScreen.value) {
0 -> FirstScreen(modifier = Modifier.padding(padding))
1 -> SecondScreen(modifier = Modifier.padding(padding))
}
}
)
}
*/
,package com.example.musicapp.ViewModel
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.musicapp.Data.AlbumDetails
import com.example.musicapp.Data.Repository.DeezerRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class AlbumDetailsViewModel : ViewModel() {
private val deezerRepository = DeezerRepository()
private val _albumDetails = MutableStateFlow<AlbumDetails?>(null)
val albumDetails: StateFlow<AlbumDetails?>
get() = _albumDetails
fun fetchAlbumDetails(albumId: Int) {
viewModelScope.launch {
try {
val albumDetails = deezerRepository.getAlbumDetails(albumId)
_albumDetails.value = albumDetails
} catch (e: Exception) {
Log.e("AlbumDetailsViewModel", "Failed to fetch album details: " + e.message)
}
}
}
},package com.example.musicapp.ViewModel
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.musicapp.Data.Artist
import com.example.musicapp.Data.ArtistDetail
//import com.example.musicapp.Data.Repository.ArtistRepository
import com.example.musicapp.Data.Repository.DeezerRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class ArtistsViewModel : ViewModel() {
private val deezerRepository = DeezerRepository()
private val _artists = MutableStateFlow<List<Artist>>(emptyList())
val artists: StateFlow<List<Artist>>
get() = _artists
private val _artistDetails = MutableStateFlow<List<ArtistDetail>>(emptyList())
val artistDetails: StateFlow<List<ArtistDetail>>
get() = _artistDetails
fun fetchArtists(genreId: Int) {
viewModelScope.launch {
try {
val artists = deezerRepository.getArtists(genreId)
_artists.value = artists
} catch (e: Exception) {
Log.e("MusicViewModel", "Failed to fetch artists: " + e.message)
}
}
}
fun fetchArtistDetails(artistId: Int) {
viewModelScope.launch {
val artistDetailsData = deezerRepository.getArtistDetail(artistId)
// Fetch artist’s albums
val artistAlbums = deezerRepository.getArtistAlbums(artistId)
// Combine artist details and albums
val artistWithAlbums = artistDetailsData.copy(albums = artistAlbums)
_artistDetails.value = listOf(artistWithAlbums)
}
}
}-> these are some of my classes and . In the album detail item composable. I am not able to observe song pictures. Why can you fix it ?
}