2021-07-12 10:14:26 +02:00
|
|
|
package audio.funkwhale.ffa.fragments
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
import android.os.Bundle
|
|
|
|
import android.view.View
|
|
|
|
import androidx.fragment.app.Fragment
|
2020-06-25 01:26:15 +02:00
|
|
|
import androidx.lifecycle.lifecycleScope
|
2019-08-19 16:50:33 +02:00
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
2020-07-11 18:15:40 +02:00
|
|
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
2021-07-16 10:03:52 +02:00
|
|
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
2021-07-12 10:14:26 +02:00
|
|
|
import audio.funkwhale.ffa.repositories.HttpUpstream
|
|
|
|
import audio.funkwhale.ffa.repositories.Repository
|
2021-09-09 09:56:15 +02:00
|
|
|
import audio.funkwhale.ffa.utils.Event
|
|
|
|
import audio.funkwhale.ffa.utils.EventBus
|
|
|
|
import audio.funkwhale.ffa.utils.FFACache
|
|
|
|
import audio.funkwhale.ffa.utils.untilNetwork
|
2019-10-29 23:41:44 +01:00
|
|
|
import com.google.gson.Gson
|
|
|
|
import kotlinx.coroutines.Dispatchers.IO
|
|
|
|
import kotlinx.coroutines.Dispatchers.Main
|
2020-06-13 19:34:57 +02:00
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
import kotlinx.coroutines.flow.collect
|
2019-10-29 23:41:44 +01:00
|
|
|
import kotlinx.coroutines.launch
|
2020-06-13 19:34:57 +02:00
|
|
|
import kotlinx.coroutines.withContext
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
abstract class FFAAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
|
2019-08-19 16:50:33 +02:00
|
|
|
var data: MutableList<D> = mutableListOf()
|
2020-07-11 18:15:40 +02:00
|
|
|
|
|
|
|
init {
|
|
|
|
super.setHasStableIds(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract override fun getItemId(position: Int): Long
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>>() : Fragment() {
|
2020-07-13 10:31:36 +02:00
|
|
|
companion object {
|
|
|
|
const val OFFSCREEN_PAGES = 20
|
|
|
|
}
|
2020-07-08 22:11:50 +02:00
|
|
|
|
2019-08-19 16:50:33 +02:00
|
|
|
abstract val recycler: RecyclerView
|
|
|
|
open val layoutManager: RecyclerView.LayoutManager get() = LinearLayoutManager(context)
|
2020-07-08 22:11:50 +02:00
|
|
|
open val alwaysRefresh = true
|
2019-08-19 16:50:33 +02:00
|
|
|
|
|
|
|
lateinit var repository: Repository<D, *>
|
|
|
|
lateinit var adapter: A
|
2021-07-16 10:03:52 +02:00
|
|
|
lateinit var swiper: SwipeRefreshLayout
|
2019-08-19 16:50:33 +02:00
|
|
|
|
2020-07-08 22:11:50 +02:00
|
|
|
private var moreLoading = false
|
2020-06-13 19:34:57 +02:00
|
|
|
private var listener: Job? = null
|
2019-10-24 12:35:34 +02:00
|
|
|
|
2019-08-19 16:50:33 +02:00
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
|
|
super.onViewCreated(view, savedInstanceState)
|
|
|
|
|
|
|
|
recycler.layoutManager = layoutManager
|
2020-07-11 18:15:40 +02:00
|
|
|
(recycler.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
|
2019-08-19 16:50:33 +02:00
|
|
|
recycler.adapter = adapter
|
|
|
|
|
2019-10-29 23:41:44 +01:00
|
|
|
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
|
|
|
if (upstream.behavior == HttpUpstream.Behavior.Progressive) {
|
2019-11-01 13:42:15 +01:00
|
|
|
recycler.setOnScrollChangeListener { _, _, _, _, _ ->
|
2020-07-08 22:11:50 +02:00
|
|
|
val offset = recycler.computeVerticalScrollOffset()
|
|
|
|
|
2020-07-10 20:28:44 +02:00
|
|
|
if (!moreLoading && offset > 0 && needsMoreOffscreenPages()) {
|
2020-07-08 22:11:50 +02:00
|
|
|
moreLoading = true
|
2020-07-10 20:28:44 +02:00
|
|
|
|
2019-10-29 23:41:44 +01:00
|
|
|
fetch(Repository.Origin.Network.origin, adapter.data.size)
|
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-13 10:31:36 +02:00
|
|
|
if (listener == null) {
|
|
|
|
listener = lifecycleScope.launch(IO) {
|
|
|
|
EventBus.get().collect { event ->
|
|
|
|
if (event is Event.ListingsChanged) {
|
|
|
|
withContext(Main) {
|
2021-07-16 10:03:52 +02:00
|
|
|
|
|
|
|
swiper.isRefreshing = true
|
2020-07-13 10:31:36 +02:00
|
|
|
fetch(Repository.Origin.Network.origin)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-25 21:39:10 +01:00
|
|
|
fetch(Repository.Origin.Cache.origin)
|
|
|
|
|
2020-07-08 22:11:50 +02:00
|
|
|
if (alwaysRefresh && adapter.data.isEmpty()) {
|
2019-11-25 21:39:10 +01:00
|
|
|
fetch(Repository.Origin.Network.origin)
|
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onResume() {
|
|
|
|
super.onResume()
|
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
swiper.setOnRefreshListener {
|
2019-10-29 23:41:44 +01:00
|
|
|
fetch(Repository.Origin.Network.origin)
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-04 19:56:09 +02:00
|
|
|
fun update() {
|
2021-07-16 10:03:52 +02:00
|
|
|
swiper.isRefreshing = true
|
2020-09-04 19:56:09 +02:00
|
|
|
fetch(Repository.Origin.Network.origin)
|
|
|
|
}
|
|
|
|
|
2019-08-19 16:50:33 +02:00
|
|
|
open fun onDataFetched(data: List<D>) {}
|
2019-10-24 12:35:34 +02:00
|
|
|
|
2019-11-25 21:39:10 +01:00
|
|
|
private fun fetch(upstreams: Int = Repository.Origin.Network.origin, size: Int = 0) {
|
2019-11-01 13:42:15 +01:00
|
|
|
var first = size == 0
|
2019-10-29 23:41:44 +01:00
|
|
|
|
2020-07-08 22:11:50 +02:00
|
|
|
if (!moreLoading && upstreams == Repository.Origin.Network.origin) {
|
2020-07-09 10:45:52 +02:00
|
|
|
lifecycleScope.launch(Main) {
|
2021-07-16 10:03:52 +02:00
|
|
|
swiper.isRefreshing = true
|
2020-07-09 10:45:52 +02:00
|
|
|
}
|
2019-11-25 21:39:10 +01:00
|
|
|
}
|
2019-10-29 23:41:44 +01:00
|
|
|
|
2020-07-10 20:28:44 +02:00
|
|
|
moreLoading = true
|
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
repository.fetch(upstreams, size)
|
|
|
|
.untilNetwork(lifecycleScope, IO) { data, isCache, _, hasMore ->
|
|
|
|
if (isCache && data.isEmpty()) {
|
|
|
|
moreLoading = false
|
2020-07-10 20:37:28 +02:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
return@untilNetwork fetch(Repository.Origin.Network.origin)
|
|
|
|
}
|
2020-07-08 23:00:10 +02:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
lifecycleScope.launch(Main) {
|
|
|
|
if (isCache) {
|
|
|
|
moreLoading = false
|
2020-07-10 20:28:44 +02:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
adapter.data = data.toMutableList()
|
|
|
|
adapter.notifyDataSetChanged()
|
2019-10-31 16:17:37 +01:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
return@launch
|
|
|
|
}
|
2019-10-24 12:35:34 +02:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
if (first) {
|
|
|
|
adapter.data.clear()
|
|
|
|
}
|
2019-10-24 12:35:34 +02:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
onDataFetched(data)
|
2019-11-01 13:42:15 +01:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
adapter.data.addAll(data)
|
2019-11-01 13:42:15 +01:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
withContext(IO) {
|
|
|
|
try {
|
|
|
|
repository.cacheId?.let { cacheId ->
|
2021-08-09 06:50:46 +02:00
|
|
|
FFACache.set(
|
2021-07-16 10:03:52 +02:00
|
|
|
context,
|
|
|
|
cacheId,
|
|
|
|
Gson().toJson(repository.cache(adapter.data)).toByteArray()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} catch (e: ConcurrentModificationException) {
|
2019-11-01 13:42:15 +01:00
|
|
|
}
|
|
|
|
}
|
2020-07-08 22:11:50 +02:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
if (hasMore) {
|
|
|
|
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
|
|
|
if (!isCache && upstream.behavior == HttpUpstream.Behavior.Progressive) {
|
|
|
|
if (first || needsMoreOffscreenPages()) {
|
|
|
|
fetch(Repository.Origin.Network.origin, adapter.data.size)
|
|
|
|
} else {
|
|
|
|
moreLoading = false
|
|
|
|
}
|
2020-07-08 23:21:47 +02:00
|
|
|
} else {
|
2020-07-10 20:28:44 +02:00
|
|
|
moreLoading = false
|
2020-07-08 22:11:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-24 12:35:34 +02:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
|
|
|
|
when (upstream.behavior) {
|
2021-08-09 06:50:46 +02:00
|
|
|
HttpUpstream.Behavior.Progressive -> if (!hasMore || !moreLoading) swiper.isRefreshing =
|
2021-07-16 10:03:52 +02:00
|
|
|
false
|
|
|
|
HttpUpstream.Behavior.AtOnce -> if (!hasMore) swiper.isRefreshing = false
|
|
|
|
HttpUpstream.Behavior.Single -> if (!hasMore) swiper.isRefreshing = false
|
|
|
|
}
|
2020-07-10 20:28:44 +02:00
|
|
|
}
|
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
when (first) {
|
|
|
|
true -> {
|
|
|
|
adapter.notifyDataSetChanged()
|
|
|
|
first = false
|
|
|
|
}
|
2019-10-31 16:17:37 +01:00
|
|
|
|
2021-07-16 10:03:52 +02:00
|
|
|
false -> adapter.notifyItemRangeInserted(adapter.data.size, data.size)
|
|
|
|
}
|
2019-10-29 23:41:44 +01:00
|
|
|
}
|
2019-10-24 12:35:34 +02:00
|
|
|
}
|
|
|
|
}
|
2020-07-10 20:28:44 +02:00
|
|
|
|
|
|
|
private fun needsMoreOffscreenPages(): Boolean {
|
2020-07-10 21:03:48 +02:00
|
|
|
view?.let {
|
|
|
|
val offset = recycler.computeVerticalScrollOffset()
|
|
|
|
val left = recycler.computeVerticalScrollRange() - recycler.height - offset
|
2020-07-10 20:28:44 +02:00
|
|
|
|
2020-07-10 21:03:48 +02:00
|
|
|
return left < (recycler.height * OFFSCREEN_PAGES)
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
2020-07-10 20:28:44 +02:00
|
|
|
}
|
2019-08-19 16:50:33 +02:00
|
|
|
}
|