Skip to content

Commit

Permalink
Add artist and "more"
Browse files Browse the repository at this point in the history
  • Loading branch information
brahmkshatriya committed Mar 10, 2024
1 parent 9ce3db3 commit 61d3ae9
Show file tree
Hide file tree
Showing 28 changed files with 1,069 additions and 228 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ package dev.brahmkshatriya.echo.data.extensions

import android.content.Context
import android.net.Uri
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.PagingState
import dev.brahmkshatriya.echo.common.clients.AlbumClient
import dev.brahmkshatriya.echo.common.clients.ArtistClient
import dev.brahmkshatriya.echo.common.clients.ExtensionClient
import dev.brahmkshatriya.echo.common.clients.HomeFeedClient
import dev.brahmkshatriya.echo.common.clients.SearchClient
import dev.brahmkshatriya.echo.common.clients.TrackClient
import dev.brahmkshatriya.echo.common.models.Album
import dev.brahmkshatriya.echo.common.models.Artist
import dev.brahmkshatriya.echo.common.models.EchoMediaItem
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItem
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItemsContainer
import dev.brahmkshatriya.echo.common.models.ExtensionMetadata
Expand All @@ -28,10 +27,9 @@ import dev.brahmkshatriya.echo.data.offline.sortedBy
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import java.io.IOException

