funkwhale-app-android/app/src/main/java/audio/funkwhale/ffa/fragments/FFAFragment.kt

221 lines
6.2 KiB
Kotlin
Raw Normal View History

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
import androidx.lifecycle.lifecycleScope
2019-08-19 16:50:33 +02:00
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
2021-07-16 10:03:52 +02:00
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
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
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
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()
2022-12-09 09:49:41 +01:00
private var unfilteredData: MutableList<D> = mutableListOf()
fun getUnfilteredData(): MutableList<D> {
return unfilteredData
}
fun setUnfilteredData(data: MutableList<D>) {
unfilteredData = data
applyFilter()
}
open fun applyFilter() {
data.clear()
data.addAll(unfilteredData)
}
init {
super.setHasStableIds(true)
}
abstract override fun getItemId(position: Int): Long
2019-08-19 16:50:33 +02:00
}
2023-01-13 10:33:24 +01:00
abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>> : Fragment() {
companion object {
const val OFFSCREEN_PAGES = 20
}
2019-08-19 16:50:33 +02:00
abstract val recycler: RecyclerView
open val layoutManager: RecyclerView.LayoutManager get() = LinearLayoutManager(context)
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
private var moreLoading = false
private var listener: Job? = null
fun <T> repository() = repository as T
2019-08-19 16:50:33 +02:00
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recycler.layoutManager = layoutManager
(recycler.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
2019-08-19 16:50:33 +02:00
recycler.adapter = adapter
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
if (upstream.behavior == HttpUpstream.Behavior.Progressive) {
recycler.setOnScrollChangeListener { _, _, _, _, _ ->
val offset = recycler.computeVerticalScrollOffset()
if (!moreLoading && offset > 0 && needsMoreOffscreenPages()) {
moreLoading = true
fetch(Repository.Origin.Network.origin, adapter.data.size)
}
2019-08-19 16:50:33 +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
fetch(Repository.Origin.Network.origin)
}
}
}
}
}
fetch(Repository.Origin.Cache.origin)
if (alwaysRefresh && adapter.data.isEmpty()) {
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 {
fetch(Repository.Origin.Network.origin)
2019-08-19 16:50:33 +02:00
}
}
fun update() {
2021-07-16 10:03:52 +02:00
swiper.isRefreshing = true
fetch(Repository.Origin.Network.origin)
}
2019-08-19 16:50:33 +02:00
open fun onDataFetched(data: List<D>) {}
private fun fetch(upstreams: Int = Repository.Origin.Network.origin, size: Int = 0) {
var first = size == 0
if (!moreLoading && upstreams == Repository.Origin.Network.origin) {
lifecycleScope.launch(Main) {
2021-07-16 10:03:52 +02:00
swiper.isRefreshing = true
}
}
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)
}
2021-07-16 10:03:52 +02:00
lifecycleScope.launch(Main) {
if (isCache) {
moreLoading = false
2022-12-09 09:49:41 +01:00
adapter.setUnfilteredData(data.toMutableList())
2021-07-16 10:03:52 +02:00
adapter.notifyDataSetChanged()
2019-10-31 16:17:37 +01:00
2021-07-16 10:03:52 +02:00
return@launch
}
2021-07-16 10:03:52 +02:00
if (first) {
2022-12-09 09:49:41 +01:00
adapter.getUnfilteredData().clear()
2021-07-16 10:03:52 +02:00
}
2021-07-16 10:03:52 +02:00
onDataFetched(data)
2022-12-09 09:49:41 +01:00
adapter.getUnfilteredData().addAll(data)
adapter.applyFilter()
2021-07-16 10:03:52 +02:00
withContext(IO) {
try {
repository.cacheId?.let { cacheId ->
FFACache.set(
2021-07-16 10:03:52 +02:00
context,
cacheId,
2022-12-09 09:49:41 +01:00
Gson().toJson(repository.cache(adapter.getUnfilteredData())).toString()
2021-07-16 10:03:52 +02:00
)
}
} catch (e: ConcurrentModificationException) {
}
}
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()) {
2022-12-09 09:49:41 +01:00
fetch(Repository.Origin.Network.origin, adapter.getUnfilteredData().size)
2021-07-16 10:03:52 +02:00
} else {
moreLoading = false
}
2020-07-08 23:21:47 +02:00
} else {
moreLoading = false
}
}
}
2021-07-16 10:03:52 +02:00
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
when (upstream.behavior) {
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
}
}
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)
}
}
}
}
private fun needsMoreOffscreenPages(): Boolean {
view?.let {
val offset = recycler.computeVerticalScrollOffset()
val left = recycler.computeVerticalScrollRange() - recycler.height - offset
return left < (recycler.height * OFFSCREEN_PAGES)
}
return false
}
2019-08-19 16:50:33 +02:00
}