diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index c0e8561ca..b89cfc9e4 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.View import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -75,6 +74,7 @@ class AlbumDetailFragment : override val listModel: ListViewModel by activityViewModels() override val musicModel: MusicViewModel by activityViewModels() override val playbackModel: PlaybackViewModel by activityViewModels() + // Information about what album to display is initially within the navigation arguments // as a UID, as that is the only safe way to parcel an album. private val args: AlbumDetailFragmentArgs by navArgs() @@ -110,7 +110,7 @@ class AlbumDetailFragment : adapter = ConcatAdapter(albumHeaderAdapter, albumListAdapter) (layoutManager as GridLayoutManager).setFullWidthLookup { if (it != 0) { - val item = detailModel.albumList.value[it - 1] + val item = detailModel.albumSongList.value[it - 1] item is Divider || item is Header || item is Disc } else { true @@ -122,7 +122,7 @@ class AlbumDetailFragment : // DetailViewModel handles most initialization from the navigation argument. detailModel.setAlbum(args.albumUid) collectImmediately(detailModel.currentAlbum, ::updateAlbum) - collectImmediately(detailModel.albumList, ::updateList) + collectImmediately(detailModel.albumSongList, ::updateList) collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) @@ -138,7 +138,7 @@ class AlbumDetailFragment : binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. - detailModel.albumInstructions.consume() + detailModel.albumSongInstructions.consume() } override fun onMenuItemClick(item: MenuItem): Boolean { @@ -181,7 +181,7 @@ class AlbumDetailFragment : playbackModel.play(item, detailModel.playInAlbumWith) } - override fun onOpenMenu(item: Song, anchor: View) { + override fun onOpenMenu(item: Song) { listModel.openMenu(R.menu.item_album_song, item, detailModel.playInAlbumWith) } @@ -192,36 +192,9 @@ class AlbumDetailFragment : override fun onShuffle() { playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value)) } - - override fun onOpenSortMenu(anchor: View) { + + override fun onOpenSortMenu() { findNavController().navigateSafe(AlbumDetailFragmentDirections.sort()) - // openMenu(anchor, R.menu.sort_album) { - // // Select the corresponding sort mode option - // val sort = detailModel.albumSongSort - // unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true - // // Select the corresponding sort direction option - // val directionItemId = - // when (sort.direction) { - // Sort.Direction.ASCENDING -> R.id.option_sort_asc - // Sort.Direction.DESCENDING -> R.id.option_sort_dec - // } - // unlikelyToBeNull(menu.findItem(directionItemId)).isChecked = true - // setOnMenuItemClickListener { item -> - // item.isChecked = !item.isChecked - // detailModel.albumSongSort = - // when (item.itemId) { - // // Sort direction options - // R.id.option_sort_asc -> - // sort.withDirection(Sort.Direction.ASCENDING) - // R.id.option_sort_dec -> - // sort.withDirection(Sort.Direction.DESCENDING) - // // Any other option is a sort mode - // else -> - // sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId))) - // } - // true - // } - // } } override fun onNavigateToParentArtist() { @@ -239,7 +212,7 @@ class AlbumDetailFragment : } private fun updateList(list: List) { - albumListAdapter.update(list, detailModel.albumInstructions.consume()) + albumListAdapter.update(list, detailModel.albumSongInstructions.consume()) } private fun handleShow(show: Show?) { @@ -365,7 +338,7 @@ class AlbumDetailFragment : private fun scrollToAlbumSong(song: Song) { // Calculate where the item for the currently played song is - val pos = detailModel.albumList.value.indexOf(song) + val pos = detailModel.albumSongList.value.indexOf(song) if (pos != -1) { // Only scroll if the song is within this album. diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 9997496b6..c50fb16b4 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.View import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -41,7 +40,6 @@ import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListViewModel import org.oxycblt.auxio.list.Menu -import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music @@ -111,7 +109,7 @@ class ArtistDetailFragment : (layoutManager as GridLayoutManager).setFullWidthLookup { if (it != 0) { val item = - detailModel.artistList.value.getOrElse(it - 1) { + detailModel.artistSongList.value.getOrElse(it - 1) { return@setFullWidthLookup false } item is Divider || item is Header @@ -125,7 +123,7 @@ class ArtistDetailFragment : // DetailViewModel handles most initialization from the navigation argument. detailModel.setArtist(args.artistUid) collectImmediately(detailModel.currentArtist, ::updateArtist) - collectImmediately(detailModel.artistList, ::updateList) + collectImmediately(detailModel.artistSongList, ::updateList) collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) @@ -141,7 +139,7 @@ class ArtistDetailFragment : binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. - detailModel.artistInstructions.consume() + detailModel.artistSongInstructions.consume() } override fun onMenuItemClick(item: MenuItem): Boolean { @@ -184,7 +182,7 @@ class ArtistDetailFragment : } } - override fun onOpenMenu(item: Music, anchor: View) { + override fun onOpenMenu(item: Music) { when (item) { is Song -> listModel.openMenu(R.menu.item_artist_song, item, detailModel.playInArtistWith) @@ -201,33 +199,8 @@ class ArtistDetailFragment : playbackModel.shuffle(unlikelyToBeNull(detailModel.currentArtist.value)) } - override fun onOpenSortMenu(anchor: View) { - openMenu(anchor, R.menu.sort_artist) { - // Select the corresponding sort mode option - val sort = detailModel.artistSongSort - unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true - // Select the corresponding sort direction option - val directionItemId = - when (sort.direction) { - Sort.Direction.ASCENDING -> R.id.option_sort_asc - Sort.Direction.DESCENDING -> R.id.option_sort_dec - } - unlikelyToBeNull(menu.findItem(directionItemId)).isChecked = true - setOnMenuItemClickListener { item -> - item.isChecked = !item.isChecked - - detailModel.artistSongSort = - when (item.itemId) { - // Sort direction options - R.id.option_sort_asc -> sort.withDirection(Sort.Direction.ASCENDING) - R.id.option_sort_dec -> sort.withDirection(Sort.Direction.DESCENDING) - // Any other option is a sort mode - else -> sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId))) - } - - true - } - } + override fun onOpenSortMenu() { + findNavController().navigateSafe(ArtistDetailFragmentDirections.sort()) } private fun updateArtist(artist: Artist?) { @@ -253,7 +226,7 @@ class ArtistDetailFragment : } private fun updateList(list: List) { - artistListAdapter.update(list, detailModel.artistInstructions.consume()) + artistListAdapter.update(list, detailModel.artistSongInstructions.consume()) } private fun handleShow(show: Show?) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index ea618078c..09d0cb01d 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -98,24 +98,19 @@ constructor( val currentAlbum: StateFlow get() = _currentAlbum - private val _albumList = MutableStateFlow(listOf()) + private val _albumSongList = MutableStateFlow(listOf()) /** The current list data derived from [currentAlbum]. */ - val albumList: StateFlow> - get() = _albumList + val albumSongList: StateFlow> + get() = _albumSongList - private val _albumInstructions = MutableEvent() - /** Instructions for updating [albumList] in the UI. */ - val albumInstructions: Event - get() = _albumInstructions + private val _albumSongInstructions = MutableEvent() + /** Instructions for updating [albumSongList] in the UI. */ + val albumSongInstructions: Event + get() = _albumSongInstructions - /** The current [Sort] used for [Song]s in [albumList]. */ - var albumSongSort: Sort + /** The current [Sort] used for [Song]s in [albumSongList]. */ + val albumSongSort: Sort get() = musicSettings.albumSongSort - set(value) { - musicSettings.albumSongSort = value - // Refresh the album list to reflect the new sort. - currentAlbum.value?.let { refreshAlbumList(it, true) } - } /** The [PlaySong] instructions to use when playing a [Song] from [Album] details. */ val playInAlbumWith @@ -128,15 +123,16 @@ constructor( val currentArtist: StateFlow get() = _currentArtist - private val _artistList = MutableStateFlow(listOf()) + private val _artistSongList = MutableStateFlow(listOf()) /** The current list derived from [currentArtist]. */ - val artistList: StateFlow> = _artistList - private val _artistInstructions = MutableEvent() - /** Instructions for updating [artistList] in the UI. */ - val artistInstructions: Event - get() = _artistInstructions + val artistSongList: StateFlow> = _artistSongList - /** The current [Sort] used for [Song]s in [artistList]. */ + private val _artistSongInstructions = MutableEvent() + /** Instructions for updating [artistSongList] in the UI. */ + val artistSongInstructions: Event + get() = _artistSongInstructions + + /** The current [Sort] used for [Song]s in [artistSongList]. */ var artistSongSort: Sort get() = musicSettings.artistSongSort set(value) { @@ -156,15 +152,16 @@ constructor( val currentGenre: StateFlow get() = _currentGenre - private val _genreList = MutableStateFlow(listOf()) + private val _genreSongList = MutableStateFlow(listOf()) /** The current list data derived from [currentGenre]. */ - val genreList: StateFlow> = _genreList - private val _genreInstructions = MutableEvent() - /** Instructions for updating [artistList] in the UI. */ - val genreInstructions: Event - get() = _genreInstructions + val genreSongList: StateFlow> = _genreSongList - /** The current [Sort] used for [Song]s in [genreList]. */ + private val _genreSongInstructions = MutableEvent() + /** Instructions for updating [artistSongList] in the UI. */ + val genreSongInstructions: Event + get() = _genreSongInstructions + + /** The current [Sort] used for [Song]s in [genreSongList]. */ var genreSongSort: Sort get() = musicSettings.genreSongSort set(value) { @@ -184,13 +181,14 @@ constructor( val currentPlaylist: StateFlow get() = _currentPlaylist - private val _playlistList = MutableStateFlow(listOf()) + private val _playlistSongList = MutableStateFlow(listOf()) /** The current list data derived from [currentPlaylist] */ - val playlistList: StateFlow> = _playlistList - private val _playlistInstructions = MutableEvent() - /** Instructions for updating [playlistList] in the UI. */ - val playlistInstructions: Event - get() = _playlistInstructions + val playlistSongList: StateFlow> = _playlistSongList + + private val _playlistSongInstructions = MutableEvent() + /** Instructions for updating [playlistSongList] in the UI. */ + val playlistSongInstructions: Event + get() = _playlistSongInstructions private val _editedPlaylist = MutableStateFlow?>(null) /** @@ -351,7 +349,7 @@ constructor( } /** - * Set a new [currentAlbum] from it's [Music.UID]. [currentAlbum] and [albumList] will be + * Set a new [currentAlbum] from it's [Music.UID]. [currentAlbum] and [albumSongList] will be * updated to align with the new [Album]. * * @param uid The [Music.UID] of the [Album] to update [currentAlbum] to. Must be valid. @@ -366,7 +364,17 @@ constructor( } /** - * Set a new [currentArtist] from it's [Music.UID]. [currentArtist] and [artistList] will be + * Apply a new [Sort] to [albumSongList]. + * + * @param sort The [Sort] to apply. + */ + fun applyAlbumSongSort(sort: Sort) { + musicSettings.albumSongSort = sort + _currentAlbum.value?.let { refreshAlbumList(it, true) } + } + + /** + * Set a new [currentArtist] from it's [Music.UID]. [currentArtist] and [artistSongList] will be * updated to align with the new [Artist]. * * @param uid The [Music.UID] of the [Artist] to update [currentArtist] to. Must be valid. @@ -381,7 +389,17 @@ constructor( } /** - * Set a new [currentGenre] from it's [Music.UID]. [currentGenre] and [genreList] will be + * Apply a new [Sort] to [artistSongList]. + * + * @param sort The [Sort] to apply. + */ + fun applyArtistSongSort(sort: Sort) { + musicSettings.artistSongSort = sort + _currentArtist.value?.let { refreshArtistList(it, true) } + } + + /** + * Set a new [currentGenre] from it's [Music.UID]. [currentGenre] and [genreSongList] will be * updated to align with the new album. * * @param uid The [Music.UID] of the [Genre] to update [currentGenre] to. Must be valid. @@ -395,6 +413,16 @@ constructor( } } + /** + * Apply a new [Sort] to [genreSongList]. + * + * @param sort The [Sort] to apply. + */ + fun applyGenreSongSort(sort: Sort) { + musicSettings.genreSongSort = sort + _currentGenre.value?.let { refreshGenreList(it, true) } + } + /** * Set a new [currentPlaylist] from it's [Music.UID]. If the [Music.UID] differs, * [currentPlaylist] and [currentPlaylist] will be updated to align with the new album. @@ -544,8 +572,8 @@ constructor( } logD("Update album list to ${list.size} items with $instructions") - _albumInstructions.put(instructions) - _albumList.value = list + _albumSongInstructions.put(instructions) + _albumSongList.value = list } private fun refreshArtistList(artist: Artist, replace: Boolean = false) { @@ -607,8 +635,8 @@ constructor( } logD("Updating artist list to ${list.size} items with $instructions") - _artistInstructions.put(instructions) - _artistList.value = list.toList() + _artistSongInstructions.put(instructions) + _artistSongList.value = list.toList() } private fun refreshGenreList(genre: Genre, replace: Boolean = false) { @@ -633,8 +661,8 @@ constructor( list.addAll(genreSongSort.songs(genre.songs)) logD("Updating genre list to ${list.size} items with $instructions") - _genreInstructions.put(instructions) - _genreList.value = list + _genreSongInstructions.put(instructions) + _genreSongList.value = list } private fun refreshPlaylistList( @@ -653,8 +681,8 @@ constructor( } logD("Updating playlist list to ${list.size} items with $instructions") - _playlistInstructions.put(instructions) - _playlistList.value = list + _playlistSongInstructions.put(instructions) + _playlistSongList.value = list } /** diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index dfb9adc29..190ae4c81 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.View import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -41,7 +40,6 @@ import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.ListFragment import org.oxycblt.auxio.list.ListViewModel import org.oxycblt.auxio.list.Menu -import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music @@ -109,7 +107,7 @@ class GenreDetailFragment : (layoutManager as GridLayoutManager).setFullWidthLookup { if (it != 0) { val item = - detailModel.genreList.value.getOrElse(it - 1) { + detailModel.genreSongList.value.getOrElse(it - 1) { return@setFullWidthLookup false } item is Divider || item is Header @@ -123,7 +121,7 @@ class GenreDetailFragment : // DetailViewModel handles most initialization from the navigation argument. detailModel.setGenre(args.genreUid) collectImmediately(detailModel.currentGenre, ::updatePlaylist) - collectImmediately(detailModel.genreList, ::updateList) + collectImmediately(detailModel.genreSongList, ::updateList) collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) @@ -139,7 +137,7 @@ class GenreDetailFragment : binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. - detailModel.genreInstructions.consume() + detailModel.genreSongInstructions.consume() } override fun onMenuItemClick(item: MenuItem): Boolean { @@ -182,7 +180,7 @@ class GenreDetailFragment : } } - override fun onOpenMenu(item: Music, anchor: View) { + override fun onOpenMenu(item: Music) { when (item) { is Artist -> listModel.openMenu(R.menu.item_parent, item) is Song -> listModel.openMenu(R.menu.item_song, item, detailModel.playInGenreWith) @@ -198,31 +196,8 @@ class GenreDetailFragment : playbackModel.shuffle(unlikelyToBeNull(detailModel.currentGenre.value)) } - override fun onOpenSortMenu(anchor: View) { - openMenu(anchor, R.menu.sort_genre) { - // Select the corresponding sort mode option - val sort = detailModel.genreSongSort - unlikelyToBeNull(menu.findItem(sort.mode.itemId)).isChecked = true - // Select the corresponding sort direction option - val directionItemId = - when (sort.direction) { - Sort.Direction.ASCENDING -> R.id.option_sort_asc - Sort.Direction.DESCENDING -> R.id.option_sort_dec - } - unlikelyToBeNull(menu.findItem(directionItemId)).isChecked = true - setOnMenuItemClickListener { item -> - item.isChecked = !item.isChecked - detailModel.genreSongSort = - when (item.itemId) { - // Sort direction options - R.id.option_sort_asc -> sort.withDirection(Sort.Direction.ASCENDING) - R.id.option_sort_dec -> sort.withDirection(Sort.Direction.DESCENDING) - // Any other option is a sort mode - else -> sort.withMode(unlikelyToBeNull(Sort.Mode.fromItemId(item.itemId))) - } - true - } - } + override fun onOpenSortMenu() { + findNavController().navigateSafe(GenreDetailFragmentDirections.sort()) } private fun updatePlaylist(genre: Genre?) { @@ -236,7 +211,7 @@ class GenreDetailFragment : } private fun updateList(list: List) { - genreListAdapter.update(list, detailModel.genreInstructions.consume()) + genreListAdapter.update(list, detailModel.genreSongInstructions.consume()) } private fun handleShow(show: Show?) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt index 4f25b96f8..9c0088c11 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/PlaylistDetailFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.detail import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.View import androidx.fragment.app.activityViewModels import androidx.navigation.NavController import androidx.navigation.NavDestination @@ -123,7 +122,7 @@ class PlaylistDetailFragment : (layoutManager as GridLayoutManager).setFullWidthLookup { if (it != 0) { val item = - detailModel.playlistList.value.getOrElse(it - 1) { + detailModel.playlistSongList.value.getOrElse(it - 1) { return@setFullWidthLookup false } item is Divider || item is Header @@ -137,7 +136,7 @@ class PlaylistDetailFragment : // DetailViewModel handles most initialization from the navigation argument. detailModel.setPlaylist(args.playlistUid) collectImmediately(detailModel.currentPlaylist, ::updatePlaylist) - collectImmediately(detailModel.playlistList, ::updateList) + collectImmediately(detailModel.playlistSongList, ::updateList) collectImmediately(detailModel.editedPlaylist, ::updateEditedList) collect(detailModel.toShow.flow, ::handleShow) collect(listModel.menu.flow, ::handleMenu) @@ -168,7 +167,7 @@ class PlaylistDetailFragment : binding.detailRecycler.adapter = null // Avoid possible race conditions that could cause a bad replace instruction to be consumed // during list initialization and crash the app. Could happen if the user is fast enough. - detailModel.playlistInstructions.consume() + detailModel.playlistSongInstructions.consume() } override fun onDestinationChanged( @@ -236,7 +235,7 @@ class PlaylistDetailFragment : requireNotNull(touchHelper) { "ItemTouchHelper was not available" }.startDrag(viewHolder) } - override fun onOpenMenu(item: Song, anchor: View) { + override fun onOpenMenu(item: Song) { listModel.openMenu(R.menu.item_playlist_song, item, detailModel.playInPlaylistWith) } @@ -252,7 +251,7 @@ class PlaylistDetailFragment : detailModel.startPlaylistEdit() } - override fun onOpenSortMenu(anchor: View) {} + override fun onOpenSortMenu() {} private fun updatePlaylist(playlist: Playlist?) { if (playlist == null) { @@ -278,7 +277,7 @@ class PlaylistDetailFragment : } private fun updateList(list: List) { - playlistListAdapter.update(list, detailModel.playlistInstructions.consume()) + playlistListAdapter.update(list, detailModel.playlistSongInstructions.consume()) } private fun updateEditedList(editedPlaylist: List?) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/list/DetailListAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/list/DetailListAdapter.kt index ba350e7b3..08293199f 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/list/DetailListAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/list/DetailListAdapter.kt @@ -82,7 +82,7 @@ abstract class DetailListAdapter( * Called when the button in a [SortHeader] item is pressed, requesting that the sort menu * should be opened. */ - fun onOpenSortMenu(anchor: View) + fun onOpenSortMenu() } protected companion object { @@ -132,7 +132,7 @@ private class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) : // Add a Tooltip based on the content description so that the purpose of this // button can be clear. TooltipCompat.setTooltipText(this, contentDescription) - setOnClickListener(listener::onOpenSortMenu) + setOnClickListener { listener.onOpenSortMenu() } } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/sort/AlbumSongSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/sort/AlbumSongSortDialog.kt new file mode 100644 index 000000000..ea1fd15bd --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/detail/sort/AlbumSongSortDialog.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 Auxio Project + * AlbumSongSortDialog.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 . + */ + +package org.oxycblt.auxio.detail.sort + +import android.os.Bundle +import androidx.fragment.app.activityViewModels +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.databinding.DialogSortBinding +import org.oxycblt.auxio.detail.DetailViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.logD + +/** + * A [SortDialog] that controls the [Sort] of [DetailViewModel.albumSongSort]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class AlbumSongSortDialog : SortDialog() { + private val detailModel: DetailViewModel by activityViewModels() + + override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) { + super.onBindingCreated(binding, savedInstanceState) + + // --- VIEWMODEL SETUP --- + collectImmediately(detailModel.currentAlbum, ::updateAlbum) + } + + override fun getInitialSort() = detailModel.albumSongSort + + override fun applyChosenSort(sort: Sort) { + detailModel.applyAlbumSongSort(sort) + } + + override fun getModeChoices() = listOf(Sort.Mode.ByDisc, Sort.Mode.ByTrack) + + private fun updateAlbum(album: Album?) { + if (album == null) { + logD("No album to sort, navigating away") + findNavController().navigateUp() + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/detail/sort/ArtistSongSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/sort/ArtistSongSortDialog.kt new file mode 100644 index 000000000..15daaff72 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/detail/sort/ArtistSongSortDialog.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Auxio Project + * ArtistSongSortDialog.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 . + */ + +package org.oxycblt.auxio.detail.sort + +import android.os.Bundle +import androidx.fragment.app.activityViewModels +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.databinding.DialogSortBinding +import org.oxycblt.auxio.detail.DetailViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.logD + +/** + * A [SortDialog] that controls the [Sort] of [DetailViewModel.artistSongSort]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class ArtistSongSortDialog : SortDialog() { + private val detailModel: DetailViewModel by activityViewModels() + + override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) { + super.onBindingCreated(binding, savedInstanceState) + + // --- VIEWMODEL SETUP --- + collectImmediately(detailModel.currentArtist, ::updateArtist) + } + + override fun getInitialSort() = detailModel.artistSongSort + + override fun applyChosenSort(sort: Sort) { + detailModel.applyArtistSongSort(sort) + } + + override fun getModeChoices() = + listOf(Sort.Mode.ByName, Sort.Mode.ByAlbum, Sort.Mode.ByDate, Sort.Mode.ByDuration) + + private fun updateArtist(artist: Artist?) { + if (artist == null) { + logD("No artist to sort, navigating away") + findNavController().navigateUp() + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/detail/sort/GenreSongSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/sort/GenreSongSortDialog.kt new file mode 100644 index 000000000..3191557e4 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/detail/sort/GenreSongSortDialog.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 Auxio Project + * GenreSongSortDialog.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 . + */ + +package org.oxycblt.auxio.detail.sort + +import android.os.Bundle +import androidx.fragment.app.activityViewModels +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.databinding.DialogSortBinding +import org.oxycblt.auxio.detail.DetailViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.logD + +/** + * A [SortDialog] that controls the [Sort] of [DetailViewModel.genreSongSort]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class GenreSongSortDialog : SortDialog() { + private val detailModel: DetailViewModel by activityViewModels() + + override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) { + super.onBindingCreated(binding, savedInstanceState) + + // --- VIEWMODEL SETUP --- + collectImmediately(detailModel.currentGenre, ::updateGenre) + } + + override fun getInitialSort() = detailModel.genreSongSort + + override fun applyChosenSort(sort: Sort) { + detailModel.applyGenreSongSort(sort) + } + + override fun getModeChoices() = + listOf( + Sort.Mode.ByName, + Sort.Mode.ByArtist, + Sort.Mode.ByAlbum, + Sort.Mode.ByDate, + Sort.Mode.ByDuration) + + private fun updateGenre(genre: Genre?) { + if (genre == null) { + logD("No genre to sort, navigating away") + findNavController().navigateUp() + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 834b25fe6..51e25378a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -26,7 +26,6 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.MenuCompat import androidx.core.view.isVisible -import androidx.core.view.iterator import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -57,7 +56,6 @@ import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.list.ListViewModel import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.list.SelectionFragment -import org.oxycblt.auxio.list.Sort import org.oxycblt.auxio.music.IndexingProgress import org.oxycblt.auxio.music.IndexingState import org.oxycblt.auxio.music.Music @@ -76,7 +74,6 @@ import org.oxycblt.auxio.util.lazyReflectedField import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.navigateSafe -import org.oxycblt.auxio.util.unlikelyToBeNull /** * The starting [SelectionFragment] of Auxio. Shows the user's music library and enables navigation @@ -172,7 +169,7 @@ class HomeFragment : // --- VIEWMODEL SETUP --- collect(homeModel.recreateTabs.flow, ::handleRecreate) collectImmediately(homeModel.currentTabType, ::updateCurrentTab) - collectImmediately(homeModel.songsList, homeModel.isFastScrolling, ::updateFab) + collectImmediately(homeModel.songList, homeModel.isFastScrolling, ::updateFab) collect(listModel.menu.flow, ::handleMenu) collectImmediately(listModel.selected, ::updateSelection) collectImmediately(musicModel.indexingState, ::updateIndexerState) @@ -232,41 +229,22 @@ class HomeFragment : } // Handle sort menu - R.id.submenu_sorting -> { + R.id.action_sort -> { // Junk click event when opening the menu - true - } - R.id.option_sort_asc -> { - logD("Switching to ascending sorting") - item.isChecked = true - homeModel.setSortForCurrentTab( - homeModel - .getSortForTab(homeModel.currentTabType.value) - .withDirection(Sort.Direction.ASCENDING)) - true - } - R.id.option_sort_dec -> { - logD("Switching to descending sorting") - item.isChecked = true - homeModel.setSortForCurrentTab( - homeModel - .getSortForTab(homeModel.currentTabType.value) - .withDirection(Sort.Direction.DESCENDING)) + val directions = + when (homeModel.currentTabType.value) { + MusicType.SONGS -> HomeFragmentDirections.sortSongs() + MusicType.ALBUMS -> HomeFragmentDirections.sortAlbums() + MusicType.ARTISTS -> HomeFragmentDirections.sortArtists() + MusicType.GENRES -> HomeFragmentDirections.sortGenres() + MusicType.PLAYLISTS -> HomeFragmentDirections.sortPlaylists() + } + findNavController().navigateSafe(directions) true } else -> { - val newMode = Sort.Mode.fromItemId(item.itemId) - if (newMode != null) { - // Sorting option was selected, mark it as selected and update the mode - logD("Updating sort mode") - item.isChecked = true - homeModel.setSortForCurrentTab( - homeModel.getSortForTab(homeModel.currentTabType.value).withMode(newMode)) - true - } else { - logW("Unexpected menu item selected") - false - } + logW("Unexpected menu item selected") + false } } } @@ -300,61 +278,6 @@ class HomeFragment : private fun updateCurrentTab(tabType: MusicType) { val binding = requireBinding() - // Update the sort options to align with those allowed by the tab - val isVisible: (Int) -> Boolean = - when (tabType) { - // Disallow sorting by count for songs - MusicType.SONGS -> { - logD("Using song-specific menu options") - ({ id -> id != R.id.option_sort_count }) - } - // Disallow sorting by album for albums - MusicType.ALBUMS -> { - logD("Using album-specific menu options") - ({ id -> id != R.id.option_sort_album }) - } - // Only allow sorting by name, count, and duration for parents - else -> { - logD("Using parent-specific menu options") - ({ id -> - id == R.id.option_sort_asc || - id == R.id.option_sort_dec || - id == R.id.option_sort_name || - id == R.id.option_sort_count || - id == R.id.option_sort_duration - }) - } - } - - val sortMenu = - unlikelyToBeNull(binding.homeNormalToolbar.menu.findItem(R.id.submenu_sorting).subMenu) - val toHighlight = homeModel.getSortForTab(tabType) - - for (option in sortMenu) { - val isCurrentMode = option.itemId == toHighlight.mode.itemId - val isCurrentlyAscending = - option.itemId == R.id.option_sort_asc && - toHighlight.direction == Sort.Direction.ASCENDING - val isCurrentlyDescending = - option.itemId == R.id.option_sort_dec && - toHighlight.direction == Sort.Direction.DESCENDING - // Check the corresponding direction and mode sort options to align with - // the current sort of the tab. - if (isCurrentMode || isCurrentlyAscending || isCurrentlyDescending) { - logD( - "Checking $option option [mode: $isCurrentMode asc: $isCurrentlyAscending dec: $isCurrentlyDescending]") - // Note: We cannot inline this boolean assignment since it unchecks all other radio - // buttons (even when setting it to false), which would result in nothing being - // selected. - option.isChecked = true - } - - // Disable options that are not allowed by the isVisible lambda - option.isVisible = isVisible(option.itemId) - if (!option.isVisible) { - logD("Hiding $option option") - } - } // Update the scrolling view in AppBarLayout to align with the current tab's // scrolling state. This prevents the lift state from being confused as one diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index a89b73b3d..0d7e1e4d0 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -55,63 +55,83 @@ constructor( private val musicSettings: MusicSettings ) : ViewModel(), MusicRepository.UpdateListener, HomeSettings.Listener { - private val _songsList = MutableStateFlow(listOf()) + private val _songList = MutableStateFlow(listOf()) /** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */ - val songsList: StateFlow> - get() = _songsList + val songList: StateFlow> + get() = _songList - private val _songsInstructions = MutableEvent() - /** Instructions for how to update [songsList] in the UI. */ - val songsInstructions: Event - get() = _songsInstructions + private val _songInstructions = MutableEvent() + /** Instructions for how to update [songList] in the UI. */ + val songInstructions: Event + get() = _songInstructions - private val _albumsLists = MutableStateFlow(listOf()) - /** A list of [Album]s, sorted by the preferred [Sort], to be shown in the home view. */ - val albumsList: StateFlow> - get() = _albumsLists - - private val _albumsInstructions = MutableEvent() - /** Instructions for how to update [albumsList] in the UI. */ - val albumsInstructions: Event - get() = _albumsInstructions - - private val _artistsList = MutableStateFlow(listOf()) - /** - * A list of [Artist]s, sorted by the preferred [Sort], to be shown in the home view. Note that - * if "Hide collaborators" is on, this list will not include collaborator [Artist]s. - */ - val artistsList: MutableStateFlow> - get() = _artistsList - - private val _artistsInstructions = MutableEvent() - /** Instructions for how to update [artistsList] in the UI. */ - val artistsInstructions: Event - get() = _artistsInstructions - - private val _genresList = MutableStateFlow(listOf()) - /** A list of [Genre]s, sorted by the preferred [Sort], to be shown in the home view. */ - val genresList: StateFlow> - get() = _genresList - - private val _genresInstructions = MutableEvent() - /** Instructions for how to update [genresList] in the UI. */ - val genresInstructions: Event - get() = _genresInstructions - - private val _playlistsList = MutableStateFlow(listOf()) - /** A list of [Playlist]s, sorted by the preferred [Sort], to be shown in the home view. */ - val playlistsList: StateFlow> - get() = _playlistsList - - private val _playlistsInstructions = MutableEvent() - /** Instructions for how to update [genresList] in the UI. */ - val playlistsInstructions: Event - get() = _playlistsInstructions + /** The current [Sort] used for [songList]. */ + val songSort: Sort + get() = musicSettings.songSort /** The [PlaySong] instructions to use when playing a [Song]. */ val playWith get() = playbackSettings.playInListWith + private val _albumList = MutableStateFlow(listOf()) + /** A list of [Album]s, sorted by the preferred [Sort], to be shown in the home view. */ + val albumList: StateFlow> + get() = _albumList + + private val _albumInstructions = MutableEvent() + /** Instructions for how to update [albumList] in the UI. */ + val albumInstructions: Event + get() = _albumInstructions + + /** The current [Sort] used for [albumList]. */ + val albumSort: Sort + get() = musicSettings.albumSort + + private val _artistList = MutableStateFlow(listOf()) + /** + * A list of [Artist]s, sorted by the preferred [Sort], to be shown in the home view. Note that + * if "Hide collaborators" is on, this list will not include collaborator [Artist]s. + */ + val artistList: MutableStateFlow> + get() = _artistList + + private val _artistInstructions = MutableEvent() + /** Instructions for how to update [artistList] in the UI. */ + val artistInstructions: Event + get() = _artistInstructions + + /** The current [Sort] used for [artistList]. */ + val artistSort: Sort + get() = musicSettings.artistSort + + private val _genreList = MutableStateFlow(listOf()) + /** A list of [Genre]s, sorted by the preferred [Sort], to be shown in the home view. */ + val genreList: StateFlow> + get() = _genreList + + private val _genreInstructions = MutableEvent() + /** Instructions for how to update [genreList] in the UI. */ + val genreInstructions: Event + get() = _genreInstructions + + /** The current [Sort] used for [genreList]. */ + val genreSort: Sort + get() = musicSettings.genreSort + + private val _playlistList = MutableStateFlow(listOf()) + /** A list of [Playlist]s, sorted by the preferred [Sort], to be shown in the home view. */ + val playlistList: StateFlow> + get() = _playlistList + + private val _playlistInstructions = MutableEvent() + /** Instructions for how to update [genreList] in the UI. */ + val playlistInstructions: Event + get() = _playlistInstructions + + /** The current [Sort] used for [genreList]. */ + val playlistSort: Sort + get() = musicSettings.playlistSort + /** * A list of [MusicType] corresponding to the current [Tab] configuration, excluding invisible * [Tab]s. @@ -157,12 +177,12 @@ constructor( logD("Refreshing library") // Get the each list of items in the library to use as our list data. // Applying the preferred sorting to them. - _songsInstructions.put(UpdateInstructions.Diff) - _songsList.value = musicSettings.songSort.songs(deviceLibrary.songs) - _albumsInstructions.put(UpdateInstructions.Diff) - _albumsLists.value = musicSettings.albumSort.albums(deviceLibrary.albums) - _artistsInstructions.put(UpdateInstructions.Diff) - _artistsList.value = + _songInstructions.put(UpdateInstructions.Diff) + _songList.value = musicSettings.songSort.songs(deviceLibrary.songs) + _albumInstructions.put(UpdateInstructions.Diff) + _albumList.value = musicSettings.albumSort.albums(deviceLibrary.albums) + _artistInstructions.put(UpdateInstructions.Diff) + _artistList.value = musicSettings.artistSort.artists( if (homeSettings.shouldHideCollaborators) { logD("Filtering collaborator artists") @@ -172,15 +192,15 @@ constructor( logD("Using all artists") deviceLibrary.artists }) - _genresInstructions.put(UpdateInstructions.Diff) - _genresList.value = musicSettings.genreSort.genres(deviceLibrary.genres) + _genreInstructions.put(UpdateInstructions.Diff) + _genreList.value = musicSettings.genreSort.genres(deviceLibrary.genres) } val userLibrary = musicRepository.userLibrary if (changes.userLibrary && userLibrary != null) { logD("Refreshing playlists") - _playlistsInstructions.put(UpdateInstructions.Diff) - _playlistsList.value = musicSettings.playlistSort.playlists(userLibrary.playlists) + _playlistInstructions.put(UpdateInstructions.Diff) + _playlistList.value = musicSettings.playlistSort.playlists(userLibrary.playlists) } } @@ -199,59 +219,58 @@ constructor( } /** - * Get the preferred [Sort] for a given [Tab]. + * Apply a new [Sort] to [songList]. * - * @param tabType The [MusicType] of the [Tab] desired. - * @return The [Sort] preferred for that [Tab] + * @param sort The [Sort] to apply. */ - fun getSortForTab(tabType: MusicType) = - when (tabType) { - MusicType.SONGS -> musicSettings.songSort - MusicType.ALBUMS -> musicSettings.albumSort - MusicType.ARTISTS -> musicSettings.artistSort - MusicType.GENRES -> musicSettings.genreSort - MusicType.PLAYLISTS -> musicSettings.playlistSort - } + fun applySongSort(sort: Sort) { + musicSettings.songSort = sort + _songInstructions.put(UpdateInstructions.Replace(0)) + _songList.value = musicSettings.songSort.songs(_songList.value) + } /** - * Update the preferred [Sort] for the current [Tab]. Will update corresponding list. + * Apply a new [Sort] to [albumList]. * - * @param sort The new [Sort] to apply. Assumed to be an allowed sort for the current [Tab]. + * @param sort The [Sort] to apply. */ - fun setSortForCurrentTab(sort: Sort) { - // Can simply re-sort the current list of items without having to access the library. - when (val type = _currentTabType.value) { - MusicType.SONGS -> { - logD("Updating song [$type] sort mode to $sort") - musicSettings.songSort = sort - _songsInstructions.put(UpdateInstructions.Replace(0)) - _songsList.value = sort.songs(_songsList.value) - } - MusicType.ALBUMS -> { - logD("Updating album [$type] sort mode to $sort") - musicSettings.albumSort = sort - _albumsInstructions.put(UpdateInstructions.Replace(0)) - _albumsLists.value = sort.albums(_albumsLists.value) - } - MusicType.ARTISTS -> { - logD("Updating artist [$type] sort mode to $sort") - musicSettings.artistSort = sort - _artistsInstructions.put(UpdateInstructions.Replace(0)) - _artistsList.value = sort.artists(_artistsList.value) - } - MusicType.GENRES -> { - logD("Updating genre [$type] sort mode to $sort") - musicSettings.genreSort = sort - _genresInstructions.put(UpdateInstructions.Replace(0)) - _genresList.value = sort.genres(_genresList.value) - } - MusicType.PLAYLISTS -> { - logD("Updating playlist [$type] sort mode to $sort") - musicSettings.playlistSort = sort - _playlistsInstructions.put(UpdateInstructions.Replace(0)) - _playlistsList.value = sort.playlists(_playlistsList.value) - } - } + fun applyAlbumSort(sort: Sort) { + musicSettings.albumSort = sort + _albumInstructions.put(UpdateInstructions.Replace(0)) + _albumList.value = musicSettings.albumSort.albums(_albumList.value) + } + + /** + * Apply a new [Sort] to [artistList]. + * + * @param sort The [Sort] to apply. + */ + fun applyArtistSort(sort: Sort) { + musicSettings.artistSort = sort + _artistInstructions.put(UpdateInstructions.Replace(0)) + _artistList.value = musicSettings.artistSort.artists(_artistList.value) + } + + /** + * Apply a new [Sort] to [genreList]. + * + * @param sort The [Sort] to apply. + */ + fun applyGenreSort(sort: Sort) { + musicSettings.genreSort = sort + _genreInstructions.put(UpdateInstructions.Replace(0)) + _genreList.value = musicSettings.genreSort.genres(_genreList.value) + } + + /** + * Apply a new [Sort] to [playlistList]. + * + * @param sort The [Sort] to apply. + */ + fun applyPlaylistSort(sort: Sort) { + musicSettings.playlistSort = sort + _playlistInstructions.put(UpdateInstructions.Replace(0)) + _playlistList.value = musicSettings.playlistSort.playlists(_playlistList.value) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt index ce8fcfb20..56f3a4d80 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.text.format.DateUtils import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import dagger.hilt.android.AndroidEntryPoint @@ -40,13 +39,13 @@ import org.oxycblt.auxio.list.recycler.AlbumViewHolder import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.playback.secsToMs import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.logD /** * A [ListFragment] that shows a list of [Album]s. @@ -81,7 +80,7 @@ class AlbumListFragment : listener = this@AlbumListFragment } - collectImmediately(homeModel.albumsList, ::updateAlbums) + collectImmediately(homeModel.albumList, ::updateAlbums) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -97,9 +96,9 @@ class AlbumListFragment : } override fun getPopup(pos: Int): String? { - val album = homeModel.albumsList.value[pos] + val album = homeModel.albumList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicType.ALBUMS).mode) { + return when (homeModel.albumSort.mode) { // By Name -> Use Name is Sort.Mode.ByName -> album.name.thumb @@ -141,12 +140,13 @@ class AlbumListFragment : detailModel.showAlbum(item) } - override fun onOpenMenu(item: Album, anchor: View) { + override fun onOpenMenu(item: Album) { listModel.openMenu(R.menu.item_album, item) } private fun updateAlbums(albums: List) { - albumAdapter.update(albums, homeModel.albumsInstructions.consume()) + logD("Absolute fucking retard") + albumAdapter.update(albums, homeModel.albumInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt index 8cda450e7..920231d2a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import dagger.hilt.android.AndroidEntryPoint @@ -38,7 +37,6 @@ import org.oxycblt.auxio.list.recycler.ArtistViewHolder import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -76,7 +74,7 @@ class ArtistListFragment : listener = this@ArtistListFragment } - collectImmediately(homeModel.artistsList, ::updateArtists) + collectImmediately(homeModel.artistList, ::updateArtists) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -92,9 +90,9 @@ class ArtistListFragment : } override fun getPopup(pos: Int): String? { - val artist = homeModel.artistsList.value[pos] + val artist = homeModel.artistList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicType.ARTISTS).mode) { + return when (homeModel.artistSort.mode) { // By Name -> Use Name is Sort.Mode.ByName -> artist.name.thumb @@ -117,12 +115,12 @@ class ArtistListFragment : detailModel.showArtist(item) } - override fun onOpenMenu(item: Artist, anchor: View) { + override fun onOpenMenu(item: Artist) { listModel.openMenu(R.menu.item_parent, item) } private fun updateArtists(artists: List) { - artistAdapter.update(artists, homeModel.artistsInstructions.consume()) + artistAdapter.update(artists, homeModel.artistInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt index bba02ff20..381422748 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import dagger.hilt.android.AndroidEntryPoint @@ -38,7 +37,6 @@ import org.oxycblt.auxio.list.recycler.GenreViewHolder import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -75,7 +73,7 @@ class GenreListFragment : listener = this@GenreListFragment } - collectImmediately(homeModel.genresList, ::updateGenres) + collectImmediately(homeModel.genreList, ::updateGenres) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -91,9 +89,9 @@ class GenreListFragment : } override fun getPopup(pos: Int): String? { - val genre = homeModel.genresList.value[pos] + val genre = homeModel.genreList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicType.GENRES).mode) { + return when (homeModel.genreSort.mode) { // By Name -> Use Name is Sort.Mode.ByName -> genre.name.thumb @@ -116,12 +114,12 @@ class GenreListFragment : detailModel.showGenre(item) } - override fun onOpenMenu(item: Genre, anchor: View) { + override fun onOpenMenu(item: Genre) { listModel.openMenu(R.menu.item_parent, item) } private fun updateGenres(genres: List) { - genreAdapter.update(genres, homeModel.genresInstructions.consume()) + genreAdapter.update(genres, homeModel.genreInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt index 164b0da92..a36db3873 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/PlaylistListFragment.kt @@ -20,7 +20,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import org.oxycblt.auxio.R @@ -36,7 +35,6 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.PlaylistViewHolder import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song @@ -73,7 +71,7 @@ class PlaylistListFragment : listener = this@PlaylistListFragment } - collectImmediately(homeModel.playlistsList, ::updatePlaylists) + collectImmediately(homeModel.playlistList, ::updatePlaylists) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -89,9 +87,9 @@ class PlaylistListFragment : } override fun getPopup(pos: Int): String? { - val playlist = homeModel.playlistsList.value[pos] + val playlist = homeModel.playlistList.value[pos] // Change how we display the popup depending on the current sort mode. - return when (homeModel.getSortForTab(MusicType.GENRES).mode) { + return when (homeModel.playlistSort.mode) { // By Name -> Use Name is Sort.Mode.ByName -> playlist.name.thumb @@ -114,12 +112,12 @@ class PlaylistListFragment : detailModel.showPlaylist(item) } - override fun onOpenMenu(item: Playlist, anchor: View) { + override fun onOpenMenu(item: Playlist) { listModel.openMenu(R.menu.item_playlist, item) } private fun updatePlaylists(playlists: List) { - playlistAdapter.update(playlists, homeModel.playlistsInstructions.consume()) + playlistAdapter.update(playlists, homeModel.playlistInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index 26b9282ea..38e63a5bd 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -21,7 +21,6 @@ package org.oxycblt.auxio.home.list import android.os.Bundle import android.text.format.DateUtils import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import dagger.hilt.android.AndroidEntryPoint @@ -38,7 +37,6 @@ import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.recycler.SongViewHolder import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -78,7 +76,7 @@ class SongListFragment : listener = this@SongListFragment } - collectImmediately(homeModel.songsList, ::updateSongs) + collectImmediately(homeModel.songList, ::updateSongs) collectImmediately(listModel.selected, ::updateSelection) collectImmediately( playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) @@ -94,11 +92,11 @@ class SongListFragment : } override fun getPopup(pos: Int): String? { - val song = homeModel.songsList.value[pos] + val song = homeModel.songList.value[pos] // Change how we display the popup depending on the current sort mode. // Note: We don't use the more correct individual artist name here, as sorts are largely // based off the names of the parent objects and not the child objects. - return when (homeModel.getSortForTab(MusicType.SONGS).mode) { + return when (homeModel.songSort.mode) { // Name -> Use name is Sort.Mode.ByName -> song.name.thumb @@ -140,12 +138,12 @@ class SongListFragment : playbackModel.play(item, homeModel.playWith) } - override fun onOpenMenu(item: Song, anchor: View) { + override fun onOpenMenu(item: Song) { listModel.openMenu(R.menu.item_song, item, homeModel.playWith) } private fun updateSongs(songs: List) { - songAdapter.update(songs, homeModel.songsInstructions.consume()) + songAdapter.update(songs, homeModel.songInstructions.consume()) } private fun updateSelection(selection: List) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/AlbumSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/AlbumSortDialog.kt new file mode 100644 index 000000000..82426eb71 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/AlbumSortDialog.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Auxio Project + * AlbumSortDialog.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 . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.albumList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class AlbumSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.albumSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applyAlbumSort(sort) + } + + override fun getModeChoices() = + listOf( + Sort.Mode.ByName, + Sort.Mode.ByArtist, + Sort.Mode.ByDate, + Sort.Mode.ByDuration, + Sort.Mode.ByCount, + Sort.Mode.ByDateAdded) +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/ArtistSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/ArtistSortDialog.kt new file mode 100644 index 000000000..5c02f777b --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/ArtistSortDialog.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Auxio Project + * ArtistSortDialog.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 . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.artistList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class ArtistSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.artistSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applyArtistSort(sort) + } + + override fun getModeChoices() = + listOf(Sort.Mode.ByName, Sort.Mode.ByDuration, Sort.Mode.ByCount) +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/GenreSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/GenreSortDialog.kt new file mode 100644 index 000000000..96093c9a9 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/GenreSortDialog.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Auxio Project + * GenreSortDialog.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 . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.genreList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class GenreSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.genreSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applyGenreSort(sort) + } + + override fun getModeChoices() = + listOf(Sort.Mode.ByName, Sort.Mode.ByDuration, Sort.Mode.ByCount) +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/PlaylistSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/PlaylistSortDialog.kt new file mode 100644 index 000000000..eb89c4bdd --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/PlaylistSortDialog.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Auxio Project + * PlaylistSortDialog.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 . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.playlistList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class PlaylistSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.playlistSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applyPlaylistSort(sort) + } + + override fun getModeChoices() = + listOf(Sort.Mode.ByName, Sort.Mode.ByDuration, Sort.Mode.ByCount) +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/sort/SongSortDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/sort/SongSortDialog.kt new file mode 100644 index 000000000..bec18719a --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/sort/SongSortDialog.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Auxio Project + * SongSortDialog.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 . + */ + +package org.oxycblt.auxio.home.sort + +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.oxycblt.auxio.home.HomeViewModel +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.sort.SortDialog + +/** + * A [SortDialog] that controls the [Sort] of [HomeViewModel.songList]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +@AndroidEntryPoint +class SongSortDialog : SortDialog() { + private val homeModel: HomeViewModel by activityViewModels() + + override fun getInitialSort() = homeModel.songSort + + override fun applyChosenSort(sort: Sort) { + homeModel.applySongSort(sort) + } + + override fun getModeChoices() = + listOf( + Sort.Mode.ByName, + Sort.Mode.ByArtist, + Sort.Mode.ByAlbum, + Sort.Mode.ByDate, + Sort.Mode.ByDuration, + Sort.Mode.ByDateAdded) +} diff --git a/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt b/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt index 4d2d45c14..546b03a49 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/ListFragment.kt @@ -18,14 +18,9 @@ package org.oxycblt.auxio.list -import android.view.View -import androidx.annotation.MenuRes -import androidx.appcompat.widget.PopupMenu -import androidx.core.view.MenuCompat import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import org.oxycblt.auxio.music.Music -import org.oxycblt.auxio.util.logD /** * A Fragment containing a selectable list. @@ -34,14 +29,6 @@ import org.oxycblt.auxio.util.logD */ abstract class ListFragment : SelectionFragment(), SelectableListListener { - private var currentMenu: PopupMenu? = null - - override fun onDestroyBinding(binding: VB) { - super.onDestroyBinding(binding) - currentMenu?.dismiss() - currentMenu = null - } - /** * Called when [onClick] is called, but does not result in the item being selected. This more or * less corresponds to an [onClick] implementation in a non-[ListFragment]. @@ -63,30 +50,4 @@ abstract class ListFragment : final override fun onSelect(item: T) { listModel.select(item) } - - /** - * Open a menu. This menu will be managed by the Fragment and closed when the view is destroyed. - * If a menu is already opened, this call is ignored. - * - * @param anchor The [View] to anchor the menu to. - * @param menuRes The resource of the menu to load. - * @param block A block that is ran within [PopupMenu] that allows further configuration. - */ - protected fun openMenu(anchor: View, @MenuRes menuRes: Int, block: PopupMenu.() -> Unit) { - if (currentMenu != null) { - logD("Menu already present, not launching") - return - } - - logD("Opening popup menu menu") - - currentMenu = - PopupMenu(requireContext(), anchor).apply { - inflate(menuRes) - MenuCompat.setGroupDividerEnabled(menu, true) - block() - setOnDismissListener { currentMenu = null } - show() - } - } } diff --git a/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt b/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt index d728a6142..c7704e503 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/Listeners.kt @@ -115,9 +115,8 @@ interface SelectableListListener : ClickableListListener { * Called when an item in the list requests that a menu related to it should be opened. * * @param item The [T] item to open a menu for. - * @param anchor The [View] to anchor the menu to. */ - fun onOpenMenu(item: T, anchor: View) + fun onOpenMenu(item: T) /** * Called when an item in the list requests that it be selected. @@ -148,6 +147,6 @@ interface SelectableListListener : ClickableListListener { true } // Map the menu button to the menu opening listener. - menuButton.setOnClickListener { onOpenMenu(item, it) } + menuButton.setOnClickListener { onOpenMenu(item) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/list/Sort.kt b/app/src/main/java/org/oxycblt/auxio/list/Sort.kt index 8a7203182..555fd6b9a 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/Sort.kt @@ -18,7 +18,6 @@ package org.oxycblt.auxio.list -import androidx.annotation.IdRes import kotlin.math.max import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R @@ -163,8 +162,8 @@ data class Sort(val mode: Mode, val direction: Direction) { sealed interface Mode { /** The integer representation of this sort mode. */ val intCode: Int - /** The item ID of this sort mode in menu resources. */ - val itemId: Int + /** The string resource of the human-readable name of this sort mode. */ + val stringRes: Int /** * Get a [Comparator] that sorts [Song]s according to this [Mode]. @@ -220,8 +219,8 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_NAME - override val itemId: Int - get() = R.id.option_sort_name + override val stringRes: Int + get() = R.string.lbl_name override fun getSongComparator(direction: Direction) = compareByDynamic(direction, BasicComparator.SONG) @@ -248,8 +247,8 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_ALBUM - override val itemId: Int - get() = R.id.option_sort_album + override val stringRes: Int + get() = R.string.lbl_album override fun getSongComparator(direction: Direction): Comparator = MultiComparator( @@ -268,8 +267,8 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_ARTIST - override val itemId: Int - get() = R.id.option_sort_artist + override val stringRes: Int + get() = R.string.lbl_artist override fun getSongComparator(direction: Direction): Comparator = MultiComparator( @@ -297,8 +296,8 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_YEAR - override val itemId: Int - get() = R.id.option_sort_year + override val stringRes: Int + get() = R.string.lbl_date override fun getSongComparator(direction: Direction): Comparator = MultiComparator( @@ -319,8 +318,8 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_DURATION - override val itemId: Int - get() = R.id.option_sort_duration + override val stringRes: Int + get() = R.string.lbl_duration override fun getSongComparator(direction: Direction): Comparator = MultiComparator( @@ -354,8 +353,8 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_COUNT - override val itemId: Int - get() = R.id.option_sort_count + override val stringRes: Int + get() = R.string.lbl_song_count override fun getAlbumComparator(direction: Direction): Comparator = MultiComparator( @@ -385,8 +384,8 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_DISC - override val itemId: Int - get() = R.id.option_sort_disc + override val stringRes: Int + get() = R.string.lbl_disc override fun getSongComparator(direction: Direction): Comparator = MultiComparator( @@ -404,8 +403,8 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_TRACK - override val itemId: Int - get() = R.id.option_sort_track + override val stringRes: Int + get() = R.string.lbl_track override fun getSongComparator(direction: Direction): Comparator = MultiComparator( @@ -424,8 +423,8 @@ data class Sort(val mode: Mode, val direction: Direction) { override val intCode: Int get() = IntegerTable.SORT_BY_DATE_ADDED - override val itemId: Int - get() = R.id.option_sort_date_added + override val stringRes: Int + get() = R.string.lbl_date_added override fun getSongComparator(direction: Direction): Comparator = MultiComparator( @@ -458,27 +457,6 @@ data class Sort(val mode: Mode, val direction: Direction) { ByDateAdded.intCode -> ByDateAdded else -> null } - - /** - * Convert a menu item ID into a [Mode]. - * - * @param itemId The menu resource ID to convert - * @return A [Mode] corresponding to the given ID, or null if the ID is invalid. - * @see itemId - */ - fun fromItemId(@IdRes itemId: Int) = - when (itemId) { - ByName.itemId -> ByName - ByAlbum.itemId -> ByAlbum - ByArtist.itemId -> ByArtist - ByDate.itemId -> ByDate - ByDuration.itemId -> ByDuration - ByCount.itemId -> ByCount - ByDisc.itemId -> ByDisc - ByTrack.itemId -> ByTrack - ByDateAdded.itemId -> ByDateAdded - else -> null - } } } diff --git a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuItemAdapter.kt b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuItemAdapter.kt index 1fd336c34..d5b5158f4 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/menu/MenuItemAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/menu/MenuItemAdapter.kt @@ -44,7 +44,7 @@ class MenuItemAdapter(private val listener: ClickableListListener) : } /** - * A [DialogRecyclerView.ViewHolder] that displays a list of menu options based on [MenuItem]. + * A [DialogRecyclerView.ViewHolder] that displays a [MenuItem]. * * @author Alexander Capehart (OxygenCobalt) */ @@ -54,7 +54,7 @@ class MenuItemViewHolder private constructor(private val binding: ItemMenuOption * Bind new data to this instance. * * @param item The new [MenuItem] to bind. - * @param listener An [ClickableListListener] to bind interactions to. + * @param listener A [ClickableListListener] to bind interactions to. */ fun bind(item: MenuItem, listener: ClickableListListener) { listener.bind(item, this) diff --git a/app/src/main/java/org/oxycblt/auxio/list/sort/SortDialog.kt b/app/src/main/java/org/oxycblt/auxio/list/sort/SortDialog.kt index 79bd9082b..f6391ce87 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/sort/SortDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/sort/SortDialog.kt @@ -1,6 +1,6 @@ /* * Copyright (c) 2023 Auxio Project - * SortDialog.kt is part of Auxio. + * MenuDialogFragment.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 @@ -16,29 +16,126 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.list.sort +package org.oxycblt.auxio.list.menu +import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater -import androidx.core.view.updatePadding -import org.oxycblt.auxio.databinding.DialogSortBinding -import org.oxycblt.auxio.list.Sort +import android.view.MenuInflater +import android.view.MenuItem +import androidx.appcompat.view.menu.MenuBuilder +import androidx.core.view.children +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.databinding.DialogMenuBinding +import org.oxycblt.auxio.list.ClickableListListener +import org.oxycblt.auxio.list.ListViewModel +import org.oxycblt.auxio.list.Menu import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.ui.ViewBindingBottomSheetDialogFragment -import org.oxycblt.auxio.util.systemBarInsetsCompat +import org.oxycblt.auxio.util.collectImmediately +import org.oxycblt.auxio.util.logD -class SortDialog : ViewBindingBottomSheetDialogFragment() { - private val sortAdapter = SortAdapter(Sort.Mode.ByName) +/** + * A [ViewBindingBottomSheetDialogFragment] that displays basic music information and a series of + * options. + * + * @author Alexander Capehart (OxygenCobalt) + * + * TODO: Extend the amount of music info shown in the dialog + */ +abstract class MenuDialogFragment : + ViewBindingBottomSheetDialogFragment(), ClickableListListener { + protected abstract val menuModel: MenuViewModel + protected abstract val listModel: ListViewModel + private val menuAdapter = MenuItemAdapter(@Suppress("LeakingThis") this) - override fun onCreateBinding(inflater: LayoutInflater) = DialogSortBinding.inflate(inflater) + abstract val parcel: Menu.Parcel - override fun onBindingCreated(binding: DialogSortBinding, savedInstanceState: Bundle?) { + /** + * Get the options to disable in the context of the currently shown [M]. + * + * @param menu The currently-shown menu [M]. + */ + abstract fun getDisabledItemIds(menu: M): Set + + /** + * Update the displayed information about the currently shown [M]. + * + * @param binding The [DialogMenuBinding] to bind information to. + * @param menu The currently-shown menu [M]. + */ + abstract fun updateMenu(binding: DialogMenuBinding, menu: M) + + /** + * Forward the clicked [MenuItem] to it's corresponding handler in another module. + * + * @param item The [MenuItem] that was clicked. + * @param menu The currently-shown menu [M]. + */ + abstract fun onClick(item: MenuItem, menu: M) + + override fun onCreateBinding(inflater: LayoutInflater) = DialogMenuBinding.inflate(inflater) + + override fun onBindingCreated(binding: DialogMenuBinding, savedInstanceState: Bundle?) { super.onBindingCreated(binding, savedInstanceState) - binding.root.setOnApplyWindowInsetsListener { v, insets -> - v.updatePadding(bottom = insets.systemBarInsetsCompat.bottom) - insets + + // --- UI SETUP --- + binding.menuName.isSelected = true + binding.menuInfo.isSelected = true + binding.menuOptionRecycler.apply { + adapter = menuAdapter + itemAnimator = null } - binding.sortModeRecycler.adapter = sortAdapter - sortAdapter.update(listOf(Sort.Mode.ByName, Sort.Mode.ByDate), UpdateInstructions.Diff) + + // --- VIEWMODEL SETUP --- + listModel.menu.consume() + menuModel.setMenu(parcel) + collectImmediately(menuModel.currentMenu, this::updateMenu) } -} + + override fun onDestroyBinding(binding: DialogMenuBinding) { + super.onDestroyBinding(binding) + binding.menuName.isSelected = false + binding.menuInfo.isSelected = false + binding.menuOptionRecycler.adapter = null + } + + private fun updateMenu(menu: Menu?) { + if (menu == null) { + logD("No menu to show, navigating away") + findNavController().navigateUp() + return + } + + @Suppress("UNCHECKED_CAST") val casted = menu as? M + check(casted != null) { "Unexpected menu instance ${menu::class.simpleName}" } + + // We need to inflate the menu on every menu update since it might have changed + // what options are available (ex. if an artist with no songs has had new songs added). + // Since we don't have (and don't want) a dummy view to inflate this menu, just + // depend on the AndroidX Toolbar internal API and hope for the best. + @SuppressLint("RestrictedApi") val builder = MenuBuilder(requireContext()) + MenuInflater(requireContext()).inflate(casted.res, builder) + + // Disable any menu options as specified by the impl + val disabledIds = getDisabledItemIds(casted) + val visible = + builder.children.mapTo(mutableListOf()) { + it.isEnabled = !disabledIds.contains(it.itemId) + it + } + menuAdapter.update(visible, UpdateInstructions.Diff) + + // Delegate to impl how to show music + updateMenu(requireBinding(), casted) + } + + final override fun onClick(item: MenuItem, viewHolder: RecyclerView.ViewHolder) { + // All option selections close the dialog currently. + // TODO: This should change if the app is 100% migrated to menu dialogs + findNavController().navigateUp() + // Delegate to impl on how to handle items + @Suppress("UNCHECKED_CAST") onClick(item, menuModel.currentMenu.value as M) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/list/sort/SortModeAdapter.kt b/app/src/main/java/org/oxycblt/auxio/list/sort/SortModeAdapter.kt new file mode 100644 index 000000000..d945a61ef --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/list/sort/SortModeAdapter.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023 Auxio Project + * SortModeAdapter.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 . + */ + +package org.oxycblt.auxio.list.sort + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import org.oxycblt.auxio.databinding.ItemSortModeBinding +import org.oxycblt.auxio.list.ClickableListListener +import org.oxycblt.auxio.list.Sort +import org.oxycblt.auxio.list.adapter.FlexibleListAdapter +import org.oxycblt.auxio.list.recycler.DialogRecyclerView +import org.oxycblt.auxio.util.context +import org.oxycblt.auxio.util.inflater + +/** + * A [FlexibleListAdapter] that displays a list of [Sort.Mode]s. + * + * @param listener A [ClickableListListener] to bind interactions to. + * @author Alexander Capehart (OxygenCobalt) + */ +class SortModeAdapter(private val listener: ClickableListListener) : + FlexibleListAdapter(SortModeViewHolder.DIFF_CALLBACK) { + /** The currently selected [Sort.Mode] item in this adapter. */ + var currentMode: Sort.Mode? = null + private set + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + SortModeViewHolder.from(parent) + + override fun onBindViewHolder(holder: SortModeViewHolder, position: Int) { + throw NotImplementedError() + } + + override fun onBindViewHolder(holder: SortModeViewHolder, position: Int, payload: List) { + val mode = getItem(position) + if (payload.isEmpty()) { + holder.bind(mode, listener) + } + holder.setSelected(mode == currentMode) + } + + /** + * Select a new [Sort.Mode] option, unselecting the prior one. Does nothing if [mode] equals + * [currentMode]. + * + * @param mode The new [Sort.Mode] to select. Should be in the adapter data. + */ + fun setSelected(mode: Sort.Mode) { + if (mode == currentMode) return + val oldMode = currentList.indexOf(currentMode) + val newMode = currentList.indexOf(mode) + currentMode = mode + if (oldMode > -1) { + notifyItemChanged(oldMode, PAYLOAD_SELECTION_CHANGED) + } + notifyItemChanged(newMode, PAYLOAD_SELECTION_CHANGED) + } + + private companion object { + val PAYLOAD_SELECTION_CHANGED = Any() + } +} + +/** + * A [DialogRecyclerView.ViewHolder] that displays a [Sort.Mode]. + * + * @author Alexander Capehart (OxygenCobalt) + */ +class SortModeViewHolder private constructor(private val binding: ItemSortModeBinding) : + DialogRecyclerView.ViewHolder(binding.root) { + /** + * Bind new data to this instance. + * + * @param mode The new [Sort.Mode] to bind. + * @param listener A [ClickableListListener] to bind interactions to. + */ + fun bind(mode: Sort.Mode, listener: ClickableListListener) { + listener.bind(mode, this) + binding.sortRadio.text = binding.context.getString(mode.stringRes) + } + + /** + * Set if this view should be shown as selected or not. + * + * @param selected True if selected, false if not. + */ + fun setSelected(selected: Boolean) { + binding.sortRadio.isChecked = selected + } + + companion object { + fun from(parent: View) = + SortModeViewHolder(ItemSortModeBinding.inflate(parent.context.inflater)) + + val DIFF_CALLBACK = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Sort.Mode, newItem: Sort.Mode) = + oldItem == newItem + + override fun areContentsTheSame(oldItem: Sort.Mode, newItem: Sort.Mode) = + oldItem == newItem + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index 27abdeab1..1a3a29d5f 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -182,7 +182,7 @@ class SearchFragment : ListFragment() { } } - override fun onOpenMenu(item: Music, anchor: View) { + override fun onOpenMenu(item: Music) { when (item) { is Song -> listModel.openMenu(R.menu.item_song, item, searchModel.playWith) is Album -> listModel.openMenu(R.menu.item_album, item) diff --git a/app/src/main/res/layout/dialog_sort.xml b/app/src/main/res/layout/dialog_sort.xml index 547c7476d..4fa676852 100644 --- a/app/src/main/res/layout/dialog_sort.xml +++ b/app/src/main/res/layout/dialog_sort.xml @@ -5,18 +5,17 @@ android:layout_height="match_parent" android:orientation="vertical"> - - + - + android:text="@string/lbl_sort_dsc" /> + app:layout_constraintTop_toBottomOf="@+id/sort_direction_group" /> diff --git a/app/src/main/res/menu/sort_album.xml b/app/src/main/res/menu/sort_album.xml deleted file mode 100644 index 44f9744aa..000000000 --- a/app/src/main/res/menu/sort_album.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/sort_artist.xml b/app/src/main/res/menu/sort_artist.xml deleted file mode 100644 index 50ba847ba..000000000 --- a/app/src/main/res/menu/sort_artist.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/sort_genre.xml b/app/src/main/res/menu/sort_genre.xml deleted file mode 100644 index ad5d920c8..000000000 --- a/app/src/main/res/menu/sort_genre.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/toolbar_home.xml b/app/src/main/res/menu/toolbar_home.xml index 278206a03..9aa0360de 100644 --- a/app/src/main/res/menu/toolbar_home.xml +++ b/app/src/main/res/menu/toolbar_home.xml @@ -9,46 +9,10 @@ app:showAsAction="ifRoom" /> - - - - - - - - - - - - - - - - + app:showAsAction="ifRoom" /> + + + + + @@ -65,6 +80,36 @@ app:destination="@id/play_from_genre_dialog" /> + + + + + + + + + + + @@ -167,6 +215,12 @@ app:destination="@id/sort_dialog" /> + + + @@ -198,6 +255,12 @@ app:destination="@id/play_from_genre_dialog" /> + + + @@ -232,6 +298,12 @@ app:destination="@id/play_from_artist_dialog" /> + + تشغيل كل الاغاني بشكل عشوائي حسنا اعادة الحالة - تنازلي + تنازلي عرض الخصائص مسح الحالة مباشر diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 8689e2dda..fcbf1ee96 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -61,7 +61,7 @@ تاريخ الإضافة فرز تصاعدياً - تنازلياً + تنازلياً يتم الآن تشغيل المُعادِل تشغيل diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index e49035776..1b783cd27 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -77,7 +77,7 @@ Ператасаваць Адмяніць Па ўзрастанні - Па змяншэнні + Па змяншэнні Гуляць далей Дадаць у чаргу Эквалайзер diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 89bc807df..d53bff062 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -279,7 +279,7 @@ Přehrávání Knihovna Perzistence - Sestupně + Sestupně Seznamy skladeb Obrázek seznamu skladeb pro %s Seznam skladeb diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 00b9ac5dc..09d153413 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -270,7 +270,7 @@ Ton und Wiedergabeverhalten konfigurieren Persistenz Lautstärkeanpassung ReplayGain - Absteigend + Absteigend Wiedergabelistenbild für %s Wiedergabeliste Wiedergabelisten diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 5b87b0574..c2e376431 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -274,7 +274,7 @@ Cambiar el tema y los colores de la aplicación Personalizar los controles y el comportamiento de la interfaz de usuario Biblioteca - Descendente + Descendente Listas de reproducción Imagen de la lista de reproducción para %s Lista de reproducción diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index ffef6ab37..1f222e0a7 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -33,7 +33,7 @@ Levy Raita Lisäyspäivä - Laskevasti + Laskevasti Nyt toistetaan Taajuuskorjain Toista diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4ee073246..822484268 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -157,7 +157,7 @@ Surveillance de votre bibliothèque musicale pour les changements… Couvertures arrondies Activer les coins arrondis sur des éléments d\'interface utilisateur supplémentaires (nécessite que les couvertures d\'album soient arrondies) - Descendant + Descendant Etat restauré Personnaliser les commandes et le comportement de l\'interface utilisateur Passer au suivant diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index b00782948..5c3288e6a 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -124,7 +124,7 @@ EP en directo EP remix Ascendente - Descendente + Descendente Ecualizador Aleatorio seleccionado Frecuencia de mostraxe diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index dfc50b8eb..e34f480f0 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -99,7 +99,7 @@ सभी गीतों से चलाएं %s हटाएँ\? इसे पूर्ववत नहीं किया जा सकता। लोड किए गए गाने: %d - अवरोही + अवरोही चयनित चलाएँ फेरबदल का चयन किया गया स्थिति साफ की गई diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 8ab7b06c6..1ce8b10cd 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -255,7 +255,7 @@ Resetiraj ReplayGain izjednačavanje glasnoće Mape - Silazni + Silazni Promijenite temu i boje aplikacije Prilagodite kontrole i ponašanje korisničkog sučelja Upravljajte učitavanjem glazbe i slika diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 254093dda..6c0b11c88 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -74,7 +74,7 @@ Remix EP Név Dátum - Csökkenő + Csökkenő Kiválasztott lejátszása Új lejátszólista Ismeretlen műfaj diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 0107cf837..483d5b02f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -274,7 +274,7 @@ Persistenza Personalizza controlli e comportamento dell\'UI Configura comportamento di suono e riproduzione - Discendente + Discendente Playlist Playlist Ordinazione intelligente diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index e439eaf5a..31b5677fd 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -36,7 +36,7 @@ תאריך הוספה מיון עולה - יורד + יורד מושמע כעת איקוולייזר ניגון diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index b571ec42e..21849864b 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -60,7 +60,7 @@ ファイル名 追加した日付け サンプルレート - 降順 + 降順 再生 シャフル 選択曲をシャフル diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 2de3ff1bd..6716d4d77 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -270,7 +270,7 @@ 지속 동작 UI 제어 및 동작 커스텀 - 내림차순 + 내림차순 재생목록 재생목록 %s의 재생 목록 이미지 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index e2bc12bf5..caa995e31 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -267,7 +267,7 @@ ReplayGain Aplankalai Pastovumas - Mažėjantis + Mažėjantis Teisingai surūšiuoti pavadinimus, kurie prasideda skaičiais arba žodžiais, tokiais kaip „the“ (geriausiai veikia su anglų kalbos muzika) Išmanusis rūšiavimas Grojaraštis diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 1f05558d8..82f0ffe92 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -90,7 +90,7 @@ കലാകാരനിലേക്ക് പോകുക സവിശേഷതകൾ കാണുക സ്ഥിതി സംരക്ഷിച്ചു - അവരോഹണം + അവരോഹണം സ്ഥിതി പുനഃസ്ഥാപിച്ചു വിക്കി സ്ഥിതി മായ്ച്ചു diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index a7e904bb3..5913008c0 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -201,7 +201,7 @@ %d album %d album - Synkende + Synkende Spor Dato tillagt Sorter diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 4e1cd3ec0..4d29de5df 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -246,7 +246,7 @@ Genre Ampersand (&) Bewerken - Aflopend + Aflopend Kan status niet wissen Afspeellijst-afbeelding voor %s Geen nummers diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 549db296b..0c83a6bfa 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -75,7 +75,7 @@ ਐਂਡਰੌਇਡ ਲਈ ਇੱਕ ਸਰਲ, ਤਰਕਸੰਗਤ ਸੰਗੀਤ ਪਲੇਅਰ। ਖੋਜੋ ਗੀਤ ਦੀ ਗਿਣਤੀ - ਘਟਦੇ ਹੋਏ + ਘਟਦੇ ਹੋਏ ਚੁਣਿਆ ਹੋਇਆ ਚਲਾਓ ਕਲਾਕਾਰ \'ਤੇ ਜਾਓ ਫਾਈਲ ਦਾ ਨਾਮ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 7b4ece49b..a472dc577 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -275,7 +275,7 @@ Muzyka Nie można wyczyścić stanu odtwarzania Nie można zapisać stanu odtwarzania - Malejąco + Malejąco Playlisty Playlista Obraz playlisty %s diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index af9052aa1..ec43dcdc8 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -272,7 +272,7 @@ Persistência Comportamento Pastas - Descendente + Descendente Ignorar artigos ao classificar Ignore palavras como \"the\" ao classificar por nome (funciona melhor com músicas em inglês) \ No newline at end of file diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index d56b5eee1..8140c3ad1 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -260,7 +260,7 @@ %d artistas Equalização de volume ReplayGain - Descendente + Descendente Mude o tema e as cores do app Personalize os controles e o comportamento da interface do usuário Controle como a música e as imagens são carregadas diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index ed696e932..793b4ec7d 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -137,7 +137,7 @@ Redare selecție Listă de redare Liste de redare - Descrescător + Descrescător Selecție aleatorie aleasă Treceți la următoarea Redă de la artist diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 13a837c1b..6f311d10a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -277,7 +277,7 @@ Воспроизведение Папки Состояние воспроизведения - По убыванию + По убыванию Плейлист Плейлисты Обложка плейлиста для %s diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 8b958539b..e9547d169 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -39,7 +39,7 @@ Spår Datum tillagt Stigande - Fallande + Fallande Nu spelar Utjämnare Spela diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3a7d65394..86915531e 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -263,7 +263,7 @@ Oynatma Kütüphane Kalıcılık - Azalan + Azalan Uygulamanın temasını ve renklerini değiştirin Klasörler Arayüz kontrollerini ve davranışını özelleştirin diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a4419687e..a8c4959d6 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -274,7 +274,7 @@ Стан відтворення Налаштуйте звук і поведінку при відтворенні Папки - За спаданням + За спаданням Зображення списку відтворення для %s Список відтворення Списки відтворення diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c2a6138f2..1b91a7870 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -268,7 +268,7 @@ 文件夹 音乐 配置声音和播放行为 - 降序 + 降序 播放列表 播放列表 %s 的播放列表图片 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0e47fc0f1..d15b90062 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,7 +105,7 @@ Sort Ascending - Descending + Descending Now playing Equalizer