Skip to content

Commit

Permalink
Add artist view (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
chvp authored Jul 20, 2021
1 parent fd3a0e0 commit f689d94
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 55 deletions.
16 changes: 16 additions & 0 deletions app/src/main/java/me/vanpetegem/accentor/data/albums/Album.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ data class Album(
albumArtists.sortedBy { aa -> aa.order }.fold("") { acc, aa -> acc + aa.name + (aa.separator ?: "") }

fun firstCharacter() = String(intArrayOf(title.codePointAt(0)), 0, 1)

fun compareToByName(other: Album): Int {
var order = this.normalizedTitle.compareTo(other.normalizedTitle)
order = if (order == 0) this.release.compareTo(other.release) else order
order = if (order == 0) compareAlbumEditions(this, other) else order
order = if (order == 0) this.id - other.id else order
return order
}
}

fun compareAlbumEditions(a1: Album, a2: Album): Int {
if (a1.edition == null && a2.edition == null) { return 0 }
if (a1.edition == null) { return -1 }
if (a2.edition == null) { return 1 }
val order = a1.edition.compareTo(a2.edition)
return if (order == 0) a1.editionDescription!!.compareTo(a2.editionDescription!!) else order
}

data class AlbumArtist(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class AlbumRepository(
}

fun findById(id: Int): LiveData<Album?> = albumDao.findById(id)
fun getById(id: Int): Album? = albumDao.getAlbumById(id)

fun findByIds(ids: List<Int>): LiveData<List<Album>> = albumDao.findByIds(ids)

Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/me/vanpetegem/accentor/data/tracks/Track.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package me.vanpetegem.accentor.data.tracks

import android.util.SparseArray
import java.time.Instant
import me.vanpetegem.accentor.data.albums.Album

data class Track(
val id: Int,
Expand All @@ -20,6 +22,17 @@ data class Track(
) {
fun stringifyTrackArtists() = trackArtists.sortedBy { ta -> ta.order }.joinToString(" / ") { ta -> ta.name }

fun compareTo(other: Track, albums: SparseArray<Album>): Int {
val a1 = albums[this.albumId]
val a2 = albums[other.albumId]
if (a1 == null && a2 == null) { return this.number - other.number }
if (a1 == null) { return 1 }
if (a2 == null) { return -1 }
val order = a1.compareToByName(a2)
if (order != 0) { return order }
return this.number - other.number
}

companion object {
const val ALBUMARTIST = "me.vanpetegem.accentor.data.tracks.Track.ALBUMARTIST"
const val ARTIST = "me.vanpetegem.accentor.data.tracks.Track.ARTIST"
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import me.vanpetegem.accentor.data.albums.Album
import me.vanpetegem.accentor.data.artists.Artist

@Dao
abstract class TrackDao {
Expand Down Expand Up @@ -91,6 +92,32 @@ abstract class TrackDao {
}
}

open fun findByArtist(artist: Artist): LiveData<List<Track>> = switchMap(findDbTracksByArtistId(artist.id)) { tracks ->
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
)
}
}
}
}

@Transaction
open fun getTrackById(id: Int): Track? {
val dbTrack = getDbTrackById(id)
Expand Down Expand Up @@ -128,6 +155,9 @@ abstract class TrackDao {
@Query("SELECT * FROM tracks WHERE id IN (:ids)")
protected abstract fun findDbTracksByIds(ids: List<Int>): LiveData<List<DbTrack>>

@Query("SELECT * FROM tracks WHERE id IN (SELECT track_id FROM track_artists WHERE artist_id = :id)")
protected abstract fun findDbTracksByArtistId(id: Int): LiveData<List<DbTrack>>

@Query("SELECT * FROM track_artists WHERE track_id = :id")
protected abstract fun getDbTrackArtistsById(id: Int): List<DbTrackArtist>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,18 @@ package me.vanpetegem.accentor.data.tracks
import androidx.lifecycle.LiveData
import me.vanpetegem.accentor.api.track.index
import me.vanpetegem.accentor.data.albums.Album
import me.vanpetegem.accentor.data.artists.Artist
import me.vanpetegem.accentor.data.authentication.AuthenticationRepository
import me.vanpetegem.accentor.util.Result

class TrackRepository(
private val trackDao: TrackDao,
private val authenticationRepository: AuthenticationRepository
) {
fun findById(id: Int): LiveData<Track?> {
return trackDao.findById(id)
}

fun findByIds(ids: List<Int>): LiveData<List<Track>> {
return trackDao.findByIds(ids)
}

fun getByAlbum(album: Album): List<Track> {
return trackDao.getByAlbum(album)
}
fun findById(id: Int): LiveData<Track?> = trackDao.findById(id)
fun findByIds(ids: List<Int>): LiveData<List<Track>> = trackDao.findByIds(ids)
fun findByArtist(artist: Artist): LiveData<List<Track>> = trackDao.findByArtist(artist)
fun getByAlbum(album: Album): List<Track> = trackDao.getByAlbum(album)

suspend fun refresh(handler: suspend (Result<Unit>) -> Unit) {
when (val result = index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,18 @@ class MediaSessionConnection(application: Application) : AndroidViewModel(applic
play(tracks)
}

suspend fun play(track: Track) {
val album = albumRepository.getById(track.albumId)
album?.let { play(listOf(Pair(track, it))) }
}

suspend fun addTrackToQueue(track: Track) = addTrackToQueue(track, _queue.value?.size ?: 0)
suspend fun addTracksToQueue(album: Album) = addTracksToQueue(album, _queue.value?.size ?: 0)

suspend fun addTrackToQueue(track: Track, index: Int) {
val album = albumRepository.getById(track.albumId)
album?.let { addTracksToQueue(listOf(Pair(track, album)), index) }
}
suspend fun addTracksToQueue(album: Album, index: Int) {
val tracks = trackRepository.getByAlbum(album).map { Pair(it, album) }
addTracksToQueue(tracks, index)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package me.vanpetegem.accentor.ui.albums

import android.app.Application
import android.os.Parcelable
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import me.vanpetegem.accentor.data.AccentorDatabase
import me.vanpetegem.accentor.data.albums.Album
import me.vanpetegem.accentor.data.albums.AlbumRepository
Expand All @@ -13,22 +11,10 @@ import me.vanpetegem.accentor.data.authentication.AuthenticationRepository
import me.vanpetegem.accentor.data.tracks.TrackRepository

class AlbumsViewModel(application: Application) : AndroidViewModel(application) {
private val database = AccentorDatabase.getDatabase(application)
private val authenticationRepository = AuthenticationRepository(AuthenticationDataSource(application))
private val albumRepository: AlbumRepository
private val trackRepository: TrackRepository
private val albumRepository = AlbumRepository(database.albumDao(), authenticationRepository)
private val trackRepository = TrackRepository(database.trackDao(), authenticationRepository)

val allAlbums: LiveData<List<Album>>
private val _scrollState = MutableLiveData<Parcelable>()
val scrollState: LiveData<Parcelable> = _scrollState

init {
val database = AccentorDatabase.getDatabase(application)
albumRepository = AlbumRepository(database.albumDao(), authenticationRepository)
trackRepository = TrackRepository(database.trackDao(), authenticationRepository)
allAlbums = albumRepository.allAlbums
}

fun saveScrollState(state: Parcelable) {
_scrollState.value = state
}
val allAlbums: LiveData<List<Album>> = albumRepository.allAlbums
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.vanpetegem.accentor.ui.artists

import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -14,14 +15,15 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import coil.compose.rememberImagePainter
import me.vanpetegem.accentor.R
import me.vanpetegem.accentor.data.artists.Artist

@Composable
public fun ArtistCard(artist: Artist) {
public fun ArtistCard(navController: NavController, artist: Artist) {
Card(
modifier = Modifier.padding(8.dp),
modifier = Modifier.padding(8.dp).clickable { navController.navigate("artists/${artist.id}") },
) {
Column {
Image(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ArtistGrid(artistsViewModel: ArtistsViewModel = viewModel()) {
fun ArtistGrid(navController: NavController, artistsViewModel: ArtistsViewModel = viewModel()) {
val artists by artistsViewModel.allArtists.observeAsState()
if (artists != null) {
FastScrollableGrid(artists!!, { it.firstCharacter().uppercase() }) { artist -> ArtistCard(artist) }
FastScrollableGrid(artists!!, { it.firstCharacter().uppercase() }) { artist -> ArtistCard(navController, artist) }
}
}
147 changes: 147 additions & 0 deletions app/src/main/java/me/vanpetegem/accentor/ui/artists/ArtistView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
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 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

@Composable
fun ArtistView(id: Int, artistViewModel: ArtistViewModel = viewModel()) {
val artistState by artistViewModel.getArtist(id).observeAsState()
if (artistState != null) {
val artist = artistState!!
val albums by artistViewModel.albumsForArtist(artist).observeAsState()
val tracks by artistViewModel.tracksForArtist(artist).observeAsState()
LazyColumn(modifier = Modifier.fillMaxSize()) {
item {
Row(modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
Image(
painter = if (artist.image500 != null) {
rememberImagePainter(artist.image500) {
placeholder(R.drawable.ic_artist)
transformations(CircleCropTransformation())
}
} else {
painterResource(R.drawable.ic_artist)
},
contentDescription = stringResource(R.string.artist_image),
modifier = Modifier.width(80.dp).aspectRatio(1f),
)
Text(artist.name, style = MaterialTheme.typography.h4, modifier = Modifier.padding(start = 8.dp))
}
}
item {
if (albums != null && albums!!.size > 0) {
Text(stringResource(R.string.albums), style = MaterialTheme.typography.h5, modifier = Modifier.padding(8.dp))
LazyRow {
items(albums!!.size) { i ->
Box(modifier = Modifier.width(192.dp)) {
AlbumCard(albums!![i])
}
}
}
}
}
if (tracks != null && tracks!!.size > 0) {
item {
Text(stringResource(R.string.tracks), style = MaterialTheme.typography.h5, modifier = Modifier.padding(8.dp))
}
items(tracks!!.size) { i -> TrackRow(tracks!![i]) }
}
}
}
}

@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()
}
Loading

0 comments on commit f689d94

Please sign in to comment.