Repository: wordpress-mobile/WordPress-Android (language: kotlin)

Issue:
Dashboard Personalization: Add more menu to Today's Stats card
Today's Stats card


```[tasklist]
### Tasks
- [x] Add more menu with options **View Stats** and **Hide This**
- [x] Implement action for Hide This -> Turn the today's stats preference to hide
- [x] Implement action for View Stats -> navigate to stats
- [x] Add track events (see below)
- [x] Remove footer links
- [x] Remove tracking events for footer CTA
- [x] Remove `my_site_dashboard_todays_stats_card` remote feature flag from code base
- [x] Create stats view model slice
```

**Tracking Events**
<div>

Action | Event | Property
-- | -- | --
Tap More | my_site_dashboard_contextual_menu_accessed | card: stats
Tap More > View Stats | my_site_dashboard_card_menu_item_tapped | card: stats item:view_stats
Tap > Hide This | my_site_dashboard_card_menu_item_tapped | card: stats item:hide_this

</div>


Discussion hints:
nan

Pull request: Dashboard Personalization: Todays Stats Implement Hide This
Closes #18944 

This PR implements the logic for hiding the `todays_stats` dashboard card when the "hide this" more menu item is tapped.

**Merge Instructions**
- Ensure PR #19058 has been merged
- Remove Not Ready for Merge
- Ensure base branch is feature/dashboard-personalization
- Merge as normal

**To test:**
- Install the app
- Login to the app and select a site that has stats access
- ✅ Verify the todays stats card is shown
- Tap on todays stats card > more menu
- Tap on the Hide this more menu item
- ✅ Verify the dashboard refresh indicator is showing
- ✅ Verify the todays stats card is hidden
- Switch to another site that has stats access
- ✅ Verify the todays stats card is shown.

## Regression Notes
1. Potential unintended areas of impact
Todays stats card is not hidden

2. What I did to test those areas of impact (or what existing automated tests I relied on)
Manual and unit tests

3. What automated tests I added (or what prevented me from doing so)
`CardsSourceTest` and `TodaysStatsViewModelSliceTest`

PR submission checklist:
- [X] I have completed the Regression Notes.
- [X] I have considered adding accessibility improvements for my changes.
- [X] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary.

UI Changes testing checklist: N/A


Test command: ./gradlew :WordPress:testWordpressVanillaDebugUnitTest --tests "org.wordpress.android.ui.mysite.cards.dashboard.CardsSourceTest" --tests "org.wordpress.android.ui.mysite.cards.dashboard.todaysstats.TodaysStatsViewModelSliceTest"

Fix patch:
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 905d06f1532a..ecf046c70f95 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
@@ -312,7 +312,8 @@ class MySiteViewModel @Inject constructor(
     val onShowJetpackIndividualPluginOverlay = _onShowJetpackIndividualPluginOverlay as LiveData<Event<Unit>>
     val onTrackWithTabSource = _onTrackWithTabSource as LiveData<Event<MySiteTrackWithTabSource>>
     val selectTab: LiveData<Event<TabNavigation>> = _selectTab
-    val refresh = merge(blazeCardViewModelSlice.refresh, pagesCardViewModelSlice.refresh)
+    val refresh =
+        merge(blazeCardViewModelSlice.refresh, pagesCardViewModelSlice.refresh, todaysStatsViewModelSlice.refresh)
     val domainTransferCardRefresh = domainTransferCardViewModel.refresh
 
     private var shouldMarkUpdateSiteTitleTaskComplete = false
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/CardsSource.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/CardsSource.kt
index 386c1d8be23c..182673578d15 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/CardsSource.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/CardsSource.kt
@@ -99,7 +99,7 @@ class CardsSource @Inject constructor(
     }
 
     private fun getCardTypes(selectedSite: SiteModel) = mutableListOf<Type>().apply {
-        add(Type.TODAYS_STATS)
+        if (shouldRequestStatsCard(selectedSite)) add(Type.TODAYS_STATS)
         if (shouldRequestPagesCard(selectedSite)) add(Type.PAGES)
         if (dashboardActivityLogCardFeatureUtils.shouldRequestActivityCard(selectedSite)) add(Type.ACTIVITY)
         add(Type.POSTS)
@@ -110,6 +110,10 @@ class CardsSource @Inject constructor(
                 !appPrefsWrapper.getShouldHidePagesDashboardCard(selectedSite.siteId)
     }
 
+    private fun shouldRequestStatsCard(selectedSite: SiteModel): Boolean {
+        return !appPrefsWrapper.getShouldHideTodaysStatsDashboardCard(selectedSite.siteId)
+    }
+
     private fun MediatorLiveData<CardsUpdate>.postErrorState() {
         val lastStateCards = this.value?.cards
         val showErrorCard = lastStateCards.isNullOrEmpty()
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsViewModelSlice.kt
index de3a06c142cc..0a6b9f64a56d 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsViewModelSlice.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsViewModelSlice.kt
@@ -7,17 +7,22 @@ import org.wordpress.android.ui.mysite.MySiteCardAndItemBuilderParams.TodaysStat
 import org.wordpress.android.ui.mysite.SelectedSiteRepository
 import org.wordpress.android.ui.mysite.SiteNavigationAction
 import org.wordpress.android.ui.mysite.cards.dashboard.CardsTracker
+import org.wordpress.android.ui.prefs.AppPrefsWrapper
 import org.wordpress.android.viewmodel.Event
 import javax.inject.Inject
 
 class TodaysStatsViewModelSlice @Inject constructor(
     private val cardsTracker: CardsTracker,
     private val selectedSiteRepository: SelectedSiteRepository,
-    private val jetpackFeatureRemovalPhaseHelper: JetpackFeatureRemovalPhaseHelper
+    private val jetpackFeatureRemovalPhaseHelper: JetpackFeatureRemovalPhaseHelper,
+    private val appPrefsWrapper: AppPrefsWrapper
 ) {
     private val _onNavigation = MutableLiveData<Event<SiteNavigationAction>>()
     val onNavigation = _onNavigation
 
+    private val _refresh = MutableLiveData<Event<Boolean>>()
+    val refresh = _refresh
+
     fun getTodaysStatsBuilderParams(todaysStatsCardModel: TodaysStatsCardModel?): TodaysStatsCardBuilderParams {
         return TodaysStatsCardBuilderParams(
             todaysStatsCard = todaysStatsCardModel,
@@ -61,7 +66,8 @@ class TodaysStatsViewModelSlice @Inject constructor(
             CardsTracker.Type.STATS.label,
             TodaysStatsMenuItemType.HIDE_THIS.label
         )
-        // todo implement the logic to hide the card
+        appPrefsWrapper.setShouldHideTodaysStatsDashboardCard(selectedSiteRepository.getSelectedSite()!!.siteId, true)
+        _refresh.postValue(Event(true))
     }
 
     private fun onViewStatsMenuItemClick() {
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java
index 9d1c38c3f221..84ead9e91dc3 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java
@@ -200,7 +200,8 @@ public enum DeletablePrefKey implements PrefKey {
         // Should show Jetpack Social no connections UI
         SHOULD_SHOW_JETPACK_SOCIAL_NO_CONNECTIONS,
         SHOULD_HIDE_ACTIVITY_DASHBOARD_CARD,
-        SHOULD_HIDE_PAGES_DASHBOARD_CARD
+        SHOULD_HIDE_PAGES_DASHBOARD_CARD,
+        SHOULD_HIDE_TODAY_STATS_DASHBOARD_CARD
     }
 
     /**
@@ -1743,4 +1744,16 @@ public static void setShouldHidePagesDashboardCard(final long siteId, final bool
     public static Boolean getShouldHidePagesDashboardCard(final long siteId) {
         return prefs().getBoolean(getSiteIdHidePagesDashboardCardKey(siteId), false);
     }
+
+    public static void setShouldHideTodaysStatsDashboardCard(final long siteId, final boolean isHidden) {
+        prefs().edit().putBoolean(getSiteIdHideTodaysStatsDashboardCardKey(siteId), isHidden).apply();
+    }
+
+    @NonNull private static String getSiteIdHideTodaysStatsDashboardCardKey(long siteId) {
+        return DeletablePrefKey.SHOULD_HIDE_TODAY_STATS_DASHBOARD_CARD.name() + siteId;
+    }
+
+    public static Boolean getShouldHideTodaysStatsDashboardCard(final long siteId) {
+        return prefs().getBoolean(getSiteIdHideTodaysStatsDashboardCardKey(siteId), false);
+    }
 }
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt
index b2d4b78952bc..6f2ca0de5d1b 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt
@@ -386,6 +386,14 @@ class AppPrefsWrapper @Inject constructor() {
     fun getShouldHidePagesDashboardCard(siteId: Long): Boolean =
         AppPrefs.getShouldHidePagesDashboardCard(siteId)
 
+    fun setShouldHideTodaysStatsDashboardCard(
+        siteId: Long,
+        isHidden: Boolean
+    ) = AppPrefs.setShouldHideTodaysStatsDashboardCard(siteId, isHidden)
+
+    fun getShouldHideTodaysStatsDashboardCard(siteId: Long): Boolean =
+        AppPrefs.getShouldHideTodaysStatsDashboardCard(siteId)
+
     fun getAllPrefs(): Map<String, Any?> = AppPrefs.getAllPrefs()
 
     fun setString(prefKey: PrefKey, value: String) {


Test patch:
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/CardsSourceTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/CardsSourceTest.kt
index bb1d6d601da8..01014309a99e 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/CardsSourceTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/CardsSourceTest.kt
@@ -188,7 +188,8 @@ class CardsSourceTest : BaseUnitTest() {
     private fun setUpMocks(
         isDashboardCardActivityLogEnabled: Boolean = false,
         isRequestPages: Boolean = false,
-        isPagesCardHidden: Boolean = false
+        isPagesCardHidden: Boolean = false,
+        isTodaysStatsCardHidden: Boolean = false
     ) {
         whenever(siteModel.id).thenReturn(SITE_LOCAL_ID)
         whenever(selectedSiteRepository.getSelectedSite()).thenReturn(siteModel)
@@ -197,6 +198,8 @@ class CardsSourceTest : BaseUnitTest() {
         whenever(siteModel.hasCapabilityEditPages).thenReturn(isRequestPages)
         whenever(appPrefsWrapper.getShouldHidePagesDashboardCard(any()))
             .thenReturn(isPagesCardHidden)
+        whenever(appPrefsWrapper.getShouldHideTodaysStatsDashboardCard(any()))
+            .thenReturn(isTodaysStatsCardHidden)
     }
 
     /* GET DATA */
@@ -544,4 +547,37 @@ class CardsSourceTest : BaseUnitTest() {
 
             verify(cardsStore).fetchCards(siteModel, DEFAULT_CARD_TYPE)
         }
+
+    @Test
+    fun `given stats card not hidden, when refresh is invoked, then stats requested from network`() =
+        test {
+            setUpMocks(isTodaysStatsCardHidden = false)
+            val result = mutableListOf<CardsUpdate>()
+            whenever(cardsStore.getCards(siteModel)).thenReturn(flowOf(data))
+            whenever(cardsStore.fetchCards(siteModel, DEFAULT_CARD_TYPE)).thenReturn(success)
+            cardSource.refresh.observeForever { }
+
+            cardSource.build(testScope(), SITE_LOCAL_ID).observeForever {
+                it?.let { result.add(it) }
+            }
+            advanceUntilIdle()
+
+            verify(cardsStore).fetchCards(siteModel, DEFAULT_CARD_TYPE)
+        }
+
+    @Test
+    fun `given stats card is hidden, when refresh is invoked, then stats not requested from network`() =
+        test {
+            setUpMocks(isTodaysStatsCardHidden = true)
+            val result = mutableListOf<CardsUpdate>()
+            whenever(cardsStore.getCards(siteModel)).thenReturn(flowOf(data))
+            cardSource.refresh.observeForever { }
+
+            cardSource.build(testScope(), SITE_LOCAL_ID).observeForever {
+                it?.let { result.add(it) }
+            }
+            advanceUntilIdle()
+
+            verify(cardsStore).fetchCards(siteModel, listOf(CardModel.Type.POSTS))
+        }
 }
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsViewModelSliceTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsViewModelSliceTest.kt
index 37d325dc3408..dc057216dcdd 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsViewModelSliceTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsViewModelSliceTest.kt
@@ -16,6 +16,7 @@ import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper
 import org.wordpress.android.ui.mysite.SelectedSiteRepository
 import org.wordpress.android.ui.mysite.SiteNavigationAction
 import org.wordpress.android.ui.mysite.cards.dashboard.CardsTracker
+import org.wordpress.android.ui.prefs.AppPrefsWrapper
 
 @ExperimentalCoroutinesApi
 @RunWith(MockitoJUnitRunner::class)
@@ -29,10 +30,15 @@ class TodaysStatsViewModelSliceTest : BaseUnitTest() {
     @Mock
     lateinit var jetpackFeatureRemovalPhaseHelper: JetpackFeatureRemovalPhaseHelper
 
+    @Mock
+    lateinit var appPrefsWrapper: AppPrefsWrapper
+
     private lateinit var todaysStatsViewModelSlice: TodaysStatsViewModelSlice
 
     private lateinit var navigationActions: MutableList<SiteNavigationAction>
 
+    private lateinit var refreshEvents: MutableList<Boolean>
+
     private val site = mock<SiteModel>()
 
     @Before
@@ -40,7 +46,8 @@ class TodaysStatsViewModelSliceTest : BaseUnitTest() {
         todaysStatsViewModelSlice = TodaysStatsViewModelSlice(
             cardsTracker,
             selectedSiteRepository,
-            jetpackFeatureRemovalPhaseHelper
+            jetpackFeatureRemovalPhaseHelper,
+            appPrefsWrapper
         )
         navigationActions = mutableListOf()
         todaysStatsViewModelSlice.onNavigation.observeForever { event ->
@@ -48,6 +55,12 @@ class TodaysStatsViewModelSliceTest : BaseUnitTest() {
                 navigationActions.add(it)
             }
         }
+        refreshEvents = mutableListOf()
+        todaysStatsViewModelSlice.refresh.observeForever { event ->
+            event?.getContentIfNotHandled()?.let {
+                refreshEvents.add(it)
+            }
+        }
         whenever(selectedSiteRepository.getSelectedSite()).thenReturn(site)
     }
 
@@ -110,13 +123,18 @@ class TodaysStatsViewModelSliceTest : BaseUnitTest() {
 
     @Test
     fun `given todays stats card, when more menu item hide this is accessed, then event is tracked`() = test {
+        val siteId = 1L
+        whenever(selectedSiteRepository.getSelectedSite()?.siteId).thenReturn(siteId)
+
         val params = todaysStatsViewModelSlice.getTodaysStatsBuilderParams(mock())
 
         params.moreMenuClickParams.onHideThisMenuItemClick.invoke()
 
+        verify(appPrefsWrapper).setShouldHideTodaysStatsDashboardCard(siteId,true)
         verify(cardsTracker).trackCardMoreMenuItemClicked(
             CardsTracker.Type.STATS.label,
             TodaysStatsMenuItemType.HIDE_THIS.label
         )
+        assertThat(refreshEvents).containsOnly(true)
     }
 }