From 61d3ae94ea880d4eadb57a7c416a41de2965ab10 Mon Sep 17 00:00:00 2001 From: brahmkshatriya <69040506+brahmkshatriya@users.noreply.github.com> Date: Sun, 10 Mar 2024 23:07:40 +0530 Subject: [PATCH] Add artist and "more" --- .../echo/data/extensions/OfflineExtension.kt | 121 ++++++++------- .../echo/data/extensions/PagedFlow.kt | 45 ++++++ .../echo/data/offline/ArtistResolver.kt | 14 +- .../echo/ui/MediaItemClickListener.kt | 41 +++-- .../echo/ui/adapters/MediaItemAdapter.kt | 9 +- .../ui/adapters/MediaItemsContainerAdapter.kt | 41 ++--- .../echo/ui/adapters/TrackAdapter.kt | 4 +- .../echo/ui/album/AlbumFragment.kt | 6 +- .../echo/ui/album/AlbumHeaderAdapter.kt | 2 - .../echo/ui/artist/ArtistFragment.kt | 133 ++++++++++++++++ .../echo/ui/artist/ArtistHeaderAdapter.kt | 111 +++++++++++++ .../echo/ui/artist/ArtistViewModel.kt | 42 +++++ .../echo/ui/category/CategoryFragment.kt | 68 ++++++++ .../echo/ui/category/CategoryViewModel.kt | 11 ++ app/src/main/res/color/button_radio.xml | 5 + app/src/main/res/drawable/ic_artist.xml | 9 ++ ..._collapsing_bar.xml => fragment_album.xml} | 0 app/src/main/res/layout/fragment_artist.xml | 100 ++++++++++++ app/src/main/res/layout/fragment_category.xml | 67 ++++++++ app/src/main/res/layout/item_album_info.xml | 6 +- app/src/main/res/layout/item_artist_info.xml | 77 +++++++++ app/src/main/res/layout/item_category.xml | 64 +++++--- .../res/layout/skeleton_item_album_info.xml | 146 +++++++++--------- .../res/layout/skeleton_item_artist_info.xml | 83 ++++++++++ app/src/main/res/navigation/navigation.xml | 52 +++++-- app/src/main/res/values/strings.xml | 7 + .../echo/common/models/EchoMediaItem.kt | 25 +-- .../echo/common/models/MediaItemsContainer.kt | 8 +- 28 files changed, 1069 insertions(+), 228 deletions(-) create mode 100644 app/src/main/java/dev/brahmkshatriya/echo/data/extensions/PagedFlow.kt create mode 100644 app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistFragment.kt create mode 100644 app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistHeaderAdapter.kt create mode 100644 app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistViewModel.kt create mode 100644 app/src/main/java/dev/brahmkshatriya/echo/ui/category/CategoryFragment.kt create mode 100644 app/src/main/java/dev/brahmkshatriya/echo/ui/category/CategoryViewModel.kt create mode 100644 app/src/main/res/color/button_radio.xml create mode 100644 app/src/main/res/drawable/ic_artist.xml rename app/src/main/res/layout/{fragment_collapsing_bar.xml => fragment_album.xml} (100%) create mode 100644 app/src/main/res/layout/fragment_artist.xml create mode 100644 app/src/main/res/layout/fragment_category.xml create mode 100644 app/src/main/res/layout/item_artist_info.xml create mode 100644 app/src/main/res/layout/skeleton_item_artist_info.xml diff --git a/app/src/main/java/dev/brahmkshatriya/echo/data/extensions/OfflineExtension.kt b/app/src/main/java/dev/brahmkshatriya/echo/data/extensions/OfflineExtension.kt index 74111ea1..a0f4607a 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/data/extensions/OfflineExtension.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/data/extensions/OfflineExtension.kt @@ -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 @@ -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", @@ -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) : - PagingSource() { - override fun getRefreshKey(state: PagingState): 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 { + return listOf( + "All", "Tracks", "Albums", "Artists" + ) + } - override suspend fun load(params: LoadParams): LoadResult { - val page = params.key ?: 0 - val pageSize = params.loadSize - return try { - val items = when (genre.value) { + override suspend fun getHomeFeed(genre: StateFlow) = + object : PagedFlow() { + override fun loadItems(page: Int, pageSize: Int): List = + when (genre.value) { "Tracks" -> trackResolver.getAll(page, pageSize) .map { MediaItemsContainer.TrackItem(it) } @@ -104,16 +106,34 @@ class OfflineExtension(val context: Context) : ExtensionClient, SearchClient, Tr val albums = albumResolver.getShuffled(page, pageSize).map { it.toMediaItem() } .ifEmpty { null } + + val albumsFlow = object : PagedFlow() { + override fun loadItems(page: Int, pageSize: Int): List = + albumResolver.getAll(page, pageSize).map { it.toMediaItem() } + }.getFlow() + val tracks = trackResolver.getShuffled(page, pageSize).map { it.toMediaItem() } .ifEmpty { null } + + val tracksFlow = object : PagedFlow() { + override fun loadItems(page: Int, pageSize: Int): List = + trackResolver.getAll(page, pageSize).map { it.toMediaItem() } + }.getFlow() + val artists = artistResolver.getShuffled(page, pageSize).map { it.toMediaItem() } .ifEmpty { null } + + val artistsFlow = object : PagedFlow() { + override fun loadItems(page: Int, pageSize: Int): List = + 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 { @@ -121,36 +141,7 @@ class OfflineExtension(val context: Context) : ExtensionClient, SearchClient, Tr .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 { - return listOf( - "All", "Tracks", "Albums", "Artists" - ) - } - - override suspend fun getHomeFeed(genre: StateFlow) = - 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) @@ -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> = + flow { + + val albums = + albumResolver.getByArtist(artist, 1, 10).map { it.toMediaItem() }.ifEmpty { null } + val albumFlow = object : PagedFlow() { + override fun loadItems(page: Int, pageSize: Int): List = + 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() { + override fun loadItems(page: Int, pageSize: Int): List = + 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)) + } } \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/data/extensions/PagedFlow.kt b/app/src/main/java/dev/brahmkshatriya/echo/data/extensions/PagedFlow.kt new file mode 100644 index 00000000..0d0588e0 --- /dev/null +++ b/app/src/main/java/dev/brahmkshatriya/echo/data/extensions/PagedFlow.kt @@ -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( + val pageSize: Int = 10 +) { + abstract fun loadItems(page: Int, pageSize: Int): List + + fun getFlow() = Pager( + config = PagingConfig(pageSize = pageSize, enablePlaceholders = false), + pagingSourceFactory = { + object : PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) + } + } + + override suspend fun load(params: LoadParams): LoadResult { + 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 +} \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/data/offline/ArtistResolver.kt b/app/src/main/java/dev/brahmkshatriya/echo/data/offline/ArtistResolver.kt index 4261aaf7..489ecaf3 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/data/offline/ArtistResolver.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/data/offline/ArtistResolver.kt @@ -30,8 +30,8 @@ class ArtistResolver(val context: Context) { private fun Context.queryArtists( whereCondition: String, selectionArgs: Array, page: Int, pageSize: Int - ): List { - val artists = mutableListOf() + ): List { + val artists = mutableListOf() createCursor( contentResolver = contentResolver, collection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, @@ -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() + } } \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/MediaItemClickListener.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/MediaItemClickListener.kt index 1745f0c2..69c5e6e4 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/MediaItemClickListener.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/MediaItemClickListener.kt @@ -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> { - override fun onClick(item: Pair) { +) : ClickListener> { + override fun onClick(item: Pair) { 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) { + override fun onLongClick(item: Pair) { 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) } diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/MediaItemAdapter.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/MediaItemAdapter.kt index 57455196..edd2faff 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/MediaItemAdapter.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/MediaItemAdapter.kt @@ -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 @@ -22,7 +24,7 @@ import dev.brahmkshatriya.echo.ui.album.albumImage import dev.brahmkshatriya.echo.utils.loadInto class MediaItemAdapter( - private val listener: ClickListener> + private val listener: ClickListener> ) : PagingDataAdapter(MediaItemComparator) { override fun getItemViewType(position: Int): Int { @@ -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 } } diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/MediaItemsContainerAdapter.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/MediaItemsContainerAdapter.kt index 87d0dcf3..e4607b9a 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/MediaItemsContainerAdapter.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/MediaItemsContainerAdapter.kt @@ -18,8 +18,6 @@ import androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL import androidx.recyclerview.widget.ListUpdateCallback 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.toMediaItem import dev.brahmkshatriya.echo.common.models.MediaItemsContainer import dev.brahmkshatriya.echo.databinding.ItemAlbumBinding import dev.brahmkshatriya.echo.databinding.ItemArtistBinding @@ -35,7 +33,7 @@ import java.lang.ref.WeakReference class MediaItemsContainerAdapter( fragment: Fragment, - private val listener: ClickListener> = MediaItemClickListener(fragment), + private val listener: ClickListener> = MediaItemClickListener(fragment), ) : PagingDataAdapter( MediaItemsContainerComparator ) { @@ -96,22 +94,13 @@ class MediaItemsContainerAdapter( val item = getItem(position) ?: return holder.bind() - val echoMediaItem = when (item) { - is MediaItemsContainer.TrackItem -> holder.transitionView to item.track.toMediaItem() - is MediaItemsContainer.AlbumItem -> holder.transitionView to item.album.toMediaItem() - is MediaItemsContainer.ArtistItem -> holder.transitionView to item.artist.toMediaItem() - is MediaItemsContainer.PlaylistItem -> holder.transitionView to item.playlist.toMediaItem() - else -> null - } - echoMediaItem?.let { - holder.itemView.apply { - setOnClickListener { - listener.onClick(echoMediaItem) - } - setOnLongClickListener { - listener.onLongClick(echoMediaItem) - true - } + holder.clickView.apply { + setOnClickListener { + listener.onClick(this to item) + } + setOnLongClickListener { + listener.onLongClick(this to item) + true } } } @@ -168,7 +157,7 @@ class MediaItemsContainerAdapter( abstract inner class MediaItemsContainerHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { abstract fun bind() - abstract val transitionView: View + abstract val clickView: View } inner class Category(val binding: ItemCategoryBinding) : @@ -189,10 +178,10 @@ class MediaItemsContainerAdapter( else it.scrollToPosition(0) } stateViewModel.visibleScrollableViews[position] = WeakReference(this) + binding.more.isVisible = category.flow != null } - override val transitionView - get() = throw IllegalArgumentException() + override val clickView = binding.more } inner class Track(val binding: ItemTrackBinding) : MediaItemsContainerHolder(binding.root) { @@ -224,7 +213,7 @@ class MediaItemsContainerAdapter( binding.root.transitionName = track.uri.toString() } - override val transitionView = binding.root + override val clickView = binding.root } inner class Album(val binding: ItemAlbumBinding) : MediaItemsContainerHolder(binding.root) { @@ -245,7 +234,7 @@ class MediaItemsContainerAdapter( binding.root.transitionName = album.uri.toString() } - override val transitionView = binding.root + override val clickView = binding.root } inner class Artist(val binding: ItemArtistBinding) : MediaItemsContainerHolder(binding.root) { @@ -259,7 +248,7 @@ class MediaItemsContainerAdapter( binding.root.transitionName = artist.uri.toString() } - override val transitionView = binding.root + override val clickView = binding.root } inner class Playlist(val binding: ItemPlaylistBinding) : @@ -279,7 +268,7 @@ class MediaItemsContainerAdapter( binding.root.transitionName = playlist.uri.toString() } - override val transitionView = binding.root + override val clickView = binding.root } companion object MediaItemsContainerComparator : DiffUtil.ItemCallback() { diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/TrackAdapter.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/TrackAdapter.kt index 142582e3..0bff3591 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/TrackAdapter.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/adapters/TrackAdapter.kt @@ -6,7 +6,7 @@ import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import dev.brahmkshatriya.echo.R -import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItem +import dev.brahmkshatriya.echo.common.models.MediaItemsContainer import dev.brahmkshatriya.echo.common.models.Track import dev.brahmkshatriya.echo.databinding.ItemTrackSmallBinding import dev.brahmkshatriya.echo.player.PlayerHelper.Companion.toTimeString @@ -25,7 +25,7 @@ class TrackAdapter( init { binding.root.setOnClickListener { val track = list?.get(bindingAdapterPosition) ?: return@setOnClickListener - callback.onClick(binding.imageView to track.toMediaItem()) + callback.onClick(binding.imageView to MediaItemsContainer.TrackItem(track)) } } } diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/album/AlbumFragment.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/album/AlbumFragment.kt index 7561c38d..5888eeb2 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/album/AlbumFragment.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/album/AlbumFragment.kt @@ -21,7 +21,7 @@ import com.google.android.material.transition.platform.MaterialFade import dev.brahmkshatriya.echo.R import dev.brahmkshatriya.echo.common.clients.AlbumClient import dev.brahmkshatriya.echo.common.models.Album -import dev.brahmkshatriya.echo.databinding.FragmentCollapsingBarBinding +import dev.brahmkshatriya.echo.databinding.FragmentAlbumBinding import dev.brahmkshatriya.echo.player.PlayerViewModel import dev.brahmkshatriya.echo.player.ui.PlayerBackButtonHelper import dev.brahmkshatriya.echo.ui.MediaItemClickListener @@ -39,7 +39,7 @@ class AlbumFragment : Fragment() { private val args: AlbumFragmentArgs by navArgs() - private var binding: FragmentCollapsingBarBinding by autoCleared() + private var binding: FragmentAlbumBinding by autoCleared() private val viewModel: AlbumViewModel by viewModels() private val extensionViewModel: ExtensionViewModel by activityViewModels() @@ -65,7 +65,7 @@ class AlbumFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = FragmentCollapsingBarBinding.inflate(inflater, container, false) + binding = FragmentAlbumBinding.inflate(inflater, container, false) enterTransition = MaterialFade() exitTransition = MaterialFade() return binding.root diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/album/AlbumHeaderAdapter.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/album/AlbumHeaderAdapter.kt index 53a06cc5..94542f7a 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/album/AlbumHeaderAdapter.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/album/AlbumHeaderAdapter.kt @@ -1,6 +1,5 @@ package dev.brahmkshatriya.echo.ui.album -import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -100,7 +99,6 @@ class AlbumHeaderAdapter(private val listener: AlbumHeaderListener) : binding.albumInfo.text = info } - @SuppressLint("NotifyDataSetChanged") fun submit(album: Album.Full) { println("submitted") _album = album diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistFragment.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistFragment.kt new file mode 100644 index 00000000..329902ad --- /dev/null +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistFragment.kt @@ -0,0 +1,133 @@ +package dev.brahmkshatriya.echo.ui.artist + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.doOnPreDraw +import androidx.core.view.updatePadding +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import androidx.navigation.fragment.navArgs +import androidx.navigation.ui.setupWithNavController +import androidx.recyclerview.widget.ConcatAdapter +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.transition.platform.MaterialContainerTransform +import com.google.android.material.transition.platform.MaterialFade +import dev.brahmkshatriya.echo.R +import dev.brahmkshatriya.echo.common.clients.ArtistClient +import dev.brahmkshatriya.echo.common.clients.RadioClient +import dev.brahmkshatriya.echo.common.clients.UserClient +import dev.brahmkshatriya.echo.common.models.Artist +import dev.brahmkshatriya.echo.databinding.FragmentArtistBinding +import dev.brahmkshatriya.echo.player.PlayerViewModel +import dev.brahmkshatriya.echo.player.ui.PlayerBackButtonHelper +import dev.brahmkshatriya.echo.ui.MediaItemClickListener +import dev.brahmkshatriya.echo.ui.adapters.MediaItemsContainerAdapter +import dev.brahmkshatriya.echo.ui.extension.ExtensionViewModel +import dev.brahmkshatriya.echo.ui.extension.getAdapterForExtension +import dev.brahmkshatriya.echo.ui.snackbar.SnackBarViewModel +import dev.brahmkshatriya.echo.utils.autoCleared +import dev.brahmkshatriya.echo.utils.loadInto +import dev.brahmkshatriya.echo.utils.observe +import dev.brahmkshatriya.echo.utils.updatePaddingWithPlayerAndSystemInsets +import kotlinx.coroutines.flow.combine + +class ArtistFragment : Fragment() { + + private val args: ArtistFragmentArgs by navArgs() + + private var binding: FragmentArtistBinding by autoCleared() + + private val viewModel: ArtistViewModel by viewModels() + private val extensionViewModel: ExtensionViewModel by activityViewModels() + private val snackBarViewModel: SnackBarViewModel by activityViewModels() + private val playerViewModel: PlayerViewModel by activityViewModels() + + private val clickListener = MediaItemClickListener(this) + private val mediaItemsContainerAdapter = MediaItemsContainerAdapter(this, clickListener) + private val header = ArtistHeaderAdapter(object : ArtistHeaderAdapter.ArtistHeaderListener { + + override fun onSubscribeClicked( + artist: Artist.Full, + subscribe: Boolean, + adapter: ArtistHeaderAdapter + ) { + adapter.submitSubscribe(subscribe) + } + + override fun onRadioClicked(artist: Artist.Full) { + } + + }) + private val concatAdapter = ConcatAdapter(header, mediaItemsContainerAdapter) + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentArtistBinding.inflate(inflater, container, false) + enterTransition = MaterialFade() + exitTransition = MaterialFade() + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + PlayerBackButtonHelper.addCallback(this) { + binding.recyclerView.updatePaddingWithPlayerAndSystemInsets(it, false) + } + + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + binding.albumCoverContainer.updatePadding(top = insets.top) + windowInsets + } + binding.appBarLayout.addOnOffsetChangedListener { appbar, verticalOffset -> + val offset = (-verticalOffset) / appbar.totalScrollRange.toFloat() + val inverted = 1 - offset + binding.endIcon.alpha = inverted + binding.toolbarOutline.alpha = offset + } + binding.toolbar.setupWithNavController(findNavController()) + postponeEnterTransition() + binding.recyclerView.doOnPreDraw { + startPostponedEnterTransition() + } + + binding.recyclerView.layoutManager = LinearLayoutManager(context) + + val artist: Artist.Small = args.artistWithCover ?: args.artistSmall ?: return + + binding.root.transitionName = artist.uri.toString() + sharedElementEnterTransition = MaterialContainerTransform(requireContext(), true).apply { + drawingViewId = R.id.nav_host_fragment + } + binding.toolbar.title = artist.name.trim() + + (artist as? Artist.WithCover).let { + it?.cover.loadInto(binding.albumCover, R.drawable.art_artist) + } + + observe(extensionViewModel.extensionFlow) { + binding.recyclerView.adapter = getAdapterForExtension( + it, R.string.artist, concatAdapter, true + ) { client -> + if (client == null) return@getAdapterForExtension + viewModel.loadArtist(client, snackBarViewModel.mutableExceptionFlow, artist) + } + } + val headerFlow = viewModel.artistFlow + .combine(extensionViewModel.extensionFlow) { it, client -> it to client } + observe(headerFlow) { (artist, client) -> + if (artist != null && client != null) { + header.submit(artist, client is UserClient, client is RadioClient) + } + } + observe(viewModel.result) { + if (it != null) mediaItemsContainerAdapter.submit(it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistHeaderAdapter.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistHeaderAdapter.kt new file mode 100644 index 00000000..d1ed3641 --- /dev/null +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistHeaderAdapter.kt @@ -0,0 +1,111 @@ +package dev.brahmkshatriya.echo.ui.artist + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import dev.brahmkshatriya.echo.R +import dev.brahmkshatriya.echo.common.models.Artist +import dev.brahmkshatriya.echo.databinding.ItemArtistInfoBinding +import dev.brahmkshatriya.echo.databinding.SkeletonItemArtistInfoBinding + +class ArtistHeaderAdapter(private val listener: ArtistHeaderListener) : + RecyclerView.Adapter() { + + override fun getItemCount() = 1 + + sealed class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + class Info( + val binding: ItemArtistInfoBinding, + val artist: Artist.Full, + listener: ArtistHeaderListener, + adapter: ArtistHeaderAdapter, + ) : ViewHolder(binding.root) { + init { + binding.artistSubscribe.setOnClickListener { + artist.let { it1 -> listener.onSubscribeClicked(it1, true, adapter) } + } + binding.artistUnsubscribe.setOnClickListener { + artist.let { it1 -> listener.onSubscribeClicked(it1, false, adapter) } + } + binding.artistRadio.setOnClickListener { + artist.let { it1 -> listener.onRadioClicked(it1) } + } + } + } + + class ShimmerViewHolder(binding: SkeletonItemArtistInfoBinding) : ViewHolder(binding.root) + } + + interface ArtistHeaderListener { + fun onSubscribeClicked(artist: Artist.Full, subscribe: Boolean, adapter: ArtistHeaderAdapter) + fun onRadioClicked(artist: Artist.Full) + } + + override fun getItemViewType(position: Int) = if (_artist == null) 0 else 1 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return if (viewType == 0) { + ViewHolder.ShimmerViewHolder( + SkeletonItemArtistInfoBinding.inflate( + LayoutInflater.from( + parent.context + ), parent, false + ) + ) + } else { + val artist = _artist!! + ViewHolder.Info( + ItemArtistInfoBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ), artist, listener, this + ) + } + } + + private var _artist: Artist.Full? = null + private var isSubscribed = false + private var _subscribe = false + private var _radio = false + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + if (holder !is ViewHolder.Info) return + val binding = holder.binding + val artist = holder.artist + + binding.artistSubtitle.isVisible = artist.followers?.let { + binding.artistSubtitle.text = binding.artistSubtitle.resources.getQuantityString( + R.plurals.number_followers, it, it + ) + true + } ?: false + + binding.artistDescriptionContainer.isVisible = artist.description?.let { + if (it.isNotBlank()) { + binding.artistDescription.text = it + true + } else false + } ?: false + + if(_subscribe) { + binding.artistSubscribe.isVisible = !isSubscribed + binding.artistUnsubscribe.isVisible = isSubscribed + } else { + binding.artistSubscribe.isVisible = false + binding.artistUnsubscribe.isVisible = false + } + binding.artistRadio.isVisible = _radio + } + + fun submit(artist: Artist.Full, subscribe: Boolean, radio: Boolean) { + _artist = artist + _subscribe = subscribe + _radio = radio + notifyItemChanged(0) + } + + fun submitSubscribe(subscribe: Boolean) { + isSubscribed = subscribe + notifyItemChanged(0) + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistViewModel.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistViewModel.kt new file mode 100644 index 00000000..8da8383b --- /dev/null +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/artist/ArtistViewModel.kt @@ -0,0 +1,42 @@ +package dev.brahmkshatriya.echo.ui.artist + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import dev.brahmkshatriya.echo.common.clients.ArtistClient +import dev.brahmkshatriya.echo.common.models.Artist +import dev.brahmkshatriya.echo.common.models.MediaItemsContainer +import dev.brahmkshatriya.echo.utils.tryWith +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class ArtistViewModel : ViewModel() { + + private var initialized = false + + private val _result: MutableStateFlow?> = MutableStateFlow(null) + val result = _result.asStateFlow() + private val mutableArtistFlow: MutableStateFlow = MutableStateFlow(null) + val artistFlow = mutableArtistFlow.asStateFlow() + + fun loadArtist( + artistClient: ArtistClient, exceptionFlow: MutableSharedFlow, artist: Artist.Small + ) { + if (initialized) return + initialized = true + viewModelScope.launch(Dispatchers.IO) { + tryWith(exceptionFlow) { + artistClient.loadArtist(artist).let { + mutableArtistFlow.value = it + artistClient.getMediaItems(it).collectLatest { data -> + _result.value = data + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/category/CategoryFragment.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/category/CategoryFragment.kt new file mode 100644 index 00000000..d019d30f --- /dev/null +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/category/CategoryFragment.kt @@ -0,0 +1,68 @@ +package dev.brahmkshatriya.echo.ui.category + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.navigation.fragment.findNavController +import androidx.navigation.ui.setupWithNavController +import androidx.paging.map +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.transition.platform.MaterialFade +import dev.brahmkshatriya.echo.common.models.EchoMediaItem.Companion.toMediaItemsContainer +import dev.brahmkshatriya.echo.databinding.FragmentCategoryBinding +import dev.brahmkshatriya.echo.player.ui.PlayerBackButtonHelper +import dev.brahmkshatriya.echo.ui.adapters.MediaItemsContainerAdapter +import dev.brahmkshatriya.echo.utils.autoCleared +import dev.brahmkshatriya.echo.utils.observe +import dev.brahmkshatriya.echo.utils.updatePaddingWithPlayerAndSystemInsets + +class CategoryFragment : Fragment() { + private val viewModel: CategoryViewModel by activityViewModels() + private var binding: FragmentCategoryBinding by autoCleared() + + private val adapter = MediaItemsContainerAdapter(this) + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentCategoryBinding.inflate(inflater, container, false) + enterTransition = MaterialFade() + exitTransition = MaterialFade() + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + PlayerBackButtonHelper.addCallback(this) { + binding.recyclerView.updatePaddingWithPlayerAndSystemInsets(it, false) + } + + binding.appBarLayout.addOnOffsetChangedListener { appbar, verticalOffset -> + val offset = (-verticalOffset) / appbar.totalScrollRange.toFloat() + binding.toolbarOutline.alpha = offset + } + + binding.title.setupWithNavController(findNavController()) + + val flow = viewModel.flow ?: return + + binding.title.title = viewModel.title + binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) + binding.recyclerView.adapter = adapter + observe(flow) { data -> + adapter.submit( + data.map { it.toMediaItemsContainer() } + ) + } + } + + override fun onDestroy() { + super.onDestroy() + viewModel.flow = null + viewModel.title = null + } + +} \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/category/CategoryViewModel.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/category/CategoryViewModel.kt new file mode 100644 index 00000000..df49b905 --- /dev/null +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/category/CategoryViewModel.kt @@ -0,0 +1,11 @@ +package dev.brahmkshatriya.echo.ui.category + +import androidx.lifecycle.ViewModel +import androidx.paging.PagingData +import dev.brahmkshatriya.echo.common.models.EchoMediaItem +import kotlinx.coroutines.flow.Flow + +class CategoryViewModel : ViewModel() { + var title: String? = null + var flow: Flow>? = null +} \ No newline at end of file diff --git a/app/src/main/res/color/button_radio.xml b/app/src/main/res/color/button_radio.xml new file mode 100644 index 00000000..28894b69 --- /dev/null +++ b/app/src/main/res/color/button_radio.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_artist.xml b/app/src/main/res/drawable/ic_artist.xml new file mode 100644 index 00000000..febbcfd6 --- /dev/null +++ b/app/src/main/res/drawable/ic_artist.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_collapsing_bar.xml b/app/src/main/res/layout/fragment_album.xml similarity index 100% rename from app/src/main/res/layout/fragment_collapsing_bar.xml rename to app/src/main/res/layout/fragment_album.xml diff --git a/app/src/main/res/layout/fragment_artist.xml b/app/src/main/res/layout/fragment_artist.xml new file mode 100644 index 00000000..f670390a --- /dev/null +++ b/app/src/main/res/layout/fragment_artist.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_category.xml b/app/src/main/res/layout/fragment_category.xml new file mode 100644 index 00000000..df612ffe --- /dev/null +++ b/app/src/main/res/layout/fragment_category.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_album_info.xml b/app/src/main/res/layout/item_album_info.xml index 8cbc8b9f..a6fdd916 100644 --- a/app/src/main/res/layout/item_album_info.xml +++ b/app/src/main/res/layout/item_album_info.xml @@ -84,9 +84,9 @@ android:layout_height="56dp" android:layout_weight="1" android:text="@string/radio" - android:textColor="?attr/colorTertiary" + android:textColor="@color/button_radio" app:icon="@drawable/ic_radio" - app:iconTint="?attr/colorTertiary" /> + app:iconTint="@color/button_radio" /> diff --git a/app/src/main/res/layout/item_artist_info.xml b/app/src/main/res/layout/item_artist_info.xml new file mode 100644 index 00000000..67c77546 --- /dev/null +++ b/app/src/main/res/layout/item_artist_info.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml index bbf605f8..b8bf9e40 100644 --- a/app/src/main/res/layout/item_category.xml +++ b/app/src/main/res/layout/item_category.xml @@ -12,32 +12,54 @@ android:paddingBottom="4dp"> + android:gravity="center" + android:orientation="horizontal"> - - - + + + + + + + + + android:contentDescription="@string/clear_playlist" + android:padding="8dp" + app:icon="@drawable/ic_more_horiz" + app:iconSize="24dp" + app:iconTint="@color/button_player" /> + diff --git a/app/src/main/res/layout/skeleton_item_album_info.xml b/app/src/main/res/layout/skeleton_item_album_info.xml index 8c093b0c..bc3cafd0 100644 --- a/app/src/main/res/layout/skeleton_item_album_info.xml +++ b/app/src/main/res/layout/skeleton_item_album_info.xml @@ -11,104 +11,102 @@ android:layout_marginBottom="16dp" android:orientation="vertical"> + - - - - + - - + + + + + + + + + + android:layout_marginHorizontal="24dp" + android:layout_marginBottom="24dp" + android:orientation="horizontal"> - - + android:layout_weight="1" + android:background="@drawable/rounded_rectangle_more" /> - - - + - + + - + android:layout_marginHorizontal="24dp" + android:layout_marginBottom="16dp" + android:background="@drawable/rounded_rectangle" /> - - - + - + android:layout_height="match_parent" + android:alpha="0.66"> + + - + android:layout_height="match_parent" + android:alpha="0.33"> + + diff --git a/app/src/main/res/layout/skeleton_item_artist_info.xml b/app/src/main/res/layout/skeleton_item_artist_info.xml new file mode 100644 index 00000000..6687fc7f --- /dev/null +++ b/app/src/main/res/layout/skeleton_item_artist_info.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/navigation.xml b/app/src/main/res/navigation/navigation.xml index 1a514adb..fee77596 100644 --- a/app/src/main/res/navigation/navigation.xml +++ b/app/src/main/res/navigation/navigation.xml @@ -1,5 +1,4 @@ - - + + + + + + + + + + tools:layout="@layout/fragment_album"> + app:nullable="true" /> + app:nullable="true" /> + + + tools:layout="@layout/dialog_extension" /> - + tools:layout="@layout/fragment_exception"> @@ -59,4 +80,11 @@ android:id="@+id/action_exception" app:destination="@id/navigation_exception" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 341bed26..56aeea6f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -34,9 +34,16 @@ %1$d Song %1$d Songs + + %1$d Follower + %1$d Followers + Songs Radio View Copy + Artist + Unfollow + Follow \ No newline at end of file diff --git a/common/src/main/java/dev/brahmkshatriya/echo/common/models/EchoMediaItem.kt b/common/src/main/java/dev/brahmkshatriya/echo/common/models/EchoMediaItem.kt index ab5f15ea..4bf95ff4 100644 --- a/common/src/main/java/dev/brahmkshatriya/echo/common/models/EchoMediaItem.kt +++ b/common/src/main/java/dev/brahmkshatriya/echo/common/models/EchoMediaItem.kt @@ -1,5 +1,8 @@ package dev.brahmkshatriya.echo.common.models +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow + sealed class EchoMediaItem { data class TrackItem(val track: Track) : EchoMediaItem() data class AlbumItem(val album: Album.WithCover) : EchoMediaItem() @@ -12,21 +15,21 @@ sealed class EchoMediaItem { fun Artist.WithCover.toMediaItem() = ArtistItem(this) fun Playlist.WithCover.toMediaItem() = PlaylistItem(this) - fun EchoMediaItem.toMediaItemsContainer() = - when(this) { - is TrackItem -> MediaItemsContainer.TrackItem(this.track) - is AlbumItem -> MediaItemsContainer.AlbumItem(this.album) - is ArtistItem -> MediaItemsContainer.ArtistItem(this.artist) - is PlaylistItem -> MediaItemsContainer.PlaylistItem(this.playlist) - } + fun EchoMediaItem.toMediaItemsContainer() = when (this) { + is TrackItem -> MediaItemsContainer.TrackItem(this.track) + is AlbumItem -> MediaItemsContainer.AlbumItem(this.album) + is ArtistItem -> MediaItemsContainer.ArtistItem(this.artist) + is PlaylistItem -> MediaItemsContainer.PlaylistItem(this.playlist) + } - fun List.toMediaItemsContainer(title: String, subtitle: String? = null) - = MediaItemsContainer.Category(title, this, subtitle) + fun List.toMediaItemsContainer( + title: String, subtitle: String? = null, flow: Flow>? = null + ) = MediaItemsContainer.Category(title, this, subtitle, flow) } override fun equals(other: Any?): Boolean { - if(other is EchoMediaItem) { - return when(this) { + if (other is EchoMediaItem) { + return when (this) { is TrackItem -> this.track.uri == (other as? TrackItem)?.track?.uri is AlbumItem -> this.album.uri == (other as? AlbumItem)?.album?.uri is ArtistItem -> this.artist.uri == (other as? ArtistItem)?.artist?.uri diff --git a/common/src/main/java/dev/brahmkshatriya/echo/common/models/MediaItemsContainer.kt b/common/src/main/java/dev/brahmkshatriya/echo/common/models/MediaItemsContainer.kt index 0c226d38..2ea75476 100644 --- a/common/src/main/java/dev/brahmkshatriya/echo/common/models/MediaItemsContainer.kt +++ b/common/src/main/java/dev/brahmkshatriya/echo/common/models/MediaItemsContainer.kt @@ -1,8 +1,14 @@ package dev.brahmkshatriya.echo.common.models +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow + sealed class MediaItemsContainer { data class Category( - val title: String, val list: List, val subtitle: String? = null + val title: String, + val list: List, + val subtitle: String? = null, + val flow: Flow>? = null ) : MediaItemsContainer() data class TrackItem(val track: Track) : MediaItemsContainer()