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.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* Dialog responsible for showing the list of accents to select.
|
||||
|
@ -44,7 +45,7 @@ class AccentCustomizeDialog :
|
|||
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (accentAdapter.selectedAccent != settingsManager.accent) {
|
||||
logD("Applying new accent")
|
||||
settingsManager.accent = requireNotNull(accentAdapter.selectedAccent)
|
||||
settingsManager.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
||||
requireActivity().recreate()
|
||||
}
|
||||
|
||||
|
@ -71,7 +72,7 @@ class AccentCustomizeDialog :
|
|||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
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) {
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.oxycblt.auxio.util.canScroll
|
|||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
import org.oxycblt.auxio.util.showToast
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* The [DetailFragment] for an album.
|
||||
|
@ -53,15 +54,16 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
|||
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
|
||||
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) {
|
||||
R.id.action_play_next -> {
|
||||
playbackModel.playNext(detailModel.currentAlbum.value!!)
|
||||
playbackModel.playNext(unlikelyToBeNull(detailModel.currentAlbum.value))
|
||||
requireContext().showToast(R.string.lbl_queue_added)
|
||||
true
|
||||
}
|
||||
R.id.action_queue_add -> {
|
||||
playbackModel.addToQueue(detailModel.currentAlbum.value!!)
|
||||
playbackModel.addToQueue(unlikelyToBeNull(detailModel.currentAlbum.value))
|
||||
requireContext().showToast(R.string.lbl_queue_added)
|
||||
true
|
||||
}
|
||||
|
@ -102,11 +104,11 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
|||
}
|
||||
|
||||
override fun onPlayParent() {
|
||||
playbackModel.playAlbum(requireNotNull(detailModel.currentAlbum.value), false)
|
||||
playbackModel.playAlbum(unlikelyToBeNull(detailModel.currentAlbum.value), false)
|
||||
}
|
||||
|
||||
override fun onShuffleParent() {
|
||||
playbackModel.playAlbum(requireNotNull(detailModel.currentAlbum.value), true)
|
||||
playbackModel.playAlbum(unlikelyToBeNull(detailModel.currentAlbum.value), true)
|
||||
}
|
||||
|
||||
override fun onShowSortMenu(anchor: View) {
|
||||
|
@ -121,7 +123,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
|||
findNavController()
|
||||
.navigate(
|
||||
AlbumDetailFragmentDirections.actionShowArtist(
|
||||
requireNotNull(detailModel.currentAlbum.value).artist.id))
|
||||
unlikelyToBeNull(detailModel.currentAlbum.value).artist.id))
|
||||
}
|
||||
|
||||
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
|
||||
// fragment should be launched otherwise.
|
||||
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")
|
||||
scrollToItem(item.id, adapter)
|
||||
detailModel.finishNavToItem()
|
||||
|
@ -144,7 +146,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
|||
// If the album matches, no need to do anything. Otherwise launch a new
|
||||
// detail fragment.
|
||||
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")
|
||||
binding.detailRecycler.scrollToPosition(0)
|
||||
detailModel.finishNavToItem()
|
||||
|
@ -197,7 +199,7 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener {
|
|||
}
|
||||
|
||||
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)
|
||||
} else {
|
||||
// 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.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* The [DetailFragment] for an artist.
|
||||
|
@ -50,7 +51,7 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
|||
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
|
||||
detailModel.setArtistId(args.artistId)
|
||||
|
||||
setupToolbar(detailModel.currentArtist.value!!)
|
||||
setupToolbar(unlikelyToBeNull(detailModel.currentArtist.value))
|
||||
requireBinding().detailRecycler.apply {
|
||||
adapter = detailAdapter
|
||||
applySpans { pos ->
|
||||
|
@ -91,11 +92,11 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
|||
}
|
||||
|
||||
override fun onPlayParent() {
|
||||
playbackModel.playArtist(requireNotNull(detailModel.currentArtist.value), false)
|
||||
playbackModel.playArtist(unlikelyToBeNull(detailModel.currentArtist.value), false)
|
||||
}
|
||||
|
||||
override fun onShuffleParent() {
|
||||
playbackModel.playArtist(requireNotNull(detailModel.currentArtist.value), true)
|
||||
playbackModel.playArtist(unlikelyToBeNull(detailModel.currentArtist.value), true)
|
||||
}
|
||||
|
||||
override fun onShowSortMenu(anchor: View) {
|
||||
|
|
|
@ -101,8 +101,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
|
||||
titleShown = visible
|
||||
|
||||
if (mTitleAnimator != null) {
|
||||
mTitleAnimator!!.cancel()
|
||||
val titleAnimator = mTitleAnimator
|
||||
if (titleAnimator != null) {
|
||||
titleAnimator.cancel()
|
||||
mTitleAnimator = null
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
|
|||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* A Base [Fragment] implementing the base features shared across all detail fragments.
|
||||
|
@ -95,6 +96,9 @@ abstract class DetailFragment : ViewBindingFragment<FragmentDetailBinding>() {
|
|||
) {
|
||||
logD("Launching menu")
|
||||
|
||||
// Scrolling breaks the menus, so we stop any momentum currently going on.
|
||||
requireBinding().detailRecycler.stopScroll()
|
||||
|
||||
PopupMenu(anchor.context, anchor).apply {
|
||||
inflate(R.menu.menu_detail_sort)
|
||||
|
||||
|
@ -104,7 +108,7 @@ abstract class DetailFragment : ViewBindingFragment<FragmentDetailBinding>() {
|
|||
onConfirm(sort.ascending(item.isChecked))
|
||||
} else {
|
||||
item.isChecked = true
|
||||
onConfirm(requireNotNull(sort.assignId(item.itemId)))
|
||||
onConfirm(unlikelyToBeNull(sort.assignId(item.itemId)))
|
||||
}
|
||||
|
||||
true
|
||||
|
|
|
@ -56,7 +56,7 @@ class DetailViewModel : ViewModel() {
|
|||
get() = settingsManager.detailAlbumSort
|
||||
set(value) {
|
||||
settingsManager.detailAlbumSort = value
|
||||
refreshAlbumData()
|
||||
currentAlbum.value?.let(::refreshAlbumData)
|
||||
}
|
||||
|
||||
private val mCurrentArtist = MutableLiveData<Artist?>()
|
||||
|
@ -70,7 +70,7 @@ class DetailViewModel : ViewModel() {
|
|||
get() = settingsManager.detailArtistSort
|
||||
set(value) {
|
||||
settingsManager.detailArtistSort = value
|
||||
refreshArtistData()
|
||||
currentArtist.value?.let(::refreshArtistData)
|
||||
}
|
||||
|
||||
private val mCurrentGenre = MutableLiveData<Genre?>()
|
||||
|
@ -84,7 +84,7 @@ class DetailViewModel : ViewModel() {
|
|||
get() = settingsManager.detailGenreSort
|
||||
set(value) {
|
||||
settingsManager.detailGenreSort = value
|
||||
refreshGenreData()
|
||||
currentGenre.value?.let(::refreshGenreData)
|
||||
}
|
||||
|
||||
private val mNavToItem = MutableLiveData<Music?>()
|
||||
|
@ -99,22 +99,30 @@ class DetailViewModel : ViewModel() {
|
|||
fun setAlbumId(id: Long) {
|
||||
if (mCurrentAlbum.value?.id == id) return
|
||||
val musicStore = MusicStore.requireInstance()
|
||||
mCurrentAlbum.value = musicStore.albums.find { it.id == id }
|
||||
refreshAlbumData()
|
||||
val album =
|
||||
requireNotNull(musicStore.albums.find { it.id == id }) { "Invalid album ID provided " }
|
||||
|
||||
mCurrentAlbum.value = album
|
||||
refreshAlbumData(album)
|
||||
}
|
||||
|
||||
fun setArtistId(id: Long) {
|
||||
if (mCurrentArtist.value?.id == id) return
|
||||
val musicStore = MusicStore.requireInstance()
|
||||
mCurrentArtist.value = musicStore.artists.find { it.id == id }
|
||||
refreshArtistData()
|
||||
val artist =
|
||||
requireNotNull(musicStore.artists.find { it.id == id }) { "Invalid artist ID provided" }
|
||||
|
||||
mCurrentArtist.value = artist
|
||||
refreshArtistData(artist)
|
||||
}
|
||||
|
||||
fun setGenreId(id: Long) {
|
||||
if (mCurrentGenre.value?.id == id) return
|
||||
val musicStore = MusicStore.requireInstance()
|
||||
mCurrentGenre.value = musicStore.genres.find { it.id == id }
|
||||
refreshGenreData()
|
||||
val genre =
|
||||
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 */
|
||||
|
@ -132,38 +140,29 @@ class DetailViewModel : ViewModel() {
|
|||
isNavigating = navigating
|
||||
}
|
||||
|
||||
private fun refreshGenreData() {
|
||||
private fun refreshGenreData(genre: Genre) {
|
||||
logD("Refreshing genre data")
|
||||
val genre = requireNotNull(currentGenre.value)
|
||||
val data = mutableListOf<Item>(genre)
|
||||
|
||||
data.add(SortHeader(-2, R.string.lbl_songs))
|
||||
data.addAll(settingsManager.detailGenreSort.genre(currentGenre.value!!))
|
||||
|
||||
data.addAll(genreSort.genre(genre))
|
||||
mGenreData.value = data
|
||||
}
|
||||
|
||||
private fun refreshArtistData() {
|
||||
private fun refreshArtistData(artist: Artist) {
|
||||
logD("Refreshing artist data")
|
||||
val artist = requireNotNull(currentArtist.value)
|
||||
val data = mutableListOf<Item>(artist)
|
||||
|
||||
data.add(Header(-2, R.string.lbl_albums))
|
||||
data.addAll(Sort.ByYear(false).albums(artist.albums))
|
||||
data.add(SortHeader(-3, R.string.lbl_songs))
|
||||
data.addAll(settingsManager.detailArtistSort.artist(artist))
|
||||
|
||||
data.addAll(artistSort.artist(artist))
|
||||
mArtistData.value = data.toList()
|
||||
}
|
||||
|
||||
private fun refreshAlbumData() {
|
||||
private fun refreshAlbumData(album: Album) {
|
||||
logD("Refreshing album data")
|
||||
val album = requireNotNull(currentAlbum.value)
|
||||
val data = mutableListOf<Item>(album)
|
||||
|
||||
data.add(SortHeader(id = -2, R.string.lbl_albums))
|
||||
data.addAll(settingsManager.detailAlbumSort.album(currentAlbum.value!!))
|
||||
|
||||
data.addAll(albumSort.album(album))
|
||||
mAlbumData.value = data
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.oxycblt.auxio.ui.newMenu
|
|||
import org.oxycblt.auxio.util.applySpans
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* The [DetailFragment] for a genre.
|
||||
|
@ -49,7 +50,7 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
|||
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
|
||||
detailModel.setGenreId(args.genreId)
|
||||
|
||||
setupToolbar(detailModel.currentGenre.value!!)
|
||||
setupToolbar(unlikelyToBeNull(detailModel.currentGenre.value))
|
||||
binding.detailRecycler.apply {
|
||||
adapter = detailAdapter
|
||||
applySpans { pos ->
|
||||
|
@ -83,11 +84,11 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
|||
}
|
||||
|
||||
override fun onPlayParent() {
|
||||
playbackModel.playGenre(requireNotNull(detailModel.currentGenre.value), false)
|
||||
playbackModel.playGenre(unlikelyToBeNull(detailModel.currentGenre.value), false)
|
||||
}
|
||||
|
||||
override fun onShuffleParent() {
|
||||
playbackModel.playGenre(requireNotNull(detailModel.currentGenre.value), true)
|
||||
playbackModel.playGenre(unlikelyToBeNull(detailModel.currentGenre.value), true)
|
||||
}
|
||||
|
||||
override fun onShowSortMenu(anchor: View) {
|
||||
|
@ -119,7 +120,7 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener {
|
|||
private fun updateSong(song: Song?, adapter: GenreDetailAdapter) {
|
||||
val binding = requireBinding()
|
||||
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)
|
||||
} else {
|
||||
// Clear the ViewHolders if the mode isn't ALL_SONGS
|
||||
|
|
|
@ -179,7 +179,11 @@ private constructor(
|
|||
override fun bind(item: Album, listener: MenuItemListener) {
|
||||
binding.parentImage.bindAlbumCover(item)
|
||||
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 {
|
||||
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.logE
|
||||
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
|
||||
|
@ -134,9 +135,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>() {
|
|||
R.id.option_sort_asc -> {
|
||||
item.isChecked = !item.isChecked
|
||||
homeModel.updateCurrentSort(
|
||||
requireNotNull(
|
||||
unlikelyToBeNull(
|
||||
homeModel
|
||||
.getSortForDisplay(homeModel.currentTab.value!!)
|
||||
.getSortForDisplay(unlikelyToBeNull(homeModel.currentTab.value))
|
||||
.ascending(item.isChecked)))
|
||||
}
|
||||
|
||||
|
@ -144,9 +145,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>() {
|
|||
else -> {
|
||||
item.isChecked = true
|
||||
homeModel.updateCurrentSort(
|
||||
requireNotNull(
|
||||
unlikelyToBeNull(
|
||||
homeModel
|
||||
.getSortForDisplay(homeModel.currentTab.value!!)
|
||||
.getSortForDisplay(unlikelyToBeNull(homeModel.currentTab.value))
|
||||
.assignId(item.itemId)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.oxycblt.auxio.settings.SettingsManager
|
|||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
|
||||
|
@ -113,19 +114,19 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
|
|||
when (mCurrentTab.value) {
|
||||
DisplayMode.SHOW_SONGS -> {
|
||||
settingsManager.libSongSort = sort
|
||||
mSongs.value = sort.songs(mSongs.value!!)
|
||||
mSongs.value = sort.songs(unlikelyToBeNull(mSongs.value))
|
||||
}
|
||||
DisplayMode.SHOW_ALBUMS -> {
|
||||
settingsManager.libAlbumSort = sort
|
||||
mAlbums.value = sort.albums(mAlbums.value!!)
|
||||
mAlbums.value = sort.albums(unlikelyToBeNull(mAlbums.value))
|
||||
}
|
||||
DisplayMode.SHOW_ARTISTS -> {
|
||||
settingsManager.libArtistSort = sort
|
||||
mArtists.value = sort.artists(mArtists.value!!)
|
||||
mArtists.value = sort.artists(unlikelyToBeNull(mArtists.value))
|
||||
}
|
||||
DisplayMode.SHOW_GENRES -> {
|
||||
settingsManager.libGenreSort = sort
|
||||
mGenres.value = sort.genres(mGenres.value!!)
|
||||
mGenres.value = sort.genres(unlikelyToBeNull(mGenres.value))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.oxycblt.auxio.ui.PrimitiveBackingData
|
|||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.ui.newMenu
|
||||
import org.oxycblt.auxio.ui.sliceArticle
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* A [HomeListFragment] for showing a list of [Album]s.
|
||||
|
@ -50,7 +51,7 @@ class AlbumListFragment : HomeListFragment<Album>() {
|
|||
}
|
||||
|
||||
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.
|
||||
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.newMenu
|
||||
import org.oxycblt.auxio.ui.sliceArticle
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* A [HomeListFragment] for showing a list of [Artist]s.
|
||||
|
@ -48,7 +49,11 @@ class ArtistListFragment : HomeListFragment<Artist>() {
|
|||
}
|
||||
|
||||
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) {
|
||||
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.newMenu
|
||||
import org.oxycblt.auxio.ui.sliceArticle
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* A [HomeListFragment] for showing a list of [Genre]s.
|
||||
|
@ -48,7 +49,11 @@ class GenreListFragment : HomeListFragment<Genre>() {
|
|||
}
|
||||
|
||||
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) {
|
||||
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.newMenu
|
||||
import org.oxycblt.auxio.ui.sliceArticle
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* A [HomeListFragment] for showing a list of [Song]s.
|
||||
|
@ -48,7 +49,7 @@ class SongListFragment : HomeListFragment<Song>() {
|
|||
}
|
||||
|
||||
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.
|
||||
// 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.provider.MediaStore
|
||||
import org.oxycblt.auxio.ui.Item
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
// --- MUSIC MODELS ---
|
||||
|
||||
|
@ -84,19 +85,16 @@ data class Song(
|
|||
/** The duration of this song, in seconds (rounded down) */
|
||||
val seconds: Long
|
||||
get() = duration / 1000
|
||||
/** The seconds of this song, but as a duration. */
|
||||
val formattedDuration: String
|
||||
get() = seconds.toDuration(false)
|
||||
|
||||
private var mAlbum: Album? = null
|
||||
/** The album of this song. */
|
||||
val album: Album
|
||||
get() = requireNotNull(mAlbum)
|
||||
get() = unlikelyToBeNull(mAlbum)
|
||||
|
||||
private var mGenre: Genre? = null
|
||||
/** The genre of this song. Will be an "unknown genre" if the song does not have any. */
|
||||
val genre: Genre
|
||||
get() = requireNotNull(mGenre)
|
||||
get() = unlikelyToBeNull(mGenre)
|
||||
|
||||
/** An album name resolved to this song in particular. */
|
||||
val resolvedAlbumName: String
|
||||
|
@ -177,7 +175,7 @@ data class Album(
|
|||
private var mArtist: Artist? = null
|
||||
/** The parent artist of this album. */
|
||||
val artist: Artist
|
||||
get() = requireNotNull(mArtist)
|
||||
get() = unlikelyToBeNull(mArtist)
|
||||
|
||||
/** The artist name, resolved to this album in particular. */
|
||||
val resolvedArtistName: String
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package org.oxycblt.auxio.music.excluded
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import org.oxycblt.auxio.databinding.ItemExcludedDirBinding
|
||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
||||
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) :
|
||||
BindingViewHolder<String, ExcludedAdapter.Listener>(binding.root) {
|
||||
override fun bind(item: String, listener: ExcludedAdapter.Listener) {
|
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
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
|
||||
|
@ -52,8 +53,9 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
|
|||
* called.
|
||||
*/
|
||||
fun addPath(path: String) {
|
||||
if (!mPaths.value!!.contains(path)) {
|
||||
mPaths.value!!.add(path)
|
||||
val paths = unlikelyToBeNull(mPaths.value)
|
||||
if (!paths.contains(path)) {
|
||||
paths.add(path)
|
||||
mPaths.value = mPaths.value
|
||||
isModified = true
|
||||
}
|
||||
|
@ -64,7 +66,7 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
|
|||
* [save] is called.
|
||||
*/
|
||||
fun removePath(path: String) {
|
||||
mPaths.value!!.remove(path)
|
||||
unlikelyToBeNull(mPaths.value).remove(path)
|
||||
mPaths.value = mPaths.value
|
||||
isModified = true
|
||||
}
|
||||
|
@ -73,7 +75,7 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
|
|||
fun save(onDone: () -> Unit) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val start = System.currentTimeMillis()
|
||||
excludedDatabase.writePaths(mPaths.value!!)
|
||||
excludedDatabase.writePaths(unlikelyToBeNull(mPaths.value))
|
||||
isModified = false
|
||||
onDone()
|
||||
this@ExcludedViewModel.logD(
|
||||
|
|
|
@ -107,12 +107,10 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
binding.playbackPlayPause.isActivated = playbackModel.isPlaying.value!!
|
||||
playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying ->
|
||||
binding.playbackPlayPause.isActivated = isPlaying
|
||||
}
|
||||
|
||||
binding.playbackProgressBar.progress = playbackModel.positionSeconds.value!!.toInt()
|
||||
playbackModel.positionSeconds.observe(viewLifecycleOwner) { position ->
|
||||
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.util.logD
|
||||
import org.oxycblt.auxio.util.logE
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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) {
|
||||
apply()
|
||||
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.
|
||||
*/
|
||||
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 to = adapterTo + delta
|
||||
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.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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.music.excluded.ExcludedDialog
|
||||
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.IntListPreferenceDialog
|
||||
import org.oxycblt.auxio.util.hardRestart
|
||||
import org.oxycblt.auxio.util.isNight
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
@ -74,9 +74,14 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
setPreferencesFromResource(R.xml.prefs_main, rootKey)
|
||||
}
|
||||
|
||||
@Suppress("Deprecation")
|
||||
override fun onDisplayPreferenceDialog(preference: Preference) {
|
||||
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 {
|
||||
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.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* 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>
|
||||
get() =
|
||||
Tab.fromSequence(prefs.getInt(KEY_LIB_TABS, Tab.SEQUENCE_DEFAULT))
|
||||
?: Tab.fromSequence(Tab.SEQUENCE_DEFAULT)!!
|
||||
?: unlikelyToBeNull(Tab.fromSequence(Tab.SEQUENCE_DEFAULT))
|
||||
set(value) {
|
||||
prefs.edit {
|
||||
putInt(KEY_LIB_TABS, Tab.toSequence(value))
|
||||
|
|
|
@ -19,41 +19,43 @@ package org.oxycblt.auxio.settings.pref
|
|||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceDialogFragmentCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
|
||||
/** The dialog shown whenever an [IntListPreference] is shown. */
|
||||
class IntListPrefDialog : DialogFragment() {
|
||||
class IntListPreferenceDialog : PreferenceDialogFragmentCompat() {
|
||||
private val listPreference: IntListPreference
|
||||
get() = (preference as IntListPreference)
|
||||
private var pendingValueIndex = -1
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = MaterialAlertDialogBuilder(requireActivity(), theme)
|
||||
|
||||
// Since we have to store the preference key as an argument, we have to find the
|
||||
// preference we need to use manually.
|
||||
val pref =
|
||||
requireNotNull(
|
||||
(parentFragment as PreferenceFragmentCompat).preferenceManager.findPreference<
|
||||
IntListPreference>(requireArguments().getString(ARG_KEY, null)))
|
||||
|
||||
builder.setTitle(pref.title)
|
||||
|
||||
builder.setSingleChoiceItems(pref.entries, pref.getValueIndex()) { _, index ->
|
||||
pref.setValueIndex(index)
|
||||
// PreferenceDialogFragmentCompat does not allow us to customize the actual creation
|
||||
// of the alert dialog, so we have to manually override onCreateDialog and customize it
|
||||
// ourselves.
|
||||
val builder = MaterialAlertDialogBuilder(requireContext(), theme)
|
||||
builder.setTitle(listPreference.title)
|
||||
builder.setPositiveButton(null, null)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
builder.setSingleChoiceItems(listPreference.entries, listPreference.getValueIndex()) {
|
||||
_,
|
||||
index ->
|
||||
pendingValueIndex = index
|
||||
dismiss()
|
||||
}
|
||||
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
|
||||
return builder.create()
|
||||
}
|
||||
|
||||
override fun onDialogClosed(positiveResult: Boolean) {
|
||||
if (pendingValueIndex > -1) {
|
||||
listPreference.setValueIndex(pendingValueIndex)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = BuildConfig.APPLICATION_ID + ".tag.INT_PREF"
|
||||
const val ARG_KEY = BuildConfig.APPLICATION_ID + ".arg.PREF_KEY"
|
||||
|
||||
fun from(pref: IntListPreference): IntListPrefDialog {
|
||||
return IntListPrefDialog().apply {
|
||||
fun from(pref: IntListPreference): IntListPreferenceDialog {
|
||||
return IntListPreferenceDialog().apply {
|
||||
arguments = Bundle().apply { putString(ARG_KEY, pref.key) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,6 @@ constructor(
|
|||
defStyleRes: Int = 0
|
||||
) : DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||
// 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 values: IntArray
|
||||
private var currentValue: Int? = null
|
||||
|
@ -108,4 +105,9 @@ constructor(
|
|||
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?> {
|
||||
override fun compare(a: T?, b: T?): Int {
|
||||
if (a == null && b != null) return -1 // -1 -> a < b
|
||||
if (a == null && b == null) return 0 // 0 -> a = b
|
||||
if (a != null && b == null) return 1 // 1 -> a > b
|
||||
return a!!.compareTo(b!!)
|
||||
return when {
|
||||
a != null && b != null -> a.compareTo(b)
|
||||
a == null && b != null -> -1 // a < 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 {
|
||||
return try {
|
||||
requireNotNull(ContextCompat.getColorStateList(this, color))
|
||||
unlikelyToBeNull(ContextCompat.getColorStateList(this, color))
|
||||
} catch (e: Exception) {
|
||||
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.os.Looper
|
||||
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
|
||||
|
@ -36,8 +37,17 @@ fun assertBackgroundThread() {
|
|||
}
|
||||
}
|
||||
|
||||
fun Fragment.requireAttached() {
|
||||
if (isDetached) {
|
||||
error("Fragment is detached from activity")
|
||||
/**
|
||||
* Sanitizes a nullable value that is not likely to be null. On debug builds, requireNotNull is
|
||||
* 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.logD
|
||||
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
|
||||
|
@ -245,7 +246,7 @@ class WidgetProvider : AppWidgetProvider() {
|
|||
logW("No good widget layout found")
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<resources>
|
||||
<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 -->
|
||||
<string-array name="entries_theme">
|
||||
<item>@string/set_theme_auto</item>
|
||||
|
|
Loading…
Reference in a new issue