From 69eb3f7f3c0a168314e1a45de90deb0a7af229fa Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Tue, 28 May 2024 16:51:57 -0300 Subject: [PATCH 01/10] Add create post FAB to Reader --- .../android/ui/main/WPMainActivity.java | 18 ++++++---- .../viewmodel/main/WPMainActivityViewModel.kt | 33 +++++++++++-------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java index 3fa7113bc2be..76e2d3a654ef 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java @@ -760,7 +760,10 @@ private void initViewModel() { .show(getSupportFragmentManager(), FeatureAnnouncementDialogFragment.TAG); }); - mFloatingActionButton.setOnClickListener(v -> mViewModel.onFabClicked(getSelectedSite())); + mFloatingActionButton.setOnClickListener(v -> { + PageType currentPage = mBottomNav != null ? mBottomNav.getCurrentSelectedPage() : null; + mViewModel.onFabClicked(getSelectedSite(), currentPage); + }); mFloatingActionButton.setOnLongClickListener(v -> { if (v.isHapticFeedbackEnabled()) { @@ -844,7 +847,7 @@ private void initViewModel() { // initialized with the most restrictive rights case. This is OK and will be frequently checked // to normalize the UI state whenever mSelectedSite changes. // It also means that the ViewModel must accept a nullable SiteModel. - mViewModel.start(getSelectedSite()); + mViewModel.start(getSelectedSite(), mBottomNav.getCurrentSelectedPage()); } private void triggerCreatePageFlow(ActionType actionType) { @@ -1202,10 +1205,12 @@ protected void onResume() { ProfilingUtils.dump(); ProfilingUtils.stop(); + PageType currentPage = mBottomNav != null ? mBottomNav.getCurrentSelectedPage() : null; + mViewModel.onResume( getSelectedSite(), - mSelectedSiteRepository.hasSelectedSite() && mBottomNav != null - && mBottomNav.getCurrentSelectedPage() == PageType.MY_SITE + mSelectedSiteRepository.hasSelectedSite(), + currentPage ); checkForInAppUpdate(); @@ -1291,8 +1296,9 @@ public void onPageChanged(int position) { } mViewModel.onPageChanged( - mSiteStore.hasSite() && pageType == PageType.MY_SITE, - getSelectedSite() + getSelectedSite(), + mSiteStore.hasSite(), + pageType ); } diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt index 2a3c932cd678..ca742cc1a86f 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt @@ -30,6 +30,7 @@ import org.wordpress.android.ui.main.MainActionListItem.ActionType.NO_ACTION import org.wordpress.android.ui.main.MainActionListItem.AnswerBloggingPromptAction import org.wordpress.android.ui.main.MainActionListItem.CreateAction import org.wordpress.android.ui.main.MainFabUiState +import org.wordpress.android.ui.main.WPMainNavigationView.PageType import org.wordpress.android.ui.mysite.SelectedSiteRepository import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptAttribution import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository @@ -42,8 +43,8 @@ import org.wordpress.android.util.BuildConfigWrapper import org.wordpress.android.util.FluxCUtils import org.wordpress.android.util.SiteUtils.hasFullAccessToContent import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper -import org.wordpress.android.util.mapSafe import org.wordpress.android.util.mapNullable +import org.wordpress.android.util.mapSafe import org.wordpress.android.util.merge import org.wordpress.android.viewmodel.Event import org.wordpress.android.viewmodel.ScopedViewModel @@ -149,7 +150,7 @@ class WPMainActivityViewModel @Inject constructor( val isSignedInWPComOrHasWPOrgSite: Boolean get() = FluxCUtils.isSignedInWPComOrHasWPOrgSite(accountStore, siteStore) - fun start(site: SiteModel?) { + fun start(site: SiteModel?, page: PageType) { if (isStarted) return isStarted = true @@ -161,13 +162,13 @@ class WPMainActivityViewModel @Inject constructor( setMainFabUiState(false, site) - launch { loadMainActions(site) } + launch { loadMainActions(site, page) } updateFeatureAnnouncements() } @Suppress("LongMethod") - private suspend fun loadMainActions(site: SiteModel?, onFabClicked: Boolean = false) { + private suspend fun loadMainActions(site: SiteModel?, page: PageType?, onFabClicked: Boolean = false) { val actionsList = ArrayList() if (bloggingPromptsSettingsHelper.shouldShowPromptsFeature()) { val prompt = site?.let { @@ -215,7 +216,7 @@ class WPMainActivityViewModel @Inject constructor( ) ) } - if (hasFullAccessToContent(site)) { + if (hasFullAccessToContent(site) && page == PageType.MY_SITE) { actionsList.add( CreateAction( actionType = CREATE_NEW_PAGE, @@ -247,7 +248,7 @@ class WPMainActivityViewModel @Inject constructor( private fun onAnswerPromptActionClicked(promptId: Int, attribution: BloggingPromptAttribution) { analyticsTracker.track( Stat.MY_SITE_CREATE_SHEET_ANSWER_PROMPT_TAPPED, - mapOf("attribution" to attribution.value).filterValues { !it.isNullOrBlank() } + mapOf("attribution" to attribution.value).filterValues { it.isNotBlank() } ) _isBottomSheetShowing.postValue(Event(false)) _createPostWithBloggingPrompt.postValue(promptId) @@ -264,7 +265,7 @@ class WPMainActivityViewModel @Inject constructor( } } - fun onFabClicked(site: SiteModel?) { + fun onFabClicked(site: SiteModel?, page: PageType?) { appPrefsWrapper.setMainFabTooltipDisabled(true) setMainFabUiState(true, site) @@ -278,7 +279,7 @@ class WPMainActivityViewModel @Inject constructor( // Reload main actions, since the first time this is initialized the SiteModel may not contain the // latest info. - loadMainActions(site, onFabClicked = true) + loadMainActions(site, page, onFabClicked = true) analyticsTracker.track(Stat.MY_SITE_CREATE_SHEET_SHOWN) _isBottomSheetShowing.postValue(Event(true)) @@ -289,8 +290,9 @@ class WPMainActivityViewModel @Inject constructor( } } - fun onPageChanged(isOnMySitePageWithValidSite: Boolean, site: SiteModel?) { - val showFab = if (buildConfigWrapper.isCreateFabEnabled) isOnMySitePageWithValidSite else false + fun onPageChanged(site: SiteModel?, hasValidSite: Boolean, page: PageType) { + val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && + page in listOf(PageType.MY_SITE, PageType.READER) setMainFabUiState(showFab, site) } @@ -298,8 +300,9 @@ class WPMainActivityViewModel @Inject constructor( _switchToMeTab.value = Event(Unit) } - fun onResume(site: SiteModel?, isOnMySitePageWithValidSite: Boolean) { - val showFab = if (buildConfigWrapper.isCreateFabEnabled) isOnMySitePageWithValidSite else false + fun onResume(site: SiteModel?, hasValidSite: Boolean, page: PageType?) { + val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && + page in listOf(PageType.MY_SITE, PageType.READER) setMainFabUiState(showFab, site) checkAndShowFeatureAnnouncement() @@ -328,6 +331,10 @@ class WPMainActivityViewModel @Inject constructor( } private fun setMainFabUiState(isFabVisible: Boolean, site: SiteModel?) { + // TODO thomashortadev, improve hasFullAccessToContent logic to actually return which content types are + // available to properly adjust the CreateContentMessageId. Or at least rename it to something more meaningful, + // since it currently basically checks if the "Create Page" option is available. Though the VoiceToContent item + // is also using this check for some reason (need to check why with AnnMarie). val newState = MainFabUiState( isFabVisible = isFabVisible, isFabTooltipVisible = if (appPrefsWrapper.isMainFabTooltipDisabled()) false else isFabVisible, @@ -375,7 +382,7 @@ class WPMainActivityViewModel @Inject constructor( selectedSiteRepository.removeSite() } - fun triggerCreatePageFlow(){ + fun triggerCreatePageFlow() { _createAction.postValue(CREATE_NEW_PAGE_FROM_PAGES_CARD) } From e9864c50f462e7bc7cbd3d1493d56aa0e9cb7787 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Tue, 28 May 2024 17:10:41 -0300 Subject: [PATCH 02/10] Show correct message based on current page --- .../android/ui/main/WPMainActivity.java | 3 ++- .../viewmodel/main/WPMainActivityViewModel.kt | 26 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java index 76e2d3a654ef..f40ee4cd4ea9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java @@ -770,7 +770,8 @@ private void initViewModel() { v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } - int messageId = mViewModel.getCreateContentMessageId(getSelectedSite()); + PageType currentPage = mBottomNav != null ? mBottomNav.getCurrentSelectedPage() : null; + int messageId = mViewModel.getCreateContentMessageId(getSelectedSite(), currentPage); Toast.makeText(v.getContext(), messageId, Toast.LENGTH_SHORT).show(); return true; diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt index ca742cc1a86f..e93f8117f2b0 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt @@ -160,7 +160,7 @@ class WPMainActivityViewModel @Inject constructor( } } - setMainFabUiState(false, site) + setMainFabUiState(false, site, page) launch { loadMainActions(site, page) } @@ -216,7 +216,7 @@ class WPMainActivityViewModel @Inject constructor( ) ) } - if (hasFullAccessToContent(site) && page == PageType.MY_SITE) { + if (canCreatePage(site, page)) { actionsList.add( CreateAction( actionType = CREATE_NEW_PAGE, @@ -267,7 +267,7 @@ class WPMainActivityViewModel @Inject constructor( fun onFabClicked(site: SiteModel?, page: PageType?) { appPrefsWrapper.setMainFabTooltipDisabled(true) - setMainFabUiState(true, site) + setMainFabUiState(true, site, page) _showQuickStarInBottomSheet.postValue(quickStartRepository.activeTask.value == PUBLISH_POST) @@ -293,7 +293,7 @@ class WPMainActivityViewModel @Inject constructor( fun onPageChanged(site: SiteModel?, hasValidSite: Boolean, page: PageType) { val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && page in listOf(PageType.MY_SITE, PageType.READER) - setMainFabUiState(showFab, site) + setMainFabUiState(showFab, site, page) } fun onOpenLoginPage() = launch { @@ -303,7 +303,7 @@ class WPMainActivityViewModel @Inject constructor( fun onResume(site: SiteModel?, hasValidSite: Boolean, page: PageType?) { val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && page in listOf(PageType.MY_SITE, PageType.READER) - setMainFabUiState(showFab, site) + setMainFabUiState(showFab, site, page) checkAndShowFeatureAnnouncement() } @@ -330,22 +330,18 @@ class WPMainActivityViewModel @Inject constructor( } } - private fun setMainFabUiState(isFabVisible: Boolean, site: SiteModel?) { - // TODO thomashortadev, improve hasFullAccessToContent logic to actually return which content types are - // available to properly adjust the CreateContentMessageId. Or at least rename it to something more meaningful, - // since it currently basically checks if the "Create Page" option is available. Though the VoiceToContent item - // is also using this check for some reason (need to check why with AnnMarie). + private fun setMainFabUiState(isFabVisible: Boolean, site: SiteModel?, page: PageType?) { val newState = MainFabUiState( isFabVisible = isFabVisible, isFabTooltipVisible = if (appPrefsWrapper.isMainFabTooltipDisabled()) false else isFabVisible, - CreateContentMessageId = getCreateContentMessageId(site) + CreateContentMessageId = getCreateContentMessageId(site, page) ) _fabUiState.value = newState } - fun getCreateContentMessageId(site: SiteModel?): Int = - if (hasFullAccessToContent(site)) { + fun getCreateContentMessageId(site: SiteModel?, page: PageType?): Int = + if (canCreatePage(site, page)) { R.string.create_post_page_fab_tooltip } else { R.string.create_post_page_fab_tooltip_contributors @@ -398,6 +394,10 @@ class WPMainActivityViewModel @Inject constructor( this._mySiteDashboardRefreshRequested.value = Event(Unit) } + private fun canCreatePage(site: SiteModel?, page: PageType?): Boolean { + return hasFullAccessToContent(site) && page == PageType.MY_SITE + } + data class FocusPointInfo( val task: QuickStartTask, val isVisible: Boolean From 659fb09f13e39a98d95ab8e64165ab2dfd4d3964 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Tue, 28 May 2024 17:17:15 -0300 Subject: [PATCH 03/10] Add showFabForPage helper function This is where logic should be added if we need to also limit specific scenarios for showing the button in a specific page (such as a feature flag). --- .../android/viewmodel/main/WPMainActivityViewModel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt index e93f8117f2b0..48d062fc764c 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt @@ -291,8 +291,7 @@ class WPMainActivityViewModel @Inject constructor( } fun onPageChanged(site: SiteModel?, hasValidSite: Boolean, page: PageType) { - val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && - page in listOf(PageType.MY_SITE, PageType.READER) + val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && showFabForPage(page) setMainFabUiState(showFab, site, page) } @@ -301,8 +300,7 @@ class WPMainActivityViewModel @Inject constructor( } fun onResume(site: SiteModel?, hasValidSite: Boolean, page: PageType?) { - val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && - page in listOf(PageType.MY_SITE, PageType.READER) + val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && showFabForPage(page) setMainFabUiState(showFab, site, page) checkAndShowFeatureAnnouncement() @@ -398,6 +396,8 @@ class WPMainActivityViewModel @Inject constructor( return hasFullAccessToContent(site) && page == PageType.MY_SITE } + private fun showFabForPage(page: PageType?) = page in listOf(PageType.MY_SITE, PageType.READER) + data class FocusPointInfo( val task: QuickStartTask, val isVisible: Boolean From 460d82bddc65d22f67b48eaa29346446925b9096 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Wed, 29 May 2024 14:28:24 -0300 Subject: [PATCH 04/10] Create FeatureConfig for ReaderFloatingButton --- WordPress/build.gradle | 1 + .../config/ReaderFloatingButtonFeatureConfig.kt | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/util/config/ReaderFloatingButtonFeatureConfig.kt diff --git a/WordPress/build.gradle b/WordPress/build.gradle index ffb8fa889646..04dd425afdf4 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -154,6 +154,7 @@ android { buildConfigField "boolean", "READER_TAGS_FEED", "false" buildConfigField "boolean", "READER_ANNOUNCEMENT_CARD", "false" buildConfigField "boolean", "VOICE_TO_CONTENT", "false" + buildConfigField "boolean", "READER_FLOATING_BUTTON", "false" // Override these constants in jetpack product flavor to enable/ disable features buildConfigField "boolean", "ENABLE_SITE_CREATION", "true" diff --git a/WordPress/src/main/java/org/wordpress/android/util/config/ReaderFloatingButtonFeatureConfig.kt b/WordPress/src/main/java/org/wordpress/android/util/config/ReaderFloatingButtonFeatureConfig.kt new file mode 100644 index 000000000000..c04c1ddb287e --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/util/config/ReaderFloatingButtonFeatureConfig.kt @@ -0,0 +1,16 @@ +package org.wordpress.android.util.config + +import org.wordpress.android.BuildConfig +import org.wordpress.android.annotation.Feature +import javax.inject.Inject + +private const val READER_FLOATING_BUTTON_REMOTE_FIELD = "reader_floating_button" + +@Feature(READER_FLOATING_BUTTON_REMOTE_FIELD, false) +class ReaderFloatingButtonFeatureConfig @Inject constructor( + appConfig: AppConfig +) : FeatureConfig( + appConfig, + BuildConfig.READER_FLOATING_BUTTON, + READER_FLOATING_BUTTON_REMOTE_FIELD +) From 0cfdf6223081e125ada89e3077f5470a858510e0 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Wed, 29 May 2024 14:28:56 -0300 Subject: [PATCH 05/10] Move some FAB and content creation sheet logic to Helper --- .../CreateContentFloatingButtonHelper.kt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/main/utils/CreateContentFloatingButtonHelper.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/utils/CreateContentFloatingButtonHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/utils/CreateContentFloatingButtonHelper.kt new file mode 100644 index 000000000000..58938a201341 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/utils/CreateContentFloatingButtonHelper.kt @@ -0,0 +1,36 @@ +package org.wordpress.android.ui.main.utils + +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper +import org.wordpress.android.ui.main.WPMainNavigationView.PageType +import org.wordpress.android.ui.voicetocontent.VoiceToContentFeatureUtils +import org.wordpress.android.util.BuildConfigWrapper +import org.wordpress.android.util.SiteUtils +import org.wordpress.android.util.config.ReaderFloatingButtonFeatureConfig +import javax.inject.Inject + +class CreateContentFloatingButtonHelper @Inject constructor( + private val voiceToContentFeatureUtils: VoiceToContentFeatureUtils, + private val readerFloatingButtonFeatureConfig: ReaderFloatingButtonFeatureConfig, + private val bloggingPromptsSettingsHelper: BloggingPromptsSettingsHelper, + private val buildConfigWrapper: BuildConfigWrapper, +) { + fun shouldShowFabForPage(page: PageType?): Boolean { + val enabledForPage = page == PageType.MY_SITE || + (page == PageType.READER && readerFloatingButtonFeatureConfig.isEnabled()) + return buildConfigWrapper.isCreateFabEnabled && enabledForPage + } + + @Suppress("FunctionOnlyReturningConstant") + fun canCreatePost(): Boolean = true // for completeness + + fun canCreatePage(site: SiteModel?, page: PageType?): Boolean { + return SiteUtils.hasFullAccessToContent(site) && page == PageType.MY_SITE + } + + fun canCreatePostFromAudio(site: SiteModel?): Boolean { + return voiceToContentFeatureUtils.isVoiceToContentEnabled() && SiteUtils.hasFullAccessToContent(site) + } + + suspend fun canCreatePromptAnswer(): Boolean = bloggingPromptsSettingsHelper.shouldShowPromptsFeature() +} From 9a0e7233b6e21a33e227d0b020e06b826722746e Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Wed, 29 May 2024 14:29:21 -0300 Subject: [PATCH 06/10] Use Helper in ViewModel for some FAB logic --- .../viewmodel/main/WPMainActivityViewModel.kt | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt index 48d062fc764c..5e922317d834 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt @@ -18,7 +18,6 @@ import org.wordpress.android.fluxc.store.QuickStartStore.QuickStartTask import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.fluxc.store.bloggingprompts.BloggingPromptsStore import org.wordpress.android.modules.UI_THREAD -import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper import org.wordpress.android.ui.debug.preferences.DebugPrefs import org.wordpress.android.ui.main.MainActionListItem import org.wordpress.android.ui.main.MainActionListItem.ActionType @@ -31,13 +30,13 @@ import org.wordpress.android.ui.main.MainActionListItem.AnswerBloggingPromptActi import org.wordpress.android.ui.main.MainActionListItem.CreateAction import org.wordpress.android.ui.main.MainFabUiState import org.wordpress.android.ui.main.WPMainNavigationView.PageType +import org.wordpress.android.ui.main.utils.CreateContentFloatingButtonHelper import org.wordpress.android.ui.mysite.SelectedSiteRepository import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptAttribution import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.ui.prefs.privacy.banner.domain.ShouldAskPrivacyConsent import org.wordpress.android.ui.utils.UiString.UiStringText -import org.wordpress.android.ui.voicetocontent.VoiceToContentFeatureUtils import org.wordpress.android.ui.whatsnew.FeatureAnnouncementProvider import org.wordpress.android.util.BuildConfigWrapper import org.wordpress.android.util.FluxCUtils @@ -66,11 +65,10 @@ class WPMainActivityViewModel @Inject constructor( private val selectedSiteRepository: SelectedSiteRepository, private val accountStore: AccountStore, private val siteStore: SiteStore, - private val bloggingPromptsSettingsHelper: BloggingPromptsSettingsHelper, private val bloggingPromptsStore: BloggingPromptsStore, @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher, private val shouldAskPrivacyConsent: ShouldAskPrivacyConsent, - private val voiceToContentFeatureUtils: VoiceToContentFeatureUtils + private val createContentFloatingButtonHelper: CreateContentFloatingButtonHelper, ) : ScopedViewModel(mainDispatcher) { private var isStarted = false @@ -170,7 +168,7 @@ class WPMainActivityViewModel @Inject constructor( @Suppress("LongMethod") private suspend fun loadMainActions(site: SiteModel?, page: PageType?, onFabClicked: Boolean = false) { val actionsList = ArrayList() - if (bloggingPromptsSettingsHelper.shouldShowPromptsFeature()) { + if (createContentFloatingButtonHelper.canCreatePromptAnswer()) { val prompt = site?.let { bloggingPromptsStore.getPromptForDate(it, Date()).firstOrNull()?.model } @@ -184,7 +182,7 @@ class WPMainActivityViewModel @Inject constructor( promptId = prompt.id, attribution = BloggingPromptAttribution.fromPrompt(prompt), onClickAction = ::onAnswerPromptActionClicked, - onHelpAction = ::onHelpPrompActionClicked + onHelpAction = ::onHelpPromptActionClicked ) ) } @@ -198,15 +196,19 @@ class WPMainActivityViewModel @Inject constructor( onClickAction = null ) ) - actionsList.add( - CreateAction( - actionType = CREATE_NEW_POST, - iconRes = R.drawable.ic_posts_white_24dp, - labelRes = R.string.my_site_bottom_sheet_add_post, - onClickAction = ::onCreateActionClicked + + if (createContentFloatingButtonHelper.canCreatePost()) { + actionsList.add( + CreateAction( + actionType = CREATE_NEW_POST, + iconRes = R.drawable.ic_posts_white_24dp, + labelRes = R.string.my_site_bottom_sheet_add_post, + onClickAction = ::onCreateActionClicked + ) ) - ) - if (voiceToContentFeatureUtils.isVoiceToContentEnabled() && hasFullAccessToContent(site)) { + } + + if (createContentFloatingButtonHelper.canCreatePostFromAudio(site)) { actionsList.add( CreateAction( actionType = ActionType.CREATE_NEW_POST_FROM_AUDIO, @@ -216,7 +218,8 @@ class WPMainActivityViewModel @Inject constructor( ) ) } - if (canCreatePage(site, page)) { + + if (createContentFloatingButtonHelper.canCreatePage(site, page)) { actionsList.add( CreateAction( actionType = CREATE_NEW_PAGE, @@ -254,7 +257,7 @@ class WPMainActivityViewModel @Inject constructor( _createPostWithBloggingPrompt.postValue(promptId) } - private fun onHelpPrompActionClicked() { + private fun onHelpPromptActionClicked() { analyticsTracker.track(Stat.MY_SITE_CREATE_SHEET_PROMPT_HELP_TAPPED) _openBloggingPromptsOnboarding.call() } @@ -291,7 +294,7 @@ class WPMainActivityViewModel @Inject constructor( } fun onPageChanged(site: SiteModel?, hasValidSite: Boolean, page: PageType) { - val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && showFabForPage(page) + val showFab = hasValidSite && createContentFloatingButtonHelper.shouldShowFabForPage(page) setMainFabUiState(showFab, site, page) } @@ -300,7 +303,7 @@ class WPMainActivityViewModel @Inject constructor( } fun onResume(site: SiteModel?, hasValidSite: Boolean, page: PageType?) { - val showFab = buildConfigWrapper.isCreateFabEnabled && hasValidSite && showFabForPage(page) + val showFab = hasValidSite && createContentFloatingButtonHelper.shouldShowFabForPage(page) setMainFabUiState(showFab, site, page) checkAndShowFeatureAnnouncement() @@ -339,7 +342,7 @@ class WPMainActivityViewModel @Inject constructor( } fun getCreateContentMessageId(site: SiteModel?, page: PageType?): Int = - if (canCreatePage(site, page)) { + if (createContentFloatingButtonHelper.canCreatePage(site, page)) { R.string.create_post_page_fab_tooltip } else { R.string.create_post_page_fab_tooltip_contributors @@ -392,12 +395,6 @@ class WPMainActivityViewModel @Inject constructor( this._mySiteDashboardRefreshRequested.value = Event(Unit) } - private fun canCreatePage(site: SiteModel?, page: PageType?): Boolean { - return hasFullAccessToContent(site) && page == PageType.MY_SITE - } - - private fun showFabForPage(page: PageType?) = page in listOf(PageType.MY_SITE, PageType.READER) - data class FocusPointInfo( val task: QuickStartTask, val isVisible: Boolean From 3937e48a6d0692dff688d23d81f4185a95f8d0c2 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Wed, 29 May 2024 15:05:47 -0300 Subject: [PATCH 07/10] Add/update create sheet and button analytics --- .../android/ui/main/WPMainActivity.java | 2 +- .../CreateContentFloatingButtonHelper.kt | 36 -------- .../ui/main/utils/MainCreateSheetHelper.kt | 91 +++++++++++++++++++ .../viewmodel/main/WPMainActivityViewModel.kt | 60 ++++++------ .../android/analytics/AnalyticsTracker.java | 8 +- 5 files changed, 130 insertions(+), 67 deletions(-) delete mode 100644 WordPress/src/main/java/org/wordpress/android/ui/main/utils/CreateContentFloatingButtonHelper.kt create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelper.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java index f40ee4cd4ea9..e11c550f848c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java @@ -762,7 +762,7 @@ private void initViewModel() { mFloatingActionButton.setOnClickListener(v -> { PageType currentPage = mBottomNav != null ? mBottomNav.getCurrentSelectedPage() : null; - mViewModel.onFabClicked(getSelectedSite(), currentPage); + if (currentPage != null) mViewModel.onFabClicked(getSelectedSite(), currentPage); }); mFloatingActionButton.setOnLongClickListener(v -> { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/utils/CreateContentFloatingButtonHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/utils/CreateContentFloatingButtonHelper.kt deleted file mode 100644 index 58938a201341..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/utils/CreateContentFloatingButtonHelper.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.wordpress.android.ui.main.utils - -import org.wordpress.android.fluxc.model.SiteModel -import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper -import org.wordpress.android.ui.main.WPMainNavigationView.PageType -import org.wordpress.android.ui.voicetocontent.VoiceToContentFeatureUtils -import org.wordpress.android.util.BuildConfigWrapper -import org.wordpress.android.util.SiteUtils -import org.wordpress.android.util.config.ReaderFloatingButtonFeatureConfig -import javax.inject.Inject - -class CreateContentFloatingButtonHelper @Inject constructor( - private val voiceToContentFeatureUtils: VoiceToContentFeatureUtils, - private val readerFloatingButtonFeatureConfig: ReaderFloatingButtonFeatureConfig, - private val bloggingPromptsSettingsHelper: BloggingPromptsSettingsHelper, - private val buildConfigWrapper: BuildConfigWrapper, -) { - fun shouldShowFabForPage(page: PageType?): Boolean { - val enabledForPage = page == PageType.MY_SITE || - (page == PageType.READER && readerFloatingButtonFeatureConfig.isEnabled()) - return buildConfigWrapper.isCreateFabEnabled && enabledForPage - } - - @Suppress("FunctionOnlyReturningConstant") - fun canCreatePost(): Boolean = true // for completeness - - fun canCreatePage(site: SiteModel?, page: PageType?): Boolean { - return SiteUtils.hasFullAccessToContent(site) && page == PageType.MY_SITE - } - - fun canCreatePostFromAudio(site: SiteModel?): Boolean { - return voiceToContentFeatureUtils.isVoiceToContentEnabled() && SiteUtils.hasFullAccessToContent(site) - } - - suspend fun canCreatePromptAnswer(): Boolean = bloggingPromptsSettingsHelper.shouldShowPromptsFeature() -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelper.kt new file mode 100644 index 000000000000..ce9b2917f305 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelper.kt @@ -0,0 +1,91 @@ +package org.wordpress.android.ui.main.utils + +import org.wordpress.android.analytics.AnalyticsTracker.Stat +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper +import org.wordpress.android.ui.main.MainActionListItem +import org.wordpress.android.ui.main.WPMainNavigationView.PageType +import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptAttribution +import org.wordpress.android.ui.voicetocontent.VoiceToContentFeatureUtils +import org.wordpress.android.util.BuildConfigWrapper +import org.wordpress.android.util.SiteUtils +import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper +import org.wordpress.android.util.config.ReaderFloatingButtonFeatureConfig +import java.util.Locale +import javax.inject.Inject + +class MainCreateSheetHelper @Inject constructor( + private val voiceToContentFeatureUtils: VoiceToContentFeatureUtils, + private val readerFloatingButtonFeatureConfig: ReaderFloatingButtonFeatureConfig, + private val bloggingPromptsSettingsHelper: BloggingPromptsSettingsHelper, + private val buildConfigWrapper: BuildConfigWrapper, + private val analyticsTracker: AnalyticsTrackerWrapper, +) { + fun shouldShowFabForPage(page: PageType?): Boolean { + val enabledForPage = page == PageType.MY_SITE || + (page == PageType.READER && readerFloatingButtonFeatureConfig.isEnabled()) + return buildConfigWrapper.isCreateFabEnabled && enabledForPage + } + + @Suppress("FunctionOnlyReturningConstant") + fun canCreatePost(): Boolean = true // for completeness + + fun canCreatePage(site: SiteModel?, page: PageType?): Boolean { + return SiteUtils.hasFullAccessToContent(site) && page == PageType.MY_SITE + } + + fun canCreatePostFromAudio(site: SiteModel?): Boolean { + return voiceToContentFeatureUtils.isVoiceToContentEnabled() && SiteUtils.hasFullAccessToContent(site) + } + + suspend fun canCreatePromptAnswer(): Boolean = bloggingPromptsSettingsHelper.shouldShowPromptsFeature() + + // region Analytics + fun trackActionTapped(page: PageType, actionType: MainActionListItem.ActionType) { + val stat = when (page) { + PageType.MY_SITE -> Stat.MY_SITE_CREATE_SHEET_ACTION_TAPPED + PageType.READER -> Stat.READER_CREATE_SHEET_ACTION_TAPPED + else -> return + } + val properties = mapOf("action" to actionType.name.lowercase(Locale.ROOT)) + analyticsTracker.track(stat, properties) + } + + fun trackAnswerPromptActionTapped(page: PageType, attribution: BloggingPromptAttribution) { + val properties = mapOf("attribution" to attribution.value).filterValues { it.isNotBlank() } + val stat = when (page) { + PageType.MY_SITE -> Stat.MY_SITE_CREATE_SHEET_ANSWER_PROMPT_TAPPED + PageType.READER -> Stat.READER_CREATE_SHEET_ANSWER_PROMPT_TAPPED + else -> return + } + analyticsTracker.track(stat, properties) + } + + fun trackHelpPromptActionTapped(page: PageType) { + val stat = when (page) { + PageType.MY_SITE -> Stat.MY_SITE_CREATE_SHEET_PROMPT_HELP_TAPPED + PageType.READER -> Stat.READER_CREATE_SHEET_PROMPT_HELP_TAPPED + else -> return + } + analyticsTracker.track(stat) + } + + fun trackSheetShown(page: PageType) { + val stat = when (page) { + PageType.MY_SITE -> Stat.MY_SITE_CREATE_SHEET_SHOWN + PageType.READER -> Stat.READER_CREATE_SHEET_SHOWN + else -> return + } + analyticsTracker.track(stat) + } + + fun trackFabShown(page: PageType) { + val stat = when (page) { + PageType.MY_SITE -> Stat.MY_SITE_CREATE_FAB_SHOWN + PageType.READER -> Stat.READER_CREATE_FAB_SHOWN + else -> return + } + analyticsTracker.track(stat) + } + // endregion +} diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt index 5e922317d834..5f0da41f6013 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModel.kt @@ -30,7 +30,7 @@ import org.wordpress.android.ui.main.MainActionListItem.AnswerBloggingPromptActi import org.wordpress.android.ui.main.MainActionListItem.CreateAction import org.wordpress.android.ui.main.MainFabUiState import org.wordpress.android.ui.main.WPMainNavigationView.PageType -import org.wordpress.android.ui.main.utils.CreateContentFloatingButtonHelper +import org.wordpress.android.ui.main.utils.MainCreateSheetHelper import org.wordpress.android.ui.mysite.SelectedSiteRepository import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptAttribution import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository @@ -50,7 +50,6 @@ import org.wordpress.android.viewmodel.ScopedViewModel import org.wordpress.android.viewmodel.SingleLiveEvent import java.io.Serializable import java.util.Date -import java.util.Locale import javax.inject.Inject import javax.inject.Named @@ -68,7 +67,7 @@ class WPMainActivityViewModel @Inject constructor( private val bloggingPromptsStore: BloggingPromptsStore, @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher, private val shouldAskPrivacyConsent: ShouldAskPrivacyConsent, - private val createContentFloatingButtonHelper: CreateContentFloatingButtonHelper, + private val mainCreateSheetHelper: MainCreateSheetHelper, ) : ScopedViewModel(mainDispatcher) { private var isStarted = false @@ -166,9 +165,9 @@ class WPMainActivityViewModel @Inject constructor( } @Suppress("LongMethod") - private suspend fun loadMainActions(site: SiteModel?, page: PageType?, onFabClicked: Boolean = false) { + private suspend fun loadMainActions(site: SiteModel?, page: PageType, onFabClicked: Boolean = false) { val actionsList = ArrayList() - if (createContentFloatingButtonHelper.canCreatePromptAnswer()) { + if (mainCreateSheetHelper.canCreatePromptAnswer()) { val prompt = site?.let { bloggingPromptsStore.getPromptForDate(it, Date()).firstOrNull()?.model } @@ -181,8 +180,14 @@ class WPMainActivityViewModel @Inject constructor( isAnswered = prompt.isAnswered, promptId = prompt.id, attribution = BloggingPromptAttribution.fromPrompt(prompt), - onClickAction = ::onAnswerPromptActionClicked, - onHelpAction = ::onHelpPromptActionClicked + onClickAction = { prompt, attribution -> + onAnswerPromptActionClicked( + prompt, + attribution, + page + ) + }, + onHelpAction = { onHelpPromptActionClicked(page) } ) ) } @@ -197,35 +202,35 @@ class WPMainActivityViewModel @Inject constructor( ) ) - if (createContentFloatingButtonHelper.canCreatePost()) { + if (mainCreateSheetHelper.canCreatePost()) { actionsList.add( CreateAction( actionType = CREATE_NEW_POST, iconRes = R.drawable.ic_posts_white_24dp, labelRes = R.string.my_site_bottom_sheet_add_post, - onClickAction = ::onCreateActionClicked + onClickAction = { onCreateActionClicked(it, page) } ) ) } - if (createContentFloatingButtonHelper.canCreatePostFromAudio(site)) { + if (mainCreateSheetHelper.canCreatePostFromAudio(site)) { actionsList.add( CreateAction( actionType = ActionType.CREATE_NEW_POST_FROM_AUDIO, iconRes = R.drawable.ic_mic_white_24dp, labelRes = R.string.my_site_bottom_sheet_add_post_from_audio, - onClickAction = ::onCreateActionClicked + onClickAction = { onCreateActionClicked(it, page) } ) ) } - if (createContentFloatingButtonHelper.canCreatePage(site, page)) { + if (mainCreateSheetHelper.canCreatePage(site, page)) { actionsList.add( CreateAction( actionType = CREATE_NEW_PAGE, iconRes = R.drawable.ic_pages_white_24dp, labelRes = R.string.my_site_bottom_sheet_add_page, - onClickAction = ::onCreateActionClicked + onClickAction = { onCreateActionClicked(it, page) } ) ) } @@ -234,9 +239,8 @@ class WPMainActivityViewModel @Inject constructor( if (onFabClicked) trackCreateActionsSheetCard(actionsList) } - private fun onCreateActionClicked(actionType: ActionType) { - val properties = mapOf("action" to actionType.name.lowercase(Locale.ROOT)) - analyticsTracker.track(Stat.MY_SITE_CREATE_SHEET_ACTION_TAPPED, properties) + private fun onCreateActionClicked(actionType: ActionType, page: PageType) { + mainCreateSheetHelper.trackActionTapped(page, actionType) _isBottomSheetShowing.postValue(Event(false)) _createAction.postValue(actionType) @@ -248,17 +252,14 @@ class WPMainActivityViewModel @Inject constructor( } } - private fun onAnswerPromptActionClicked(promptId: Int, attribution: BloggingPromptAttribution) { - analyticsTracker.track( - Stat.MY_SITE_CREATE_SHEET_ANSWER_PROMPT_TAPPED, - mapOf("attribution" to attribution.value).filterValues { it.isNotBlank() } - ) + private fun onAnswerPromptActionClicked(promptId: Int, attribution: BloggingPromptAttribution, page: PageType) { + mainCreateSheetHelper.trackAnswerPromptActionTapped(page, attribution) _isBottomSheetShowing.postValue(Event(false)) _createPostWithBloggingPrompt.postValue(promptId) } - private fun onHelpPromptActionClicked() { - analyticsTracker.track(Stat.MY_SITE_CREATE_SHEET_PROMPT_HELP_TAPPED) + private fun onHelpPromptActionClicked(page: PageType) { + mainCreateSheetHelper.trackHelpPromptActionTapped(page) _openBloggingPromptsOnboarding.call() } @@ -268,9 +269,8 @@ class WPMainActivityViewModel @Inject constructor( } } - fun onFabClicked(site: SiteModel?, page: PageType?) { + fun onFabClicked(site: SiteModel?, page: PageType) { appPrefsWrapper.setMainFabTooltipDisabled(true) - setMainFabUiState(true, site, page) _showQuickStarInBottomSheet.postValue(quickStartRepository.activeTask.value == PUBLISH_POST) @@ -284,7 +284,7 @@ class WPMainActivityViewModel @Inject constructor( // latest info. loadMainActions(site, page, onFabClicked = true) - analyticsTracker.track(Stat.MY_SITE_CREATE_SHEET_SHOWN) + mainCreateSheetHelper.trackSheetShown(page) _isBottomSheetShowing.postValue(Event(true)) } } else { @@ -294,7 +294,7 @@ class WPMainActivityViewModel @Inject constructor( } fun onPageChanged(site: SiteModel?, hasValidSite: Boolean, page: PageType) { - val showFab = hasValidSite && createContentFloatingButtonHelper.shouldShowFabForPage(page) + val showFab = hasValidSite && mainCreateSheetHelper.shouldShowFabForPage(page) setMainFabUiState(showFab, site, page) } @@ -303,7 +303,7 @@ class WPMainActivityViewModel @Inject constructor( } fun onResume(site: SiteModel?, hasValidSite: Boolean, page: PageType?) { - val showFab = hasValidSite && createContentFloatingButtonHelper.shouldShowFabForPage(page) + val showFab = hasValidSite && mainCreateSheetHelper.shouldShowFabForPage(page) setMainFabUiState(showFab, site, page) checkAndShowFeatureAnnouncement() @@ -332,6 +332,8 @@ class WPMainActivityViewModel @Inject constructor( } private fun setMainFabUiState(isFabVisible: Boolean, site: SiteModel?, page: PageType?) { + if (isFabVisible && page != null) mainCreateSheetHelper.trackFabShown(page) + val newState = MainFabUiState( isFabVisible = isFabVisible, isFabTooltipVisible = if (appPrefsWrapper.isMainFabTooltipDisabled()) false else isFabVisible, @@ -342,7 +344,7 @@ class WPMainActivityViewModel @Inject constructor( } fun getCreateContentMessageId(site: SiteModel?, page: PageType?): Int = - if (createContentFloatingButtonHelper.canCreatePage(site, page)) { + if (mainCreateSheetHelper.canCreatePage(site, page)) { R.string.create_post_page_fab_tooltip } else { R.string.create_post_page_fab_tooltip_contributors diff --git a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java index 83135f79892e..84f3f0848657 100644 --- a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java +++ b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java @@ -300,7 +300,6 @@ public enum Stat { MY_SITE_ICON_CROPPED, MY_SITE_ICON_UPLOADED, MY_SITE_ICON_UPLOAD_UNSUCCESSFUL, - MY_SITE_CREATE_SHEET_ANSWER_PROMPT_TAPPED, NOTIFICATIONS_DISABLED, NOTIFICATIONS_ENABLED, NOTIFICATIONS_ACCESSED, @@ -827,7 +826,14 @@ public enum Stat { JETPACK_BACKUP_DOWNLOAD_SHARE_LINK_TAPPED, MY_SITE_CREATE_SHEET_SHOWN, MY_SITE_CREATE_SHEET_ACTION_TAPPED, + MY_SITE_CREATE_SHEET_ANSWER_PROMPT_TAPPED, MY_SITE_CREATE_SHEET_PROMPT_HELP_TAPPED, + MY_SITE_CREATE_FAB_SHOWN, + READER_CREATE_SHEET_SHOWN, + READER_CREATE_SHEET_ACTION_TAPPED, + READER_CREATE_SHEET_ANSWER_PROMPT_TAPPED, + READER_CREATE_SHEET_PROMPT_HELP_TAPPED, + READER_CREATE_FAB_SHOWN, BLOGGING_PROMPTS_CREATE_SHEET_CARD_VIEWED, MY_SITE_NO_SITES_VIEW_DISPLAYED, MY_SITE_NO_SITES_VIEW_ACTION_TAPPED, From 3094f5f40dca2e67ca5d7691a965072b2a6cf9d0 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Wed, 29 May 2024 15:55:45 -0300 Subject: [PATCH 08/10] Use SiteUtilsWrapper for better testability --- .../android/ui/main/utils/MainCreateSheetHelper.kt | 11 ++++++----- .../org/wordpress/android/util/SiteUtilsWrapper.kt | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelper.kt index ce9b2917f305..7f43f60b04e0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelper.kt @@ -8,7 +8,7 @@ import org.wordpress.android.ui.main.WPMainNavigationView.PageType import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptAttribution import org.wordpress.android.ui.voicetocontent.VoiceToContentFeatureUtils import org.wordpress.android.util.BuildConfigWrapper -import org.wordpress.android.util.SiteUtils +import org.wordpress.android.util.SiteUtilsWrapper import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper import org.wordpress.android.util.config.ReaderFloatingButtonFeatureConfig import java.util.Locale @@ -18,24 +18,25 @@ class MainCreateSheetHelper @Inject constructor( private val voiceToContentFeatureUtils: VoiceToContentFeatureUtils, private val readerFloatingButtonFeatureConfig: ReaderFloatingButtonFeatureConfig, private val bloggingPromptsSettingsHelper: BloggingPromptsSettingsHelper, - private val buildConfigWrapper: BuildConfigWrapper, + private val buildConfig: BuildConfigWrapper, + private val siteUtils: SiteUtilsWrapper, private val analyticsTracker: AnalyticsTrackerWrapper, ) { fun shouldShowFabForPage(page: PageType?): Boolean { val enabledForPage = page == PageType.MY_SITE || (page == PageType.READER && readerFloatingButtonFeatureConfig.isEnabled()) - return buildConfigWrapper.isCreateFabEnabled && enabledForPage + return buildConfig.isCreateFabEnabled && enabledForPage } @Suppress("FunctionOnlyReturningConstant") fun canCreatePost(): Boolean = true // for completeness fun canCreatePage(site: SiteModel?, page: PageType?): Boolean { - return SiteUtils.hasFullAccessToContent(site) && page == PageType.MY_SITE + return siteUtils.hasFullAccessToContent(site) && page == PageType.MY_SITE } fun canCreatePostFromAudio(site: SiteModel?): Boolean { - return voiceToContentFeatureUtils.isVoiceToContentEnabled() && SiteUtils.hasFullAccessToContent(site) + return voiceToContentFeatureUtils.isVoiceToContentEnabled() && siteUtils.hasFullAccessToContent(site) } suspend fun canCreatePromptAnswer(): Boolean = bloggingPromptsSettingsHelper.shouldShowPromptsFeature() diff --git a/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt index 5a4df6403e6e..a44a13192627 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt @@ -27,4 +27,5 @@ class SiteUtilsWrapper @Inject constructor(private val appContext: Context) { fun getSiteIconUrlOfResourceSize(site: SiteModel, @DimenRes sizeRes: Int): String { return SiteUtils.getSiteIconUrl(site, appContext.resources.getDimensionPixelSize(sizeRes)) } + fun hasFullAccessToContent(site: SiteModel?): Boolean = SiteUtils.hasFullAccessToContent(site) } From 7656f3b0ad31d22b182b317da7a05e85a8acf162 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Wed, 29 May 2024 15:56:28 -0300 Subject: [PATCH 09/10] Add unit tests for MainCreateSheetHelper --- .../main/utils/MainCreateSheetHelperTest.kt | 479 ++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 WordPress/src/test/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelperTest.kt diff --git a/WordPress/src/test/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelperTest.kt new file mode 100644 index 000000000000..b54dda92261e --- /dev/null +++ b/WordPress/src/test/java/org/wordpress/android/ui/main/utils/MainCreateSheetHelperTest.kt @@ -0,0 +1,479 @@ +package org.wordpress.android.ui.main.utils + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.mockito.Mock +import org.mockito.kotlin.argThat +import org.mockito.kotlin.eq +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.whenever +import org.wordpress.android.BaseUnitTest +import org.wordpress.android.analytics.AnalyticsTracker.Stat +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper +import org.wordpress.android.ui.main.MainActionListItem +import org.wordpress.android.ui.main.WPMainNavigationView.PageType +import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptAttribution +import org.wordpress.android.ui.voicetocontent.VoiceToContentFeatureUtils +import org.wordpress.android.util.BuildConfigWrapper +import org.wordpress.android.util.SiteUtilsWrapper +import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper +import org.wordpress.android.util.config.ReaderFloatingButtonFeatureConfig +import kotlin.test.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class MainCreateSheetHelperTest : BaseUnitTest() { + @Mock + private lateinit var voiceToContentFeatureUtils: VoiceToContentFeatureUtils + + @Mock + private lateinit var readerFloatingButtonFeatureConfig: ReaderFloatingButtonFeatureConfig + + @Mock + private lateinit var bloggingPromptsSettingsHelper: BloggingPromptsSettingsHelper + + @Mock + private lateinit var buildConfig: BuildConfigWrapper + + @Mock + private lateinit var siteUtils: SiteUtilsWrapper + + @Mock + private lateinit var analyticsTracker: AnalyticsTrackerWrapper + + private lateinit var helper: MainCreateSheetHelper + + @Before + fun setUp() { + helper = MainCreateSheetHelper( + voiceToContentFeatureUtils, + readerFloatingButtonFeatureConfig, + bloggingPromptsSettingsHelper, + buildConfig, + siteUtils, + analyticsTracker, + ) + } + + // region shouldShowFabForPage + @Test + fun `shouldShowFabForPage returns true for my site page`() { + // Arrange + val page = PageType.MY_SITE + whenever(buildConfig.isCreateFabEnabled).thenReturn(true) + + // Act + val result = helper.shouldShowFabForPage(page) + + // Assert + assertThat(result).isTrue() + } + + @Test + fun `shouldShowFabForPage returns true for reader page when reader floating button feature is enabled`() { + // Arrange + val page = PageType.READER + whenever(buildConfig.isCreateFabEnabled).thenReturn(true) + whenever(readerFloatingButtonFeatureConfig.isEnabled()).thenReturn(true) + + // Act + val result = helper.shouldShowFabForPage(page) + + // Assert + assertThat(result).isTrue() + } + + @Test + fun `shouldShowFabForPage returns false for reader page when reader floating button feature is disabled`() { + // Arrange + val page = PageType.READER + whenever(buildConfig.isCreateFabEnabled).thenReturn(true) + whenever(readerFloatingButtonFeatureConfig.isEnabled()).thenReturn(false) + + // Act + val result = helper.shouldShowFabForPage(page) + + // Assert + assertThat(result).isFalse() + } + + @Test + fun `shouldShowFabForPage returns false for my site page when create fab is disabled`() { + // Arrange + val page = PageType.MY_SITE + whenever(buildConfig.isCreateFabEnabled).thenReturn(false) + + // Act + val result = helper.shouldShowFabForPage(page) + + // Assert + assertThat(result).isFalse() + } + + @Test + fun `shouldShowFabForPage returns false for reader page when create fab is disabled`() { + // Arrange + val page = PageType.READER + whenever(buildConfig.isCreateFabEnabled).thenReturn(false) + whenever(readerFloatingButtonFeatureConfig.isEnabled()).thenReturn(true) + + // Act + val result = helper.shouldShowFabForPage(page) + + // Assert + assertThat(result).isFalse() + } + // endregion + + // region canCreatePost + @Test + fun `canCreatePost returns true`() { + // Act + val result = helper.canCreatePost() + + // Assert + assertThat(result).isTrue() + } + // endregion + + // region canCreatePage + @Test + fun `canCreatePage returns true for my site page with full access to content`() { + // Arrange + val site = SiteModel() + val page = PageType.MY_SITE + whenever(siteUtils.hasFullAccessToContent(site)).thenReturn(true) + + // Act + val result = helper.canCreatePage(site, page) + + // Assert + assertThat(result).isTrue() + } + + @Test + fun `canCreatePage returns false for my site page without full access to content`() { + // Arrange + val site = SiteModel() + val page = PageType.MY_SITE + whenever(siteUtils.hasFullAccessToContent(site)).thenReturn(false) + + // Act + val result = helper.canCreatePage(site, page) + + // Assert + assertThat(result).isFalse() + } + + @Test + fun `canCreatePage returns false for other pages with full access to content`() { + PageType.entries + .filterNot { it == PageType.MY_SITE } + .forEach { page -> + // Arrange + val site = SiteModel() + whenever(siteUtils.hasFullAccessToContent(site)).thenReturn(true) + + // Act + val result = helper.canCreatePage(site, page) + + // Assert + assertThat(result).isFalse() + } + } + // endregion + + // region canCreatePostFromAudio + @Test + fun `canCreatePostFromAudio returns true when voice to content is enabled and site has full access to content`() { + // Arrange + val site = SiteModel() + whenever(voiceToContentFeatureUtils.isVoiceToContentEnabled()).thenReturn(true) + whenever(siteUtils.hasFullAccessToContent(site)).thenReturn(true) + + // Act + val result = helper.canCreatePostFromAudio(site) + + // Assert + assertThat(result).isTrue() + } + + @Test + fun `canCreatePostFromAudio returns false when voice to content is disabled`() { + // Arrange + val site = SiteModel() + whenever(voiceToContentFeatureUtils.isVoiceToContentEnabled()).thenReturn(false) + + // Act + val result = helper.canCreatePostFromAudio(site) + + // Assert + assertThat(result).isFalse() + } + + @Test + fun `canCreatePostFromAudio returns false when site does not have full access to content`() { + // Arrange + val site = SiteModel() + whenever(voiceToContentFeatureUtils.isVoiceToContentEnabled()).thenReturn(true) + whenever(siteUtils.hasFullAccessToContent(site)).thenReturn(false) + + // Act + val result = helper.canCreatePostFromAudio(site) + + // Assert + assertThat(result).isFalse() + } + // endregion + + // region canCreatePromptAnswer + @Test + fun `canCreatePromptAnswer returns true when prompts feature should be shown`() = test { + // Arrange + whenever(bloggingPromptsSettingsHelper.shouldShowPromptsFeature()).thenReturn(true) + + // Act + val result = helper.canCreatePromptAnswer() + + // Assert + assertThat(result).isTrue() + } + + @Test + fun `canCreatePromptAnswer returns false when prompts feature should not be shown`() = test { + // Arrange + whenever(bloggingPromptsSettingsHelper.shouldShowPromptsFeature()).thenReturn(false) + + // Act + val result = helper.canCreatePromptAnswer() + + // Assert + assertThat(result).isFalse() + } + // endregion + + // region trackActionTapped + @Test + fun `trackActionTapped tracks action tapped for my site page`() { + // Arrange + val page = PageType.MY_SITE + val actionType = MainActionListItem.ActionType.CREATE_NEW_POST + val expectedStat = Stat.MY_SITE_CREATE_SHEET_ACTION_TAPPED + + // Act + helper.trackActionTapped(page, actionType) + + // Assert + verify(analyticsTracker).track(eq(expectedStat), argThat> { + this["action"] == "create_new_post" + }) + } + + @Test + fun `trackActionTapped tracks action tapped for reader page`() { + // Arrange + val page = PageType.READER + val actionType = MainActionListItem.ActionType.CREATE_NEW_POST + val expectedStat = Stat.READER_CREATE_SHEET_ACTION_TAPPED + + // Act + helper.trackActionTapped(page, actionType) + + // Assert + verify(analyticsTracker).track(eq(expectedStat), argThat> { + this["action"] == "create_new_post" + }) + } + + @Test + fun `trackActionTapped does not track action tapped for other pages`() { + PageType.entries + .filterNot { it == PageType.MY_SITE || it == PageType.READER } + .forEach { page -> + // Arrange + val actionType = MainActionListItem.ActionType.CREATE_NEW_POST + + // Act + helper.trackActionTapped(page, actionType) + + // Assert + verifyNoInteractions(analyticsTracker) + } + } + // endregion + + // region trackAnswerPromptActionTapped + @Test + fun `trackAnswerPromptActionTapped tracks answer prompt action tapped for my site page`() { + // Arrange + val page = PageType.MY_SITE + val attribution = BloggingPromptAttribution.DAY_ONE + val expectedStat = Stat.MY_SITE_CREATE_SHEET_ANSWER_PROMPT_TAPPED + + // Act + helper.trackAnswerPromptActionTapped(page, attribution) + + // Assert + verify(analyticsTracker).track(eq(expectedStat), argThat> { + this["attribution"] == attribution.value + }) + } + + @Test + fun `trackAnswerPromptActionTapped tracks answer prompt action tapped for reader page`() { + // Arrange + val page = PageType.READER + val attribution = BloggingPromptAttribution.DAY_ONE + val expectedStat = Stat.READER_CREATE_SHEET_ANSWER_PROMPT_TAPPED + + // Act + helper.trackAnswerPromptActionTapped(page, attribution) + + // Assert + verify(analyticsTracker).track(eq(expectedStat), argThat> { + this["attribution"] == attribution.value + }) + } + + @Test + fun `trackAnswerPromptActionTapped does not track answer prompt action tapped for other pages`() { + PageType.entries + .filterNot { it == PageType.MY_SITE || it == PageType.READER } + .forEach { page -> + // Arrange + val attribution = BloggingPromptAttribution.DAY_ONE + + // Act + helper.trackAnswerPromptActionTapped(page, attribution) + + // Assert + verifyNoInteractions(analyticsTracker) + } + } + // endregion + + // region trackHelpPromptActionTapped + @Test + fun `trackHelpPromptActionTapped tracks help prompt action tapped for my site page`() { + // Arrange + val page = PageType.MY_SITE + val expectedStat = Stat.MY_SITE_CREATE_SHEET_PROMPT_HELP_TAPPED + + // Act + helper.trackHelpPromptActionTapped(page) + + // Assert + verify(analyticsTracker).track(expectedStat) + } + + @Test + fun `trackHelpPromptActionTapped tracks help prompt action tapped for reader page`() { + // Arrange + val page = PageType.READER + val expectedStat = Stat.READER_CREATE_SHEET_PROMPT_HELP_TAPPED + + // Act + helper.trackHelpPromptActionTapped(page) + + // Assert + verify(analyticsTracker).track(expectedStat) + } + + @Test + fun `trackHelpPromptActionTapped does not track help prompt action tapped for other pages`() { + PageType.entries + .filterNot { it == PageType.MY_SITE || it == PageType.READER } + .forEach { page -> + // Act + helper.trackHelpPromptActionTapped(page) + + // Assert + verifyNoInteractions(analyticsTracker) + } + } + // endregion + + // region trackSheetShown + @Test + fun `trackSheetShown tracks sheet shown for my site page`() { + // Arrange + val page = PageType.MY_SITE + val expectedStat = Stat.MY_SITE_CREATE_SHEET_SHOWN + + // Act + helper.trackSheetShown(page) + + // Assert + verify(analyticsTracker).track(expectedStat) + } + + @Test + fun `trackSheetShown tracks sheet shown for reader page`() { + // Arrange + val page = PageType.READER + val expectedStat = Stat.READER_CREATE_SHEET_SHOWN + + // Act + helper.trackSheetShown(page) + + // Assert + verify(analyticsTracker).track(expectedStat) + } + + @Test + fun `trackSheetShown does not track sheet shown for other pages`() { + PageType.entries + .filterNot { it == PageType.MY_SITE || it == PageType.READER } + .forEach { page -> + // Act + helper.trackSheetShown(page) + + // Assert + verifyNoInteractions(analyticsTracker) + } + } + // endregion + + // region trackFabShown + @Test + fun `trackFabShown tracks fab shown for my site page`() { + // Arrange + val page = PageType.MY_SITE + val expectedStat = Stat.MY_SITE_CREATE_FAB_SHOWN + + // Act + helper.trackFabShown(page) + + // Assert + verify(analyticsTracker).track(expectedStat) + } + + @Test + fun `trackFabShown tracks fab shown for reader page`() { + // Arrange + val page = PageType.READER + val expectedStat = Stat.READER_CREATE_FAB_SHOWN + + // Act + helper.trackFabShown(page) + + // Assert + verify(analyticsTracker).track(expectedStat) + } + + @Test + fun `trackFabShown does not track fab shown for other pages`() { + PageType.entries + .filterNot { it == PageType.MY_SITE || it == PageType.READER } + .forEach { page -> + // Act + helper.trackFabShown(page) + + // Assert + verifyNoInteractions(analyticsTracker) + } + } + // endregion +} From 944f4edb08d1acebdf0d80af0b3096a8de7dd64d Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Wed, 29 May 2024 16:14:40 -0300 Subject: [PATCH 10/10] Add/Update unit tests for WPMainActivityViewModel --- .../main/WPMainActivityViewModelTest.kt | 192 +++++++++++------- 1 file changed, 113 insertions(+), 79 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModelTest.kt index 68f9e62a6174..a6c81d98b44c 100644 --- a/WordPress/src/test/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/viewmodel/main/WPMainActivityViewModelTest.kt @@ -35,21 +35,22 @@ import org.wordpress.android.fluxc.store.QuickStartStore.QuickStartTask import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.fluxc.store.bloggingprompts.BloggingPromptsStore import org.wordpress.android.fluxc.store.bloggingprompts.BloggingPromptsStore.BloggingPromptsResult -import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper import org.wordpress.android.ui.main.MainActionListItem.ActionType.ANSWER_BLOGGING_PROMPT import org.wordpress.android.ui.main.MainActionListItem.ActionType.CREATE_NEW_PAGE import org.wordpress.android.ui.main.MainActionListItem.ActionType.CREATE_NEW_POST +import org.wordpress.android.ui.main.MainActionListItem.ActionType.CREATE_NEW_POST_FROM_AUDIO import org.wordpress.android.ui.main.MainActionListItem.ActionType.NO_ACTION import org.wordpress.android.ui.main.MainActionListItem.AnswerBloggingPromptAction import org.wordpress.android.ui.main.MainActionListItem.CreateAction import org.wordpress.android.ui.main.MainFabUiState +import org.wordpress.android.ui.main.WPMainNavigationView.PageType +import org.wordpress.android.ui.main.utils.MainCreateSheetHelper import org.wordpress.android.ui.mysite.SelectedSiteRepository import org.wordpress.android.ui.mysite.cards.dashboard.bloggingprompts.BloggingPromptAttribution import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.ui.prefs.privacy.banner.domain.ShouldAskPrivacyConsent import org.wordpress.android.ui.quickstart.QuickStartType -import org.wordpress.android.ui.voicetocontent.VoiceToContentFeatureUtils import org.wordpress.android.ui.whatsnew.FeatureAnnouncement import org.wordpress.android.ui.whatsnew.FeatureAnnouncementItem import org.wordpress.android.ui.whatsnew.FeatureAnnouncementProvider @@ -95,9 +96,6 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Mock lateinit var siteStore: SiteStore - @Mock - lateinit var bloggingPromptsSettingsHelper: BloggingPromptsSettingsHelper - @Mock lateinit var bloggingPromptsStore: BloggingPromptsStore @@ -111,7 +109,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { private lateinit var shouldAskPrivacyConsent: ShouldAskPrivacyConsent @Mock - private lateinit var voiceToContentFeatureUtils: VoiceToContentFeatureUtils + private lateinit var mainCreateSheetHelper: MainCreateSheetHelper private val featureAnnouncement = FeatureAnnouncement( "14.7", @@ -157,10 +155,10 @@ class WPMainActivityViewModelTest : BaseUnitTest() { activeTask = MutableLiveData() externalFocusPointEvents = mutableListOf() whenever(quickStartRepository.activeTask).thenReturn(activeTask) - whenever(bloggingPromptsSettingsHelper.shouldShowPromptsFeature()).thenReturn(false) whenever(bloggingPromptsStore.getPromptForDate(any(), any())).thenReturn(flowOf(bloggingPrompt)) whenever(shouldAskPrivacyConsent()).thenReturn(false) - whenever(voiceToContentFeatureUtils.isVoiceToContentEnabled()).thenReturn(false) + whenever(mainCreateSheetHelper.canCreatePost()).thenReturn(true) + whenever(mainCreateSheetHelper.canCreatePromptAnswer()).thenReturn(false) viewModel = WPMainActivityViewModel( featureAnnouncementProvider, buildConfigWrapper, @@ -170,11 +168,10 @@ class WPMainActivityViewModelTest : BaseUnitTest() { selectedSiteRepository, accountStore, siteStore, - bloggingPromptsSettingsHelper, bloggingPromptsStore, NoDelayCoroutineDispatcher(), shouldAskPrivacyConsent, - voiceToContentFeatureUtils + mainCreateSheetHelper, ) viewModel.onFeatureAnnouncementRequested.observeForever( onFeatureAnnouncementRequestedObserver @@ -195,73 +192,41 @@ class WPMainActivityViewModelTest : BaseUnitTest() { /* FAB VISIBILITY */ @Test - fun `given fab enabled, when page changed to my site, then fab is visible`() { + fun `given fab enabled and page changed to supported page, then fab is visible`() { startViewModelWithDefaultParameters() + whenever(mainCreateSheetHelper.shouldShowFabForPage(any())).thenReturn(true) - viewModel.onPageChanged(isOnMySitePageWithValidSite = true, site = initSite(hasFullAccessToContent = true)) + viewModel.onPageChanged(site = initSite(hasFullAccessToContent = true), hasValidSite = true, page = mock()) assertThat(fabUiState?.isFabVisible).isTrue } @Test - fun `given fab enabled, when page changed away from my site, then fab is hidden`() { + fun `given fab disabled or page changed to non-supported page, then fab is hidden`() { startViewModelWithDefaultParameters() + whenever(mainCreateSheetHelper.shouldShowFabForPage(any())).thenReturn(false) - viewModel.onPageChanged(isOnMySitePageWithValidSite = false, site = initSite(hasFullAccessToContent = true)) + viewModel.onPageChanged(site = initSite(hasFullAccessToContent = true), hasValidSite = true, page = mock()) - assertThat(fabUiState?.isFabVisible).isFalse + assertThat(fabUiState?.isFabVisible).isFalse() } @Test - fun `given fab enabled, when my site page is resumed, then fab is visible`() { + fun `given fab enabled and supported page is resumed, then fab is visible`() { startViewModelWithDefaultParameters() + whenever(mainCreateSheetHelper.shouldShowFabForPage(any())).thenReturn(true) - viewModel.onResume(isOnMySitePageWithValidSite = true, site = initSite(hasFullAccessToContent = true)) + viewModel.onResume(site = initSite(hasFullAccessToContent = true), hasValidSite = true, page = mock()) assertThat(fabUiState?.isFabVisible).isTrue } @Test - fun `given fab enabled, when non my site page is resumed, then fab is hidden`() { + fun `given fab disabled or non-supported page is resumed, then fab is hidden`() { startViewModelWithDefaultParameters() + whenever(mainCreateSheetHelper.shouldShowFabForPage(any())).thenReturn(false) - viewModel.onResume(isOnMySitePageWithValidSite = false, site = initSite(hasFullAccessToContent = true)) - - assertThat(fabUiState?.isFabVisible).isFalse - } - - @Test - fun `given fab disabled, when page changed to my site, then fab is hidden`() { - startViewModelWithDefaultParameters(isCreateFabEnabled = false) - - viewModel.onPageChanged(isOnMySitePageWithValidSite = true, site = initSite(hasFullAccessToContent = true)) - - assertThat(fabUiState?.isFabVisible).isFalse - } - - @Test - fun `given fab disabled, when page changed away from my site, then fab is hidden`() { - startViewModelWithDefaultParameters(isCreateFabEnabled = false) - - viewModel.onPageChanged(isOnMySitePageWithValidSite = false, site = initSite(hasFullAccessToContent = true)) - - assertThat(fabUiState?.isFabVisible).isFalse - } - - @Test - fun `given fab disabled, when my site page is resumed, then fab is hidden`() { - startViewModelWithDefaultParameters(isCreateFabEnabled = false) - - viewModel.onResume(isOnMySitePageWithValidSite = true, site = initSite(hasFullAccessToContent = true)) - - assertThat(fabUiState?.isFabVisible).isFalse - } - - @Test - fun `given fab disabled, when non my site page is resumed, then fab is hidden`() { - startViewModelWithDefaultParameters(isCreateFabEnabled = false) - - viewModel.onResume(isOnMySitePageWithValidSite = false, site = initSite(hasFullAccessToContent = true)) + viewModel.onResume(site = initSite(hasFullAccessToContent = true), hasValidSite = true, page = mock()) assertThat(fabUiState?.isFabVisible).isFalse } @@ -269,8 +234,10 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Test fun `fab focus point visible when active task is PUBLISH_POST`() { startViewModelWithDefaultParameters() + whenever(mainCreateSheetHelper.shouldShowFabForPage(any())).thenReturn(true) + activeTask.value = PUBLISH_POST - viewModel.onPageChanged(isOnMySitePageWithValidSite = true, site = initSite(hasFullAccessToContent = true)) + viewModel.onPageChanged(site = initSite(hasFullAccessToContent = true), hasValidSite = true, page = mock()) assertThat(fabUiState?.isFocusPointVisible).isTrue } @@ -279,7 +246,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { fun `fab focus point gone when active task is different`() { startViewModelWithDefaultParameters() activeTask.value = UPDATE_SITE_TITLE - viewModel.onPageChanged(isOnMySitePageWithValidSite = true, site = initSite(hasFullAccessToContent = true)) + viewModel.onPageChanged(site = initSite(hasFullAccessToContent = true), hasValidSite = true, page = mock()) assertThat(fabUiState?.isFocusPointVisible).isFalse } @@ -288,7 +255,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { fun `fab focus point gone when active task is null`() { startViewModelWithDefaultParameters() activeTask.value = null - viewModel.onPageChanged(isOnMySitePageWithValidSite = true, site = initSite(hasFullAccessToContent = true)) + viewModel.onPageChanged(site = initSite(hasFullAccessToContent = true), hasValidSite = true, page = mock()) assertThat(fabUiState?.isFocusPointVisible).isFalse } @@ -304,6 +271,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Test fun `bottom sheet action is new page when new page is tapped`() { + whenever(mainCreateSheetHelper.canCreatePage(any(), any())).thenReturn(true) startViewModelWithDefaultParameters() val action = viewModel.mainActions.value?.first { it.actionType == CREATE_NEW_PAGE } as CreateAction assertThat(action).isNotNull @@ -313,7 +281,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Test fun `bottom sheet does not show prompt card when prompts feature is not active`() = test { - whenever(bloggingPromptsSettingsHelper.shouldShowPromptsFeature()).thenReturn(false) + whenever(mainCreateSheetHelper.canCreatePromptAnswer()).thenReturn(false) startViewModelWithDefaultParameters() val hasBloggingPromptAction = viewModel.mainActions.value?.any { it.actionType == ANSWER_BLOGGING_PROMPT } assertThat(hasBloggingPromptAction).isFalse() @@ -321,7 +289,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Test fun `bottom sheet does show prompt card when prompts feature is active`() = test { - whenever(bloggingPromptsSettingsHelper.shouldShowPromptsFeature()).thenReturn(true) + whenever(mainCreateSheetHelper.canCreatePromptAnswer()).thenReturn(true) startViewModelWithDefaultParameters() val hasBloggingPromptAction = viewModel.mainActions.value?.any { it.actionType == ANSWER_BLOGGING_PROMPT } assertThat(hasBloggingPromptAction).isTrue() @@ -329,7 +297,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Test fun `bottom sheet action is ANSWER_BLOGGING_PROMPT when the BP answer button is clicked`() = test { - whenever(bloggingPromptsSettingsHelper.shouldShowPromptsFeature()).thenReturn(true) + whenever(mainCreateSheetHelper.canCreatePromptAnswer()).thenReturn(true) startViewModelWithDefaultParameters() val action = viewModel.mainActions.value?.firstOrNull { it.actionType == ANSWER_BLOGGING_PROMPT @@ -345,7 +313,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Test fun `bottom sheet does not show quick start focus point by default`() { startViewModelWithDefaultParameters() - viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true)) + viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true), page = PageType.MY_SITE) assertThat(viewModel.isBottomSheetShowing.value!!.peekContent()).isTrue assertThat(viewModel.mainActions.value?.any { it is CreateAction && it.showQuickStartFocusPoint }).isEqualTo( false @@ -356,7 +324,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { fun `CREATE_NEW_POST action in bottom sheet with active Quick Start completes task and hides the focus point`() { startViewModelWithDefaultParameters() activeTask.value = PUBLISH_POST - viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true)) + viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true), page = PageType.MY_SITE) assertThat(viewModel.isBottomSheetShowing.value!!.peekContent()).isTrue assertThat(viewModel.mainActions.value?.any { it is CreateAction && it.showQuickStartFocusPoint }).isEqualTo( true @@ -376,7 +344,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { fun `CREATE_NEW_POST action sets task as done in QuickStartRepository`() { startViewModelWithDefaultParameters() activeTask.value = PUBLISH_POST - viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true)) + viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true), page = PageType.MY_SITE) val action = viewModel.mainActions.value?.first { it.actionType == CREATE_NEW_POST } as CreateAction assertThat(action).isNotNull @@ -387,9 +355,11 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Test fun `actions that are not CREATE_NEW_POST will not complete quick start task`() { + whenever(mainCreateSheetHelper.canCreatePage(any(), any())).thenReturn(true) startViewModelWithDefaultParameters() + activeTask.value = PUBLISH_POST - viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true)) + viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true), page = PageType.MY_SITE) assertThat(viewModel.isBottomSheetShowing.value!!.peekContent()).isTrue assertThat(viewModel.mainActions.value?.any { it is CreateAction && it.showQuickStartFocusPoint }).isEqualTo( true @@ -407,15 +377,29 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Test fun `new post action is triggered from FAB when no full access to content if stories unavailable`() { startViewModelWithDefaultParameters() - viewModel.onFabClicked(site = initSite(hasFullAccessToContent = false, isWpcomOrJpSite = false)) + viewModel.onFabClicked( + site = initSite(hasFullAccessToContent = false, isWpcomOrJpSite = false), + page = PageType.MY_SITE + ) assertThat(viewModel.isBottomSheetShowing.value).isNull() assertThat(viewModel.createAction.value).isEqualTo(CREATE_NEW_POST) } @Test - fun `bottom sheet is visualized when user has full access to content and has all 3 options`() { + fun `bottom sheet is visualized when user has full access to content and has 2 options`() { + whenever(mainCreateSheetHelper.canCreatePage(any(), any())).thenReturn(false) startViewModelWithDefaultParameters() - viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true)) + viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true), page = PageType.MY_SITE) + assertThat(viewModel.createAction.value).isNull() + assertThat(viewModel.mainActions.value?.size).isEqualTo(2) // 1 option plus NO_ACTION, first in list + assertThat(viewModel.isBottomSheetShowing.value!!.peekContent()).isTrue + } + + @Test + fun `bottom sheet is visualized when user has full access to content and has 3 options`() { + whenever(mainCreateSheetHelper.canCreatePage(any(), any())).thenReturn(true) + startViewModelWithDefaultParameters() + viewModel.onFabClicked(site = initSite(hasFullAccessToContent = true), page = PageType.MY_SITE) assertThat(viewModel.createAction.value).isNull() assertThat(viewModel.mainActions.value?.size).isEqualTo(3) // 2 options plus NO_ACTION, first in list assertThat(viewModel.isBottomSheetShowing.value!!.peekContent()).isTrue @@ -606,10 +590,39 @@ class WPMainActivityViewModelTest : BaseUnitTest() { } @Test - fun `bottom sheet actions are sorted in the correct order`() { + fun `bottom sheet actions are sorted in the correct order when can create post only`() { + startViewModelWithDefaultParameters() + + val expectedOrder = listOf( + NO_ACTION, + CREATE_NEW_POST, + ) + + assertThat(viewModel.mainActions.value!!.map { it.actionType }).isEqualTo(expectedOrder) + } + + @Test + fun `bottom sheet actions are sorted in the correct order when can create post, and page`() { + whenever(mainCreateSheetHelper.canCreatePage(any(), any())).thenReturn(true) + startViewModelWithDefaultParameters() + + val expectedOrder = listOf( + NO_ACTION, + CREATE_NEW_POST, + CREATE_NEW_PAGE + ) + + assertThat(viewModel.mainActions.value!!.map { it.actionType }).isEqualTo(expectedOrder) + } + + @Test + fun `bottom sheet actions are sorted in the correct order when can create post, prompts, and page`() = test { + whenever(mainCreateSheetHelper.canCreatePromptAnswer()).thenReturn(true) + whenever(mainCreateSheetHelper.canCreatePage(any(), any())).thenReturn(true) startViewModelWithDefaultParameters() val expectedOrder = listOf( + ANSWER_BLOGGING_PROMPT, NO_ACTION, CREATE_NEW_POST, CREATE_NEW_PAGE @@ -618,6 +631,25 @@ class WPMainActivityViewModelTest : BaseUnitTest() { assertThat(viewModel.mainActions.value!!.map { it.actionType }).isEqualTo(expectedOrder) } + @Test + fun `bottom sheet actions are sorted in the correct order when can create post, from audio, prompts, and page`() = + test { + whenever(mainCreateSheetHelper.canCreatePostFromAudio(any())).thenReturn(true) + whenever(mainCreateSheetHelper.canCreatePromptAnswer()).thenReturn(true) + whenever(mainCreateSheetHelper.canCreatePage(any(), any())).thenReturn(true) + startViewModelWithDefaultParameters() + + val expectedOrder = listOf( + ANSWER_BLOGGING_PROMPT, + NO_ACTION, + CREATE_NEW_POST, + CREATE_NEW_POST_FROM_AUDIO, + CREATE_NEW_PAGE + ) + + assertThat(viewModel.mainActions.value!!.map { it.actionType }).isEqualTo(expectedOrder) + } + @Test fun `hasMultipleSites should be true when there are more than one site`() { whenever(siteStore.sitesCount).thenReturn(2) @@ -656,18 +688,18 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Test fun `Should track analytics event when onHelpPromptActionClicked is called`() = test { - whenever(bloggingPromptsSettingsHelper.shouldShowPromptsFeature()).thenReturn(true) + whenever(mainCreateSheetHelper.canCreatePromptAnswer()).thenReturn(true) startViewModelWithDefaultParameters() val action = viewModel.mainActions.value?.first { it.actionType == ANSWER_BLOGGING_PROMPT } as AnswerBloggingPromptAction action.onHelpAction?.invoke() - verify(analyticsTrackerWrapper).track(Stat.MY_SITE_CREATE_SHEET_PROMPT_HELP_TAPPED) + verify(mainCreateSheetHelper).trackHelpPromptActionTapped(any()) } @Test fun `Should trigger openBloggingPromptsOnboarding when onHelpPromptActionClicked is called`() = test { - whenever(bloggingPromptsSettingsHelper.shouldShowPromptsFeature()).thenReturn(true) + whenever(mainCreateSheetHelper.canCreatePromptAnswer()).thenReturn(true) startViewModelWithDefaultParameters() val action = viewModel.mainActions.value?.first { it.actionType == ANSWER_BLOGGING_PROMPT @@ -680,9 +712,9 @@ class WPMainActivityViewModelTest : BaseUnitTest() { @Suppress("MaxLineLength") fun `Should track BLOGGING_PROMPTS_CREATE_SHEET_CARD_VIEWED when onFabClicked is called and actions contains AnswerBloggingPromptAction`() = test { - whenever(bloggingPromptsSettingsHelper.shouldShowPromptsFeature()).thenReturn(true) + whenever(mainCreateSheetHelper.canCreatePromptAnswer()).thenReturn(true) startViewModelWithDefaultParameters() - viewModel.onFabClicked(initSite()) + viewModel.onFabClicked(initSite(), page = PageType.MY_SITE) verify(analyticsTrackerWrapper).track(Stat.BLOGGING_PROMPTS_CREATE_SHEET_CARD_VIEWED) } @@ -731,12 +763,14 @@ class WPMainActivityViewModelTest : BaseUnitTest() { private fun startViewModelWithDefaultParameters( isWhatsNewFeatureEnabled: Boolean = true, - isCreateFabEnabled: Boolean = true, - isWpcomOrJpSite: Boolean = true + isWpcomOrJpSite: Boolean = true, + pageType: PageType = PageType.MY_SITE, ) { whenever(buildConfigWrapper.isWhatsNewFeatureEnabled).thenReturn(isWhatsNewFeatureEnabled) - whenever(buildConfigWrapper.isCreateFabEnabled).thenReturn(isCreateFabEnabled) - viewModel.start(site = initSite(hasFullAccessToContent = true, isWpcomOrJpSite = isWpcomOrJpSite)) + viewModel.start( + site = initSite(hasFullAccessToContent = true, isWpcomOrJpSite = isWpcomOrJpSite), + page = pageType + ) } private fun setupObservers() { @@ -748,7 +782,7 @@ class WPMainActivityViewModelTest : BaseUnitTest() { } private fun resumeViewModelWithDefaultParameters() { - viewModel.onResume(site = initSite(hasFullAccessToContent = true), isOnMySitePageWithValidSite = true) + viewModel.onResume(site = initSite(hasFullAccessToContent = true), hasValidSite = true, page = PageType.MY_SITE) } private fun initSite(