diff --git a/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java b/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java index 0947e4604d6f..c403f7e807c2 100644 --- a/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java +++ b/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java @@ -9,7 +9,6 @@ import org.wordpress.android.ui.ShareIntentReceiverFragment; import org.wordpress.android.ui.WPWebViewActivity; import org.wordpress.android.ui.about.UnifiedAboutActivity; -import org.wordpress.android.ui.accounts.PostSignupInterstitialActivity; import org.wordpress.android.ui.accounts.SignupEpilogueActivity; import org.wordpress.android.ui.accounts.signup.SignupEpilogueFragment; import org.wordpress.android.ui.activitylog.detail.ActivityLogDetailFragment; @@ -49,7 +48,6 @@ import org.wordpress.android.ui.main.AddContentAdapter; import org.wordpress.android.ui.main.MainBottomSheetFragment; import org.wordpress.android.ui.main.MeFragment; -import org.wordpress.android.ui.main.SitePickerActivity; import org.wordpress.android.ui.main.SitePickerAdapter; import org.wordpress.android.ui.main.WPMainActivity; import org.wordpress.android.ui.media.MediaBrowserActivity; @@ -213,8 +211,6 @@ public interface AppComponent { void inject(SignupEpilogueFragment object); - void inject(PostSignupInterstitialActivity object); - void inject(JetpackConnectionResultActivity object); void inject(StatsConnectJetpackActivity object); @@ -241,8 +237,6 @@ public interface AppComponent { void inject(AccountSettingsFragment object); - void inject(SitePickerActivity object); - void inject(SitePickerAdapter object); void inject(SiteSettingsFragment object); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/FullscreenBottomSheetDialogFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/FullscreenBottomSheetDialogFragment.kt index 60f5d0d95a43..93580ce380d6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/FullscreenBottomSheetDialogFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/FullscreenBottomSheetDialogFragment.kt @@ -2,11 +2,10 @@ package org.wordpress.android.ui import android.content.DialogInterface import android.os.Bundle -import android.view.View import android.view.WindowManager -import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.wordpress.android.util.extensions.fillScreen /** * Customises [BottomSheetDialogFragment] for fullscreen @@ -20,24 +19,7 @@ abstract class FullscreenBottomSheetDialogFragment : BottomSheetDialogFragment() } override fun onCreateDialog(savedInstanceState: Bundle?) = BottomSheetDialog(requireContext(), getTheme()).apply { - fillTheScreen(this) + this.fillScreen(isDraggable = true) window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) } - - private fun fillTheScreen(dialog: BottomSheetDialog) { - dialog.setOnShowListener { - dialog.findViewById(com.google.android.material.R.id.design_bottom_sheet)?.let { - val behaviour = BottomSheetBehavior.from(it) - setupFullHeight(it) - behaviour.skipCollapsed = true - behaviour.state = BottomSheetBehavior.STATE_EXPANDED - } - } - } - - private fun setupFullHeight(bottomSheet: View) { - val layoutParams = bottomSheet.layoutParams - layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT - bottomSheet.layoutParams = layoutParams - } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginEpilogueActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginEpilogueActivity.java index 0271557de23d..8e31fe1a8e60 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginEpilogueActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginEpilogueActivity.java @@ -16,11 +16,13 @@ import org.wordpress.android.ui.accounts.LoginNavigationEvents.CloseWithResultOk; import org.wordpress.android.ui.accounts.LoginNavigationEvents.CreateNewSite; import org.wordpress.android.ui.accounts.LoginNavigationEvents.SelectSite; +import org.wordpress.android.ui.accounts.LoginNavigationEvents.ShowJetpackIndividualPluginOverlay; import org.wordpress.android.ui.accounts.LoginNavigationEvents.ShowNoJetpackSites; import org.wordpress.android.ui.accounts.LoginNavigationEvents.ShowPostSignupInterstitialScreen; import org.wordpress.android.ui.accounts.login.LoginEpilogueFragment; import org.wordpress.android.ui.accounts.login.LoginEpilogueListener; import org.wordpress.android.ui.accounts.login.jetpack.LoginNoSitesFragment; +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginFragment; import org.wordpress.android.ui.main.SitePickerActivity; import org.wordpress.android.ui.mysite.SelectedSiteRepository; import org.wordpress.android.ui.sitecreation.misc.SiteCreationSource; @@ -80,6 +82,8 @@ private void initObservers() { closeWithResultOk(); } else if (loginEvent instanceof ShowNoJetpackSites) { showNoJetpackSites(); + } else if (loginEvent instanceof ShowJetpackIndividualPluginOverlay) { + showJetpackIndividualPluginOverlay(); } }); } @@ -150,6 +154,10 @@ private void showFragment(Fragment fragment, String tag, boolean applySlideAnima fragmentTransaction.commit(); } + private void showJetpackIndividualPluginOverlay() { + WPJetpackIndividualPluginFragment.show(getSupportFragmentManager()); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginEpilogueViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginEpilogueViewModel.kt index 565ef74fd283..8d376dfa35be 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginEpilogueViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginEpilogueViewModel.kt @@ -3,17 +3,21 @@ package org.wordpress.android.ui.accounts import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.wordpress.android.fluxc.store.SiteStore +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.util.BuildConfigWrapper import org.wordpress.android.viewmodel.Event - import javax.inject.Inject class LoginEpilogueViewModel @Inject constructor( private val appPrefsWrapper: AppPrefsWrapper, private val buildConfigWrapper: BuildConfigWrapper, - private val siteStore: SiteStore + private val siteStore: SiteStore, + private val wpJetpackIndividualPluginHelper: WPJetpackIndividualPluginHelper, ) : ViewModel() { private val _navigationEvents = MediatorLiveData>() val navigationEvents: LiveData> = _navigationEvents @@ -52,4 +56,21 @@ class LoginEpilogueViewModel @Inject constructor( fun onLoginFinished(doLoginUpdate: Boolean) { if (doLoginUpdate && !siteStore.hasSite()) handleNoSitesFound() } + + fun onSiteListLoaded() { + // don't check if already shown + if (_navigationEvents.value?.peekContent() == LoginNavigationEvents.ShowJetpackIndividualPluginOverlay) return + + viewModelScope.launch { + val showOverlay = wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay() + if (showOverlay) { + delay(DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) + _navigationEvents.postValue(Event(LoginNavigationEvents.ShowJetpackIndividualPluginOverlay)) + } + } + } + + companion object { + private const val DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY = 500L + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginNavigationEvents.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginNavigationEvents.kt index 7d6d7d9278d1..f40cdd64de71 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginNavigationEvents.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginNavigationEvents.kt @@ -13,4 +13,5 @@ sealed class LoginNavigationEvents { object CloseWithResultOk : LoginNavigationEvents() object ShowEmailLoginScreen : LoginNavigationEvents() object ShowLoginViaSiteAddressScreen : LoginNavigationEvents() + object ShowJetpackIndividualPluginOverlay : LoginNavigationEvents() } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/PostSignupInterstitialActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/PostSignupInterstitialActivity.kt index 7086e85b4116..bc86e7841c6e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/PostSignupInterstitialActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/PostSignupInterstitialActivity.kt @@ -4,19 +4,22 @@ import android.os.Bundle import androidx.activity.addCallback import androidx.lifecycle.ViewModelProvider import com.google.android.material.button.MaterialButton +import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.R -import org.wordpress.android.WordPress import org.wordpress.android.databinding.PostSignupInterstitialActivityBinding import org.wordpress.android.ui.ActivityLauncher import org.wordpress.android.ui.LocaleAwareActivity +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginFragment import org.wordpress.android.ui.sitecreation.misc.SiteCreationSource import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.DISMISS +import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.START_SITE_CONNECTION_FLOW import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.START_SITE_CREATION_FLOW import javax.inject.Inject +@AndroidEntryPoint class PostSignupInterstitialActivity : LocaleAwareActivity() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory @@ -24,7 +27,6 @@ class PostSignupInterstitialActivity : LocaleAwareActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - (application as WordPress).component().inject(this) LoginFlowThemeHelper.injectMissingCustomAttributes(theme) @@ -57,6 +59,7 @@ class PostSignupInterstitialActivity : LocaleAwareActivity() { private fun executeAction(navigationAction: NavigationAction) = when (navigationAction) { START_SITE_CREATION_FLOW -> startSiteCreationFlow() START_SITE_CONNECTION_FLOW -> startSiteConnectionFlow() + SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY -> showJetpackIndividualPluginOverlay() DISMISS -> dismiss() } @@ -74,4 +77,8 @@ class PostSignupInterstitialActivity : LocaleAwareActivity() { ActivityLauncher.viewReader(this) finish() } + + private fun showJetpackIndividualPluginOverlay() { + WPJetpackIndividualPluginFragment.show(supportFragmentManager) + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginEpilogueFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginEpilogueFragment.java index 345d10911ca3..3fb18dae8a21 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginEpilogueFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginEpilogueFragment.java @@ -228,6 +228,8 @@ public void onAfterLoad() { mBottomShadow.setVisibility(View.GONE); } } + + mParentViewModel.onSiteListLoaded(); }); } }; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/JetpackFeatureFullScreenOverlayFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/JetpackFeatureFullScreenOverlayFragment.kt index 7b8cdae5cb72..0f87850bff38 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/JetpackFeatureFullScreenOverlayFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/JetpackFeatureFullScreenOverlayFragment.kt @@ -1,13 +1,10 @@ package org.wordpress.android.ui.jetpackoverlay -import android.content.res.Resources import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout import androidx.fragment.app.activityViewModels -import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import dagger.hilt.android.AndroidEntryPoint @@ -27,6 +24,7 @@ import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.util.RtlUtils import org.wordpress.android.util.UrlUtils import org.wordpress.android.util.extensions.exhaustive +import org.wordpress.android.util.extensions.fillScreen import org.wordpress.android.util.extensions.getSerializableCompat import org.wordpress.android.util.extensions.setVisible import javax.inject.Inject @@ -66,32 +64,7 @@ class JetpackFeatureFullScreenOverlayFragment : BottomSheetDialogFragment() { RtlUtils.isRtl(view.context) ) binding.setupObservers() - - (dialog as? BottomSheetDialog)?.apply { - setOnShowListener { - val bottomSheet: FrameLayout = dialog?.findViewById( - com.google.android.material.R.id.design_bottom_sheet - ) ?: return@setOnShowListener - val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet) - bottomSheetBehavior.maxWidth = ViewGroup.LayoutParams.MATCH_PARENT - bottomSheetBehavior.isDraggable = false - if (bottomSheet.layoutParams != null) { - showFullScreenBottomSheet(bottomSheet) - } - expandBottomSheet(bottomSheetBehavior) - } - } - } - - private fun showFullScreenBottomSheet(bottomSheet: FrameLayout) { - val layoutParams = bottomSheet.layoutParams - layoutParams.height = Resources.getSystem().displayMetrics.heightPixels - bottomSheet.layoutParams = layoutParams - } - - private fun expandBottomSheet(bottomSheetBehavior: BottomSheetBehavior) { - bottomSheetBehavior.skipCollapsed = true - bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + (dialog as? BottomSheetDialog)?.fillScreen() } private fun getSiteScreen() = arguments?.getSerializableCompat(OVERLAY_SCREEN_TYPE) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/individualplugin/WPJetpackIndividualPluginFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/individualplugin/WPJetpackIndividualPluginFragment.kt new file mode 100644 index 000000000000..7620d77b110a --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/individualplugin/WPJetpackIndividualPluginFragment.kt @@ -0,0 +1,102 @@ +package org.wordpress.android.ui.jetpackoverlay.individualplugin + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.wordpress.android.ui.ActivityLauncherWrapper +import org.wordpress.android.ui.compose.theme.AppTheme +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginViewModel.ActionEvent +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginViewModel.UiState +import org.wordpress.android.ui.jetpackoverlay.individualplugin.compose.WPJetpackIndividualPluginOverlayScreen +import org.wordpress.android.util.extensions.exhaustive +import org.wordpress.android.util.extensions.fillScreen +import javax.inject.Inject + +@AndroidEntryPoint +class WPJetpackIndividualPluginFragment : BottomSheetDialogFragment() { + private val viewModel: WPJetpackIndividualPluginViewModel by viewModels() + + @Inject + lateinit var activityLauncher: ActivityLauncherWrapper + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = ComposeView(requireContext()).apply { + setContent { + AppTheme { + val uiState by viewModel.uiState.collectAsState() + when (val state = uiState) { + is UiState.Loaded -> { + WPJetpackIndividualPluginOverlayScreen( + state.sites, + onCloseClick = viewModel::onDismissScreenClick, + onPrimaryButtonClick = viewModel::onPrimaryButtonClick, + onSecondaryButtonClick = viewModel::onDismissScreenClick, + ) + } + + is UiState.None -> {} + } + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.onScreenShown() + observeActionEvents() + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = + super.onCreateDialog(savedInstanceState).apply { + (this as? BottomSheetDialog)?.fillScreen() + } + + override fun onCancel(dialog: DialogInterface) { + // called when user hits the back button + viewModel.onDismissScreenClick() + } + + private fun observeActionEvents() { + viewModel.actionEvents.onEach(this::handleActionEvents).launchIn(lifecycleScope) + } + + private fun handleActionEvents(actionEvent: ActionEvent) { + when (actionEvent) { + is ActionEvent.PrimaryButtonClick -> activityLauncher.openPlayStoreLink( + requireActivity(), + ActivityLauncherWrapper.JETPACK_PACKAGE_NAME + ) + + is ActionEvent.Dismiss -> dismiss() + }.exhaustive + } + + companion object { + const val TAG = "WP_JETPACK_INDIVIDUAL_PLUGIN_FRAGMENT" + + @JvmStatic + fun newInstance(): WPJetpackIndividualPluginFragment = WPJetpackIndividualPluginFragment() + + @JvmStatic + fun show(fm: FragmentManager): WPJetpackIndividualPluginFragment = newInstance().also { + it.show(fm, TAG) + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/individualplugin/WPJetpackIndividualPluginViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/individualplugin/WPJetpackIndividualPluginViewModel.kt new file mode 100644 index 000000000000..ea27bc93d522 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/individualplugin/WPJetpackIndividualPluginViewModel.kt @@ -0,0 +1,58 @@ +package org.wordpress.android.ui.jetpackoverlay.individualplugin + +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import org.wordpress.android.modules.BG_THREAD +import org.wordpress.android.viewmodel.ScopedViewModel +import javax.inject.Inject +import javax.inject.Named + +@HiltViewModel +class WPJetpackIndividualPluginViewModel @Inject constructor( + private val wpJetpackIndividualPluginHelper: WPJetpackIndividualPluginHelper, + @Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher, +) : ScopedViewModel(bgDispatcher) { + private val _uiState = MutableStateFlow(UiState.None) + val uiState = _uiState.asStateFlow() + + private val _actionEvents = MutableSharedFlow() + val actionEvents = _actionEvents + + fun onScreenShown() { + // TODO thomashortadev add tracking + launch { + val sites = wpJetpackIndividualPluginHelper.getJetpackConnectedSitesWithIndividualPlugins() + _uiState.update { UiState.Loaded(sites) } + } + } + + fun onDismissScreenClick() { + // TODO thomashortadev add tracking + postActionEvent(ActionEvent.Dismiss) + } + + fun onPrimaryButtonClick() { + // TODO thomashortadev add tracking + postActionEvent(ActionEvent.PrimaryButtonClick) + } + + private fun postActionEvent(actionEvent: ActionEvent) { + launch { _actionEvents.emit(actionEvent) } + } + + sealed class UiState { + object None : UiState() + data class Loaded( + val sites: List, + ) : UiState() + } + + sealed class ActionEvent { + object PrimaryButtonClick : ActionEvent() + object Dismiss : ActionEvent() + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/SitePickerActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/SitePickerActivity.java index 7c92327a498c..96f1d84f00f7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/SitePickerActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/SitePickerActivity.java @@ -43,6 +43,7 @@ import org.wordpress.android.ui.ActivityLauncher; import org.wordpress.android.ui.LocaleAwareActivity; import org.wordpress.android.ui.RequestCodes; +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginFragment; import org.wordpress.android.ui.main.SitePickerAdapter.SiteList; import org.wordpress.android.ui.main.SitePickerAdapter.SitePickerMode; import org.wordpress.android.ui.main.SitePickerAdapter.SiteRecord; @@ -79,6 +80,9 @@ import static org.wordpress.android.util.WPSwipeToRefreshHelper.buildSwipeToRefreshHelper; +import dagger.hilt.android.AndroidEntryPoint; + +@AndroidEntryPoint public class SitePickerActivity extends LocaleAwareActivity implements SitePickerAdapter.OnSiteClickListener, SitePickerAdapter.OnSelectedCountChangedListener, @@ -139,7 +143,6 @@ public class SitePickerActivity extends LocaleAwareActivity @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ((WordPress) getApplication()).component().inject(this); mViewModel = new ViewModelProvider(this, mViewModelFactory).get(SitePickerViewModel.class); @@ -155,59 +158,59 @@ public void onCreate(Bundle savedInstanceState) { AnalyticsTracker.track(Stat.SITE_SWITCHER_DISPLAYED); } - if (mSitePickerMode.isReblogMode()) { - mViewModel.getOnActionTriggered().observe( - this, - unitEvent -> unitEvent.applyIfNotHandled(action -> { - switch (action.getActionType()) { - case NAVIGATE_TO_STATE: - switch (((NavigateToState) action).getNavigateState()) { - case TO_SITE_SELECTED: - mSitePickerMode = SitePickerMode.REBLOG_CONTINUE_MODE; - if (getAdapter().getIsInSearchMode()) { - disableSearchMode(); - } - - if (mReblogActionMode == null) { - startSupportActionMode(new ReblogActionModeCallback()); - } - - SiteRecord site = ((NavigateToState) action).getSiteForReblog(); - if (site != null) { - mReblogActionMode.setTitle(site.getBlogNameOrHomeURL()); - } - break; - case TO_NO_SITE_SELECTED: - mSitePickerMode = SitePickerMode.REBLOG_SELECT_MODE; - getAdapter().clearReblogSelection(); - break; - } - break; - case CONTINUE_REBLOG_TO: - SiteRecord siteToReblog = ((ContinueReblogTo) action).getSiteForReblog(); - selectSiteAndFinish(siteToReblog); - break; - case ASK_FOR_SITE_SELECTION: - if (BuildConfig.DEBUG) { - throw new IllegalStateException( - "SitePickerActivity > Selected site was null while attempting to reblog" - ); - } else { - AppLog.e( - AppLog.T.READER, - "SitePickerActivity > Selected site was null while attempting to reblog" - ); - ToastUtils.showToast(this, R.string.site_picker_ask_site_select); - } - break; - } - return null; - })); - } - - mViewModel.getShowJetpackIndividualPluginOverlay().observe(this, this::onShowJetpackIndividualPluginOverlay); - mViewModel.checkJetpackIndividualPluginOverlayNeeded(); - + mViewModel.getOnActionTriggered().observe( + this, + unitEvent -> unitEvent.applyIfNotHandled(action -> { + switch (action.getActionType()) { + case NAVIGATE_TO_STATE: + if (!mSitePickerMode.isReblogMode()) break; + switch (((NavigateToState) action).getNavigateState()) { + case TO_SITE_SELECTED: + mSitePickerMode = SitePickerMode.REBLOG_CONTINUE_MODE; + if (getAdapter().getIsInSearchMode()) { + disableSearchMode(); + } + + if (mReblogActionMode == null) { + startSupportActionMode(new ReblogActionModeCallback()); + } + + SiteRecord site = ((NavigateToState) action).getSiteForReblog(); + if (site != null) { + mReblogActionMode.setTitle(site.getBlogNameOrHomeURL()); + } + break; + case TO_NO_SITE_SELECTED: + mSitePickerMode = SitePickerMode.REBLOG_SELECT_MODE; + getAdapter().clearReblogSelection(); + break; + } + break; + case CONTINUE_REBLOG_TO: + if (!mSitePickerMode.isReblogMode()) break; + SiteRecord siteToReblog = ((ContinueReblogTo) action).getSiteForReblog(); + selectSiteAndFinish(siteToReblog); + break; + case ASK_FOR_SITE_SELECTION: + if (!mSitePickerMode.isReblogMode()) break; + if (BuildConfig.DEBUG) { + throw new IllegalStateException( + "SitePickerActivity > Selected site was null while attempting to reblog" + ); + } else { + AppLog.e( + AppLog.T.READER, + "SitePickerActivity > Selected site was null while attempting to reblog" + ); + ToastUtils.showToast(this, R.string.site_picker_ask_site_select); + } + break; + case SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY: + WPJetpackIndividualPluginFragment.show(getSupportFragmentManager()); + break; + } + return null; + })); // If the picker is already in editing mode from previous configuration, re-enable the editing mode. if (mIsInEditMode) { startEditingVisibility(); @@ -220,16 +223,6 @@ public void onResume() { ActivityId.trackLastActivity(ActivityId.SITE_PICKER); } - private void onShowJetpackIndividualPluginOverlay(Boolean shouldShowJetpackIndividualPluginOverlay) { - // TODO thomashortadev temporary during development of #18114 - if (shouldShowJetpackIndividualPluginOverlay) { - ToastUtils.showToast( - this, - "WP - Individual plugin overlay is enabled & user has sites with Individual plugins" - ); - } - } - @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_SITE_LOCAL_ID, mCurrentLocalId); @@ -492,6 +485,7 @@ public void onAfterLoad() { mRecycleView.scrollToPosition(scrollPos); } } + mViewModel.onSiteListLoaded(); } }, mSitePickerMode, 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 386accb55434..0f1cd14db71e 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 @@ -22,6 +22,7 @@ import org.wordpress.android.WordPress import org.wordpress.android.databinding.MySiteFragmentBinding import org.wordpress.android.databinding.MySiteInfoHeaderCardBinding import org.wordpress.android.ui.ActivityLauncher +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginFragment import org.wordpress.android.ui.main.SitePickerActivity import org.wordpress.android.ui.main.utils.MeGravatarLoader import org.wordpress.android.ui.mysite.MySiteCardAndItem.SiteInfoHeaderCard @@ -187,6 +188,9 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment), viewModel.selectTab.observeEvent(viewLifecycleOwner) { navTarget -> viewPager.setCurrentItem(navTarget.position, navTarget.smoothAnimation) } + viewModel.onShowJetpackIndividualPluginOverlay.observeEvent(viewLifecycleOwner) { + WPJetpackIndividualPluginFragment.show(requireActivity().supportFragmentManager) + } } private fun MySiteFragmentBinding.loadGravatar(avatarUrl: String) = @@ -204,9 +208,9 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment), private fun MySiteFragmentBinding.loadData(state: State.SiteSelected) { tabLayout.setVisible(state.tabsUiState.showTabs) updateTabs(state.tabsUiState) - actionableEmptyView.setVisible(false) - viewModel.setActionableEmptyViewGone(actionableEmptyView.isVisible) { + if (actionableEmptyView.isVisible) { actionableEmptyView.setVisible(false) + viewModel.onActionableEmptyViewGone() } if (state.siteInfoHeaderState.hasUpdates || !header.isVisible) { siteInfo.loadMySiteDetails(state.siteInfoHeaderState.siteInfoHeader) @@ -259,11 +263,11 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment), private fun MySiteFragmentBinding.loadEmptyView(state: State.NoSites) { tabLayout.setVisible(state.tabsUiState.showTabs) - viewModel.setActionableEmptyViewVisible(actionableEmptyView.isVisible) { + if (!actionableEmptyView.isVisible) { actionableEmptyView.setVisible(true) actionableEmptyView.image.setVisible(state.shouldShowImage) + viewModel.onActionableEmptyViewVisible() } - actionableEmptyView.image.setVisible(state.shouldShowImage) siteTitle = getString(R.string.my_site_section_screen_title) updateSiteInfoToolbarView(state.siteInfoToolbarViewParams) appbarMain.setExpanded(false, true) 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 12c935202862..d7f6b36796e7 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 @@ -15,6 +15,7 @@ import androidx.lifecycle.switchMap import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode.MAIN import org.wordpress.android.R @@ -39,13 +40,14 @@ import org.wordpress.android.models.ReaderTag import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.modules.UI_THREAD import org.wordpress.android.ui.PagePostCreationSourcesDetail.STORY_FROM_MY_SITE -import org.wordpress.android.ui.blaze.BlazeFlowSource import org.wordpress.android.ui.blaze.BlazeFeatureUtils +import org.wordpress.android.ui.blaze.BlazeFlowSource import org.wordpress.android.ui.bloggingprompts.BloggingPromptsPostTagProvider import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalOverlayUtil import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalOverlayUtil.JetpackFeatureCollectionOverlaySource.FEATURE_CARD import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper import org.wordpress.android.ui.jetpackplugininstall.fullplugin.GetShowJetpackFullPluginInstallOnboardingUseCase import org.wordpress.android.ui.mysite.MySiteCardAndItem.Card.DashboardCards import org.wordpress.android.ui.mysite.MySiteCardAndItem.Card.DomainRegistrationCard @@ -203,7 +205,8 @@ class MySiteViewModel @Inject constructor( private val getShowJetpackFullPluginInstallOnboardingUseCase: GetShowJetpackFullPluginInstallOnboardingUseCase, private val jetpackInstallFullPluginShownTracker: JetpackInstallFullPluginShownTracker, private val blazeFeatureUtils: BlazeFeatureUtils, - private val jetpackFeatureRemovalPhaseHelper: JetpackFeatureRemovalPhaseHelper + private val jetpackFeatureRemovalPhaseHelper: JetpackFeatureRemovalPhaseHelper, + private val wpJetpackIndividualPluginHelper: WPJetpackIndividualPluginHelper, ) : ScopedViewModel(mainDispatcher) { private var isDefaultTabSet: Boolean = false private val _onSnackbarMessage = MutableLiveData>() @@ -222,6 +225,7 @@ class MySiteViewModel @Inject constructor( private val _onBloggingPromptsViewMore = SingleLiveEvent>() private val _onBloggingPromptsRemoved = SingleLiveEvent>() private val _onOpenJetpackInstallFullPluginOnboarding = SingleLiveEvent>() + private val _onShowJetpackIndividualPluginOverlay = SingleLiveEvent>() private val tabsUiState: LiveData = quickStartRepository.onQuickStartTabStep .switchMap { quickStartSiteMenuStep -> @@ -296,6 +300,7 @@ class MySiteViewModel @Inject constructor( val onBloggingPromptsViewMore = _onBloggingPromptsViewMore as LiveData> val onBloggingPromptsRemoved = _onBloggingPromptsRemoved as LiveData> val onOpenJetpackInstallFullPluginOnboarding = _onOpenJetpackInstallFullPluginOnboarding as LiveData> + val onShowJetpackIndividualPluginOverlay = _onShowJetpackIndividualPluginOverlay as LiveData> val onTrackWithTabSource = _onTrackWithTabSource as LiveData> val selectTab: LiveData> = _selectTab private var shouldMarkUpdateSiteTitleTaskComplete = false @@ -704,6 +709,7 @@ class MySiteViewModel @Inject constructor( add(Type.QUICK_LINK_RIBBON) add(Type.JETPACK_INSTALL_FULL_PLUGIN_CARD) } + MySiteTabType.DASHBOARD -> mutableListOf().apply { if (defaultTab == MySiteTabType.SITE_MENU) { add(Type.QUICK_START_CARD) @@ -711,6 +717,7 @@ class MySiteViewModel @Inject constructor( add(Type.DOMAIN_REGISTRATION_CARD) add(Type.QUICK_ACTIONS_CARD) } + MySiteTabType.ALL -> emptyList() } @@ -825,6 +832,7 @@ class MySiteViewModel @Inject constructor( QuickStartNewSiteTask.UPDATE_SITE_TITLE, QuickStartNewSiteTask.UPLOAD_SITE_ICON, quickStartRepository.quickStartType.getTaskFromString(QUICK_START_VIEW_SITE_LABEL) -> true + else -> false } } @@ -854,17 +862,20 @@ class MySiteViewModel @Inject constructor( ListItemAction.PLAN -> { SiteNavigationAction.OpenPlan(selectedSite) } + ListItemAction.POSTS -> SiteNavigationAction.OpenPosts(selectedSite) ListItemAction.PAGES -> { quickStartRepository.completeTask(QuickStartNewSiteTask.REVIEW_PAGES) SiteNavigationAction.OpenPages(selectedSite) } + ListItemAction.ADMIN -> SiteNavigationAction.OpenAdmin(selectedSite) ListItemAction.PEOPLE -> SiteNavigationAction.OpenPeople(selectedSite) ListItemAction.SHARING -> { quickStartRepository.requestNextStepOfTask(QuickStartNewSiteTask.ENABLE_POST_SHARING) SiteNavigationAction.OpenSharing(selectedSite) } + ListItemAction.DOMAINS -> SiteNavigationAction.OpenDomains(selectedSite) ListItemAction.SITE_SETTINGS -> SiteNavigationAction.OpenSiteSettings(selectedSite) ListItemAction.THEMES -> SiteNavigationAction.OpenThemes(selectedSite) @@ -875,16 +886,19 @@ class MySiteViewModel @Inject constructor( ) getStatsNavigationActionForSite(selectedSite) } + ListItemAction.MEDIA -> { quickStartRepository.requestNextStepOfTask( quickStartRepository.quickStartType.getTaskFromString(QUICK_START_UPLOAD_MEDIA_LABEL) ) SiteNavigationAction.OpenMedia(selectedSite) } + ListItemAction.COMMENTS -> SiteNavigationAction.OpenUnifiedComments(selectedSite) ListItemAction.VIEW_SITE -> { SiteNavigationAction.OpenSite(selectedSite) } + ListItemAction.JETPACK_SETTINGS -> SiteNavigationAction.OpenJetpackSettings(selectedSite) ListItemAction.BLAZE -> { blazeFeatureUtils.trackEntryPointTapped(BlazeFlowSource.MENU_ITEM) @@ -960,9 +974,11 @@ class MySiteViewModel @Inject constructor( !selectedSite.isUsingWpComRestApi -> { R.string.my_site_icon_dialog_change_requires_jetpack_message } + hasIcon -> { R.string.my_site_icon_dialog_change_requires_permission_message } + else -> { R.string.my_site_icon_dialog_add_requires_permission_message } @@ -1116,21 +1132,26 @@ class MySiteViewModel @Inject constructor( ) ) } + TAG_REMOVE_NEXT_STEPS_DIALOG -> onRemoveNextStepsDialogPositiveButtonClicked() } + is Negative -> when (interaction.tag) { TAG_ADD_SITE_ICON_DIALOG -> { quickStartRepository.completeTask(QuickStartNewSiteTask.UPLOAD_SITE_ICON) quickStartRepository.checkAndShowQuickStartNotice() } + TAG_CHANGE_SITE_ICON_DIALOG -> { analyticsTrackerWrapper.track(Stat.MY_SITE_ICON_REMOVED) quickStartRepository.completeTask(QuickStartNewSiteTask.UPLOAD_SITE_ICON) quickStartRepository.checkAndShowQuickStartNotice() selectedSiteRepository.updateSiteIconMediaId(0, true) } + TAG_REMOVE_NEXT_STEPS_DIALOG -> onRemoveNextStepsDialogNegativeButtonClicked() } + is Dismissed -> when (interaction.tag) { TAG_ADD_SITE_ICON_DIALOG, TAG_CHANGE_SITE_ICON_DIALOG -> { quickStartRepository.completeTask(QuickStartNewSiteTask.UPLOAD_SITE_ICON) @@ -1382,8 +1403,10 @@ class MySiteViewModel @Inject constructor( when (params.postCardType) { PostCardType.CREATE_FIRST, PostCardType.CREATE_NEXT -> _onNavigation.value = Event(SiteNavigationAction.OpenEditorToCreateNewPost(site)) + PostCardType.DRAFT -> _onNavigation.value = Event(SiteNavigationAction.EditDraftPost(site, params.postId)) + PostCardType.SCHEDULED -> _onNavigation.value = Event(SiteNavigationAction.EditScheduledPost(site, params.postId)) } @@ -1400,6 +1423,7 @@ class MySiteViewModel @Inject constructor( _onNavigation.value = when (postCardType) { PostCardType.CREATE_FIRST, PostCardType.CREATE_NEXT -> Event(SiteNavigationAction.OpenEditorToCreateNewPost(site)) + PostCardType.DRAFT -> Event(SiteNavigationAction.OpenDraftsPosts(site)) PostCardType.SCHEDULED -> Event(SiteNavigationAction.OpenScheduledPosts(site)) } @@ -1541,14 +1565,26 @@ class MySiteViewModel @Inject constructor( fun isRefreshing() = mySiteSourceManager.isRefreshing() - fun setActionableEmptyViewGone(isVisible: Boolean, setGone: () -> Unit) { - if (isVisible) analyticsTrackerWrapper.track(Stat.MY_SITE_NO_SITES_VIEW_HIDDEN) - setGone() + fun onActionableEmptyViewGone() { + analyticsTrackerWrapper.track(Stat.MY_SITE_NO_SITES_VIEW_HIDDEN) + } + + fun onActionableEmptyViewVisible() { + analyticsTrackerWrapper.track(Stat.MY_SITE_NO_SITES_VIEW_DISPLAYED) + checkJetpackIndividualPluginOverlayShouldShow() } - fun setActionableEmptyViewVisible(isVisible: Boolean, setVisible: () -> Unit) { - if (!isVisible) analyticsTrackerWrapper.track(Stat.MY_SITE_NO_SITES_VIEW_DISPLAYED) - setVisible() + private fun checkJetpackIndividualPluginOverlayShouldShow() { + // don't check if already shown + if (_onShowJetpackIndividualPluginOverlay.value?.peekContent() == Unit) return + + viewModelScope.launch { + val showOverlay = wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay() + if (showOverlay) { + delay(DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) + _onShowJetpackIndividualPluginOverlay.value = Event(Unit) + } + } } fun trackWithTabSource(event: MySiteTrackWithTabSource) { @@ -1764,5 +1800,6 @@ class MySiteViewModel @Inject constructor( const val LIST_SCROLL_DELAY_MS = 500L const val MY_SITE_TAB = "tab" const val TAB_SOURCE = "tab_source" + private const val DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY = 500L } } diff --git a/WordPress/src/main/java/org/wordpress/android/util/extensions/DialogExtensions.kt b/WordPress/src/main/java/org/wordpress/android/util/extensions/DialogExtensions.kt index 64fc71f67118..93056c4071e7 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/extensions/DialogExtensions.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/extensions/DialogExtensions.kt @@ -2,6 +2,11 @@ package org.wordpress.android.util.extensions import android.app.Dialog import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.FrameLayout +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog import org.wordpress.android.R import org.wordpress.android.R.attr @@ -26,3 +31,22 @@ fun Dialog.setStatusBarAsSurfaceColor() { } } } + +fun BottomSheetDialog.fillScreen(isDraggable: Boolean = false) { + setOnShowListener { + val bottomSheet: FrameLayout = findViewById( + com.google.android.material.R.id.design_bottom_sheet + ) ?: return@setOnShowListener + + val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet) + bottomSheetBehavior.maxWidth = ViewGroup.LayoutParams.MATCH_PARENT + bottomSheetBehavior.isDraggable = isDraggable + bottomSheetBehavior.skipCollapsed = true + bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + + bottomSheet.layoutParams?.let { layoutParams -> + layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT + bottomSheet.layoutParams = layoutParams + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModel.kt index 3bed4a03c1ab..203416e5d8c0 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModel.kt @@ -1,6 +1,9 @@ package org.wordpress.android.viewmodel.accounts import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_ADD_SELF_HOSTED_SITE_TAPPED import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_CREATE_NEW_SITE_TAPPED import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_DISMISSED @@ -8,10 +11,12 @@ import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_IN import org.wordpress.android.ui.accounts.UnifiedLoginTracker import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Click import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Step.SUCCESS +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper import org.wordpress.android.viewmodel.SingleLiveEvent import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.DISMISS +import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.START_SITE_CONNECTION_FLOW import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.START_SITE_CREATION_FLOW import javax.inject.Inject @@ -20,7 +25,8 @@ class PostSignupInterstitialViewModel @Inject constructor( private val appPrefs: AppPrefsWrapper, private val unifiedLoginTracker: UnifiedLoginTracker, - private val analyticsTracker: AnalyticsTrackerWrapper + private val analyticsTracker: AnalyticsTrackerWrapper, + private val wpJetpackIndividualPluginHelper: WPJetpackIndividualPluginHelper, ) : ViewModel() { val navigationAction: SingleLiveEvent = SingleLiveEvent() @@ -28,6 +34,7 @@ class PostSignupInterstitialViewModel analyticsTracker.track(WELCOME_NO_SITES_INTERSTITIAL_SHOWN) unifiedLoginTracker.track(step = SUCCESS) appPrefs.shouldShowPostSignupInterstitial = false + checkJetpackIndividualPluginOverlayShouldShow() } fun onCreateNewSiteButtonPressed() { @@ -52,5 +59,27 @@ class PostSignupInterstitialViewModel navigationAction.value = DISMISS } - enum class NavigationAction { START_SITE_CREATION_FLOW, START_SITE_CONNECTION_FLOW, DISMISS } + private fun checkJetpackIndividualPluginOverlayShouldShow() { + // don't check if already shown + if (navigationAction.value == SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) return + + viewModelScope.launch { + val showOverlay = wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay() + if (showOverlay) { + delay(DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) + navigationAction.postValue(SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) + } + } + } + + enum class NavigationAction { + START_SITE_CREATION_FLOW, + START_SITE_CONNECTION_FLOW, + DISMISS, + SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY + } + + companion object { + private const val DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY = 500L + } } diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/SitePickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/SitePickerViewModel.kt index 497df0dda413..44aa163e863e 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/main/SitePickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/main/SitePickerViewModel.kt @@ -4,17 +4,19 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper import org.wordpress.android.ui.main.SitePickerAdapter.SiteRecord import org.wordpress.android.viewmodel.Event -import org.wordpress.android.viewmodel.SingleLiveEvent import org.wordpress.android.viewmodel.main.SitePickerViewModel.Action.AskForSiteSelection import org.wordpress.android.viewmodel.main.SitePickerViewModel.Action.ContinueReblogTo import org.wordpress.android.viewmodel.main.SitePickerViewModel.Action.NavigateToState +import org.wordpress.android.viewmodel.main.SitePickerViewModel.Action.ShowJetpackIndividualPluginOverlay import org.wordpress.android.viewmodel.main.SitePickerViewModel.ActionType.ASK_FOR_SITE_SELECTION import org.wordpress.android.viewmodel.main.SitePickerViewModel.ActionType.CONTINUE_REBLOG_TO import org.wordpress.android.viewmodel.main.SitePickerViewModel.ActionType.NAVIGATE_TO_STATE +import org.wordpress.android.viewmodel.main.SitePickerViewModel.ActionType.SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY import org.wordpress.android.viewmodel.main.SitePickerViewModel.NavigateState.TO_NO_SITE_SELECTED import org.wordpress.android.viewmodel.main.SitePickerViewModel.NavigateState.TO_SITE_SELECTED import javax.inject.Inject @@ -25,18 +27,8 @@ class SitePickerViewModel @Inject constructor( private val _onActionTriggered = MutableLiveData>() val onActionTriggered: LiveData> = _onActionTriggered - private val _showJetpackIndividualPluginOverlay = SingleLiveEvent() - val showJetpackIndividualPluginOverlay: LiveData = _showJetpackIndividualPluginOverlay - private var siteForReblog: SiteRecord? = null - fun checkJetpackIndividualPluginOverlayNeeded() { - viewModelScope.launch { - _showJetpackIndividualPluginOverlay - .postValue(wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay()) - } - } - fun onSiteForReblogSelected(siteRecord: SiteRecord) { selectSite(siteRecord) } @@ -66,10 +58,24 @@ class SitePickerViewModel @Inject constructor( _onActionTriggered.value = Event(NavigateToState(TO_SITE_SELECTED, siteRecord)) } + fun onSiteListLoaded() { + // don't check if already shown + if (_onActionTriggered.value?.peekContent() == ShowJetpackIndividualPluginOverlay) return + + viewModelScope.launch { + val showOverlay = wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay() + if (showOverlay) { + delay(DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) + _onActionTriggered.postValue(Event(ShowJetpackIndividualPluginOverlay)) + } + } + } + enum class ActionType { NAVIGATE_TO_STATE, CONTINUE_REBLOG_TO, - ASK_FOR_SITE_SELECTION + ASK_FOR_SITE_SELECTION, + SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY } enum class NavigateState { @@ -87,5 +93,11 @@ class SitePickerViewModel @Inject constructor( ) object AskForSiteSelection : Action(ASK_FOR_SITE_SELECTION) + + object ShowJetpackIndividualPluginOverlay : Action(SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) + } + + companion object { + private const val DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY = 500L } } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/accounts/LoginEpilogueViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/accounts/LoginEpilogueViewModelTest.kt index 173bcffa50b5..95acc2d4f38c 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/accounts/LoginEpilogueViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/accounts/LoginEpilogueViewModelTest.kt @@ -8,6 +8,7 @@ import org.mockito.Mock import org.mockito.kotlin.whenever import org.wordpress.android.BaseUnitTest import org.wordpress.android.fluxc.store.SiteStore +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.util.BuildConfigWrapper @@ -24,9 +25,17 @@ class LoginEpilogueViewModelTest : BaseUnitTest() { @Mock lateinit var siteStore: SiteStore + @Mock + private lateinit var wpJetpackIndividualPluginHelper: WPJetpackIndividualPluginHelper + @Before fun setUp() { - viewModel = LoginEpilogueViewModel(appPrefsWrapper, buildConfigWrapper, siteStore) + viewModel = LoginEpilogueViewModel( + appPrefsWrapper, + buildConfigWrapper, + siteStore, + wpJetpackIndividualPluginHelper + ) } @Test @@ -262,6 +271,30 @@ class LoginEpilogueViewModelTest : BaseUnitTest() { assertThat(navigationEvents.last()).isInstanceOf(LoginNavigationEvents.ShowNoJetpackSites::class.java) } + @Test + fun `when onSiteListLoaded is invoked then show jetpack individual plugin overlay`() = + test { + val navigationEvents = initObservers().navigationEvents + whenever(wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay()).thenReturn(true) + + viewModel.onSiteListLoaded() + advanceUntilIdle() + + assertThat(navigationEvents.last()).isEqualTo(LoginNavigationEvents.ShowJetpackIndividualPluginOverlay) + } + + @Test + fun `when onSiteListLoaded is invoked then don't show jetpack individual plugin overlay`() = + test { + val navigationEvents = initObservers().navigationEvents + whenever(wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay()).thenReturn(false) + + viewModel.onSiteListLoaded() + advanceUntilIdle() + + assertThat(navigationEvents.lastOrNull()).isNull() + } + private data class Observers(val navigationEvents: List) private fun initObservers(): Observers { 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 0d7f724b9f77..c58ff140134c 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 @@ -58,6 +58,7 @@ import org.wordpress.android.ui.bloggingprompts.BloggingPromptsPostTagProvider import org.wordpress.android.ui.bloggingprompts.BloggingPromptsSettingsHelper import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalOverlayUtil import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper import org.wordpress.android.ui.jetpackplugininstall.fullplugin.GetShowJetpackFullPluginInstallOnboardingUseCase import org.wordpress.android.ui.mysite.MySiteCardAndItem.Card import org.wordpress.android.ui.mysite.MySiteCardAndItem.Card.DashboardCards @@ -327,6 +328,9 @@ class MySiteViewModelTest : BaseUnitTest() { @Mock lateinit var jetpackFeatureRemovalPhaseHelper: JetpackFeatureRemovalPhaseHelper + @Mock + lateinit var wpJetpackIndividualPluginHelper: WPJetpackIndividualPluginHelper + private lateinit var viewModel: MySiteViewModel private lateinit var uiModels: MutableList private lateinit var snackbars: MutableList @@ -546,7 +550,8 @@ class MySiteViewModelTest : BaseUnitTest() { getShowJetpackFullPluginInstallOnboardingUseCase, jetpackInstallFullPluginShownTracker, blazeFeatureUtils, - jetpackFeatureRemovalPhaseHelper + jetpackFeatureRemovalPhaseHelper, + wpJetpackIndividualPluginHelper, ) uiModels = mutableListOf() snackbars = mutableListOf() @@ -3273,6 +3278,7 @@ class MySiteViewModelTest : BaseUnitTest() { verify(blazeFeatureUtils).trackEntryPointTapped(BlazeFlowSource.DASHBOARD_CARD) } + @Test fun `when promote with blaze card menu is accessed, then blaze card menu is accessed is tracked`() = test { initSelectedSite() @@ -3297,6 +3303,28 @@ class MySiteViewModelTest : BaseUnitTest() { ) } + @Test + fun `when onActionableEmptyViewVisible is invoked then show jetpack individual plugin overlay`() = + test { + whenever(wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay()).thenReturn(true) + + viewModel.onActionableEmptyViewVisible() + advanceUntilIdle() + + assertThat(viewModel.onShowJetpackIndividualPluginOverlay.value?.peekContent()).isEqualTo(Unit) + } + + @Test + fun `when onActionableEmptyViewVisible is invoked then don't show jetpack individual plugin overlay`() = + test { + whenever(wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay()).thenReturn(false) + + viewModel.onActionableEmptyViewVisible() + advanceUntilIdle() + + assertThat(viewModel.onShowJetpackIndividualPluginOverlay.value?.peekContent()).isNull() + } + private fun findQuickActionsCard() = getLastItems().find { it is QuickActionsCard } as QuickActionsCard? private fun findQuickStartDynamicCard() = getLastItems().find { it is DynamicCard } as DynamicCard? diff --git a/WordPress/src/test/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModelTest.kt index f027a3af04b2..09cc72d8ceb2 100644 --- a/WordPress/src/test/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModelTest.kt @@ -2,18 +2,21 @@ package org.wordpress.android.viewmodel.accounts import androidx.lifecycle.Observer import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.junit.MockitoJUnitRunner import org.mockito.kotlin.mock import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import org.wordpress.android.BaseUnitTest import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_ADD_SELF_HOSTED_SITE_TAPPED import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_CREATE_NEW_SITE_TAPPED import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_DISMISSED import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_SHOWN import org.wordpress.android.ui.accounts.UnifiedLoginTracker +import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction @@ -27,13 +30,19 @@ class PostSignupInterstitialViewModelTest : BaseUnitTest() { private val appPrefs: AppPrefsWrapper = mock() private val unifiedLoginTracker: UnifiedLoginTracker = mock() private val analyticsTracker: AnalyticsTrackerWrapper = mock() + private val wpJetpackIndividualPluginHelper: WPJetpackIndividualPluginHelper = mock() private val observer: Observer = mock() private lateinit var viewModel: PostSignupInterstitialViewModel @Before fun setUp() { - viewModel = PostSignupInterstitialViewModel(appPrefs, unifiedLoginTracker, analyticsTracker) + viewModel = PostSignupInterstitialViewModel( + appPrefs, + unifiedLoginTracker, + analyticsTracker, + wpJetpackIndividualPluginHelper + ) viewModel.navigationAction.observeForever(observer) } @@ -45,6 +54,27 @@ class PostSignupInterstitialViewModelTest : BaseUnitTest() { verify(appPrefs).shouldShowPostSignupInterstitial = false } + @Test + fun `given overlay should show when interstitial is shown then show jetpack individual plugin overlay`() = test { + whenever(wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay()).thenReturn(true) + + viewModel.onInterstitialShown() + advanceUntilIdle() + + assertThat(viewModel.navigationAction.value).isEqualTo(NavigationAction.SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) + } + + @Test + fun `given overlay should not show when interstitial is shown then don't show jetpack individual plugin overlay`() = + test { + whenever(wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay()).thenReturn(false) + + viewModel.onInterstitialShown() + advanceUntilIdle() + + assertThat(viewModel.navigationAction.value).isNull() + } + @Test fun `when create new site button is pressed should start site creation flow`() { viewModel.onCreateNewSiteButtonPressed() diff --git a/WordPress/src/test/java/org/wordpress/android/viewmodel/main/SitePickerViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/viewmodel/main/SitePickerViewModelTest.kt index 71dfce3940be..b3d3088f2e6a 100644 --- a/WordPress/src/test/java/org/wordpress/android/viewmodel/main/SitePickerViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/viewmodel/main/SitePickerViewModelTest.kt @@ -16,6 +16,7 @@ import org.wordpress.android.viewmodel.main.SitePickerViewModel.Action import org.wordpress.android.viewmodel.main.SitePickerViewModel.Action.AskForSiteSelection import org.wordpress.android.viewmodel.main.SitePickerViewModel.Action.ContinueReblogTo import org.wordpress.android.viewmodel.main.SitePickerViewModel.Action.NavigateToState +import org.wordpress.android.viewmodel.main.SitePickerViewModel.Action.ShowJetpackIndividualPluginOverlay import org.wordpress.android.viewmodel.main.SitePickerViewModel.NavigateState.TO_NO_SITE_SELECTED import org.wordpress.android.viewmodel.main.SitePickerViewModel.NavigateState.TO_SITE_SELECTED @@ -94,22 +95,24 @@ class SitePickerViewModelTest : BaseUnitTest() { } @Test - fun `when checkJetpackIndividualPluginOverlayNeeded is invoked then showJetpackIndividualPluginOverlay is true`() = + fun `when onSiteListLoaded is invoked then show jetpack individual plugin overlay`() = test { whenever(wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay()).thenReturn(true) - viewModel.checkJetpackIndividualPluginOverlayNeeded() + viewModel.onSiteListLoaded() + advanceUntilIdle() - assertThat(viewModel.showJetpackIndividualPluginOverlay.value).isTrue() + assertThat(viewModel.onActionTriggered.value?.peekContent()).isEqualTo(ShowJetpackIndividualPluginOverlay) } @Test - fun `when checkJetpackIndividualPluginOverlayNeeded is invoked then showJetpackIndividualPluginOverlay is false`() = + fun `when onSiteListLoaded is invoked then don't show jetpack individual plugin overlay`() = test { whenever(wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay()).thenReturn(false) - viewModel.checkJetpackIndividualPluginOverlayNeeded() + viewModel.onSiteListLoaded() + advanceUntilIdle() - assertThat(viewModel.showJetpackIndividualPluginOverlay.value).isFalse() + assertThat(viewModel.onActionTriggered.value?.peekContent()).isNull() } }