Update code style
Heavily tweak the code style across the app, doing things such as fixing returns and giving names to lambda values.
This commit is contained in:
parent
fab377eba4
commit
2c93e3f362
41 changed files with 347 additions and 327 deletions
|
@ -83,8 +83,8 @@ class MainFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
navController?.let { controller ->
|
navController?.let { controller ->
|
||||||
binding.navBar.setOnNavigationItemSelectedListener {
|
binding.navBar.setOnNavigationItemSelectedListener { item ->
|
||||||
navigateWithItem(controller, it)
|
navigateWithItem(controller, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,12 +94,12 @@ class MainFragment : Fragment() {
|
||||||
// Change CompactPlaybackFragment's visibility here so that an animation occurs.
|
// Change CompactPlaybackFragment's visibility here so that an animation occurs.
|
||||||
handleCompactPlaybackVisibility(binding, playbackModel.song.value)
|
handleCompactPlaybackVisibility(binding, playbackModel.song.value)
|
||||||
|
|
||||||
playbackModel.song.observe(viewLifecycleOwner) {
|
playbackModel.song.observe(viewLifecycleOwner) { song ->
|
||||||
handleCompactPlaybackVisibility(binding, it)
|
handleCompactPlaybackVisibility(binding, song)
|
||||||
}
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) {
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
if (it != null && navController != null) {
|
if (item != null && navController != null) {
|
||||||
val curDest = navController.currentDestination?.id
|
val curDest = navController.currentDestination?.id
|
||||||
|
|
||||||
// SongsFragment and SettingsFragment have no navigation pathways, so correct
|
// SongsFragment and SettingsFragment have no navigation pathways, so correct
|
||||||
|
|
|
@ -15,7 +15,6 @@ import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.toURI
|
import org.oxycblt.auxio.music.toURI
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetcher that returns the album art for a given [Album]. Handles settings on whether to use
|
* Fetcher that returns the album art for a given [Album]. Handles settings on whether to use
|
||||||
|
@ -39,11 +38,13 @@ class AlbumArtFetcher(private val context: Context) : Fetcher<Album> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadMediaStoreCovers(data: Album): SourceResult {
|
private fun loadMediaStoreCovers(data: Album): SourceResult {
|
||||||
val stream: InputStream? = context.contentResolver.openInputStream(data.coverUri)
|
val stream = context.contentResolver.openInputStream(data.coverUri)
|
||||||
|
|
||||||
stream?.let { stm ->
|
if (stream != null) {
|
||||||
|
// Don't close the stream here as it will cause an error later from an attempted read.
|
||||||
|
// This stream still seems to close itself at some point, so its fine.
|
||||||
return SourceResult(
|
return SourceResult(
|
||||||
source = stm.source().buffer(),
|
source = stream.source().buffer(),
|
||||||
mimeType = context.contentResolver.getType(data.coverUri),
|
mimeType = context.contentResolver.getType(data.coverUri),
|
||||||
dataSource = DataSource.DISK
|
dataSource = DataSource.DISK
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,6 +34,7 @@ class MosaicFetcher(private val context: Context) : Fetcher<Parent> {
|
||||||
size: Size,
|
size: Size,
|
||||||
options: Options
|
options: Options
|
||||||
): FetchResult {
|
): FetchResult {
|
||||||
|
options.allowRgb565
|
||||||
// Get the URIs for either a genre or artist
|
// Get the URIs for either a genre or artist
|
||||||
val uris = mutableListOf<Uri>()
|
val uris = mutableListOf<Uri>()
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,6 @@ class PlaybackStateDatabase(context: Context) :
|
||||||
assertBackgroundThread()
|
assertBackgroundThread()
|
||||||
|
|
||||||
val database = writableDatabase
|
val database = writableDatabase
|
||||||
|
|
||||||
var state: PlaybackState? = null
|
var state: PlaybackState? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -55,16 +55,13 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
|
|
||||||
binding.lifecycleOwner = this
|
binding.lifecycleOwner = this
|
||||||
|
|
||||||
setupToolbar(R.menu.menu_album_detail) {
|
setupToolbar(R.menu.menu_album_detail) { itemId ->
|
||||||
when (it) {
|
if (itemId == R.id.action_queue_add) {
|
||||||
R.id.action_queue_add -> {
|
|
||||||
playbackModel.addToUserQueue(detailModel.currentAlbum.value!!)
|
playbackModel.addToUserQueue(detailModel.currentAlbum.value!!)
|
||||||
getString(R.string.label_queue_added).createToast(requireContext())
|
getString(R.string.label_queue_added).createToast(requireContext())
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
} else {
|
||||||
|
false
|
||||||
else -> false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,18 +80,18 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
detailAdapter.submitList(data)
|
detailAdapter.submitList(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) {
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
when (it) {
|
when (item) {
|
||||||
// Songs should be scrolled to if the album matches, or a new detail
|
// Songs should be scrolled to if the album matches, or a new detail
|
||||||
// fragment should be launched otherwise.
|
// fragment should be launched otherwise.
|
||||||
is Song -> {
|
is Song -> {
|
||||||
if (detailModel.currentAlbum.value!!.id == it.album.id) {
|
if (detailModel.currentAlbum.value!!.id == item.album.id) {
|
||||||
scrollToItem(it.id)
|
scrollToItem(item.id)
|
||||||
|
|
||||||
detailModel.doneWithNavToItem()
|
detailModel.doneWithNavToItem()
|
||||||
} else {
|
} else {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
AlbumDetailFragmentDirections.actionShowAlbum(it.album.id)
|
AlbumDetailFragmentDirections.actionShowAlbum(item.album.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,12 +99,12 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
// If the album matches, no need to do anything. Otherwise launch a new
|
// If the album matches, no need to do anything. Otherwise launch a new
|
||||||
// detail fragment.
|
// detail fragment.
|
||||||
is Album -> {
|
is Album -> {
|
||||||
if (detailModel.currentAlbum.value!!.id == it.id) {
|
if (detailModel.currentAlbum.value!!.id == item.id) {
|
||||||
binding.detailRecycler.scrollToPosition(0)
|
binding.detailRecycler.scrollToPosition(0)
|
||||||
detailModel.doneWithNavToItem()
|
detailModel.doneWithNavToItem()
|
||||||
} else {
|
} else {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
AlbumDetailFragmentDirections.actionShowAlbum(it.id)
|
AlbumDetailFragmentDirections.actionShowAlbum(item.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +112,7 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
// Always launch a new ArtistDetailFragment.
|
// Always launch a new ArtistDetailFragment.
|
||||||
is Artist -> {
|
is Artist -> {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
AlbumDetailFragmentDirections.actionShowArtist(it.id)
|
AlbumDetailFragmentDirections.actionShowArtist(item.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,19 +122,19 @@ class AlbumDetailFragment : DetailFragment() {
|
||||||
|
|
||||||
// --- PLAYBACKVIEWMODEL SETUP ---
|
// --- PLAYBACKVIEWMODEL SETUP ---
|
||||||
|
|
||||||
playbackModel.song.observe(viewLifecycleOwner) {
|
playbackModel.song.observe(viewLifecycleOwner) { song ->
|
||||||
if (playbackModel.mode.value == PlaybackMode.IN_ALBUM &&
|
if (playbackModel.mode.value == PlaybackMode.IN_ALBUM &&
|
||||||
playbackModel.parent.value?.id == detailModel.currentAlbum.value!!.id
|
playbackModel.parent.value?.id == detailModel.currentAlbum.value!!.id
|
||||||
) {
|
) {
|
||||||
detailAdapter.highlightSong(playbackModel.song.value, binding.detailRecycler)
|
detailAdapter.highlightSong(song, binding.detailRecycler)
|
||||||
} else {
|
} else {
|
||||||
// Clear the viewholders if the mode isn't ALL_SONGS
|
// Clear the viewholders if the mode isn't ALL_SONGS
|
||||||
detailAdapter.highlightSong(null, binding.detailRecycler)
|
detailAdapter.highlightSong(null, binding.detailRecycler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.isInUserQueue.observe(viewLifecycleOwner) {
|
playbackModel.isInUserQueue.observe(viewLifecycleOwner) { inUserQueue ->
|
||||||
if (it) {
|
if (inUserQueue) {
|
||||||
detailAdapter.highlightSong(null, binding.detailRecycler)
|
detailAdapter.highlightSong(null, binding.detailRecycler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Genre
|
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.ui.ActionMenu
|
import org.oxycblt.auxio.ui.ActionMenu
|
||||||
|
@ -44,16 +43,18 @@ class ArtistDetailFragment : DetailFragment() {
|
||||||
|
|
||||||
val detailAdapter = ArtistDetailAdapter(
|
val detailAdapter = ArtistDetailAdapter(
|
||||||
detailModel, playbackModel, viewLifecycleOwner,
|
detailModel, playbackModel, viewLifecycleOwner,
|
||||||
doOnClick = {
|
doOnClick = { album ->
|
||||||
if (!detailModel.isNavigating) {
|
if (!detailModel.isNavigating) {
|
||||||
detailModel.updateNavigationStatus(true)
|
detailModel.setNavigating(true)
|
||||||
|
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
ArtistDetailFragmentDirections.actionShowAlbum(it.id)
|
ArtistDetailFragmentDirections.actionShowAlbum(album.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_ARTIST) }
|
doOnLongClick = { view, data ->
|
||||||
|
newMenu(view, data, ActionMenu.FLAG_IN_ARTIST)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
@ -76,26 +77,28 @@ class ArtistDetailFragment : DetailFragment() {
|
||||||
detailAdapter.submitList(data)
|
detailAdapter.submitList(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) {
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
if (it != null) {
|
when (item) {
|
||||||
// If the artist matches, no need to do anything, otherwise launch a new detail
|
is Artist -> {
|
||||||
if (it is Artist) {
|
if (item.id == detailModel.currentArtist.value!!.id) {
|
||||||
if (it.id == detailModel.currentArtist.value!!.id) {
|
|
||||||
binding.detailRecycler.scrollToPosition(0)
|
binding.detailRecycler.scrollToPosition(0)
|
||||||
detailModel.doneWithNavToItem()
|
detailModel.doneWithNavToItem()
|
||||||
} else {
|
} else {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
ArtistDetailFragmentDirections.actionShowArtist(it.id)
|
ArtistDetailFragmentDirections.actionShowArtist(item.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (it !is Genre) {
|
}
|
||||||
// Determine the album id of the song or album, and then launch it otherwise
|
|
||||||
val albumId = if (it is Song) it.album.id else it.id
|
|
||||||
|
|
||||||
findNavController().navigate(
|
is Album -> findNavController().navigate(
|
||||||
ArtistDetailFragmentDirections.actionShowAlbum(albumId)
|
ArtistDetailFragmentDirections.actionShowAlbum(item.id)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
is Song -> findNavController().navigate(
|
||||||
|
ArtistDetailFragmentDirections.actionShowAlbum(item.album.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ abstract class DetailFragment : Fragment() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
callback.isEnabled = true
|
callback.isEnabled = true
|
||||||
detailModel.updateNavigationStatus(false)
|
detailModel.setNavigating(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
@ -56,7 +56,7 @@ abstract class DetailFragment : Fragment() {
|
||||||
*/
|
*/
|
||||||
protected fun setupToolbar(
|
protected fun setupToolbar(
|
||||||
@MenuRes menu: Int = -1,
|
@MenuRes menu: Int = -1,
|
||||||
onMenuClick: ((id: Int) -> Boolean)? = null
|
onMenuClick: ((itemId: Int) -> Boolean)? = null
|
||||||
) {
|
) {
|
||||||
binding.detailToolbar.apply {
|
binding.detailToolbar.apply {
|
||||||
if (menu != -1) {
|
if (menu != -1) {
|
||||||
|
@ -67,9 +67,9 @@ abstract class DetailFragment : Fragment() {
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMenuClick?.let {
|
onMenuClick?.let { onClick ->
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener { item ->
|
||||||
it(it.itemId)
|
onClick(item.itemId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,26 +16,27 @@ import org.oxycblt.auxio.recycler.SortMode
|
||||||
*/
|
*/
|
||||||
class DetailViewModel : ViewModel() {
|
class DetailViewModel : ViewModel() {
|
||||||
private val mCurrentGenre = MutableLiveData<Genre?>()
|
private val mCurrentGenre = MutableLiveData<Genre?>()
|
||||||
private val mCurrentArtist = MutableLiveData<Artist?>()
|
|
||||||
private val mCurrentAlbum = MutableLiveData<Album?>()
|
|
||||||
|
|
||||||
private val mGenreSortMode = MutableLiveData(SortMode.ALPHA_DOWN)
|
|
||||||
private val mArtistSortMode = MutableLiveData(SortMode.NUMERIC_DOWN)
|
|
||||||
private val mAlbumSortMode = MutableLiveData(SortMode.NUMERIC_DOWN)
|
|
||||||
|
|
||||||
private val mNavToItem = MutableLiveData<BaseModel?>()
|
|
||||||
private var mIsNavigating = false
|
|
||||||
|
|
||||||
val currentGenre: LiveData<Genre?> get() = mCurrentGenre
|
val currentGenre: LiveData<Genre?> get() = mCurrentGenre
|
||||||
|
|
||||||
|
private val mCurrentArtist = MutableLiveData<Artist?>()
|
||||||
val currentArtist: LiveData<Artist?> get() = mCurrentArtist
|
val currentArtist: LiveData<Artist?> get() = mCurrentArtist
|
||||||
|
|
||||||
|
private val mCurrentAlbum = MutableLiveData<Album?>()
|
||||||
val currentAlbum: LiveData<Album?> get() = mCurrentAlbum
|
val currentAlbum: LiveData<Album?> get() = mCurrentAlbum
|
||||||
|
|
||||||
|
private val mGenreSortMode = MutableLiveData(SortMode.ALPHA_DOWN)
|
||||||
val genreSortMode: LiveData<SortMode> get() = mGenreSortMode
|
val genreSortMode: LiveData<SortMode> get() = mGenreSortMode
|
||||||
|
|
||||||
|
private val mArtistSortMode = MutableLiveData(SortMode.NUMERIC_DOWN)
|
||||||
val albumSortMode: LiveData<SortMode> get() = mAlbumSortMode
|
val albumSortMode: LiveData<SortMode> get() = mAlbumSortMode
|
||||||
|
|
||||||
|
private val mAlbumSortMode = MutableLiveData(SortMode.NUMERIC_DOWN)
|
||||||
val artistSortMode: LiveData<SortMode> get() = mArtistSortMode
|
val artistSortMode: LiveData<SortMode> get() = mArtistSortMode
|
||||||
|
|
||||||
|
private var mIsNavigating = false
|
||||||
val isNavigating: Boolean get() = mIsNavigating
|
val isNavigating: Boolean get() = mIsNavigating
|
||||||
|
|
||||||
|
private val mNavToItem = MutableLiveData<BaseModel?>()
|
||||||
/** Flag for unified navigation. Observe this to coordinate navigation to an item's UI. */
|
/** Flag for unified navigation. Observe this to coordinate navigation to an item's UI. */
|
||||||
val navToItem: LiveData<BaseModel?> get() = mNavToItem
|
val navToItem: LiveData<BaseModel?> get() = mNavToItem
|
||||||
|
|
||||||
|
@ -106,7 +107,7 @@ class DetailViewModel : ViewModel() {
|
||||||
/**
|
/**
|
||||||
* Update the current navigation status to [isNavigating]
|
* Update the current navigation status to [isNavigating]
|
||||||
*/
|
*/
|
||||||
fun updateNavigationStatus(isNavigating: Boolean) {
|
fun setNavigating(isNavigating: Boolean) {
|
||||||
mIsNavigating = isNavigating
|
mIsNavigating = isNavigating
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,12 @@ class GenreDetailFragment : DetailFragment() {
|
||||||
|
|
||||||
val detailAdapter = GenreDetailAdapter(
|
val detailAdapter = GenreDetailAdapter(
|
||||||
detailModel, playbackModel, viewLifecycleOwner,
|
detailModel, playbackModel, viewLifecycleOwner,
|
||||||
doOnClick = {
|
doOnClick = { song ->
|
||||||
playbackModel.playSong(it, PlaybackMode.IN_GENRE)
|
playbackModel.playSong(song, PlaybackMode.IN_GENRE)
|
||||||
},
|
},
|
||||||
doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_GENRE) }
|
doOnLongClick = { view, data ->
|
||||||
|
newMenu(view, data, ActionMenu.FLAG_IN_GENRE)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
@ -69,19 +71,19 @@ class GenreDetailFragment : DetailFragment() {
|
||||||
detailAdapter.submitList(data)
|
detailAdapter.submitList(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) {
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
when (it) {
|
when (item) {
|
||||||
// All items will launch new detail fragments.
|
// All items will launch new detail fragments.
|
||||||
is Artist -> findNavController().navigate(
|
is Artist -> findNavController().navigate(
|
||||||
GenreDetailFragmentDirections.actionShowArtist(it.id)
|
GenreDetailFragmentDirections.actionShowArtist(item.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
is Album -> findNavController().navigate(
|
is Album -> findNavController().navigate(
|
||||||
GenreDetailFragmentDirections.actionShowAlbum(it.id)
|
GenreDetailFragmentDirections.actionShowAlbum(item.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
is Song -> findNavController().navigate(
|
is Song -> findNavController().navigate(
|
||||||
GenreDetailFragmentDirections.actionShowAlbum(it.album.id)
|
GenreDetailFragmentDirections.actionShowAlbum(item.album.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
|
@ -90,19 +92,19 @@ class GenreDetailFragment : DetailFragment() {
|
||||||
|
|
||||||
// --- PLAYBACKVIEWMODEL SETUP ---
|
// --- PLAYBACKVIEWMODEL SETUP ---
|
||||||
|
|
||||||
playbackModel.song.observe(viewLifecycleOwner) {
|
playbackModel.song.observe(viewLifecycleOwner) { song ->
|
||||||
if (playbackModel.mode.value == PlaybackMode.IN_GENRE &&
|
if (playbackModel.mode.value == PlaybackMode.IN_GENRE &&
|
||||||
playbackModel.parent.value?.id == detailModel.currentGenre.value!!.id
|
playbackModel.parent.value?.id == detailModel.currentGenre.value!!.id
|
||||||
) {
|
) {
|
||||||
detailAdapter.highlightSong(playbackModel.song.value, binding.detailRecycler)
|
detailAdapter.highlightSong(song, binding.detailRecycler)
|
||||||
} else {
|
} else {
|
||||||
// Clear the viewholders if the mode isn't ALL_SONGS
|
// Clear the viewholders if the mode isn't ALL_SONGS
|
||||||
detailAdapter.highlightSong(null, binding.detailRecycler)
|
detailAdapter.highlightSong(null, binding.detailRecycler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.isInUserQueue.observe(viewLifecycleOwner) {
|
playbackModel.isInUserQueue.observe(viewLifecycleOwner) { inUserQueue ->
|
||||||
if (it) {
|
if (inUserQueue) {
|
||||||
detailAdapter.highlightSong(null, binding.detailRecycler)
|
detailAdapter.highlightSong(null, binding.detailRecycler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,8 +89,8 @@ class AlbumDetailAdapter(
|
||||||
|
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
// Use existing data instead of having to re-sort it.
|
// Use existing data instead of having to re-sort it.
|
||||||
val pos = currentList.indexOfFirst {
|
val pos = currentList.indexOfFirst { item ->
|
||||||
it.name == song.name && it is Song
|
item.name == song.name && item is Song
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the ViewHolder for this song is visible, if it is then highlight it.
|
// Check if the ViewHolder for this song is visible, if it is then highlight it.
|
||||||
|
|
|
@ -90,8 +90,8 @@ class ArtistDetailAdapter(
|
||||||
|
|
||||||
if (album != null) {
|
if (album != null) {
|
||||||
// Use existing data instead of having to re-sort it.
|
// Use existing data instead of having to re-sort it.
|
||||||
val pos = currentList.indexOfFirst {
|
val pos = currentList.indexOfFirst { item ->
|
||||||
it.name == album.name && it is Album
|
item.name == album.name && item is Album
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the ViewHolder if this album is visible, and highlight it if so.
|
// Check if the ViewHolder if this album is visible, and highlight it if so.
|
||||||
|
|
|
@ -90,8 +90,8 @@ class GenreDetailAdapter(
|
||||||
|
|
||||||
if (song != null) {
|
if (song != null) {
|
||||||
// Use existing data instead of having to re-sort it.
|
// Use existing data instead of having to re-sort it.
|
||||||
val pos = currentList.indexOfFirst {
|
val pos = currentList.indexOfFirst { item ->
|
||||||
it.name == song.name && it is Song
|
item.name == song.name && item is Song
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the ViewHolder for this song is visible, if it is then highlight it.
|
// Check if the ViewHolder for this song is visible, if it is then highlight it.
|
||||||
|
|
|
@ -37,24 +37,23 @@ class LibraryFragment : Fragment() {
|
||||||
): View {
|
): View {
|
||||||
val binding = FragmentLibraryBinding.inflate(inflater)
|
val binding = FragmentLibraryBinding.inflate(inflater)
|
||||||
|
|
||||||
val libraryAdapter = LibraryAdapter(::navToDetail) { view, data -> newMenu(view, data) }
|
val libraryAdapter = LibraryAdapter(::navToDetail) { view, data ->
|
||||||
|
newMenu(view, data)
|
||||||
|
}
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
||||||
binding.libraryToolbar.apply {
|
binding.libraryToolbar.apply {
|
||||||
menu.findItem(libraryModel.sortMode.toMenuId()).isChecked = true
|
menu.findItem(libraryModel.sortMode.toMenuId()).isChecked = true
|
||||||
|
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener { item ->
|
||||||
when (it.itemId) {
|
if (item.itemId != R.id.submenu_sorting) {
|
||||||
R.id.submenu_sorting -> {}
|
libraryModel.updateSortMode(item.itemId)
|
||||||
|
item.isChecked = true
|
||||||
else -> {
|
|
||||||
it.isChecked = true
|
|
||||||
libraryModel.updateSortMode(it.itemId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,18 +69,18 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
libraryModel.libraryData.observe(viewLifecycleOwner) {
|
libraryModel.libraryData.observe(viewLifecycleOwner) { data ->
|
||||||
libraryAdapter.updateData(it)
|
libraryAdapter.updateData(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) {
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
if (it != null) {
|
if (item != null) {
|
||||||
libraryModel.updateNavigationStatus(false)
|
libraryModel.setNavigating(false)
|
||||||
|
|
||||||
if (it is Parent) {
|
if (item is Parent) {
|
||||||
navToDetail(it)
|
navToDetail(item)
|
||||||
} else if (it is Song) {
|
} else if (item is Song) {
|
||||||
navToDetail(it.album)
|
navToDetail(item.album)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +93,7 @@ class LibraryFragment : Fragment() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
libraryModel.updateNavigationStatus(false)
|
libraryModel.setNavigating(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@ -110,7 +109,7 @@ class LibraryFragment : Fragment() {
|
||||||
requireView().rootView.clearFocus()
|
requireView().rootView.clearFocus()
|
||||||
|
|
||||||
if (!libraryModel.isNavigating) {
|
if (!libraryModel.isNavigating) {
|
||||||
libraryModel.updateNavigationStatus(true)
|
libraryModel.setNavigating(true)
|
||||||
|
|
||||||
logD("Navigating to the detail fragment for ${parent.name}")
|
logD("Navigating to the detail fragment for ${parent.name}")
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ class LibraryViewModel : ViewModel(), SettingsManager.Callback {
|
||||||
/**
|
/**
|
||||||
* Update the current navigation status
|
* Update the current navigation status
|
||||||
*/
|
*/
|
||||||
fun updateNavigationStatus(isNavigating: Boolean) {
|
fun setNavigating(isNavigating: Boolean) {
|
||||||
mIsNavigating = isNavigating
|
mIsNavigating = isNavigating
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,8 @@ class LoadingFragment : Fragment() {
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
loadingModel.doGrant.observe(viewLifecycleOwner) {
|
loadingModel.doGrant.observe(viewLifecycleOwner) { doGrant ->
|
||||||
if (it) {
|
if (doGrant) {
|
||||||
permLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
permLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
loadingModel.doneWithGrant()
|
loadingModel.doneWithGrant()
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,9 @@ class LoadingFragment : Fragment() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
if (MusicStore.getInstance().loaded) {
|
// Navigate away if the music has already been loaded.
|
||||||
|
// This causes a memory leak, but there's nothing I can do about it.
|
||||||
|
if (loadingModel.loaded) {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
LoadingFragmentDirections.actionToMain()
|
LoadingFragmentDirections.actionToMain()
|
||||||
)
|
)
|
||||||
|
@ -106,8 +108,8 @@ class LoadingFragment : Fragment() {
|
||||||
|
|
||||||
private fun onPermResult(granted: Boolean) {
|
private fun onPermResult(granted: Boolean) {
|
||||||
if (granted) {
|
if (granted) {
|
||||||
// If granted, its now safe to load [Which will clear the NO_PERMS response we applied
|
// If granted, its now safe to load, which will clear the NO_PERMS response
|
||||||
// earlier]
|
// we applied earlier.
|
||||||
loadingModel.load()
|
loadingModel.load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,12 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
|
||||||
private val mResponse = MutableLiveData<MusicStore.Response?>(null)
|
private val mResponse = MutableLiveData<MusicStore.Response?>(null)
|
||||||
private val mDoGrant = MutableLiveData(false)
|
private val mDoGrant = MutableLiveData(false)
|
||||||
|
|
||||||
|
private var isBusy = false
|
||||||
|
|
||||||
/** The last response from [MusicStore]. Null if the loading process is occurring. */
|
/** The last response from [MusicStore]. Null if the loading process is occurring. */
|
||||||
val response: LiveData<MusicStore.Response?> = mResponse
|
val response: LiveData<MusicStore.Response?> = mResponse
|
||||||
val doGrant: LiveData<Boolean> = mDoGrant
|
val doGrant: LiveData<Boolean> = mDoGrant
|
||||||
|
val loaded: Boolean get() = musicStore.loaded
|
||||||
private var isBusy = false
|
|
||||||
|
|
||||||
private val musicStore = MusicStore.getInstance()
|
private val musicStore = MusicStore.getInstance()
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ class MusicStore private constructor() {
|
||||||
*/
|
*/
|
||||||
suspend fun load(app: Application): Response {
|
suspend fun load(app: Application): Response {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
|
// TODO: Move this to an internal function
|
||||||
this@MusicStore.logD("Starting initial music load...")
|
this@MusicStore.logD("Starting initial music load...")
|
||||||
|
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
|
|
|
@ -5,12 +5,10 @@ import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.text.isDigitsOnly
|
import androidx.core.text.isDigitsOnly
|
||||||
import androidx.databinding.BindingAdapter
|
import androidx.databinding.BindingAdapter
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.recycler.SortMode
|
|
||||||
import org.oxycblt.auxio.ui.getPlural
|
import org.oxycblt.auxio.ui.getPlural
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,11 +167,3 @@ fun TextView.bindAlbumInfo(album: Album) {
|
||||||
fun TextView.bindAlbumYear(album: Album) {
|
fun TextView.bindAlbumYear(album: Album) {
|
||||||
text = album.year.toYear(context)
|
text = album.year.toYear(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind the [SortMode] icon for an ImageButton.
|
|
||||||
*/
|
|
||||||
@BindingAdapter("sortIcon")
|
|
||||||
fun ImageButton.bindSortIcon(mode: SortMode) {
|
|
||||||
setImageResource(mode.iconRes)
|
|
||||||
}
|
|
||||||
|
|
|
@ -64,8 +64,8 @@ class CompactPlaybackFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.isPlaying.observe(viewLifecycleOwner) {
|
playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying ->
|
||||||
binding.playbackPlayPause.setPlaying(it, playbackModel.canAnimate)
|
binding.playbackPlayPause.setPlaying(isPlaying, playbackModel.canAnimate)
|
||||||
|
|
||||||
playbackModel.enableAnimation()
|
playbackModel.enableAnimation()
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,12 +61,14 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener { item ->
|
||||||
if (it.itemId == R.id.action_queue) {
|
if (item.itemId == R.id.action_queue) {
|
||||||
findNavController().navigate(PlaybackFragmentDirections.actionShowQueue())
|
findNavController().navigate(PlaybackFragmentDirections.actionShowQueue())
|
||||||
|
|
||||||
true
|
true
|
||||||
} else false
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queueItem = menu.findItem(R.id.action_queue)
|
queueItem = menu.findItem(R.id.action_queue)
|
||||||
|
@ -90,12 +92,16 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.isShuffling.observe(viewLifecycleOwner) {
|
playbackModel.isShuffling.observe(viewLifecycleOwner) { isShuffling ->
|
||||||
binding.playbackShuffle.imageTintList = if (it) accentColor else controlColor
|
binding.playbackShuffle.imageTintList = if (isShuffling) {
|
||||||
|
accentColor
|
||||||
|
} else {
|
||||||
|
controlColor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.loopMode.observe(viewLifecycleOwner) {
|
playbackModel.loopMode.observe(viewLifecycleOwner) { loopMode ->
|
||||||
when (it) {
|
when (loopMode) {
|
||||||
LoopMode.NONE -> {
|
LoopMode.NONE -> {
|
||||||
binding.playbackLoop.imageTintList = controlColor
|
binding.playbackLoop.imageTintList = controlColor
|
||||||
binding.playbackLoop.setImageResource(R.drawable.ic_loop)
|
binding.playbackLoop.setImageResource(R.drawable.ic_loop)
|
||||||
|
@ -115,17 +121,17 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.isSeeking.observe(viewLifecycleOwner) {
|
playbackModel.isSeeking.observe(viewLifecycleOwner) { isSeeking ->
|
||||||
if (it) {
|
if (isSeeking) {
|
||||||
binding.playbackDurationCurrent.setTextColor(accentColor)
|
binding.playbackDurationCurrent.setTextColor(accentColor)
|
||||||
} else {
|
} else {
|
||||||
binding.playbackDurationCurrent.setTextColor(normalTextColor)
|
binding.playbackDurationCurrent.setTextColor(normalTextColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.positionAsProgress.observe(viewLifecycleOwner) {
|
playbackModel.positionAsProgress.observe(viewLifecycleOwner) { pos ->
|
||||||
if (!playbackModel.isSeeking.value!!) {
|
if (!playbackModel.isSeeking.value!!) {
|
||||||
binding.playbackSeekBar.progress = it
|
binding.playbackSeekBar.progress = pos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,9 +159,9 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.isPlaying.observe(viewLifecycleOwner) {
|
playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying ->
|
||||||
binding.playbackPlayPause.apply {
|
binding.playbackPlayPause.apply {
|
||||||
if (it) {
|
if (isPlaying) {
|
||||||
backgroundTintList = accentColor
|
backgroundTintList = accentColor
|
||||||
setPlaying(true, playbackModel.canAnimate)
|
setPlaying(true, playbackModel.canAnimate)
|
||||||
} else {
|
} else {
|
||||||
|
@ -167,8 +173,8 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
||||||
playbackModel.enableAnimation()
|
playbackModel.enableAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) {
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
if (it != null) {
|
if (item != null) {
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -380,7 +380,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
mIntentUri = null
|
mIntentUri = null
|
||||||
|
|
||||||
// Were not going to be restoring playbackManager after this, so mark it as such.
|
// Were not going to be restoring playbackManager after this, so mark it as such.
|
||||||
playbackManager.setRestored()
|
playbackManager.markRestored()
|
||||||
} else if (!playbackManager.isRestored) {
|
} else if (!playbackManager.isRestored) {
|
||||||
// Otherwise just restore
|
// Otherwise just restore
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|
|
@ -61,23 +61,21 @@ class QueueAdapter(
|
||||||
ItemQueueSongBinding.inflate(parent.context.inflater)
|
ItemQueueSongBinding.inflate(parent.context.inflater)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> error("Someone messed with the ViewHolder item types.")
|
else -> error("Invalid viewholder item type $viewType.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
when (val item = data[position]) {
|
when (val item = data[position]) {
|
||||||
is Song -> (holder as QueueSongViewHolder).bind(item)
|
is Song -> (holder as QueueSongViewHolder).bind(item)
|
||||||
is Header ->
|
|
||||||
if (item.isAction) {
|
is Header -> if (item.isAction) {
|
||||||
(holder as UserQueueHeaderViewHolder).bind(item)
|
(holder as UserQueueHeaderViewHolder).bind(item)
|
||||||
} else {
|
} else {
|
||||||
(holder as HeaderViewHolder).bind(item)
|
(holder as HeaderViewHolder).bind(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> logE("Bad data given to QueueAdapter.")
|
||||||
logE("Bad data fed to QueueAdapter.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +93,7 @@ class QueueAdapter(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move Items.
|
* Move Items.
|
||||||
* Used since [submitList] will cause QueueAdapter to freak-out here.
|
* Used since [submitList] will cause QueueAdapter to freak out.
|
||||||
*/
|
*/
|
||||||
fun moveItems(adapterFrom: Int, adapterTo: Int) {
|
fun moveItems(adapterFrom: Int, adapterTo: Int) {
|
||||||
val item = data.removeAt(adapterFrom)
|
val item = data.removeAt(adapterFrom)
|
||||||
|
@ -106,7 +104,7 @@ class QueueAdapter(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an item.
|
* Remove an item.
|
||||||
* Used since [submitList] will cause QueueAdapter to freak-out here.
|
* Used since [submitList] will cause QueueAdapter to freak out.
|
||||||
*/
|
*/
|
||||||
fun removeItem(adapterIndex: Int) {
|
fun removeItem(adapterIndex: Int) {
|
||||||
data.removeAt(adapterIndex)
|
data.removeAt(adapterIndex)
|
||||||
|
@ -154,10 +152,8 @@ class QueueAdapter(
|
||||||
|
|
||||||
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
|
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||||
touchHelper.startDrag(this)
|
touchHelper.startDrag(this)
|
||||||
return@setOnTouchListener true
|
true
|
||||||
}
|
} else false
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,11 @@ class QueueFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!requireActivity().isIrregularLandscape() && isEdgeOn()) {
|
if (!requireActivity().isIrregularLandscape() && isEdgeOn()) {
|
||||||
setOnApplyWindowInsetsListener @Suppress("DEPRECATION") { _, insets ->
|
setOnApplyWindowInsetsListener { _, insets ->
|
||||||
val top = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
val top = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
insets.getInsets(WindowInsets.Type.systemBars()).top
|
insets.getInsets(WindowInsets.Type.systemBars()).top
|
||||||
} else {
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
insets.systemWindowInsetTop
|
insets.systemWindowInsetTop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,8 +75,8 @@ class QueueFragment : Fragment() {
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ----
|
// --- VIEWMODEL SETUP ----
|
||||||
|
|
||||||
playbackModel.userQueue.observe(viewLifecycleOwner) {
|
playbackModel.userQueue.observe(viewLifecycleOwner) { userQueue ->
|
||||||
if (it.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
|
if (userQueue.isEmpty() && playbackModel.nextItemsInQueue.value!!.isEmpty()) {
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
|
|
||||||
return@observe
|
return@observe
|
||||||
|
@ -84,17 +85,17 @@ class QueueFragment : Fragment() {
|
||||||
queueAdapter.submitList(createQueueData())
|
queueAdapter.submitList(createQueueData())
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) {
|
playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { nextQueue ->
|
||||||
if (it.isEmpty() && playbackModel.userQueue.value!!.isEmpty()) {
|
if (nextQueue.isEmpty() && playbackModel.userQueue.value!!.isEmpty()) {
|
||||||
findNavController().navigateUp()
|
findNavController().navigateUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
queueAdapter.submitList(createQueueData())
|
queueAdapter.submitList(createQueueData())
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackModel.isShuffling.observe(viewLifecycleOwner) {
|
playbackModel.isShuffling.observe(viewLifecycleOwner) { isShuffling ->
|
||||||
if (it != lastShuffle) {
|
if (isShuffling != lastShuffle) {
|
||||||
lastShuffle = it
|
lastShuffle = isShuffling
|
||||||
|
|
||||||
binding.queueRecycler.scrollToPosition(0)
|
binding.queueRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
@ -109,21 +110,27 @@ class QueueFragment : Fragment() {
|
||||||
*/
|
*/
|
||||||
private fun createQueueData(): MutableList<BaseModel> {
|
private fun createQueueData(): MutableList<BaseModel> {
|
||||||
val queue = mutableListOf<BaseModel>()
|
val queue = mutableListOf<BaseModel>()
|
||||||
|
val userQueue = playbackModel.userQueue.value!!
|
||||||
|
val nextQueue = playbackModel.nextItemsInQueue.value!!
|
||||||
|
|
||||||
if (playbackModel.userQueue.value!!.isNotEmpty()) {
|
if (userQueue.isNotEmpty()) {
|
||||||
queue.add(Header(id = -2, name = getString(R.string.label_next_user_queue), isAction = true))
|
queue += Header(
|
||||||
queue.addAll(playbackModel.userQueue.value!!)
|
id = -2,
|
||||||
|
name = getString(R.string.label_next_user_queue),
|
||||||
|
isAction = true
|
||||||
|
)
|
||||||
|
|
||||||
|
queue += userQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playbackModel.nextItemsInQueue.value!!.isNotEmpty()) {
|
if (nextQueue.isNotEmpty()) {
|
||||||
queue.add(
|
queue += Header(
|
||||||
Header(
|
|
||||||
id = -3,
|
id = -3,
|
||||||
name = getString(R.string.format_next_from, getParentName()),
|
name = getString(R.string.format_next_from, getParentName()),
|
||||||
isAction = false
|
isAction = false
|
||||||
)
|
)
|
||||||
)
|
|
||||||
queue.addAll(playbackModel.nextItemsInQueue.value!!)
|
queue += nextQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
return queue
|
return queue
|
||||||
|
|
|
@ -24,16 +24,16 @@ enum class LoopMode {
|
||||||
*/
|
*/
|
||||||
fun toInt(): Int {
|
fun toInt(): Int {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
NONE -> CONSTANT_NONE
|
NONE -> CONST_NONE
|
||||||
ONCE -> CONSTANT_ONCE
|
ONCE -> CONST_ONCE
|
||||||
INFINITE -> CONSTANT_INFINITE
|
INFINITE -> CONST_INFINITE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CONSTANT_NONE = 0xA050
|
private const val CONST_NONE = 0xA050
|
||||||
const val CONSTANT_ONCE = 0xA051
|
private const val CONST_ONCE = 0xA051
|
||||||
const val CONSTANT_INFINITE = 0xA052
|
private const val CONST_INFINITE = 0xA052
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an int [constant] into a LoopMode
|
* Convert an int [constant] into a LoopMode
|
||||||
|
@ -41,9 +41,9 @@ enum class LoopMode {
|
||||||
*/
|
*/
|
||||||
fun fromInt(constant: Int): LoopMode? {
|
fun fromInt(constant: Int): LoopMode? {
|
||||||
return when (constant) {
|
return when (constant) {
|
||||||
CONSTANT_NONE -> NONE
|
CONST_NONE -> NONE
|
||||||
CONSTANT_ONCE -> ONCE
|
CONST_ONCE -> ONCE
|
||||||
CONSTANT_INFINITE -> INFINITE
|
CONST_INFINITE -> INFINITE
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,18 +20,18 @@ enum class PlaybackMode {
|
||||||
*/
|
*/
|
||||||
fun toInt(): Int {
|
fun toInt(): Int {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
IN_ARTIST -> CONSTANT_IN_ARTIST
|
IN_ARTIST -> CONST_IN_ARTIST
|
||||||
IN_GENRE -> CONSTANT_IN_GENRE
|
IN_GENRE -> CONST_IN_GENRE
|
||||||
IN_ALBUM -> CONSTANT_IN_ALBUM
|
IN_ALBUM -> CONST_IN_ALBUM
|
||||||
ALL_SONGS -> CONSTANT_ALL_SONGS
|
ALL_SONGS -> CONST_ALL_SONGS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CONSTANT_IN_ARTIST = 0xA040
|
private const val CONST_IN_ARTIST = 0xA040
|
||||||
const val CONSTANT_IN_GENRE = 0xA041
|
private const val CONST_IN_GENRE = 0xA041
|
||||||
const val CONSTANT_IN_ALBUM = 0xA042
|
private const val CONST_IN_ALBUM = 0xA042
|
||||||
const val CONSTANT_ALL_SONGS = 0xA043
|
private const val CONST_ALL_SONGS = 0xA043
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a [PlaybackMode] for an int [constant]
|
* Get a [PlaybackMode] for an int [constant]
|
||||||
|
@ -39,10 +39,10 @@ enum class PlaybackMode {
|
||||||
*/
|
*/
|
||||||
fun fromInt(constant: Int): PlaybackMode? {
|
fun fromInt(constant: Int): PlaybackMode? {
|
||||||
return when (constant) {
|
return when (constant) {
|
||||||
CONSTANT_IN_ARTIST -> IN_ARTIST
|
CONST_IN_ARTIST -> IN_ARTIST
|
||||||
CONSTANT_IN_ALBUM -> IN_ALBUM
|
CONST_IN_ALBUM -> IN_ALBUM
|
||||||
CONSTANT_IN_GENRE -> IN_GENRE
|
CONST_IN_GENRE -> IN_GENRE
|
||||||
CONSTANT_ALL_SONGS -> ALL_SONGS
|
CONST_ALL_SONGS -> ALL_SONGS
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,8 @@ class PlaybackStateManager private constructor() {
|
||||||
|
|
||||||
clearLoopMode()
|
clearLoopMode()
|
||||||
updatePlayback(song)
|
updatePlayback(song)
|
||||||
|
|
||||||
|
// Keep shuffle on, if enabled
|
||||||
setShuffling(settingsManager.keepShuffle && mIsShuffling, keepSong = true)
|
setShuffling(settingsManager.keepShuffle && mIsShuffling, keepSong = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,10 +205,12 @@ class PlaybackStateManager private constructor() {
|
||||||
mQueue = parent.songs.toMutableList()
|
mQueue = parent.songs.toMutableList()
|
||||||
mMode = PlaybackMode.IN_ALBUM
|
mMode = PlaybackMode.IN_ALBUM
|
||||||
}
|
}
|
||||||
|
|
||||||
is Artist -> {
|
is Artist -> {
|
||||||
mQueue = parent.songs.toMutableList()
|
mQueue = parent.songs.toMutableList()
|
||||||
mMode = PlaybackMode.IN_ARTIST
|
mMode = PlaybackMode.IN_ARTIST
|
||||||
}
|
}
|
||||||
|
|
||||||
is Genre -> {
|
is Genre -> {
|
||||||
mQueue = parent.songs.toMutableList()
|
mQueue = parent.songs.toMutableList()
|
||||||
mMode = PlaybackMode.IN_GENRE
|
mMode = PlaybackMode.IN_GENRE
|
||||||
|
@ -228,6 +232,8 @@ class PlaybackStateManager private constructor() {
|
||||||
|
|
||||||
setShuffling(true, keepSong = false)
|
setShuffling(true, keepSong = false)
|
||||||
updatePlayback(mQueue[0])
|
updatePlayback(mQueue[0])
|
||||||
|
|
||||||
|
// FIXME: Add clearLoopMode here?
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -293,9 +299,7 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
clearLoopMode()
|
clearLoopMode()
|
||||||
|
|
||||||
updatePlayback(mQueue[mIndex])
|
updatePlayback(mQueue[mIndex])
|
||||||
|
|
||||||
forceQueueUpdate()
|
forceQueueUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,18 +311,18 @@ class PlaybackStateManager private constructor() {
|
||||||
when (settingsManager.doAtEnd) {
|
when (settingsManager.doAtEnd) {
|
||||||
SettingsManager.EntryValues.AT_END_LOOP_PAUSE -> {
|
SettingsManager.EntryValues.AT_END_LOOP_PAUSE -> {
|
||||||
mIndex = 0
|
mIndex = 0
|
||||||
forceQueueUpdate()
|
|
||||||
|
|
||||||
mSong = mQueue[0]
|
|
||||||
mPosition = 0
|
mPosition = 0
|
||||||
setPlaying(false)
|
|
||||||
mIsInUserQueue = false
|
mIsInUserQueue = false
|
||||||
|
mSong = mQueue[0]
|
||||||
|
|
||||||
|
setPlaying(false)
|
||||||
|
forceQueueUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsManager.EntryValues.AT_END_LOOP -> {
|
SettingsManager.EntryValues.AT_END_LOOP -> {
|
||||||
mIndex = 0
|
mIndex = 0
|
||||||
forceQueueUpdate()
|
|
||||||
|
|
||||||
|
forceQueueUpdate()
|
||||||
updatePlayback(mQueue[0])
|
updatePlayback(mQueue[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,9 +533,9 @@ class PlaybackStateManager private constructor() {
|
||||||
* @see seekTo
|
* @see seekTo
|
||||||
*/
|
*/
|
||||||
fun setPosition(position: Long) {
|
fun setPosition(position: Long) {
|
||||||
mSong?.let {
|
mSong?.let { song ->
|
||||||
// Don't accept any bugged positions that are over the duration of the song.
|
// Don't accept any bugged positions that are over the duration of the song.
|
||||||
if (position <= it.duration) {
|
if (position <= song.duration) {
|
||||||
mPosition = position
|
mPosition = position
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -584,7 +588,7 @@ class PlaybackStateManager private constructor() {
|
||||||
/**
|
/**
|
||||||
* Mark this instance as restored.
|
* Mark this instance as restored.
|
||||||
*/
|
*/
|
||||||
fun setRestored() {
|
fun markRestored() {
|
||||||
mIsRestored = true
|
mIsRestored = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,32 +624,34 @@ class PlaybackStateManager private constructor() {
|
||||||
logD("Getting state from DB.")
|
logD("Getting state from DB.")
|
||||||
|
|
||||||
val now: Long
|
val now: Long
|
||||||
val state: PlaybackState?
|
val playbackState: PlaybackState?
|
||||||
|
|
||||||
// The coroutine call is locked at queueItems so that this function does not
|
// The coroutine call is locked at queueItems so that this function does not
|
||||||
// go ahead until EVERYTHING is read.
|
// go ahead until EVERYTHING is read.
|
||||||
|
// TODO: Improve this
|
||||||
val queueItems = withContext(Dispatchers.IO) {
|
val queueItems = withContext(Dispatchers.IO) {
|
||||||
now = System.currentTimeMillis()
|
now = System.currentTimeMillis()
|
||||||
|
|
||||||
val database = PlaybackStateDatabase.getInstance(context)
|
val database = PlaybackStateDatabase.getInstance(context)
|
||||||
state = database.readState()
|
|
||||||
|
playbackState = database.readState()
|
||||||
database.readQueue()
|
database.readQueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get off the IO coroutine since it will cause LiveData updates to throw an exception
|
// Get off the IO coroutine since it will cause LiveData updates to throw an exception
|
||||||
|
|
||||||
state?.let {
|
if (playbackState != null) {
|
||||||
logD("Valid playback state $it")
|
logD("Found playback state $playbackState")
|
||||||
logD("Valid queue size ${queueItems.size}")
|
logD("Found queue size ${queueItems.size}")
|
||||||
|
|
||||||
unpackFromPlaybackState(it)
|
unpackFromPlaybackState(playbackState)
|
||||||
unpackQueue(queueItems)
|
unpackQueue(queueItems)
|
||||||
doParentSanityCheck()
|
doParentSanityCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
logD("Restore finished in ${System.currentTimeMillis() - now}ms")
|
logD("Restore finished in ${System.currentTimeMillis() - now}ms")
|
||||||
|
|
||||||
setRestored()
|
markRestored()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -653,19 +659,14 @@ class PlaybackStateManager private constructor() {
|
||||||
* @return A [PlaybackState] reflecting the current state.
|
* @return A [PlaybackState] reflecting the current state.
|
||||||
*/
|
*/
|
||||||
private fun packToPlaybackState(): PlaybackState {
|
private fun packToPlaybackState(): PlaybackState {
|
||||||
val songName = mSong?.name ?: ""
|
|
||||||
val parentName = mParent?.name ?: ""
|
|
||||||
val intMode = mMode.toInt()
|
|
||||||
val intLoopMode = mLoopMode.toInt()
|
|
||||||
|
|
||||||
return PlaybackState(
|
return PlaybackState(
|
||||||
songName = songName,
|
songName = mSong?.name ?: "",
|
||||||
position = mPosition,
|
position = mPosition,
|
||||||
parentName = parentName,
|
parentName = mParent?.name ?: "",
|
||||||
index = mIndex,
|
index = mIndex,
|
||||||
mode = intMode,
|
mode = mMode.toInt(),
|
||||||
isShuffling = mIsShuffling,
|
isShuffling = mIsShuffling,
|
||||||
loopMode = intLoopMode,
|
loopMode = mLoopMode.toInt(),
|
||||||
inUserQueue = mIsInUserQueue
|
inUserQueue = mIsInUserQueue
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -695,13 +696,13 @@ class PlaybackStateManager private constructor() {
|
||||||
|
|
||||||
var queueItemId = 0L
|
var queueItemId = 0L
|
||||||
|
|
||||||
mUserQueue.forEach {
|
mUserQueue.forEach { song ->
|
||||||
unified.add(QueueItem(queueItemId, it.name, it.album.name, true))
|
unified.add(QueueItem(queueItemId, song.name, song.album.name, true))
|
||||||
queueItemId++
|
queueItemId++
|
||||||
}
|
}
|
||||||
|
|
||||||
mQueue.forEach {
|
mQueue.forEach { song ->
|
||||||
unified.add(QueueItem(queueItemId, it.name, it.album.name, false))
|
unified.add(QueueItem(queueItemId, song.name, song.album.name, false))
|
||||||
queueItemId++
|
queueItemId++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -714,15 +715,15 @@ class PlaybackStateManager private constructor() {
|
||||||
*/
|
*/
|
||||||
private fun unpackQueue(queueItems: List<QueueItem>) {
|
private fun unpackQueue(queueItems: List<QueueItem>) {
|
||||||
// When unpacking, first traverse albums and then traverse album songs to reduce
|
// When unpacking, first traverse albums and then traverse album songs to reduce
|
||||||
// the amount of useless comparisons in large queues.
|
// the amount of comparisons in large queues.
|
||||||
queueItems.forEach { item ->
|
queueItems.forEach { item ->
|
||||||
musicStore.albums.find { it.name == item.albumName }?.songs?.find {
|
musicStore.albums.find {
|
||||||
it.name == item.songName
|
it.name == item.albumName
|
||||||
}?.let {
|
}?.songs?.find { it.name == item.songName }?.let { song ->
|
||||||
if (item.isUserQueue) {
|
if (item.isUserQueue) {
|
||||||
mUserQueue.add(it)
|
mUserQueue.add(song)
|
||||||
} else {
|
} else {
|
||||||
mQueue.add(it)
|
mQueue.add(song)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -731,8 +732,8 @@ class PlaybackStateManager private constructor() {
|
||||||
// to the db but are now deleted when the restore occurred.
|
// to the db but are now deleted when the restore occurred.
|
||||||
// Not done if in user queue because that could result in a bad index being created.
|
// Not done if in user queue because that could result in a bad index being created.
|
||||||
if (!mIsInUserQueue) {
|
if (!mIsInUserQueue) {
|
||||||
mSong?.let {
|
mSong?.let { song ->
|
||||||
val index = mQueue.indexOf(it)
|
val index = mQueue.indexOf(song)
|
||||||
mIndex = if (index != -1) index else mIndex
|
mIndex = if (index != -1) index else mIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ class AudioReactor(
|
||||||
onEnd = { player.volume = VOLUME_FULL }
|
onEnd = { player.volume = VOLUME_FULL }
|
||||||
)
|
)
|
||||||
addUpdateListener {
|
addUpdateListener {
|
||||||
player.volume = it.animatedValue as Float
|
player.volume = animatedValue as Float
|
||||||
}
|
}
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,14 +77,12 @@ class PlaybackNotification private constructor(
|
||||||
if (colorize) {
|
if (colorize) {
|
||||||
// loadBitmap() is concurrent, so only call back to the object calling this function when
|
// loadBitmap() is concurrent, so only call back to the object calling this function when
|
||||||
// the loading is over.
|
// the loading is over.
|
||||||
loadBitmap(context, song) {
|
loadBitmap(context, song) { bitmap ->
|
||||||
setLargeIcon(it)
|
setLargeIcon(bitmap)
|
||||||
|
|
||||||
onDone()
|
onDone()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setLargeIcon(null)
|
setLargeIcon(null)
|
||||||
|
|
||||||
onDone()
|
onDone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,9 +112,7 @@ class PlaybackNotification private constructor(
|
||||||
* Apply the current [parent] to the header of the notification.
|
* Apply the current [parent] to the header of the notification.
|
||||||
*/
|
*/
|
||||||
fun setParent(context: Context, parent: Parent?) {
|
fun setParent(context: Context, parent: Parent?) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// A blank parent always means that the mode is ALL_SONGS
|
// A blank parent always means that the mode is ALL_SONGS
|
||||||
setSubText(parent?.displayName ?: context.getString(R.string.label_all_songs))
|
setSubText(parent?.displayName ?: context.getString(R.string.label_all_songs))
|
||||||
|
|
|
@ -282,7 +282,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
override fun onColorizeNotifUpdate(doColorize: Boolean) {
|
override fun onColorizeNotifUpdate(doColorize: Boolean) {
|
||||||
playbackManager.song?.let { song ->
|
playbackManager.song?.let { song ->
|
||||||
notification.setMetadata(
|
notification.setMetadata(
|
||||||
this, song, settingsManager.colorizeNotif, {}
|
this, song, settingsManager.colorizeNotif, ::startForegroundOrNotify
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,8 +365,8 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.album.name)
|
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.album.name)
|
||||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
|
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
|
||||||
|
|
||||||
loadBitmap(this, song) {
|
loadBitmap(this, song) { bitmap ->
|
||||||
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, it)
|
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
|
||||||
mediaSession.setMetadata(builder.build())
|
mediaSession.setMetadata(builder.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -383,8 +383,8 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
}.conflate()
|
}.conflate()
|
||||||
|
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
pollFlow.takeWhile { player.isPlaying }.collect {
|
pollFlow.takeWhile { player.isPlaying }.collect { pos ->
|
||||||
playbackManager.setPosition(it)
|
playbackManager.setPosition(pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package org.oxycblt.auxio.recycler
|
package org.oxycblt.auxio.recycler
|
||||||
|
|
||||||
|
import android.widget.ImageButton
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
@ -163,20 +165,22 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
|
||||||
*/
|
*/
|
||||||
fun toInt(): Int {
|
fun toInt(): Int {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
NONE -> CONSTANT_NONE
|
NONE -> CONST_NONE
|
||||||
ALPHA_UP -> CONSTANT_ALPHA_UP
|
ALPHA_UP -> CONST_ALPHA_UP
|
||||||
ALPHA_DOWN -> CONSTANT_ALPHA_DOWN
|
ALPHA_DOWN -> CONST_ALPHA_DOWN
|
||||||
NUMERIC_UP -> CONSTANT_NUMERIC_UP
|
NUMERIC_UP -> CONST_NUMERIC_UP
|
||||||
NUMERIC_DOWN -> CONSTANT_NUMERIC_DOWN
|
NUMERIC_DOWN -> CONST_NUMERIC_DOWN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CONSTANT_NONE = 0xA060
|
private const val CONST_NONE = 0xA060
|
||||||
const val CONSTANT_ALPHA_UP = 0xA061
|
private const val CONST_ALPHA_UP = 0xA061
|
||||||
const val CONSTANT_ALPHA_DOWN = 0xA062
|
private const val CONST_ALPHA_DOWN = 0xA062
|
||||||
const val CONSTANT_NUMERIC_UP = 0xA063
|
private const val CONST_NUMERIC_UP = 0xA063
|
||||||
const val CONSTANT_NUMERIC_DOWN = 0xA065
|
private const val CONST_NUMERIC_DOWN = 0xA065
|
||||||
|
|
||||||
|
const val CONST_SORT_DEFAULT = CONST_ALPHA_DOWN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an enum for an int constant
|
* Get an enum for an int constant
|
||||||
|
@ -184,14 +188,22 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
|
||||||
*/
|
*/
|
||||||
fun fromInt(value: Int): SortMode? {
|
fun fromInt(value: Int): SortMode? {
|
||||||
return when (value) {
|
return when (value) {
|
||||||
CONSTANT_NONE -> NONE
|
CONST_NONE -> NONE
|
||||||
CONSTANT_ALPHA_UP -> ALPHA_UP
|
CONST_ALPHA_UP -> ALPHA_UP
|
||||||
CONSTANT_ALPHA_DOWN -> ALPHA_DOWN
|
CONST_ALPHA_DOWN -> ALPHA_DOWN
|
||||||
CONSTANT_NUMERIC_UP -> NUMERIC_UP
|
CONST_NUMERIC_UP -> NUMERIC_UP
|
||||||
CONSTANT_NUMERIC_DOWN -> NUMERIC_DOWN
|
CONST_NUMERIC_DOWN -> NUMERIC_DOWN
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind the [SortMode] icon for an ImageButton.
|
||||||
|
*/
|
||||||
|
@BindingAdapter("sortIcon")
|
||||||
|
fun ImageButton.bindSortIcon(mode: SortMode) {
|
||||||
|
setImageResource(mode.iconRes)
|
||||||
|
}
|
||||||
|
|
|
@ -47,10 +47,13 @@ class SearchFragment : Fragment() {
|
||||||
): View {
|
): View {
|
||||||
val binding = FragmentSearchBinding.inflate(inflater)
|
val binding = FragmentSearchBinding.inflate(inflater)
|
||||||
|
|
||||||
|
val searchAdapter = SearchAdapter(::onItemSelection) { view, data ->
|
||||||
|
newMenu(view, data)
|
||||||
|
}
|
||||||
|
|
||||||
// Apply the accents manually. Not going through the mess of converting my app's
|
// Apply the accents manually. Not going through the mess of converting my app's
|
||||||
// styling to Material given all the second-and-third-order effects it has.
|
// styling to Material given all the second-and-third-order effects it has.
|
||||||
val accent = Accent.get().color.toColor(requireContext())
|
val accent = Accent.get().color.toColor(requireContext())
|
||||||
val searchAdapter = SearchAdapter(::onItemSelection) { view, data -> newMenu(view, data) }
|
|
||||||
val toolbarParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
val toolbarParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
||||||
val defaultParams = toolbarParams.scrollFlags
|
val defaultParams = toolbarParams.scrollFlags
|
||||||
|
|
||||||
|
@ -59,13 +62,14 @@ class SearchFragment : Fragment() {
|
||||||
binding.searchToolbar.apply {
|
binding.searchToolbar.apply {
|
||||||
menu.findItem(searchModel.filterMode.toId()).isChecked = true
|
menu.findItem(searchModel.filterMode.toId()).isChecked = true
|
||||||
|
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener { item ->
|
||||||
if (it.itemId != R.id.submenu_filtering) {
|
if (item.itemId != R.id.submenu_filtering) {
|
||||||
it.isChecked = true
|
searchModel.updateFilterModeWithId(item.itemId, requireContext())
|
||||||
searchModel.updateFilterModeWithId(it.itemId, requireContext())
|
item.isChecked = true
|
||||||
|
|
||||||
true
|
true
|
||||||
} else false
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,9 +79,9 @@ class SearchFragment : Fragment() {
|
||||||
setEndIconTintList(R.color.control_color.toStateList(context))
|
setEndIconTintList(R.color.control_color.toStateList(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.searchEditText.addTextChangedListener {
|
binding.searchEditText.addTextChangedListener { text ->
|
||||||
// Run the search with the updated text as the query
|
// Run the search with the updated text as the query
|
||||||
searchModel.doSearch(it?.toString() ?: "", requireContext())
|
searchModel.doSearch(text?.toString() ?: "", requireContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.searchRecycler.apply {
|
binding.searchRecycler.apply {
|
||||||
|
@ -96,12 +100,12 @@ class SearchFragment : Fragment() {
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
searchModel.searchResults.observe(viewLifecycleOwner) {
|
searchModel.searchResults.observe(viewLifecycleOwner) { results ->
|
||||||
searchAdapter.submitList(it) {
|
searchAdapter.submitList(results) {
|
||||||
binding.searchRecycler.scrollToPosition(0)
|
binding.searchRecycler.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
// If the data is empty, then the ability for the toolbar to collapse
|
// If the data is empty, then the ability for the toolbar to collapse
|
||||||
// on scroll should be disabled.
|
// on scroll should be disabled.
|
||||||
binding.searchAppbar.setExpanded(true)
|
binding.searchAppbar.setExpanded(true)
|
||||||
|
@ -113,19 +117,17 @@ class SearchFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) {
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
if (it != null) {
|
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
when (it) {
|
when (item) {
|
||||||
is Song -> SearchFragmentDirections.actionShowAlbum(it.album.id)
|
is Song -> SearchFragmentDirections.actionShowAlbum(item.album.id)
|
||||||
is Album -> SearchFragmentDirections.actionShowAlbum(it.id)
|
is Album -> SearchFragmentDirections.actionShowAlbum(item.id)
|
||||||
is Artist -> SearchFragmentDirections.actionShowArtist(it.id)
|
is Artist -> SearchFragmentDirections.actionShowArtist(item.id)
|
||||||
|
|
||||||
else -> return@observe
|
else -> return@observe
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
logD("Fragment created.")
|
logD("Fragment created.")
|
||||||
|
|
||||||
|
@ -135,7 +137,7 @@ class SearchFragment : Fragment() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
searchModel.updateNavigationStatus(false)
|
searchModel.setNavigating(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@ -159,7 +161,7 @@ class SearchFragment : Fragment() {
|
||||||
requireView().rootView.clearFocus()
|
requireView().rootView.clearFocus()
|
||||||
|
|
||||||
if (!searchModel.isNavigating) {
|
if (!searchModel.isNavigating) {
|
||||||
searchModel.updateNavigationStatus(true)
|
searchModel.setNavigating(true)
|
||||||
|
|
||||||
logD("Navigating to the detail fragment for ${item.name}")
|
logD("Navigating to the detail fragment for ${item.name}")
|
||||||
|
|
||||||
|
@ -172,7 +174,7 @@ class SearchFragment : Fragment() {
|
||||||
// If given model wasn't valid, then reset the navigation status
|
// If given model wasn't valid, then reset the navigation status
|
||||||
// and abort the navigation.
|
// and abort the navigation.
|
||||||
else -> {
|
else -> {
|
||||||
searchModel.updateNavigationStatus(false)
|
searchModel.setNavigating(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,30 +53,30 @@ class SearchViewModel : ViewModel() {
|
||||||
val results = mutableListOf<BaseModel>()
|
val results = mutableListOf<BaseModel>()
|
||||||
|
|
||||||
if (mFilterMode.isAllOr(DisplayMode.SHOW_ARTISTS)) {
|
if (mFilterMode.isAllOr(DisplayMode.SHOW_ARTISTS)) {
|
||||||
musicStore.artists.filterByOrNull(query)?.let {
|
musicStore.artists.filterByOrNull(query)?.let { artists ->
|
||||||
results.add(Header(id = -2, name = context.getString(R.string.label_artists)))
|
results.add(Header(id = -2, name = context.getString(R.string.label_artists)))
|
||||||
results.addAll(it)
|
results.addAll(artists)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFilterMode.isAllOr(DisplayMode.SHOW_ALBUMS)) {
|
if (mFilterMode.isAllOr(DisplayMode.SHOW_ALBUMS)) {
|
||||||
musicStore.albums.filterByOrNull(query)?.let {
|
musicStore.albums.filterByOrNull(query)?.let { albums ->
|
||||||
results.add(Header(id = -3, name = context.getString(R.string.label_albums)))
|
results.add(Header(id = -3, name = context.getString(R.string.label_albums)))
|
||||||
results.addAll(it)
|
results.addAll(albums)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFilterMode.isAllOr(DisplayMode.SHOW_GENRES)) {
|
if (mFilterMode.isAllOr(DisplayMode.SHOW_GENRES)) {
|
||||||
musicStore.genres.filterByOrNull(query)?.let {
|
musicStore.genres.filterByOrNull(query)?.let { genres ->
|
||||||
results.add(Header(id = -4, name = context.getString(R.string.label_genres)))
|
results.add(Header(id = -4, name = context.getString(R.string.label_genres)))
|
||||||
results.addAll(it)
|
results.addAll(genres)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFilterMode.isAllOr(DisplayMode.SHOW_SONGS)) {
|
if (mFilterMode.isAllOr(DisplayMode.SHOW_SONGS)) {
|
||||||
musicStore.songs.filterByOrNull(query)?.let {
|
musicStore.songs.filterByOrNull(query)?.let { songs ->
|
||||||
results.add(Header(id = -5, name = context.getString(R.string.label_songs)))
|
results.add(Header(id = -5, name = context.getString(R.string.label_songs)))
|
||||||
results.addAll(it)
|
results.addAll(songs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,9 @@ class SearchViewModel : ViewModel() {
|
||||||
* a value if the resulting list is empty.
|
* a value if the resulting list is empty.
|
||||||
*/
|
*/
|
||||||
private fun List<BaseModel>.filterByOrNull(value: String): List<BaseModel>? {
|
private fun List<BaseModel>.filterByOrNull(value: String): List<BaseModel>? {
|
||||||
val filtered = filter { it.name.contains(value, ignoreCase = true) }
|
val filtered = filter {
|
||||||
|
it.name.contains(value, ignoreCase = true)
|
||||||
|
}
|
||||||
|
|
||||||
return if (filtered.isNotEmpty()) filtered else null
|
return if (filtered.isNotEmpty()) filtered else null
|
||||||
}
|
}
|
||||||
|
@ -109,7 +111,7 @@ class SearchViewModel : ViewModel() {
|
||||||
/**
|
/**
|
||||||
* Update the current navigation status to [isNavigating]
|
* Update the current navigation status to [isNavigating]
|
||||||
*/
|
*/
|
||||||
fun updateNavigationStatus(isNavigating: Boolean) {
|
fun setNavigating(isNavigating: Boolean) {
|
||||||
mIsNavigating = isNavigating
|
mIsNavigating = isNavigating
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,17 @@ class SettingsFragment : Fragment() {
|
||||||
|
|
||||||
binding.settingsToolbar.setOnMenuItemClickListener {
|
binding.settingsToolbar.setOnMenuItemClickListener {
|
||||||
if (it.itemId == R.id.action_open_about) {
|
if (it.itemId == R.id.action_open_about) {
|
||||||
AboutDialog().show(childFragmentManager, "DIALOG")
|
AboutDialog().show(childFragmentManager, ABOUT_DIALOG_TAG)
|
||||||
true
|
true
|
||||||
} else false
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ABOUT_DIALOG_TAG = "TAG_ABOUT_DIALOG"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,9 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
private fun recursivelyHandleChildren(pref: Preference) {
|
private fun recursivelyHandleChildren(pref: Preference) {
|
||||||
if (pref is PreferenceCategory) {
|
if (pref is PreferenceCategory) {
|
||||||
// If this preference is a category of its own, handle its own children
|
// If this preference is a category of its own, handle its own children
|
||||||
pref.children.forEach { recursivelyHandleChildren(it) }
|
pref.children.forEach { pref ->
|
||||||
|
recursivelyHandleChildren(pref)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
handlePreference(pref)
|
handlePreference(pref)
|
||||||
}
|
}
|
||||||
|
@ -151,9 +153,10 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
// has a bug where ugly dividers will show with the RecyclerView even if you disable them.
|
// has a bug where ugly dividers will show with the RecyclerView even if you disable them.
|
||||||
// This is why I hate using third party libraries.
|
// This is why I hate using third party libraries.
|
||||||
val recycler = RecyclerView(requireContext()).apply {
|
val recycler = RecyclerView(requireContext()).apply {
|
||||||
adapter = AccentAdapter {
|
adapter = AccentAdapter { accent ->
|
||||||
if (it != Accent.get()) {
|
if (accent != Accent.get()) {
|
||||||
settingsManager.accent = it
|
// TODO: Move this to Accent.set?
|
||||||
|
settingsManager.accent = accent
|
||||||
|
|
||||||
requireActivity().recreate()
|
requireActivity().recreate()
|
||||||
}
|
}
|
||||||
|
@ -179,11 +182,8 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
customView(view = recycler)
|
customView(view = recycler)
|
||||||
|
|
||||||
invalidateDividers(showTop = false, showBottom = false)
|
invalidateDividers(showTop = false, showBottom = false)
|
||||||
|
|
||||||
negativeButton(android.R.string.cancel)
|
negativeButton(android.R.string.cancel)
|
||||||
|
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,10 @@ class SettingsManager private constructor(context: Context) :
|
||||||
)
|
)
|
||||||
|
|
||||||
// When converted, write them to the new accent pref and delete the old one.
|
// When converted, write them to the new accent pref and delete the old one.
|
||||||
sharedPrefs.edit {
|
sharedPrefs.edit()
|
||||||
putInt(Keys.KEY_ACCENT, newAccent)
|
.putInt(Keys.KEY_ACCENT, newAccent)
|
||||||
remove(Keys.KEY_ACCENT_OLD)
|
.remove(Keys.KEY_ACCENT_OLD)
|
||||||
apply()
|
.apply()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ACCENTS[sharedPrefs.getInt(Keys.KEY_ACCENT, 5)]
|
return ACCENTS[sharedPrefs.getInt(Keys.KEY_ACCENT, 5)]
|
||||||
|
@ -124,8 +123,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
var librarySortMode: SortMode
|
var librarySortMode: SortMode
|
||||||
get() = SortMode.fromInt(
|
get() = SortMode.fromInt(
|
||||||
sharedPrefs.getInt(
|
sharedPrefs.getInt(
|
||||||
Keys.KEY_LIBRARY_SORT_MODE,
|
Keys.KEY_LIBRARY_SORT_MODE, SortMode.CONST_SORT_DEFAULT
|
||||||
SortMode.CONSTANT_ALPHA_DOWN
|
|
||||||
)
|
)
|
||||||
) ?: SortMode.ALPHA_DOWN
|
) ?: SortMode.ALPHA_DOWN
|
||||||
|
|
||||||
|
|
|
@ -68,14 +68,13 @@ class SongsFragment : Fragment() {
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
|
|
||||||
val spans = getSpans()
|
val spans = getSpans()
|
||||||
|
|
||||||
if (spans != 1) {
|
if (spans != 1) {
|
||||||
layoutManager = GridLayoutManager(requireContext(), spans)
|
layoutManager = GridLayoutManager(requireContext(), spans)
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
// Disable fast scrolling if there is nothing to scroll
|
|
||||||
if (!canScroll()) {
|
if (!canScroll()) {
|
||||||
|
// Disable fast scrolling if there is nothing to scroll
|
||||||
binding.songFastScroll.visibility = View.GONE
|
binding.songFastScroll.visibility = View.GONE
|
||||||
binding.songFastScrollThumb.visibility = View.GONE
|
binding.songFastScrollThumb.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,8 @@ class ActionMenu(
|
||||||
}
|
}
|
||||||
|
|
||||||
inflate(menuRes)
|
inflate(menuRes)
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener { item ->
|
||||||
onMenuClick(it.itemId)
|
onMenuClick(item.itemId)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,8 +196,7 @@ fun RecyclerView.canScroll() = computeVerticalScrollRange() > height
|
||||||
* @return True if we are in the irregular landscape mode, false if not.
|
* @return True if we are in the irregular landscape mode, false if not.
|
||||||
*/
|
*/
|
||||||
fun Activity.isIrregularLandscape(): Boolean {
|
fun Activity.isIrregularLandscape(): Boolean {
|
||||||
return isLandscape(resources) &&
|
return isLandscape(resources) && !isSystemBarOnBottom(this)
|
||||||
!isSystemBarOnBottom(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -64,8 +64,8 @@ class SlideLinearLayout @JvmOverloads constructor(
|
||||||
// I dont even know what this does.
|
// I dont even know what this does.
|
||||||
var consumed = false
|
var consumed = false
|
||||||
|
|
||||||
children.forEach {
|
children.forEach { view ->
|
||||||
consumed = consumed or super.drawChild(canvas, it, drawingTime)
|
consumed = consumed or super.drawChild(canvas, view, drawingTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
return consumed
|
return consumed
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?><!-- Animated icons derived from noice https://github.com/ashutoshgngwr/noice/ -->
|
||||||
<!-- Animated icons derived from noice https://github.com/ashutoshgngwr/noice/ -->
|
|
||||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
android:drawable="@drawable/ic_pause_large">
|
android:drawable="@drawable/ic_pause_large">
|
||||||
|
|
|
@ -132,9 +132,9 @@
|
||||||
android:id="@+id/playback_duration_current"
|
android:id="@+id/playback_duration_current"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@{playbackModel.formattedPosition}"
|
|
||||||
android:layout_marginStart="@dimen/margin_mid_huge"
|
android:layout_marginStart="@dimen/margin_mid_huge"
|
||||||
android:layout_marginBottom="@dimen/margin_medium"
|
android:layout_marginBottom="@dimen/margin_medium"
|
||||||
|
android:text="@{playbackModel.formattedPosition}"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/playback_play_pause"
|
app:layout_constraintBottom_toTopOf="@+id/playback_play_pause"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
tools:text="11:38" />
|
tools:text="11:38" />
|
||||||
|
|
Loading…
Reference in a new issue