diff --git a/app/src/main/java/com/aurora/store/Aliases.kt b/app/src/main/java/com/aurora/store/Aliases.kt new file mode 100644 index 000000000..a99365f26 --- /dev/null +++ b/app/src/main/java/com/aurora/store/Aliases.kt @@ -0,0 +1,9 @@ +package com.aurora.store + +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.contracts.StreamContract +import com.aurora.gplayapi.helpers.contracts.TopChartsContract + +typealias TopChartStash = Map> +typealias HomeStash = MutableMap \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/model/State.kt b/app/src/main/java/com/aurora/store/data/model/State.kt index d56183e9e..219d97342 100644 --- a/app/src/main/java/com/aurora/store/data/model/State.kt +++ b/app/src/main/java/com/aurora/store/data/model/State.kt @@ -21,6 +21,10 @@ package com.aurora.store.data.model sealed class ViewState { + inline fun ViewState.getDataAs(): T { + return (this as? Success<*>)?.data as T + } + data object Loading : ViewState() data object Empty : ViewState() data class Error(val error: String?) : ViewState() @@ -34,7 +38,7 @@ sealed class AuthState { data object SignedIn : AuthState() data object SignedOut : AuthState() data object Valid : AuthState() - data object Fetching: AuthState() - data object Verifying: AuthState() + data object Fetching : AuthState() + data object Verifying : AuthState() data class Failed(val status: String) : AuthState() } diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt index 743c8e875..98952c98d 100644 --- a/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt @@ -28,8 +28,10 @@ import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.helpers.contracts.StreamContract.Category import com.aurora.gplayapi.helpers.contracts.StreamContract.Type +import com.aurora.store.HomeStash import com.aurora.store.R import com.aurora.store.data.model.ViewState +import com.aurora.store.data.ViewState.Loading.getDataAs import com.aurora.store.databinding.FragmentForYouBinding import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener import com.aurora.store.view.epoxy.controller.GenericCarouselController @@ -43,10 +45,10 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you), private var _binding: FragmentForYouBinding? = null private val binding get() = _binding!! - private var category: Category = Category.APPLICATION private val viewModel: StreamViewModel by activityViewModels() - private lateinit var streamBundle: StreamBundle + private var category: Category = Category.APPLICATION + private var streamBundle: StreamBundle? = StreamBundle() companion object { @JvmStatic @@ -84,6 +86,13 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you), } binding.recycler.setController(genericCarouselController) + binding.recycler.addOnScrollListener( + object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + viewModel.observe(category, Type.HOME) + } + } + ) viewModel.liveData.observe(viewLifecycleOwner) { when (it) { @@ -92,17 +101,9 @@ class ForYouFragment : BaseFragment(R.layout.fragment_for_you), } is ViewState.Success<*> -> { - if (!::streamBundle.isInitialized) { - binding.recycler.addOnScrollListener( - object : EndlessRecyclerOnScrollListener() { - override fun onLoadMore(currentPage: Int) { - viewModel.observe(category, Type.HOME) - } - } - ) - } + val stash = it.getDataAs() + streamBundle = stash[category] - streamBundle = it.data as StreamBundle genericCarouselController.setData(streamBundle) } diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/TopChartFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/TopChartFragment.kt index 9aa33234a..c627417c4 100644 --- a/app/src/main/java/com/aurora/store/view/ui/commons/TopChartFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/commons/TopChartFragment.kt @@ -21,12 +21,15 @@ package com.aurora.store.view.ui.commons import android.os.Bundle import android.view.View -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import com.aurora.Constants import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Chart import com.aurora.gplayapi.helpers.contracts.TopChartsContract.Type import com.aurora.store.R +import com.aurora.store.TopChartStash +import com.aurora.store.data.ViewState +import com.aurora.store.data.ViewState.Empty.getDataAs import com.aurora.store.databinding.FragmentTopContainerBinding import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener import com.aurora.store.view.epoxy.views.AppProgressViewModel_ @@ -41,7 +44,9 @@ class TopChartFragment : BaseFragment(R.layout.fragment_top_container) { private var _binding: FragmentTopContainerBinding? = null private val binding get() = _binding!! - private val viewModel: TopChartViewModel by viewModels() + private val viewModel: TopChartViewModel by activityViewModels() + + private var streamCluster: StreamCluster? = StreamCluster() companion object { @JvmStatic @@ -59,42 +64,52 @@ class TopChartFragment : BaseFragment(R.layout.fragment_top_container) { super.onViewCreated(view, savedInstanceState) _binding = FragmentTopContainerBinding.bind(view) - var chartType = 0 - var chartCategory = 0 + var type = 0 + var category = 0 val bundle = arguments if (bundle != null) { - chartType = bundle.getInt(Constants.TOP_CHART_TYPE, 0) - chartCategory = bundle.getInt(Constants.TOP_CHART_CATEGORY, 0) + type = bundle.getInt(Constants.TOP_CHART_TYPE, 0) + category = bundle.getInt(Constants.TOP_CHART_CATEGORY, 0) } - when (chartType) { - 0 -> when (chartCategory) { - 0 -> viewModel.getStreamCluster(Type.APPLICATION, Chart.TOP_SELLING_FREE) - 1 -> viewModel.getStreamCluster(Type.APPLICATION, Chart.TOP_GROSSING) - 2 -> viewModel.getStreamCluster(Type.APPLICATION, Chart.MOVERS_SHAKERS) - 3 -> viewModel.getStreamCluster(Type.APPLICATION, Chart.TOP_SELLING_PAID) - } - - 1 -> when (chartCategory) { - 0 -> viewModel.getStreamCluster(Type.GAME, Chart.TOP_SELLING_FREE) - 1 -> viewModel.getStreamCluster(Type.GAME, Chart.TOP_GROSSING) - 2 -> viewModel.getStreamCluster(Type.GAME, Chart.MOVERS_SHAKERS) - 3 -> viewModel.getStreamCluster(Type.GAME, Chart.TOP_SELLING_PAID) - } + val chartType = when (type) { + 1 -> Type.GAME + else -> Type.APPLICATION } - viewModel.liveData.observe(viewLifecycleOwner) { - updateController(it) + val chartCategory = when (category) { + 1 -> Chart.TOP_GROSSING + 2 -> Chart.MOVERS_SHAKERS + 3 -> Chart.TOP_SELLING_PAID + else -> Chart.TOP_SELLING_FREE } binding.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() { override fun onLoadMore(currentPage: Int) { - viewModel.nextCluster() + viewModel.nextCluster(chartType, chartCategory) } }) updateController(null) + + viewModel.getStreamCluster(chartType, chartCategory) + viewModel.liveData.observe(viewLifecycleOwner) { + when (it) { + is ViewState.Loading, is ViewState.Error -> { + updateController(null) + } + + is ViewState.Success<*> -> { + val stash = it.getDataAs() + streamCluster = stash[chartType]?.get(chartCategory) + + updateController(streamCluster) + } + + else -> {} + } + } } override fun onDestroyView() { diff --git a/app/src/main/java/com/aurora/store/viewmodel/homestream/StreamViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/homestream/StreamViewModel.kt index 9ede9de7f..a5083028f 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/homestream/StreamViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/homestream/StreamViewModel.kt @@ -30,6 +30,7 @@ import com.aurora.gplayapi.helpers.StreamHelper import com.aurora.store.data.model.ViewState import com.aurora.gplayapi.helpers.contracts.StreamContract import com.aurora.gplayapi.helpers.web.WebStreamHelper +import com.aurora.store.HomeStash import com.aurora.store.data.network.HttpClient import com.aurora.store.data.providers.AuthProvider import com.aurora.store.util.Log @@ -55,7 +56,7 @@ class StreamViewModel @Inject constructor( val liveData: MutableLiveData = MutableLiveData() - private var streamBundleMap: MutableMap = mutableMapOf( + private var stash: HomeStash = mutableMapOf( StreamContract.Category.APPLICATION to StreamBundle(), StreamContract.Category.GAME to StreamBundle() ) @@ -77,7 +78,7 @@ class StreamViewModel @Inject constructor( viewModelScope.launch(Dispatchers.IO) { supervisorScope { if (targetBundle(category).streamClusters.isNotEmpty()) { - liveData.postValue(ViewState.Success(targetBundle(category))) + liveData.postValue(ViewState.Success(stash)) } try { @@ -100,7 +101,7 @@ class StreamViewModel @Inject constructor( } //Post updated to UI - liveData.postValue(ViewState.Success(targetBundle(category))) + liveData.postValue(ViewState.Success(stash)) } else { Log.i("End of Bundle") } @@ -119,7 +120,7 @@ class StreamViewModel @Inject constructor( val newCluster = contract().nextStreamCluster(streamCluster.clusterNextPageUrl) updateCluster(category, streamCluster.id, newCluster) - liveData.postValue(ViewState.Success(targetBundle(category))) + liveData.postValue(ViewState.Success(stash)) } else { Log.i("End of cluster") streamCluster.clusterNextPageUrl = String() @@ -143,8 +144,9 @@ class StreamViewModel @Inject constructor( } private fun targetBundle(category: StreamContract.Category): StreamBundle { - val streamBundle = streamBundleMap[category] + // stash is initialized with empty StreamBundle so this will never return null or throw an exception + val streamBundle = stash.getValue(category) - return streamBundle!! + return streamBundle } } diff --git a/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt index 2d099b6c1..1d84dbf16 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt @@ -28,6 +28,8 @@ import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.helpers.TopChartsHelper import com.aurora.gplayapi.helpers.contracts.TopChartsContract import com.aurora.gplayapi.helpers.web.WebTopChartsHelper +import com.aurora.store.TopChartStash +import com.aurora.store.data.ViewState import com.aurora.store.data.network.HttpClient import com.aurora.store.data.providers.AuthProvider import dagger.hilt.android.lifecycle.HiltViewModel @@ -50,11 +52,22 @@ class TopChartViewModel @Inject constructor( private val webTopChartsHelper: TopChartsContract = WebTopChartsHelper() .using(HttpClient.getPreferredClient(context)) - val liveData: MutableLiveData = MutableLiveData() - var streamCluster: StreamCluster = StreamCluster() + private val dummyChart = mapOf( + TopChartsContract.Chart.TOP_GROSSING to StreamCluster(), + TopChartsContract.Chart.TOP_SELLING_FREE to StreamCluster(), + TopChartsContract.Chart.TOP_SELLING_PAID to StreamCluster(), + TopChartsContract.Chart.MOVERS_SHAKERS to StreamCluster(), + ) + + private var stash: TopChartStash = mapOf( + TopChartsContract.Type.APPLICATION to dummyChart, + TopChartsContract.Type.GAME to dummyChart + ) + + val liveData: MutableLiveData = MutableLiveData() private fun contract(): TopChartsContract { - return if(authProvider.isAnonymous){ + return if (authProvider.isAnonymous) { webTopChartsHelper } else { topChartsHelper @@ -63,34 +76,56 @@ class TopChartViewModel @Inject constructor( fun getStreamCluster(type: TopChartsContract.Type, chart: TopChartsContract.Chart) { viewModelScope.launch(Dispatchers.IO) { + if (targetCluster(type, chart).clusterAppList.isNotEmpty()) { + liveData.postValue(ViewState.Success(stash)) + } + try { - streamCluster = contract().getCluster(type.value, chart.value) - liveData.postValue(streamCluster) + val cluster = contract().getCluster(type.value, chart.value) + updateCluster(type, chart, cluster) + liveData.postValue(ViewState.Success(stash)) } catch (_: Exception) { } } } - fun nextCluster() { + fun nextCluster(type: TopChartsContract.Type, chart: TopChartsContract.Chart) { viewModelScope.launch(Dispatchers.IO) { supervisorScope { try { - if (streamCluster.hasNext()) { + val target = targetCluster(type, chart) + if (target.hasNext()) { val newCluster = topChartsHelper.getNextStreamCluster( - streamCluster.clusterNextPageUrl + target.clusterNextPageUrl ) - streamCluster.apply { - clusterAppList.addAll(newCluster.clusterAppList) - clusterNextPageUrl = newCluster.clusterNextPageUrl - } + updateCluster(type, chart, newCluster) - liveData.postValue(streamCluster) - } else { + liveData.postValue(ViewState.Success(stash)) } } catch (_: Exception) { } } } } + + private fun updateCluster( + type: TopChartsContract.Type, + chart: TopChartsContract.Chart, + newCluster: StreamCluster + ) { + targetCluster(type, chart).apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + } + + private fun targetCluster( + type: TopChartsContract.Type, + chart: TopChartsContract.Chart + ): StreamCluster { + // Stash is initialized with empty clusters so this will never return null or throw an exception + val cluster = stash.getValue(type).getValue(chart) + return cluster + } }