Otter-App-Android-Funkwhale/app/src/main/java/com/github/apognu/otter/fragments/OtterFragment.kt

222 lines
6.7 KiB
Kotlin

package com.github.apognu.otter.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.*
import androidx.paging.PagingData
import androidx.paging.PagingDataAdapter
import androidx.paging.map
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.github.apognu.otter.repositories.HttpUpstream
import com.github.apognu.otter.repositories.Repository
import com.github.apognu.otter.utils.Event
import com.github.apognu.otter.utils.EventBus
import com.github.apognu.otter.utils.log
import com.github.apognu.otter.utils.untilNetwork
import kotlinx.android.synthetic.main.fragment_artists.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
abstract class OtterAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
var data: MutableList<D> = mutableListOf()
init {
super.setHasStableIds(true)
}
abstract override fun getItemId(position: Int): Long
}
abstract class OtterFragment<DAO : Any, D : Any, A : OtterAdapter<D, *>> : Fragment() {
open val OFFSCREEN_PAGES = 10
abstract val repository: Repository<DAO>
abstract val adapter: A
open val viewModel: ViewModel? = null
abstract val liveData: LiveData<List<D>>
abstract val viewRes: Int
abstract val recycler: RecyclerView
open val layoutManager: RecyclerView.LayoutManager get() = LinearLayoutManager(context)
open val alwaysRefresh = true
private var moreLoading = false
private var listener: Job? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(viewRes, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
liveData.observe(viewLifecycleOwner) {
onDataUpdated(it)
adapter.data = it.toMutableList()
adapter.notifyDataSetChanged()
}
recycler.layoutManager = layoutManager
(recycler.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
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(adapter.data.size)
}
}
}
}
if (listener == null) {
listener = lifecycleScope.launch(IO) {
EventBus.get().collect { event ->
if (event is Event.ListingsChanged) {
withContext(Main) {
swiper?.isRefreshing = true
fetch()
}
}
}
}
}
}
override fun onResume() {
super.onResume()
swiper?.setOnRefreshListener {
fetch()
}
}
open fun onDataFetched(data: List<DAO>) {}
open fun onDataUpdated(data: List<D>?) {}
private fun fetch(size: Int = 0) {
moreLoading = true
repository.fetch(size).untilNetwork(lifecycleScope, IO) { data, _, hasMore ->
lifecycleScope.launch(Main) {
onDataFetched(data)
if (hasMore) {
(repository.upstream as? HttpUpstream<*>)?.let { upstream ->
if (upstream.behavior == HttpUpstream.Behavior.Progressive) {
if (size == 0 || needsMoreOffscreenPages()) {
fetch(size + data.size)
} else {
moreLoading = false
}
}
}
}
(repository.upstream as? HttpUpstream<*>)?.let { upstream ->
when (upstream.behavior) {
HttpUpstream.Behavior.Progressive -> if (!hasMore || !moreLoading) swiper?.isRefreshing = false
HttpUpstream.Behavior.AtOnce -> if (!hasMore) swiper?.isRefreshing = false
HttpUpstream.Behavior.Single -> if (!hasMore) swiper?.isRefreshing = false
}
}
}
}
}
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
}
}
abstract class PagedOtterFragment<DAO : Any, D : Any, A : PagingDataAdapter<D, *>> : Fragment() {
abstract val repository: Repository<DAO>
abstract val adapter: A
open val viewModel: ViewModel? = null
abstract val liveData: LiveData<PagingData<D>>
abstract val viewRes: Int
abstract val recycler: RecyclerView
open val layoutManager: RecyclerView.LayoutManager get() = LinearLayoutManager(context)
open val alwaysRefresh = true
private var moreLoading = false
private var listener: Job? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(viewRes, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
liveData.observe(viewLifecycleOwner) {
viewLifecycleOwner.lifecycleScope.launch(IO) {
adapter.submitData(it)
}
}
recycler.layoutManager = layoutManager
recycler.adapter = adapter
if (listener == null) {
listener = lifecycleScope.launch(IO) {
EventBus.get().collect { event ->
if (event is Event.ListingsChanged) {
withContext(Main) {
swiper?.isRefreshing = true
fetch()
}
}
}
}
}
}
open fun onDataFetched(data: List<DAO>) {}
private fun fetch(size: Int = 0) {
moreLoading = true
repository.fetch(size).untilNetwork(lifecycleScope, IO) { data, _, hasMore ->
lifecycleScope.launch(Main) {
onDataFetched(data)
(repository.upstream as? HttpUpstream<*>)?.let { upstream ->
when (upstream.behavior) {
HttpUpstream.Behavior.Progressive -> if (!hasMore || !moreLoading) swiper?.isRefreshing = false
HttpUpstream.Behavior.AtOnce -> if (!hasMore) swiper?.isRefreshing = false
HttpUpstream.Behavior.Single -> if (!hasMore) swiper?.isRefreshing = false
}
}
}
}
}
}