Redesign headers/menus

Redesign the header items so they look nicer, update the PopupMenus with some new things as well.
This commit is contained in:
OxygenCobalt 2020-11-10 19:58:53 -07:00
parent 67d10009d4
commit 2c783beaba
18 changed files with 149 additions and 93 deletions

View file

@ -13,6 +13,7 @@ import org.oxycblt.auxio.ui.accent
// lead to nothing being displayed [Possibly Un-fixable]
// FIXME: Compat issues with Versions 5/6 that cause recyclerview
// dividers not to show and for progress bars to look wonky
// FIXME: Navigation memory leak that is really confusing
class MainActivity : AppCompatActivity(R.layout.activity_main) {
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {

View file

@ -5,6 +5,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
@ -17,6 +18,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.applyDivider
import org.oxycblt.auxio.ui.disable
import org.oxycblt.auxio.ui.setupAlbumSongActions
class AlbumDetailFragment : Fragment() {
@ -36,7 +38,6 @@ class AlbumDetailFragment : Fragment() {
if (detailModel.currentAlbum.value == null ||
detailModel.currentAlbum.value?.id != args.albumId
) {
detailModel.updateAlbum(
MusicStore.getInstance().albums.find {
it.id == args.albumId
@ -44,9 +45,16 @@ class AlbumDetailFragment : Fragment() {
)
}
val songAdapter = DetailSongAdapter {
playbackModel.playSong(it, PlaybackMode.IN_ALBUM)
}
val songAdapter = DetailSongAdapter(
{
playbackModel.playSong(it, PlaybackMode.IN_ALBUM)
},
{ data, view ->
PopupMenu(requireContext(), view).setupAlbumSongActions(
data, requireContext(), detailModel, playbackModel
)
}
)
// --- UI SETUP ---
@ -100,24 +108,20 @@ class AlbumDetailFragment : Fragment() {
)
}
// If the album was shown directly from LibraryFragment, Then enable the ability to
// navigate upwards to the parent artist
if (args.enableParentNav) {
detailModel.doneWithNavToParent()
detailModel.doneWithNavToParent()
detailModel.navToParent.observe(viewLifecycleOwner) {
if (it) {
detailModel.navToParent.observe(viewLifecycleOwner) {
if (it) {
if (!args.enableParentNav) {
findNavController().navigateUp()
} else {
findNavController().navigate(
AlbumDetailFragmentDirections.actionShowParentArtist(
detailModel.currentAlbum.value!!.artist.id
)
)
detailModel.doneWithNavToParent()
}
}
binding.albumArtist.setBackgroundResource(R.drawable.ui_ripple)
}
Log.d(this::class.simpleName, "Fragment created.")

View file

@ -1,6 +1,7 @@
package org.oxycblt.auxio.detail.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
@ -9,7 +10,8 @@ import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
class DetailSongAdapter(
private val doOnClick: (data: Song) -> Unit
private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (data: Song, view: View) -> Unit
) : ListAdapter<Song, DetailSongAdapter.ViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
@ -24,7 +26,7 @@ class DetailSongAdapter(
// Generic ViewHolder for a song
inner class ViewHolder(
private val binding: ItemAlbumSongBinding,
) : BaseViewHolder<Song>(binding, doOnClick, null) {
) : BaseViewHolder<Song>(binding, doOnClick, doOnLongClick) {
override fun onBind(data: Song) {
binding.song = data

View file

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.view.forEach
@ -30,7 +31,7 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.applyColor
import org.oxycblt.auxio.ui.applyDivider
import org.oxycblt.auxio.ui.resolveAttr
import org.oxycblt.auxio.ui.showActionMenuForSong
import org.oxycblt.auxio.ui.setupSongActions
// A Fragment to show all the music in the Library.
class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
@ -57,7 +58,9 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
},
{ data, view ->
if (data is Song) {
showActionMenuForSong(requireContext(), data, view, playbackModel)
PopupMenu(requireContext(), view).setupSongActions(
data, requireContext(), playbackModel
)
}
}
)

View file

@ -173,25 +173,27 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
}
// Move queue OR user queue items, given QueueAdapter indices.
// I have no idea what is going on in this function, but it works, so
fun moveQueueItems(adapterFrom: Int, adapterTo: Int): Boolean {
var from = adapterFrom.dec()
var to = adapterTo.dec()
if (from < mUserQueue.value!!.size) {
// Ignore invalid movements to out of bounds, header, or queue positions
if (to >= mUserQueue.value!!.size || to < 0) return false
playbackManager.moveUserQueueItems(from, to)
} else {
// Ignore invalid movements to out of bounds or header positions
if (to < 0) return false
// Get the real queue positions from the nextInQueue positions
val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size
from += delta
to += delta
if (userQueue.value!!.isNotEmpty()) {
// Ignore user queue positions
if (to <= mUserQueue.value!!.size.inc()) return false
from -= mUserQueue.value!!.size.inc()

View file

@ -44,6 +44,10 @@ class QueueFragment : Fragment() {
}
playbackModel.userQueue.observe(viewLifecycleOwner) {
if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
findNavController().navigateUp()
}
queueAdapter.submitList(createQueueDisplay()) {
binding.queueRecycler.scrollToPosition(0)
scrollRecyclerIfNeeded(binding)
@ -51,6 +55,10 @@ class QueueFragment : Fragment() {
}
playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) {
if (it.isEmpty() && playbackModel.userQueue.value!!.isEmpty()) {
findNavController().navigateUp()
}
queueAdapter.submitList(createQueueDisplay()) {
scrollRecyclerIfNeeded(binding)
}
@ -87,7 +95,7 @@ class QueueFragment : Fragment() {
private fun scrollRecyclerIfNeeded(binding: FragmentQueueBinding) {
if ((binding.queueRecycler.layoutManager as LinearLayoutManager)
.findFirstVisibleItemPosition() < 1
.findFirstVisibleItemPosition() < 1
) {
binding.queueRecycler.scrollToPosition(0)
}

View file

@ -5,6 +5,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.R
@ -13,7 +14,7 @@ import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.applyDivider
import org.oxycblt.auxio.ui.showActionMenuForSong
import org.oxycblt.auxio.ui.setupSongActions
class SongsFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()
@ -46,7 +47,9 @@ class SongsFragment : Fragment() {
playbackModel.playSong(it, PlaybackMode.ALL_SONGS)
},
{ data, view ->
showActionMenuForSong(requireContext(), data, view, playbackModel)
PopupMenu(requireContext(), view).setupSongActions(
data, requireContext(), playbackModel
)
}
)
applyDivider()

View file

@ -6,61 +6,20 @@ import android.graphics.drawable.ColorDrawable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.view.MenuItem
import android.view.View
import android.widget.ImageButton
import android.widget.PopupMenu
import android.widget.Toast
import androidx.annotation.ColorInt
import androidx.appcompat.widget.PopupMenu
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
// Functions for managing UI elements [Not Colors]
fun showActionMenuForSong(
context: Context,
song: Song,
view: View,
playbackModel: PlaybackViewModel
) {
PopupMenu(context, view).apply {
inflate(R.menu.menu_song_actions)
setOnMenuItemClickListener {
return@setOnMenuItemClickListener when (it.itemId) {
R.id.action_queue_add -> {
playbackModel.addToUserQueue(song)
Toast.makeText(
context,
context.getString(R.string.label_queue_added),
Toast.LENGTH_SHORT
).show()
true
}
R.id.action_play_artist -> {
playbackModel.playSong(song, PlaybackMode.IN_ARTIST)
true
}
R.id.action_play_album -> {
playbackModel.playSong(song, PlaybackMode.IN_ALBUM)
true
}
else -> false
}
}
show()
}
}
// Apply a color to a Menu Item
fun MenuItem.applyColor(@ColorInt color: Int) {
SpannableString(title).apply {
@ -95,3 +54,67 @@ fun RecyclerView.applyDivider() {
addItemDecoration(div)
}
fun PopupMenu.setupSongActions(song: Song, context: Context, playbackModel: PlaybackViewModel) {
inflate(R.menu.menu_song_actions)
setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_queue_add -> {
playbackModel.addToUserQueue(song)
Toast.makeText(
context,
context.getString(R.string.label_queue_added),
Toast.LENGTH_SHORT
).show()
true
}
R.id.action_play_artist -> {
playbackModel.playSong(song, PlaybackMode.IN_ARTIST)
true
}
R.id.action_play_album -> {
playbackModel.playSong(song, PlaybackMode.IN_ALBUM)
true
}
else -> false
}
}
show()
}
fun PopupMenu.setupAlbumSongActions(
song: Song,
context: Context,
detailViewModel: DetailViewModel,
playbackModel: PlaybackViewModel
) {
inflate(R.menu.menu_album_song_actions)
setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_queue_add -> {
playbackModel.addToUserQueue(song)
Toast.makeText(
context,
context.getString(R.string.label_queue_added),
Toast.LENGTH_SHORT
).show()
true
}
R.id.action_go_artist -> {
detailViewModel.doNavToParent()
true
}
else -> false
}
}
show()
}

View file

@ -112,14 +112,14 @@
android:layout_height="match_parent"
android:layout_marginTop="@dimen/padding_medium"
android:background="@drawable/ui_header_dividers"
android:fontFamily="@font/inter_bold"
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="@font/inter_semibold"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/padding_small"
android:paddingBottom="@dimen/padding_small"
android:text="@string/label_songs"
android:textAppearance="@style/TextAppearance.MaterialComponents.Overline"
android:textSize="16sp"
android:textSize="19sp"
app:layout_constraintTop_toBottomOf="@+id/album_details" />
<ImageButton

View file

@ -61,9 +61,9 @@
style="@style/DetailHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:layout_marginEnd="@dimen/margin_medium"
android:layout_marginStart="@dimen/margin_medium"
android:text="@{artist.name}"
app:autoSizeMaxTextSize="@dimen/text_size_header_max"
app:autoSizeMinTextSize="@dimen/text_size_min"
@ -79,9 +79,9 @@
android:id="@+id/artist_genre"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginStart="@dimen/margin_medium"
app:artistGenre="@{artist}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_name"
@ -91,9 +91,9 @@
android:id="@+id/artist_counts"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginStart="@dimen/margin_medium"
app:artistCounts="@{artist}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_genre"
@ -102,18 +102,17 @@
<TextView
android:id="@+id/artist_album_header"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_medium"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/padding_medium"
android:background="@drawable/ui_header_dividers"
android:fontFamily="@font/inter_bold"
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="@font/inter_semibold"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/padding_small"
android:paddingBottom="@dimen/padding_small"
android:text="@string/label_albums"
android:textAppearance="@style/TextAppearance.MaterialComponents.Overline"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
android:textSize="19sp"
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
<ImageButton
@ -123,15 +122,15 @@
android:layout_marginTop="@dimen/margin_medium"
android:background="@drawable/ui_header_dividers"
android:contentDescription="@string/description_sort_button"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/margin_medium"
android:paddingBottom="@dimen/padding_small"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
tools:src="@drawable/ic_sort_numeric_down"
app:layout_constraintBottom_toTopOf="@+id/artist_album_recycler"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
app:layout_constraintTop_toBottomOf="@+id/artist_counts"
tools:src="@drawable/ic_sort_numeric_down" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/artist_album_recycler"

View file

@ -104,18 +104,17 @@
<TextView
android:id="@+id/genre_artist_header"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_medium"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/padding_medium"
android:background="@drawable/ui_header_dividers"
android:fontFamily="@font/inter_bold"
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="@font/inter_semibold"
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"
android:textSize="19sp"
app:layout_constraintTop_toBottomOf="@+id/genre_song_count" />
<ImageButton

View file

@ -18,6 +18,7 @@
android:theme="@style/Toolbar.Style"
android:clickable="true"
android:focusable="true"
android:elevation="@dimen/elevation_normal"
app:navigationIcon="@drawable/ic_down"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

View file

@ -19,14 +19,13 @@
android:id="@+id/header_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ui_header_dividers"
android:fontFamily="@font/inter_bold"
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="@font/inter_semibold"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/padding_small"
android:paddingBottom="@dimen/padding_small"
android:textAppearance="@style/TextAppearance.MaterialComponents.Overline"
android:textSize="16sp"
android:textSize="19sp"
android:text="@{header.name}"
tools:text="Songs" />

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_queue_add"
android:title="@string/label_queue_add"
android:icon="@drawable/ic_queue_add" />
<item
android:id="@+id/action_go_artist"
android:title="@string/label_go_artist"
android:icon="@drawable/ic_artist" />
</menu>

View file

@ -3,7 +3,7 @@
<item
android:id="@+id/action_queue_add"
android:title="@string/label_queue_add"
android:icon="@drawable/ic_user_queue" />
android:icon="@drawable/ic_queue_add" />
<item
android:id="@+id/action_play_artist"
android:title="@string/label_play_artist"

View file

@ -27,6 +27,7 @@
<string name="label_play">Play</string>
<string name="label_play_artist">Play from artist</string>
<string name="label_play_album">Play from album</string>
<string name="label_go_artist">Go to artist</string>
<string name="label_queue">Queue</string>
<string name="label_queue_add">Add to queue</string>
<string name="label_queue_added">Added to queue</string>

View file

@ -8,7 +8,7 @@
<item name="android:textCursorDrawable">@drawable/ui_cursor</item>
<item name="android:fitsSystemWindows">true</item>
<item name="android:popupMenuStyle">@style/Widget.CustomPopup</item>
<item name="popupMenuStyle">@style/Widget.CustomPopup</item>
<item name="colorControlNormal">@color/control_color</item>
</style>