Add DetailFragment for genres
Add a DetailFragment for genres.
This commit is contained in:
parent
21626d8d74
commit
9ffc194c4d
16 changed files with 497 additions and 16 deletions
|
@ -33,6 +33,10 @@ TODO:
|
|||
|
||||
/other/
|
||||
|
||||
- Create inherited adapter/viewholder so I dont have to repeat as much code
|
||||
- ? Condense detail fragments into a single fragment ?
|
||||
- Make data items inherit a single class
|
||||
- Condense artist/album recyclerview items into single item
|
||||
- Remove binding adapters
|
||||
|
||||
To be added:
|
||||
|
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import org.oxycblt.auxio.music.models.Album
|
||||
import org.oxycblt.auxio.music.models.Artist
|
||||
import org.oxycblt.auxio.music.models.Genre
|
||||
import org.oxycblt.auxio.recycler.SortMode
|
||||
|
||||
class DetailViewModel : ViewModel() {
|
||||
|
@ -13,12 +14,16 @@ class DetailViewModel : ViewModel() {
|
|||
private val mNavToParentArtist = MutableLiveData<Boolean>()
|
||||
val navToParentArtist: LiveData<Boolean> get() = mNavToParentArtist
|
||||
|
||||
private val mGenreSortMode = MutableLiveData(SortMode.ALPHA_DOWN)
|
||||
val genreSortMode: LiveData<SortMode> get() = mGenreSortMode
|
||||
|
||||
private val mArtistSortMode = MutableLiveData(SortMode.NUMERIC_DOWN)
|
||||
val artistSortMode: LiveData<SortMode> get() = mArtistSortMode
|
||||
|
||||
private val mAlbumSortMode = MutableLiveData(SortMode.NUMERIC_DOWN)
|
||||
val albumSortMode: LiveData<SortMode> get() = mAlbumSortMode
|
||||
|
||||
var currentGenre: Genre? = null
|
||||
var currentArtist: Artist? = null
|
||||
var currentAlbum: Album? = null
|
||||
|
||||
|
@ -30,6 +35,15 @@ class DetailViewModel : ViewModel() {
|
|||
mNavToParentArtist.value = false
|
||||
}
|
||||
|
||||
fun incrementGenreSortMode() {
|
||||
mGenreSortMode.value = when (mGenreSortMode.value) {
|
||||
SortMode.ALPHA_DOWN -> SortMode.ALPHA_UP
|
||||
SortMode.ALPHA_UP -> SortMode.ALPHA_DOWN
|
||||
|
||||
else -> SortMode.ALPHA_DOWN
|
||||
}
|
||||
}
|
||||
|
||||
fun incrementArtistSortMode() {
|
||||
mArtistSortMode.value = when (mArtistSortMode.value) {
|
||||
SortMode.NUMERIC_DOWN -> SortMode.NUMERIC_UP
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
package org.oxycblt.auxio.detail
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
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.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding
|
||||
import org.oxycblt.auxio.databinding.FragmentGenreDetailBinding
|
||||
import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter
|
||||
import org.oxycblt.auxio.detail.adapters.DetailArtistAdapter
|
||||
import org.oxycblt.auxio.music.MusicViewModel
|
||||
import org.oxycblt.auxio.music.models.Album
|
||||
import org.oxycblt.auxio.music.models.Artist
|
||||
import org.oxycblt.auxio.recycler.ClickListener
|
||||
import org.oxycblt.auxio.recycler.SortMode
|
||||
import org.oxycblt.auxio.theme.applyDivider
|
||||
import org.oxycblt.auxio.theme.toColor
|
||||
|
||||
class GenreDetailFragment : Fragment() {
|
||||
|
||||
private val args: GenreDetailFragmentArgs by navArgs()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val binding = FragmentGenreDetailBinding.inflate(inflater)
|
||||
|
||||
// If DetailViewModel isn't already storing the genre, get it from MusicViewModel
|
||||
// using the ID given by the navigation arguments
|
||||
if (detailModel.currentGenre == null) {
|
||||
val musicModel: MusicViewModel by activityViewModels()
|
||||
detailModel.currentGenre = musicModel.genres.value!!.find {
|
||||
it.id == args.genreId
|
||||
}!!
|
||||
}
|
||||
|
||||
val albumAdapter = DetailArtistAdapter(
|
||||
ClickListener {
|
||||
navToArtist(it)
|
||||
}
|
||||
)
|
||||
|
||||
binding.lifecycleOwner = this
|
||||
binding.detailModel = detailModel
|
||||
binding.genre = detailModel.currentGenre!!
|
||||
|
||||
binding.albumRecycler.adapter = albumAdapter
|
||||
binding.albumRecycler.applyDivider()
|
||||
binding.albumRecycler.setHasFixedSize(true)
|
||||
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
|
||||
detailModel.genreSortMode.observe(viewLifecycleOwner) { mode ->
|
||||
// Update the current sort icon
|
||||
binding.sortButton.setImageResource(mode.iconRes)
|
||||
|
||||
// Then update the sort mode of the album adapter.
|
||||
albumAdapter.submitList(
|
||||
detailModel.currentGenre!!.artists.sortedWith(
|
||||
SortMode.artistSortComparators.getOrDefault(
|
||||
mode,
|
||||
|
||||
// If any invalid value is given, just default to the normal sort order.
|
||||
compareByDescending { it.name }
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Don't enable the sort button if there is only one artist [Or less]
|
||||
if (detailModel.currentGenre!!.numArtists < 2) {
|
||||
binding.sortButton.imageTintList = ColorStateList.valueOf(
|
||||
R.color.inactive_color.toColor(requireContext())
|
||||
)
|
||||
|
||||
binding.sortButton.isEnabled = false
|
||||
}
|
||||
|
||||
Log.d(this::class.simpleName, "Fragment created.")
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
detailModel.isAlreadyNavigating = false
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
// Reset the stored artist so that the next instance of GenreDetailFragment
|
||||
// will not read it.
|
||||
detailModel.currentGenre = null
|
||||
}
|
||||
|
||||
private fun navToArtist(artist: Artist) {
|
||||
// Don't navigate if an item already has been selected.
|
||||
if (!detailModel.isAlreadyNavigating) {
|
||||
detailModel.isAlreadyNavigating = true
|
||||
|
||||
findNavController().navigate(
|
||||
GenreDetailFragmentDirections.actionShowArtist(artist.id)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package org.oxycblt.auxio.detail.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
|
||||
import org.oxycblt.auxio.databinding.ItemGenreArtistBinding
|
||||
import org.oxycblt.auxio.music.models.Album
|
||||
import org.oxycblt.auxio.music.models.Artist
|
||||
import org.oxycblt.auxio.recycler.AlbumDiffCallback
|
||||
import org.oxycblt.auxio.recycler.ArtistDiffCallback
|
||||
import org.oxycblt.auxio.recycler.ClickListener
|
||||
|
||||
class DetailArtistAdapter(
|
||||
private val listener: ClickListener<Artist>
|
||||
) : ListAdapter<Artist, DetailArtistAdapter.ViewHolder>(ArtistDiffCallback()) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(
|
||||
ItemGenreArtistBinding.inflate(LayoutInflater.from(parent.context))
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
// Generic ViewHolder for an album
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemGenreArtistBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
init {
|
||||
// Force the viewholder to *actually* be the screen width so ellipsizing can work.
|
||||
binding.root.layoutParams = RecyclerView.LayoutParams(
|
||||
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
|
||||
// Bind the view w/new data
|
||||
fun bind(artist: Artist) {
|
||||
binding.artist = artist
|
||||
|
||||
binding.root.setOnClickListener {
|
||||
listener.onClick(artist)
|
||||
}
|
||||
|
||||
// Force-update the layout so ellipsizing works.
|
||||
binding.artistImage.requestLayout()
|
||||
binding.executePendingBindings()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import org.oxycblt.auxio.library.adapters.GenreAdapter
|
|||
import org.oxycblt.auxio.music.MusicViewModel
|
||||
import org.oxycblt.auxio.music.models.Album
|
||||
import org.oxycblt.auxio.music.models.Artist
|
||||
import org.oxycblt.auxio.music.models.Genre
|
||||
import org.oxycblt.auxio.recycler.ClickListener
|
||||
import org.oxycblt.auxio.theme.SHOW_ALBUMS
|
||||
import org.oxycblt.auxio.theme.SHOW_ARTISTS
|
||||
|
@ -55,7 +56,7 @@ class LibraryFragment : Fragment() {
|
|||
SHOW_GENRES -> GenreAdapter(
|
||||
musicModel.genres.value!!,
|
||||
ClickListener {
|
||||
Log.d(this::class.simpleName, it.name)
|
||||
navToGenre(it)
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -100,4 +101,16 @@ class LibraryFragment : Fragment() {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navToGenre(genre: Genre) {
|
||||
if (!libraryModel.isAlreadyNavigating) {
|
||||
libraryModel.isAlreadyNavigating = true
|
||||
|
||||
findNavController().navigate(
|
||||
MainFragmentDirections.actionShowGenre(
|
||||
genre.id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,8 +78,17 @@ fun ImageView.getGenreImage(genre: Genre) {
|
|||
if (genre.numArtists >= 4) {
|
||||
val uris = mutableListOf<Uri>()
|
||||
|
||||
// For each artist, get the nth album from them [if possible].
|
||||
for (i in 0..3) {
|
||||
uris.add(genre.artists[i].albums[0].coverUri)
|
||||
val artist = genre.artists[i]
|
||||
|
||||
uris.add(
|
||||
if (artist.albums.size > i) {
|
||||
artist.albums[i].coverUri
|
||||
} else {
|
||||
artist.albums[0].coverUri
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val fetcher = MosaicFetcher(context)
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.graphics.Bitmap
|
|||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil.bitmap.BitmapPool
|
||||
import coil.decode.DataSource
|
||||
|
@ -17,8 +18,11 @@ import coil.size.Size
|
|||
import okio.buffer
|
||||
import okio.source
|
||||
import java.io.InputStream
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
|
||||
const val MOSAIC_BITMAP_SIZE = 512
|
||||
const val MOSAIC_BITMAP_INCREMENT = 256
|
||||
|
||||
class MosaicFetcher(private val context: Context) : Fetcher<List<Uri>> {
|
||||
override suspend fun fetch(
|
||||
|
@ -59,25 +63,24 @@ class MosaicFetcher(private val context: Context) : Fetcher<List<Uri>> {
|
|||
|
||||
var x = 0
|
||||
var y = 0
|
||||
val increment = MOSAIC_BITMAP_SIZE / 2
|
||||
|
||||
// For each stream, create a bitmap scaled to 1/4th of the mosaics combined size
|
||||
// and place it on a corner of the canvas.
|
||||
for (stream in streams) {
|
||||
val bitmap = Bitmap.createScaledBitmap(
|
||||
BitmapFactory.decodeStream(stream),
|
||||
increment,
|
||||
increment,
|
||||
MOSAIC_BITMAP_INCREMENT,
|
||||
MOSAIC_BITMAP_INCREMENT,
|
||||
true
|
||||
)
|
||||
|
||||
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
||||
|
||||
x += increment
|
||||
x += MOSAIC_BITMAP_INCREMENT
|
||||
|
||||
if (x == MOSAIC_BITMAP_SIZE) {
|
||||
x = 0
|
||||
y += increment
|
||||
y += MOSAIC_BITMAP_INCREMENT
|
||||
|
||||
if (y == MOSAIC_BITMAP_SIZE) {
|
||||
break
|
||||
|
|
|
@ -14,4 +14,11 @@ data class Genre(
|
|||
}
|
||||
return num
|
||||
}
|
||||
val numSongs: Int get() {
|
||||
var num = 0
|
||||
artists.forEach {
|
||||
num += it.numSongs
|
||||
}
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,8 +158,7 @@ class MusicSorter(
|
|||
|
||||
// Finalize music
|
||||
private fun finalizeMusic() {
|
||||
// Remove genre duplicates now, as there's a risk duplicates could be added during the
|
||||
// sorting process.
|
||||
// Remove genre duplicates now, as duplicate genres can be added during the sorting process.
|
||||
genres = genres.distinctBy {
|
||||
it.name
|
||||
}.toMutableList()
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.oxycblt.auxio.recycler
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.models.Album
|
||||
import org.oxycblt.auxio.music.models.Artist
|
||||
import org.oxycblt.auxio.music.models.Song
|
||||
|
||||
// RecyclerView click listener
|
||||
|
@ -30,6 +31,16 @@ class AlbumDiffCallback : DiffUtil.ItemCallback<Album>() {
|
|||
}
|
||||
}
|
||||
|
||||
class ArtistDiffCallback : DiffUtil.ItemCallback<Artist>() {
|
||||
override fun areContentsTheSame(oldItem: Artist, newItem: Artist): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(oldItem: Artist, newItem: Artist): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
// Sorting modes
|
||||
enum class SortMode(val iconRes: Int) {
|
||||
// Icons for each mode are assigned to the enums themselves
|
||||
|
@ -56,7 +67,17 @@ enum class SortMode(val iconRes: Int) {
|
|||
) { it.name },
|
||||
ALPHA_UP to compareBy(
|
||||
String.CASE_INSENSITIVE_ORDER
|
||||
) { it.name }
|
||||
)
|
||||
|
||||
val artistSortComparators = mapOf<SortMode, Comparator<Artist>>(
|
||||
// Alphabetic sorting needs to be case-insensitive
|
||||
ALPHA_DOWN to compareBy(
|
||||
String.CASE_INSENSITIVE_ORDER
|
||||
) { it.name },
|
||||
ALPHA_UP to compareByDescending(
|
||||
String.CASE_INSENSITIVE_ORDER
|
||||
) { it.name }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
tools:text="Artist Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/artist_genre"
|
||||
android:id="@+id/genre_counts"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
|
@ -83,7 +83,7 @@
|
|||
tools:text="Genre Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/artist_counts"
|
||||
android:id="@+id/song_count"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
|
@ -91,7 +91,7 @@
|
|||
android:layout_marginStart="@dimen/margin_medium"
|
||||
app:artistCounts="@{artist}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/artist_genre"
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_counts"
|
||||
tools:text="2 Albums, 20 Songs" />
|
||||
|
||||
<TextView
|
||||
|
@ -109,7 +109,7 @@
|
|||
android:textAppearance="@style/TextAppearance.MaterialComponents.Overline"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/song_count" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sort_button"
|
||||
|
@ -126,7 +126,7 @@
|
|||
tools:src="@drawable/ic_sort_numeric_down"
|
||||
app:layout_constraintBottom_toTopOf="@+id/album_recycler"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/song_count" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/album_recycler"
|
||||
|
|
147
app/src/main/res/layout/fragment_genre_detail.xml
Normal file
147
app/src/main/res/layout/fragment_genre_detail.xml
Normal file
|
@ -0,0 +1,147 @@
|
|||
<?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="genre"
|
||||
type="org.oxycblt.auxio.music.models.Genre" />
|
||||
|
||||
<variable
|
||||
name="detailModel"
|
||||
type="org.oxycblt.auxio.detail.DetailViewModel" />
|
||||
</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:navigationIcon="@drawable/ic_back"
|
||||
app:title="@string/title_library_fragment" />
|
||||
|
||||
<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/artist_image"
|
||||
android:layout_width="@dimen/cover_size_huge"
|
||||
android:layout_height="@dimen/cover_size_huge"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:contentDescription="@{@string/description_genre_image(genre.name)}"
|
||||
app:genreImage="@{genre}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_genre" />
|
||||
|
||||
<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="@{genre.name}"
|
||||
app:autoSizeMaxTextSize="@dimen/detail_header_size_max"
|
||||
app:autoSizeMinTextSize="@dimen/generic_size_min"
|
||||
app:autoSizeStepGranularity="@dimen/generic_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/artist_image"
|
||||
tools:text="Genre Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/genre_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:genreCounts="@{genre}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/artist_name"
|
||||
tools:text="2 Artists, 4 Ablums" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/song_count"
|
||||
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="@{@plurals/format_song_count(genre.numSongs, genre.numSongs)}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/genre_counts"
|
||||
tools:text="80 Songs" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:background="@drawable/header_dividers"
|
||||
android:fontFamily="@font/inter_bold"
|
||||
android:paddingStart="@dimen/padding_medium"
|
||||
android:paddingTop="@dimen/padding_small"
|
||||
android:paddingEnd="@dimen/padding_small"
|
||||
android:paddingBottom="@dimen/padding_small"
|
||||
android:text="@string/label_artists"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Overline"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/song_count" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sort_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:background="@drawable/header_dividers"
|
||||
android:contentDescription="@string/description_sort_button"
|
||||
android:paddingStart="@dimen/padding_medium"
|
||||
android:paddingTop="@dimen/padding_small"
|
||||
android:paddingEnd="@dimen/margin_medium"
|
||||
android:paddingBottom="@dimen/padding_small"
|
||||
android:onClick="@{() -> detailModel.incrementGenreSortMode()}"
|
||||
tools:src="@drawable/ic_sort_numeric_down"
|
||||
app:layout_constraintBottom_toTopOf="@+id/album_recycler"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/song_count" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/album_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/header_title"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/item_genre_artist" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
|
@ -20,8 +20,8 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/cover"
|
||||
android:layout_width="@dimen/cover_size_normal"
|
||||
android:layout_height="@dimen/cover_size_normal"
|
||||
android:layout_width="@dimen/cover_size_large"
|
||||
android:layout_height="@dimen/cover_size_large"
|
||||
android:contentDescription="@{@string/description_album_cover(album.name)}"
|
||||
app:coverArt="@{album}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
63
app/src/main/res/layout/item_genre_artist.xml
Normal file
63
app/src/main/res/layout/item_genre_artist.xml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="artist"
|
||||
type="org.oxycblt.auxio.music.models.Artist" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ripple"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/padding_medium">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/artist_image"
|
||||
android:layout_width="@dimen/cover_size_large"
|
||||
android:layout_height="@dimen/cover_size_large"
|
||||
android:contentDescription="@{@string/description_artist_image(artist.name)}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:artistImage="@{artist}"
|
||||
tools:src="@drawable/ic_artist" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/artist_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{artist.name}"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/album_song_count"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/artist_image"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="Artist Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_song_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="@{@plurals/format_album_count(artist.numAlbums, artist.numAlbums)}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/artist_image"
|
||||
app:layout_constraintTop_toBottomOf="@+id/artist_name"
|
||||
tools:text="2 Albums" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
|
@ -35,7 +35,18 @@
|
|||
app:launchSingleTop="true" />
|
||||
<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" />
|
||||
<action
|
||||
android:id="@+id/action_show_genre"
|
||||
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/genreDetailFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/artist_detail_fragment"
|
||||
|
@ -69,4 +80,19 @@
|
|||
app:popExitAnim="@anim/fragment_fade_exit"
|
||||
app:destination="@id/artist_detail_fragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/genreDetailFragment"
|
||||
android:name="org.oxycblt.auxio.detail.GenreDetailFragment"
|
||||
android:label="GenreDetailFragment" >
|
||||
<action
|
||||
android:id="@+id/action_show_artist"
|
||||
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/artist_detail_fragment" />
|
||||
<argument
|
||||
android:name="genreId"
|
||||
app:argType="long" />
|
||||
</fragment>
|
||||
</navigation>
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
<string name="label_retry">Retry</string>
|
||||
<string name="label_grant">Grant</string>
|
||||
<string name="label_artists">Artists</string>
|
||||
<string name="label_albums">Albums</string>
|
||||
<string name="label_songs">Songs</string>
|
||||
|
||||
|
|
Loading…
Reference in a new issue