Add shuffling
Add basic shuffling to PlaybackFragment.
This commit is contained in:
parent
339100e436
commit
c422071e93
15 changed files with 190 additions and 78 deletions
|
@ -8,6 +8,7 @@ import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.recycler.DiffCallback
|
import org.oxycblt.auxio.recycler.DiffCallback
|
||||||
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||||
|
|
||||||
|
// TODO: Add ability to highlight currently playing songs
|
||||||
class DetailAlbumAdapter(
|
class DetailAlbumAdapter(
|
||||||
private val doOnClick: (Album) -> Unit
|
private val doOnClick: (Album) -> Unit
|
||||||
) : ListAdapter<Album, DetailAlbumAdapter.ViewHolder>(DiffCallback()) {
|
) : ListAdapter<Album, DetailAlbumAdapter.ViewHolder>(DiffCallback()) {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
|
||||||
class DetailSongAdapter(
|
class DetailSongAdapter(
|
||||||
private val doOnClick: (Song) -> Unit
|
private val doOnClick: (Song) -> Unit
|
||||||
) : ListAdapter<Song, DetailSongAdapter.ViewHolder>(DiffCallback()) {
|
) : ListAdapter<Song, DetailSongAdapter.ViewHolder>(DiffCallback()) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(
|
return ViewHolder(
|
||||||
ItemAlbumSongBinding.inflate(LayoutInflater.from(parent.context))
|
ItemAlbumSongBinding.inflate(LayoutInflater.from(parent.context))
|
||||||
|
|
|
@ -22,7 +22,9 @@ class MusicStore private constructor() {
|
||||||
private var mSongs = listOf<Song>()
|
private var mSongs = listOf<Song>()
|
||||||
val songs: List<Song> get() = mSongs
|
val songs: List<Song> get() = mSongs
|
||||||
|
|
||||||
suspend fun load(app: Application): MusicLoaderResponse {
|
// Load/Sort the entire library.
|
||||||
|
// ONLY CALL THIS FROM AN IO THREAD.
|
||||||
|
fun load(app: Application): MusicLoaderResponse {
|
||||||
Log.i(this::class.simpleName, "Starting initial music load...")
|
Log.i(this::class.simpleName, "Starting initial music load...")
|
||||||
|
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
|
|
|
@ -25,7 +25,6 @@ class CompactPlaybackFragment : Fragment() {
|
||||||
): View? {
|
): View? {
|
||||||
val binding = FragmentCompactPlaybackBinding.inflate(inflater)
|
val binding = FragmentCompactPlaybackBinding.inflate(inflater)
|
||||||
|
|
||||||
// FIXME: Stop these icons from self-animating on creation.
|
|
||||||
val iconPauseToPlay = ContextCompat.getDrawable(
|
val iconPauseToPlay = ContextCompat.getDrawable(
|
||||||
requireContext(), R.drawable.ic_pause_to_play
|
requireContext(), R.drawable.ic_pause_to_play
|
||||||
) as AnimatedVectorDrawable
|
) as AnimatedVectorDrawable
|
||||||
|
|
|
@ -23,9 +23,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
|
||||||
// TODO: Implement media controls
|
// TODO: Implement media controls
|
||||||
// TODO: Make exit icon bigger
|
|
||||||
// TODO: Implement nav to artists/albums
|
// TODO: Implement nav to artists/albums
|
||||||
// TODO: Possibly implement a trackbar with a spectrum shown as well.
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
@ -98,6 +96,15 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playbackModel.isShuffling.observe(viewLifecycleOwner) {
|
||||||
|
// Highlight the shuffle button if Playback is shuffled, and revert it if not.
|
||||||
|
if (it) {
|
||||||
|
binding.playbackShuffle.imageTintList = accentColor
|
||||||
|
} else {
|
||||||
|
binding.playbackShuffle.imageTintList = controlColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
playbackModel.isSeeking.observe(viewLifecycleOwner) {
|
playbackModel.isSeeking.observe(viewLifecycleOwner) {
|
||||||
// Highlight the current duration if the user is seeking, and revert it if not.
|
// Highlight the current duration if the user is seeking, and revert it if not.
|
||||||
if (it) {
|
if (it) {
|
||||||
|
|
|
@ -7,12 +7,4 @@ package org.oxycblt.auxio.playback
|
||||||
// IN_ALBUM -> Play from the songs of the album
|
// IN_ALBUM -> Play from the songs of the album
|
||||||
enum class PlaybackMode {
|
enum class PlaybackMode {
|
||||||
IN_ARTIST, IN_ALBUM, ALL_SONGS;
|
IN_ARTIST, IN_ALBUM, ALL_SONGS;
|
||||||
|
|
||||||
// Make a slice of all the values that this ShowMode covers.
|
|
||||||
// ex. SHOW_ARTISTS would return SHOW_ARTISTS, SHOW_ALBUMS, and SHOW_SONGS
|
|
||||||
fun getChildren(): List<PlaybackMode> {
|
|
||||||
val vals = values()
|
|
||||||
|
|
||||||
return vals.slice(vals.indexOf(this) until vals.size)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,11 @@ import androidx.lifecycle.ViewModel
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Genre
|
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.toDuration
|
import org.oxycblt.auxio.music.toDuration
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.random.Random.Default.nextLong
|
||||||
|
|
||||||
// TODO: Implement media controls
|
// TODO: Implement media controls
|
||||||
// TODO: Implement persistence
|
// TODO: Implement persistence
|
||||||
|
@ -39,6 +40,11 @@ class PlaybackViewModel : ViewModel() {
|
||||||
private val mIsPlaying = MutableLiveData(false)
|
private val mIsPlaying = MutableLiveData(false)
|
||||||
val isPlaying: LiveData<Boolean> get() = mIsPlaying
|
val isPlaying: LiveData<Boolean> get() = mIsPlaying
|
||||||
|
|
||||||
|
private val mIsShuffling = MutableLiveData(false)
|
||||||
|
val isShuffling: LiveData<Boolean> get() = mIsShuffling
|
||||||
|
|
||||||
|
private val mShuffleSeed = MutableLiveData(-1L)
|
||||||
|
|
||||||
private val mIsSeeking = MutableLiveData(false)
|
private val mIsSeeking = MutableLiveData(false)
|
||||||
val isSeeking: LiveData<Boolean> get() = mIsSeeking
|
val isSeeking: LiveData<Boolean> get() = mIsSeeking
|
||||||
|
|
||||||
|
@ -57,6 +63,8 @@ class PlaybackViewModel : ViewModel() {
|
||||||
|
|
||||||
val musicStore = MusicStore.getInstance()
|
val musicStore = MusicStore.getInstance()
|
||||||
|
|
||||||
|
mCurrentMode.value = mode
|
||||||
|
|
||||||
updatePlayback(song)
|
updatePlayback(song)
|
||||||
|
|
||||||
mQueue.value = when (mode) {
|
mQueue.value = when (mode) {
|
||||||
|
@ -65,7 +73,18 @@ class PlaybackViewModel : ViewModel() {
|
||||||
PlaybackMode.IN_ALBUM -> song.album.songs
|
PlaybackMode.IN_ALBUM -> song.album.songs
|
||||||
}
|
}
|
||||||
|
|
||||||
mCurrentMode.value = mode
|
mCurrentParent.value = when (mode) {
|
||||||
|
PlaybackMode.ALL_SONGS -> null
|
||||||
|
PlaybackMode.IN_ARTIST -> song.album.artist
|
||||||
|
PlaybackMode.IN_ALBUM -> song.album
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isShuffling.value!!) {
|
||||||
|
genShuffle(true)
|
||||||
|
} else {
|
||||||
|
resetShuffle()
|
||||||
|
}
|
||||||
|
|
||||||
mCurrentIndex.value = mQueue.value!!.indexOf(song)
|
mCurrentIndex.value = mQueue.value!!.indexOf(song)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +98,7 @@ class PlaybackViewModel : ViewModel() {
|
||||||
mQueue.value = songs
|
mQueue.value = songs
|
||||||
mCurrentIndex.value = 0
|
mCurrentIndex.value = 0
|
||||||
mCurrentParent.value = album
|
mCurrentParent.value = album
|
||||||
|
mIsShuffling.value = isShuffled
|
||||||
mCurrentMode.value = PlaybackMode.IN_ALBUM
|
mCurrentMode.value = PlaybackMode.IN_ALBUM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,71 +112,49 @@ class PlaybackViewModel : ViewModel() {
|
||||||
mQueue.value = songs
|
mQueue.value = songs
|
||||||
mCurrentIndex.value = 0
|
mCurrentIndex.value = 0
|
||||||
mCurrentParent.value = artist
|
mCurrentParent.value = artist
|
||||||
|
mIsShuffling.value = isShuffled
|
||||||
mCurrentMode.value = PlaybackMode.IN_ARTIST
|
mCurrentMode.value = PlaybackMode.IN_ARTIST
|
||||||
}
|
}
|
||||||
|
|
||||||
fun play(genre: Genre, isShuffled: Boolean) {
|
|
||||||
Log.d(this::class.simpleName, "Playing genre ${genre.name}")
|
|
||||||
|
|
||||||
val songs = orderSongsInGenre(genre)
|
|
||||||
|
|
||||||
updatePlayback(songs[0])
|
|
||||||
|
|
||||||
mQueue.value = songs
|
|
||||||
mCurrentIndex.value = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updatePlayback(song: Song) {
|
|
||||||
mCurrentSong.value = song
|
|
||||||
mCurrentDuration.value = 0
|
|
||||||
|
|
||||||
if (!mIsPlaying.value!!) {
|
|
||||||
mIsPlaying.value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic sorting functions when things are played in order
|
|
||||||
private fun orderSongsInAlbum(album: Album): MutableList<Song> {
|
|
||||||
return album.songs.sortedBy { it.track }.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun orderSongsInArtist(artist: Artist): MutableList<Song> {
|
|
||||||
val final = mutableListOf<Song>()
|
|
||||||
|
|
||||||
artist.albums.sortedByDescending { it.year }.forEach {
|
|
||||||
final.addAll(it.songs.sortedBy { it.track })
|
|
||||||
}
|
|
||||||
|
|
||||||
return final
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun orderSongsInGenre(genre: Genre): MutableList<Song> {
|
|
||||||
val final = mutableListOf<Song>()
|
|
||||||
|
|
||||||
genre.artists.sortedByDescending { it.name }.forEach { artist ->
|
|
||||||
artist.albums.sortedByDescending { it.year }.forEach { album ->
|
|
||||||
final.addAll(album.songs.sortedBy { it.track })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return final
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invert, not directly set the playing status
|
|
||||||
fun invertPlayingStatus() {
|
|
||||||
mIsPlaying.value = !mIsPlaying.value!!
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the seeking status
|
|
||||||
fun setSeekingStatus(status: Boolean) {
|
|
||||||
mIsSeeking.value = status
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the current duration using a SeekBar progress
|
// Update the current duration using a SeekBar progress
|
||||||
fun updateCurrentDurationWithProgress(progress: Int) {
|
fun updateCurrentDurationWithProgress(progress: Int) {
|
||||||
mCurrentDuration.value = progress.toLong()
|
mCurrentDuration.value = progress.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invert, not directly set the playing/shuffling status
|
||||||
|
// Used by the toggle buttons in playback fragment.
|
||||||
|
fun invertPlayingStatus() {
|
||||||
|
mIsPlaying.value = !mIsPlaying.value!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invertShuffleStatus() {
|
||||||
|
mIsShuffling.value = !mIsShuffling.value!!
|
||||||
|
|
||||||
|
if (mIsShuffling.value!!) {
|
||||||
|
genShuffle(true)
|
||||||
|
} else {
|
||||||
|
resetShuffle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle all the songs.
|
||||||
|
fun shuffleAll() {
|
||||||
|
val musicStore = MusicStore.getInstance()
|
||||||
|
|
||||||
|
mIsShuffling.value = true
|
||||||
|
mQueue.value = musicStore.songs.toMutableList()
|
||||||
|
mCurrentMode.value = PlaybackMode.ALL_SONGS
|
||||||
|
mCurrentIndex.value = 0
|
||||||
|
|
||||||
|
genShuffle(false)
|
||||||
|
updatePlayback(mQueue.value!![0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the seeking status
|
||||||
|
fun setSeekingStatus(status: Boolean) {
|
||||||
|
mIsSeeking.value = status
|
||||||
|
}
|
||||||
|
|
||||||
fun skipNext() {
|
fun skipNext() {
|
||||||
if (mCurrentIndex.value!! < mQueue.value!!.size) {
|
if (mCurrentIndex.value!! < mQueue.value!!.size) {
|
||||||
mCurrentIndex.value = mCurrentIndex.value!!.inc()
|
mCurrentIndex.value = mCurrentIndex.value!!.inc()
|
||||||
|
@ -172,4 +170,64 @@ class PlaybackViewModel : ViewModel() {
|
||||||
|
|
||||||
updatePlayback(mQueue.value!![mCurrentIndex.value!!])
|
updatePlayback(mQueue.value!![mCurrentIndex.value!!])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updatePlayback(song: Song) {
|
||||||
|
mCurrentSong.value = song
|
||||||
|
mCurrentDuration.value = 0
|
||||||
|
|
||||||
|
if (!mIsPlaying.value!!) {
|
||||||
|
mIsPlaying.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new shuffled queue.
|
||||||
|
private fun genShuffle(keepSong: Boolean) {
|
||||||
|
// Take a random seed and then shuffle the current queue based off of that.
|
||||||
|
// This seed will be saved in a bundle if the app closes, so that the shuffle mode
|
||||||
|
// can be restored when its started again.
|
||||||
|
val newSeed = Random.Default.nextLong()
|
||||||
|
|
||||||
|
Log.d(this::class.simpleName, "Shuffling queue with a seed of $newSeed.")
|
||||||
|
|
||||||
|
mShuffleSeed.value = newSeed
|
||||||
|
|
||||||
|
mQueue.value!!.shuffle(Random(newSeed))
|
||||||
|
mCurrentIndex.value = 0
|
||||||
|
|
||||||
|
// If specified, make the current song the first member of the queue.
|
||||||
|
if (keepSong) {
|
||||||
|
mQueue.value!!.remove(mCurrentSong.value)
|
||||||
|
mQueue.value!!.add(0, mCurrentSong.value!!)
|
||||||
|
} else {
|
||||||
|
// Otherwise, just start from the zeroth position in the queue.
|
||||||
|
mCurrentSong.value = mQueue.value!![0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetShuffle() {
|
||||||
|
mShuffleSeed.value = -1
|
||||||
|
|
||||||
|
mQueue.value = when (mCurrentMode.value!!) {
|
||||||
|
PlaybackMode.IN_ARTIST -> orderSongsInArtist(mCurrentParent.value as Artist)
|
||||||
|
PlaybackMode.IN_ALBUM -> orderSongsInAlbum(mCurrentParent.value as Album)
|
||||||
|
PlaybackMode.ALL_SONGS -> MusicStore.getInstance().songs.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
mCurrentIndex.value = mQueue.value!!.indexOf(mCurrentSong.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic sorting functions when things are played in order
|
||||||
|
private fun orderSongsInAlbum(album: Album): MutableList<Song> {
|
||||||
|
return album.songs.sortedBy { it.track }.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun orderSongsInArtist(artist: Artist): MutableList<Song> {
|
||||||
|
val final = mutableListOf<Song>()
|
||||||
|
|
||||||
|
artist.albums.sortedByDescending { it.year }.forEach {
|
||||||
|
final.addAll(it.songs.sortedBy { it.track })
|
||||||
|
}
|
||||||
|
|
||||||
|
return final
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ 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 org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentSongsBinding
|
import org.oxycblt.auxio.databinding.FragmentSongsBinding
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.playback.PlaybackMode
|
import org.oxycblt.auxio.playback.PlaybackMode
|
||||||
|
@ -30,6 +31,13 @@ class SongsFragment : Fragment() {
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
||||||
|
binding.songToolbar.setOnMenuItemClickListener {
|
||||||
|
if (it.itemId == R.id.action_shuffle) {
|
||||||
|
playbackModel.shuffleAll()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
binding.songRecycler.apply {
|
binding.songRecycler.apply {
|
||||||
adapter = SongAdapter(musicStore.songs) {
|
adapter = SongAdapter(musicStore.songs) {
|
||||||
playbackModel.update(it, PlaybackMode.ALL_SONGS)
|
playbackModel.update(it, PlaybackMode.ALL_SONGS)
|
||||||
|
|
11
app/src/main/res/drawable/ic_shuffle.xml
Normal file
11
app/src/main/res/drawable/ic_shuffle.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="@color/control_color">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M10.59,9.17L5.41,4 4,5.41l5.17,5.17 1.42,-1.41zM14.5,4l2.04,2.04L4,18.59 5.41,20 17.96,7.46 20,9.5L20,4h-5.5zM14.83,13.41l-1.41,1.41 3.13,3.13L14.5,20L20,20v-5.5l-2.04,2.04 -3.13,-3.13z" />
|
||||||
|
</vector>
|
11
app/src/main/res/drawable/ic_shuffle_small.xml
Normal file
11
app/src/main/res/drawable/ic_shuffle_small.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="@color/control_color">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M10.59,9.17L5.41,4 4,5.41l5.17,5.17 1.42,-1.41zM14.5,4l2.04,2.04L4,18.59 5.41,20 17.96,7.46 20,9.5L20,4h-5.5zM14.83,13.41l-1.41,1.41 3.13,3.13L14.5,20L20,20v-5.5l-2.04,2.04 -3.13,-3.13z" />
|
||||||
|
</vector>
|
|
@ -185,5 +185,19 @@
|
||||||
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
|
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
|
||||||
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/playback_shuffle"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="@dimen/size_play_pause_compact"
|
||||||
|
android:layout_height="@dimen/size_play_pause_compact"
|
||||||
|
android:layout_marginEnd="@dimen/margin_mid_large"
|
||||||
|
android:src="@drawable/ic_shuffle"
|
||||||
|
android:background="@drawable/ui_unbounded_ripple"
|
||||||
|
android:onClick="@{() -> playbackModel.invertShuffleStatus()}"
|
||||||
|
android:contentDescription="@{playbackModel.isShuffling() ? @string/description_shuffle_off : @string/description_shuffle_on"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/playback_play_pause" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</layout>
|
</layout>
|
|
@ -16,6 +16,7 @@
|
||||||
android:layout_height="?android:attr/actionBarSize"
|
android:layout_height="?android:attr/actionBarSize"
|
||||||
android:background="?android:attr/windowBackground"
|
android:background="?android:attr/windowBackground"
|
||||||
android:elevation="@dimen/elevation_normal"
|
android:elevation="@dimen/elevation_normal"
|
||||||
|
app:menu="@menu/menu_songs"
|
||||||
app:titleTextAppearance="@style/TextAppearance.Toolbar.Header"
|
app:titleTextAppearance="@style/TextAppearance.Toolbar.Header"
|
||||||
app:title="@string/title_all_songs" />
|
app:title="@string/title_all_songs" />
|
||||||
|
|
||||||
|
|
9
app/src/main/res/menu/menu_songs.xml
Normal file
9
app/src/main/res/menu/menu_songs.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_shuffle"
|
||||||
|
android:icon="@drawable/ic_shuffle_small"
|
||||||
|
android:title="@string/label_shuffle"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
</menu>
|
|
@ -23,6 +23,7 @@
|
||||||
<string name="label_sort_none">Default</string>
|
<string name="label_sort_none">Default</string>
|
||||||
<string name="label_sort_alpha_down">A-Z</string>
|
<string name="label_sort_alpha_down">A-Z</string>
|
||||||
<string name="label_sort_alpha_up">Z-A</string>
|
<string name="label_sort_alpha_up">Z-A</string>
|
||||||
|
<string name="label_shuffle">Shuffle</string>
|
||||||
|
|
||||||
<!-- Hint Namespace | EditText Hints -->
|
<!-- Hint Namespace | EditText Hints -->
|
||||||
<string name="hint_search_library">Search Library…</string>
|
<string name="hint_search_library">Search Library…</string>
|
||||||
|
@ -41,6 +42,8 @@
|
||||||
<string name="description_pause">Pause</string>
|
<string name="description_pause">Pause</string>
|
||||||
<string name="description_skip_next">Skip to next song</string>
|
<string name="description_skip_next">Skip to next song</string>
|
||||||
<string name="description_skip_prev">Skip to last song</string>
|
<string name="description_skip_prev">Skip to last song</string>
|
||||||
|
<string name="description_shuffle_on">Turn shuffle on</string>
|
||||||
|
<string name="description_shuffle_off">Turn shuffle off</string>
|
||||||
|
|
||||||
<!-- Placeholder Namespace | Placeholder values -->
|
<!-- Placeholder Namespace | Placeholder values -->
|
||||||
<string name="placeholder_genre">Unknown Genre</string>
|
<string name="placeholder_genre">Unknown Genre</string>
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- TODO: Try to make some of these styles better -->
|
|
||||||
<!-- Base theme -->
|
<!-- Base theme -->
|
||||||
<style name="Theme.Base" parent="Theme.AppCompat.DayNight.NoActionBar">
|
<style name="Theme.Base" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||||
<item name="android:windowBackground">@color/background</item>
|
<item name="android:windowBackground">@color/background</item>
|
||||||
<item name="android:statusBarColor">@android:color/black</item>
|
<item name="android:statusBarColor">@android:color/black</item>
|
||||||
<item name="android:fontFamily">@font/inter</item>
|
<item name="android:fontFamily">@font/inter</item>
|
||||||
<item name="android:textCursorDrawable">@drawable/ui_cursor</item>
|
<item name="android:textCursorDrawable">@drawable/ui_cursor</item>
|
||||||
<item name="actionBarPopupTheme">@style/AppThemeOverlay.Popup</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- Toolbar Themes -->
|
||||||
<style name="Toolbar.Style" parent="ThemeOverlay.MaterialComponents.ActionBar">
|
<style name="Toolbar.Style" parent="ThemeOverlay.MaterialComponents.ActionBar">
|
||||||
<item name="android:searchViewStyle">@style/Widget.AppCompat.SearchView</item>
|
<item name="android:searchViewStyle">@style/Widget.AppCompat.SearchView</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -19,6 +18,7 @@
|
||||||
<item name="android:textColor">?android:attr/colorPrimary</item>
|
<item name="android:textColor">?android:attr/colorPrimary</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- Header Themes -->
|
||||||
<style name="DetailHeader">
|
<style name="DetailHeader">
|
||||||
<item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
|
<item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
|
||||||
<item name="android:textColor">?android:attr/colorPrimary</item>
|
<item name="android:textColor">?android:attr/colorPrimary</item>
|
||||||
|
@ -30,12 +30,9 @@
|
||||||
<item name="android:fontFamily">@font/inter_semibold</item>
|
<item name="android:fontFamily">@font/inter_semibold</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- Custom popup theme -->
|
||||||
<style name="AppThemeOverlay.Popup" parent="ThemeOverlay.AppCompat.DayNight">
|
<style name="AppThemeOverlay.Popup" parent="ThemeOverlay.AppCompat.DayNight">
|
||||||
<item name="android:colorBackground">@color/background</item>
|
<item name="android:colorBackground">@color/background</item>
|
||||||
<item name="colorControlHighlight">@color/selection_color</item>
|
<item name="colorControlHighlight">@color/selection_color</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.BottomSheet" parent="Theme.Design.BottomSheetDialog">
|
|
||||||
<item name="android:colorPrimary">?android:attr/colorPrimary</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue