ui: audit null safety
Audit null safety to remove extraneous and stupid calls while optimizing certain checks here and there. This commit is primarily centered around the introduction of a new utility: unlikelyToBeNull. This call uses requireNotNull on debug builds and !! on release builds, which allows for bug catching in an easier manner on normal builds while also allowing for optimizations on the release builds.
This commit is contained in:
parent
05a5ef5c3f
commit
e54a58c612
29 changed files with 165 additions and 116 deletions
|
@ -26,6 +26,7 @@ import org.oxycblt.auxio.databinding.DialogAccentBinding
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog responsible for showing the list of accents to select.
|
* Dialog responsible for showing the list of accents to select.
|
||||||
|
@ -44,7 +45,7 @@ class AccentCustomizeDialog :
|
||||||
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
if (accentAdapter.selectedAccent != settingsManager.accent) {
|
if (accentAdapter.selectedAccent != settingsManager.accent) {
|
||||||
logD("Applying new accent")
|
logD("Applying new accent")
|
||||||
settingsManager.accent = requireNotNull(accentAdapter.selectedAccent)
|
settingsManager.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
||||||
requireActivity().recreate()
|
requireActivity().recreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ class AccentCustomizeDialog :
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
outState.putInt(KEY_PENDING_ACCENT, requireNotNull(accentAdapter.selectedAccent).index)
|
outState.putInt(KEY_PENDING_ACCENT, unlikelyToBeNull(accentAdapter.selectedAccent).index)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyBinding(binding: DialogAccentBinding) {
|
override fun onDestroyBinding(binding: DialogAccentBinding) {
|
||||||
|
|
|
@ -41,6 +41,7 @@ import org.oxycblt.auxio.util.canScroll
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
import org.oxycblt.auxio.util.showToast
|
import org.oxycblt.auxio.util.showToast
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [DetailFragment] for an album.
|
* The [DetailFragment] for an album.
|
||||||
|
@ -53,15 +54,16 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
||||||
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
|
||||||
detailModel.setAlbumId(args.albumId)
|
detailModel.setAlbumId(args.albumId)
|
||||||
|
|
||||||
setupToolbar(detailModel.currentAlbum.value!!, R.menu.menu_album_detail) { itemId ->
|
setupToolbar(unlikelyToBeNull(detailModel.currentAlbum.value), R.menu.menu_album_detail) {
|
||||||
|
itemId ->
|
||||||
when (itemId) {
|
when (itemId) {
|
||||||
R.id.action_play_next -> {
|
R.id.action_play_next -> {
|
||||||
playbackModel.playNext(detailModel.currentAlbum.value!!)
|
playbackModel.playNext(unlikelyToBeNull(detailModel.currentAlbum.value))
|
||||||
requireContext().showToast(R.string.lbl_queue_added)
|
requireContext().showToast(R.string.lbl_queue_added)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.action_queue_add -> {
|
R.id.action_queue_add -> {
|
||||||
playbackModel.addToQueue(detailModel.currentAlbum.value!!)
|
playbackModel.addToQueue(unlikelyToBeNull(detailModel.currentAlbum.value))
|
||||||
requireContext().showToast(R.string.lbl_queue_added)
|
requireContext().showToast(R.string.lbl_queue_added)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -102,11 +104,11 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayParent() {
|
override fun onPlayParent() {
|
||||||
playbackModel.playAlbum(requireNotNull(detailModel.currentAlbum.value), false)
|
playbackModel.playAlbum(unlikelyToBeNull(detailModel.currentAlbum.value), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onShuffleParent() {
|
override fun onShuffleParent() {
|
||||||
playbackModel.playAlbum(requireNotNull(detailModel.currentAlbum.value), true)
|
playbackModel.playAlbum(unlikelyToBeNull(detailModel.currentAlbum.value), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onShowSortMenu(anchor: View) {
|
override fun onShowSortMenu(anchor: View) {
|
||||||
|
@ -121,7 +123,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigate(
|
.navigate(
|
||||||
AlbumDetailFragmentDirections.actionShowArtist(
|
AlbumDetailFragmentDirections.actionShowArtist(
|
||||||
requireNotNull(detailModel.currentAlbum.value).artist.id))
|
unlikelyToBeNull(detailModel.currentAlbum.value).artist.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleNavigation(item: Music?, adapter: AlbumDetailAdapter) {
|
private fun handleNavigation(item: Music?, adapter: AlbumDetailAdapter) {
|
||||||
|
@ -130,7 +132,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
||||||
// 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 == item.album.id) {
|
if (unlikelyToBeNull(detailModel.currentAlbum.value).id == item.album.id) {
|
||||||
logD("Navigating to a song in this album")
|
logD("Navigating to a song in this album")
|
||||||
scrollToItem(item.id, adapter)
|
scrollToItem(item.id, adapter)
|
||||||
detailModel.finishNavToItem()
|
detailModel.finishNavToItem()
|
||||||
|
@ -144,7 +146,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
||||||
// 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 == item.id) {
|
if (unlikelyToBeNull(detailModel.currentAlbum.value).id == item.id) {
|
||||||
logD("Navigating to the top of this album")
|
logD("Navigating to the top of this album")
|
||||||
binding.detailRecycler.scrollToPosition(0)
|
binding.detailRecycler.scrollToPosition(0)
|
||||||
detailModel.finishNavToItem()
|
detailModel.finishNavToItem()
|
||||||
|
@ -197,7 +199,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playbackModel.playbackMode.value == PlaybackMode.IN_ALBUM &&
|
if (playbackModel.playbackMode.value == PlaybackMode.IN_ALBUM &&
|
||||||
playbackModel.parent.value?.id == detailModel.currentAlbum.value!!.id) {
|
playbackModel.parent.value?.id == unlikelyToBeNull(detailModel.currentAlbum.value).id) {
|
||||||
adapter.highlightSong(song, binding.detailRecycler)
|
adapter.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
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.util.applySpans
|
import org.oxycblt.auxio.util.applySpans
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [DetailFragment] for an artist.
|
* The [DetailFragment] for an artist.
|
||||||
|
@ -50,7 +51,7 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
||||||
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
|
||||||
detailModel.setArtistId(args.artistId)
|
detailModel.setArtistId(args.artistId)
|
||||||
|
|
||||||
setupToolbar(detailModel.currentArtist.value!!)
|
setupToolbar(unlikelyToBeNull(detailModel.currentArtist.value))
|
||||||
requireBinding().detailRecycler.apply {
|
requireBinding().detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
applySpans { pos ->
|
applySpans { pos ->
|
||||||
|
@ -91,11 +92,11 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayParent() {
|
override fun onPlayParent() {
|
||||||
playbackModel.playArtist(requireNotNull(detailModel.currentArtist.value), false)
|
playbackModel.playArtist(unlikelyToBeNull(detailModel.currentArtist.value), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onShuffleParent() {
|
override fun onShuffleParent() {
|
||||||
playbackModel.playArtist(requireNotNull(detailModel.currentArtist.value), true)
|
playbackModel.playArtist(unlikelyToBeNull(detailModel.currentArtist.value), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onShowSortMenu(anchor: View) {
|
override fun onShowSortMenu(anchor: View) {
|
||||||
|
|
|
@ -101,8 +101,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
|
|
||||||
titleShown = visible
|
titleShown = visible
|
||||||
|
|
||||||
if (mTitleAnimator != null) {
|
val titleAnimator = mTitleAnimator
|
||||||
mTitleAnimator!!.cancel()
|
if (titleAnimator != null) {
|
||||||
|
titleAnimator.cancel()
|
||||||
mTitleAnimator = null
|
mTitleAnimator = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Base [Fragment] implementing the base features shared across all detail fragments.
|
* A Base [Fragment] implementing the base features shared across all detail fragments.
|
||||||
|
@ -95,6 +96,9 @@ abstract class DetailFragment : ViewBindingFragment<FragmentDetailBinding>() {
|
||||||
) {
|
) {
|
||||||
logD("Launching menu")
|
logD("Launching menu")
|
||||||
|
|
||||||
|
// Scrolling breaks the menus, so we stop any momentum currently going on.
|
||||||
|
requireBinding().detailRecycler.stopScroll()
|
||||||
|
|
||||||
PopupMenu(anchor.context, anchor).apply {
|
PopupMenu(anchor.context, anchor).apply {
|
||||||
inflate(R.menu.menu_detail_sort)
|
inflate(R.menu.menu_detail_sort)
|
||||||
|
|
||||||
|
@ -104,7 +108,7 @@ abstract class DetailFragment : ViewBindingFragment<FragmentDetailBinding>() {
|
||||||
onConfirm(sort.ascending(item.isChecked))
|
onConfirm(sort.ascending(item.isChecked))
|
||||||
} else {
|
} else {
|
||||||
item.isChecked = true
|
item.isChecked = true
|
||||||
onConfirm(requireNotNull(sort.assignId(item.itemId)))
|
onConfirm(unlikelyToBeNull(sort.assignId(item.itemId)))
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
|
@ -56,7 +56,7 @@ class DetailViewModel : ViewModel() {
|
||||||
get() = settingsManager.detailAlbumSort
|
get() = settingsManager.detailAlbumSort
|
||||||
set(value) {
|
set(value) {
|
||||||
settingsManager.detailAlbumSort = value
|
settingsManager.detailAlbumSort = value
|
||||||
refreshAlbumData()
|
currentAlbum.value?.let(::refreshAlbumData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mCurrentArtist = MutableLiveData<Artist?>()
|
private val mCurrentArtist = MutableLiveData<Artist?>()
|
||||||
|
@ -70,7 +70,7 @@ class DetailViewModel : ViewModel() {
|
||||||
get() = settingsManager.detailArtistSort
|
get() = settingsManager.detailArtistSort
|
||||||
set(value) {
|
set(value) {
|
||||||
settingsManager.detailArtistSort = value
|
settingsManager.detailArtistSort = value
|
||||||
refreshArtistData()
|
currentArtist.value?.let(::refreshArtistData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mCurrentGenre = MutableLiveData<Genre?>()
|
private val mCurrentGenre = MutableLiveData<Genre?>()
|
||||||
|
@ -84,7 +84,7 @@ class DetailViewModel : ViewModel() {
|
||||||
get() = settingsManager.detailGenreSort
|
get() = settingsManager.detailGenreSort
|
||||||
set(value) {
|
set(value) {
|
||||||
settingsManager.detailGenreSort = value
|
settingsManager.detailGenreSort = value
|
||||||
refreshGenreData()
|
currentGenre.value?.let(::refreshGenreData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mNavToItem = MutableLiveData<Music?>()
|
private val mNavToItem = MutableLiveData<Music?>()
|
||||||
|
@ -99,22 +99,30 @@ class DetailViewModel : ViewModel() {
|
||||||
fun setAlbumId(id: Long) {
|
fun setAlbumId(id: Long) {
|
||||||
if (mCurrentAlbum.value?.id == id) return
|
if (mCurrentAlbum.value?.id == id) return
|
||||||
val musicStore = MusicStore.requireInstance()
|
val musicStore = MusicStore.requireInstance()
|
||||||
mCurrentAlbum.value = musicStore.albums.find { it.id == id }
|
val album =
|
||||||
refreshAlbumData()
|
requireNotNull(musicStore.albums.find { it.id == id }) { "Invalid album ID provided " }
|
||||||
|
|
||||||
|
mCurrentAlbum.value = album
|
||||||
|
refreshAlbumData(album)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setArtistId(id: Long) {
|
fun setArtistId(id: Long) {
|
||||||
if (mCurrentArtist.value?.id == id) return
|
if (mCurrentArtist.value?.id == id) return
|
||||||
val musicStore = MusicStore.requireInstance()
|
val musicStore = MusicStore.requireInstance()
|
||||||
mCurrentArtist.value = musicStore.artists.find { it.id == id }
|
val artist =
|
||||||
refreshArtistData()
|
requireNotNull(musicStore.artists.find { it.id == id }) { "Invalid artist ID provided" }
|
||||||
|
|
||||||
|
mCurrentArtist.value = artist
|
||||||
|
refreshArtistData(artist)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setGenreId(id: Long) {
|
fun setGenreId(id: Long) {
|
||||||
if (mCurrentGenre.value?.id == id) return
|
if (mCurrentGenre.value?.id == id) return
|
||||||
val musicStore = MusicStore.requireInstance()
|
val musicStore = MusicStore.requireInstance()
|
||||||
mCurrentGenre.value = musicStore.genres.find { it.id == id }
|
val genre =
|
||||||
refreshGenreData()
|
requireNotNull(musicStore.genres.find { it.id == id }) { "Invalid genre ID provided" }
|
||||||
|
mCurrentGenre.value = genre
|
||||||
|
refreshGenreData(genre)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Navigate to an item, whether a song/album/artist */
|
/** Navigate to an item, whether a song/album/artist */
|
||||||
|
@ -132,38 +140,29 @@ class DetailViewModel : ViewModel() {
|
||||||
isNavigating = navigating
|
isNavigating = navigating
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshGenreData() {
|
private fun refreshGenreData(genre: Genre) {
|
||||||
logD("Refreshing genre data")
|
logD("Refreshing genre data")
|
||||||
val genre = requireNotNull(currentGenre.value)
|
|
||||||
val data = mutableListOf<Item>(genre)
|
val data = mutableListOf<Item>(genre)
|
||||||
|
|
||||||
data.add(SortHeader(-2, R.string.lbl_songs))
|
data.add(SortHeader(-2, R.string.lbl_songs))
|
||||||
data.addAll(settingsManager.detailGenreSort.genre(currentGenre.value!!))
|
data.addAll(genreSort.genre(genre))
|
||||||
|
|
||||||
mGenreData.value = data
|
mGenreData.value = data
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshArtistData() {
|
private fun refreshArtistData(artist: Artist) {
|
||||||
logD("Refreshing artist data")
|
logD("Refreshing artist data")
|
||||||
val artist = requireNotNull(currentArtist.value)
|
|
||||||
val data = mutableListOf<Item>(artist)
|
val data = mutableListOf<Item>(artist)
|
||||||
|
|
||||||
data.add(Header(-2, R.string.lbl_albums))
|
data.add(Header(-2, R.string.lbl_albums))
|
||||||
data.addAll(Sort.ByYear(false).albums(artist.albums))
|
data.addAll(Sort.ByYear(false).albums(artist.albums))
|
||||||
data.add(SortHeader(-3, R.string.lbl_songs))
|
data.add(SortHeader(-3, R.string.lbl_songs))
|
||||||
data.addAll(settingsManager.detailArtistSort.artist(artist))
|
data.addAll(artistSort.artist(artist))
|
||||||
|
|
||||||
mArtistData.value = data.toList()
|
mArtistData.value = data.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshAlbumData() {
|
private fun refreshAlbumData(album: Album) {
|
||||||
logD("Refreshing album data")
|
logD("Refreshing album data")
|
||||||
val album = requireNotNull(currentAlbum.value)
|
|
||||||
val data = mutableListOf<Item>(album)
|
val data = mutableListOf<Item>(album)
|
||||||
|
|
||||||
data.add(SortHeader(id = -2, R.string.lbl_albums))
|
data.add(SortHeader(id = -2, R.string.lbl_albums))
|
||||||
data.addAll(settingsManager.detailAlbumSort.album(currentAlbum.value!!))
|
data.addAll(albumSort.album(album))
|
||||||
|
|
||||||
mAlbumData.value = data
|
mAlbumData.value = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.util.applySpans
|
import org.oxycblt.auxio.util.applySpans
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [DetailFragment] for a genre.
|
* The [DetailFragment] for a genre.
|
||||||
|
@ -49,7 +50,7 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
||||||
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
|
||||||
detailModel.setGenreId(args.genreId)
|
detailModel.setGenreId(args.genreId)
|
||||||
|
|
||||||
setupToolbar(detailModel.currentGenre.value!!)
|
setupToolbar(unlikelyToBeNull(detailModel.currentGenre.value))
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
applySpans { pos ->
|
applySpans { pos ->
|
||||||
|
@ -83,11 +84,11 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayParent() {
|
override fun onPlayParent() {
|
||||||
playbackModel.playGenre(requireNotNull(detailModel.currentGenre.value), false)
|
playbackModel.playGenre(unlikelyToBeNull(detailModel.currentGenre.value), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onShuffleParent() {
|
override fun onShuffleParent() {
|
||||||
playbackModel.playGenre(requireNotNull(detailModel.currentGenre.value), true)
|
playbackModel.playGenre(unlikelyToBeNull(detailModel.currentGenre.value), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onShowSortMenu(anchor: View) {
|
override fun onShowSortMenu(anchor: View) {
|
||||||
|
@ -119,7 +120,7 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
||||||
private fun updateSong(song: Song?, adapter: GenreDetailAdapter) {
|
private fun updateSong(song: Song?, adapter: GenreDetailAdapter) {
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
if (playbackModel.playbackMode.value == PlaybackMode.IN_GENRE &&
|
if (playbackModel.playbackMode.value == PlaybackMode.IN_GENRE &&
|
||||||
playbackModel.parent.value?.id == detailModel.currentGenre.value!!.id) {
|
playbackModel.parent.value?.id == unlikelyToBeNull(detailModel.currentGenre.value).id) {
|
||||||
adapter.highlightSong(song, binding.detailRecycler)
|
adapter.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
|
||||||
|
|
|
@ -179,7 +179,11 @@ private constructor(
|
||||||
override fun bind(item: Album, listener: MenuItemListener) {
|
override fun bind(item: Album, listener: MenuItemListener) {
|
||||||
binding.parentImage.bindAlbumCover(item)
|
binding.parentImage.bindAlbumCover(item)
|
||||||
binding.parentName.textSafe = item.resolvedName
|
binding.parentName.textSafe = item.resolvedName
|
||||||
binding.parentInfo.textSafe = binding.context.getString(R.string.fmt_number, item.year)
|
binding.parentInfo.textSafe = if (item.year != null) {
|
||||||
|
binding.context.getString(R.string.fmt_number, item.year)
|
||||||
|
} else {
|
||||||
|
binding.context.getString(R.string.def_date)
|
||||||
|
}
|
||||||
|
|
||||||
binding.root.apply {
|
binding.root.apply {
|
||||||
setOnClickListener { listener.onItemClick(item) }
|
setOnClickListener { listener.onItemClick(item) }
|
||||||
|
|
|
@ -49,6 +49,7 @@ import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
import org.oxycblt.auxio.util.logTraceOrThrow
|
import org.oxycblt.auxio.util.logTraceOrThrow
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each
|
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each
|
||||||
|
@ -134,9 +135,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>() {
|
||||||
R.id.option_sort_asc -> {
|
R.id.option_sort_asc -> {
|
||||||
item.isChecked = !item.isChecked
|
item.isChecked = !item.isChecked
|
||||||
homeModel.updateCurrentSort(
|
homeModel.updateCurrentSort(
|
||||||
requireNotNull(
|
unlikelyToBeNull(
|
||||||
homeModel
|
homeModel
|
||||||
.getSortForDisplay(homeModel.currentTab.value!!)
|
.getSortForDisplay(unlikelyToBeNull(homeModel.currentTab.value))
|
||||||
.ascending(item.isChecked)))
|
.ascending(item.isChecked)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,9 +145,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>() {
|
||||||
else -> {
|
else -> {
|
||||||
item.isChecked = true
|
item.isChecked = true
|
||||||
homeModel.updateCurrentSort(
|
homeModel.updateCurrentSort(
|
||||||
requireNotNull(
|
unlikelyToBeNull(
|
||||||
homeModel
|
homeModel
|
||||||
.getSortForDisplay(homeModel.currentTab.value!!)
|
.getSortForDisplay(unlikelyToBeNull(homeModel.currentTab.value))
|
||||||
.assignId(item.itemId)))
|
.assignId(item.itemId)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
|
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
|
||||||
|
@ -113,19 +114,19 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
|
||||||
when (mCurrentTab.value) {
|
when (mCurrentTab.value) {
|
||||||
DisplayMode.SHOW_SONGS -> {
|
DisplayMode.SHOW_SONGS -> {
|
||||||
settingsManager.libSongSort = sort
|
settingsManager.libSongSort = sort
|
||||||
mSongs.value = sort.songs(mSongs.value!!)
|
mSongs.value = sort.songs(unlikelyToBeNull(mSongs.value))
|
||||||
}
|
}
|
||||||
DisplayMode.SHOW_ALBUMS -> {
|
DisplayMode.SHOW_ALBUMS -> {
|
||||||
settingsManager.libAlbumSort = sort
|
settingsManager.libAlbumSort = sort
|
||||||
mAlbums.value = sort.albums(mAlbums.value!!)
|
mAlbums.value = sort.albums(unlikelyToBeNull(mAlbums.value))
|
||||||
}
|
}
|
||||||
DisplayMode.SHOW_ARTISTS -> {
|
DisplayMode.SHOW_ARTISTS -> {
|
||||||
settingsManager.libArtistSort = sort
|
settingsManager.libArtistSort = sort
|
||||||
mArtists.value = sort.artists(mArtists.value!!)
|
mArtists.value = sort.artists(unlikelyToBeNull(mArtists.value))
|
||||||
}
|
}
|
||||||
DisplayMode.SHOW_GENRES -> {
|
DisplayMode.SHOW_GENRES -> {
|
||||||
settingsManager.libGenreSort = sort
|
settingsManager.libGenreSort = sort
|
||||||
mGenres.value = sort.genres(mGenres.value!!)
|
mGenres.value = sort.genres(unlikelyToBeNull(mGenres.value))
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.oxycblt.auxio.ui.PrimitiveBackingData
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.ui.sliceArticle
|
import org.oxycblt.auxio.ui.sliceArticle
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [HomeListFragment] for showing a list of [Album]s.
|
* A [HomeListFragment] for showing a list of [Album]s.
|
||||||
|
@ -50,7 +51,7 @@ class AlbumListFragment : HomeListFragment<Album>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int): String? {
|
override fun getPopup(pos: Int): String? {
|
||||||
val album = homeModel.albums.value!![pos]
|
val album = unlikelyToBeNull(homeModel.albums.value)[pos]
|
||||||
|
|
||||||
// Change how we display the popup depending on the mode.
|
// Change how we display the popup depending on the mode.
|
||||||
return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS)) {
|
return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS)) {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.oxycblt.auxio.ui.MonoAdapter
|
||||||
import org.oxycblt.auxio.ui.PrimitiveBackingData
|
import org.oxycblt.auxio.ui.PrimitiveBackingData
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.ui.sliceArticle
|
import org.oxycblt.auxio.ui.sliceArticle
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [HomeListFragment] for showing a list of [Artist]s.
|
* A [HomeListFragment] for showing a list of [Artist]s.
|
||||||
|
@ -48,7 +49,11 @@ class ArtistListFragment : HomeListFragment<Artist>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int) =
|
override fun getPopup(pos: Int) =
|
||||||
homeModel.artists.value!![pos].resolvedName.sliceArticle().first().uppercase()
|
unlikelyToBeNull(homeModel.artists.value)[pos]
|
||||||
|
.resolvedName
|
||||||
|
.sliceArticle()
|
||||||
|
.first()
|
||||||
|
.uppercase()
|
||||||
|
|
||||||
override fun onItemClick(item: Item) {
|
override fun onItemClick(item: Item) {
|
||||||
check(item is Artist)
|
check(item is Artist)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.oxycblt.auxio.ui.MonoAdapter
|
||||||
import org.oxycblt.auxio.ui.PrimitiveBackingData
|
import org.oxycblt.auxio.ui.PrimitiveBackingData
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.ui.sliceArticle
|
import org.oxycblt.auxio.ui.sliceArticle
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [HomeListFragment] for showing a list of [Genre]s.
|
* A [HomeListFragment] for showing a list of [Genre]s.
|
||||||
|
@ -48,7 +49,11 @@ class GenreListFragment : HomeListFragment<Genre>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int) =
|
override fun getPopup(pos: Int) =
|
||||||
homeModel.genres.value!![pos].resolvedName.sliceArticle().first().uppercase()
|
unlikelyToBeNull(homeModel.genres.value)[pos]
|
||||||
|
.resolvedName
|
||||||
|
.sliceArticle()
|
||||||
|
.first()
|
||||||
|
.uppercase()
|
||||||
|
|
||||||
override fun onItemClick(item: Item) {
|
override fun onItemClick(item: Item) {
|
||||||
check(item is Genre)
|
check(item is Genre)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.oxycblt.auxio.ui.SongViewHolder
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
import org.oxycblt.auxio.ui.sliceArticle
|
import org.oxycblt.auxio.ui.sliceArticle
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [HomeListFragment] for showing a list of [Song]s.
|
* A [HomeListFragment] for showing a list of [Song]s.
|
||||||
|
@ -48,7 +49,7 @@ class SongListFragment : HomeListFragment<Song>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPopup(pos: Int): String {
|
override fun getPopup(pos: Int): String {
|
||||||
val song = homeModel.songs.value!![pos]
|
val song = unlikelyToBeNull(homeModel.songs.value)[pos]
|
||||||
|
|
||||||
// Change how we display the popup depending on the mode.
|
// Change how we display the popup depending on the mode.
|
||||||
// We don't use the more correct resolve(Model)Name here, as sorts are largely
|
// We don't use the more correct resolve(Model)Name here, as sorts are largely
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.content.ContentUris
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
// --- MUSIC MODELS ---
|
// --- MUSIC MODELS ---
|
||||||
|
|
||||||
|
@ -84,19 +85,16 @@ data class Song(
|
||||||
/** The duration of this song, in seconds (rounded down) */
|
/** The duration of this song, in seconds (rounded down) */
|
||||||
val seconds: Long
|
val seconds: Long
|
||||||
get() = duration / 1000
|
get() = duration / 1000
|
||||||
/** The seconds of this song, but as a duration. */
|
|
||||||
val formattedDuration: String
|
|
||||||
get() = seconds.toDuration(false)
|
|
||||||
|
|
||||||
private var mAlbum: Album? = null
|
private var mAlbum: Album? = null
|
||||||
/** The album of this song. */
|
/** The album of this song. */
|
||||||
val album: Album
|
val album: Album
|
||||||
get() = requireNotNull(mAlbum)
|
get() = unlikelyToBeNull(mAlbum)
|
||||||
|
|
||||||
private var mGenre: Genre? = null
|
private var mGenre: Genre? = null
|
||||||
/** The genre of this song. Will be an "unknown genre" if the song does not have any. */
|
/** The genre of this song. Will be an "unknown genre" if the song does not have any. */
|
||||||
val genre: Genre
|
val genre: Genre
|
||||||
get() = requireNotNull(mGenre)
|
get() = unlikelyToBeNull(mGenre)
|
||||||
|
|
||||||
/** An album name resolved to this song in particular. */
|
/** An album name resolved to this song in particular. */
|
||||||
val resolvedAlbumName: String
|
val resolvedAlbumName: String
|
||||||
|
@ -177,7 +175,7 @@ data class Album(
|
||||||
private var mArtist: Artist? = null
|
private var mArtist: Artist? = null
|
||||||
/** The parent artist of this album. */
|
/** The parent artist of this album. */
|
||||||
val artist: Artist
|
val artist: Artist
|
||||||
get() = requireNotNull(mArtist)
|
get() = unlikelyToBeNull(mArtist)
|
||||||
|
|
||||||
/** The artist name, resolved to this album in particular. */
|
/** The artist name, resolved to this album in particular. */
|
||||||
val resolvedArtistName: String
|
val resolvedArtistName: String
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
package org.oxycblt.auxio.music.excluded
|
package org.oxycblt.auxio.music.excluded
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import org.oxycblt.auxio.databinding.ItemExcludedDirBinding
|
import org.oxycblt.auxio.databinding.ItemExcludedDirBinding
|
||||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
import org.oxycblt.auxio.ui.BindingViewHolder
|
||||||
import org.oxycblt.auxio.ui.MonoAdapter
|
import org.oxycblt.auxio.ui.MonoAdapter
|
||||||
|
@ -40,9 +39,7 @@ class ExcludedAdapter(listener: Listener) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** The viewholder for [ExcludedAdapter]. Not intended for use in other adapters. */
|
||||||
* The viewholder for [ExcludedAdapter]. Not intended for use in other adapters.
|
|
||||||
*/
|
|
||||||
class ExcludedViewHolder private constructor(private val binding: ItemExcludedDirBinding) :
|
class ExcludedViewHolder private constructor(private val binding: ItemExcludedDirBinding) :
|
||||||
BindingViewHolder<String, ExcludedAdapter.Listener>(binding.root) {
|
BindingViewHolder<String, ExcludedAdapter.Listener>(binding.root) {
|
||||||
override fun bind(item: String, listener: ExcludedAdapter.Listener) {
|
override fun bind(item: String, listener: ExcludedAdapter.Listener) {
|
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModel that acts as a wrapper around [ExcludedDatabase], allowing for the addition/removal of
|
* ViewModel that acts as a wrapper around [ExcludedDatabase], allowing for the addition/removal of
|
||||||
|
@ -52,8 +53,9 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
|
||||||
* called.
|
* called.
|
||||||
*/
|
*/
|
||||||
fun addPath(path: String) {
|
fun addPath(path: String) {
|
||||||
if (!mPaths.value!!.contains(path)) {
|
val paths = unlikelyToBeNull(mPaths.value)
|
||||||
mPaths.value!!.add(path)
|
if (!paths.contains(path)) {
|
||||||
|
paths.add(path)
|
||||||
mPaths.value = mPaths.value
|
mPaths.value = mPaths.value
|
||||||
isModified = true
|
isModified = true
|
||||||
}
|
}
|
||||||
|
@ -64,7 +66,7 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
|
||||||
* [save] is called.
|
* [save] is called.
|
||||||
*/
|
*/
|
||||||
fun removePath(path: String) {
|
fun removePath(path: String) {
|
||||||
mPaths.value!!.remove(path)
|
unlikelyToBeNull(mPaths.value).remove(path)
|
||||||
mPaths.value = mPaths.value
|
mPaths.value = mPaths.value
|
||||||
isModified = true
|
isModified = true
|
||||||
}
|
}
|
||||||
|
@ -73,7 +75,7 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
|
||||||
fun save(onDone: () -> Unit) {
|
fun save(onDone: () -> Unit) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
excludedDatabase.writePaths(mPaths.value!!)
|
excludedDatabase.writePaths(unlikelyToBeNull(mPaths.value))
|
||||||
isModified = false
|
isModified = false
|
||||||
onDone()
|
onDone()
|
||||||
this@ExcludedViewModel.logD(
|
this@ExcludedViewModel.logD(
|
||||||
|
|
|
@ -107,12 +107,10 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playbackPlayPause.isActivated = playbackModel.isPlaying.value!!
|
|
||||||
playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying ->
|
playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying ->
|
||||||
binding.playbackPlayPause.isActivated = isPlaying
|
binding.playbackPlayPause.isActivated = isPlaying
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playbackProgressBar.progress = playbackModel.positionSeconds.value!!.toInt()
|
|
||||||
playbackModel.positionSeconds.observe(viewLifecycleOwner) { position ->
|
playbackModel.positionSeconds.observe(viewLifecycleOwner) { position ->
|
||||||
binding.playbackProgressBar.progress = position.toInt()
|
binding.playbackProgressBar.progress = position.toInt()
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ViewModel that provides a UI frontend for [PlaybackStateManager].
|
* The ViewModel that provides a UI frontend for [PlaybackStateManager].
|
||||||
|
@ -211,7 +212,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
* is called just before the change is committed so that the adapter can be updated.
|
* is called just before the change is committed so that the adapter can be updated.
|
||||||
*/
|
*/
|
||||||
fun removeQueueDataItem(adapterIndex: Int, apply: () -> Unit) {
|
fun removeQueueDataItem(adapterIndex: Int, apply: () -> Unit) {
|
||||||
val index = adapterIndex + (playbackManager.queue.size - mNextUp.value!!.size)
|
val index =
|
||||||
|
adapterIndex + (playbackManager.queue.size - unlikelyToBeNull(mNextUp.value).size)
|
||||||
if (index in playbackManager.queue.indices) {
|
if (index in playbackManager.queue.indices) {
|
||||||
apply()
|
apply()
|
||||||
playbackManager.removeQueueItem(index)
|
playbackManager.removeQueueItem(index)
|
||||||
|
@ -222,7 +224,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
* is called just before the change is committed so that the adapter can be updated.
|
* is called just before the change is committed so that the adapter can be updated.
|
||||||
*/
|
*/
|
||||||
fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int, apply: () -> Unit): Boolean {
|
fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int, apply: () -> Unit): Boolean {
|
||||||
val delta = (playbackManager.queue.size - mNextUp.value!!.size)
|
val delta = (playbackManager.queue.size - unlikelyToBeNull(mNextUp.value).size)
|
||||||
val from = adapterFrom + delta
|
val from = adapterFrom + delta
|
||||||
val to = adapterTo + delta
|
val to = adapterTo + delta
|
||||||
if (from in playbackManager.queue.indices && to in playbackManager.queue.indices) {
|
if (from in playbackManager.queue.indices && to in playbackManager.queue.indices) {
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the current volume and playback state across ReplayGain and AudioFocus events.
|
* Manages the current volume and playback state across ReplayGain and AudioFocus events.
|
||||||
|
@ -172,7 +173,7 @@ class AudioReactor(context: Context, private val callback: (Float) -> Unit) :
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key in REPLAY_GAIN_TAGS) {
|
if (key in REPLAY_GAIN_TAGS) {
|
||||||
tags.add(GainTag(requireNotNull(key), parseReplayGainFloat(value)))
|
tags.add(GainTag(unlikelyToBeNull(key), parseReplayGainFloat(value)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,8 @@ import org.oxycblt.auxio.accent.AccentCustomizeDialog
|
||||||
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
|
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
|
||||||
import org.oxycblt.auxio.music.excluded.ExcludedDialog
|
import org.oxycblt.auxio.music.excluded.ExcludedDialog
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.settings.pref.IntListPrefDialog
|
|
||||||
import org.oxycblt.auxio.settings.pref.IntListPreference
|
import org.oxycblt.auxio.settings.pref.IntListPreference
|
||||||
|
import org.oxycblt.auxio.settings.pref.IntListPreferenceDialog
|
||||||
import org.oxycblt.auxio.util.hardRestart
|
import org.oxycblt.auxio.util.hardRestart
|
||||||
import org.oxycblt.auxio.util.isNight
|
import org.oxycblt.auxio.util.isNight
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
@ -74,9 +74,14 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
setPreferencesFromResource(R.xml.prefs_main, rootKey)
|
setPreferencesFromResource(R.xml.prefs_main, rootKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("Deprecation")
|
||||||
override fun onDisplayPreferenceDialog(preference: Preference) {
|
override fun onDisplayPreferenceDialog(preference: Preference) {
|
||||||
if (preference is IntListPreference) {
|
if (preference is IntListPreference) {
|
||||||
IntListPrefDialog.from(preference).show(childFragmentManager, IntListPrefDialog.TAG)
|
// Creating our own preference dialog is hilariously difficult. For one, we need
|
||||||
|
// to override this random method within the class in order to
|
||||||
|
val dialog = IntListPreferenceDialog.from(preference)
|
||||||
|
dialog.setTargetFragment(this, 0)
|
||||||
|
dialog.show(parentFragmentManager, IntListPreferenceDialog.TAG)
|
||||||
} else {
|
} else {
|
||||||
super.onDisplayPreferenceDialog(preference)
|
super.onDisplayPreferenceDialog(preference)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
import org.oxycblt.auxio.playback.system.ReplayGainMode
|
import org.oxycblt.auxio.playback.system.ReplayGainMode
|
||||||
import org.oxycblt.auxio.ui.DisplayMode
|
import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
|
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
|
||||||
|
@ -73,7 +74,7 @@ class SettingsManager private constructor(context: Context) :
|
||||||
var libTabs: Array<Tab>
|
var libTabs: Array<Tab>
|
||||||
get() =
|
get() =
|
||||||
Tab.fromSequence(prefs.getInt(KEY_LIB_TABS, Tab.SEQUENCE_DEFAULT))
|
Tab.fromSequence(prefs.getInt(KEY_LIB_TABS, Tab.SEQUENCE_DEFAULT))
|
||||||
?: Tab.fromSequence(Tab.SEQUENCE_DEFAULT)!!
|
?: unlikelyToBeNull(Tab.fromSequence(Tab.SEQUENCE_DEFAULT))
|
||||||
set(value) {
|
set(value) {
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
putInt(KEY_LIB_TABS, Tab.toSequence(value))
|
putInt(KEY_LIB_TABS, Tab.toSequence(value))
|
||||||
|
|
|
@ -19,41 +19,43 @@ package org.oxycblt.auxio.settings.pref
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.preference.PreferenceDialogFragmentCompat
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
|
|
||||||
/** The dialog shown whenever an [IntListPreference] is shown. */
|
class IntListPreferenceDialog : PreferenceDialogFragmentCompat() {
|
||||||
class IntListPrefDialog : DialogFragment() {
|
private val listPreference: IntListPreference
|
||||||
|
get() = (preference as IntListPreference)
|
||||||
|
private var pendingValueIndex = -1
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val builder = MaterialAlertDialogBuilder(requireActivity(), theme)
|
// PreferenceDialogFragmentCompat does not allow us to customize the actual creation
|
||||||
|
// of the alert dialog, so we have to manually override onCreateDialog and customize it
|
||||||
// Since we have to store the preference key as an argument, we have to find the
|
// ourselves.
|
||||||
// preference we need to use manually.
|
val builder = MaterialAlertDialogBuilder(requireContext(), theme)
|
||||||
val pref =
|
builder.setTitle(listPreference.title)
|
||||||
requireNotNull(
|
builder.setPositiveButton(null, null)
|
||||||
(parentFragment as PreferenceFragmentCompat).preferenceManager.findPreference<
|
builder.setNegativeButton(android.R.string.cancel, null)
|
||||||
IntListPreference>(requireArguments().getString(ARG_KEY, null)))
|
builder.setSingleChoiceItems(listPreference.entries, listPreference.getValueIndex()) {
|
||||||
|
_,
|
||||||
builder.setTitle(pref.title)
|
index ->
|
||||||
|
pendingValueIndex = index
|
||||||
builder.setSingleChoiceItems(pref.entries, pref.getValueIndex()) { _, index ->
|
|
||||||
pref.setValueIndex(index)
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDialogClosed(positiveResult: Boolean) {
|
||||||
|
if (pendingValueIndex > -1) {
|
||||||
|
listPreference.setValueIndex(pendingValueIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = BuildConfig.APPLICATION_ID + ".tag.INT_PREF"
|
const val TAG = BuildConfig.APPLICATION_ID + ".tag.INT_PREF"
|
||||||
const val ARG_KEY = BuildConfig.APPLICATION_ID + ".arg.PREF_KEY"
|
|
||||||
|
|
||||||
fun from(pref: IntListPreference): IntListPrefDialog {
|
fun from(pref: IntListPreference): IntListPreferenceDialog {
|
||||||
return IntListPrefDialog().apply {
|
return IntListPreferenceDialog().apply {
|
||||||
arguments = Bundle().apply { putString(ARG_KEY, pref.key) }
|
arguments = Bundle().apply { putString(ARG_KEY, pref.key) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,6 @@ constructor(
|
||||||
defStyleRes: Int = 0
|
defStyleRes: Int = 0
|
||||||
) : DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
) : DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
// Reflect into Preference to get the (normally inaccessible) default value.
|
// Reflect into Preference to get the (normally inaccessible) default value.
|
||||||
private val defValueField =
|
|
||||||
Preference::class.java.getDeclaredField("mDefaultValue").apply { isAccessible = true }
|
|
||||||
|
|
||||||
val entries: Array<CharSequence>
|
val entries: Array<CharSequence>
|
||||||
val values: IntArray
|
val values: IntArray
|
||||||
private var currentValue: Int? = null
|
private var currentValue: Int? = null
|
||||||
|
@ -108,4 +105,9 @@ constructor(
|
||||||
return "<not set>"
|
return "<not set>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val defValueField =
|
||||||
|
Preference::class.java.getDeclaredField("mDefaultValue").apply { isAccessible = true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,10 +248,13 @@ sealed class Sort(open val isAscending: Boolean) {
|
||||||
|
|
||||||
class NullableComparator<T : Comparable<T>> : Comparator<T?> {
|
class NullableComparator<T : Comparable<T>> : Comparator<T?> {
|
||||||
override fun compare(a: T?, b: T?): Int {
|
override fun compare(a: T?, b: T?): Int {
|
||||||
if (a == null && b != null) return -1 // -1 -> a < b
|
return when {
|
||||||
if (a == null && b == null) return 0 // 0 -> a = b
|
a != null && b != null -> a.compareTo(b)
|
||||||
if (a != null && b == null) return 1 // 1 -> a > b
|
a == null && b != null -> -1 // a < b
|
||||||
return a!!.compareTo(b!!)
|
a == null && b == null -> 0 // a = b
|
||||||
|
a != null && b == null -> 1 // a < b
|
||||||
|
else -> error("Unreachable")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ fun Context.getColorSafe(@ColorRes color: Int): Int {
|
||||||
*/
|
*/
|
||||||
fun Context.getColorStateListSafe(@ColorRes color: Int): ColorStateList {
|
fun Context.getColorStateListSafe(@ColorRes color: Int): ColorStateList {
|
||||||
return try {
|
return try {
|
||||||
requireNotNull(ContextCompat.getColorStateList(this, color))
|
unlikelyToBeNull(ContextCompat.getColorStateList(this, color))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
handleResourceFailure(e, "color state list", getColorSafe(android.R.color.black).stateList)
|
handleResourceFailure(e, "color state list", getColorSafe(android.R.color.black).stateList)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import org.oxycblt.auxio.BuildConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for querying all items in a database and running [block] with the cursor returned. Will
|
* Shortcut for querying all items in a database and running [block] with the cursor returned. Will
|
||||||
|
@ -36,8 +37,17 @@ fun assertBackgroundThread() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Fragment.requireAttached() {
|
/**
|
||||||
if (isDetached) {
|
* Sanitizes a nullable value that is not likely to be null. On debug builds, requireNotNull is
|
||||||
error("Fragment is detached from activity")
|
* used, while on release builds, the unsafe assertion operator [!!] ]is used
|
||||||
|
*/
|
||||||
|
fun <T> unlikelyToBeNull(value: T?): T {
|
||||||
|
return if (BuildConfig.DEBUG) {
|
||||||
|
requireNotNull(value)
|
||||||
|
} else {
|
||||||
|
value!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Require the fragment is attached to an activity. */
|
||||||
|
fun Fragment.requireAttached() = check(!isDetached) { "Fragment is detached from activity" }
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.oxycblt.auxio.util.getDimenSizeSafe
|
||||||
import org.oxycblt.auxio.util.isLandscape
|
import org.oxycblt.auxio.util.isLandscape
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively
|
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively
|
||||||
|
@ -245,7 +246,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
logW("No good widget layout found")
|
logW("No good widget layout found")
|
||||||
|
|
||||||
val minimum =
|
val minimum =
|
||||||
requireNotNull(views.minByOrNull { it.key.width * it.key.height }?.value)
|
unlikelyToBeNull(views.minByOrNull { it.key.width * it.key.height }?.value)
|
||||||
|
|
||||||
updateAppWidget(id, minimum)
|
updateAppWidget(id, minimum)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<resources>
|
<resources>
|
||||||
<integer name="detail_app_bar_title_anim_duration">150</integer>
|
<integer name="detail_app_bar_title_anim_duration">150</integer>
|
||||||
|
|
||||||
<!-- FIXME: This is really stupid, figure out how we can unify it with the IntegerTable object-->
|
<!-- FIXME: Unify this with the integer table object by simply defining a class for each dependency. Will reduce bugs. -->
|
||||||
<!-- Preference values -->
|
<!-- Preference values -->
|
||||||
<string-array name="entries_theme">
|
<string-array name="entries_theme">
|
||||||
<item>@string/set_theme_auto</item>
|
<item>@string/set_theme_auto</item>
|
||||||
|
|
Loading…
Reference in a new issue