Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue/19952 update traffic tab by granularity #20125

Merged
merged 7 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading