mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2026-10-22 23:42:14 +03:00
Merge branch 'decouple' into 'develop'
Decouple list ViewModels from SwipeRefreshLayout See merge request ultrasonic/ultrasonic!1428
This commit is contained in:
commit
d84c3a646e
14 files changed
+47
-40
No files matched your search
@@ -82,7 +82,7 @@ class AlbumListFragment(
|
||||
listModel.viewModelScope.launch(
|
||||
toastingExceptionHandler()
|
||||
) {
|
||||
listModel.withDelayedLoadingIndicator(swipeRefresh) {
|
||||
listModel.withDelayedLoadingIndicator {
|
||||
if (navArgs.byArtist) {
|
||||
listModel.getAlbumsOfArtist(
|
||||
id = navArgs.id!!,
|
||||
|
||||
@@ -42,7 +42,7 @@ class ArtistListFragment : EntryListFragment<ArtistOrIndex>() {
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
override fun getLiveData(refresh: Boolean, append: Boolean): LiveData<List<ArtistOrIndex>> =
|
||||
listModel.getItems(navArgs.refresh || refresh, swipeRefresh!!)
|
||||
listModel.getItems(navArgs.refresh || refresh)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@@ -72,7 +72,7 @@ abstract class EntryListFragment<T : GenericEntry> :
|
||||
currentSetting.musicFolderId = it.id
|
||||
serverSettingsModel.updateItem(currentSetting)
|
||||
}
|
||||
listModel.refresh(swipeRefresh!!)
|
||||
listModel.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,13 @@ abstract class MultiListFragment<T : Identifiable> :
|
||||
// Setup refresh handler
|
||||
swipeRefresh = view.findViewById(refreshListId)
|
||||
swipeRefresh?.setOnRefreshListener {
|
||||
listModel.refresh(swipeRefresh!!)
|
||||
listModel.refresh()
|
||||
}
|
||||
|
||||
// The model signals its loading state; forward it to the pull-to-refresh
|
||||
// indicator. This keeps the View knowledge on this side of the boundary.
|
||||
listModel.isRefreshing.observe(viewLifecycleOwner) { refreshing ->
|
||||
swipeRefresh?.isRefreshing = refreshing
|
||||
}
|
||||
|
||||
// Populate the LiveData. Skip the fetch on config changes (e.g. rotation) when data
|
||||
|
||||
@@ -61,7 +61,7 @@ class PlaylistsFragment : MultiListFragment<Playlist>() {
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
override fun getLiveData(refresh: Boolean, append: Boolean): LiveData<List<Playlist>> =
|
||||
listModel.getItems(refresh, swipeRefresh!!)
|
||||
listModel.getItems(refresh)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
@@ -241,7 +241,7 @@ class PlaylistsFragment : MultiListFragment<Playlist>() {
|
||||
)
|
||||
}
|
||||
|
||||
listModel.getItems(true, swipeRefresh!!)
|
||||
listModel.getItems(true)
|
||||
toast(resources.getString(R.string.playlist_updated_info, playlist.name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class PodcastFragment : MultiListFragment<PodcastsChannel>() {
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
override fun getLiveData(refresh: Boolean, append: Boolean): LiveData<List<PodcastsChannel>> =
|
||||
listModel.getItems(refresh, swipeRefresh!!)
|
||||
listModel.getItems(refresh)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@@ -40,7 +40,7 @@ class SelectGenreFragment : MultiListFragment<Genre>() {
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
override fun getLiveData(refresh: Boolean, append: Boolean): LiveData<List<Genre>> =
|
||||
listModel.getItems(refresh, swipeRefresh!!)
|
||||
listModel.getItems(refresh)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@@ -69,7 +69,7 @@ class SharesFragment :
|
||||
* The central function to pass a query to the model and return a LiveData object
|
||||
*/
|
||||
override fun getLiveData(refresh: Boolean, append: Boolean): LiveData<List<Share>> =
|
||||
listModel.getItems(refresh, swipeRefresh!!)
|
||||
listModel.getItems(refresh)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
@@ -310,7 +310,7 @@ class SharesFragment :
|
||||
getMusicService().updateShare(share.id, description, millis)
|
||||
}
|
||||
|
||||
listModel.getItems(true, swipeRefresh!!)
|
||||
listModel.getItems(true)
|
||||
toast(resources.getString(R.string.playlist_updated_info, share.name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package org.moire.ultrasonic.model
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import java.text.Collator
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.domain.ArtistOrIndex
|
||||
@@ -24,11 +23,11 @@ class ArtistListModel(application: Application) : GenericListModel(application)
|
||||
/**
|
||||
* Retrieves all available Artists in a LiveData
|
||||
*/
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<ArtistOrIndex>> {
|
||||
fun getItems(refresh: Boolean): LiveData<List<ArtistOrIndex>> {
|
||||
// Don't reload the data if navigating back to the view that was active before.
|
||||
// This way, we keep the scroll position
|
||||
if (artists.value?.isEmpty() != false || refresh) {
|
||||
backgroundLoadFromServer(refresh, swipe)
|
||||
backgroundLoadFromServer(refresh)
|
||||
}
|
||||
return artists
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ package org.moire.ultrasonic.model
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -47,6 +47,13 @@ open class GenericListModel(application: Application) :
|
||||
|
||||
val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData(listOf())
|
||||
|
||||
/**
|
||||
* The loading state of the current load operation. Fragments observe this to drive
|
||||
* their pull-to-refresh indicator, keeping the View out of the model's API.
|
||||
*/
|
||||
private val _isRefreshing = MutableLiveData(false)
|
||||
val isRefreshing: LiveData<Boolean> get() = _isRefreshing
|
||||
|
||||
open fun showSelectFolderHeader(): Boolean = false
|
||||
|
||||
/**
|
||||
@@ -57,17 +64,17 @@ open class GenericListModel(application: Application) :
|
||||
/**
|
||||
* Refreshes the cached items from the server
|
||||
*/
|
||||
fun refresh(swipe: SwipeRefreshLayout) {
|
||||
backgroundLoadFromServer(true, swipe)
|
||||
fun refresh() {
|
||||
backgroundLoadFromServer(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a load() and notify the UI that we are loading
|
||||
*/
|
||||
fun backgroundLoadFromServer(refresh: Boolean, swipe: SwipeRefreshLayout) {
|
||||
fun backgroundLoadFromServer(refresh: Boolean) {
|
||||
viewModelScope.launch {
|
||||
withDelayedLoadingIndicator(swipe) {
|
||||
loadFromServer(refresh, swipe)
|
||||
withDelayedLoadingIndicator {
|
||||
loadFromServer(refresh)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,16 +82,15 @@ open class GenericListModel(application: Application) :
|
||||
/**
|
||||
* Avoid showing a loading indicator for requests that complete quickly, such as Room reads.
|
||||
* A pull-to-refresh gesture may already have made the indicator visible; this preserves that.
|
||||
* The indicator itself is driven through [isRefreshing], which the observing fragment
|
||||
* forwards to its SwipeRefreshLayout.
|
||||
*/
|
||||
suspend fun <T> withDelayedLoadingIndicator(
|
||||
swipe: SwipeRefreshLayout?,
|
||||
block: suspend () -> T
|
||||
): T {
|
||||
suspend fun <T> withDelayedLoadingIndicator(block: suspend () -> T): T {
|
||||
var indicatorJob: Job? = null
|
||||
if (swipe != null && !swipe.isRefreshing) {
|
||||
if (_isRefreshing.value != true) {
|
||||
indicatorJob = viewModelScope.launch {
|
||||
delay(LOADING_INDICATOR_DELAY_MS)
|
||||
swipe.isRefreshing = true
|
||||
_isRefreshing.value = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,14 +98,14 @@ open class GenericListModel(application: Application) :
|
||||
block()
|
||||
} finally {
|
||||
indicatorJob?.cancel()
|
||||
swipe?.isRefreshing = false
|
||||
_isRefreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the load() function with error handling
|
||||
*/
|
||||
private suspend fun loadFromServer(refresh: Boolean, swipe: SwipeRefreshLayout) {
|
||||
private suspend fun loadFromServer(refresh: Boolean) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val musicService = MusicServiceFactory.getMusicService()
|
||||
val isOffline = ActiveServerProvider.isOffline()
|
||||
@@ -111,7 +117,7 @@ open class GenericListModel(application: Application) :
|
||||
// CommunicationError reports transport failures to the provider, which switches
|
||||
// subsequent loads to the local catalog, and shows a unified message. API and
|
||||
// authentication failures are deliberately left fully visible to the user.
|
||||
CommunicationError.handleError(all, swipe.context)
|
||||
CommunicationError.handleError(all, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package org.moire.ultrasonic.model
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import org.moire.ultrasonic.domain.Genre
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
|
||||
@@ -22,11 +21,11 @@ class GenreListModel(application: Application) : GenericListModel(application) {
|
||||
/**
|
||||
* Retrieves the Genres in a LiveData
|
||||
*/
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Genre>> {
|
||||
fun getItems(refresh: Boolean): LiveData<List<Genre>> {
|
||||
// Don't reload the data if navigating back to the view that was active before.
|
||||
// This way, we keep the scroll position
|
||||
if (genres.value?.isEmpty() != false || refresh) {
|
||||
backgroundLoadFromServer(refresh, swipe)
|
||||
backgroundLoadFromServer(refresh)
|
||||
}
|
||||
return genres
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package org.moire.ultrasonic.model
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import org.koin.core.component.inject
|
||||
import org.moire.ultrasonic.domain.Playlist
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
@@ -25,11 +24,11 @@ class PlaylistListModel(application: Application) : GenericListModel(application
|
||||
/**
|
||||
* Retrieves the Playlists in a LiveData
|
||||
*/
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Playlist>> {
|
||||
fun getItems(refresh: Boolean): LiveData<List<Playlist>> {
|
||||
// Don't reload the data if navigating back to the view that was active before.
|
||||
// This way, we keep the scroll position
|
||||
if (playlists.value?.isEmpty() != false || refresh) {
|
||||
backgroundLoadFromServer(refresh, swipe)
|
||||
backgroundLoadFromServer(refresh)
|
||||
}
|
||||
return playlists
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package org.moire.ultrasonic.model
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import org.moire.ultrasonic.domain.PodcastsChannel
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
|
||||
@@ -22,11 +21,11 @@ class PodcastListModel(application: Application) : GenericListModel(application)
|
||||
/**
|
||||
* Retrieves the Podcast channels in a LiveData
|
||||
*/
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<PodcastsChannel>> {
|
||||
fun getItems(refresh: Boolean): LiveData<List<PodcastsChannel>> {
|
||||
// Don't reload the data if navigating back to the view that was active before.
|
||||
// This way, we keep the scroll position
|
||||
if (channels.value?.isEmpty() != false || refresh) {
|
||||
backgroundLoadFromServer(refresh, swipe)
|
||||
backgroundLoadFromServer(refresh)
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ package org.moire.ultrasonic.model
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import org.moire.ultrasonic.domain.Share
|
||||
import org.moire.ultrasonic.service.MusicService
|
||||
|
||||
@@ -22,11 +21,11 @@ class ShareListModel(application: Application) : GenericListModel(application) {
|
||||
/**
|
||||
* Retrieves the Shares in a LiveData
|
||||
*/
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Share>> {
|
||||
fun getItems(refresh: Boolean): LiveData<List<Share>> {
|
||||
// Don't reload the data if navigating back to the view that was active before.
|
||||
// This way, we keep the scroll position
|
||||
if (shares.value?.isEmpty() != false || refresh) {
|
||||
backgroundLoadFromServer(refresh, swipe)
|
||||
backgroundLoadFromServer(refresh)
|
||||
}
|
||||
return shares
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user