Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions WordPress/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@
android:theme="@style/WordPress.NoActionBar"
android:exported="false" />

<activity
android:name=".ui.newstats.poststats.PostStatsDetailActivity"
android:theme="@style/WordPress.NoActionBar"
android:exported="false" />

<activity
android:name=".ui.newstats.subscribers.subscriberslist.SubscribersListDetailActivity"
android:theme="@style/WordPress.NoActionBar"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@ import org.wordpress.android.ui.domains.management.purchasedomain.PurchaseDomain
import org.wordpress.android.ui.main.WPMainActivity
import org.wordpress.android.ui.media.MediaBrowserActivity
import org.wordpress.android.ui.media.MediaBrowserType
import org.wordpress.android.ui.mysite.SelectedSiteRepository
import org.wordpress.android.ui.mysite.menu.MenuActivity
import org.wordpress.android.ui.mysite.personalization.PersonalizationActivity
import org.wordpress.android.ui.newstats.NewStatsActivity
import org.wordpress.android.ui.newstats.NewStatsRouting
import org.wordpress.android.ui.newstats.toNewStatsTarget
import org.wordpress.android.ui.stats.StatsConstants
import org.wordpress.android.ui.stats.StatsTimeframe
import org.wordpress.android.ui.stats.refresh.lists.detail.StatsDetailActivity
import org.wordpress.android.ui.stats.refresh.utils.StatsLaunchedFrom
import org.wordpress.android.ui.sitemonitor.SiteMonitorParentActivity
import org.wordpress.android.ui.sitemonitor.SiteMonitorType
Expand All @@ -50,7 +47,6 @@ import javax.inject.Singleton

@Singleton
class ActivityNavigator @Inject constructor(
private val selectedSiteRepository: SelectedSiteRepository,
private val newStatsRouting: NewStatsRouting
) {
/**
Expand Down Expand Up @@ -317,26 +313,6 @@ class ActivityNavigator @Inject constructor(
.build()
}

/**
* Opens the per-post stats detail screen for a Posts & Pages item tapped in the new stats.
* Shows an error toast when no site is selected.
*/
fun openPostDetailStats(context: Context, postId: Long, postType: String?, postTitle: String, postUrl: String?) {
val site = selectedSiteRepository.getSelectedSite() ?: run {
ToastUtils.showToast(context, R.string.blog_not_found, ToastUtils.Duration.SHORT)
return
}
AnalyticsTracker.track(AnalyticsTracker.Stat.STATS_POSTS_AND_PAGES_ITEM_TAPPED)
StatsDetailActivity.start(
context = context,
site = site,
postId = postId,
postType = statsDetailItemType(postType),
postTitle = postTitle,
postUrl = postUrl
)
}

fun navigateToApplicationPasswordReauthentication(activity: Activity, authenticationUrl: String) {
val intent = Intent(activity, ApplicationPasswordReauthenticateDialogActivity::class.java)
intent.putExtra(ApplicationPasswordDialogActivity.EXTRA_SITE_URL, authenticationUrl)
Expand All @@ -349,12 +325,3 @@ class ActivityNavigator @Inject constructor(
)
}
}

/**
* Maps the API post type of a Posts & Pages item to the item type expected by the post detail
* stats screen, mirroring the old stats mapping (pages and the homepage share one detail layout).
*/
internal fun statsDetailItemType(postType: String?): String = when (postType) {
StatsConstants.ITEM_TYPE_POST, StatsConstants.ITEM_TYPE_ATTACHMENT -> postType
else -> StatsConstants.ITEM_TYPE_HOME_PAGE
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ enum class InsightsCardType(
ALL_TIME_STATS(
R.string.stats_insights_all_time_stats_title
),
LATEST_POST(
R.string.stats_insights_latest_post_summary
),
MOST_POPULAR_DAY(
R.string.stats_insights_most_popular_day
),
Expand All @@ -27,6 +30,7 @@ enum class InsightsCardType(
listOf(
YEAR_IN_REVIEW,
ALL_TIME_STATS,
LATEST_POST,
MOST_POPULAR_DAY,
MOST_POPULAR_TIME,
TAGS_AND_CATEGORIES
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,11 @@ class InsightsViewModel @Inject constructor(
}

companion object {
// TAGS_AND_CATEGORIES is intentionally absent
// from both checks: it has its own dedicated
// fetch path via StatsTagsUseCase in
// TagsAndCategoriesViewModel.
// TAGS_AND_CATEGORIES and LATEST_POST are
// intentionally absent from both checks: each
// fetches on its own, from
// TagsAndCategoriesViewModel and
// LatestPostViewModel respectively.
private fun List<InsightsCardType>.needsSummary():
Boolean = any {
it == InsightsCardType.ALL_TIME_STATS ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import org.wordpress.android.R
import org.wordpress.android.WordPress
import org.wordpress.android.fluxc.store.SiteStore
import org.wordpress.android.ui.ActivityLauncher
import org.wordpress.android.ui.PagePostCreationSourcesDetail
import org.wordpress.android.ui.ActivityNavigator
import org.wordpress.android.ui.compose.components.FeedbackDialog
import org.wordpress.android.ui.compose.theme.AppThemeM3
Expand Down Expand Up @@ -114,8 +115,11 @@ import org.wordpress.android.ui.newstats.subscribers.SubscribersTabContent
import android.widget.Toast
import org.wordpress.android.ui.newstats.alltimestats.AllTimeStatsCard
import org.wordpress.android.ui.newstats.alltimestats.AllTimeStatsViewModel
import org.wordpress.android.ui.newstats.latestpost.LatestPostCard
import org.wordpress.android.ui.newstats.latestpost.LatestPostViewModel
import org.wordpress.android.ui.newstats.mostpopularday.MostPopularDayCard
import org.wordpress.android.ui.newstats.mostpopularday.MostPopularDayViewModel
import org.wordpress.android.ui.newstats.poststats.PostStatsDetailActivity
import org.wordpress.android.ui.newstats.mostpopulartime.MostPopularTimeCard
import org.wordpress.android.ui.newstats.mostpopulartime.MostPopularTimeViewModel
import org.wordpress.android.ui.newstats.yearinreview.YearInReviewCard
Expand Down Expand Up @@ -213,7 +217,8 @@ class NewStatsActivity : BaseAppCompatActivity() {
onStatsUrlClick = { url ->
activityNavigator.openInCustomTab(this, url)
},
onPostItemClick = ::openPostDetailStats
onPostItemClick = ::openPostDetailStats,
onCreatePostClick = ::createNewPost
)
}
}
Expand Down Expand Up @@ -243,7 +248,21 @@ class NewStatsActivity : BaseAppCompatActivity() {
}

private fun openPostDetailStats(item: MostViewedItem) {
activityNavigator.openPostDetailStats(this, item.id, item.postType, item.title, item.url)
analyticsTracker.track(Stat.STATS_POSTS_AND_PAGES_ITEM_TAPPED)
PostStatsDetailActivity.start(this, item.id, item.title)
}

private fun createNewPost() {
selectedSiteRepository.getSelectedSite()?.let { site ->
ActivityLauncher.addNewPostForResult(
this,
site,
false,
PagePostCreationSourcesDetail.POST_FROM_STATS,
-1,
null
)
}
}

/**
Expand Down Expand Up @@ -365,7 +384,8 @@ private fun NewStatsScreen(
showIntroBottomSheet: Boolean = false,
onIntroDismissed: () -> Unit = {},
onStatsUrlClick: (String) -> Unit = {},
onPostItemClick: (MostViewedItem) -> Unit = {}
onPostItemClick: (MostViewedItem) -> Unit = {},
onCreatePostClick: () -> Unit = {}
) {
val viewsStatsViewModel: ViewsStatsViewModel = viewModel()
val selectedPeriod by viewsStatsViewModel.selectedPeriod.collectAsState()
Expand Down Expand Up @@ -547,7 +567,8 @@ private fun NewStatsScreen(
tab = tabs[page],
viewsStatsViewModel = viewsStatsViewModel,
onStatsUrlClick = onStatsUrlClick,
onPostItemClick = onPostItemClick
onPostItemClick = onPostItemClick,
onCreatePostClick = onCreatePostClick
)
}
}
Expand All @@ -559,7 +580,8 @@ private fun StatsTabContent(
tab: StatsTab,
viewsStatsViewModel: ViewsStatsViewModel,
onStatsUrlClick: (String) -> Unit = {},
onPostItemClick: (MostViewedItem) -> Unit = {}
onPostItemClick: (MostViewedItem) -> Unit = {},
onCreatePostClick: () -> Unit = {}
) {
when (tab) {
StatsTab.TRAFFIC -> TrafficTabContent(
Expand All @@ -568,7 +590,8 @@ private fun StatsTabContent(
onPostItemClick = onPostItemClick
)
StatsTab.INSIGHTS -> InsightsTabContent(
onStatsUrlClick = onStatsUrlClick
onStatsUrlClick = onStatsUrlClick,
onCreatePostClick = onCreatePostClick
)
StatsTab.SUBSCRIBERS -> SubscribersTabContent()
}
Expand Down Expand Up @@ -1220,15 +1243,18 @@ private fun InsightsTabContent(
mostPopularDayViewModel: MostPopularDayViewModel = viewModel(),
mostPopularTimeViewModel: MostPopularTimeViewModel = viewModel(),
tagsAndCategoriesViewModel: TagsAndCategoriesViewModel = viewModel(),
latestPostViewModel: LatestPostViewModel = viewModel(),
insightsViewModel: InsightsViewModel = viewModel(),
onStatsUrlClick: (String) -> Unit = {}
onStatsUrlClick: (String) -> Unit = {},
onCreatePostClick: () -> Unit = {}
) {
val context = LocalContext.current
val yearInReviewUiState by yearInReviewViewModel.uiState.collectAsState()
val allTimeStatsUiState by allTimeStatsViewModel.uiState.collectAsState()
val mostPopularDayUiState by mostPopularDayViewModel.uiState.collectAsState()
val mostPopularTimeUiState by mostPopularTimeViewModel.uiState.collectAsState()
val tagsAndCategoriesUiState by tagsAndCategoriesViewModel.uiState.collectAsState()
val latestPostUiState by latestPostViewModel.uiState.collectAsState()
val isRefreshing by insightsViewModel.isDataRefreshing.collectAsState()
val pullToRefreshState = rememberPullToRefreshState()

Expand All @@ -1244,6 +1270,9 @@ private fun InsightsTabContent(
if (InsightsCardType.TAGS_AND_CATEGORIES in cardsToLoad) {
tagsAndCategoriesViewModel.loadData()
}
if (InsightsCardType.LATEST_POST in cardsToLoad) {
latestPostViewModel.loadData()
}
}

val onRetryData = remember { { insightsViewModel.fetchData() } }
Expand Down Expand Up @@ -1317,6 +1346,11 @@ private fun InsightsTabContent(
) {
tagsAndCategoriesViewModel.refresh()
}
if (InsightsCardType.LATEST_POST
in visibleCards
) {
latestPostViewModel.refresh()
}
},
indicator = {
PullToRefreshDefaults.Indicator(
Expand Down Expand Up @@ -1368,6 +1402,24 @@ private fun InsightsTabContent(
onMoveDown = { insightsViewModel.moveCardDown(cardType) },
onMoveToBottom = { insightsViewModel.moveCardToBottom(cardType) }
)
InsightsCardType.LATEST_POST -> LatestPostCard(
uiState = latestPostUiState,
onRemoveCard = { insightsViewModel.removeCard(cardType) },
onRetry = { latestPostViewModel.refresh() },
onPostClick = { postId, title ->
PostStatsDetailActivity.start(
context,
postId,
title
)
},
onCreatePostClick = onCreatePostClick,
cardPosition = pos,
onMoveUp = { insightsViewModel.moveCardUp(cardType) },
onMoveToTop = { insightsViewModel.moveCardToTop(cardType) },
onMoveDown = { insightsViewModel.moveCardDown(cardType) },
onMoveToBottom = { insightsViewModel.moveCardToBottom(cardType) }
)
InsightsCardType.MOST_POPULAR_DAY -> MostPopularDayCard(
uiState = mostPopularDayUiState,
onRemoveCard = { insightsViewModel.removeCard(cardType) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ import androidx.compose.ui.graphics.Color
object StatsColors {
val ChangeBadgePositive = Color(0xFF2E7D32)
val ChangeBadgeNegative = Color(0xFFE91E63)

/**
* The selected bar in a day-views chart. Shares the negative badge's hue, but it marks the
* user's selection, not a decline -- keep them separate so either can move independently.
*/
val ChartSelectedBar = Color(0xFFE91E63)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.wordpress.android.ui.newstats.components

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

// Zero-value entries still get a sliver of a bar so the series reads as continuous.
private const val MIN_BAR_FRACTION = 0.02f

/**
* A compact bar chart of a single series, scaled against its own largest value. Renders nothing
* when every value is zero.
*/
@Composable
fun StatsBarChart(
values: List<Long>,
height: Dp,
barSpacing: Dp,
modifier: Modifier = Modifier
) {
val maxValue = values.maxOrNull() ?: 0L
if (maxValue <= 0L) return

val barColor = MaterialTheme.colorScheme.primary

Row(
modifier = modifier
.fillMaxWidth()
.height(height),
horizontalArrangement = Arrangement.spacedBy(barSpacing),
verticalAlignment = Alignment.Bottom
) {
values.forEach { value ->
Box(
modifier = Modifier
.weight(1f)
.fillMaxSize(),
contentAlignment = Alignment.BottomCenter
) {
val fraction = (
value.toFloat() / maxValue.toFloat()
).coerceIn(MIN_BAR_FRACTION, 1f)
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(fraction)
.clip(
RoundedCornerShape(
topStart = 2.dp,
topEnd = 2.dp
)
)
.background(barColor)
)
}
}
}
}
Loading
Loading