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