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:
OxygenCobalt 2021-03-05 12:56:04 -07:00
parent fab377eba4
commit 2c93e3f362
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
41 changed files with 347 additions and 327 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -62,8 +62,8 @@ class ActionMenu(
} }
inflate(menuRes) inflate(menuRes)
setOnMenuItemClickListener { setOnMenuItemClickListener { item ->
onMenuClick(it.itemId) onMenuClick(item.itemId)
true true
} }
} }

View file

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

View file

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

View file

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

View file

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