From 7faa984ebb8063a8de915ebef80e9216946dfd6f Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 5 Oct 2023 14:52:22 -0400 Subject: [PATCH 01/23] Add QuickStartMenuStep to support steps that are in the more menu. --- .../cards/quickstart/QuickStartRepository.kt | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepository.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepository.kt index 9a9b14bd18c0..f9a7f698d4c7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepository.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepository.kt @@ -71,10 +71,12 @@ class QuickStartRepository private val _activeTask = MutableLiveData() private val _onSnackbar = MutableLiveData>() private val _onQuickStartMySitePrompts = MutableLiveData>() + private val _quickStartMenuStep = MutableLiveData() private var _isQuickStartNoticeShown: Boolean = false val onSnackbar = _onSnackbar as LiveData> val onQuickStartMySitePrompts = _onQuickStartMySitePrompts as LiveData> val activeTask = _activeTask as LiveData + val quickStartMenuStep = _quickStartMenuStep as LiveData val isQuickStartNoticeShown = _isQuickStartNoticeShown val quickStartType: QuickStartType get() = selectedSiteRepository.getSelectedSite()?.let { @@ -129,6 +131,7 @@ class QuickStartRepository _activeTask.postValue(task) clearPendingTask() when { + task.isShownInMenu() -> requestMoreStepForTask(task) task == QuickStartNewSiteTask.UPDATE_SITE_TITLE -> { val shortQuickStartMessage = resourceProvider.getString( R.string.quick_start_dialog_update_site_title_message_short, @@ -184,6 +187,18 @@ class QuickStartRepository quickStartTracker.track(quickStartUtilsWrapper.getTaskCompletedTracker(task)) } + private fun requestMoreStepForTask(task: QuickStartTask) { + clearActiveTask() + pendingTask = task + val shortQuickStartMessage = resourceProvider.getString( + R.string.quick_start_site_menu_tab_message_short, + resourceProvider.getString(R.string.more) + ) + + _onSnackbar.postValue(Event(SnackbarMessageHolder(UiStringText(htmlCompat.fromHtml(shortQuickStartMessage))))) + _quickStartMenuStep.postValue(QuickStartMenuStep(true, task)) + } + fun requestNextStepOfTask(task: QuickStartTask) { if (task != activeTask.value) return clearActiveTask() @@ -206,7 +221,7 @@ class QuickStartRepository } } - fun showCompletedQuickStartNotice() { + private fun showCompletedQuickStartNotice() { launch { delay(QUICK_START_COMPLETED_NOTICE_DELAY) val message = htmlMessageUtils.getHtmlMessageFromStringFormat( @@ -273,12 +288,26 @@ class QuickStartRepository appPrefsWrapper.setLastSkippedQuickStartTask(task) } + private fun QuickStartTask.isShownInMenu() = + when (this) { + quickStartType.getTaskFromString(QuickStartStore.QUICK_START_CHECK_STATS_LABEL), + quickStartType.getTaskFromString(QuickStartStore.QUICK_START_UPLOAD_MEDIA_LABEL), + QuickStartNewSiteTask.REVIEW_PAGES, + QuickStartNewSiteTask.CHECK_STATS, + QuickStartNewSiteTask.ENABLE_POST_SHARING -> true + else -> false + } + data class QuickStartCategory( val taskType: QuickStartTaskType, val uncompletedTasks: List, val completedTasks: List ) + data class QuickStartMenuStep( + val isStarted: Boolean, + val task: QuickStartTask? = null + ) companion object { private const val QUICK_START_NOTICE_DURATION = 7000 private const val QUICK_START_COMPLETED_NOTICE_DELAY = 5000L From a2e96cf441ba3dfcb4611bac9b701611cfcef5d8 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 5 Oct 2023 14:52:41 -0400 Subject: [PATCH 02/23] Adjust tests to support QuickStartMenuStep --- .../quickstart/QuickStartRepositoryTest.kt | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt index 53944813d08a..4a1d16a9b6bf 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt @@ -59,7 +59,7 @@ class QuickStartRepositoryTest : BaseUnitTest() { lateinit var eventBus: EventBusWrapper @Mock - lateinit var htmlCompat: HtmlCompatWrapper + lateinit var htmlCompatWrapper: HtmlCompatWrapper @Mock lateinit var contextProvider: ContextProvider @@ -79,6 +79,7 @@ class QuickStartRepositoryTest : BaseUnitTest() { private lateinit var quickStartRepository: QuickStartRepository private lateinit var snackbars: MutableList private lateinit var quickStartPrompts: MutableList + private lateinit var quickStartMenuStep: MutableList private val siteLocalId = 1 @Before @@ -93,13 +94,14 @@ class QuickStartRepositoryTest : BaseUnitTest() { resourceProvider, dispatcher, eventBus, - htmlCompat, + htmlCompatWrapper, contextProvider, htmlMessageUtils, quickStartTracker ) snackbars = mutableListOf() quickStartPrompts = mutableListOf() + quickStartMenuStep = mutableListOf() quickStartRepository.onSnackbar.observeForever { event -> event?.getContentIfNotHandled() ?.let { snackbars.add(it) } @@ -107,6 +109,9 @@ class QuickStartRepositoryTest : BaseUnitTest() { quickStartRepository.onQuickStartMySitePrompts.observeForever { event -> event?.getContentIfNotHandled()?.let { quickStartPrompts.add(it) } } + quickStartRepository.quickStartMenuStep.observeForever { event -> + quickStartMenuStep.add(event!!) + } site = SiteModel() site.id = siteLocalId } @@ -161,15 +166,24 @@ class QuickStartRepositoryTest : BaseUnitTest() { } /* QUICK START REQUEST NEXT STEP */ - +// todo: annmarie @Test fun `requestNextStepOfTask emits quick start event`() = test { initQuickStartInProgress() + quickStartRepository.setActiveTask(QuickStartNewSiteTask.FOLLOW_SITE) + quickStartRepository.requestNextStepOfTask(QuickStartNewSiteTask.FOLLOW_SITE) + + verify(eventBus).postSticky(QuickStartEvent(QuickStartNewSiteTask.FOLLOW_SITE)) + } + + @Test + fun `given more menu task, when setActiveTask invoked, then quick start menu step is posted`() = test { + initQuickStartInProgress() + quickStartRepository.setActiveTask(QuickStartNewSiteTask.ENABLE_POST_SHARING) - quickStartRepository.requestNextStepOfTask(QuickStartNewSiteTask.ENABLE_POST_SHARING) - verify(eventBus).postSticky(QuickStartEvent(QuickStartNewSiteTask.ENABLE_POST_SHARING)) + assertThat(quickStartMenuStep.last()).isInstanceOf(QuickStartRepository.QuickStartMenuStep::class.java) } /* QUICK START REMINDER NOTIFICATION */ @@ -270,5 +284,8 @@ class QuickStartRepositoryTest : BaseUnitTest() { whenever(quickStartUtilsWrapper.getNextUncompletedQuickStartTask(quickStartType, siteLocalId.toLong())) .thenReturn(nextUncompletedTask) whenever(htmlMessageUtils.getHtmlMessageFromStringFormat(anyOrNull())).thenReturn("") + whenever(resourceProvider.getString(any())).thenReturn("") + whenever(resourceProvider.getString(any(), any())).thenReturn("") + whenever(htmlCompatWrapper.fromHtml(any(), any())).thenReturn(" ") } } From d09177a95f5ebe6bdf5a23a72ab2436d1b801423 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 5 Oct 2023 14:53:44 -0400 Subject: [PATCH 03/23] Refactor: use QuickStartMenuStep instead of activeTask because more focus is an interim step, not the active task --- .../cards/quicklinksitem/QuickLinksItemViewModelSlice.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quicklinksitem/QuickLinksItemViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quicklinksitem/QuickLinksItemViewModelSlice.kt index 75fdba50fa63..fdb837517a2b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quicklinksitem/QuickLinksItemViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quicklinksitem/QuickLinksItemViewModelSlice.kt @@ -15,6 +15,7 @@ import org.wordpress.android.ui.mysite.MySiteCardAndItemBuilderParams import org.wordpress.android.ui.mysite.SelectedSiteRepository import org.wordpress.android.ui.mysite.SiteNavigationAction import org.wordpress.android.ui.mysite.cards.ListItemActionHandler +import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository import org.wordpress.android.ui.mysite.items.listitem.ListItemAction import org.wordpress.android.ui.mysite.items.listitem.SiteItemsBuilder import org.wordpress.android.ui.pages.SnackbarMessageHolder @@ -32,7 +33,7 @@ class QuickLinksItemViewModelSlice @Inject constructor( private val jetpackCapabilitiesUseCase: JetpackCapabilitiesUseCase, private val listItemActionHandler: ListItemActionHandler, private val blazeFeatureUtils: BlazeFeatureUtils, - private val appPrefsWrapper: AppPrefsWrapper, + private val appPrefsWrapper: AppPrefsWrapper ) { lateinit var scope: CoroutineScope @@ -159,9 +160,9 @@ class QuickLinksItemViewModelSlice @Inject constructor( fun updateToShowMoreFocusPointIfNeeded( quickLinks: MySiteCardAndItem.Card.QuickLinksItem, - activeTask: QuickStartStore.QuickStartTask + quickStartMenuStep: QuickStartRepository.QuickStartMenuStep ): MySiteCardAndItem.Card.QuickLinksItem { - val updatedQuickLinks = if (isActiveTaskInMoreMenu(activeTask)) { + val updatedQuickLinks = if (isActiveTaskInMoreMenu(quickStartMenuStep.task)) { val quickLinkItems = quickLinks.quickLinkItems.toMutableList() val lastItem = quickLinkItems.last().copy(showFocusPoint = true) quickLinkItems.removeLast() From 021fcc0d110d2ebb56eebbed580c526bbdba3e13 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 5 Oct 2023 14:54:41 -0400 Subject: [PATCH 04/23] Refactor: have quickLins depend on quickStartMenuStep and not activeTask. More tap is an interim step, not the active task --- .../wordpress/android/ui/mysite/MySiteViewModel.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt index 1c44fdce0aa6..194755866f1b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.launch import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode.MAIN import org.wordpress.android.R -import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.model.SiteModel @@ -182,11 +181,11 @@ class MySiteViewModel @Inject constructor( val quickLinks: LiveData = merge( quickLinksItemViewModelSlice.uiState, - quickStartRepository.activeTask - ) { quickLinks, activeTask -> + quickStartRepository.quickStartMenuStep + ) { quickLinks, quickStartMenuStep -> if (quickLinks != null && - activeTask != null) { - return@merge quickLinksItemViewModelSlice.updateToShowMoreFocusPointIfNeeded(quickLinks, activeTask) + quickStartMenuStep != null) { + return@merge quickLinksItemViewModelSlice.updateToShowMoreFocusPointIfNeeded(quickLinks, quickStartMenuStep) } return@merge quickLinks } @@ -702,7 +701,7 @@ class MySiteViewModel @Inject constructor( } fun handleSuccessfulDomainRegistrationResult(email: String?) { - analyticsTrackerWrapper.track(AnalyticsTracker.Stat.DOMAIN_CREDIT_REDEMPTION_SUCCESS) + analyticsTrackerWrapper.track(Stat.DOMAIN_CREDIT_REDEMPTION_SUCCESS) _onSnackbarMessage.postValue(Event(SnackbarMessageHolder(getEmailValidationMessage(email)))) } From d3ba37c337acb25a5d902a86ab472966049089a9 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 5 Oct 2023 17:36:07 -0400 Subject: [PATCH 05/23] Refactor tests to support QuickStartMenuStep for the more menu --- .../ui/mysite/cards/quickstart/QuickStartCardSourceTest.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartCardSourceTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartCardSourceTest.kt index a58b40fa5909..1afa115e037b 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartCardSourceTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartCardSourceTest.kt @@ -208,8 +208,8 @@ class QuickStartCardSourceTest : BaseUnitTest() { fun `requestNextStepOfTask clears current active task`() = test { initQuickStartInProgress() - quickStartRepository.setActiveTask(ENABLE_POST_SHARING) - quickStartRepository.requestNextStepOfTask(ENABLE_POST_SHARING) + quickStartRepository.setActiveTask(QuickStartStore.QuickStartNewSiteTask.FOLLOW_SITE) + quickStartRepository.requestNextStepOfTask(QuickStartStore.QuickStartNewSiteTask.FOLLOW_SITE) val update = result.last() assertThat(update.activeTask).isNull() @@ -342,6 +342,9 @@ class QuickStartCardSourceTest : BaseUnitTest() { whenever(quickStartUtilsWrapper.getNextUncompletedQuickStartTask(quickStartType, siteLocalId.toLong())) .thenReturn(nextUncompletedTask) whenever(htmlMessageUtils.getHtmlMessageFromStringFormat(anyOrNull())).thenReturn("") + whenever(resourceProvider.getString(any())).thenReturn("") + whenever(resourceProvider.getString(any(), any())).thenReturn("") + whenever(htmlCompat.fromHtml(any(), any())).thenReturn(" ") initBuild() } From e7c970b4bc5a06174c004dcc5529a16d77239456 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 5 Oct 2023 17:37:13 -0400 Subject: [PATCH 06/23] Remove todo --- .../ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt index 4a1d16a9b6bf..b413e83c216a 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt @@ -166,7 +166,6 @@ class QuickStartRepositoryTest : BaseUnitTest() { } /* QUICK START REQUEST NEXT STEP */ -// todo: annmarie @Test fun `requestNextStepOfTask emits quick start event`() = test { initQuickStartInProgress() From a3696349d46d4708c9065898f1ed694929d335c6 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 5 Oct 2023 17:50:11 -0400 Subject: [PATCH 07/23] Update tests to support QuickStartMenuStep --- .../java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt index f23ca17162d2..eb04e0157f7f 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt @@ -415,6 +415,7 @@ class MySiteViewModelTest : BaseUnitTest() { whenever(personalizeCardBuilder.build(any())).thenReturn(mock()) whenever(bloggingPromptCardViewModelSlice.getBuilderParams(anyOrNull())).thenReturn(mock()) whenever(quickLinksItemViewModelSlice.uiState).thenReturn(mock()) + whenever(quickStartRepository.quickStartMenuStep).thenReturn(mock()) viewModel = MySiteViewModel( testDispatcher(), From 2e9b37a5d5bd9e08c0af1bd7860e715384b8276c Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 11:40:36 -0400 Subject: [PATCH 08/23] Refactor: Add quickStartEvent to OpenMore class --- .../org/wordpress/android/ui/mysite/SiteNavigationAction.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/SiteNavigationAction.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/SiteNavigationAction.kt index abbfbc4cbce7..845598162436 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/SiteNavigationAction.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/SiteNavigationAction.kt @@ -10,6 +10,7 @@ import org.wordpress.android.ui.blaze.BlazeFlowSource import org.wordpress.android.ui.blaze.blazecampaigns.campaigndetail.CampaignDetailPageSource import org.wordpress.android.ui.blaze.blazecampaigns.campaignlisting.CampaignListingPageSource import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalOverlayUtil.JetpackFeatureCollectionOverlaySource +import org.wordpress.android.ui.quickstart.QuickStartEvent import org.wordpress.android.ui.sitecreation.misc.SiteCreationSource import org.wordpress.android.util.UriWrapper @@ -39,7 +40,7 @@ sealed class SiteNavigationAction { data class OpenThemes(val site: SiteModel) : SiteNavigationAction() data class OpenPlugins(val site: SiteModel) : SiteNavigationAction() data class OpenMedia(val site: SiteModel) : SiteNavigationAction() - data class OpenMore(val site:SiteModel) : SiteNavigationAction() + data class OpenMore(val site:SiteModel, val quickStartEvent: QuickStartEvent?) : SiteNavigationAction() data class OpenUnifiedComments(val site: SiteModel) : SiteNavigationAction() object StartWPComLoginForJetpackStats : SiteNavigationAction() data class OpenStats(val site: SiteModel) : SiteNavigationAction() From 14b49bd543339eaecad8b1ffedce5ad8a18b92ec Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 11:41:33 -0400 Subject: [PATCH 09/23] Refactor: Set quickStartEvent in OpenMore SiteNavigationAction --- .../android/ui/mysite/cards/ListItemActionHandler.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/ListItemActionHandler.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/ListItemActionHandler.kt index cf6c2a92f2d3..ba55fc1133c3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/ListItemActionHandler.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/ListItemActionHandler.kt @@ -8,6 +8,7 @@ import org.wordpress.android.ui.blaze.blazecampaigns.campaignlisting.CampaignLis import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper import org.wordpress.android.ui.mysite.SiteNavigationAction import org.wordpress.android.ui.mysite.items.listitem.ListItemAction +import org.wordpress.android.ui.quickstart.QuickStartEvent import javax.inject.Inject class ListItemActionHandler @Inject constructor( @@ -15,7 +16,11 @@ class ListItemActionHandler @Inject constructor( private val jetpackFeatureRemovalPhaseHelper: JetpackFeatureRemovalPhaseHelper, private val blazeFeatureUtils: BlazeFeatureUtils ) { - fun handleAction(action: ListItemAction, selectedSite: SiteModel): SiteNavigationAction { + fun handleAction( + action: ListItemAction, + selectedSite: SiteModel, + quickStartEvent: QuickStartEvent? = null + ): SiteNavigationAction { return when (action) { ListItemAction.ACTIVITY_LOG -> SiteNavigationAction.OpenActivityLog(selectedSite) ListItemAction.BACKUP -> SiteNavigationAction.OpenBackup(selectedSite) @@ -35,7 +40,7 @@ class ListItemActionHandler @Inject constructor( ListItemAction.MEDIA -> SiteNavigationAction.OpenMedia(selectedSite) ListItemAction.COMMENTS -> SiteNavigationAction.OpenUnifiedComments(selectedSite) ListItemAction.BLAZE -> onBlazeMenuItemClick() - ListItemAction.MORE -> SiteNavigationAction.OpenMore(selectedSite) + ListItemAction.MORE -> SiteNavigationAction.OpenMore(selectedSite, quickStartEvent) } } From c7679150f9bcc95520919b75a552a5dd850ffa37 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 11:42:05 -0400 Subject: [PATCH 10/23] Refactor: Pass along quickStartEvent from OpenMore SiteNavigationAction to activityNavigator --- .../java/org/wordpress/android/ui/mysite/MySiteFragment.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt index eadc41ec501b..4d10c21aff24 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt @@ -602,8 +602,10 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment), is SiteNavigationAction.OpenThemes -> ActivityLauncher.viewCurrentBlogThemes(activity, action.site) is SiteNavigationAction.OpenPlugins -> ActivityLauncher.viewPluginBrowser(activity, action.site) is SiteNavigationAction.OpenMedia -> ActivityLauncher.viewCurrentBlogMedia(activity, action.site) - // is SiteNavigationAction.OpenMore -> ActivityLauncher.viewQuickLinkMoreMenu(activity, action.site) - is SiteNavigationAction.OpenMore -> activityNavigator.openUnifiedMySiteMenu(requireActivity()) + is SiteNavigationAction.OpenMore -> activityNavigator.openUnifiedMySiteMenu( + requireActivity(), + action.quickStartEvent + ) is SiteNavigationAction.OpenUnifiedComments -> ActivityLauncher.viewUnifiedComments(activity, action.site) is SiteNavigationAction.OpenStats -> ActivityLauncher.viewBlogStats(activity, action.site) is SiteNavigationAction.ConnectJetpackForStats -> From 9da06b95abe3e9495fbd065d0fe69b70fce22179 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 11:42:25 -0400 Subject: [PATCH 11/23] Add intent KEY for quick start event --- .../java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt index ec15a9bbc956..0d644e5f5d5a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt @@ -62,6 +62,7 @@ import org.wordpress.android.ui.utils.UiString import org.wordpress.android.util.LocaleManager import javax.inject.Inject +const val KEY_QUICK_START_EVENT = "key_quick_start_event" @AndroidEntryPoint class MenuActivity : AppCompatActivity() { @Inject From cb5bb8c327da42744bcaaf0edd811e28388525de Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 11:43:15 -0400 Subject: [PATCH 12/23] Set quickStartEvent, if available in openUnifiedSiteMenu --- .../org/wordpress/android/ui/ActivityNavigator.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityNavigator.kt b/WordPress/src/main/java/org/wordpress/android/ui/ActivityNavigator.kt index b94444b707de..9ae93dd4e3b6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityNavigator.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityNavigator.kt @@ -11,8 +11,10 @@ import org.wordpress.android.ui.blaze.blazecampaigns.campaignlisting.CampaignLis import org.wordpress.android.ui.blaze.blazepromote.ARG_BLAZE_FLOW_SOURCE import org.wordpress.android.ui.blaze.blazepromote.ARG_BLAZE_SHOULD_SHOW_OVERLAY import org.wordpress.android.ui.blaze.blazepromote.BlazePromoteParentActivity +import org.wordpress.android.ui.mysite.menu.KEY_QUICK_START_EVENT import org.wordpress.android.ui.mysite.menu.MenuActivity import org.wordpress.android.ui.mysite.personalization.PersonalizationActivity +import org.wordpress.android.ui.quickstart.QuickStartEvent import javax.inject.Inject import javax.inject.Singleton @@ -66,7 +68,15 @@ class ActivityNavigator @Inject constructor() { context.startActivity(Intent(context, PersonalizationActivity::class.java)) } - fun openUnifiedMySiteMenu(context: Context) { + fun openUnifiedMySiteMenu(context: Context, quickStartEvent: QuickStartEvent? = null) { + if (quickStartEvent != null) { + context.startActivity( + Intent(context, MenuActivity::class.java).apply { + putExtra(KEY_QUICK_START_EVENT, quickStartEvent) + } + ) + return + } context.startActivity(Intent(context, MenuActivity::class.java)) } } From c80341b8762248c8fb4f57672fd0321ebfbc62fb Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 15:54:23 -0400 Subject: [PATCH 13/23] Refactor: add isFromMenu boolean to setActiveTask so the next step of the tutorial shows when on the more menu --- .../mysite/cards/quickstart/QuickStartRepository.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepository.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepository.kt index f9a7f698d4c7..b55d0e878e31 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepository.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepository.kt @@ -95,6 +95,7 @@ class QuickStartRepository fun resetTask() { clearActiveTask() clearPendingTask() + clearMenuStep() } fun clearActiveTask() { @@ -127,11 +128,12 @@ class QuickStartRepository } } - fun setActiveTask(task: QuickStartTask) { + fun setActiveTask(task: QuickStartTask, isFromMenu: Boolean = false) { _activeTask.postValue(task) clearPendingTask() + clearMenuStep() when { - task.isShownInMenu() -> requestMoreStepForTask(task) + !isFromMenu && task.isShownInMenu() -> requestMoreStepForTask(task) task == QuickStartNewSiteTask.UPDATE_SITE_TITLE -> { val shortQuickStartMessage = resourceProvider.getString( R.string.quick_start_dialog_update_site_title_message_short, @@ -298,6 +300,12 @@ class QuickStartRepository else -> false } + fun clearMenuStep() { + if (_quickStartMenuStep.value != null) { + _quickStartMenuStep.value = null + } + } + data class QuickStartCategory( val taskType: QuickStartTaskType, val uncompletedTasks: List, From 05c27a4b94bc9404ce2fb60eec5458e55ec5916b Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 15:56:11 -0400 Subject: [PATCH 14/23] Refactor: add separate click for more if in a quick start situation --- .../QuickLinksItemViewModelSlice.kt | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quicklinksitem/QuickLinksItemViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quicklinksitem/QuickLinksItemViewModelSlice.kt index fdb837517a2b..5c2d64586e08 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quicklinksitem/QuickLinksItemViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/quicklinksitem/QuickLinksItemViewModelSlice.kt @@ -20,6 +20,7 @@ import org.wordpress.android.ui.mysite.items.listitem.ListItemAction import org.wordpress.android.ui.mysite.items.listitem.SiteItemsBuilder import org.wordpress.android.ui.pages.SnackbarMessageHolder import org.wordpress.android.ui.prefs.AppPrefsWrapper +import org.wordpress.android.ui.quickstart.QuickStartEvent import org.wordpress.android.ui.utils.ListItemInteraction import org.wordpress.android.ui.utils.UiString import org.wordpress.android.viewmodel.Event @@ -33,7 +34,8 @@ class QuickLinksItemViewModelSlice @Inject constructor( private val jetpackCapabilitiesUseCase: JetpackCapabilitiesUseCase, private val listItemActionHandler: ListItemActionHandler, private val blazeFeatureUtils: BlazeFeatureUtils, - private val appPrefsWrapper: AppPrefsWrapper + private val appPrefsWrapper: AppPrefsWrapper, + private val quickStartRepository: QuickStartRepository ) { lateinit var scope: CoroutineScope @@ -164,7 +166,13 @@ class QuickLinksItemViewModelSlice @Inject constructor( ): MySiteCardAndItem.Card.QuickLinksItem { val updatedQuickLinks = if (isActiveTaskInMoreMenu(quickStartMenuStep.task)) { val quickLinkItems = quickLinks.quickLinkItems.toMutableList() - val lastItem = quickLinkItems.last().copy(showFocusPoint = true) + val lastItem = quickLinkItems.last().copy(showFocusPoint = true, + onClick = ListItemInteraction.create( + MoreClickWithTask(ListItemAction.MORE, + QuickStartEvent(task = quickStartMenuStep.task!!) + ), + this@QuickLinksItemViewModelSlice::onMoreClick + )) quickLinkItems.removeLast() quickLinkItems.add(lastItem) quickLinks.copy(quickLinkItems = quickLinkItems, showMoreFocusPoint = true) @@ -174,6 +182,26 @@ class QuickLinksItemViewModelSlice @Inject constructor( return updatedQuickLinks } + private fun onMoreClick(moreClickWithTask: MoreClickWithTask) { + quickStartRepository.clearMenuStep() + selectedSiteRepository.getSelectedSite()?.let { selectedSite -> + // add the tracking logic here + _onNavigation.postValue( + Event( + listItemActionHandler.handleAction( + moreClickWithTask.listActionType, + selectedSite, + moreClickWithTask.quickStartEvent + ) + ) + ) + } ?: run { + _onSnackbarMessage.postValue( + Event(SnackbarMessageHolder(UiString.UiStringRes(R.string.site_cannot_be_loaded))) + ) + } + } + private fun isActiveTaskInMoreMenu(activeTask: QuickStartStore.QuickStartTask?): Boolean { return activeTask == QuickStartStore.QuickStartNewSiteTask.REVIEW_PAGES || activeTask == QuickStartStore.QuickStartNewSiteTask.CHECK_STATS || @@ -185,4 +213,9 @@ class QuickLinksItemViewModelSlice @Inject constructor( fun onSiteChanged() { buildQuickLinks() } + + data class MoreClickWithTask( + val listActionType: ListItemAction, + val quickStartEvent: QuickStartEvent + ) } From 560da039db1dafa4e9d063224a7fdb2e698a1d3f Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 15:57:11 -0400 Subject: [PATCH 15/23] Capture quickStartPrompts and snackbarMessage from quickStartRepo. Hanlde QuickStartEvent --- .../android/ui/mysite/menu/MenuViewModel.kt | 134 +++++++++++++----- 1 file changed, 101 insertions(+), 33 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuViewModel.kt index 7a5e39e50fd1..3b3a530bed2b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.QuickStartStore +import org.wordpress.android.fluxc.store.QuickStartStore.QuickStartTask import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.ui.blaze.BlazeFeatureUtils import org.wordpress.android.ui.jetpack.JetpackCapabilitiesUseCase @@ -19,8 +20,11 @@ import org.wordpress.android.ui.mysite.cards.ListItemActionHandler import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository import org.wordpress.android.ui.mysite.items.listitem.ListItemAction import org.wordpress.android.ui.mysite.items.listitem.SiteItemsBuilder +import org.wordpress.android.ui.pages.SnackbarMessageHolder +import org.wordpress.android.ui.quickstart.QuickStartEvent import org.wordpress.android.util.JetpackMigrationLanguageUtil import org.wordpress.android.util.LocaleManagerWrapper +import org.wordpress.android.util.merge import org.wordpress.android.viewmodel.Event import org.wordpress.android.viewmodel.ScopedViewModel import java.util.Locale @@ -42,6 +46,11 @@ class MenuViewModel @Inject constructor( private val _onNavigation = MutableLiveData>() val navigation = _onNavigation + private val _onSnackbarMessage = MutableLiveData>() + val onSnackbarMessage = merge(_onSnackbarMessage, quickStartRepository.onSnackbar) + + val onQuickStartMySitePrompts = quickStartRepository.onQuickStartMySitePrompts + private val _refreshAppLanguage = MutableLiveData() val refreshAppLanguage: LiveData = _refreshAppLanguage @@ -49,50 +58,61 @@ class MenuViewModel @Inject constructor( val uiState: StateFlow = _uiState + private var quickStartEvent: QuickStartEvent? = null + private var isStarted = false + init { emitLanguageRefreshIfNeeded(localeManagerWrapper.getLanguage()) } - fun start() { + fun start(quickStartEvent: QuickStartEvent? = null) { + if (isStarted) { + return + } val site = selectedSiteRepository.getSelectedSite()!! + this.quickStartEvent = quickStartEvent + if (quickStartEvent != null) { + quickStartRepository.setActiveTask(quickStartEvent.task, true) + } buildSiteMenu(site) + isStarted = true } private fun buildSiteMenu(site: SiteModel) { - _uiState.value = MenuViewState(items = siteItemsBuilder.build( - MySiteCardAndItemBuilderParams.SiteItemsBuilderParams( - enableFocusPoints = true, - site = site, - activeTask = null, - onClick = this::onClick, - isBlazeEligible = isSiteBlazeEligible() - ) - ).filterIsInstance().map { - it.toMenuItemState() - }.toList() - ) - - updateSiteItemsForJetpackCapabilities(site) + val currentItems = siteItemsBuilder.build( + MySiteCardAndItemBuilderParams.SiteItemsBuilderParams( + enableFocusPoints = true, + site = site, + activeTask = quickStartEvent?.task, + onClick = this::onClick, + isBlazeEligible = isSiteBlazeEligible() + ) + ).filterIsInstance().map { + it.toMenuItemState() + }.toList() + + _uiState.value = MenuViewState(items = applyFocusPointIfNeeded(currentItems)) + + rebuildSiteItemsForJetpackCapabilities(site) } - private fun updateSiteItemsForJetpackCapabilities(site: SiteModel) { + private fun rebuildSiteItemsForJetpackCapabilities(site: SiteModel) { launch(bgDispatcher) { jetpackCapabilitiesUseCase.getJetpackPurchasedProducts(site.siteId).collect { - _uiState.value = MenuViewState( - items = siteItemsBuilder.build( - MySiteCardAndItemBuilderParams.SiteItemsBuilderParams( - site = site, - enableFocusPoints = true, - activeTask = null, - onClick = this@MenuViewModel::onClick, - isBlazeEligible = isSiteBlazeEligible(), - backupAvailable = it.backup, - scanAvailable = (it.scan && !site.isWPCom && !site.isWPComAtomic) - ) - ).filterIsInstance().map { item -> - item.toMenuItemState() - }.toList() - ) + val currentItems = siteItemsBuilder.build( + MySiteCardAndItemBuilderParams.SiteItemsBuilderParams( + enableFocusPoints = true, + site = site, + activeTask = quickStartEvent?.task, + onClick = this@MenuViewModel::onClick, + isBlazeEligible = isSiteBlazeEligible(), + scanAvailable = (it.scan && !site.isWPCom && !site.isWPComAtomic) + ) + ).filterIsInstance().map { item -> + item.toMenuItemState() + }.toList() + + _uiState.value = MenuViewState(items = applyFocusPointIfNeeded(currentItems)) } // end collect } } @@ -100,8 +120,8 @@ class MenuViewModel @Inject constructor( private fun isSiteBlazeEligible() = blazeFeatureUtils.isSiteBlazeEligible(selectedSiteRepository.getSelectedSite()!!) - private fun onClick(action: ListItemAction) { + clearQuickStartEvent() selectedSiteRepository.getSelectedSite()?.let { selectedSite -> when(action){ ListItemAction.PAGES -> { @@ -130,11 +150,59 @@ class MenuViewModel @Inject constructor( else -> {} } - // add the tracking logic here + // todo: add the tracking logic here _onNavigation.postValue(Event(listItemActionHandler.handleAction(action, selectedSite))) } } + private fun applyFocusPointIfNeeded(items: List) : List { + return quickStartEvent?.let { + val showFocusPointOn = convertQuickStartTaskToListItemAction(it.task) + items.map { item -> + if (item is MenuItemState.MenuListItem) { + if (item.listItemAction == showFocusPointOn) { + item.copy(showFocusPoint = true) + } else { + item + } + } else { + item + } + }.toList() + } ?: items + } + + private fun convertQuickStartTaskToListItemAction(task: QuickStartTask): ListItemAction { + return when (task) { + QuickStartStore.QuickStartNewSiteTask.REVIEW_PAGES -> ListItemAction.PAGES + QuickStartStore.QuickStartNewSiteTask.CHECK_STATS -> ListItemAction.STATS + QuickStartStore.QuickStartNewSiteTask.ENABLE_POST_SHARING -> ListItemAction.SHARING + QuickStartStore.QuickStartExistingSiteTask.UPLOAD_MEDIA -> ListItemAction.MEDIA + QuickStartStore.QuickStartExistingSiteTask.CHECK_STATS -> ListItemAction.STATS + else -> ListItemAction.MORE + } + } + + fun onResume() { + removeFocusPoints() + } + + private fun removeFocusPoints() { + if (quickStartEvent == null) { + val items = _uiState.value.items.map { item -> + if (item is MenuItemState.MenuListItem) { + item.copy(showFocusPoint = false) + } else { + item + } + }.toList() + _uiState.value = MenuViewState(items = items) + } + } + private fun clearQuickStartEvent() { + quickStartEvent = null + } + private fun emitLanguageRefreshIfNeeded(languageCode: String) { if (languageCode.isNotEmpty()) { val shouldEmitLanguageRefresh = !localeManagerWrapper.isSameLanguage(languageCode) From 35e02360e2644fdcfabc05295a0fde44a7343f86 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 15:57:54 -0400 Subject: [PATCH 16/23] Observe quickStartPrompts and snackbarMessage from quickStartRepo.Pass quickStartEvent to the viewmodel --- .../android/ui/mysite/menu/MenuActivity.kt | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt index 0d644e5f5d5a..82f53680f014 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt @@ -57,9 +57,15 @@ import org.wordpress.android.ui.compose.utils.LocaleAwareComposable import org.wordpress.android.ui.compose.utils.uiStringText import org.wordpress.android.ui.mysite.SiteNavigationAction import org.wordpress.android.ui.mysite.items.listitem.ListItemAction +import org.wordpress.android.ui.pages.SnackbarMessageHolder +import org.wordpress.android.ui.quickstart.QuickStartMySitePrompts import org.wordpress.android.ui.utils.ListItemInteraction import org.wordpress.android.ui.utils.UiString import org.wordpress.android.util.LocaleManager +import org.wordpress.android.util.QuickStartUtilsWrapper +import org.wordpress.android.util.SnackbarItem +import org.wordpress.android.util.SnackbarSequencer +import org.wordpress.android.util.extensions.getParcelableExtraCompat import javax.inject.Inject const val KEY_QUICK_START_EVENT = "key_quick_start_event" @@ -67,6 +73,13 @@ const val KEY_QUICK_START_EVENT = "key_quick_start_event" class MenuActivity : AppCompatActivity() { @Inject lateinit var activityNavigator: ActivityNavigator + + @Inject + lateinit var snackbarSequencer: SnackbarSequencer + + @Inject + lateinit var quickStartUtils: QuickStartUtilsWrapper + private val viewModel: MenuViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { @@ -80,7 +93,7 @@ class MenuActivity : AppCompatActivity() { locale = LocaleManager.languageLocale(userLanguage), onLocaleChange = viewModel::setAppLanguage ) { - viewModel.start() + viewModel.start(intent.getParcelableExtraCompat(KEY_QUICK_START_EVENT)) MenuScreen() } } @@ -89,9 +102,10 @@ class MenuActivity : AppCompatActivity() { private fun initObservers() { viewModel.navigation.observe(this) { handleNavigationAction(it.getContentIfNotHandled()) } + viewModel.onSnackbarMessage.observe(this) { showSnackbar(it.getContentIfNotHandled()) } + viewModel.onQuickStartMySitePrompts.observe(this) { handleActiveTutorialPrompt(it.getContentIfNotHandled()) } } - @Suppress("ComplexMethod", "LongMethod") private fun handleNavigationAction(action: SiteNavigationAction?) { when (action) { @@ -123,6 +137,45 @@ class MenuActivity : AppCompatActivity() { } } + private fun showSnackbar(holder: SnackbarMessageHolder?) { + holder?.let { + snackbarSequencer.enqueue( + SnackbarItem( + info = SnackbarItem.Info( + view = parent.findViewById(R.id.coordinator), + textRes = holder.message, + duration = holder.duration, + isImportant = holder.isImportant + ), + action = holder.buttonTitle?.let { + SnackbarItem.Action( + textRes = holder.buttonTitle, + clickListener = { holder.buttonAction() } + ) + }, + dismissCallback = { _, event -> holder.onDismissAction(event) } + ) + ) + } + } + + private fun handleActiveTutorialPrompt(activeTutorialPrompt: QuickStartMySitePrompts?) { + activeTutorialPrompt?.let { + val message = quickStartUtils.stylizeQuickStartPrompt( + this, + activeTutorialPrompt.shortMessagePrompt, + activeTutorialPrompt.iconId + ) + + showSnackbar(SnackbarMessageHolder(UiString.UiStringText(message))) + } + } + + override fun onResume() { + super.onResume() + viewModel.onResume() + } + @Composable @SuppressLint("UnusedMaterialScaffoldPaddingParameter") fun MenuScreen(modifier: Modifier = Modifier) { From 0924f4bface8795b88aa18ce55373fe0d641da39 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 6 Oct 2023 16:03:02 -0400 Subject: [PATCH 17/23] Refactor: swap the double bang for let to handle resetting the menuStep --- .../ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt index b413e83c216a..1bd4c01d1efe 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/quickstart/QuickStartRepositoryTest.kt @@ -110,7 +110,7 @@ class QuickStartRepositoryTest : BaseUnitTest() { event?.getContentIfNotHandled()?.let { quickStartPrompts.add(it) } } quickStartRepository.quickStartMenuStep.observeForever { event -> - quickStartMenuStep.add(event!!) + event?.let { quickStartMenuStep.add(it) } } site = SiteModel() site.id = siteLocalId From b081c1d3a98b7aac20793a3d0a5f3a59b0e88a4f Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Mon, 9 Oct 2023 15:00:10 -0400 Subject: [PATCH 18/23] Refactor: set the constants to public so they can be used for quickstart snackbars within more menu --- .../src/main/java/org/wordpress/android/util/SnackbarItem.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/SnackbarItem.kt b/WordPress/src/main/java/org/wordpress/android/util/SnackbarItem.kt index d54fbb97c472..ac6b3a1e999f 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/SnackbarItem.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/SnackbarItem.kt @@ -9,8 +9,8 @@ import java.lang.ref.WeakReference // Taken from com.google.android.material.snackbar.SnackbarManager.java // Did not find a way to get them directly from the android framework for now -private const val SHORT_DURATION_MS = 1500L -private const val LONG_DURATION_MS = 2750L +const val SHORT_DURATION_MS = 1500L +const val LONG_DURATION_MS = 2750L const val INDEFINITE_SNACKBAR_NOT_ALLOWED = "Snackbar.LENGTH_INDEFINITE not allowed in getSnackbarDurationMs." From f6936c3c8836430aa5fb9208efa57355f8b97ad6 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Mon, 9 Oct 2023 15:02:12 -0400 Subject: [PATCH 19/23] Add callback to tie in compose views; they don't show traditional snackbars, so a hook is needed to properly show.This is a lightweight implementation to support unified dashboard quick start. --- .../android/util/SnackbarSequencer.kt | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/SnackbarSequencer.kt b/WordPress/src/main/java/org/wordpress/android/util/SnackbarSequencer.kt index c94314875da4..a79f242626d7 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/SnackbarSequencer.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/SnackbarSequencer.kt @@ -24,6 +24,7 @@ import kotlin.coroutines.CoroutineContext const val QUEUE_SIZE_LIMIT: Int = 5 @Singleton +@Suppress("NestedBlockDepth") class SnackbarSequencer @Inject constructor( private val uiHelper: UiHelpers, private val wpSnackbarWrapper: WPSnackbarWrapper, @@ -35,10 +36,16 @@ class SnackbarSequencer @Inject constructor( private var lastSnackBarReference: SoftReference? = null + private var showSnackbarComposeCallback: ((SnackbarItem) -> Unit)? = null + + fun setComposeSnackbarCallback(callback: (SnackbarItem?) -> Unit) { showSnackbarComposeCallback = callback } + + fun clearComposeSnackbarCallback() { showSnackbarComposeCallback = null } + override val coroutineContext: CoroutineContext get() = mainDispatcher + job - fun enqueue(item: SnackbarItem) { + fun enqueue(item: SnackbarItem) { // This needs to be run on a single thread or synchronized - we are accessing a critical zone (`snackBarQueue`) launch { AppLog.d(T.UTILS, "SnackbarSequencer > New item added") @@ -66,7 +73,16 @@ class SnackbarSequencer @Inject constructor( } as? Activity if (context != null && isContextAlive(context)) { - item?.let { prepareSnackBar(context, it)?.show() } + item?.let { + if (showSnackbarComposeCallback != null) { + showSnackbarComposeCallback?.invoke(it) + } else { + // prepareSnackBar(context, it)?.show() + prepareSnackBar(context, it)?.show() + AppLog.d(T.UTILS, "SnackbarSequencer > before delay") + } + } + AppLog.d(T.UTILS, "SnackbarSequencer > before delay") /** * Delay showing the next snackbar only if the current snack bar is important. From 1a38f7f616341db5d0d758104bc8f43ad274d2e0 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Mon, 9 Oct 2023 15:03:03 -0400 Subject: [PATCH 20/23] Add support for showing quickStart snackbars for quick start. --- .../android/ui/mysite/menu/MenuViewModel.kt | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuViewModel.kt index 3b3a530bed2b..e663d041ae26 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuViewModel.kt @@ -1,11 +1,14 @@ package org.wordpress.android.ui.mysite.menu +import androidx.compose.material.SnackbarDuration import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.QuickStartStore import org.wordpress.android.fluxc.store.QuickStartStore.QuickStartTask @@ -22,9 +25,14 @@ import org.wordpress.android.ui.mysite.items.listitem.ListItemAction import org.wordpress.android.ui.mysite.items.listitem.SiteItemsBuilder import org.wordpress.android.ui.pages.SnackbarMessageHolder import org.wordpress.android.ui.quickstart.QuickStartEvent +import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.util.JetpackMigrationLanguageUtil +import org.wordpress.android.util.LONG_DURATION_MS import org.wordpress.android.util.LocaleManagerWrapper +import org.wordpress.android.util.SHORT_DURATION_MS +import org.wordpress.android.util.SnackbarItem import org.wordpress.android.util.merge +import org.wordpress.android.viewmodel.ContextProvider import org.wordpress.android.viewmodel.Event import org.wordpress.android.viewmodel.ScopedViewModel import java.util.Locale @@ -41,6 +49,8 @@ class MenuViewModel @Inject constructor( private val quickStartRepository: QuickStartRepository, private val selectedSiteRepository: SelectedSiteRepository, private val siteItemsBuilder: SiteItemsBuilder, + private val contextProvider: ContextProvider, + private val uiHelpers: UiHelpers, @param:Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher, ) : ScopedViewModel(bgDispatcher) { private val _onNavigation = MutableLiveData>() @@ -55,8 +65,10 @@ class MenuViewModel @Inject constructor( val refreshAppLanguage: LiveData = _refreshAppLanguage private val _uiState = MutableStateFlow(MenuViewState(items = emptyList())) + val uiState: StateFlow = _uiState - val uiState: StateFlow = _uiState + private val _snackbar = MutableSharedFlow() + val snackBar = _snackbar.asSharedFlow() private var quickStartEvent: QuickStartEvent? = null private var isStarted = false @@ -91,7 +103,8 @@ class MenuViewModel @Inject constructor( it.toMenuItemState() }.toList() - _uiState.value = MenuViewState(items = applyFocusPointIfNeeded(currentItems)) + _uiState.value = MenuViewState( + items = applyFocusPointIfNeeded(currentItems)) rebuildSiteItemsForJetpackCapabilities(site) } @@ -183,8 +196,10 @@ class MenuViewModel @Inject constructor( } } - fun onResume() { - removeFocusPoints() + fun showSnackbarRequest(item: SnackbarItem) { + launch(bgDispatcher) { + handleShowSnackbarRequest(item) + } } private fun removeFocusPoints() { @@ -199,10 +214,32 @@ class MenuViewModel @Inject constructor( _uiState.value = MenuViewState(items = items) } } + private fun clearQuickStartEvent() { quickStartEvent = null } + /* + * This creates a very lightweight snackbar messages for quick start. No action events and no icons. At the + * point of this function execution, the snackbar is already created and ready to be shown. If in the future, + * the entire snackbar creation process should be refactored to be handle both compose and non-compose. + */ + private suspend fun handleShowSnackbarRequest(item: SnackbarItem) { + val snackbarMessage = SnackbarMessage( + message = uiHelpers.getTextOfUiString(contextProvider.getContext(), item.info.textRes).toString(), + actionLabel = item.action?.let { + uiHelpers.getTextOfUiString(contextProvider.getContext(), it.textRes).toString() + }, + // these values are set when the snackbar is created in SnackbarItem, this just reverses that + duration = when ((item.info.duration).toLong()) { + LONG_DURATION_MS -> SnackbarDuration.Long + SHORT_DURATION_MS -> SnackbarDuration.Short + else -> SnackbarDuration.Short + } + ) + _snackbar.emit(snackbarMessage) + } + private fun emitLanguageRefreshIfNeeded(languageCode: String) { if (languageCode.isNotEmpty()) { val shouldEmitLanguageRefresh = !localeManagerWrapper.isSameLanguage(languageCode) @@ -220,4 +257,14 @@ class MenuViewModel @Inject constructor( jetpackCapabilitiesUseCase.clear() super.onCleared() } + + fun onResume() { + removeFocusPoints() + } + + data class SnackbarMessage( + val message: String, + val actionLabel: String? = null, + val duration: SnackbarDuration + ) } From 501dd856ef18276585a5a3e6f66ecf1bf6bf7f3a Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Mon, 9 Oct 2023 15:03:47 -0400 Subject: [PATCH 21/23] Add support for showing quickStart snackbars for quick start by hooking into the composeSnackbarCallback in SnackbarSequencer --- .../android/ui/mysite/menu/MenuActivity.kt | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt index 82f53680f014..ea085da61863 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/menu/MenuActivity.kt @@ -25,9 +25,12 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material.ContentAlpha import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold +import androidx.compose.material.SnackbarHost import androidx.compose.material.Text +import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -104,6 +107,11 @@ class MenuActivity : AppCompatActivity() { viewModel.navigation.observe(this) { handleNavigationAction(it.getContentIfNotHandled()) } viewModel.onSnackbarMessage.observe(this) { showSnackbar(it.getContentIfNotHandled()) } viewModel.onQuickStartMySitePrompts.observe(this) { handleActiveTutorialPrompt(it.getContentIfNotHandled()) } + + // Set the Compose callback for SnackbarSequencer + snackbarSequencer.setComposeSnackbarCallback { item -> + item?.let { viewModel.showSnackbarRequest(it) } + } } @Suppress("ComplexMethod", "LongMethod") @@ -142,7 +150,7 @@ class MenuActivity : AppCompatActivity() { snackbarSequencer.enqueue( SnackbarItem( info = SnackbarItem.Info( - view = parent.findViewById(R.id.coordinator), + view = window.decorView.findViewById(android.R.id.content), textRes = holder.message, duration = holder.duration, isImportant = holder.isImportant @@ -176,10 +184,21 @@ class MenuActivity : AppCompatActivity() { viewModel.onResume() } + override fun onStop() { + snackbarSequencer.clearComposeSnackbarCallback() + super.onStop() + } + @Composable @SuppressLint("UnusedMaterialScaffoldPaddingParameter") fun MenuScreen(modifier: Modifier = Modifier) { + val scaffoldState = rememberScaffoldState() + Scaffold( + scaffoldState = scaffoldState, + snackbarHost = { snackbarHostState -> + SnackbarHost(hostState = snackbarHostState) + }, topBar = { MainTopAppBar( title = stringResource(id = R.string.my_site_section_screen_title), @@ -191,8 +210,15 @@ class MenuActivity : AppCompatActivity() { MenuContent(modifier = modifier) } ) + LaunchedEffect(viewModel.snackBar) { + viewModel.snackBar.collect { message -> + scaffoldState.snackbarHostState.showSnackbar( + message.message, message.actionLabel, message.duration) + } + } } + @Composable fun MenuContent(modifier: Modifier = Modifier) { val uiState by viewModel.uiState.collectAsState() @@ -214,6 +240,7 @@ class MenuActivity : AppCompatActivity() { } } } + @Composable fun MySiteListItemHeader(headerItem: MenuItemState.MenuHeaderItem) { Text( From 69ba37cad36a872991ed0687f56fd7417cc6d56f Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Mon, 9 Oct 2023 15:04:19 -0400 Subject: [PATCH 22/23] Add a delay before showing the snackbar because the QuickStartEvent arrives too quickly. --- .../wordpress/android/ui/media/MediaGridFragment.java | 9 ++++----- .../android/ui/publicize/PublicizeListFragment.java | 5 +++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridFragment.java index 993ffe0cfc9d..721771c1e569 100755 --- a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaGridFragment.java @@ -56,6 +56,7 @@ import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.util.QuickStartUtilsWrapper; import org.wordpress.android.util.SnackbarItem; +import org.wordpress.android.util.SnackbarItem.Info; import org.wordpress.android.util.SnackbarSequencer; import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.WPMediaUtils; @@ -253,11 +254,9 @@ private void showQuickStartSnackbar() { R.string.quick_start_dialog_upload_media_message_short_plus, R.drawable.ic_plus_white_12dp ); - mSnackbarSequencer.enqueue( - new SnackbarItem( - new SnackbarItem.Info(getSnackbarParent(), new UiStringText(title), Snackbar.LENGTH_LONG) - ) - ); + new Handler().postDelayed(() -> mSnackbarSequencer.enqueue( + new SnackbarItem(new Info(getSnackbarParent(), new UiStringText(title), Snackbar.LENGTH_LONG)) + ), 500L); } private View getSnackbarParent() { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/publicize/PublicizeListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/publicize/PublicizeListFragment.java index 73ce4003a960..dfdac6f4d56f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/publicize/PublicizeListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/publicize/PublicizeListFragment.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.os.Bundle; +import android.os.Handler; import android.text.Spannable; import android.view.LayoutInflater; import android.view.View; @@ -297,9 +298,9 @@ private void showQuickStartSnackbar() { requireContext(), R.string.quick_start_dialog_enable_sharing_message_short_connections ); - mSnackbarSequencer.enqueue( + new Handler().postDelayed(() -> mSnackbarSequencer.enqueue( new SnackbarItem(new Info(mRecycler, new UiStringText(title), Snackbar.LENGTH_LONG)) - ); + ), 500L); } @Override From 52326d1d6e252efa45b1389fae4b4fc69068a497 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Mon, 9 Oct 2023 15:11:50 -0400 Subject: [PATCH 23/23] Refactor: set mocks for new di injections --- .../wordpress/android/ui/mysite/menu/MenuViewModelTest.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/menu/MenuViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/menu/MenuViewModelTest.kt index 3e53640318ac..6c0af33cfd40 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/menu/MenuViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/menu/MenuViewModelTest.kt @@ -26,9 +26,11 @@ import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository import org.wordpress.android.ui.mysite.items.listitem.ListItemAction import org.wordpress.android.ui.mysite.items.listitem.SiteItemsBuilder import org.wordpress.android.ui.utils.ListItemInteraction +import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.ui.utils.UiString import org.wordpress.android.util.JetpackMigrationLanguageUtil import org.wordpress.android.util.LocaleManagerWrapper +import org.wordpress.android.viewmodel.ContextProvider @ExperimentalCoroutinesApi @RunWith(MockitoJUnitRunner::class) @@ -42,6 +44,8 @@ class MenuViewModelTest : BaseUnitTest() { private val selectedSiteRepository: SelectedSiteRepository = mock() private val siteItemsBuilder: SiteItemsBuilder = mock() private val refreshAppLanguageObserver: Observer = mock() + private val contextProvider: ContextProvider = mock() + private val uiHelpers: UiHelpers = mock() private lateinit var viewModel: MenuViewModel @@ -62,6 +66,8 @@ class MenuViewModelTest : BaseUnitTest() { quickStartRepository, selectedSiteRepository, siteItemsBuilder, + contextProvider, + uiHelpers, testDispatcher() )