diff --git a/app/src/main/java/me/vanpetegem/accentor/data/tracks/Track.kt b/app/src/main/java/me/vanpetegem/accentor/data/tracks/Track.kt index 1b554c7b..753d9468 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/tracks/Track.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/tracks/Track.kt @@ -37,6 +37,24 @@ data class Track( const val ALBUMARTIST = "me.vanpetegem.accentor.data.tracks.Track.ALBUMARTIST" const val ARTIST = "me.vanpetegem.accentor.data.tracks.Track.ARTIST" const val YEAR = "me.vanpetegem.accentor.data.tracks.Track.YEAR" + + fun fromDbTrack(t: DbTrack, trackArtists: SparseArray>, trackGenres: SparseArray>) = + Track( + t.id, + t.title, + t.normalizedTitle, + t.number, + t.albumId, + t.reviewComment, + t.createdAt, + t.updatedAt, + trackGenres.get(t.id, ArrayList()), + trackArtists.get(t.id, ArrayList()), + t.codecId, + t.length, + t.bitrate, + t.locationId + ) } } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackDao.kt b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackDao.kt index 1ec2e883..930ecf40 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackDao.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackDao.kt @@ -20,47 +20,13 @@ abstract class TrackDao { val ids = tracks.map { it.id } val trackGenres = getTrackGenresByTrackIdWhereTrackIds(ids) val trackArtists = getTrackArtistsByTrackIdWhereTrackIds(ids) - return tracks.map { t -> - Track( - t.id, - t.title, - t.normalizedTitle, - t.number, - t.albumId, - t.reviewComment, - t.createdAt, - t.updatedAt, - trackGenres.get(t.id, ArrayList()), - trackArtists.get(t.id, ArrayList()), - t.codecId, - t.length, - t.bitrate, - t.locationId - ) - } + return tracks.map { t -> Track.fromDbTrack(t, trackArtists, trackGenres) } } open fun findByIds(ids: List): LiveData> = switchMap(findDbTracksByIds(ids)) { tracks -> switchMap(findTrackArtistsByTrackIdWhereTrackIds(ids)) { trackArtists -> map(findTrackGenresByTrackIdWhereTrackIds(ids)) { trackGenres -> - tracks.map { t -> - Track( - t.id, - t.title, - t.normalizedTitle, - t.number, - t.albumId, - t.reviewComment, - t.createdAt, - t.updatedAt, - trackGenres.get(t.id, ArrayList()), - trackArtists.get(t.id, ArrayList()), - t.codecId, - t.length, - t.bitrate, - t.locationId - ) - } + tracks.map { t -> Track.fromDbTrack(t, trackArtists, trackGenres) } } } } @@ -68,25 +34,23 @@ abstract class TrackDao { open fun findById(id: Int): LiveData = switchMap(findDbTrackById(id)) { dbTrack -> switchMap(findDbTrackArtistsById(id)) { trackArtists -> map(findDbTrackGenresById(id)) { trackGenres -> - if (dbTrack != null) { + dbTrack?.let { Track( - dbTrack.id, - dbTrack.title, - dbTrack.normalizedTitle, - dbTrack.number, - dbTrack.albumId, - dbTrack.reviewComment, - dbTrack.createdAt, - dbTrack.updatedAt, + it.id, + it.title, + it.normalizedTitle, + it.number, + it.albumId, + it.reviewComment, + it.createdAt, + it.updatedAt, trackGenres.map { it.genreId }, trackArtists.map { TrackArtist(it.artistId, it.name, it.normalizedName, it.role, it.order) }, - dbTrack.codecId, - dbTrack.length, - dbTrack.bitrate, - dbTrack.locationId + it.codecId, + it.length, + it.bitrate, + it.locationId ) - } else { - null } } } @@ -96,24 +60,16 @@ abstract class TrackDao { val ids = tracks.map { it.id } switchMap(findTrackArtistsByTrackIdWhereTrackIds(ids)) { trackArtists -> map(findTrackGenresByTrackIdWhereTrackIds(ids)) { trackGenres -> - tracks.map { t -> - Track( - t.id, - t.title, - t.normalizedTitle, - t.number, - t.albumId, - t.reviewComment, - t.createdAt, - t.updatedAt, - trackGenres.get(t.id, ArrayList()), - trackArtists.get(t.id, ArrayList()), - t.codecId, - t.length, - t.bitrate, - t.locationId - ) - } + tracks.map { Track.fromDbTrack(it, trackArtists, trackGenres) } + } + } + } + + open fun findByAlbum(album: Album): LiveData> = switchMap(findDbTracksByAlbumId(album.id)) { tracks -> + val ids = tracks.map { it.id } + switchMap(findTrackArtistsByTrackIdWhereTrackIds(ids)) { trackArtists -> + map(findTrackGenresByTrackIdWhereTrackIds(ids)) { trackGenres -> + tracks.map { Track.fromDbTrack(it, trackArtists, trackGenres) } } } } @@ -155,6 +111,9 @@ abstract class TrackDao { @Query("SELECT * FROM tracks WHERE id IN (:ids)") protected abstract fun findDbTracksByIds(ids: List): LiveData> + @Query("SELECT * FROM tracks WHERE album_id = :id") + protected abstract fun findDbTracksByAlbumId(id: Int): LiveData> + @Query("SELECT * FROM tracks WHERE id IN (SELECT track_id FROM track_artists WHERE artist_id = :id)") protected abstract fun findDbTracksByArtistId(id: Int): LiveData> diff --git a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt index f04cc5d3..647ff2d5 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt @@ -14,6 +14,7 @@ class TrackRepository( fun findById(id: Int): LiveData = trackDao.findById(id) fun findByIds(ids: List): LiveData> = trackDao.findByIds(ids) fun findByArtist(artist: Artist): LiveData> = trackDao.findByArtist(artist) + fun findByAlbum(album: Album): LiveData> = trackDao.findByAlbum(album) fun getByAlbum(album: Album): List = trackDao.getByAlbum(album) suspend fun refresh(handler: suspend (Result) -> Unit) { diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumCard.kt b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumCard.kt index 4fe5e4b8..69626534 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumCard.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumCard.kt @@ -1,6 +1,7 @@ package me.vanpetegem.accentor.ui.albums import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -35,6 +36,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController import coil.compose.rememberImagePainter import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch @@ -43,9 +45,9 @@ import me.vanpetegem.accentor.data.albums.Album import me.vanpetegem.accentor.media.MediaSessionConnection @Composable -public fun AlbumCard(album: Album, mediaSessionConnection: MediaSessionConnection = viewModel()) { +public fun AlbumCard(album: Album, navController: NavController, mediaSessionConnection: MediaSessionConnection = viewModel()) { val scope = rememberCoroutineScope() - Card(modifier = Modifier.padding(8.dp)) { + Card(modifier = Modifier.padding(8.dp).clickable { navController.navigate("albums/${album.id}") }) { Column { Image( painter = if (album.image500 != null) { diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumGrid.kt b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumGrid.kt index 577a75bc..fde5a744 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumGrid.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumGrid.kt @@ -4,12 +4,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController import me.vanpetegem.accentor.ui.util.FastScrollableGrid @Composable -fun AlbumGrid(albumsViewModel: AlbumsViewModel = viewModel()) { +fun AlbumGrid(navController: NavController, albumsViewModel: AlbumsViewModel = viewModel()) { val albums by albumsViewModel.allAlbums.observeAsState() if (albums != null) { - FastScrollableGrid(albums!!, { it.firstCharacter().uppercase() }) { album -> AlbumCard(album) } + FastScrollableGrid(albums!!, { it.firstCharacter().uppercase() }) { album -> AlbumCard(album, navController) } } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumView.kt b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumView.kt new file mode 100644 index 00000000..3434e84a --- /dev/null +++ b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumView.kt @@ -0,0 +1,80 @@ +package me.vanpetegem.accentor.ui.albums + +import androidx.compose.foundation.Image +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.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.ContentAlpha +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.LocalContentColor +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import coil.compose.rememberImagePainter +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch +import me.vanpetegem.accentor.R +import me.vanpetegem.accentor.media.MediaSessionConnection +import me.vanpetegem.accentor.ui.tracks.TrackRow + +@Composable +fun AlbumView(id: Int, albumViewModel: AlbumViewModel = viewModel(), mediaSessionConnection: MediaSessionConnection = viewModel()) { + val scope = rememberCoroutineScope() + val albumState by albumViewModel.getAlbum(id).observeAsState() + if (albumState != null) { + val album = albumState!! + val tracks by albumViewModel.tracksForAlbum(album).observeAsState() + LazyColumn(modifier = Modifier.fillMaxSize()) { + item { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 8.dp)) { + Image( + painter = if (album.image500 != null) { + rememberImagePainter(album.image500) { + placeholder(R.drawable.ic_album) + } + } else { + painterResource(R.drawable.ic_album) + }, + contentDescription = stringResource(R.string.artist_image), + modifier = Modifier.width(128.dp).aspectRatio(1f), + ) + Column { + Text(album.title, style = MaterialTheme.typography.h4, modifier = Modifier.padding(start = 8.dp)) + Text( + album.stringifyAlbumArtists(), + style = MaterialTheme.typography.subtitle1, + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), + modifier = Modifier.padding(start = 8.dp), + ) + Row(modifier = Modifier.padding(8.dp)) { + IconButton(onClick = { scope.launch(IO) { mediaSessionConnection.play(album) } }) { + Icon(painterResource(R.drawable.ic_play), contentDescription = stringResource(R.string.play_now)) + } + IconButton(onClick = { scope.launch(IO) { mediaSessionConnection.addTracksToQueue(album) } }) { + Icon(painterResource(R.drawable.ic_queue_add), contentDescription = stringResource(R.string.play_last)) + } + } + } + } + } + if (tracks != null && tracks!!.size > 0) { + items(tracks!!.size) { i -> TrackRow(tracks!![i]) } + } + } + } +} diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumViewModel.kt b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumViewModel.kt new file mode 100644 index 00000000..7d059d32 --- /dev/null +++ b/app/src/main/java/me/vanpetegem/accentor/ui/albums/AlbumViewModel.kt @@ -0,0 +1,30 @@ +package me.vanpetegem.accentor.ui.albums + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations.map +import me.vanpetegem.accentor.data.AccentorDatabase +import me.vanpetegem.accentor.data.albums.Album +import me.vanpetegem.accentor.data.albums.AlbumRepository +import me.vanpetegem.accentor.data.authentication.AuthenticationDataSource +import me.vanpetegem.accentor.data.authentication.AuthenticationRepository +import me.vanpetegem.accentor.data.tracks.Track +import me.vanpetegem.accentor.data.tracks.TrackRepository + +class AlbumViewModel(application: Application) : AndroidViewModel(application) { + private val database = AccentorDatabase.getDatabase(application) + private val authenticationRepository = AuthenticationRepository(AuthenticationDataSource(application)) + private val albumRepository = AlbumRepository(database.albumDao(), authenticationRepository) + private val trackRepository = TrackRepository(database.trackDao(), authenticationRepository) + + fun getAlbum(id: Int): LiveData = map(albumRepository.allAlbumsById) { albums -> + albums[id] + } + + fun tracksForAlbum(album: Album): LiveData> = map(trackRepository.findByAlbum(album)) { tracks -> + val copy = tracks.toMutableList() + copy.sortWith({ t1, t2 -> t1.number.compareTo(t2.number) }) + copy + } +} diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistView.kt b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistView.kt index f3c3bb53..02bd6d33 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistView.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistView.kt @@ -1,55 +1,35 @@ package me.vanpetegem.accentor.ui.artists import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box -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.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.material.ContentAlpha -import androidx.compose.material.Divider -import androidx.compose.material.DropdownMenu -import androidx.compose.material.DropdownMenuItem -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.MoreVert import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController import coil.compose.rememberImagePainter import coil.transform.CircleCropTransformation -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.launch import me.vanpetegem.accentor.R -import me.vanpetegem.accentor.data.tracks.Track -import me.vanpetegem.accentor.media.MediaSessionConnection import me.vanpetegem.accentor.ui.albums.AlbumCard +import me.vanpetegem.accentor.ui.tracks.TrackRow @Composable -fun ArtistView(id: Int, artistViewModel: ArtistViewModel = viewModel()) { +fun ArtistView(id: Int, navController: NavController, artistViewModel: ArtistViewModel = viewModel()) { val artistState by artistViewModel.getArtist(id).observeAsState() if (artistState != null) { val artist = artistState!! @@ -79,7 +59,7 @@ fun ArtistView(id: Int, artistViewModel: ArtistViewModel = viewModel()) { LazyRow { items(albums!!.size) { i -> Box(modifier = Modifier.width(192.dp)) { - AlbumCard(albums!![i]) + AlbumCard(albums!![i], navController) } } } @@ -94,54 +74,3 @@ fun ArtistView(id: Int, artistViewModel: ArtistViewModel = viewModel()) { } } } - -@Composable -fun TrackRow(track: Track, mediaSessionConnection: MediaSessionConnection = viewModel()) { - val scope = rememberCoroutineScope() - Row( - modifier = Modifier.fillMaxWidth().padding(8.dp).clickable { - scope.launch(IO) { mediaSessionConnection.play(track) } - } - ) { - Column(modifier = Modifier.weight(1f)) { - Text( - track.title, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.subtitle1, - ) - Text( - track.stringifyTrackArtists(), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.subtitle2, - color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), - ) - } - var expanded by remember { mutableStateOf(false) } - Box(modifier = Modifier.height(40.dp).aspectRatio(1f).wrapContentSize(Alignment.TopStart)) { - IconButton(onClick = { expanded = true }) { - Icon(Icons.Default.MoreVert, contentDescription = stringResource(R.string.open_menu)) - } - DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { - DropdownMenuItem( - onClick = { - expanded = false - scope.launch(IO) { mediaSessionConnection.addTrackToQueue(track, maxOf(0, mediaSessionConnection.queuePosition.value ?: 0)) } - } - ) { - Text(stringResource(R.string.play_next)) - } - DropdownMenuItem( - onClick = { - expanded = false - scope.launch(IO) { mediaSessionConnection.addTrackToQueue(track) } - } - ) { - Text(stringResource(R.string.play_last)) - } - } - } - } - Divider() -} diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeFragment.kt b/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeFragment.kt index cdad2174..81616458 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeFragment.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/home/HomeFragment.kt @@ -15,12 +15,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController import me.vanpetegem.accentor.R import me.vanpetegem.accentor.ui.albums.AlbumCard import me.vanpetegem.accentor.ui.util.Timer @Composable -fun Home(homeViewModel: HomeViewModel = viewModel()) { +fun Home(navController: NavController, homeViewModel: HomeViewModel = viewModel()) { LazyColumn(modifier = Modifier.fillMaxSize()) { item { val albums by homeViewModel.recentlyReleasedAlbums.observeAsState() @@ -29,7 +30,7 @@ fun Home(homeViewModel: HomeViewModel = viewModel()) { LazyRow { items(albums!!.size) { i -> Box(modifier = Modifier.width(192.dp)) { - AlbumCard(albums!![i]) + AlbumCard(albums!![i], navController) } } } @@ -42,7 +43,7 @@ fun Home(homeViewModel: HomeViewModel = viewModel()) { LazyRow { items(albums!!.size) { i -> Box(modifier = Modifier.width(192.dp)) { - AlbumCard(albums!![i]) + AlbumCard(albums!![i], navController) } } } @@ -59,7 +60,7 @@ fun Home(homeViewModel: HomeViewModel = viewModel()) { LazyRow { items(albums!!.size) { i -> Box(modifier = Modifier.width(192.dp)) { - AlbumCard(albums!![i]) + AlbumCard(albums!![i], navController) } } } @@ -72,7 +73,7 @@ fun Home(homeViewModel: HomeViewModel = viewModel()) { LazyRow { items(albums!!.size) { i -> Box(modifier = Modifier.width(192.dp)) { - AlbumCard(albums!![i]) + AlbumCard(albums!![i], navController) } } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt b/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt index d6d7fa6e..3bc46410 100644 --- a/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt +++ b/app/src/main/java/me/vanpetegem/accentor/ui/main/MainActivity.kt @@ -57,6 +57,7 @@ import kotlinx.coroutines.launch import me.vanpetegem.accentor.R import me.vanpetegem.accentor.ui.AccentorTheme import me.vanpetegem.accentor.ui.albums.AlbumGrid +import me.vanpetegem.accentor.ui.albums.AlbumView import me.vanpetegem.accentor.ui.artists.ArtistGrid import me.vanpetegem.accentor.ui.artists.ArtistView import me.vanpetegem.accentor.ui.home.Home @@ -168,12 +169,15 @@ fun Content(mainViewModel: MainViewModel = viewModel(), playerViewModel: PlayerV indicator = { state, trigger -> SwipeRefreshIndicator(state, trigger, contentColor = MaterialTheme.colors.secondary) } ) { NavHost(navController = navController, startDestination = "home") { - composable("home") { Home() } + composable("home") { Home(navController) } composable("artists") { ArtistGrid(navController) } composable("artists/{artistId}", arguments = listOf(navArgument("artistId") { type = NavType.IntType })) { entry -> - ArtistView(entry.arguments!!.getInt("artistId")) + ArtistView(entry.arguments!!.getInt("artistId"), navController) + } + composable("albums") { AlbumGrid(navController) } + composable("albums/{albumId}", arguments = listOf(navArgument("albumId") { type = NavType.IntType })) { entry -> + AlbumView(entry.arguments!!.getInt("albumId")) } - composable("albums") { AlbumGrid() } } } } diff --git a/app/src/main/java/me/vanpetegem/accentor/ui/tracks/TrackRow.kt b/app/src/main/java/me/vanpetegem/accentor/ui/tracks/TrackRow.kt new file mode 100644 index 00000000..9ae9b4ee --- /dev/null +++ b/app/src/main/java/me/vanpetegem/accentor/ui/tracks/TrackRow.kt @@ -0,0 +1,90 @@ +package me.vanpetegem.accentor.ui.tracks + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.ContentAlpha +import androidx.compose.material.Divider +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.LocalContentColor +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch +import me.vanpetegem.accentor.R +import me.vanpetegem.accentor.data.tracks.Track +import me.vanpetegem.accentor.media.MediaSessionConnection + +@Composable +fun TrackRow(track: Track, mediaSessionConnection: MediaSessionConnection = viewModel()) { + val scope = rememberCoroutineScope() + Row( + modifier = Modifier.fillMaxWidth().padding(8.dp).clickable { + scope.launch(IO) { mediaSessionConnection.play(track) } + } + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + track.title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.subtitle1, + ) + Text( + track.stringifyTrackArtists(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.subtitle2, + color = LocalContentColor.current.copy(alpha = ContentAlpha.medium), + ) + } + var expanded by remember { mutableStateOf(false) } + Box(modifier = Modifier.height(40.dp).aspectRatio(1f).wrapContentSize(Alignment.TopStart)) { + IconButton(onClick = { expanded = true }) { + Icon(Icons.Default.MoreVert, contentDescription = stringResource(R.string.open_menu)) + } + DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { + DropdownMenuItem( + onClick = { + expanded = false + scope.launch(IO) { mediaSessionConnection.addTrackToQueue(track, maxOf(0, mediaSessionConnection.queuePosition.value ?: 0)) } + } + ) { + Text(stringResource(R.string.play_next)) + } + DropdownMenuItem( + onClick = { + expanded = false + scope.launch(IO) { mediaSessionConnection.addTrackToQueue(track) } + } + ) { + Text(stringResource(R.string.play_last)) + } + } + } + } + Divider() +} diff --git a/app/src/main/res/drawable/ic_menu_preferences.xml b/app/src/main/res/drawable/ic_menu_preferences.xml index ad90e824..0b63fcfe 100644 --- a/app/src/main/res/drawable/ic_menu_preferences.xml +++ b/app/src/main/res/drawable/ic_menu_preferences.xml @@ -1,8 +1,8 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_queue_add.xml b/app/src/main/res/drawable/ic_queue_add.xml new file mode 100644 index 00000000..da066b60 --- /dev/null +++ b/app/src/main/res/drawable/ic_queue_add.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file