Skip to content

Commit

Permalink
Merge pull request #20125 from wordpress-mobile/issue/19952-update-tr…
Browse files Browse the repository at this point in the history
…affic-tab-by-granularity

Issue/19952 update traffic tab by granularity
  • Loading branch information
ravishanker authored Feb 12, 2024
2 parents 7cb41dd + 70e624a commit 819fa49
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.T
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.TotalFollowersUseCase.TotalFollowersUseCaseFactory
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.TotalLikesUseCase.TotalLikesUseCaseFactory
import org.wordpress.android.ui.stats.refresh.lists.sections.insights.usecases.ViewsAndVisitorsUseCase.ViewsAndVisitorsUseCaseFactory
import org.wordpress.android.ui.stats.refresh.utils.SelectedTrafficGranularityManager
import org.wordpress.android.ui.stats.refresh.utils.StatsSiteProvider
import org.wordpress.android.util.config.StatsTrafficTabFeatureConfig
import javax.inject.Named
import javax.inject.Singleton

Expand Down Expand Up @@ -271,7 +273,6 @@ class StatsModule {
* @param useCasesFactories build the use cases for the DAYS granularity
*/
@Provides
@Singleton
@Named(TRAFFIC_USE_CASE)
@Suppress("LongParameterList")
fun provideTrafficUseCase(
Expand All @@ -280,13 +281,16 @@ class StatsModule {
@Named(UI_THREAD) mainDispatcher: CoroutineDispatcher,
statsSiteProvider: StatsSiteProvider,
@Named(GRANULAR_USE_CASE_FACTORIES) useCasesFactories: List<@JvmSuppressWildcards GranularUseCaseFactory>,
selectedTrafficGranularityManager: SelectedTrafficGranularityManager,
uiModelMapper: UiModelMapper
): BaseListUseCase {
return BaseListUseCase(
bgDispatcher,
mainDispatcher,
statsSiteProvider,
useCasesFactories.map { it.build(DAYS, BLOCK) },
useCasesFactories.map {
it.build(selectedTrafficGranularityManager.getSelectedTrafficGranularity(), BLOCK)
},
{ statsStore.getTimeStatsTypes(it) },
uiModelMapper::mapTimeStats
)
Expand Down Expand Up @@ -408,16 +412,20 @@ class StatsModule {
@Named(DAY_STATS_USE_CASE) dayStatsUseCase: BaseListUseCase,
@Named(WEEK_STATS_USE_CASE) weekStatsUseCase: BaseListUseCase,
@Named(MONTH_STATS_USE_CASE) monthStatsUseCase: BaseListUseCase,
@Named(YEAR_STATS_USE_CASE) yearStatsUseCase: BaseListUseCase
@Named(YEAR_STATS_USE_CASE) yearStatsUseCase: BaseListUseCase,
trafficTabFeatureConfig: StatsTrafficTabFeatureConfig
): Map<StatsSection, BaseListUseCase> {
return mapOf(
StatsSection.INSIGHTS to insightsUseCase,
StatsSection.TRAFFIC to trafficUseCase,
StatsSection.DAYS to dayStatsUseCase,
StatsSection.WEEKS to weekStatsUseCase,
StatsSection.MONTHS to monthStatsUseCase,
StatsSection.YEARS to yearStatsUseCase
)
return if (trafficTabFeatureConfig.isEnabled()) {
mapOf(StatsSection.TRAFFIC to trafficUseCase, StatsSection.INSIGHTS to insightsUseCase)
} else {
mapOf(
StatsSection.INSIGHTS to insightsUseCase,
StatsSection.DAYS to dayStatsUseCase,
StatsSection.WEEKS to weekStatsUseCase,
StatsSection.MONTHS to monthStatsUseCase,
StatsSection.YEARS to yearStatsUseCase
)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import org.wordpress.android.ui.utils.UiString.UiStringRes
import org.wordpress.android.util.PackageUtils
import org.wordpress.android.util.combineMap
import org.wordpress.android.util.distinct
import org.wordpress.android.util.mapSafe
import org.wordpress.android.util.mapAsync
import org.wordpress.android.util.mapSafe
import org.wordpress.android.util.mergeAsyncNotNull
import org.wordpress.android.util.mergeNotNull
import org.wordpress.android.viewmodel.Event
Expand Down Expand Up @@ -147,4 +147,13 @@ class BaseListUseCase(
fun onListSelected() {
mutableListSelected.call()
}

fun clone(newUseCases: List<BaseStatsUseCase<*, *>>) = BaseListUseCase(
bgDispatcher,
mainDispatcher,
statsSiteProvider,
newUseCases,
getStatsTypes,
mapUiModel
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,10 @@ class StatsListFragment : ViewPagerFragment(R.layout.stats_list_fragment) {

dateSelector.granularitySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
selectedTrafficGranularityManager.setSelectedTrafficGranularity(StatsGranularity.entries[position])
with(StatsGranularity.entries[position]) {
selectedTrafficGranularityManager.setSelectedTrafficGranularity(this)
(viewModel as TrafficListViewModel).onGranularitySelected(this)
}
}

@Suppress("EmptyFunctionBlock")
Expand Down Expand Up @@ -235,8 +238,15 @@ class StatsListFragment : ViewPagerFragment(R.layout.stats_list_fragment) {
}

private fun StatsListFragmentBinding.setupObservers(activity: FragmentActivity) {
viewModel.uiModel.observe(viewLifecycleOwner) {
showUiModel(it)
viewModel.uiSourceRemoved.observe(viewLifecycleOwner) {
viewModel.uiModel.removeObservers(viewLifecycleOwner)
viewModel.navigationTarget.removeObservers(viewLifecycleOwner)
viewModel.listSelected.removeObservers(viewLifecycleOwner)
viewModel.scrollToNewCard.removeObservers(viewLifecycleOwner)
}

viewModel.uiSourceAdded.observe(viewLifecycleOwner) {
observeUiChanges(activity)
}

viewModel.dateSelectorData.observe(viewLifecycleOwner) { dateSelectorUiModel ->
Expand All @@ -248,20 +258,12 @@ class StatsListFragment : ViewPagerFragment(R.layout.stats_list_fragment) {
}
}

viewModel.navigationTarget.observeEvent(viewLifecycleOwner) { target ->
navigator.navigate(activity, target)
}

viewModel.selectedDate?.observe(viewLifecycleOwner) { event ->
if (event != null) {
viewModel.onDateChanged(event.selectedGranularity)
}
}

viewModel.listSelected.observe(viewLifecycleOwner) {
viewModel.onListSelected()
}

viewModel.typesChanged.observeEvent(viewLifecycleOwner) {
viewModel.onTypesChanged()
}
Expand All @@ -271,6 +273,16 @@ class StatsListFragment : ViewPagerFragment(R.layout.stats_list_fragment) {
recyclerView.smoothScrollToPosition(adapter.positionOf(statsType))
}
}
}

private fun StatsListFragmentBinding.observeUiChanges(activity: FragmentActivity) {
viewModel.uiModel.observe(viewLifecycleOwner) {
showUiModel(it)
}

viewModel.navigationTarget.observeEvent(viewLifecycleOwner) { target -> navigator.navigate(activity, target) }

viewModel.listSelected.observe(viewLifecycleOwner) { viewModel.onListSelected() }

viewModel.scrollToNewCard.observeEvent(viewLifecycleOwner) {
(recyclerView.adapter as? StatsBlockAdapter)?.let { adapter ->
Expand Down Expand Up @@ -331,6 +343,7 @@ class StatsListFragment : ViewPagerFragment(R.layout.stats_list_fragment) {
val layoutManager = recyclerView.layoutManager
val recyclerViewState = layoutManager?.onSaveInstanceState()
adapter.update(statsState)
recyclerView.scrollToPosition(0)
layoutManager?.onRestoreInstanceState(recyclerViewState)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import kotlinx.coroutines.delay
import org.wordpress.android.R
import org.wordpress.android.analytics.AnalyticsTracker.Stat
import org.wordpress.android.fluxc.network.utils.StatsGranularity
import org.wordpress.android.fluxc.store.StatsStore
import org.wordpress.android.modules.UI_THREAD
import org.wordpress.android.ui.stats.refresh.DAY_STATS_USE_CASE
import org.wordpress.android.ui.stats.refresh.GRANULAR_USE_CASE_FACTORIES
import org.wordpress.android.ui.stats.refresh.INSIGHTS_USE_CASE
import org.wordpress.android.ui.stats.refresh.MONTH_STATS_USE_CASE
import org.wordpress.android.ui.stats.refresh.NavigationTarget
Expand All @@ -25,9 +27,12 @@ import org.wordpress.android.ui.stats.refresh.TRAFFIC_USE_CASE
import org.wordpress.android.ui.stats.refresh.VIEWS_AND_VISITORS_USE_CASE
import org.wordpress.android.ui.stats.refresh.WEEK_STATS_USE_CASE
import org.wordpress.android.ui.stats.refresh.YEAR_STATS_USE_CASE
import org.wordpress.android.ui.stats.refresh.lists.sections.BaseStatsUseCase
import org.wordpress.android.ui.stats.refresh.lists.sections.granular.GranularUseCaseFactory
import org.wordpress.android.ui.stats.refresh.utils.ActionCardHandler
import org.wordpress.android.ui.stats.refresh.utils.ItemPopupMenuHandler
import org.wordpress.android.ui.stats.refresh.utils.NewsCardHandler
import org.wordpress.android.ui.stats.refresh.utils.SelectedTrafficGranularityManager
import org.wordpress.android.ui.stats.refresh.utils.StatsDateSelector
import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper
import org.wordpress.android.util.mapNullable
Expand All @@ -36,16 +41,17 @@ import org.wordpress.android.util.mergeNotNull
import org.wordpress.android.util.throttle
import org.wordpress.android.viewmodel.Event
import org.wordpress.android.viewmodel.ScopedViewModel
import org.wordpress.android.viewmodel.SingleLiveEvent
import javax.inject.Inject
import javax.inject.Named

const val SCROLL_EVENT_DELAY = 2000L

abstract class StatsListViewModel(
defaultDispatcher: CoroutineDispatcher,
private val statsUseCase: BaseListUseCase,
protected var statsUseCase: BaseListUseCase,
private val analyticsTracker: AnalyticsTrackerWrapper,
protected val dateSelector: StatsDateSelector?,
protected var dateSelector: StatsDateSelector?,
popupMenuHandler: ItemPopupMenuHandler? = null,
private val newsCardHandler: NewsCardHandler? = null,
actionCardHandler: ActionCardHandler? = null
Expand All @@ -71,15 +77,17 @@ abstract class StatsListViewModel(
val selectedDate = dateSelector?.selectedDate

private val mutableNavigationTarget = MutableLiveData<Event<NavigationTarget>>()
val navigationTarget: LiveData<Event<NavigationTarget>> = mergeNotNull(
statsUseCase.navigationTarget, mutableNavigationTarget
)
lateinit var navigationTarget: LiveData<Event<NavigationTarget>>

val listSelected = statsUseCase.listSelected
lateinit var listSelected: LiveData<Unit?>

val uiModel: LiveData<UiModel?> by lazy {
statsUseCase.data.throttle(viewModelScope, distinct = true)
}
private val mutableUiSourceAdded = SingleLiveEvent<Unit?>()
val uiSourceAdded: LiveData<Unit?> = mutableUiSourceAdded

protected val mutableUiSourceRemoved = SingleLiveEvent<Unit?>()
val uiSourceRemoved: LiveData<Unit?> = mutableUiSourceRemoved

lateinit var uiModel: LiveData<UiModel?>

val dateSelectorData: LiveData<DateSelectorUiModel> = dateSelector?.dateSelectorData?.mapNullable {
it ?: DateSelectorUiModel(false)
Expand All @@ -93,7 +101,7 @@ abstract class StatsListViewModel(

val scrollTo = newsCardHandler?.scrollTo

val scrollToNewCard = statsUseCase.scrollTo
lateinit var scrollToNewCard: LiveData<Event<StatsStore.StatsType>>

override fun onCleared() {
statsUseCase.onCleared()
Expand Down Expand Up @@ -150,6 +158,7 @@ abstract class StatsListViewModel(
fun start() {
if (!isInitialized) {
isInitialized = true
setUiLiveData()
launch {
statsUseCase.loadData()
dateSelector?.updateDateSelector()
Expand All @@ -158,6 +167,14 @@ abstract class StatsListViewModel(
dateSelector?.updateDateSelector()
}

protected fun setUiLiveData() {
uiModel = statsUseCase.data.throttle(viewModelScope, distinct = true)
listSelected = statsUseCase.listSelected
navigationTarget = mergeNotNull(statsUseCase.navigationTarget, mutableNavigationTarget)
scrollToNewCard = statsUseCase.scrollTo
mutableUiSourceAdded.call()
}

sealed class UiModel {
data class Success(val data: List<StatsBlock>) : UiModel()
data class Error(val message: Int = R.string.stats_loading_error) : UiModel()
Expand Down Expand Up @@ -196,15 +213,44 @@ class InsightsListViewModel

class TrafficListViewModel @Inject constructor(
@Named(UI_THREAD) mainDispatcher: CoroutineDispatcher,
@Named(TRAFFIC_USE_CASE) statsUseCase: BaseListUseCase,
@Named(TRAFFIC_USE_CASE) private val trafficStatsUseCase: BaseListUseCase,
analyticsTracker: AnalyticsTrackerWrapper,
dateSelectorFactory: StatsDateSelector.Factory
dateSelectorFactory: StatsDateSelector.Factory,
@Named(GRANULAR_USE_CASE_FACTORIES)
private val useCasesFactories: List<@JvmSuppressWildcards GranularUseCaseFactory>,
private val selectedTrafficGranularityManager: SelectedTrafficGranularityManager,
) : StatsListViewModel(
mainDispatcher,
statsUseCase,
trafficStatsUseCase,
analyticsTracker,
dateSelectorFactory.build(StatsGranularity.DAYS, isGranularitySpinnerVisible = true)
)
dateSelectorFactory.build(
selectedTrafficGranularityManager.getSelectedTrafficGranularity(),
isGranularitySpinnerVisible = true
)
) {
fun onGranularitySelected(statsGranularity: StatsGranularity) {
if (dateSelector?.statsGranularity != statsGranularity) {
// Remove observers from the UI before changing the statsUseCase. This prevents removed use cases from
// affecting the UI.
mutableUiSourceRemoved.call()

dateSelector?.statsGranularity = statsGranularity
val newUseCases = useCasesFactories.map {
it.build(
selectedTrafficGranularityManager.getSelectedTrafficGranularity(),
BaseStatsUseCase.UseCaseMode.BLOCK
)
}
statsUseCase.onCleared()
statsUseCase = statsUseCase.clone(newUseCases) // Create new BaseListUseCase with updated useCases
launch {
statsUseCase.loadData()
dateSelector?.updateDateSelector()
}
setUiLiveData() // Set UI live data and observers again
}
}
}

class YearsListViewModel @Inject constructor(
@Named(UI_THREAD) mainDispatcher: CoroutineDispatcher,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import org.wordpress.android.ui.stats.refresh.utils.trackWithGranularity
import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper
import org.wordpress.android.util.extensions.getParcelableCompat
import org.wordpress.android.util.extensions.readListCompat
import org.wordpress.android.util.filter
import java.util.Date
import javax.inject.Inject
import javax.inject.Singleton
Expand All @@ -38,9 +37,7 @@ class SelectedDateProvider

private val selectedDateChanged = MutableLiveData<GranularityChange?>()

fun granularSelectedDateChanged(statsGranularity: StatsGranularity): LiveData<GranularityChange?> {
return selectedDateChanged.filter { it?.selectedGranularity == statsGranularity }
}
fun granularSelectedDateChanged(): LiveData<GranularityChange?> = selectedDateChanged

fun selectDate(date: Date, statsGranularity: StatsGranularity) {
val selectedDate = getSelectedDateState(statsGranularity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ constructor(
private val selectedDateProvider: SelectedDateProvider,
private val statsDateFormatter: StatsDateFormatter,
private val siteProvider: StatsSiteProvider,
private val statsGranularity: StatsGranularity,
var statsGranularity: StatsGranularity,
private val isGranularitySpinnerVisible: Boolean,
private val statsTrafficTabFeatureConfig: StatsTrafficTabFeatureConfig
) {
private val _dateSelectorUiModel = MutableLiveData<DateSelectorUiModel>()
val dateSelectorData: LiveData<DateSelectorUiModel> = _dateSelectorUiModel

val selectedDate = selectedDateProvider.granularSelectedDateChanged(statsGranularity)
.perform {
var selectedDate = selectedDateProvider.granularSelectedDateChanged().perform {
if (statsGranularity == it?.selectedGranularity) {
updateDateSelector()
}
}

fun start(startDate: SelectedDate) {
selectedDateProvider.updateSelectedDate(startDate, statsGranularity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class StatsDateSelectorTest : BaseUnitTest() {
@Before
fun setUp() {
dateProviderSelectedDate.value = GranularityChange(statsGranularity)
whenever(selectedDateProvider.granularSelectedDateChanged(statsGranularity))
whenever(selectedDateProvider.granularSelectedDateChanged())
.thenReturn(dateProviderSelectedDate)

dateSelector = StatsDateSelector(
Expand Down

0 comments on commit 819fa49

Please sign in to comment.