Add stashing to SubCategory Streams

This commit is contained in:
Rahul Patel
2024-06-29 04:03:01 +05:30
committed by Aayush Gupta
parent 6f425fe641
commit 8a12ffcd96
4 changed files with 65 additions and 83 deletions

View File

@@ -21,15 +21,16 @@ package com.aurora.store.view.ui.commons
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.contracts.StreamContract
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.model.ViewState import com.aurora.store.data.model.ViewState
import com.aurora.store.data.model.ViewState.Loading.getDataAs
import com.aurora.store.databinding.ActivityGenericRecyclerBinding import com.aurora.store.databinding.ActivityGenericRecyclerBinding
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
import com.aurora.store.view.epoxy.controller.CategoryCarouselController import com.aurora.store.view.epoxy.controller.CategoryCarouselController
@@ -46,16 +47,20 @@ class CategoryBrowseFragment : BaseFragment(R.layout.activity_generic_recycler),
get() = _binding!! get() = _binding!!
private val args: CategoryBrowseFragmentArgs by navArgs() private val args: CategoryBrowseFragmentArgs by navArgs()
private val viewModel: SubCategoryClusterViewModel by viewModels() private val viewModel: SubCategoryClusterViewModel by activityViewModels()
private lateinit var genericCarouselController: GenericCarouselController private lateinit var category: StreamContract.Category
private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener private var streamBundle: StreamBundle? = StreamBundle()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = ActivityGenericRecyclerBinding.bind(view) _binding = ActivityGenericRecyclerBinding.bind(view)
genericCarouselController = CategoryCarouselController(this)
val rawCategory = args.browseUrl.split("/").last()
category = StreamContract.Category.NONE.apply { value = rawCategory }
val genericCarouselController = CategoryCarouselController(this)
// Toolbar // Toolbar
binding.layoutToolbarAction.apply { binding.layoutToolbarAction.apply {
@@ -65,39 +70,30 @@ class CategoryBrowseFragment : BaseFragment(R.layout.activity_generic_recycler),
// RecyclerView // RecyclerView
binding.recycler.setController(genericCarouselController) binding.recycler.setController(genericCarouselController)
binding.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() {
override fun onLoadMore(currentPage: Int) {
viewModel.observe(category)
}
})
viewModel.liveData.observe(viewLifecycleOwner) { viewModel.liveData.observe(viewLifecycleOwner) {
when (it) { when (it) {
is ViewState.Empty -> {
}
is ViewState.Loading -> { is ViewState.Loading -> {
updateController(null) genericCarouselController.setData(null)
}
is ViewState.Error -> {
} }
is ViewState.Success<*> -> { is ViewState.Success<*> -> {
updateController(it.data as StreamBundle) val stash = it.getDataAs<Map<String, StreamBundle>>()
streamBundle = stash[category.value]
genericCarouselController.setData(streamBundle)
} }
else -> {} else -> {}
} }
} }
endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { viewModel.observe(category)
override fun onLoadMore(currentPage: Int) {
viewModel.observe()
}
}
endlessRecyclerOnScrollListener.disable()
binding.recycler.addOnScrollListener(endlessRecyclerOnScrollListener)
viewModel.observeCategory(args.browseUrl)
updateController(null)
} }
override fun onDestroyView() { override fun onDestroyView() {
@@ -105,25 +101,12 @@ class CategoryBrowseFragment : BaseFragment(R.layout.activity_generic_recycler),
_binding = null _binding = null
} }
private fun updateController(streamBundle: StreamBundle?) {
if (streamBundle != null) endlessRecyclerOnScrollListener.enable()
genericCarouselController.setData(streamBundle)
}
override fun onHeaderClicked(streamCluster: StreamCluster) { override fun onHeaderClicked(streamCluster: StreamCluster) {
if (streamCluster.clusterBrowseUrl.isNotEmpty())
openStreamBrowseFragment(streamCluster.clusterBrowseUrl)
else
Toast.makeText(
requireContext(),
getString(R.string.toast_page_unavailable),
Toast.LENGTH_SHORT
)
.show()
} }
override fun onClusterScrolled(streamCluster: StreamCluster) { override fun onClusterScrolled(streamCluster: StreamCluster) {
viewModel.observeCluster(streamCluster) viewModel.observeCluster(category, streamCluster)
} }
override fun onAppClick(app: App) { override fun onAppClick(app: App) {

View File

@@ -118,8 +118,7 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you),
} }
override fun onHeaderClicked(streamCluster: StreamCluster) { override fun onHeaderClicked(streamCluster: StreamCluster) {
if (streamCluster.clusterBrowseUrl.isNotEmpty())
openStreamBrowseFragment(streamCluster.clusterBrowseUrl, streamCluster.clusterTitle)
} }
override fun onClusterScrolled(streamCluster: StreamCluster) { override fun onClusterScrolled(streamCluster: StreamCluster) {

View File

@@ -77,25 +77,26 @@ class StreamViewModel @Inject constructor(
fun observe(category: StreamContract.Category, type: StreamContract.Type) { fun observe(category: StreamContract.Category, type: StreamContract.Type) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
if (targetBundle(category).streamClusters.isNotEmpty()) { val bundle = targetBundle(category)
if (bundle.streamClusters.isNotEmpty()) {
liveData.postValue(ViewState.Success(stash)) liveData.postValue(ViewState.Success(stash))
} }
try { try {
if (!targetBundle(category).hasCluster() || targetBundle(category).hasNext()) { if (!bundle.hasCluster() || bundle.hasNext()) {
//Fetch new stream bundle //Fetch new stream bundle
val newBundle = if (targetBundle(category).streamClusters.isEmpty()) { val newBundle = if (bundle.streamClusters.isEmpty()) {
contract().fetch(type, category) contract().fetch(type, category)
} else { } else {
contract().nextStreamBundle( contract().nextStreamBundle(
category, category,
targetBundle((category)).streamNextPageUrl bundle.streamNextPageUrl
) )
} }
//Update old bundle //Update old bundle
targetBundle(category).apply { bundle.apply {
streamClusters.putAll(newBundle.streamClusters) streamClusters.putAll(newBundle.streamClusters)
streamNextPageUrl = newBundle.streamNextPageUrl streamNextPageUrl = newBundle.streamNextPageUrl
} }

View File

@@ -31,7 +31,6 @@ import com.aurora.store.data.model.ViewState
import com.aurora.gplayapi.helpers.contracts.StreamContract import com.aurora.gplayapi.helpers.contracts.StreamContract
import com.aurora.gplayapi.helpers.web.WebStreamHelper import com.aurora.gplayapi.helpers.web.WebStreamHelper
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log import com.aurora.store.util.Log
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@@ -50,76 +49,64 @@ class SubCategoryClusterViewModel @Inject constructor(
.using(HttpClient.getPreferredClient(context)) .using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<ViewState> = MutableLiveData() val liveData: MutableLiveData<ViewState> = MutableLiveData()
var streamBundle: StreamBundle = StreamBundle()
private lateinit var homeUrl: String private var stash: MutableMap<String, StreamBundle> = mutableMapOf()
private lateinit var type: StreamContract.Type
private lateinit var category: StreamContract.Category
init {
liveData.postValue(ViewState.Loading)
}
private fun getCategoryStreamBundle( private fun getCategoryStreamBundle(
category: StreamContract.Category,
nextPageUrl: String nextPageUrl: String
): StreamBundle { ): StreamBundle {
return if (streamBundle.streamClusters.isEmpty()) return if (targetBundle(category).streamClusters.isEmpty())
contract.fetch(type, category) contract.fetch(StreamContract.Type.HOME, category)
else else
contract.nextStreamBundle(category, nextPageUrl) contract.nextStreamBundle(category, nextPageUrl)
} }
fun observeCategory(homeUrl: String) { fun observe(category: StreamContract.Category) {
this.homeUrl = homeUrl liveData.postValue(ViewState.Loading)
val rawCategory = homeUrl.split("/").last()
type = StreamContract.Type.HOME
category = StreamContract.Category.NONE.apply {
value = rawCategory
}
observe()
}
fun observe() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
val bundle = targetBundle(category)
if (bundle.streamClusters.isNotEmpty()) {
liveData.postValue(ViewState.Success(stash))
}
try { try {
if (!streamBundle.hasCluster() || streamBundle.hasNext()) { if (!bundle.hasCluster() || bundle.hasNext()) {
//Fetch new stream bundle //Fetch new stream bundle
val newBundle = getCategoryStreamBundle( val newBundle = getCategoryStreamBundle(
streamBundle.streamNextPageUrl category,
bundle.streamNextPageUrl
) )
//Update old bundle //Update old bundle
streamBundle.apply { bundle.apply {
streamClusters.putAll(newBundle.streamClusters) streamClusters.putAll(newBundle.streamClusters)
streamNextPageUrl = newBundle.streamNextPageUrl streamNextPageUrl = newBundle.streamNextPageUrl
} }
//Post updated to UI //Post updated to UI
liveData.postValue(ViewState.Success(streamBundle)) liveData.postValue(ViewState.Success(stash))
} else { } else {
Log.i("End of Bundle") Log.i("End of Bundle")
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace()
liveData.postValue(ViewState.Error(e.message)) liveData.postValue(ViewState.Error(e.message))
} }
} }
} }
} }
fun observeCluster(streamCluster: StreamCluster) { fun observeCluster(category: StreamContract.Category, streamCluster: StreamCluster) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
if (streamCluster.hasNext()) { if (streamCluster.hasNext()) {
val newCluster = val newCluster =
contract.nextStreamCluster(streamCluster.clusterNextPageUrl) contract.nextStreamCluster(streamCluster.clusterNextPageUrl)
updateCluster(newCluster) updateCluster(category, streamCluster.id, newCluster)
liveData.postValue(ViewState.Success(streamBundle)) liveData.postValue(ViewState.Success(stash))
} else { } else {
Log.i("End of cluster") Log.i("End of cluster")
streamCluster.clusterNextPageUrl = String() streamCluster.clusterNextPageUrl = String()
@@ -131,10 +118,22 @@ class SubCategoryClusterViewModel @Inject constructor(
} }
} }
private fun updateCluster(newCluster: StreamCluster) { private fun updateCluster(
streamBundle.streamClusters[newCluster.id]?.apply { category: StreamContract.Category,
clusterID: Int,
newCluster: StreamCluster
) {
targetBundle(category).streamClusters[clusterID]?.apply {
clusterAppList.addAll(newCluster.clusterAppList) clusterAppList.addAll(newCluster.clusterAppList)
clusterNextPageUrl = newCluster.clusterNextPageUrl clusterNextPageUrl = newCluster.clusterNextPageUrl
} }
} }
private fun targetBundle(category: StreamContract.Category): StreamBundle {
val streamBundle = stash.getOrPut(category.value) {
StreamBundle()
}
return streamBundle
}
} }