home: re-add sorting
Re-add sorting to HomeFragment, except heavily improved. The major improvement here is the addition of song sorting, which was a heavily requested feature judging by #16. The setting does not save yet and is not present in the detail fragments, but it is still a major milestone for the new home ui.
This commit is contained in:
parent
0fc8f1cd02
commit
dae334b1d6
39 changed files with 371 additions and 143 deletions
|
@ -32,6 +32,9 @@ import org.oxycblt.auxio.ui.ArtistViewHolder
|
|||
import org.oxycblt.auxio.ui.GenreViewHolder
|
||||
import org.oxycblt.auxio.ui.SongViewHolder
|
||||
|
||||
/**
|
||||
* A universal adapter for displaying data in [HomeFragment].
|
||||
*/
|
||||
class HomeAdapter(
|
||||
private val doOnClick: (data: BaseModel) -> Unit,
|
||||
private val doOnLongClick: (view: View, data: BaseModel) -> Unit
|
||||
|
@ -89,6 +92,9 @@ class HomeAdapter(
|
|||
fun updateData(newData: List<BaseModel>) {
|
||||
data = newData
|
||||
|
||||
// I would use ListAdapter instead of this inefficient invalidate call, but they still
|
||||
// haven't fixed the issue where ListAdapter's calculations will cause wild scrolling
|
||||
// for basically no reason.
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,10 @@ package org.oxycblt.auxio.home
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.iterator
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
|
@ -48,13 +50,9 @@ import org.oxycblt.auxio.util.makeScrollingViewFade
|
|||
/**
|
||||
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
|
||||
* views for each respective fragment.
|
||||
* TODO: Re-add sorting (but new and improved)
|
||||
* It will require a new SortMode to be made simply for compat. Migrate the old SortMode
|
||||
* eventually.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class HomeFragment : Fragment() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
private val homeModel: HomeViewModel by activityViewModels()
|
||||
|
||||
|
@ -64,6 +62,7 @@ class HomeFragment : Fragment() {
|
|||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val binding = FragmentHomeBinding.inflate(inflater)
|
||||
val sortItem: MenuItem
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
|
@ -75,7 +74,8 @@ class HomeFragment : Fragment() {
|
|||
|
||||
binding.homeAppbar.makeScrollingViewFade(binding.homeToolbar)
|
||||
|
||||
binding.homeToolbar.setOnMenuItemClickListener { item ->
|
||||
binding.homeToolbar.apply {
|
||||
setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
R.id.action_settings -> {
|
||||
parentFragment?.parentFragment?.findNavController()?.navigate(
|
||||
|
@ -86,11 +86,25 @@ class HomeFragment : Fragment() {
|
|||
R.id.action_search -> {
|
||||
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
|
||||
}
|
||||
|
||||
R.id.submenu_sorting -> { }
|
||||
|
||||
// Sorting option was selected, check then and update the mode
|
||||
else -> {
|
||||
item.isChecked = true
|
||||
|
||||
homeModel.updateCurrentSort(
|
||||
requireNotNull(LibSortMode.fromId(item.itemId))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
sortItem = menu.findItem(R.id.submenu_sorting)
|
||||
}
|
||||
|
||||
binding.homePager.apply {
|
||||
adapter = HomePagerAdapter()
|
||||
|
||||
|
@ -121,24 +135,13 @@ class HomeFragment : Fragment() {
|
|||
// page transitions.
|
||||
offscreenPageLimit = homeModel.tabs.value!!.size
|
||||
|
||||
// ViewPager2 tends to garble any scrolling view events that occur within it's
|
||||
// fragments, so we fix that by instructing our AppBarLayout to follow the specific
|
||||
// view we have just selected.
|
||||
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
binding.homeAppbar.liftOnScrollTargetViewId =
|
||||
when (homeModel.tabs.value!![position]) {
|
||||
DisplayMode.SHOW_SONGS -> R.id.home_song_list
|
||||
DisplayMode.SHOW_ALBUMS -> R.id.home_album_list
|
||||
DisplayMode.SHOW_ARTISTS -> R.id.home_artist_list
|
||||
DisplayMode.SHOW_GENRES -> R.id.home_genre_list
|
||||
}
|
||||
}
|
||||
override fun onPageSelected(position: Int) = homeModel.updateCurrentTab(position)
|
||||
})
|
||||
}
|
||||
|
||||
TabLayoutMediator(binding.homeTabs, binding.homePager) { tab, pos ->
|
||||
val labelRes = when (requireNotNull(homeModel.tabs.value)[pos]) {
|
||||
val labelRes = when (homeModel.tabs.value!![pos]) {
|
||||
DisplayMode.SHOW_SONGS -> R.string.lbl_songs
|
||||
DisplayMode.SHOW_ALBUMS -> R.string.lbl_albums
|
||||
DisplayMode.SHOW_ARTISTS -> R.string.lbl_artists
|
||||
|
@ -150,6 +153,40 @@ class HomeFragment : Fragment() {
|
|||
|
||||
// --- VIEWMODEL SETUP ---
|
||||
|
||||
homeModel.curTab.observe(viewLifecycleOwner) { tab ->
|
||||
binding.homeAppbar.liftOnScrollTargetViewId = when (requireNotNull(tab)) {
|
||||
DisplayMode.SHOW_SONGS -> {
|
||||
updateSortMenu(sortItem, homeModel.songSortMode)
|
||||
|
||||
R.id.home_song_list
|
||||
}
|
||||
|
||||
DisplayMode.SHOW_ALBUMS -> {
|
||||
updateSortMenu(sortItem, homeModel.albumSortMode) { id ->
|
||||
id != R.id.option_sort_album
|
||||
}
|
||||
|
||||
R.id.home_album_list
|
||||
}
|
||||
|
||||
DisplayMode.SHOW_ARTISTS -> {
|
||||
updateSortMenu(sortItem, homeModel.artistSortMode) { id ->
|
||||
id == R.id.option_sort_asc || id == R.id.option_sort_dsc
|
||||
}
|
||||
|
||||
R.id.home_artist_list
|
||||
}
|
||||
|
||||
DisplayMode.SHOW_GENRES -> {
|
||||
updateSortMenu(sortItem, homeModel.genreSortMode) { id ->
|
||||
id == R.id.option_sort_asc || id == R.id.option_sort_dsc
|
||||
}
|
||||
|
||||
R.id.home_genre_list
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||
// The AppBarLayout gets confused and collapses when we navigate too fast, wait for it
|
||||
// to draw before we continue.
|
||||
|
@ -182,10 +219,24 @@ class HomeFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
private fun updateSortMenu(
|
||||
item: MenuItem,
|
||||
toHighlight: LibSortMode,
|
||||
isVisible: (Int) -> Boolean = { true }
|
||||
) {
|
||||
for (option in item.subMenu) {
|
||||
if (option.itemId == toHighlight.itemId) {
|
||||
option.isChecked = true
|
||||
}
|
||||
|
||||
option.isVisible = isVisible(option.itemId)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class HomePagerAdapter :
|
||||
FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
|
||||
|
||||
override fun getItemCount(): Int = requireNotNull(homeModel.tabs.value).size
|
||||
override fun getItemCount(): Int = homeModel.tabs.value!!.size
|
||||
override fun createFragment(position: Int): Fragment = HomeListFragment.new(position)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
|
@ -38,7 +38,6 @@ import org.oxycblt.auxio.music.Song
|
|||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.newMenu
|
||||
import org.oxycblt.auxio.ui.sliceArticle
|
||||
import org.oxycblt.auxio.util.applySpans
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
|
@ -47,8 +46,8 @@ import org.oxycblt.auxio.util.logD
|
|||
* should be created using the [new] method with it's position in the ViewPager.
|
||||
*/
|
||||
class HomeListFragment : Fragment() {
|
||||
private val homeModel: HomeViewModel by viewModels()
|
||||
private val playbackModel: PlaybackViewModel by viewModels()
|
||||
private val homeModel: HomeViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
@ -81,36 +80,36 @@ class HomeListFragment : Fragment() {
|
|||
::newMenu
|
||||
)
|
||||
|
||||
// --- ITEM SETUP ---
|
||||
|
||||
// Get some tab-specific values before we go ahead. More specifically, the data to use
|
||||
// and the unique ID that HomeFragment's AppBarLayout uses to determine lift state.
|
||||
val pos = requireNotNull(arguments).getInt(ARG_POS)
|
||||
|
||||
@IdRes val customId: Int
|
||||
val toObserve: LiveData<out List<BaseModel>>
|
||||
val homeData: LiveData<out List<BaseModel>>
|
||||
|
||||
when (requireNotNull(homeModel.tabs.value)[pos]) {
|
||||
when (homeModel.tabs.value!![pos]) {
|
||||
DisplayMode.SHOW_SONGS -> {
|
||||
customId = R.id.home_song_list
|
||||
toObserve = homeModel.songs
|
||||
homeData = homeModel.songs
|
||||
}
|
||||
DisplayMode.SHOW_ALBUMS -> {
|
||||
customId = R.id.home_album_list
|
||||
toObserve = homeModel.albums
|
||||
homeData = homeModel.albums
|
||||
}
|
||||
DisplayMode.SHOW_ARTISTS -> {
|
||||
customId = R.id.home_artist_list
|
||||
toObserve = homeModel.artists
|
||||
homeData = homeModel.artists
|
||||
}
|
||||
DisplayMode.SHOW_GENRES -> {
|
||||
customId = R.id.home_genre_list
|
||||
toObserve = homeModel.genres
|
||||
homeData = homeModel.genres
|
||||
}
|
||||
}
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
|
||||
binding.homeRecycler.apply {
|
||||
id = customId
|
||||
adapter = homeAdapter
|
||||
|
@ -121,16 +120,8 @@ class HomeListFragment : Fragment() {
|
|||
// --- VIEWMODEL SETUP ---
|
||||
|
||||
// Make sure that this RecyclerView has data before startup
|
||||
homeAdapter.updateData(toObserve.value!!)
|
||||
|
||||
toObserve.observe(viewLifecycleOwner) { data ->
|
||||
homeAdapter.updateData(
|
||||
data.sortedWith(
|
||||
compareBy(String.CASE_INSENSITIVE_ORDER) {
|
||||
it.name.sliceArticle()
|
||||
}
|
||||
)
|
||||
)
|
||||
homeData.observe(viewLifecycleOwner) { data ->
|
||||
homeAdapter.updateData(data)
|
||||
}
|
||||
|
||||
logD("Fragment created")
|
||||
|
|
|
@ -28,32 +28,85 @@ import org.oxycblt.auxio.music.MusicStore
|
|||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
|
||||
/**
|
||||
* The ViewModel for managing [HomeFragment]'s data and sorting modes.
|
||||
*/
|
||||
class HomeViewModel : ViewModel() {
|
||||
private val mGenres = MutableLiveData(listOf<Genre>())
|
||||
val genres: LiveData<List<Genre>> get() = mGenres
|
||||
|
||||
private val mArtists = MutableLiveData(listOf<Artist>())
|
||||
val artists: LiveData<List<Artist>> get() = mArtists
|
||||
private val mSongs = MutableLiveData(listOf<Song>())
|
||||
val songs: LiveData<List<Song>> get() = mSongs
|
||||
|
||||
private val mAlbums = MutableLiveData(listOf<Album>())
|
||||
val albums: LiveData<List<Album>> get() = mAlbums
|
||||
|
||||
private val mSongs = MutableLiveData(listOf<Song>())
|
||||
val songs: LiveData<List<Song>> get() = mSongs
|
||||
private val mArtists = MutableLiveData(listOf<Artist>())
|
||||
val artists: LiveData<List<Artist>> get() = mArtists
|
||||
|
||||
private val mTabs = MutableLiveData(arrayOf<DisplayMode>())
|
||||
private val mGenres = MutableLiveData(listOf<Genre>())
|
||||
val genres: LiveData<List<Genre>> get() = mGenres
|
||||
|
||||
private val mTabs = MutableLiveData(
|
||||
arrayOf(
|
||||
DisplayMode.SHOW_SONGS, DisplayMode.SHOW_ALBUMS,
|
||||
DisplayMode.SHOW_ARTISTS, DisplayMode.SHOW_GENRES
|
||||
)
|
||||
)
|
||||
val tabs: LiveData<Array<DisplayMode>> = mTabs
|
||||
|
||||
private val mCurTab = MutableLiveData(mTabs.value!![0])
|
||||
val curTab: LiveData<DisplayMode> = mCurTab
|
||||
|
||||
var genreSortMode = LibSortMode.ASCENDING
|
||||
private set
|
||||
|
||||
var artistSortMode = LibSortMode.ASCENDING
|
||||
private set
|
||||
|
||||
var albumSortMode = LibSortMode.ASCENDING
|
||||
private set
|
||||
|
||||
var songSortMode = LibSortMode.ASCENDING
|
||||
private set
|
||||
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
|
||||
init {
|
||||
mGenres.value = musicStore.genres
|
||||
mArtists.value = musicStore.artists
|
||||
mAlbums.value = musicStore.albums
|
||||
mSongs.value = musicStore.songs
|
||||
mTabs.value = arrayOf(
|
||||
DisplayMode.SHOW_SONGS, DisplayMode.SHOW_ALBUMS,
|
||||
DisplayMode.SHOW_ARTISTS, DisplayMode.SHOW_GENRES
|
||||
)
|
||||
mSongs.value = songSortMode.sortSongs(musicStore.songs)
|
||||
mAlbums.value = albumSortMode.sortAlbums(musicStore.albums)
|
||||
mArtists.value = artistSortMode.sortModels(musicStore.artists)
|
||||
mGenres.value = genreSortMode.sortModels(musicStore.genres)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current tab based off of the new ViewPager position.
|
||||
*/
|
||||
fun updateCurrentTab(pos: Int) {
|
||||
val mode = mTabs.value!![pos]
|
||||
|
||||
mCurTab.value = mode
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the currently displayed item's [LibSortMode].
|
||||
*/
|
||||
fun updateCurrentSort(sort: LibSortMode) {
|
||||
when (mCurTab.value) {
|
||||
DisplayMode.SHOW_SONGS -> {
|
||||
songSortMode = sort
|
||||
mSongs.value = sort.sortSongs(mSongs.value!!)
|
||||
}
|
||||
|
||||
DisplayMode.SHOW_ALBUMS -> {
|
||||
albumSortMode = sort
|
||||
mAlbums.value = sort.sortAlbums(mAlbums.value!!)
|
||||
}
|
||||
DisplayMode.SHOW_ARTISTS -> {
|
||||
artistSortMode = sort
|
||||
mArtists.value = sort.sortModels(mArtists.value!!)
|
||||
}
|
||||
DisplayMode.SHOW_GENRES -> {
|
||||
genreSortMode = sort
|
||||
mGenres.value = sort.sortModels(mGenres.value!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
148
app/src/main/java/org/oxycblt/auxio/home/LibSortMode.kt
Normal file
148
app/src/main/java/org/oxycblt/auxio/home/LibSortMode.kt
Normal file
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Auxio Project
|
||||
* LibSortMode.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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.home
|
||||
|
||||
import androidx.annotation.IdRes
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.BaseModel
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.ui.sliceArticle
|
||||
|
||||
/**
|
||||
* The enum for the current sort state.
|
||||
* This enum is semantic depending on the context it is used. Documentation describing each
|
||||
* sorting functions behavior can be found in the function definition.
|
||||
* @param itemId Menu ID associated with this enum
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
enum class LibSortMode(@IdRes val itemId: Int) {
|
||||
ASCENDING(R.id.option_sort_asc),
|
||||
DESCENDING(R.id.option_sort_dsc),
|
||||
ARTIST(R.id.option_sort_artist),
|
||||
ALBUM(R.id.option_sort_album),
|
||||
YEAR(R.id.option_sort_year);
|
||||
|
||||
/**
|
||||
* Sort a list of songs.
|
||||
*
|
||||
* **Behavior:**
|
||||
* - [ASCENDING] & [DESCENDING]: See [sortModels]
|
||||
* - [ARTIST]: Grouped by album and then sorted [ASCENDING] based off the artist name.
|
||||
* - [ALBUM]: Grouped by album and sorted [ASCENDING]
|
||||
* - [YEAR]: Grouped by album and sorted by year
|
||||
*
|
||||
* The grouping mode for songs in an album will be by track, [ASCENDING].
|
||||
* @see sortAlbums
|
||||
*/
|
||||
fun sortSongs(songs: Collection<Song>): List<Song> {
|
||||
return when (this) {
|
||||
ASCENDING, DESCENDING -> sortModels(songs)
|
||||
|
||||
else -> sortAlbums(songs.groupBy { it.album }.keys).flatMap { album ->
|
||||
ASCENDING.sortAlbum(album)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of albums.
|
||||
*
|
||||
* **Behavior:**
|
||||
* - [ASCENDING] & [DESCENDING]: See [sortModels]
|
||||
* - [ARTIST]: Grouped by artist and sorted [ASCENDING]
|
||||
* - [ALBUM]: [ASCENDING]
|
||||
* - [YEAR]: Sorted by year
|
||||
*
|
||||
* The grouping mode for albums in an artist will be [YEAR].
|
||||
*/
|
||||
fun sortAlbums(albums: Collection<Album>): List<Album> {
|
||||
return when (this) {
|
||||
ASCENDING, DESCENDING -> sortModels(albums)
|
||||
|
||||
ARTIST -> ASCENDING.sortModels(albums.groupBy { it.artist }.keys)
|
||||
.flatMap { YEAR.sortAlbums(it.albums) }
|
||||
|
||||
ALBUM -> ASCENDING.sortModels(albums)
|
||||
|
||||
YEAR -> albums.sortedByDescending { it.year }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort a list of generic [BaseModel] instances.
|
||||
*
|
||||
* **Behavior:**
|
||||
* - [ASCENDING]: Sorted by name, ascending
|
||||
* - [DESCENDING]: Sorted by name, descending
|
||||
* - Same list is returned otherwise.
|
||||
*
|
||||
* Names will be treated as case-insensitive. Articles like "the" and "a" will be skipped
|
||||
* to line up with MediaStore behavior.
|
||||
*/
|
||||
fun <T : BaseModel> sortModels(models: Collection<T>): List<T> {
|
||||
return when (this) {
|
||||
ASCENDING -> models.sortedWith(
|
||||
compareBy(String.CASE_INSENSITIVE_ORDER) { model ->
|
||||
model.name.sliceArticle()
|
||||
}
|
||||
)
|
||||
|
||||
DESCENDING -> models.sortedWith(
|
||||
compareByDescending(String.CASE_INSENSITIVE_ORDER) { model ->
|
||||
model.name.sliceArticle()
|
||||
}
|
||||
)
|
||||
|
||||
else -> models.toList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the songs in an album.
|
||||
*
|
||||
* **Behavior:**
|
||||
* - [ASCENDING]: By track, ascending
|
||||
* - [DESCENDING]: By track, descending
|
||||
* - Same song list is returned otherwise.
|
||||
*/
|
||||
fun sortAlbum(album: Album): List<Song> {
|
||||
return when (this) {
|
||||
ASCENDING -> album.songs.sortedBy { it.track }
|
||||
DESCENDING -> album.songs.sortedByDescending { it.track }
|
||||
else -> album.songs
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Convert a menu [id] to an instance of [LibSortMode].
|
||||
*/
|
||||
fun fromId(@IdRes id: Int): LibSortMode? {
|
||||
return when (id) {
|
||||
ASCENDING.itemId -> ASCENDING
|
||||
DESCENDING.itemId -> DESCENDING
|
||||
ARTIST.itemId -> ARTIST
|
||||
ALBUM.itemId -> ALBUM
|
||||
YEAR.itemId -> YEAR
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -149,7 +149,7 @@ class PlaybackNotification private constructor(
|
|||
loopMode: LoopMode
|
||||
): NotificationCompat.Action {
|
||||
val drawableRes = when (loopMode) {
|
||||
LoopMode.NONE -> R.drawable.ic_loop_inactive
|
||||
LoopMode.NONE -> R.drawable.ic_loop_off
|
||||
LoopMode.ALL -> R.drawable.ic_loop
|
||||
LoopMode.TRACK -> R.drawable.ic_loop_one
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ class PlaybackNotification private constructor(
|
|||
context: Context,
|
||||
isShuffled: Boolean
|
||||
): NotificationCompat.Action {
|
||||
val drawableRes = if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_shuffle_inactive
|
||||
val drawableRes = if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_shuffle_off
|
||||
|
||||
return buildAction(context, PlaybackService.ACTION_SHUFFLE, drawableRes)
|
||||
}
|
||||
|
|
|
@ -29,8 +29,7 @@ import org.oxycblt.auxio.music.Genre
|
|||
import org.oxycblt.auxio.music.Song
|
||||
|
||||
/**
|
||||
* An enum for the current sorting mode. Contains helper functions to sort lists based
|
||||
* off the given sorting mode.
|
||||
* The legacy enum for sorting. This is set to be removed soon.
|
||||
* @property iconRes The icon for this [SortMode]
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
|
@ -190,10 +189,9 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
|
|||
@IdRes
|
||||
fun toMenuId(): Int {
|
||||
return when (this) {
|
||||
ALPHA_UP -> R.id.option_sort_alpha_up
|
||||
ALPHA_DOWN -> R.id.option_sort_alpha_down
|
||||
|
||||
else -> R.id.option_sort_alpha_up
|
||||
ALPHA_UP -> R.id.option_sort_asc
|
||||
ALPHA_DOWN -> R.id.option_sort_dsc
|
||||
else -> R.id.option_sort_dsc
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,11 +27,9 @@ import android.util.TypedValue
|
|||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.widget.ImageButton
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -39,9 +37,6 @@ import androidx.viewbinding.ViewBinding
|
|||
import com.google.android.material.appbar.AppBarLayout
|
||||
import org.oxycblt.auxio.R
|
||||
|
||||
// TODO: Make a helper AppBarLayout of some kind that auto-updates the lifted state. I know
|
||||
// what to do, it's just hard to make it work correctly.
|
||||
|
||||
/**
|
||||
* Apply the recommended spans for a [RecyclerView].
|
||||
*
|
||||
|
@ -72,6 +67,7 @@ fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) {
|
|||
|
||||
/**
|
||||
* Disable an image button.
|
||||
* TODO: Replace this fragile function with something else.
|
||||
*/
|
||||
fun ImageButton.disable() {
|
||||
if (isEnabled) {
|
||||
|
@ -79,14 +75,6 @@ fun ImageButton.disable() {
|
|||
isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a [TextView] text color, without having to resolve the resource.
|
||||
*/
|
||||
fun TextView.setTextColorResource(@ColorRes color: Int) {
|
||||
setTextColor(color.resolveColor(context))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a recyclerview can scroll.
|
||||
*/
|
||||
|
@ -116,20 +104,14 @@ fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
|
|||
* @see resolveColor
|
||||
*/
|
||||
fun @receiver:ColorRes Int.resolveStateList(context: Context) =
|
||||
ColorStateList.valueOf(resolveColor(context))
|
||||
|
||||
/**
|
||||
* Resolve a drawable resource into a [Drawable]
|
||||
*/
|
||||
fun @receiver:DrawableRes Int.resolveDrawable(context: Context) =
|
||||
requireNotNull(ContextCompat.getDrawable(context, this))
|
||||
ContextCompat.getColorStateList(context, this)
|
||||
|
||||
/**
|
||||
* Resolve this int into a color as if it was an attribute
|
||||
*/
|
||||
@ColorInt
|
||||
fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
|
||||
// Convert the attribute into its color
|
||||
// First resolve the attribute into its ID
|
||||
val resolvedAttr = TypedValue()
|
||||
context.theme.resolveAttribute(this, resolvedAttr, true)
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ fun createFullWidget(context: Context, state: WidgetState): RemoteViews {
|
|||
// And no, we can't control state drawables with RemoteViews. Because of course we can't.
|
||||
|
||||
val shuffleRes = when {
|
||||
state.isShuffled -> R.drawable.ic_shuffle_tinted
|
||||
state.isShuffled -> R.drawable.ic_shuffle_on
|
||||
else -> R.drawable.ic_shuffle
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
<ImageButton
|
||||
android:id="@+id/playback_play_pause"
|
||||
style="@style/Widget.Button.Unbounded"
|
||||
android:src="@drawable/ic_playing_state"
|
||||
android:src="@drawable/sel_playing_state"
|
||||
android:layout_margin="@dimen/spacing_small"
|
||||
android:contentDescription="@string/desc_play_pause"
|
||||
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
android:id="@+id/widget_play_pause"
|
||||
style="@style/Widget.Component.AppWidget.Button"
|
||||
android:contentDescription="@string/desc_play_pause"
|
||||
android:src="@drawable/ic_playing_state" />
|
||||
android:src="@drawable/sel_playing_state" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/widget_skip_next"
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
<menu>
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/option_sort_alpha_down"
|
||||
android:title="@string/lbl_sort_alpha_down" />
|
||||
android:id="@+id/option_sort_asc"
|
||||
android:title="@string/lbl_sort_asc" />
|
||||
<item
|
||||
android:id="@+id/option_sort_alpha_up"
|
||||
android:title="@string/lbl_sort_alpha_up" />
|
||||
android:id="@+id/option_sort_dsc"
|
||||
android:title="@string/lbl_sort_dsc" />
|
||||
<item
|
||||
android:id="@+id/option_sort_artist"
|
||||
android:title="@string/lbl_sort_artist" />
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
<string name="lbl_filter">"Filtr"</string>
|
||||
<string name="lbl_filter_all">"Vše"</string>
|
||||
<string name="lbl_sort">"Řadit"</string>
|
||||
<string name="lbl_sort_alpha_down">"Vzestupně"</string>
|
||||
<string name="lbl_sort_alpha_up">"Sestupně"</string>
|
||||
<string name="lbl_sort_asc">"Vzestupně"</string>
|
||||
<string name="lbl_sort_dsc">"Sestupně"</string>
|
||||
<string name="lbl_sort_artist">"Umělec"</string>
|
||||
<string name="lbl_sort_album">"Album"</string>
|
||||
<string name="lbl_sort_year">"Rok"</string>
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
<string name="lbl_filter_all">Alles</string>
|
||||
|
||||
<string name="lbl_sort">Sortierung</string>
|
||||
<string name="lbl_sort_alpha_down">Aufsteigend</string>
|
||||
<string name="lbl_sort_alpha_up">Absteigend</string>
|
||||
<string name="lbl_sort_asc">Aufsteigend</string>
|
||||
<string name="lbl_sort_dsc">Absteigend</string>
|
||||
|
||||
<string name="lbl_play">Abspielen</string>
|
||||
<string name="lbl_shuffle">Zufällig</string>
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
<string name="lbl_filter_all">Todo</string>
|
||||
|
||||
<string name="lbl_sort">Ordenar</string>
|
||||
<string name="lbl_sort_alpha_down">Ascendente</string>
|
||||
<string name="lbl_sort_alpha_up">Descendente</string>
|
||||
<string name="lbl_sort_asc">Ascendente</string>
|
||||
<string name="lbl_sort_dsc">Descendente</string>
|
||||
|
||||
<string name="lbl_play">Reproducir</string>
|
||||
<string name="lbl_shuffle">Aleatorio</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">Tout</string>
|
||||
|
||||
<string name="lbl_sort">Tri</string>
|
||||
<string name="lbl_sort_alpha_down">Ascendant</string>
|
||||
<string name="lbl_sort_alpha_up">Descendant</string>
|
||||
<string name="lbl_sort_asc">Ascendant</string>
|
||||
<string name="lbl_sort_dsc">Descendant</string>
|
||||
|
||||
<string name="lbl_play">Lecture</string>
|
||||
<string name="lbl_shuffle">Aléatoire</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">Összes</string>
|
||||
|
||||
<string name="lbl_sort">Összes</string>
|
||||
<string name="lbl_sort_alpha_down">Növekvő</string>
|
||||
<string name="lbl_sort_alpha_up">Csökkenő</string>
|
||||
<string name="lbl_sort_asc">Növekvő</string>
|
||||
<string name="lbl_sort_dsc">Csökkenő</string>
|
||||
|
||||
<string name="lbl_play">Lejátszás</string>
|
||||
<string name="lbl_shuffle">Keverés</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">Semua</string>
|
||||
|
||||
<string name="lbl_sort">Urutan</string>
|
||||
<string name="lbl_sort_alpha_down">Naik</string>
|
||||
<string name="lbl_sort_alpha_up">Turun</string>
|
||||
<string name="lbl_sort_asc">Naik</string>
|
||||
<string name="lbl_sort_dsc">Turun</string>
|
||||
|
||||
<string name="lbl_play">Putar</string>
|
||||
<string name="lbl_shuffle">Acak</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">Tutto</string>
|
||||
|
||||
<string name="lbl_sort">Ordine</string>
|
||||
<string name="lbl_sort_alpha_down">Ascendente</string>
|
||||
<string name="lbl_sort_alpha_up">Discendente</string>
|
||||
<string name="lbl_sort_asc">Ascendente</string>
|
||||
<string name="lbl_sort_dsc">Discendente</string>
|
||||
|
||||
<string name="lbl_play">Riproduci</string>
|
||||
<string name="lbl_shuffle">Casuale</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">전부</string>
|
||||
|
||||
<string name="lbl_sort">분류</string>
|
||||
<string name="lbl_sort_alpha_down">오름차순</string>
|
||||
<string name="lbl_sort_alpha_up">내림차순</string>
|
||||
<string name="lbl_sort_asc">오름차순</string>
|
||||
<string name="lbl_sort_dsc">내림차순</string>
|
||||
|
||||
<string name="lbl_play">재생</string>
|
||||
<string name="lbl_shuffle">모든 곡 랜덤 재생</string>
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
<string name="lbl_filter_all">Alles</string>
|
||||
|
||||
<string name="lbl_sort">Sorteren</string>
|
||||
<string name="lbl_sort_alpha_down">Oplopend</string>
|
||||
<string name="lbl_sort_alpha_up">Aflopend</string>
|
||||
<string name="lbl_sort_asc">Oplopend</string>
|
||||
<string name="lbl_sort_dsc">Aflopend</string>
|
||||
|
||||
<string name="lbl_play">Afspelen</string>
|
||||
<string name="lbl_shuffle">Shuffle</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">Wszystkie</string>
|
||||
|
||||
<string name="lbl_sort">Sortowanie</string>
|
||||
<string name="lbl_sort_alpha_down">Rosnąco</string>
|
||||
<string name="lbl_sort_alpha_up">Malejąco</string>
|
||||
<string name="lbl_sort_asc">Rosnąco</string>
|
||||
<string name="lbl_sort_dsc">Malejąco</string>
|
||||
|
||||
<string name="lbl_play">Graj</string>
|
||||
<string name="lbl_shuffle">Losowo</string>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<string name="lbl_filter_all">Tudo</string>
|
||||
|
||||
<string name="lbl_sort">Classificação</string>
|
||||
<string name="lbl_sort_alpha_up">Descendente</string>
|
||||
<string name="lbl_sort_dsc">Descendente</string>
|
||||
|
||||
<string name="lbl_play">Reproduzir</string>
|
||||
<string name="lbl_shuffle">Embaralhar</string>
|
||||
|
@ -98,5 +98,5 @@
|
|||
<item quantity="one">%d Álbum</item>
|
||||
<item quantity="other">%d Álbuns</item>
|
||||
</plurals>
|
||||
<string name="lbl_sort_alpha_down">Ascendente</string>
|
||||
<string name="lbl_sort_asc">Ascendente</string>
|
||||
</resources>
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">Tudo</string>
|
||||
|
||||
<string name="lbl_sort">Classificação</string>
|
||||
<string name="lbl_sort_alpha_down">Ascendente</string>
|
||||
<string name="lbl_sort_alpha_up">Descendente</string>
|
||||
<string name="lbl_sort_asc">Ascendente</string>
|
||||
<string name="lbl_sort_dsc">Descendente</string>
|
||||
|
||||
<string name="lbl_play">Reproduzir</string>
|
||||
<string name="lbl_shuffle">Embaralhar</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">Tot</string>
|
||||
|
||||
<string name="lbl_sort">Sortare</string>
|
||||
<string name="lbl_sort_alpha_down">Crescător</string>
|
||||
<string name="lbl_sort_alpha_up">Descrescător</string>
|
||||
<string name="lbl_sort_asc">Crescător</string>
|
||||
<string name="lbl_sort_dsc">Descrescător</string>
|
||||
|
||||
<string name="lbl_play">Redă</string>
|
||||
<string name="lbl_shuffle">Amestecare</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">Всё</string>
|
||||
|
||||
<string name="lbl_sort">Сортировка</string>
|
||||
<string name="lbl_sort_alpha_down">По возрастанию</string>
|
||||
<string name="lbl_sort_alpha_up">По убыванию</string>
|
||||
<string name="lbl_sort_asc">По возрастанию</string>
|
||||
<string name="lbl_sort_dsc">По убыванию</string>
|
||||
|
||||
<string name="lbl_play">Воспроизвести</string>
|
||||
<string name="lbl_shuffle">Перемешать</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">Tümü</string>
|
||||
|
||||
<string name="lbl_sort">Sıralama</string>
|
||||
<string name="lbl_sort_alpha_down">Artan</string>
|
||||
<string name="lbl_sort_alpha_up">Azalan</string>
|
||||
<string name="lbl_sort_asc">Artan</string>
|
||||
<string name="lbl_sort_dsc">Azalan</string>
|
||||
|
||||
<string name="lbl_play">Başlat</string>
|
||||
<string name="lbl_shuffle">Karıştır</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">全部</string>
|
||||
|
||||
<string name="lbl_sort">排序方式</string>
|
||||
<string name="lbl_sort_alpha_down">按首字符(正序)</string>
|
||||
<string name="lbl_sort_alpha_up">按首字符(倒序)</string>
|
||||
<string name="lbl_sort_asc">按首字符(正序)</string>
|
||||
<string name="lbl_sort_dsc">按首字符(倒序)</string>
|
||||
|
||||
<string name="lbl_play">播放</string>
|
||||
<string name="lbl_shuffle">随机播放</string>
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<string name="lbl_filter_all">全部</string>
|
||||
|
||||
<string name="lbl_sort">排序</string>
|
||||
<string name="lbl_sort_alpha_down">升序排列</string>
|
||||
<string name="lbl_sort_alpha_up">降序排列</string>
|
||||
<string name="lbl_sort_asc">升序排列</string>
|
||||
<string name="lbl_sort_dsc">降序排列</string>
|
||||
|
||||
<string name="lbl_play">播放</string>
|
||||
<string name="lbl_shuffle">隨機播放</string>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="surface">#fafafa</color>
|
||||
<color name="surface_black">@android:color/black</color>
|
||||
<color name="control">#202020</color>
|
||||
<color name="nav_bar">#01fafafa</color>
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
<string name="lbl_filter_all">All</string>
|
||||
|
||||
<string name="lbl_sort">Sort</string>
|
||||
<string name="lbl_sort_alpha_down">Ascending</string>
|
||||
<string name="lbl_sort_alpha_up">Descending</string>
|
||||
<string name="lbl_sort_asc">Ascending</string>
|
||||
<string name="lbl_sort_dsc">Descending</string>
|
||||
<string name="lbl_sort_artist">Artist</string>
|
||||
<string name="lbl_sort_album">Album</string>
|
||||
<string name="lbl_sort_year">Year</string>
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
<!-- Black theme dialog theme -->
|
||||
<style name="Theme.CustomDialog.Black" parent="Theme.CustomDialog.Base">
|
||||
<item name="colorSurface">@color/surface_black</item>
|
||||
<item name="colorSurface">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<!-- Material-specific dialog style -->
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<item name="android:elevation">@dimen/elevation_normal</item>
|
||||
<item name="android:contentDescription">@string/desc_play_pause</item>
|
||||
<item name="android:tint">?attr/colorSurface</item>
|
||||
<item name="android:src">@drawable/ic_playing_state</item>
|
||||
<item name="android:src">@drawable/sel_playing_state</item>
|
||||
<item name="android:layout_marginStart">@dimen/spacing_large</item>
|
||||
<item name="android:layout_marginTop">@dimen/spacing_medium</item>
|
||||
<item name="android:layout_marginEnd">@dimen/spacing_large</item>
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
<!-- The basic black theme derived in all black accents. -->
|
||||
<style name="Theme.Base.Black" parent="Theme.Base">
|
||||
<item name="colorSurface">@color/surface_black</item>
|
||||
<item name="colorSurface">@android:color/black</item>
|
||||
<item name="materialAlertDialogTheme">@style/Theme.CustomDialog.Black</item>
|
||||
</style>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.1'
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
|
||||
|
||||
|
|
Loading…
Reference in a new issue