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.GenreViewHolder
|
||||||
import org.oxycblt.auxio.ui.SongViewHolder
|
import org.oxycblt.auxio.ui.SongViewHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A universal adapter for displaying data in [HomeFragment].
|
||||||
|
*/
|
||||||
class HomeAdapter(
|
class HomeAdapter(
|
||||||
private val doOnClick: (data: BaseModel) -> Unit,
|
private val doOnClick: (data: BaseModel) -> Unit,
|
||||||
private val doOnLongClick: (view: View, data: BaseModel) -> Unit
|
private val doOnLongClick: (view: View, data: BaseModel) -> Unit
|
||||||
|
@ -89,6 +92,9 @@ class HomeAdapter(
|
||||||
fun updateData(newData: List<BaseModel>) {
|
fun updateData(newData: List<BaseModel>) {
|
||||||
data = newData
|
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()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,10 @@ package org.oxycblt.auxio.home
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.iterator
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
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
|
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
|
||||||
* views for each respective fragment.
|
* 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
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
|
||||||
private val detailModel: DetailViewModel by activityViewModels()
|
private val detailModel: DetailViewModel by activityViewModels()
|
||||||
private val homeModel: HomeViewModel by activityViewModels()
|
private val homeModel: HomeViewModel by activityViewModels()
|
||||||
|
|
||||||
|
@ -64,6 +62,7 @@ class HomeFragment : Fragment() {
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
val binding = FragmentHomeBinding.inflate(inflater)
|
val binding = FragmentHomeBinding.inflate(inflater)
|
||||||
|
val sortItem: MenuItem
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
||||||
|
@ -75,20 +74,35 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
binding.homeAppbar.makeScrollingViewFade(binding.homeToolbar)
|
binding.homeAppbar.makeScrollingViewFade(binding.homeToolbar)
|
||||||
|
|
||||||
binding.homeToolbar.setOnMenuItemClickListener { item ->
|
binding.homeToolbar.apply {
|
||||||
when (item.itemId) {
|
setOnMenuItemClickListener { item ->
|
||||||
R.id.action_settings -> {
|
when (item.itemId) {
|
||||||
parentFragment?.parentFragment?.findNavController()?.navigate(
|
R.id.action_settings -> {
|
||||||
MainFragmentDirections.actionShowSettings()
|
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 -> {
|
true
|
||||||
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
sortItem = menu.findItem(R.id.submenu_sorting)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.homePager.apply {
|
binding.homePager.apply {
|
||||||
|
@ -121,24 +135,13 @@ class HomeFragment : Fragment() {
|
||||||
// page transitions.
|
// page transitions.
|
||||||
offscreenPageLimit = homeModel.tabs.value!!.size
|
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() {
|
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) = homeModel.updateCurrentTab(position)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
TabLayoutMediator(binding.homeTabs, binding.homePager) { tab, pos ->
|
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_SONGS -> R.string.lbl_songs
|
||||||
DisplayMode.SHOW_ALBUMS -> R.string.lbl_albums
|
DisplayMode.SHOW_ALBUMS -> R.string.lbl_albums
|
||||||
DisplayMode.SHOW_ARTISTS -> R.string.lbl_artists
|
DisplayMode.SHOW_ARTISTS -> R.string.lbl_artists
|
||||||
|
@ -150,6 +153,40 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- 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 ->
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
// The AppBarLayout gets confused and collapses when we navigate too fast, wait for it
|
// The AppBarLayout gets confused and collapses when we navigate too fast, wait for it
|
||||||
// to draw before we continue.
|
// to draw before we continue.
|
||||||
|
@ -182,10 +219,24 @@ class HomeFragment : Fragment() {
|
||||||
return binding.root
|
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 :
|
private inner class HomePagerAdapter :
|
||||||
FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
|
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)
|
override fun createFragment(position: Int): Fragment = HomeListFragment.new(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import org.oxycblt.auxio.BuildConfig
|
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.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.ui.sliceArticle
|
|
||||||
import org.oxycblt.auxio.util.applySpans
|
import org.oxycblt.auxio.util.applySpans
|
||||||
import org.oxycblt.auxio.util.logD
|
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.
|
* should be created using the [new] method with it's position in the ViewPager.
|
||||||
*/
|
*/
|
||||||
class HomeListFragment : Fragment() {
|
class HomeListFragment : Fragment() {
|
||||||
private val homeModel: HomeViewModel by viewModels()
|
private val homeModel: HomeViewModel by activityViewModels()
|
||||||
private val playbackModel: PlaybackViewModel by viewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
@ -81,36 +80,36 @@ class HomeListFragment : Fragment() {
|
||||||
::newMenu
|
::newMenu
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- ITEM SETUP ---
|
|
||||||
|
|
||||||
// Get some tab-specific values before we go ahead. More specifically, the data to use
|
// 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.
|
// and the unique ID that HomeFragment's AppBarLayout uses to determine lift state.
|
||||||
val pos = requireNotNull(arguments).getInt(ARG_POS)
|
val pos = requireNotNull(arguments).getInt(ARG_POS)
|
||||||
|
|
||||||
@IdRes val customId: Int
|
@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 -> {
|
DisplayMode.SHOW_SONGS -> {
|
||||||
customId = R.id.home_song_list
|
customId = R.id.home_song_list
|
||||||
toObserve = homeModel.songs
|
homeData = homeModel.songs
|
||||||
}
|
}
|
||||||
DisplayMode.SHOW_ALBUMS -> {
|
DisplayMode.SHOW_ALBUMS -> {
|
||||||
customId = R.id.home_album_list
|
customId = R.id.home_album_list
|
||||||
toObserve = homeModel.albums
|
homeData = homeModel.albums
|
||||||
}
|
}
|
||||||
DisplayMode.SHOW_ARTISTS -> {
|
DisplayMode.SHOW_ARTISTS -> {
|
||||||
customId = R.id.home_artist_list
|
customId = R.id.home_artist_list
|
||||||
toObserve = homeModel.artists
|
homeData = homeModel.artists
|
||||||
}
|
}
|
||||||
DisplayMode.SHOW_GENRES -> {
|
DisplayMode.SHOW_GENRES -> {
|
||||||
customId = R.id.home_genre_list
|
customId = R.id.home_genre_list
|
||||||
toObserve = homeModel.genres
|
homeData = homeModel.genres
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
||||||
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
|
|
||||||
binding.homeRecycler.apply {
|
binding.homeRecycler.apply {
|
||||||
id = customId
|
id = customId
|
||||||
adapter = homeAdapter
|
adapter = homeAdapter
|
||||||
|
@ -121,16 +120,8 @@ class HomeListFragment : Fragment() {
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
// Make sure that this RecyclerView has data before startup
|
// Make sure that this RecyclerView has data before startup
|
||||||
homeAdapter.updateData(toObserve.value!!)
|
homeData.observe(viewLifecycleOwner) { data ->
|
||||||
|
homeAdapter.updateData(data)
|
||||||
toObserve.observe(viewLifecycleOwner) { data ->
|
|
||||||
homeAdapter.updateData(
|
|
||||||
data.sortedWith(
|
|
||||||
compareBy(String.CASE_INSENSITIVE_ORDER) {
|
|
||||||
it.name.sliceArticle()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logD("Fragment created")
|
logD("Fragment created")
|
||||||
|
|
|
@ -28,32 +28,85 @@ import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ViewModel for managing [HomeFragment]'s data and sorting modes.
|
||||||
|
*/
|
||||||
class HomeViewModel : ViewModel() {
|
class HomeViewModel : ViewModel() {
|
||||||
private val mGenres = MutableLiveData(listOf<Genre>())
|
private val mSongs = MutableLiveData(listOf<Song>())
|
||||||
val genres: LiveData<List<Genre>> get() = mGenres
|
val songs: LiveData<List<Song>> get() = mSongs
|
||||||
|
|
||||||
private val mArtists = MutableLiveData(listOf<Artist>())
|
|
||||||
val artists: LiveData<List<Artist>> get() = mArtists
|
|
||||||
|
|
||||||
private val mAlbums = MutableLiveData(listOf<Album>())
|
private val mAlbums = MutableLiveData(listOf<Album>())
|
||||||
val albums: LiveData<List<Album>> get() = mAlbums
|
val albums: LiveData<List<Album>> get() = mAlbums
|
||||||
|
|
||||||
private val mSongs = MutableLiveData(listOf<Song>())
|
private val mArtists = MutableLiveData(listOf<Artist>())
|
||||||
val songs: LiveData<List<Song>> get() = mSongs
|
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
|
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()
|
private val musicStore = MusicStore.getInstance()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mGenres.value = musicStore.genres
|
mSongs.value = songSortMode.sortSongs(musicStore.songs)
|
||||||
mArtists.value = musicStore.artists
|
mAlbums.value = albumSortMode.sortAlbums(musicStore.albums)
|
||||||
mAlbums.value = musicStore.albums
|
mArtists.value = artistSortMode.sortModels(musicStore.artists)
|
||||||
mSongs.value = musicStore.songs
|
mGenres.value = genreSortMode.sortModels(musicStore.genres)
|
||||||
mTabs.value = arrayOf(
|
}
|
||||||
DisplayMode.SHOW_SONGS, DisplayMode.SHOW_ALBUMS,
|
|
||||||
DisplayMode.SHOW_ARTISTS, DisplayMode.SHOW_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
|
loopMode: LoopMode
|
||||||
): NotificationCompat.Action {
|
): NotificationCompat.Action {
|
||||||
val drawableRes = when (loopMode) {
|
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.ALL -> R.drawable.ic_loop
|
||||||
LoopMode.TRACK -> R.drawable.ic_loop_one
|
LoopMode.TRACK -> R.drawable.ic_loop_one
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,7 @@ class PlaybackNotification private constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
isShuffled: Boolean
|
isShuffled: Boolean
|
||||||
): NotificationCompat.Action {
|
): 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)
|
return buildAction(context, PlaybackService.ACTION_SHUFFLE, drawableRes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,7 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enum for the current sorting mode. Contains helper functions to sort lists based
|
* The legacy enum for sorting. This is set to be removed soon.
|
||||||
* off the given sorting mode.
|
|
||||||
* @property iconRes The icon for this [SortMode]
|
* @property iconRes The icon for this [SortMode]
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
|
@ -190,10 +189,9 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
|
||||||
@IdRes
|
@IdRes
|
||||||
fun toMenuId(): Int {
|
fun toMenuId(): Int {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
ALPHA_UP -> R.id.option_sort_alpha_up
|
ALPHA_UP -> R.id.option_sort_asc
|
||||||
ALPHA_DOWN -> R.id.option_sort_alpha_down
|
ALPHA_DOWN -> R.id.option_sort_dsc
|
||||||
|
else -> R.id.option_sort_dsc
|
||||||
else -> R.id.option_sort_alpha_up
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,9 @@ import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -39,9 +37,6 @@ import androidx.viewbinding.ViewBinding
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import org.oxycblt.auxio.R
|
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].
|
* Apply the recommended spans for a [RecyclerView].
|
||||||
*
|
*
|
||||||
|
@ -72,6 +67,7 @@ fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable an image button.
|
* Disable an image button.
|
||||||
|
* TODO: Replace this fragile function with something else.
|
||||||
*/
|
*/
|
||||||
fun ImageButton.disable() {
|
fun ImageButton.disable() {
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
|
@ -79,14 +75,6 @@ fun ImageButton.disable() {
|
||||||
isEnabled = false
|
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.
|
* Returns whether a recyclerview can scroll.
|
||||||
*/
|
*/
|
||||||
|
@ -116,20 +104,14 @@ fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
|
||||||
* @see resolveColor
|
* @see resolveColor
|
||||||
*/
|
*/
|
||||||
fun @receiver:ColorRes Int.resolveStateList(context: Context) =
|
fun @receiver:ColorRes Int.resolveStateList(context: Context) =
|
||||||
ColorStateList.valueOf(resolveColor(context))
|
ContextCompat.getColorStateList(context, this)
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a drawable resource into a [Drawable]
|
|
||||||
*/
|
|
||||||
fun @receiver:DrawableRes Int.resolveDrawable(context: Context) =
|
|
||||||
requireNotNull(ContextCompat.getDrawable(context, this))
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve this int into a color as if it was an attribute
|
* Resolve this int into a color as if it was an attribute
|
||||||
*/
|
*/
|
||||||
@ColorInt
|
@ColorInt
|
||||||
fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
|
fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
|
||||||
// Convert the attribute into its color
|
// First resolve the attribute into its ID
|
||||||
val resolvedAttr = TypedValue()
|
val resolvedAttr = TypedValue()
|
||||||
context.theme.resolveAttribute(this, resolvedAttr, true)
|
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.
|
// And no, we can't control state drawables with RemoteViews. Because of course we can't.
|
||||||
|
|
||||||
val shuffleRes = when {
|
val shuffleRes = when {
|
||||||
state.isShuffled -> R.drawable.ic_shuffle_tinted
|
state.isShuffled -> R.drawable.ic_shuffle_on
|
||||||
else -> R.drawable.ic_shuffle
|
else -> R.drawable.ic_shuffle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/playback_play_pause"
|
android:id="@+id/playback_play_pause"
|
||||||
style="@style/Widget.Button.Unbounded"
|
style="@style/Widget.Button.Unbounded"
|
||||||
android:src="@drawable/ic_playing_state"
|
android:src="@drawable/sel_playing_state"
|
||||||
android:layout_margin="@dimen/spacing_small"
|
android:layout_margin="@dimen/spacing_small"
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
|
android:onClick="@{() -> playbackModel.invertPlayingStatus()}"
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
android:id="@+id/widget_play_pause"
|
android:id="@+id/widget_play_pause"
|
||||||
style="@style/Widget.Component.AppWidget.Button"
|
style="@style/Widget.Component.AppWidget.Button"
|
||||||
android:contentDescription="@string/desc_play_pause"
|
android:contentDescription="@string/desc_play_pause"
|
||||||
android:src="@drawable/ic_playing_state" />
|
android:src="@drawable/sel_playing_state" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/widget_skip_next"
|
android:id="@+id/widget_skip_next"
|
||||||
|
|
|
@ -16,11 +16,11 @@
|
||||||
<menu>
|
<menu>
|
||||||
<group android:checkableBehavior="single">
|
<group android:checkableBehavior="single">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/option_sort_alpha_down"
|
android:id="@+id/option_sort_asc"
|
||||||
android:title="@string/lbl_sort_alpha_down" />
|
android:title="@string/lbl_sort_asc" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/option_sort_alpha_up"
|
android:id="@+id/option_sort_dsc"
|
||||||
android:title="@string/lbl_sort_alpha_up" />
|
android:title="@string/lbl_sort_dsc" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/option_sort_artist"
|
android:id="@+id/option_sort_artist"
|
||||||
android:title="@string/lbl_sort_artist" />
|
android:title="@string/lbl_sort_artist" />
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
<string name="lbl_filter">"Filtr"</string>
|
<string name="lbl_filter">"Filtr"</string>
|
||||||
<string name="lbl_filter_all">"Vše"</string>
|
<string name="lbl_filter_all">"Vše"</string>
|
||||||
<string name="lbl_sort">"Řadit"</string>
|
<string name="lbl_sort">"Řadit"</string>
|
||||||
<string name="lbl_sort_alpha_down">"Vzestupně"</string>
|
<string name="lbl_sort_asc">"Vzestupně"</string>
|
||||||
<string name="lbl_sort_alpha_up">"Sestupně"</string>
|
<string name="lbl_sort_dsc">"Sestupně"</string>
|
||||||
<string name="lbl_sort_artist">"Umělec"</string>
|
<string name="lbl_sort_artist">"Umělec"</string>
|
||||||
<string name="lbl_sort_album">"Album"</string>
|
<string name="lbl_sort_album">"Album"</string>
|
||||||
<string name="lbl_sort_year">"Rok"</string>
|
<string name="lbl_sort_year">"Rok"</string>
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
<string name="lbl_filter_all">Alles</string>
|
<string name="lbl_filter_all">Alles</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Sortierung</string>
|
<string name="lbl_sort">Sortierung</string>
|
||||||
<string name="lbl_sort_alpha_down">Aufsteigend</string>
|
<string name="lbl_sort_asc">Aufsteigend</string>
|
||||||
<string name="lbl_sort_alpha_up">Absteigend</string>
|
<string name="lbl_sort_dsc">Absteigend</string>
|
||||||
|
|
||||||
<string name="lbl_play">Abspielen</string>
|
<string name="lbl_play">Abspielen</string>
|
||||||
<string name="lbl_shuffle">Zufällig</string>
|
<string name="lbl_shuffle">Zufällig</string>
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
<string name="lbl_filter_all">Todo</string>
|
<string name="lbl_filter_all">Todo</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Ordenar</string>
|
<string name="lbl_sort">Ordenar</string>
|
||||||
<string name="lbl_sort_alpha_down">Ascendente</string>
|
<string name="lbl_sort_asc">Ascendente</string>
|
||||||
<string name="lbl_sort_alpha_up">Descendente</string>
|
<string name="lbl_sort_dsc">Descendente</string>
|
||||||
|
|
||||||
<string name="lbl_play">Reproducir</string>
|
<string name="lbl_play">Reproducir</string>
|
||||||
<string name="lbl_shuffle">Aleatorio</string>
|
<string name="lbl_shuffle">Aleatorio</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">Tout</string>
|
<string name="lbl_filter_all">Tout</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Tri</string>
|
<string name="lbl_sort">Tri</string>
|
||||||
<string name="lbl_sort_alpha_down">Ascendant</string>
|
<string name="lbl_sort_asc">Ascendant</string>
|
||||||
<string name="lbl_sort_alpha_up">Descendant</string>
|
<string name="lbl_sort_dsc">Descendant</string>
|
||||||
|
|
||||||
<string name="lbl_play">Lecture</string>
|
<string name="lbl_play">Lecture</string>
|
||||||
<string name="lbl_shuffle">Aléatoire</string>
|
<string name="lbl_shuffle">Aléatoire</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">Összes</string>
|
<string name="lbl_filter_all">Összes</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Összes</string>
|
<string name="lbl_sort">Összes</string>
|
||||||
<string name="lbl_sort_alpha_down">Növekvő</string>
|
<string name="lbl_sort_asc">Növekvő</string>
|
||||||
<string name="lbl_sort_alpha_up">Csökkenő</string>
|
<string name="lbl_sort_dsc">Csökkenő</string>
|
||||||
|
|
||||||
<string name="lbl_play">Lejátszás</string>
|
<string name="lbl_play">Lejátszás</string>
|
||||||
<string name="lbl_shuffle">Keverés</string>
|
<string name="lbl_shuffle">Keverés</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">Semua</string>
|
<string name="lbl_filter_all">Semua</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Urutan</string>
|
<string name="lbl_sort">Urutan</string>
|
||||||
<string name="lbl_sort_alpha_down">Naik</string>
|
<string name="lbl_sort_asc">Naik</string>
|
||||||
<string name="lbl_sort_alpha_up">Turun</string>
|
<string name="lbl_sort_dsc">Turun</string>
|
||||||
|
|
||||||
<string name="lbl_play">Putar</string>
|
<string name="lbl_play">Putar</string>
|
||||||
<string name="lbl_shuffle">Acak</string>
|
<string name="lbl_shuffle">Acak</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">Tutto</string>
|
<string name="lbl_filter_all">Tutto</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Ordine</string>
|
<string name="lbl_sort">Ordine</string>
|
||||||
<string name="lbl_sort_alpha_down">Ascendente</string>
|
<string name="lbl_sort_asc">Ascendente</string>
|
||||||
<string name="lbl_sort_alpha_up">Discendente</string>
|
<string name="lbl_sort_dsc">Discendente</string>
|
||||||
|
|
||||||
<string name="lbl_play">Riproduci</string>
|
<string name="lbl_play">Riproduci</string>
|
||||||
<string name="lbl_shuffle">Casuale</string>
|
<string name="lbl_shuffle">Casuale</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">전부</string>
|
<string name="lbl_filter_all">전부</string>
|
||||||
|
|
||||||
<string name="lbl_sort">분류</string>
|
<string name="lbl_sort">분류</string>
|
||||||
<string name="lbl_sort_alpha_down">오름차순</string>
|
<string name="lbl_sort_asc">오름차순</string>
|
||||||
<string name="lbl_sort_alpha_up">내림차순</string>
|
<string name="lbl_sort_dsc">내림차순</string>
|
||||||
|
|
||||||
<string name="lbl_play">재생</string>
|
<string name="lbl_play">재생</string>
|
||||||
<string name="lbl_shuffle">모든 곡 랜덤 재생</string>
|
<string name="lbl_shuffle">모든 곡 랜덤 재생</string>
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
<string name="lbl_filter_all">Alles</string>
|
<string name="lbl_filter_all">Alles</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Sorteren</string>
|
<string name="lbl_sort">Sorteren</string>
|
||||||
<string name="lbl_sort_alpha_down">Oplopend</string>
|
<string name="lbl_sort_asc">Oplopend</string>
|
||||||
<string name="lbl_sort_alpha_up">Aflopend</string>
|
<string name="lbl_sort_dsc">Aflopend</string>
|
||||||
|
|
||||||
<string name="lbl_play">Afspelen</string>
|
<string name="lbl_play">Afspelen</string>
|
||||||
<string name="lbl_shuffle">Shuffle</string>
|
<string name="lbl_shuffle">Shuffle</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">Wszystkie</string>
|
<string name="lbl_filter_all">Wszystkie</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Sortowanie</string>
|
<string name="lbl_sort">Sortowanie</string>
|
||||||
<string name="lbl_sort_alpha_down">Rosnąco</string>
|
<string name="lbl_sort_asc">Rosnąco</string>
|
||||||
<string name="lbl_sort_alpha_up">Malejąco</string>
|
<string name="lbl_sort_dsc">Malejąco</string>
|
||||||
|
|
||||||
<string name="lbl_play">Graj</string>
|
<string name="lbl_play">Graj</string>
|
||||||
<string name="lbl_shuffle">Losowo</string>
|
<string name="lbl_shuffle">Losowo</string>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<string name="lbl_filter_all">Tudo</string>
|
<string name="lbl_filter_all">Tudo</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Classificação</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_play">Reproduzir</string>
|
||||||
<string name="lbl_shuffle">Embaralhar</string>
|
<string name="lbl_shuffle">Embaralhar</string>
|
||||||
|
@ -98,5 +98,5 @@
|
||||||
<item quantity="one">%d Álbum</item>
|
<item quantity="one">%d Álbum</item>
|
||||||
<item quantity="other">%d Álbuns</item>
|
<item quantity="other">%d Álbuns</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="lbl_sort_alpha_down">Ascendente</string>
|
<string name="lbl_sort_asc">Ascendente</string>
|
||||||
</resources>
|
</resources>
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">Tudo</string>
|
<string name="lbl_filter_all">Tudo</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Classificação</string>
|
<string name="lbl_sort">Classificação</string>
|
||||||
<string name="lbl_sort_alpha_down">Ascendente</string>
|
<string name="lbl_sort_asc">Ascendente</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_play">Reproduzir</string>
|
||||||
<string name="lbl_shuffle">Embaralhar</string>
|
<string name="lbl_shuffle">Embaralhar</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">Tot</string>
|
<string name="lbl_filter_all">Tot</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Sortare</string>
|
<string name="lbl_sort">Sortare</string>
|
||||||
<string name="lbl_sort_alpha_down">Crescător</string>
|
<string name="lbl_sort_asc">Crescător</string>
|
||||||
<string name="lbl_sort_alpha_up">Descrescător</string>
|
<string name="lbl_sort_dsc">Descrescător</string>
|
||||||
|
|
||||||
<string name="lbl_play">Redă</string>
|
<string name="lbl_play">Redă</string>
|
||||||
<string name="lbl_shuffle">Amestecare</string>
|
<string name="lbl_shuffle">Amestecare</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">Всё</string>
|
<string name="lbl_filter_all">Всё</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Сортировка</string>
|
<string name="lbl_sort">Сортировка</string>
|
||||||
<string name="lbl_sort_alpha_down">По возрастанию</string>
|
<string name="lbl_sort_asc">По возрастанию</string>
|
||||||
<string name="lbl_sort_alpha_up">По убыванию</string>
|
<string name="lbl_sort_dsc">По убыванию</string>
|
||||||
|
|
||||||
<string name="lbl_play">Воспроизвести</string>
|
<string name="lbl_play">Воспроизвести</string>
|
||||||
<string name="lbl_shuffle">Перемешать</string>
|
<string name="lbl_shuffle">Перемешать</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">Tümü</string>
|
<string name="lbl_filter_all">Tümü</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Sıralama</string>
|
<string name="lbl_sort">Sıralama</string>
|
||||||
<string name="lbl_sort_alpha_down">Artan</string>
|
<string name="lbl_sort_asc">Artan</string>
|
||||||
<string name="lbl_sort_alpha_up">Azalan</string>
|
<string name="lbl_sort_dsc">Azalan</string>
|
||||||
|
|
||||||
<string name="lbl_play">Başlat</string>
|
<string name="lbl_play">Başlat</string>
|
||||||
<string name="lbl_shuffle">Karıştır</string>
|
<string name="lbl_shuffle">Karıştır</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">全部</string>
|
<string name="lbl_filter_all">全部</string>
|
||||||
|
|
||||||
<string name="lbl_sort">排序方式</string>
|
<string name="lbl_sort">排序方式</string>
|
||||||
<string name="lbl_sort_alpha_down">按首字符(正序)</string>
|
<string name="lbl_sort_asc">按首字符(正序)</string>
|
||||||
<string name="lbl_sort_alpha_up">按首字符(倒序)</string>
|
<string name="lbl_sort_dsc">按首字符(倒序)</string>
|
||||||
|
|
||||||
<string name="lbl_play">播放</string>
|
<string name="lbl_play">播放</string>
|
||||||
<string name="lbl_shuffle">随机播放</string>
|
<string name="lbl_shuffle">随机播放</string>
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
<string name="lbl_filter_all">全部</string>
|
<string name="lbl_filter_all">全部</string>
|
||||||
|
|
||||||
<string name="lbl_sort">排序</string>
|
<string name="lbl_sort">排序</string>
|
||||||
<string name="lbl_sort_alpha_down">升序排列</string>
|
<string name="lbl_sort_asc">升序排列</string>
|
||||||
<string name="lbl_sort_alpha_up">降序排列</string>
|
<string name="lbl_sort_dsc">降序排列</string>
|
||||||
|
|
||||||
<string name="lbl_play">播放</string>
|
<string name="lbl_play">播放</string>
|
||||||
<string name="lbl_shuffle">隨機播放</string>
|
<string name="lbl_shuffle">隨機播放</string>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="surface">#fafafa</color>
|
<color name="surface">#fafafa</color>
|
||||||
<color name="surface_black">@android:color/black</color>
|
|
||||||
<color name="control">#202020</color>
|
<color name="control">#202020</color>
|
||||||
<color name="nav_bar">#01fafafa</color>
|
<color name="nav_bar">#01fafafa</color>
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,8 @@
|
||||||
<string name="lbl_filter_all">All</string>
|
<string name="lbl_filter_all">All</string>
|
||||||
|
|
||||||
<string name="lbl_sort">Sort</string>
|
<string name="lbl_sort">Sort</string>
|
||||||
<string name="lbl_sort_alpha_down">Ascending</string>
|
<string name="lbl_sort_asc">Ascending</string>
|
||||||
<string name="lbl_sort_alpha_up">Descending</string>
|
<string name="lbl_sort_dsc">Descending</string>
|
||||||
<string name="lbl_sort_artist">Artist</string>
|
<string name="lbl_sort_artist">Artist</string>
|
||||||
<string name="lbl_sort_album">Album</string>
|
<string name="lbl_sort_album">Album</string>
|
||||||
<string name="lbl_sort_year">Year</string>
|
<string name="lbl_sort_year">Year</string>
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
<!-- Black theme dialog theme -->
|
<!-- Black theme dialog theme -->
|
||||||
<style name="Theme.CustomDialog.Black" parent="Theme.CustomDialog.Base">
|
<style name="Theme.CustomDialog.Black" parent="Theme.CustomDialog.Base">
|
||||||
<item name="colorSurface">@color/surface_black</item>
|
<item name="colorSurface">@android:color/black</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Material-specific dialog style -->
|
<!-- Material-specific dialog style -->
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<item name="android:elevation">@dimen/elevation_normal</item>
|
<item name="android:elevation">@dimen/elevation_normal</item>
|
||||||
<item name="android:contentDescription">@string/desc_play_pause</item>
|
<item name="android:contentDescription">@string/desc_play_pause</item>
|
||||||
<item name="android:tint">?attr/colorSurface</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_marginStart">@dimen/spacing_large</item>
|
||||||
<item name="android:layout_marginTop">@dimen/spacing_medium</item>
|
<item name="android:layout_marginTop">@dimen/spacing_medium</item>
|
||||||
<item name="android:layout_marginEnd">@dimen/spacing_large</item>
|
<item name="android:layout_marginEnd">@dimen/spacing_large</item>
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
|
|
||||||
<!-- The basic black theme derived in all black accents. -->
|
<!-- The basic black theme derived in all black accents. -->
|
||||||
<style name="Theme.Base.Black" parent="Theme.Base">
|
<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>
|
<item name="materialAlertDialogTheme">@style/Theme.CustomDialog.Black</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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 "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
|
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue