Skip to content

Commit 2895dae

Browse files
authored
Merge pull request #15601 from wordpress-mobile/issue/15215-my-site-dashboard-ptr
My Site Dashboard-Phase 2: Implement pull-to-refresh
2 parents 2eb2a2d + 40f3dcc commit 2895dae

File tree

4 files changed

+289
-42
lines changed

4 files changed

+289
-42
lines changed

WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment),
9595

9696
private var binding: MySiteFragmentBinding? = null
9797

98+
// Capture and track the first `onResume` event so we can circumvent refreshing sources on initial onResume
99+
private var isFirstResume = true
100+
98101
override fun onCreate(savedInstanceState: Bundle?) {
99102
super.onCreate(savedInstanceState)
100103
// The following prevents the soft keyboard from leaving a white space when dismissed.
@@ -174,7 +177,7 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment),
174177

175178
swipeToRefreshHelper = buildSwipeToRefreshHelper(swipeRefreshLayout) {
176179
if (NetworkUtils.checkConnection(requireActivity())) {
177-
viewModel.onPullToRefresh()
180+
viewModel.refresh()
178181
} else {
179182
swipeToRefreshHelper.isRefreshing = false
180183
}
@@ -185,6 +188,7 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment),
185188
private fun MySiteFragmentBinding.setupObservers() {
186189
viewModel.uiModel.observe(viewLifecycleOwner, { uiModel ->
187190
loadGravatar(uiModel.accountAvatarUrl)
191+
hideRefreshIndicatorIfNeeded()
188192
when (val state = uiModel.state) {
189193
is State.SiteSelected -> loadData(state.cardAndItems)
190194
is State.NoSites -> loadEmptyView(state.shouldShowImage)
@@ -372,8 +376,8 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment),
372376

373377
override fun onResume() {
374378
super.onResume()
375-
viewModel.refresh()
376-
viewModel.checkAndShowQuickStartNotice()
379+
viewModel.onResume(isFirstResume)
380+
isFirstResume = false
377381
}
378382

379383
override fun onPause() {
@@ -512,10 +516,10 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment),
512516

513517
private fun MySiteFragmentBinding.loadData(cardAndItems: List<MySiteCardAndItem>) {
514518
recyclerView.setVisible(true)
519+
actionableEmptyView.setVisible(false)
515520
viewModel.setActionableEmptyViewGone(actionableEmptyView.isVisible) {
516521
actionableEmptyView.setVisible(false)
517522
}
518-
swipeToRefreshHelper.isRefreshing = false
519523
(recyclerView.adapter as? MySiteAdapter)?.loadData(cardAndItems)
520524
}
521525

@@ -525,7 +529,6 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment),
525529
actionableEmptyView.setVisible(true)
526530
actionableEmptyView.image.setVisible(shouldShowEmptyViewImage)
527531
}
528-
swipeToRefreshHelper.isRefreshing = false
529532
actionableEmptyView.image.setVisible(shouldShowEmptyViewImage)
530533
}
531534

@@ -555,6 +558,12 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment),
555558
swipeToRefreshHelper.setEnabled(isEnabled)
556559
}
557560

561+
private fun hideRefreshIndicatorIfNeeded() {
562+
if (swipeToRefreshHelper.isRefreshing) {
563+
swipeToRefreshHelper.isRefreshing = viewModel.isRefreshing()
564+
}
565+
}
566+
558567
companion object {
559568
private const val KEY_LIST_STATE = "key_list_state"
560569
private const val KEY_NESTED_LISTS_STATES = "key_nested_lists_states"

WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import org.wordpress.android.ui.mysite.MySiteCardAndItemBuilderParams.QuickActio
2828
import org.wordpress.android.ui.mysite.MySiteCardAndItemBuilderParams.QuickStartCardBuilderParams
2929
import org.wordpress.android.ui.mysite.MySiteCardAndItemBuilderParams.SiteInfoCardBuilderParams
3030
import org.wordpress.android.ui.mysite.MySiteCardAndItemBuilderParams.SiteItemsBuilderParams
31+
import org.wordpress.android.ui.mysite.MySiteSource.MySiteRefreshSource
3132
import org.wordpress.android.ui.mysite.MySiteSource.SiteIndependentSource
3233
import org.wordpress.android.ui.mysite.MySiteUiState.PartialState
3334
import org.wordpress.android.ui.mysite.MySiteViewModel.State.NoSites
@@ -114,10 +115,10 @@ class MySiteViewModel @Inject constructor(
114115
private val snackbarSequencer: SnackbarSequencer,
115116
private val cardsBuilder: CardsBuilder,
116117
private val dynamicCardsBuilder: DynamicCardsBuilder,
117-
private val postCardsSource: PostCardsSource,
118-
selectedSiteSource: SelectedSiteSource,
118+
postCardsSource: PostCardsSource,
119+
private val selectedSiteSource: SelectedSiteSource,
119120
siteIconProgressSource: SiteIconProgressSource,
120-
mySiteDashboardPhase2FeatureConfig: MySiteDashboardPhase2FeatureConfig
121+
private val mySiteDashboardPhase2FeatureConfig: MySiteDashboardPhase2FeatureConfig
121122
) : ScopedViewModel(mainDispatcher) {
122123
private val _onSnackbarMessage = MutableLiveData<Event<SnackbarMessageHolder>>()
123124
private val _onTechInputDialogShown = MutableLiveData<Event<TextInputDialogModel>>()
@@ -126,7 +127,7 @@ class MySiteViewModel @Inject constructor(
126127
private val _onNavigation = MutableLiveData<Event<SiteNavigationAction>>()
127128
private val _onMediaUpload = MutableLiveData<Event<MediaModel>>()
128129
private val _activeTaskPosition = MutableLiveData<Pair<QuickStartTask, Int>>()
129-
private val _onShowSwipeRefreshLayout = MutableLiveData((Event(mySiteDashboardPhase2FeatureConfig.isEnabled())))
130+
private val _onShowSwipeRefreshLayout = MutableLiveData<Event<Boolean>>()
130131

131132
val onScrollTo: LiveData<Event<Int>> = merge(
132133
_activeTaskPosition.distinctUntilChanged(),
@@ -159,25 +160,26 @@ class MySiteViewModel @Inject constructor(
159160
postCardsSource
160161
)
161162

162-
val state: LiveData<MySiteUiState> = selectedSiteRepository.siteSelected.switchMap { siteLocalId ->
163-
val result = MediatorLiveData<SiteIdToState>()
164-
val currentSources = if (siteLocalId != null) {
165-
mySiteSources.map { source -> source.build(viewModelScope, siteLocalId).distinctUntilChanged() }
166-
} else {
167-
mySiteSources.filterIsInstance(SiteIndependentSource::class.java)
168-
.map { source -> source.build(viewModelScope).distinctUntilChanged() }
169-
}
170-
for (newSource in currentSources) {
171-
result.addSource(newSource) { partialState ->
172-
if (partialState != null) {
173-
result.value = (result.value ?: SiteIdToState(siteLocalId)).update(partialState)
163+
val state: LiveData<MySiteUiState> =
164+
selectedSiteRepository.siteSelected.switchMap { siteLocalId ->
165+
val result = MediatorLiveData<SiteIdToState>()
166+
val currentSources = if (siteLocalId != null) {
167+
mySiteSources.map { source -> source.build(viewModelScope, siteLocalId).distinctUntilChanged() }
168+
} else {
169+
mySiteSources.filterIsInstance(SiteIndependentSource::class.java)
170+
.map { source -> source.build(viewModelScope).distinctUntilChanged() }
171+
}
172+
for (newSource in currentSources) {
173+
result.addSource(newSource) { partialState ->
174+
if (partialState != null) {
175+
result.value = (result.value ?: SiteIdToState(siteLocalId)).update(partialState)
176+
}
174177
}
175178
}
176-
}
177-
// We want to filter out the empty state where we have a site ID but site object is missing.
178-
// Without this check there is an emission of a NoSites state even if we have the site
179-
result.filter { it.siteId == null || it.state.site != null }.map { it.state }
180-
}.distinctUntilChanged()
179+
// We want to filter out the empty state where we have a site ID but site object is missing.
180+
// Without this check there is an emission of a NoSites state even if we have the site
181+
result.filter { it.siteId == null || it.state.site != null }.map { it.state }
182+
}.distinctUntilChanged()
181183

182184
val uiModel: LiveData<UiModel> = state.map { (
183185
currentAvatarUrl,
@@ -483,11 +485,36 @@ class MySiteViewModel @Inject constructor(
483485
}
484486

485487
fun refresh() {
488+
if (mySiteDashboardPhase2FeatureConfig.isEnabled()) {
489+
refreshNew()
490+
} else {
491+
refreshOld()
492+
}
493+
}
494+
495+
private fun refreshNew() {
496+
mySiteSources.filterIsInstance(MySiteRefreshSource::class.java).forEach { it.refresh() }
497+
}
498+
499+
private fun refreshOld() {
486500
selectedSiteRepository.updateSiteSettingsIfNecessary()
487501
quickStartCardSource.refresh()
488502
currentAvatarSource.refresh()
489503
}
490504

505+
fun onResume(isFirstResume: Boolean) {
506+
when (isFirstResume) {
507+
true -> refreshOld()
508+
false -> if (mySiteDashboardPhase2FeatureConfig.isEnabled()) {
509+
refreshNew()
510+
} else {
511+
refreshOld()
512+
}
513+
}
514+
checkAndShowQuickStartNotice()
515+
_onShowSwipeRefreshLayout.postValue(Event(mySiteDashboardPhase2FeatureConfig.isEnabled()))
516+
}
517+
491518
fun clearActiveQuickStartTask() {
492519
quickStartRepository.clearActiveTask()
493520
}
@@ -671,6 +698,7 @@ class MySiteViewModel @Inject constructor(
671698
domainRegistrationSource.clear()
672699
quickStartRepository.clear()
673700
scanAndBackupSource.clear()
701+
selectedSiteSource.clear()
674702
super.onCleared()
675703
}
676704

@@ -738,10 +766,6 @@ class MySiteViewModel @Inject constructor(
738766
analyticsTrackerWrapper.track(Stat.QUICK_START_REQUEST_DIALOG_NEGATIVE_TAPPED)
739767
}
740768

741-
fun onPullToRefresh() {
742-
postCardsSource.refresh()
743-
}
744-
745769
private fun onPostItemClick(postId: Int) {
746770
selectedSiteRepository.getSelectedSite()?.let { site ->
747771
_onNavigation.value = Event(SiteNavigationAction.EditPost(site, postId))
@@ -759,6 +783,19 @@ class MySiteViewModel @Inject constructor(
759783
}
760784
}
761785

786+
fun isRefreshing() = areSourcesRefreshing()
787+
788+
private fun areSourcesRefreshing(): Boolean {
789+
if (mySiteDashboardPhase2FeatureConfig.isEnabled()) {
790+
mySiteSources.filterIsInstance(MySiteRefreshSource::class.java).forEach {
791+
if (it.isRefreshing() == true) {
792+
return true
793+
}
794+
}
795+
}
796+
return false
797+
}
798+
762799
fun setActionableEmptyViewGone(isVisible: Boolean, setGone: () -> Unit) {
763800
if (isVisible) analyticsTrackerWrapper.track(Stat.MY_SITE_NO_SITES_VIEW_HIDDEN)
764801
setGone()

WordPress/src/main/java/org/wordpress/android/ui/mysite/SelectedSiteSource.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ import org.wordpress.android.ui.mysite.MySiteUiState.PartialState.SelectedSite
1212
import org.wordpress.android.util.filter
1313
import org.wordpress.android.util.map
1414
import javax.inject.Inject
15-
import javax.inject.Singleton
1615

17-
@Singleton
1816
class SelectedSiteSource @Inject constructor(
1917
private val selectedSiteRepository: SelectedSiteRepository,
2018
private val dispatcher: Dispatcher

0 commit comments

Comments
 (0)