Skip to content

Commit 19696a3

Browse files
authored
CMM-1141 create new stats screen and experimental feature flag (#22491)
* Add experimental feature flag * Creating new stats screen * Making the screen tabbed * Fixing and adding tests * Hiding feature for non-dev builds
1 parent 20cb0e7 commit 19696a3

File tree

10 files changed

+191
-5
lines changed

10 files changed

+191
-5
lines changed

WordPress/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@
106106
android:label="@string/my_site_section_screen_title"
107107
android:exported="false" />
108108

109+
<activity
110+
android:name=".ui.newstats.NewStatsActivity"
111+
android:theme="@style/WordPress.NoActionBar"
112+
android:label="@string/stats"
113+
android:exported="false" />
114+
109115
<!-- Account activities -->
110116
<activity
111117
android:name=".ui.main.MeActivity"

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import org.wordpress.android.ui.posts.PostListType
5353
import org.wordpress.android.ui.posts.PostUtils
5454
import org.wordpress.android.ui.reader.ReaderActivityLauncher
5555
import org.wordpress.android.ui.reader.tracker.ReaderTracker
56+
import org.wordpress.android.ui.newstats.NewStatsActivity
5657
import org.wordpress.android.ui.stats.StatsTimeframe
5758
import org.wordpress.android.ui.stats.refresh.utils.StatsLaunchedFrom
5859
import org.wordpress.android.ui.uploads.UploadService
@@ -588,6 +589,8 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment),
588589
StatsLaunchedFrom.QUICK_ACTIONS
589590
)
590591

592+
is SiteNavigationAction.OpenNewStats -> NewStatsActivity.start(requireContext())
593+
591594
is SiteNavigationAction.ConnectJetpackForStats ->
592595
ActivityLauncher.viewConnectJetpackForStats(activity, action.site)
593596
is SiteNavigationAction.StartWPComLoginForJetpackStats ->

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ sealed class SiteNavigationAction {
4242
data class OpenUnifiedComments(val site: SiteModel) : SiteNavigationAction()
4343
object StartWPComLoginForJetpackStats : SiteNavigationAction()
4444
data class OpenStats(val site: SiteModel) : SiteNavigationAction()
45+
object OpenNewStats : SiteNavigationAction()
4546
data class ConnectJetpackForStats(val site: SiteModel) : SiteNavigationAction()
4647
data class OpenDomainRegistration(val site: SiteModel) : SiteNavigationAction()
4748
data class OpenPaidDomainSearch(val site: SiteModel) : SiteNavigationAction()

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ import org.wordpress.android.ui.blaze.blazecampaigns.campaignlisting.CampaignLis
88
import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper
99
import org.wordpress.android.ui.mysite.SiteNavigationAction
1010
import org.wordpress.android.ui.mysite.items.listitem.ListItemAction
11+
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures
12+
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures.Feature
1113
import javax.inject.Inject
1214

1315
class ListItemActionHandler @Inject constructor(
1416
private val accountStore: AccountStore,
1517
private val jetpackFeatureRemovalPhaseHelper: JetpackFeatureRemovalPhaseHelper,
16-
private val blazeFeatureUtils: BlazeFeatureUtils
18+
private val blazeFeatureUtils: BlazeFeatureUtils,
19+
private val experimentalFeatures: ExperimentalFeatures
1720
) {
1821
fun handleAction(
1922
action: ListItemAction,
@@ -56,7 +59,13 @@ class ListItemActionHandler @Inject constructor(
5659
!accountStore.hasAccessToken() && site.isJetpackConnected -> SiteNavigationAction.StartWPComLoginForJetpackStats
5760

5861
// If it's a WordPress.com or Jetpack site, show the Stats screen.
59-
site.isWPCom || site.isJetpackInstalled && site.isJetpackConnected -> SiteNavigationAction.OpenStats(site)
62+
site.isWPCom || site.isJetpackInstalled && site.isJetpackConnected -> {
63+
if (experimentalFeatures.isEnabled(Feature.NEW_STATS)) {
64+
SiteNavigationAction.OpenNewStats
65+
} else {
66+
SiteNavigationAction.OpenStats(site)
67+
}
68+
}
6069

6170
// If it's a self-hosted site, ask to connect to Jetpack.
6271
else -> SiteNavigationAction.ConnectJetpackForStats(site)

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import org.wordpress.android.ui.compose.utils.uiStringText
5454
import org.wordpress.android.ui.main.BaseAppCompatActivity
5555
import org.wordpress.android.ui.mysite.SiteNavigationAction
5656
import org.wordpress.android.ui.mysite.items.listitem.ListItemAction
57+
import org.wordpress.android.ui.newstats.NewStatsActivity
5758
import org.wordpress.android.ui.pages.SnackbarMessageHolder
5859
import org.wordpress.android.ui.prefs.SiteSettingsFragment
5960
import org.wordpress.android.ui.stats.refresh.utils.StatsLaunchedFrom
@@ -133,6 +134,8 @@ class MenuActivity : BaseAppCompatActivity() {
133134
StatsLaunchedFrom.ROW
134135
)
135136

137+
is SiteNavigationAction.OpenNewStats -> NewStatsActivity.start(this)
138+
136139
is SiteNavigationAction.OpenDomains -> ActivityLauncher.viewDomainsDashboardActivity(
137140
this,
138141
action.site
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package org.wordpress.android.ui.newstats
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import androidx.activity.compose.setContent
7+
import androidx.compose.foundation.layout.Box
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.fillMaxSize
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.pager.HorizontalPager
12+
import androidx.compose.foundation.pager.rememberPagerState
13+
import androidx.compose.material.icons.Icons
14+
import androidx.compose.material.icons.automirrored.filled.ArrowBack
15+
import androidx.compose.material3.ExperimentalMaterial3Api
16+
import androidx.compose.material3.Icon
17+
import androidx.compose.material3.IconButton
18+
import androidx.compose.material3.Scaffold
19+
import androidx.compose.material3.PrimaryTabRow
20+
import androidx.compose.material3.Tab
21+
import androidx.compose.material3.Text
22+
import androidx.compose.material3.TopAppBar
23+
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.rememberCoroutineScope
25+
import androidx.compose.ui.Alignment
26+
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.res.stringResource
28+
import androidx.compose.ui.tooling.preview.Preview
29+
import dagger.hilt.android.AndroidEntryPoint
30+
import kotlinx.coroutines.launch
31+
import org.wordpress.android.R
32+
import org.wordpress.android.ui.compose.theme.AppThemeM3
33+
import org.wordpress.android.ui.main.BaseAppCompatActivity
34+
35+
@AndroidEntryPoint
36+
class NewStatsActivity : BaseAppCompatActivity() {
37+
override fun onCreate(savedInstanceState: Bundle?) {
38+
super.onCreate(savedInstanceState)
39+
setContent {
40+
AppThemeM3 {
41+
NewStatsScreen(
42+
onBackPressed = onBackPressedDispatcher::onBackPressed
43+
)
44+
}
45+
}
46+
}
47+
48+
companion object {
49+
fun start(context: Context) {
50+
context.startActivity(Intent(context, NewStatsActivity::class.java))
51+
}
52+
}
53+
}
54+
55+
private enum class StatsTab(val titleResId: Int) {
56+
TRAFFIC(R.string.stats_traffic),
57+
INSIGHTS(R.string.stats_insights),
58+
SUBSCRIBERS(R.string.subscribers)
59+
}
60+
61+
@OptIn(ExperimentalMaterial3Api::class)
62+
@Composable
63+
private fun NewStatsScreen(
64+
onBackPressed: () -> Unit
65+
) {
66+
val tabs = StatsTab.entries
67+
val pagerState = rememberPagerState(pageCount = { tabs.size })
68+
val coroutineScope = rememberCoroutineScope()
69+
70+
Scaffold(
71+
topBar = {
72+
TopAppBar(
73+
title = {
74+
Text(text = stringResource(id = R.string.stats))
75+
},
76+
navigationIcon = {
77+
IconButton(onClick = onBackPressed) {
78+
Icon(
79+
Icons.AutoMirrored.Filled.ArrowBack,
80+
contentDescription = stringResource(R.string.back)
81+
)
82+
}
83+
}
84+
)
85+
}
86+
) { contentPadding ->
87+
Column(
88+
modifier = Modifier
89+
.fillMaxSize()
90+
.padding(contentPadding)
91+
) {
92+
PrimaryTabRow(selectedTabIndex = pagerState.currentPage) {
93+
tabs.forEachIndexed { index, tab ->
94+
Tab(
95+
selected = pagerState.currentPage == index,
96+
onClick = {
97+
coroutineScope.launch {
98+
pagerState.animateScrollToPage(index)
99+
}
100+
},
101+
text = { Text(text = stringResource(id = tab.titleResId)) }
102+
)
103+
}
104+
}
105+
106+
HorizontalPager(
107+
state = pagerState,
108+
modifier = Modifier.fillMaxSize()
109+
) { page ->
110+
StatsTabContent(tab = tabs[page])
111+
}
112+
}
113+
}
114+
}
115+
116+
@Composable
117+
private fun StatsTabContent(tab: StatsTab) {
118+
Box(
119+
modifier = Modifier.fillMaxSize(),
120+
contentAlignment = Alignment.Center
121+
) {
122+
Text(text = "${stringResource(id = tab.titleResId)} - Coming Soon")
123+
}
124+
}
125+
126+
@Preview
127+
@Composable
128+
fun NewStatsScreenPreview() {
129+
AppThemeM3 {
130+
NewStatsScreen(onBackPressed = {})
131+
}
132+
}

WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeatures.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ class ExperimentalFeatures @Inject constructor(
4949
"experimental_post_types",
5050
R.string.experimental_post_types,
5151
R.string.experimental_post_types_description
52+
),
53+
NEW_STATS(
54+
"new_stats",
55+
R.string.experimental_new_stats,
56+
R.string.experimental_new_stats_description
5257
);
5358
}
5459
}

WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeaturesViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ internal class ExperimentalFeaturesViewModel @Inject constructor(
5151
}
5252

5353
private fun shouldShowFeature(feature: Feature): Boolean {
54-
// Only show Post Types feature in debug builds
55-
return if (feature == Feature.EXPERIMENTAL_POST_TYPES) {
54+
// Only show Post Types and New Stats features in debug builds
55+
return if (feature == Feature.EXPERIMENTAL_POST_TYPES || feature == Feature.NEW_STATS) {
5656
BuildConfig.DEBUG
5757
} else if (gutenbergKitFeature.isEnabled()) {
5858
feature != Feature.EXPERIMENTAL_BLOCK_EDITOR

WordPress/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,8 @@
994994
<string name="network_requests_disable">Disable</string>
995995
<string name="experimental_post_types">Post Types</string>
996996
<string name="experimental_post_types_description">Enable experimental post types screen</string>
997+
<string name="experimental_new_stats">New Stats</string>
998+
<string name="experimental_new_stats_description">Show stats in a more modern and advanced UI</string>
997999
<string name="post_types_screen_title">Post Types</string>
9981000

9991001
<!-- Debug settings -->

WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/ListItemActionHandlerTest.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.wordpress.android.ui.blaze.blazecampaigns.campaignlisting.CampaignLis
1616
import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper
1717
import org.wordpress.android.ui.mysite.SiteNavigationAction
1818
import org.wordpress.android.ui.mysite.items.listitem.ListItemAction
19+
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures
1920
import kotlin.test.assertEquals
2021

2122
@ExperimentalCoroutinesApi
@@ -30,6 +31,8 @@ class ListItemActionHandlerTest: BaseUnitTest() {
3031
@Mock
3132
lateinit var blazeFeatureUtils: BlazeFeatureUtils
3233

34+
@Mock
35+
lateinit var experimentalFeatures: ExperimentalFeatures
3336

3437
private val site = SiteModel()
3538

@@ -40,7 +43,8 @@ class ListItemActionHandlerTest: BaseUnitTest() {
4043
listItemActionHandler = ListItemActionHandler(
4144
accountStore,
4245
jetpackFeatureRemovalPhaseHelper,
43-
blazeFeatureUtils
46+
blazeFeatureUtils,
47+
experimentalFeatures
4448
)
4549
}
4650

@@ -164,6 +168,27 @@ class ListItemActionHandlerTest: BaseUnitTest() {
164168
assertEquals(navigationAction, SiteNavigationAction.StartWPComLoginForJetpackStats)
165169
}
166170

171+
@Test
172+
fun `stats item click emits OpenNewStats when NEW_STATS feature is enabled and site is WPCom`() {
173+
site.setIsWPCom(true)
174+
whenever(experimentalFeatures.isEnabled(ExperimentalFeatures.Feature.NEW_STATS)).thenReturn(true)
175+
176+
val navigationAction = invokeItemClickAction(action = ListItemAction.STATS)
177+
178+
assertEquals(SiteNavigationAction.OpenNewStats, navigationAction)
179+
}
180+
181+
@Test
182+
fun `stats item click emits OpenNewStats when NEW_STATS feature is enabled and site is Jetpack`() {
183+
site.setIsJetpackConnected(true)
184+
site.setIsWPCom(true)
185+
whenever(accountStore.hasAccessToken()).thenReturn(true)
186+
whenever(experimentalFeatures.isEnabled(ExperimentalFeatures.Feature.NEW_STATS)).thenReturn(true)
187+
188+
val navigationAction = invokeItemClickAction(action = ListItemAction.STATS)
189+
190+
assertEquals(SiteNavigationAction.OpenNewStats, navigationAction)
191+
}
167192

168193
@Test
169194
fun `given campaigns enabled, when menu clicked, then navigated to campaign listing page`() {

0 commit comments

Comments
 (0)