diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 56b271310..c80f1cda6 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -23,20 +23,11 @@ import android.view.LayoutInflater import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearSmoothScroller -import com.google.android.material.appbar.AppBarLayout -import com.google.android.material.transition.MaterialSharedAxis import dagger.hilt.android.AndroidEntryPoint -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.min import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetail2Binding import org.oxycblt.auxio.detail.list.AlbumDetailListAdapter -import org.oxycblt.auxio.detail.list.DetailListAdapter -import org.oxycblt.auxio.list.Divider -import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListViewModel @@ -54,12 +45,9 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately -import org.oxycblt.auxio.util.getDimenPixels import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.navigateSafe -import org.oxycblt.auxio.util.overrideOnOverflowMenuClick -import org.oxycblt.auxio.util.setFullWidthLookup import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull @@ -69,10 +57,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class AlbumDetailFragment : - ListFragment(), - DetailListAdapter.Listener, - AppBarLayout.OnOffsetChangedListener { +class AlbumDetailFragment : DetailFragment() { private val detailModel: DetailViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels() @@ -83,56 +68,17 @@ class AlbumDetailFragment : private val args: AlbumDetailFragmentArgs by navArgs() private val albumListAdapter = AlbumDetailListAdapter(this) - private var spacingSmall = 0 - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // Detail transitions are always on the X axis. Shared element transitions are more - // semantically correct, but are also too buggy to be sensible. - enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) - returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) - exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) - reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) - } - override fun onCreateBinding(inflater: LayoutInflater) = FragmentDetail2Binding.inflate(inflater) + override fun getDetailListAdapter() = albumListAdapter + override fun getSelectionToolbar(binding: FragmentDetail2Binding) = binding.detailSelectionToolbar override fun onBindingCreated(binding: FragmentDetail2Binding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) - // --- UI SETUP -- - binding.detailAppbar.addOnOffsetChangedListener(this) - - binding.detailNormalToolbar.apply { - setNavigationOnClickListener { findNavController().navigateUp() } - overrideOnOverflowMenuClick { - listModel.openMenu( - R.menu.detail_album, unlikelyToBeNull(detailModel.currentAlbum.value)) - } - } - - binding.detailRecycler.apply { - adapter = albumListAdapter - - (layoutManager as GridLayoutManager).setFullWidthLookup { - if (it != 0) { - val item = - detailModel.genreSongList.value.getOrElse(it - 1) { - return@setFullWidthLookup false - } - item is Divider || item is Header - } else { - true - } - } - } - - spacingSmall = requireContext().getDimenPixels(R.dimen.spacing_small) - // -- VIEWMODEL SETUP --- // DetailViewModel handles most initialization from the navigation argument. detailModel.setAlbum(args.albumUid) @@ -150,33 +96,19 @@ class AlbumDetailFragment : override fun onDestroyBinding(binding: FragmentDetail2Binding) { super.onDestroyBinding(binding) - binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. detailModel.albumSongInstructions.consume() } - override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) { - val binding = requireBinding() - val range = appBarLayout.totalScrollRange - val ratio = abs(verticalOffset.toFloat()) / range.toFloat() - - val outRatio = min(ratio * 2, 1f) - val detailHeader = binding.detailHeader - detailHeader.scaleX = 1 - 0.05f * outRatio - detailHeader.scaleY = 1 - 0.05f * outRatio - detailHeader.alpha = 1 - outRatio - - val inRatio = max(ratio - 0.5f, 0f) * 2 - val detailContent = binding.detailToolbarContent - detailContent.alpha = inRatio - detailContent.translationY = spacingSmall * (1 - inRatio) - } - override fun onRealClick(item: Song) { playbackModel.play(item, detailModel.playInAlbumWith) } + override fun onOpenParentMenu() { + listModel.openMenu(R.menu.album, unlikelyToBeNull(detailModel.currentAlbum.value)) + } + override fun onOpenMenu(item: Song) { listModel.openMenu(R.menu.album_song, item, detailModel.playInAlbumWith) } @@ -193,12 +125,14 @@ class AlbumDetailFragment : } val binding = requireBinding() + val context = requireContext() + val name = album.name.resolve(context) - binding.detailToolbarTitle.text = album.name.resolve(requireContext()) + binding.detailToolbarTitle.text = name binding.detailCover.bind(album) // The type text depends on the release type (Album, EP, Single, etc.) binding.detailType.text = getString(album.releaseType.stringRes) - binding.detailName.text = album.name.resolve(requireContext()) + binding.detailName.text = name // Artist name maps to the subhead text binding.detailSubhead.apply { text = album.artists.resolveNames(context) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 5fdd4e7f7..af65b9a27 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -20,21 +20,15 @@ package org.oxycblt.auxio.detail import android.os.Bundle import android.view.LayoutInflater +import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import androidx.recyclerview.widget.ConcatAdapter -import androidx.recyclerview.widget.GridLayoutManager import com.google.android.material.transition.MaterialSharedAxis import dagger.hilt.android.AndroidEntryPoint import org.oxycblt.auxio.R -import org.oxycblt.auxio.databinding.FragmentDetailBinding -import org.oxycblt.auxio.detail.header.ArtistDetailHeaderAdapter -import org.oxycblt.auxio.detail.header.DetailHeaderAdapter +import org.oxycblt.auxio.databinding.FragmentDetail2Binding import org.oxycblt.auxio.detail.list.ArtistDetailListAdapter -import org.oxycblt.auxio.detail.list.DetailListAdapter -import org.oxycblt.auxio.list.Divider -import org.oxycblt.auxio.list.Header import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListViewModel @@ -47,14 +41,15 @@ import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.PlaylistDecision import org.oxycblt.auxio.music.PlaylistMessage import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.resolveNames import org.oxycblt.auxio.playback.PlaybackDecision import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.context +import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.navigateSafe -import org.oxycblt.auxio.util.overrideOnOverflowMenuClick -import org.oxycblt.auxio.util.setFullWidthLookup import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.unlikelyToBeNull @@ -64,18 +59,15 @@ import org.oxycblt.auxio.util.unlikelyToBeNull * @author Alexander Capehart (OxygenCobalt) */ @AndroidEntryPoint -class ArtistDetailFragment : - ListFragment(), - DetailHeaderAdapter.Listener, - DetailListAdapter.Listener { +class ArtistDetailFragment : DetailFragment() { private val detailModel: DetailViewModel by activityViewModels() override val listModel: ListViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels() + // Information about what artist to display is initially within the navigation arguments // as a UID, as that is the only safe way to parcel an artist. private val args: ArtistDetailFragmentArgs by navArgs() - private val artistHeaderAdapter = ArtistDetailHeaderAdapter(this) private val artistListAdapter = ArtistDetailListAdapter(this) override fun onCreate(savedInstanceState: Bundle?) { @@ -88,39 +80,17 @@ class ArtistDetailFragment : reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) } - override fun onCreateBinding(inflater: LayoutInflater) = FragmentDetailBinding.inflate(inflater) + override fun onCreateBinding(inflater: LayoutInflater) = + FragmentDetail2Binding.inflate(inflater) - override fun getSelectionToolbar(binding: FragmentDetailBinding) = + override fun getSelectionToolbar(binding: FragmentDetail2Binding) = binding.detailSelectionToolbar - override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) { + override fun getDetailListAdapter() = artistListAdapter + + override fun onBindingCreated(binding: FragmentDetail2Binding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) - // --- UI SETUP --- - binding.detailNormalToolbar.apply { - setNavigationOnClickListener { findNavController().navigateUp() } - setOnMenuItemClickListener(this@ArtistDetailFragment) - overrideOnOverflowMenuClick { - listModel.openMenu( - R.menu.detail_parent, unlikelyToBeNull(detailModel.currentArtist.value)) - } - } - - binding.detailRecycler.apply { - adapter = ConcatAdapter(artistHeaderAdapter, artistListAdapter) - (layoutManager as GridLayoutManager).setFullWidthLookup { - if (it != 0) { - val item = - detailModel.artistSongList.value.getOrElse(it - 1) { - return@setFullWidthLookup false - } - item is Divider || item is Header - } else { - true - } - } - } - // --- VIEWMODEL SETUP --- // DetailViewModel handles most initialization from the navigation argument. detailModel.setArtist(args.artistUid) @@ -136,10 +106,8 @@ class ArtistDetailFragment : collect(playbackModel.playbackDecision.flow, ::handlePlaybackDecision) } - override fun onDestroyBinding(binding: FragmentDetailBinding) { + override fun onDestroyBinding(binding: FragmentDetail2Binding) { super.onDestroyBinding(binding) - binding.detailNormalToolbar.setOnMenuItemClickListener(null) - binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. detailModel.artistSongInstructions.consume() @@ -153,6 +121,10 @@ class ArtistDetailFragment : } } + override fun onOpenParentMenu() { + listModel.openMenu(R.menu.detail_parent, unlikelyToBeNull(detailModel.currentArtist.value)) + } + override fun onOpenMenu(item: Music) { when (item) { is Song -> listModel.openMenu(R.menu.artist_song, item, detailModel.playInArtistWith) @@ -161,14 +133,6 @@ class ArtistDetailFragment : } } - override fun onPlay() { - playbackModel.play(unlikelyToBeNull(detailModel.currentArtist.value)) - } - - override fun onShuffle() { - playbackModel.shuffle(unlikelyToBeNull(detailModel.currentArtist.value)) - } - override fun onOpenSortMenu() { findNavController().navigateSafe(ArtistDetailFragmentDirections.sort()) } @@ -179,8 +143,57 @@ class ArtistDetailFragment : findNavController().navigateUp() return } - requireBinding().detailNormalToolbar.title = artist.name.resolve(requireContext()) - artistHeaderAdapter.setParent(artist) + val binding = requireBinding() + val context = requireContext() + val name = artist.name.resolve(context) + binding.detailToolbarTitle.text = name + + binding.detailCover.bind(artist) + binding.detailType.text = context.getString(R.string.lbl_artist) + binding.detailName.text = name + + // Song and album counts map to the info + binding.detailInfo.text = + context.getString( + R.string.fmt_two, + if (artist.explicitAlbums.isNotEmpty()) { + context.getPlural(R.plurals.fmt_album_count, artist.explicitAlbums.size) + } else { + context.getString(R.string.def_album_count) + }, + if (artist.songs.isNotEmpty()) { + context.getPlural(R.plurals.fmt_song_count, artist.songs.size) + } else { + context.getString(R.string.def_song_count) + }) + + if (artist.songs.isNotEmpty()) { + // Information about the artist's genre(s) map to the sub-head text + binding.detailSubhead.apply { + isVisible = true + text = artist.genres.resolveNames(context) + } + + // In the case that this header used to he configured to have no songs, + // we want to reset the visibility of all information that was hidden. + binding.detailPlayButton.isVisible = true + binding.detailShuffleButton.isVisible = true + } else { + // The artist does not have any songs, so hide functionality that makes no sense. + // ex. Play and Shuffle, Song Counts, and Genre Information. + // Artists are always guaranteed to have albums however, so continue to show those. + logD("Artist is empty, disabling genres and playback") + binding.detailSubhead.isVisible = false + binding.detailPlayButton.isEnabled = false + binding.detailShuffleButton.isEnabled = false + } + + binding.detailPlayButton.setOnClickListener { + playbackModel.play(unlikelyToBeNull(detailModel.currentArtist.value)) + } + binding.detailShuffleButton.setOnClickListener { + playbackModel.shuffle(unlikelyToBeNull(detailModel.currentArtist.value)) + } } private fun updateList(list: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt new file mode 100644 index 000000000..334142172 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024 Auxio Project + * DetailFragment.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.detail + +import android.os.Bundle +import android.view.LayoutInflater +import androidx.fragment.app.activityViewModels +import androidx.navigation.findNavController +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.transition.MaterialSharedAxis +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import org.oxycblt.auxio.R +import org.oxycblt.auxio.databinding.FragmentDetail2Binding +import org.oxycblt.auxio.detail.list.DetailListAdapter +import org.oxycblt.auxio.list.Divider +import org.oxycblt.auxio.list.Header +import org.oxycblt.auxio.list.ListFragment +import org.oxycblt.auxio.list.ListViewModel +import org.oxycblt.auxio.music.Music +import org.oxycblt.auxio.music.MusicParent +import org.oxycblt.auxio.music.MusicViewModel +import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.util.getDimenPixels +import org.oxycblt.auxio.util.overrideOnOverflowMenuClick +import org.oxycblt.auxio.util.setFullWidthLookup + +abstract class DetailFragment

: + ListFragment(), + DetailListAdapter.Listener, + AppBarLayout.OnOffsetChangedListener { + private val detailModel: DetailViewModel by activityViewModels() + override val listModel: ListViewModel by activityViewModels() + override val musicModel: MusicViewModel by activityViewModels() + override val playbackModel: PlaybackViewModel by activityViewModels() + + private var spacingSmall = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // Detail transitions are always on the X axis. Shared element transitions are more + // semantically correct, but are also too buggy to be sensible. + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } + + override fun onCreateBinding(inflater: LayoutInflater) = + FragmentDetail2Binding.inflate(inflater) + + abstract fun getDetailListAdapter(): DetailListAdapter + + override fun getSelectionToolbar(binding: FragmentDetail2Binding) = + binding.detailSelectionToolbar + + override fun onBindingCreated(binding: FragmentDetail2Binding, savedInstanceState: Bundle?) { + super.onBindingCreated(binding, savedInstanceState) + + // --- UI SETUP --- + binding.detailAppbar.addOnOffsetChangedListener(this) + + binding.detailNormalToolbar.apply { + setNavigationOnClickListener { findNavController().navigateUp() } + setOnMenuItemClickListener(this@DetailFragment) + overrideOnOverflowMenuClick { onOpenParentMenu() } + } + + binding.detailRecycler.apply { + adapter = getDetailListAdapter() + (layoutManager as GridLayoutManager).setFullWidthLookup { + if (it != 0) { + val item = + detailModel.artistSongList.value.getOrElse(it - 1) { + return@setFullWidthLookup false + } + item is Divider || item is Header + } else { + true + } + } + } + + spacingSmall = requireContext().getDimenPixels(R.dimen.spacing_small) + } + + override fun onDestroyBinding(binding: FragmentDetail2Binding) { + super.onDestroyBinding(binding) + binding.detailAppbar.removeOnOffsetChangedListener(this) + binding.detailNormalToolbar.setOnMenuItemClickListener(null) + binding.detailRecycler.adapter = null + } + + override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) { + val binding = requireBinding() + val range = appBarLayout.totalScrollRange + val ratio = abs(verticalOffset.toFloat()) / range.toFloat() + + val outRatio = min(ratio * 2, 1f) + val detailHeader = binding.detailHeader + detailHeader.scaleX = 1 - 0.05f * outRatio + detailHeader.scaleY = 1 - 0.05f * outRatio + detailHeader.alpha = 1 - outRatio + + val inRatio = max(ratio - 0.5f, 0f) * 2 + val detailContent = binding.detailToolbarContent + detailContent.alpha = inRatio + detailContent.translationY = spacingSmall * (1 - inRatio) + } + + abstract fun onOpenParentMenu() +} diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index a9695df55..0e1de5d35 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -625,7 +625,6 @@ constructor( for (entry in grouping.entries) { val header = BasicHeader(entry.key.headerTitleRes) - list.add(Divider(header)) list.add(header) list.addAll(ARTIST_ALBUM_SORT.albums(entry.value)) } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/header/ArtistDetailHeaderAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/header/ArtistDetailHeaderAdapter.kt deleted file mode 100644 index e85c892e7..000000000 --- a/app/src/main/java/org/oxycblt/auxio/detail/header/ArtistDetailHeaderAdapter.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2023 Auxio Project - * ArtistDetailHeaderAdapter.kt is part of Auxio. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.oxycblt.auxio.detail.header - -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import androidx.recyclerview.widget.RecyclerView -import org.oxycblt.auxio.R -import org.oxycblt.auxio.databinding.ItemDetailHeaderBinding -import org.oxycblt.auxio.music.Artist -import org.oxycblt.auxio.music.resolveNames -import org.oxycblt.auxio.util.context -import org.oxycblt.auxio.util.getPlural -import org.oxycblt.auxio.util.inflater -import org.oxycblt.auxio.util.logD - -/** - * A [DetailHeaderAdapter] that shows [Artist] information. - * - * @param listener [DetailHeaderAdapter.Listener] to bind interactions to. - * @author Alexander Capehart (OxygenCobalt) - */ -class ArtistDetailHeaderAdapter(private val listener: Listener) : - DetailHeaderAdapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = - ArtistDetailHeaderViewHolder.from(parent) - - override fun onBindHeader(holder: ArtistDetailHeaderViewHolder, parent: Artist) = - holder.bind(parent, listener) -} - -/** - * A [RecyclerView.ViewHolder] that displays the [Artist] header in the detail view. Use [from] to - * create an instance. - * - * @author Alexander Capehart (OxygenCobalt) - */ -class ArtistDetailHeaderViewHolder -private constructor(private val binding: ItemDetailHeaderBinding) : - RecyclerView.ViewHolder(binding.root) { - - /** - * Bind new data to this instance. - * - * @param artist The new [Artist] to bind. - * @param listener A [DetailHeaderAdapter.Listener] to bind interactions to. - */ - fun bind(artist: Artist, listener: DetailHeaderAdapter.Listener) { - binding.detailCover.bind(artist) - binding.detailType.text = binding.context.getString(R.string.lbl_artist) - binding.detailName.text = artist.name.resolve(binding.context) - - // Song and album counts map to the info - binding.detailInfo.text = - binding.context.getString( - R.string.fmt_two, - if (artist.explicitAlbums.isNotEmpty()) { - binding.context.getPlural(R.plurals.fmt_album_count, artist.explicitAlbums.size) - } else { - binding.context.getString(R.string.def_album_count) - }, - if (artist.songs.isNotEmpty()) { - binding.context.getPlural(R.plurals.fmt_song_count, artist.songs.size) - } else { - binding.context.getString(R.string.def_song_count) - }) - - if (artist.songs.isNotEmpty()) { - // Information about the artist's genre(s) map to the sub-head text - binding.detailSubhead.apply { - isVisible = true - text = artist.genres.resolveNames(context) - } - - // In the case that this header used to he configured to have no songs, - // we want to reset the visibility of all information that was hidden. - binding.detailPlayButton.isVisible = true - binding.detailShuffleButton.isVisible = true - } else { - // The artist does not have any songs, so hide functionality that makes no sense. - // ex. Play and Shuffle, Song Counts, and Genre Information. - // Artists are always guaranteed to have albums however, so continue to show those. - logD("Artist is empty, disabling genres and playback") - binding.detailSubhead.isVisible = false - binding.detailPlayButton.isEnabled = false - binding.detailShuffleButton.isEnabled = false - } - - binding.detailPlayButton.setOnClickListener { listener.onPlay() } - binding.detailShuffleButton.setOnClickListener { listener.onShuffle() } - } - - companion object { - /** - * Create a new instance. - * - * @param parent The parent to inflate this instance from. - * @return A new instance. - */ - fun from(parent: View) = - ArtistDetailHeaderViewHolder(ItemDetailHeaderBinding.inflate(parent.context.inflater)) - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt index d0776e4a6..3972718e8 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/fs/MediaStoreExtractor.kt @@ -285,8 +285,9 @@ private class MediaStoreExtractorImpl( // know when it corresponds to the folder and not, say, Low Roar's breakout album "0"? // Also, on some devices it's literally just null. To maintain behavior sanity just // replicate the majority behavior described prior. - rawSong.albumName = cursor.getStringOrNull(albumIndex) - ?: requireNotNull(rawSong.path?.name) { "Invalid raw: No path" } + rawSong.albumName = + cursor.getStringOrNull(albumIndex) + ?: requireNotNull(rawSong.path?.name) { "Invalid raw: No path" } // Android does not make a non-existent artist tag null, it instead fills it in // as , which makes absolutely no sense given how other columns default // to null if they are not present. If this column is such, null it so that