all: add collection utils

Add new shortcut utilities for collecting StateFlows in a safe manner.

The priamry addition here is collectImmediately. collectImmediately
just calls block with the existing value initially, which helps remove
a good amount of bugs regarding state initialization. Sure, it is a bit
inefficient given that it will also initialize on startup, but this is
okay.

The other utilities are the same, but simply remove the launch
boilerplate.
This commit is contained in:
OxygenCobalt 2022-06-21 10:51:56 -06:00
parent 8465cda637
commit 16eccee8e5
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
17 changed files with 97 additions and 75 deletions

View file

@ -33,7 +33,8 @@ import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
/** /**
* A wrapper around the home fragment that shows the playback fragment and controls the more * A wrapper around the home fragment that shows the playback fragment and controls the more
@ -60,7 +61,6 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
// the screen is too small because of course we have to. // the screen is too small because of course we have to.
if (requireActivity().isInMultiWindowMode) { if (requireActivity().isInMultiWindowMode) {
val config = resources.configuration val config = resources.configuration
if (config.screenHeightDp < 250 || config.screenWidthDp < 250) { if (config.screenHeightDp < 250 || config.screenWidthDp < 250) {
binding.layoutTooSmall.visibility = View.VISIBLE binding.layoutTooSmall.visibility = View.VISIBLE
} }
@ -69,9 +69,9 @@ class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
launch { navModel.mainNavigationAction.collect(::handleMainNavigation) } collect(navModel.mainNavigationAction, ::handleMainNavigation)
launch { navModel.exploreNavigationItem.collect(::handleExploreNavigation) } collect(navModel.exploreNavigationItem, ::handleExploreNavigation)
launch { playbackModel.song.collect(::updateSong) } collectImmediately(playbackModel.song, ::updateSong)
} }
override fun onResume() { override fun onResume() {

View file

@ -44,9 +44,9 @@ import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.MenuFragment import org.oxycblt.auxio.ui.MenuFragment
import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.canScroll import org.oxycblt.auxio.util.canScroll
import org.oxycblt.auxio.util.collectWith import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logEOrThrow import org.oxycblt.auxio.util.logEOrThrow
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
@ -87,10 +87,10 @@ class AlbumDetailFragment :
// -- VIEWMODEL SETUP --- // -- VIEWMODEL SETUP ---
launch { detailModel.currentAlbum.collect(::handleItemChange) } collectImmediately(detailModel.currentAlbum, ::handleItemChange)
launch { detailModel.albumData.collect(detailAdapter.data::submitList) } collectImmediately(detailModel.albumData, detailAdapter.data::submitList)
launch { navModel.exploreNavigationItem.collect(::handleNavigation) } collectImmediately(playbackModel.song, playbackModel.parent, ::updatePlayback)
launch { playbackModel.song.collectWith(playbackModel.parent, ::updatePlayback) } collect(navModel.exploreNavigationItem, ::handleNavigation)
} }
override fun onDestroyBinding(binding: FragmentDetailBinding) { override fun onDestroyBinding(binding: FragmentDetailBinding) {

View file

@ -41,9 +41,9 @@ import org.oxycblt.auxio.ui.Header
import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.MenuFragment import org.oxycblt.auxio.ui.MenuFragment
import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.collectWith import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logEOrThrow import org.oxycblt.auxio.util.logEOrThrow
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
@ -83,10 +83,10 @@ class ArtistDetailFragment :
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
launch { detailModel.currentArtist.collect(::handleItemChange) } collectImmediately(detailModel.currentArtist, ::handleItemChange)
launch { detailModel.artistData.collect(detailAdapter.data::submitList) } collectImmediately(detailModel.artistData, detailAdapter.data::submitList)
launch { navModel.exploreNavigationItem.collect(::handleNavigation) } collectImmediately(playbackModel.song, playbackModel.parent, ::updatePlayback)
launch { playbackModel.song.collectWith(playbackModel.parent, ::updatePlayback) } collect(navModel.exploreNavigationItem, ::handleNavigation)
} }
override fun onDestroyBinding(binding: FragmentDetailBinding) { override fun onDestroyBinding(binding: FragmentDetailBinding) {

View file

@ -42,9 +42,9 @@ import org.oxycblt.auxio.ui.Header
import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.MenuFragment import org.oxycblt.auxio.ui.MenuFragment
import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.collectWith import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logEOrThrow import org.oxycblt.auxio.util.logEOrThrow
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
@ -83,10 +83,10 @@ class GenreDetailFragment :
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
launch { detailModel.currentGenre.collect(::handleItemChange) } collectImmediately(detailModel.currentGenre, ::handleItemChange)
launch { detailModel.genreData.collect(detailAdapter.data::submitList) } collectImmediately(detailModel.genreData, detailAdapter.data::submitList)
launch { navModel.exploreNavigationItem.collect(::handleNavigation) } collectImmediately(playbackModel.song, playbackModel.parent, ::updatePlayback)
launch { playbackModel.song.collectWith(playbackModel.parent, ::updatePlayback) } collect(navModel.exploreNavigationItem, ::handleNavigation)
} }
override fun onDestroyBinding(binding: FragmentDetailBinding) { override fun onDestroyBinding(binding: FragmentDetailBinding) {

View file

@ -27,8 +27,8 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogSongDetailBinding import org.oxycblt.auxio.databinding.DialogSongDetailBinding
import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.formatDuration import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.launch
class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() { class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
private val detailModel: DetailViewModel by androidActivityViewModels() private val detailModel: DetailViewModel by androidActivityViewModels()
@ -45,7 +45,7 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
override fun onBindingCreated(binding: DialogSongDetailBinding, savedInstanceState: Bundle?) { override fun onBindingCreated(binding: DialogSongDetailBinding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState) super.onBindingCreated(binding, savedInstanceState)
detailModel.setSongId(args.songId) detailModel.setSongId(args.songId)
launch { detailModel.currentSong.collect(::updateSong) } collectImmediately(detailModel.currentSong, ::updateSong)
} }
override fun onDestroy() { override fun onDestroy() {

View file

@ -56,9 +56,10 @@ import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.getColorStateListSafe import org.oxycblt.auxio.util.getColorStateListSafe
import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.lazyReflectedField import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
@ -140,11 +141,11 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
launch { homeModel.isFastScrolling.collect(::updateFastScrolling) } collect(homeModel.isFastScrolling, ::updateFastScrolling)
launch { homeModel.currentTab.collect(::updateCurrentTab) } collectImmediately(homeModel.recreateTabs, ::handleRecreateTabs)
launch { homeModel.recreateTabs.collect(::handleRecreateTabs) } collectImmediately(homeModel.currentTab, ::updateCurrentTab)
launch { indexerModel.state.collect(::handleIndexerState) } collectImmediately(indexerModel.state, ::handleIndexerState)
launch { navModel.exploreNavigationItem.collect(::handleNavigation) } collect(navModel.exploreNavigationItem, ::handleNavigation)
} }
override fun onDestroyBinding(binding: FragmentHomeBinding) { override fun onDestroyBinding(binding: FragmentHomeBinding) {
@ -280,7 +281,10 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
is Indexer.State.Complete -> handleIndexerResponse(binding, state.response) is Indexer.State.Complete -> handleIndexerResponse(binding, state.response)
is Indexer.State.Indexing -> handleIndexingState(binding, state.indexing) is Indexer.State.Indexing -> handleIndexingState(binding, state.indexing)
null -> { null -> {
logD("Indexer is in indeterminate state, doing nothing") logD("Indexer is in indeterminate state")
binding.homeFab.hide()
binding.homeIndexingContainer.visibility = View.INVISIBLE
binding.homePager.visibility = View.INVISIBLE
} }
} }
} }

View file

@ -30,8 +30,8 @@ import org.oxycblt.auxio.ui.MenuItemListener
import org.oxycblt.auxio.ui.MonoAdapter import org.oxycblt.auxio.ui.MonoAdapter
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.SyncBackingData import org.oxycblt.auxio.ui.SyncBackingData
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.formatDuration import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logEOrThrow import org.oxycblt.auxio.util.logEOrThrow
/** /**
@ -49,7 +49,7 @@ class AlbumListFragment : HomeListFragment<Album>() {
adapter = homeAdapter adapter = homeAdapter
} }
launch { homeModel.albums.collect(homeAdapter.data::replaceList) } collectImmediately(homeModel.albums, homeAdapter.data::replaceList)
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {

View file

@ -30,8 +30,8 @@ import org.oxycblt.auxio.ui.MenuItemListener
import org.oxycblt.auxio.ui.MonoAdapter import org.oxycblt.auxio.ui.MonoAdapter
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.SyncBackingData import org.oxycblt.auxio.ui.SyncBackingData
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.formatDuration import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logEOrThrow import org.oxycblt.auxio.util.logEOrThrow
/** /**
@ -49,7 +49,7 @@ class ArtistListFragment : HomeListFragment<Artist>() {
adapter = homeAdapter adapter = homeAdapter
} }
launch { homeModel.artists.collect(homeAdapter.data::replaceList) } collectImmediately(homeModel.artists, homeAdapter.data::replaceList)
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {

View file

@ -30,8 +30,8 @@ import org.oxycblt.auxio.ui.MenuItemListener
import org.oxycblt.auxio.ui.MonoAdapter import org.oxycblt.auxio.ui.MonoAdapter
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.SyncBackingData import org.oxycblt.auxio.ui.SyncBackingData
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.formatDuration import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logEOrThrow import org.oxycblt.auxio.util.logEOrThrow
/** /**
@ -49,7 +49,7 @@ class GenreListFragment : HomeListFragment<Genre>() {
adapter = homeAdapter adapter = homeAdapter
} }
launch { homeModel.genres.collect(homeAdapter.data::replaceList) } collectImmediately(homeModel.genres, homeAdapter.data::replaceList)
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {

View file

@ -30,9 +30,9 @@ import org.oxycblt.auxio.ui.MonoAdapter
import org.oxycblt.auxio.ui.SongViewHolder import org.oxycblt.auxio.ui.SongViewHolder
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.SyncBackingData import org.oxycblt.auxio.ui.SyncBackingData
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.formatDuration import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logEOrThrow import org.oxycblt.auxio.util.logEOrThrow
/** /**
@ -51,7 +51,7 @@ class SongListFragment : HomeListFragment<Song>() {
adapter = homeAdapter adapter = homeAdapter
} }
launch { homeModel.songs.collect(homeAdapter.data::replaceList) } collectImmediately(homeModel.songs, homeAdapter.data::replaceList)
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {

View file

@ -34,10 +34,10 @@ import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.getDrawableSafe import org.oxycblt.auxio.util.getDrawableSafe
import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.getSystemGestureInsetsCompat import org.oxycblt.auxio.util.getSystemGestureInsetsCompat
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.textSafe import org.oxycblt.auxio.util.textSafe
@ -116,13 +116,13 @@ class PlaybackPanelFragment :
// --- VIEWMODEL SETUP -- // --- VIEWMODEL SETUP --
launch { playbackModel.song.collect(::updateSong) } collectImmediately(playbackModel.song, ::updateSong)
launch { playbackModel.parent.collect(::updateParent) } collectImmediately(playbackModel.parent, ::updateParent)
launch { playbackModel.positionSecs.collect(::updatePosition) } collectImmediately(playbackModel.positionSecs, ::updatePosition)
launch { playbackModel.repeatMode.collect(::updateRepeat) } collectImmediately(playbackModel.repeatMode, ::updateRepeat)
launch { playbackModel.isPlaying.collect(::updatePlaying) } collectImmediately(playbackModel.isPlaying, ::updatePlaying)
launch { playbackModel.isShuffled.collect(::updateShuffled) } collectImmediately(playbackModel.isShuffled, ::updateShuffled)
launch { playbackModel.nextUp.collect(::updateNextUp) } collectImmediately(playbackModel.nextUp, ::updateNextUp)
logD("Fragment Created") logD("Fragment Created")
} }

View file

@ -28,7 +28,7 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.collectImmediately
/** /**
* A [Fragment] that shows the queue and enables editing as well. * A [Fragment] that shows the queue and enables editing as well.
@ -53,7 +53,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
// --- VIEWMODEL SETUP ---- // --- VIEWMODEL SETUP ----
launch { playbackModel.nextUp.collect(::updateQueue) } collectImmediately(playbackModel.nextUp, ::updateQueue)
} }
override fun onDestroyBinding(binding: FragmentQueueBinding) { override fun onDestroyBinding(binding: FragmentQueueBinding) {

View file

@ -43,9 +43,10 @@ import org.oxycblt.auxio.ui.MenuFragment
import org.oxycblt.auxio.ui.MenuItemListener import org.oxycblt.auxio.ui.MenuItemListener
import org.oxycblt.auxio.util.androidViewModels import org.oxycblt.auxio.util.androidViewModels
import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getSystemServiceSafe import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
/** /**
@ -101,8 +102,8 @@ class SearchFragment :
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
launch { searchModel.searchResults.collect(::updateResults) } collectImmediately(searchModel.searchResults, ::updateResults)
launch { navModel.exploreNavigationItem.collect(::handleNavigation) } collect(navModel.exploreNavigationItem, ::handleNavigation)
} }
override fun onDestroyBinding(binding: FragmentSearchBinding) { override fun onDestroyBinding(binding: FragmentSearchBinding) {

View file

@ -37,9 +37,9 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.formatDuration import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.textSafe import org.oxycblt.auxio.util.textSafe
@ -66,10 +66,10 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
binding.aboutFaq.setOnClickListener { openLinkInBrowser(LINK_FAQ) } binding.aboutFaq.setOnClickListener { openLinkInBrowser(LINK_FAQ) }
binding.aboutLicenses.setOnClickListener { openLinkInBrowser(LINK_LICENSES) } binding.aboutLicenses.setOnClickListener { openLinkInBrowser(LINK_LICENSES) }
launch { homeModel.songs.collect(::updateSongCount) } collectImmediately(homeModel.songs, ::updateSongCount)
launch { homeModel.albums.collect(::updateAlbumCount) } collectImmediately(homeModel.albums, ::updateAlbumCount)
launch { homeModel.artists.collect(::updateArtistCount) } collectImmediately(homeModel.artists, ::updateArtistCount)
launch { homeModel.genres.collect(::updateGenreCount) } collectImmediately(homeModel.genres, ::updateGenreCount)
} }
private fun updateSongCount(songs: List<Song>) { private fun updateSongCount(songs: List<Song>) {

View file

@ -45,8 +45,8 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
@ -159,26 +159,47 @@ val @receiver:ColorRes Int.stateList
get() = ColorStateList.valueOf(this) get() = ColorStateList.valueOf(this)
/** /**
* Collect a [stateFlow] into [block] a UI-safe way. * Collect a [stateFlow] into [block] eventually.
*
* This does have an initializing call, but it usually occurs ~100ms into draw-time, which might not
* be ideal for some views. This should be used in cases where the state only needs to be updated
* during runtime.
*/
fun <T> Fragment.collect(stateFlow: StateFlow<T>, block: (T) -> Unit) {
launch { stateFlow.collect(block) }
}
/**
* Collect a [stateFlow] into [block] immediately.
* *
* This method automatically calls [block] when initially starting to ensure UI state consistency. * This method automatically calls [block] when initially starting to ensure UI state consistency.
* This does nominally mean that there are two initializing collections, but this is considered * This does nominally mean that there are two initializing collections, but this is considered
* okay. [block] should be a function pointer in order to ensure lifecycle consistency. * okay. [block] should be a function pointer in order to ensure lifecycle consistency.
* *
* Only use this if your code absolutely needs to have a good state for ~100ms of draw-time. * This should be used for state the absolutely needs to be shown at draw-time.
* Otherwise, it's somewhat in-efficient.
*/ */
fun <T> Fragment.collectImmediately(stateFlow: StateFlow<T>, block: (T) -> Unit) { fun <T> Fragment.collectImmediately(stateFlow: StateFlow<T>, block: (T) -> Unit) {
block(stateFlow.value) block(stateFlow.value)
launch { stateFlow.collect(block) } launch { stateFlow.collect(block) }
} }
/** Like [collectImmediately], but with two [StateFlow] values. */
fun <T1, T2> Fragment.collectImmediately(
a: StateFlow<T1>,
b: StateFlow<T2>,
block: (T1, T2) -> Unit
) {
block(a.value, b.value)
val combine = a.combine(b) { first, second -> Pair(first, second) }
launch { combine.collect { block(it.first, it.second) } }
}
/** /**
* Launches [block] in a lifecycle-aware coroutine once [state] is reached. This is primarily a * Launches [block] in a lifecycle-aware coroutine once [state] is reached. This is primarily a
* shortcut intended to correctly launch a co-routine on a fragment in a way that won't cause * shortcut intended to correctly launch a co-routine on a fragment in a way that won't cause
* miscellaneous coroutine insanity. * miscellaneous coroutine insanity.
*/ */
fun Fragment.launch( private fun Fragment.launch(
state: Lifecycle.State = Lifecycle.State.STARTED, state: Lifecycle.State = Lifecycle.State.STARTED,
block: suspend CoroutineScope.() -> Unit block: suspend CoroutineScope.() -> Unit
) { ) {
@ -209,18 +230,6 @@ inline fun <reified T : AndroidViewModel> Fragment.androidActivityViewModels() =
val AndroidViewModel.application: Application val AndroidViewModel.application: Application
get() = getApplication() get() = getApplication()
/**
* Combines the called flow with the given flow and then collects them both into [block]. This is a
* bit of a dumb hack with [combine], as when we have to combine flows, we often just want to call
* the same block with both functions, and not do any transformations.
*/
suspend inline fun <T1, T2> Flow<T1>.collectWith(
other: Flow<T2>,
crossinline block: (T1, T2) -> Unit
) {
combine(this, other) { a, b -> a to b }.collect { block(it.first, it.second) }
}
/** /**
* 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
* not run if the cursor is null. * not run if the cursor is null.

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ui_scroll_thumb" />
</selector>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/transparent" />
</selector>