Add album detail fragment
Add a fragment for album details.
This commit is contained in:
parent
ebc5aac011
commit
20047313fc
11 changed files with 198 additions and 23 deletions
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.oxycblt.auxio.detail
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
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.navArgs
|
||||||
|
import org.oxycblt.auxio.databinding.FragmentAlbumDetailBinding
|
||||||
|
import org.oxycblt.auxio.music.MusicViewModel
|
||||||
|
|
||||||
|
class AlbumDetailFragment : Fragment() {
|
||||||
|
|
||||||
|
private val args: AlbumDetailFragmentArgs by navArgs()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val binding = FragmentAlbumDetailBinding.inflate(inflater)
|
||||||
|
|
||||||
|
// I honestly don't want to turn of the any data classes into parcelables due to how
|
||||||
|
// many lists they store, so just pick up the artist id and find it from musicModel.
|
||||||
|
val musicModel: MusicViewModel by activityViewModels()
|
||||||
|
val album = musicModel.albums.value?.find { it.id == args.albumId }!!
|
||||||
|
|
||||||
|
binding.lifecycleOwner = this
|
||||||
|
binding.album = album
|
||||||
|
|
||||||
|
Log.d(this::class.simpleName, "Fragment created.")
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,14 +7,21 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.navigation.fragment.navArgs
|
||||||
import org.oxycblt.auxio.ClickListener
|
import org.oxycblt.auxio.ClickListener
|
||||||
import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding
|
import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding
|
||||||
import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter
|
import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter
|
||||||
|
import org.oxycblt.auxio.library.DetailViewModel
|
||||||
import org.oxycblt.auxio.music.MusicViewModel
|
import org.oxycblt.auxio.music.MusicViewModel
|
||||||
|
import org.oxycblt.auxio.music.models.Album
|
||||||
import org.oxycblt.auxio.theme.applyDivider
|
import org.oxycblt.auxio.theme.applyDivider
|
||||||
|
|
||||||
class ArtistDetailFragment : Fragment() {
|
class ArtistDetailFragment : Fragment() {
|
||||||
|
|
||||||
|
private val args: ArtistDetailFragmentArgs by navArgs()
|
||||||
|
private val detailModel: DetailViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
@ -22,12 +29,10 @@ class ArtistDetailFragment : Fragment() {
|
||||||
): View? {
|
): View? {
|
||||||
val binding = FragmentArtistDetailBinding.inflate(inflater)
|
val binding = FragmentArtistDetailBinding.inflate(inflater)
|
||||||
|
|
||||||
// I honestly don't want to turn of the any data classes into a parcelables due to how
|
// I honestly don't want to turn of the any data classes into parcelables due to how
|
||||||
// many lists they store, so just pick up the artist id and find it from musicModel.
|
// many lists they store, so just pick up the artist id and find it from musicModel.
|
||||||
val musicModel: MusicViewModel by activityViewModels()
|
val musicModel: MusicViewModel by activityViewModels()
|
||||||
val artistId = ArtistDetailFragmentArgs.fromBundle(requireArguments()).artistId
|
val artist = musicModel.artists.value?.find { it.id == args.artistId }!!
|
||||||
|
|
||||||
val artist = musicModel.artists.value?.find { it.id == artistId }!!
|
|
||||||
|
|
||||||
binding.lifecycleOwner = this
|
binding.lifecycleOwner = this
|
||||||
binding.artist = artist
|
binding.artist = artist
|
||||||
|
|
@ -35,7 +40,7 @@ class ArtistDetailFragment : Fragment() {
|
||||||
binding.albumRecycler.adapter = DetailAlbumAdapter(
|
binding.albumRecycler.adapter = DetailAlbumAdapter(
|
||||||
artist.albums,
|
artist.albums,
|
||||||
ClickListener {
|
ClickListener {
|
||||||
Log.d(this::class.simpleName, it.name)
|
navToAlbum(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
binding.albumRecycler.applyDivider()
|
binding.albumRecycler.applyDivider()
|
||||||
|
|
@ -45,4 +50,21 @@ class ArtistDetailFragment : Fragment() {
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
detailModel.isAlreadyNavigating = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navToAlbum(album: Album) {
|
||||||
|
// Don't navigate if an item already has been selected.
|
||||||
|
if (!detailModel.isAlreadyNavigating) {
|
||||||
|
detailModel.isAlreadyNavigating = true
|
||||||
|
|
||||||
|
findNavController().navigate(
|
||||||
|
ArtistDetailFragmentDirections.actionShowAlbum(album.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.oxycblt.auxio.library
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
class DetailViewModel : ViewModel() {
|
||||||
|
var isAlreadyNavigating = false
|
||||||
|
}
|
||||||
|
|
@ -50,11 +50,10 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navToArtist(artist: Artist) {
|
private fun navToArtist(artist: Artist) {
|
||||||
|
// Don't navigate if an item already has been selected.
|
||||||
if (!libraryModel.isAlreadyNavigating) {
|
if (!libraryModel.isAlreadyNavigating) {
|
||||||
libraryModel.isAlreadyNavigating = true
|
libraryModel.isAlreadyNavigating = true
|
||||||
|
|
||||||
// When navigation, pass the artistImage of the item as a shared element to create
|
|
||||||
// the image popup.
|
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
MainFragmentDirections.actionShowArtist(artist.id)
|
MainFragmentDirections.actionShowArtist(artist.id)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -82,17 +82,8 @@ fun TextView.bindArtistCounts(artist: Artist) {
|
||||||
text = context.getString(R.string.format_double_counts, albums, songs)
|
text = context.getString(R.string.format_double_counts, albums, songs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add option to just list all genres.
|
||||||
@BindingAdapter("artistGenre")
|
@BindingAdapter("artistGenre")
|
||||||
fun TextView.getArtistGenre(artist: Artist) {
|
fun TextView.getArtistGenre(artist: Artist) {
|
||||||
// If the artist has more than one genre, pick the most used one.
|
text = artist.genre
|
||||||
// TODO: Add an option to display all genres.
|
|
||||||
val genre: String = if (artist.genres.size > 1) {
|
|
||||||
val genres = artist.genres.groupBy { it.name }
|
|
||||||
|
|
||||||
genres.keys.sortedByDescending { genres[it]?.size }[0]
|
|
||||||
} else {
|
|
||||||
artist.genres[0].name
|
|
||||||
}
|
|
||||||
|
|
||||||
text = genre
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ data class Album(
|
||||||
var name: String = "",
|
var name: String = "",
|
||||||
val artistName: String = "", // only used for sorting. Use artist.name instead.
|
val artistName: String = "", // only used for sorting. Use artist.name instead.
|
||||||
val coverUri: Uri = Uri.EMPTY,
|
val coverUri: Uri = Uri.EMPTY,
|
||||||
val year: Int = 0
|
val year: String = ""
|
||||||
) {
|
) {
|
||||||
lateinit var artist: Artist
|
lateinit var artist: Artist
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ data class Artist(
|
||||||
val genres: MutableList<Genre> = mutableListOf(Genre())
|
val genres: MutableList<Genre> = mutableListOf(Genre())
|
||||||
) {
|
) {
|
||||||
val albums = mutableListOf<Album>()
|
val albums = mutableListOf<Album>()
|
||||||
|
var genre = ""
|
||||||
|
|
||||||
val numAlbums: Int get() = albums.size
|
val numAlbums: Int get() = albums.size
|
||||||
var numSongs = 0
|
var numSongs = 0
|
||||||
|
|
@ -17,5 +18,14 @@ data class Artist(
|
||||||
albums.forEach { album ->
|
albums.forEach { album ->
|
||||||
numSongs += album.numSongs
|
numSongs += album.numSongs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the artist has more than one genre, pick the most used one.
|
||||||
|
genre = if (genres.size > 1) {
|
||||||
|
val groupGenres = genres.groupBy { it.name }
|
||||||
|
|
||||||
|
groupGenres.keys.sortedByDescending { groupGenres[it]?.size }[0]
|
||||||
|
} else {
|
||||||
|
genres[0].name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ class MusicLoader(private val resolver: ContentResolver) {
|
||||||
albums.add(
|
albums.add(
|
||||||
Album(
|
Album(
|
||||||
id, name, artist,
|
id, name, artist,
|
||||||
coverUri, year
|
coverUri, year.toString()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
96
app/src/main/res/layout/fragment_album_detail.xml
Normal file
96
app/src/main/res/layout/fragment_album_detail.xml
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="album"
|
||||||
|
type="org.oxycblt.auxio.music.models.Album" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?android:attr/actionBarSize"
|
||||||
|
android:background="?android:attr/windowBackground"
|
||||||
|
android:elevation="@dimen/elevation_normal"
|
||||||
|
app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold"
|
||||||
|
app:title="@string/title_library_fragment"
|
||||||
|
tools:titleTextColor="@color/blue" />
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/cover"
|
||||||
|
android:layout_width="@dimen/cover_size_huge"
|
||||||
|
android:layout_height="@dimen/cover_size_huge"
|
||||||
|
android:layout_marginTop="@dimen/margin_medium"
|
||||||
|
app:coverArt="@{album}"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/ic_album" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/artist_name"
|
||||||
|
style="@style/DetailHeader"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_medium"
|
||||||
|
android:layout_marginEnd="@dimen/margin_medium"
|
||||||
|
android:layout_marginStart="@dimen/margin_medium"
|
||||||
|
android:text="@{album.name}"
|
||||||
|
app:autoSizeMaxTextSize="@dimen/detail_header_size_max"
|
||||||
|
app:autoSizeMinTextSize="@dimen/detail_header_size_min"
|
||||||
|
app:autoSizeStepGranularity="@dimen/detail_header_size_increment"
|
||||||
|
app:autoSizeTextType="uniform"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/cover"
|
||||||
|
tools:text="Album Name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/album_year"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:layout_marginStart="@dimen/margin_medium"
|
||||||
|
android:text="@{album.year}"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/artist_name"
|
||||||
|
tools:text="2020" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/artist_counts"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:layout_marginStart="@dimen/margin_medium"
|
||||||
|
app:songCount="@{album}"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/album_year"
|
||||||
|
tools:text="20 Songs" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</layout>
|
||||||
|
|
@ -38,5 +38,21 @@
|
||||||
<argument
|
<argument
|
||||||
android:name="artistId"
|
android:name="artistId"
|
||||||
app:argType="long" />
|
app:argType="long" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_show_album"
|
||||||
|
app:enterAnim="@anim/fragment_fade_enter"
|
||||||
|
app:exitAnim="@anim/fragment_fade_exit"
|
||||||
|
app:popEnterAnim="@anim/fragment_fade_enter"
|
||||||
|
app:popExitAnim="@anim/fragment_fade_exit"
|
||||||
|
app:destination="@id/album_detail_fragment" />
|
||||||
|
</fragment>
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/album_detail_fragment"
|
||||||
|
android:name="org.oxycblt.auxio.detail.AlbumDetailFragment"
|
||||||
|
android:label="AlbumDetailFragment"
|
||||||
|
tools:layout="@layout/fragment_album_detail">
|
||||||
|
<argument
|
||||||
|
android:name="albumId"
|
||||||
|
app:argType="long" />
|
||||||
</fragment>
|
</fragment>
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<fade xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:duration="@android:integer/config_mediumAnimTime" />
|
|
||||||
Loading…
Reference in a new issue