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:
OxygenCobalt 2021-09-05 16:11:37 -06:00
parent 0fc8f1cd02
commit dae334b1d6
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
39 changed files with 371 additions and 143 deletions

View file

@ -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()
}
}

View file

@ -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,20 +74,35 @@ class HomeFragment : Fragment() {
binding.homeAppbar.makeScrollingViewFade(binding.homeToolbar)
binding.homeToolbar.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_settings -> {
parentFragment?.parentFragment?.findNavController()?.navigate(
MainFragmentDirections.actionShowSettings()
)
binding.homeToolbar.apply {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_settings -> {
parentFragment?.parentFragment?.findNavController()?.navigate(
MainFragmentDirections.actionShowSettings()
)
}
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))
)
}
}
R.id.action_search -> {
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
}
true
}
true
sortItem = menu.findItem(R.id.submenu_sorting)
}
binding.homePager.apply {
@ -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)
}
}

View file

@ -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")

View file

@ -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!!)
}
}
}
}

View 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
}
}
}
}

View file

@ -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)
}

View file

@ -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
}
}

View file

@ -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)

View file

@ -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
}

View file

@ -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()}"

View file

@ -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"

View file

@ -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" />

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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 -->

View file

@ -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>

View file

@ -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>

View file

@ -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"