kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale-android
Improved loading of new and cached items.
Scrolling through large lists was a pain. The next page would only load when the end of the list was reached, stopping the scroll action while the new page was fetched. This commits adds two items: * Artists, albums and playlists do not refresh data on resume, only using cached data until manually refreshed. * When manually refreshed, we initially fetch a few pages instead of only one. Also, on scroll, we try as best as we can to always keep 10 pages (pages as in screen estate) worth of data loaded.housekeeping/remove-warnings
rodzic
de0a494b43
commit
b2e6ec43a8
|
|
@ -408,7 +408,7 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
now_playing_details_favorite?.let { now_playing_details_favorite ->
|
||||
favoriteCheckRepository.fetch().untilNetwork(lifecycleScope, IO) { favorites, _, _ ->
|
||||
favoriteCheckRepository.fetch().untilNetwork(lifecycleScope, IO) { favorites, _, _, _ ->
|
||||
lifecycleScope.launch(Main) {
|
||||
track.favorite = favorites.contains(track.id)
|
||||
|
||||
|
|
|
|||
|
|
@ -67,21 +67,21 @@ class SearchActivity : AppCompatActivity() {
|
|||
adapter.tracks.clear()
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
artistsRepository.fetch(Repository.Origin.Network.origin).untilNetwork(lifecycleScope) { artists, _, _ ->
|
||||
artistsRepository.fetch(Repository.Origin.Network.origin).untilNetwork(lifecycleScope) { artists, _, _, _ ->
|
||||
done++
|
||||
|
||||
adapter.artists.addAll(artists)
|
||||
refresh()
|
||||
}
|
||||
|
||||
albumsRepository.fetch(Repository.Origin.Network.origin).untilNetwork(lifecycleScope) { albums, _, _ ->
|
||||
albumsRepository.fetch(Repository.Origin.Network.origin).untilNetwork(lifecycleScope) { albums, _, _ ,_ ->
|
||||
done++
|
||||
|
||||
adapter.albums.addAll(albums)
|
||||
refresh()
|
||||
}
|
||||
|
||||
tracksRepository.fetch(Repository.Origin.Network.origin).untilNetwork(lifecycleScope) { tracks, _, _ ->
|
||||
tracksRepository.fetch(Repository.Origin.Network.origin).untilNetwork(lifecycleScope) { tracks, _, _, _ ->
|
||||
done++
|
||||
|
||||
adapter.tracks.addAll(tracks)
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@ import kotlinx.coroutines.withContext
|
|||
class AlbumsFragment : FunkwhaleFragment<Album, AlbumsAdapter>() {
|
||||
override val viewRes = R.layout.fragment_albums
|
||||
override val recycler: RecyclerView get() = albums
|
||||
override val alwaysRefresh = false
|
||||
|
||||
lateinit var artistTracksRepository: ArtistTracksRepository
|
||||
private lateinit var artistTracksRepository: ArtistTracksRepository
|
||||
|
||||
var artistId = 0
|
||||
var artistName = ""
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import kotlinx.android.synthetic.main.fragment_artists.*
|
|||
class ArtistsFragment : FunkwhaleFragment<Artist, ArtistsAdapter>() {
|
||||
override val viewRes = R.layout.fragment_artists
|
||||
override val recycler: RecyclerView get() = artists
|
||||
override val alwaysRefresh = false
|
||||
|
||||
companion object {
|
||||
fun openAlbums(context: Context?, artist: Artist, fragment: Fragment? = null, art: String? = null) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import kotlinx.coroutines.withContext
|
|||
class FavoritesFragment : FunkwhaleFragment<Track, FavoritesAdapter>() {
|
||||
override val viewRes = R.layout.fragment_favorites
|
||||
override val recycler: RecyclerView get() = favorites
|
||||
override val alwaysRefresh = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
|||
|
|
@ -28,14 +28,19 @@ abstract class FunkwhaleAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.
|
|||
}
|
||||
|
||||
abstract class FunkwhaleFragment<D : Any, A : FunkwhaleAdapter<D, *>> : Fragment() {
|
||||
val INITIAL_PAGES = 5
|
||||
val OFFSCREEN_PAGES = 10
|
||||
|
||||
abstract val viewRes: Int
|
||||
abstract val recycler: RecyclerView
|
||||
open val layoutManager: RecyclerView.LayoutManager get() = LinearLayoutManager(context)
|
||||
open val alwaysRefresh = true
|
||||
|
||||
lateinit var repository: Repository<D, *>
|
||||
lateinit var adapter: A
|
||||
|
||||
private var initialFetched = false
|
||||
private var moreLoading = false
|
||||
private var listener: Job? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
|
|
@ -51,7 +56,11 @@ abstract class FunkwhaleFragment<D : Any, A : FunkwhaleAdapter<D, *>> : Fragment
|
|||
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
||||
if (upstream.behavior == HttpUpstream.Behavior.Progressive) {
|
||||
recycler.setOnScrollChangeListener { _, _, _, _, _ ->
|
||||
if (recycler.computeVerticalScrollOffset() > 0 && !recycler.canScrollVertically(1)) {
|
||||
val offset = recycler.computeVerticalScrollOffset()
|
||||
val left = recycler.computeVerticalScrollRange() - recycler.height - offset
|
||||
|
||||
if (initialFetched && !moreLoading && offset > 0 && left < (recycler.height * OFFSCREEN_PAGES)) {
|
||||
moreLoading = true
|
||||
fetch(Repository.Origin.Network.origin, adapter.data.size)
|
||||
}
|
||||
}
|
||||
|
|
@ -60,7 +69,7 @@ abstract class FunkwhaleFragment<D : Any, A : FunkwhaleAdapter<D, *>> : Fragment
|
|||
|
||||
fetch(Repository.Origin.Cache.origin)
|
||||
|
||||
if (adapter.data.isEmpty()) {
|
||||
if (alwaysRefresh && adapter.data.isEmpty()) {
|
||||
fetch(Repository.Origin.Network.origin)
|
||||
}
|
||||
}
|
||||
|
|
@ -91,11 +100,11 @@ abstract class FunkwhaleFragment<D : Any, A : FunkwhaleAdapter<D, *>> : Fragment
|
|||
private fun fetch(upstreams: Int = Repository.Origin.Network.origin, size: Int = 0) {
|
||||
var first = size == 0
|
||||
|
||||
if (upstreams == Repository.Origin.Network.origin) {
|
||||
if (!moreLoading && upstreams == Repository.Origin.Network.origin) {
|
||||
swiper?.isRefreshing = true
|
||||
}
|
||||
|
||||
repository.fetch(upstreams, size).untilNetwork(lifecycleScope, IO) { data, isCache, hasMore ->
|
||||
repository.fetch(upstreams, size).untilNetwork(lifecycleScope, IO) { data, isCache, page, hasMore ->
|
||||
lifecycleScope.launch(Main) {
|
||||
if (isCache) {
|
||||
adapter.data = data.toMutableList()
|
||||
|
|
@ -112,10 +121,16 @@ abstract class FunkwhaleFragment<D : Any, A : FunkwhaleAdapter<D, *>> : Fragment
|
|||
|
||||
adapter.data.addAll(data)
|
||||
|
||||
if (!hasMore) {
|
||||
swiper?.isRefreshing = false
|
||||
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
||||
when (upstream.behavior) {
|
||||
HttpUpstream.Behavior.Progressive -> if (!hasMore || page >= INITIAL_PAGES) swiper?.isRefreshing = false
|
||||
HttpUpstream.Behavior.AtOnce -> if (!hasMore) swiper?.isRefreshing = false
|
||||
HttpUpstream.Behavior.Single -> if (!hasMore) swiper?.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
withContext(IO) {
|
||||
when (hasMore) {
|
||||
false -> withContext(IO) {
|
||||
if (adapter.data.isNotEmpty()) {
|
||||
try {
|
||||
repository.cacheId?.let { cacheId ->
|
||||
|
|
@ -129,6 +144,21 @@ abstract class FunkwhaleFragment<D : Any, A : FunkwhaleAdapter<D, *>> : Fragment
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
true -> {
|
||||
moreLoading = false
|
||||
|
||||
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
||||
if (upstream.behavior == HttpUpstream.Behavior.Progressive) {
|
||||
if (page < INITIAL_PAGES) {
|
||||
moreLoading = true
|
||||
fetch(Repository.Origin.Network.origin, adapter.data.size)
|
||||
} else {
|
||||
initialFetched = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (first) {
|
||||
|
|
|
|||
|
|
@ -41,18 +41,19 @@ class HttpUpstream<D : Any, R : FunkwhaleResponse<D>>(val behavior: Behavior, pr
|
|||
{ response ->
|
||||
val data = response.getData()
|
||||
|
||||
if (behavior == Behavior.Progressive || response.next == null) {
|
||||
emit(Repository.Response(Repository.Origin.Network, data, false))
|
||||
} else {
|
||||
emit(Repository.Response(Repository.Origin.Network, data, true))
|
||||
when (behavior) {
|
||||
Behavior.Progressive -> emit(Repository.Response(Repository.Origin.Network, data, page, response.next != null))
|
||||
else -> {
|
||||
emit(Repository.Response(Repository.Origin.Network, data, page, response.next != null))
|
||||
|
||||
fetch(size + data.size).collect { emit(it) }
|
||||
fetch(size + data.size).collect { emit(it) }
|
||||
}
|
||||
}
|
||||
},
|
||||
{ error ->
|
||||
when (error.exception) {
|
||||
is RefreshError -> EventBus.send(Event.LogOut)
|
||||
else -> emit(Repository.Response(Repository.Origin.Network, listOf(), false))
|
||||
else -> emit(Repository.Response(Repository.Origin.Network, listOf(), page,false))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.github.apognu.otter.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.utils.Cache
|
||||
import com.github.apognu.otter.utils.CacheItem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -8,6 +9,7 @@ import kotlinx.coroutines.Dispatchers.IO
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.io.BufferedReader
|
||||
import kotlin.math.ceil
|
||||
|
||||
interface Upstream<D> {
|
||||
fun fetch(size: Int = 0): Flow<Repository.Response<D>>
|
||||
|
|
@ -21,7 +23,7 @@ abstract class Repository<D : Any, C : CacheItem<D>> {
|
|||
Network(0b10)
|
||||
}
|
||||
|
||||
data class Response<D>(val origin: Origin, val data: List<D>, val hasMore: Boolean)
|
||||
data class Response<D>(val origin: Origin, val data: List<D>, val page: Int, val hasMore: Boolean)
|
||||
|
||||
abstract val context: Context?
|
||||
abstract val cacheId: String?
|
||||
|
|
@ -39,7 +41,7 @@ abstract class Repository<D : Any, C : CacheItem<D>> {
|
|||
cacheId?.let { cacheId ->
|
||||
Cache.get(context, cacheId)?.let { reader ->
|
||||
uncache(reader)?.let { cache ->
|
||||
emit(Response(Origin.Cache, cache.data, false))
|
||||
emit(Response(Origin.Cache, cache.data, ceil(cache.data.size / AppContext.PAGE_SIZE.toDouble()).toInt(), false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,8 +50,8 @@ abstract class Repository<D : Any, C : CacheItem<D>> {
|
|||
private fun fromNetwork(size: Int) = flow {
|
||||
upstream
|
||||
.fetch(size)
|
||||
.map { response -> Response(Origin.Network, onDataFetched(response.data), response.hasMore) }
|
||||
.collect { response -> emit(Response(Origin.Network, response.data, response.hasMore)) }
|
||||
.map { response -> Response(Origin.Network, onDataFetched(response.data), response.page, response.hasMore) }
|
||||
.collect { response -> emit(Response(Origin.Network, response.data, response.page, response.hasMore)) }
|
||||
}
|
||||
|
||||
protected open fun onDataFetched(data: List<D>) = data
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ import kotlinx.coroutines.flow.collect
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
inline fun <D> Flow<Repository.Response<D>>.untilNetwork(scope: CoroutineScope, context: CoroutineContext = Main, crossinline callback: (data: List<D>, isCache: Boolean, hasMore: Boolean) -> Unit) {
|
||||
inline fun <D> Flow<Repository.Response<D>>.untilNetwork(scope: CoroutineScope, context: CoroutineContext = Main, crossinline callback: (data: List<D>, isCache: Boolean, page: Int, hasMore: Boolean) -> Unit) {
|
||||
scope.launch(context) {
|
||||
collect { data ->
|
||||
callback(data.data, data.origin == Repository.Origin.Cache, data.hasMore)
|
||||
callback(data.data, data.origin == Repository.Origin.Cache, data.page, data.hasMore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config xmlns:tools="http://schemas.android.com/tools">
|
||||
<base-config>
|
||||
<trust-anchors>
|
||||
|
||||
<certificates src="system" />
|
||||
|
||||
<certificates
|
||||
src="user"
|
||||
tools:ignore="AcceptsUserCertificates" />
|
||||
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
</network-security-config>
|
||||
Ładowanie…
Reference in New Issue