class OfflineExtension(val context: Context) : ExtensionClient, SearchClient, TrackClient,
HomeFeedClient, AlbumClient {
HomeFeedClient, AlbumClient, ArtistClient {

override val metadata = ExtensionMetadata(
name = "Offline",
Expand Down Expand Up @@ -77,20 +75,24 @@ class OfflineExtension(val context: Context) : ExtensionClient, SearchClient, Tr
emit(PagingData.from(result))
}

inner class OfflinePagingSource(val context: Context, private val genre: StateFlow<String?>) :
PagingSource<Int, MediaItemsContainer>() {
override fun getRefreshKey(state: PagingState<Int, MediaItemsContainer>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
override suspend fun getTrack(uri: Uri): Track? {
return trackResolver.get(uri)
}

override suspend fun getStreamable(track: Track): StreamableAudio {
return trackResolver.getStream(track).toAudio()
}

override suspend fun getHomeGenres(): List<String> {
return listOf(
"All", "Tracks", "Albums", "Artists"
)
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MediaItemsContainer> {
val page = params.key ?: 0
val pageSize = params.loadSize
return try {
val items = when (genre.value) {
override suspend fun getHomeFeed(genre: StateFlow<String?>) =
object : PagedFlow<MediaItemsContainer>() {
override fun loadItems(page: Int, pageSize: Int): List<MediaItemsContainer> =
when (genre.value) {
"Tracks" -> trackResolver.getAll(page, pageSize)
.map { MediaItemsContainer.TrackItem(it) }

Expand All @@ -104,53 +106,42 @@ class OfflineExtension(val context: Context) : ExtensionClient, SearchClient, Tr
val albums =
albumResolver.getShuffled(page, pageSize).map { it.toMediaItem() }
.ifEmpty { null }

val albumsFlow = object : PagedFlow<EchoMediaItem>() {
override fun loadItems(page: Int, pageSize: Int): List<EchoMediaItem> =
albumResolver.getAll(page, pageSize).map { it.toMediaItem() }
}.getFlow()

val tracks =
trackResolver.getShuffled(page, pageSize).map { it.toMediaItem() }
.ifEmpty { null }

val tracksFlow = object : PagedFlow<EchoMediaItem>() {
override fun loadItems(page: Int, pageSize: Int): List<EchoMediaItem> =
trackResolver.getAll(page, pageSize).map { it.toMediaItem() }
}.getFlow()

val artists =
artistResolver.getShuffled(page, pageSize).map { it.toMediaItem() }
.ifEmpty { null }

val artistsFlow = object : PagedFlow<EchoMediaItem>() {
override fun loadItems(page: Int, pageSize: Int): List<EchoMediaItem> =
artistResolver.getAll(page, pageSize).map { it.toMediaItem() }
}.getFlow()

val result = listOfNotNull(
tracks?.toMediaItemsContainer("Tracks"),
albums?.toMediaItemsContainer("Albums"),
artists?.toMediaItemsContainer("Artists")
tracks?.toMediaItemsContainer("Tracks", flow = tracksFlow),
albums?.toMediaItemsContainer("Albums", flow = albumsFlow),
artists?.toMediaItemsContainer("Artists", flow = artistsFlow)
)
result
} else {
trackResolver.getAll(page, pageSize)
.map { MediaItemsContainer.TrackItem(it) }
}
}
val nextKey = if (items.isEmpty()) null
else if (page == 0) 1
else page + 1

LoadResult.Page(
data = items, prevKey = if (page == 0) null else page - 1, nextKey = nextKey
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
}

override suspend fun getTrack(uri: Uri): Track? {
return trackResolver.get(uri)
}

override suspend fun getStreamable(track: Track): StreamableAudio {
return trackResolver.getStream(track).toAudio()
}

override suspend fun getHomeGenres(): List<String> {
return listOf(
"All", "Tracks", "Albums", "Artists"
)
}

override suspend fun getHomeFeed(genre: StateFlow<String?>) =
Pager(config = PagingConfig(pageSize = 10, enablePlaceholders = false),
pagingSourceFactory = { OfflinePagingSource(context, genre) }).flow
}.getFlow()

override suspend fun loadAlbum(small: Album.Small): Album.Full {
return albumResolver.get(small.uri, trackResolver)
Expand All @@ -170,5 +161,31 @@ class OfflineExtension(val context: Context) : ExtensionClient, SearchClient, Tr
emit(PagingData.from(result))
}

override suspend fun loadArtist(small: Artist.Small): Artist.Full {
return artistResolver.get(small.uri)
}

override suspend fun getMediaItems(artist: Artist.Full): Flow<PagingData<MediaItemsContainer>> =
flow {

val albums =
albumResolver.getByArtist(artist, 1, 10).map { it.toMediaItem() }.ifEmpty { null }
val albumFlow = object : PagedFlow<EchoMediaItem>() {
override fun loadItems(page: Int, pageSize: Int): List<EchoMediaItem> =
albumResolver.getByArtist(artist, page, pageSize).map { it.toMediaItem() }
}.getFlow()

val tracks =
trackResolver.getByArtist(artist, 1, 10).map { it.toMediaItem() }.ifEmpty { null }
val trackFlow = object : PagedFlow<EchoMediaItem>() {
override fun loadItems(page: Int, pageSize: Int): List<EchoMediaItem> =
trackResolver.getByArtist(artist, page, pageSize).map { it.toMediaItem() }
}.getFlow()

val result = listOfNotNull(
tracks?.toMediaItemsContainer("Tracks", flow = trackFlow),
albums?.toMediaItemsContainer("Albums", flow = albumFlow)
)
emit(PagingData.from(result))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.brahmkshatriya.echo.data.extensions

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
import androidx.paging.PagingState
import java.io.IOException

abstract class PagedFlow<T : Any>(
val pageSize: Int = 10
) {
abstract fun loadItems(page: Int, pageSize: Int): List<T>

fun getFlow() = Pager(
config = PagingConfig(pageSize = pageSize, enablePlaceholders = false),
pagingSourceFactory = {
object : PagingSource<Int, T>() {
override fun getRefreshKey(state: PagingState<Int, T>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
val page = params.key ?: 0
val pageSize = params.loadSize
return try {
val items = loadItems(page, pageSize)
val nextKey = if (items.isEmpty()) null
else if (page == 0) 1
else page + 1

LoadResult.Page(
data = items,
prevKey = if (page == 0) null else page - 1,
nextKey = nextKey
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
}
}
}
}).flow
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class ArtistResolver(val context: Context) {

private fun Context.queryArtists(
whereCondition: String, selectionArgs: Array<String>, page: Int, pageSize: Int
): List<Artist.WithCover> {
val artists = mutableListOf<Artist.WithCover>()
): List<Artist.Full> {
val artists = mutableListOf<Artist.Full>()
createCursor(
contentResolver = contentResolver,
collection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
Expand All @@ -57,14 +57,22 @@ class ArtistResolver(val context: Context) {
ids.add(id)
val uri = Uri.parse("$URI$ARTIST_AUTH$id")
artists.add(
Artist.WithCover(
Artist.Full(
uri = uri,
name = it.getStringOrNull(artistColumn) ?: "THIS IS THE PROBLEM",
cover = null,
description = null,
)
)
}
}
return artists
}

fun get(uri: Uri) : Artist.Full {
val id = uri.lastPathSegment!!.toLong()
val whereCondition = "${MediaStore.Audio.Media.ARTIST_ID} = ?"
val selectionArgs = arrayOf(id.toString())
return context.queryArtists(whereCondition, selectionArgs, 1, 1).first()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,55 @@ package dev.brahmkshatriya.echo.ui
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.paging.cachedIn
import dev.brahmkshatriya.echo.NavigationDirections
import dev.brahmkshatriya.echo.common.models.EchoMediaItem
import dev.brahmkshatriya.echo.common.models.MediaItemsContainer
import dev.brahmkshatriya.echo.player.PlayerViewModel
import dev.brahmkshatriya.echo.ui.category.CategoryViewModel

class MediaItemClickListener(
val fragment: Fragment
) : ClickListener<Pair<View, EchoMediaItem>> {
override fun onClick(item: Pair<View, EchoMediaItem>) {
) : ClickListener<Pair<View, MediaItemsContainer>> {
override fun onClick(item: Pair<View, MediaItemsContainer>) {
val view = item.first
when (val mediaItem = item.second) {
is EchoMediaItem.AlbumItem -> NavigationDirections.actionAlbum(
albumWithCover = mediaItem.album
).let {
is MediaItemsContainer.AlbumItem -> {
val action = NavigationDirections.actionAlbum(albumWithCover = mediaItem.album)
val extras = FragmentNavigatorExtras(view to view.transitionName)
fragment.findNavController().navigate(it, extras)
fragment.findNavController().navigate(action, extras)
}

// is EchoMediaItem.ArtistItem -> TODO()
// is EchoMediaItem.PlaylistItem -> TODO()
is EchoMediaItem.TrackItem -> {
is MediaItemsContainer.ArtistItem -> {
val action = NavigationDirections.actionArtist(artistWithCover = mediaItem.artist)
val extras = FragmentNavigatorExtras(view to view.transitionName)
fragment.findNavController().navigate(action, extras)
}
is MediaItemsContainer.TrackItem -> {
val playerViewModel: PlayerViewModel by fragment.activityViewModels()
// animatePlayerImage(fragment, view)
playerViewModel.play(mediaItem.track)
}
is MediaItemsContainer.Category -> {
val categoryViewModel : CategoryViewModel by fragment.activityViewModels()
categoryViewModel.title = mediaItem.title
categoryViewModel.flow = mediaItem.flow
mediaItem.flow?.cachedIn(categoryViewModel.viewModelScope)
val action = NavigationDirections.actionCategory()
fragment.findNavController().navigate(action)
}


else -> {}
}

}

override fun onLongClick(item: Pair<View, EchoMediaItem>) {
override fun onLongClick(item: Pair<View, MediaItemsContainer>) {
when (val mediaItem = item.second) {
// is EchoMediaItem.AlbumItem -> TODO()
// is EchoMediaItem.ArtistItem -> TODO()
// is EchoMediaItem.PlaylistItem -> TODO()
is EchoMediaItem.TrackItem -> {
is MediaItemsContainer.TrackItem -> {
val playerViewModel: PlayerViewModel by fragment.activityViewModels()
playerViewModel.addToQueue(mediaItem.track)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import dev.brahmkshatriya.echo.R
import dev.brahmkshatriya.echo.common.models.EchoMediaItem
import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItemsContainer
import dev.brahmkshatriya.echo.common.models.MediaItemsContainer
import dev.brahmkshatriya.echo.common.models.Playlist
import dev.brahmkshatriya.echo.databinding.ItemMediaAlbumBinding
import dev.brahmkshatriya.echo.databinding.ItemMediaArtistBinding
Expand All @@ -22,7 +24,7 @@ import dev.brahmkshatriya.echo.ui.album.albumImage
import dev.brahmkshatriya.echo.utils.loadInto

class MediaItemAdapter(
private val listener: ClickListener<Pair<View, EchoMediaItem>>
private val listener: ClickListener<Pair<View, MediaItemsContainer>>
) : PagingDataAdapter<EchoMediaItem, MediaItemAdapter.MediaItemHolder>(MediaItemComparator) {

override fun getItemViewType(position: Int): Int {
Expand Down Expand Up @@ -68,12 +70,13 @@ class MediaItemAdapter(
val item = getItem(position) ?: return
val container = holder.container

val mediaItem = item.toMediaItemsContainer()
holder.itemView.apply {
setOnClickListener {
listener.onClick(container.transitionView to item)
listener.onClick(container.transitionView to mediaItem)
}
setOnLongClickListener {
listener.onLongClick(container.transitionView to item)
listener.onLongClick(container.transitionView to mediaItem)
true
}
}
Expand Down
Loading

0 comments on commit 61d3ae9

Please sign in to comment.