diff --git a/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt index 9f62d75..c094935 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt @@ -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) diff --git a/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt index d2bf20d..ba7e644 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/SearchActivity.kt @@ -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) diff --git a/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt index e756373..636f31a 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/AlbumsFragment.kt @@ -33,8 +33,9 @@ import kotlinx.coroutines.withContext class AlbumsFragment : FunkwhaleFragment() { 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 = "" diff --git a/app/src/main/java/com/github/apognu/otter/fragments/ArtistsFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/ArtistsFragment.kt index a7b0b56..0bd9a27 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/ArtistsFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/ArtistsFragment.kt @@ -21,6 +21,7 @@ import kotlinx.android.synthetic.main.fragment_artists.* class ArtistsFragment : FunkwhaleFragment() { 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) { diff --git a/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt index 580241f..581a4a9 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/FavoritesFragment.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.withContext class FavoritesFragment : FunkwhaleFragment() { 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) diff --git a/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt index 9ed73c8..12f2b9b 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt @@ -28,14 +28,19 @@ abstract class FunkwhaleAdapter : RecyclerView. } abstract class FunkwhaleFragment> : 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 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> : 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> : 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> : 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> : 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> : 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) { diff --git a/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt index 6a6b978..d4584a5 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt @@ -41,18 +41,19 @@ class HttpUpstream>(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)) } } ) diff --git a/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt b/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt index 22e309e..9ce30a7 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/Repository.kt @@ -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 { fun fetch(size: Int = 0): Flow> @@ -21,7 +23,7 @@ abstract class Repository> { Network(0b10) } - data class Response(val origin: Origin, val data: List, val hasMore: Boolean) + data class Response(val origin: Origin, val data: List, val page: Int, val hasMore: Boolean) abstract val context: Context? abstract val cacheId: String? @@ -39,7 +41,7 @@ abstract class Repository> { 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> { 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) = data diff --git a/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt b/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt index a707b94..64a1fa7 100644 --- a/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt +++ b/app/src/main/java/com/github/apognu/otter/utils/Extensions.kt @@ -17,10 +17,10 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlin.coroutines.CoroutineContext -inline fun Flow>.untilNetwork(scope: CoroutineScope, context: CoroutineContext = Main, crossinline callback: (data: List, isCache: Boolean, hasMore: Boolean) -> Unit) { +inline fun Flow>.untilNetwork(scope: CoroutineScope, context: CoroutineContext = Main, crossinline callback: (data: List, 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) } } } diff --git a/app/src/main/res/xml/security.xml b/app/src/main/res/xml/security.xml new file mode 100644 index 0000000..ed8892b --- /dev/null +++ b/app/src/main/res/xml/security.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file