diff --git a/app/src/main/java/org/wikipedia/activitytab/ActivityTabFragment.kt b/app/src/main/java/org/wikipedia/activitytab/ActivityTabFragment.kt index 3ee5cadebe7..0e4b5fc98fd 100644 --- a/app/src/main/java/org/wikipedia/activitytab/ActivityTabFragment.kt +++ b/app/src/main/java/org/wikipedia/activitytab/ActivityTabFragment.kt @@ -9,9 +9,11 @@ import android.view.View import android.view.ViewGroup import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -161,9 +163,11 @@ class ActivityTabFragment : Fragment() { ActivityTabScreen( isLoggedIn = AccountUtil.isLoggedIn && !AccountUtil.isTemporaryAccount, userName = AccountUtil.userName, + languageCode = WikipediaApp.instance.wikiSite.languageCode, modules = Prefs.activityTabModules, haveAtLeastOneDonation = Prefs.donationResults.isNotEmpty(), areGamesAvailable = OnThisDayGameViewModel.isLangSupported(WikipediaApp.instance.wikiSite.languageCode), + refreshSilently = viewModel.shouldRefreshTimelineSilently, readingHistoryState = viewModel.readingHistoryState.collectAsState().value, donationUiState = viewModel.donationUiState.collectAsState().value, wikiGamesUiState = viewModel.wikiGamesUiState.collectAsState().value, @@ -192,13 +196,15 @@ class ActivityTabFragment : Fragment() { fun ActivityTabScreen( isLoggedIn: Boolean, userName: String, + languageCode: String, modules: ActivityTabModules, haveAtLeastOneDonation: Boolean, areGamesAvailable: Boolean, + refreshSilently: Boolean, readingHistoryState: UiState, donationUiState: UiState, wikiGamesUiState: UiState, - impactUiState: UiState, + impactUiState: UiState>, timelineFlow: Flow> ) { val timelineItems = timelineFlow.collectAsLazyPagingItems() @@ -414,13 +420,34 @@ class ActivityTabFragment : Fragment() { ) ) { if (modules.isModuleVisible(ModuleType.EDITING_INSIGHTS) || modules.isModuleVisible(ModuleType.IMPACT)) { - Text( - modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 24.dp), - text = stringResource(R.string.activity_tab_impact), - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Medium, - color = WikipediaTheme.colors.primaryColor - ) + Row( + modifier = Modifier.fillMaxWidth(), + ) { + Text( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, top = 24.dp) + .weight(1f), + text = stringResource(R.string.activity_tab_impact), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Medium, + color = WikipediaTheme.colors.primaryColor + ) + Box( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 24.dp) + .background(color = WikipediaTheme.colors.paperColor).border( + 0.5.dp, + WikipediaTheme.colors.borderColor, + RoundedCornerShape(4.dp) + ) + ) { + Text( + modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), + text = languageCode.uppercase(), + style = MaterialTheme.typography.bodyLarge, + color = WikipediaTheme.colors.primaryColor + ) + } + } } if (modules.isModuleVisible(ModuleType.EDITING_INSIGHTS)) { @@ -469,6 +496,9 @@ class ActivityTabFragment : Fragment() { .fillMaxWidth() .padding(start = 16.dp, end = 16.dp, top = 16.dp), uiState = impactUiState, + onTotalEditsClick = { + startActivity(UserContribListActivity.newIntent(requireContext(), userName)) + }, wikiErrorClickEvents = WikiErrorClickEvents( retryClickListener = { viewModel.loadImpact() @@ -548,7 +578,7 @@ class ActivityTabFragment : Fragment() { val isEmpty = timelineItems.itemCount == 0 when { // Show loading for fresh navigation or explicit refresh, User came from tab navigation OR pulled to refresh - isRefreshing && !viewModel.shouldRefreshTimelineSilently -> { + isRefreshing && !refreshSilently -> { item { ActivityTabShimmerView() } @@ -557,7 +587,7 @@ class ActivityTabFragment : Fragment() { // Show loading UI during silent refresh transition // User clicked timeline item (shouldRefreshTimelineSilently = true) and returned, // but timeline data is still loading/empty. Without this case, user would see empty state briefly before data loads instead of loading UI. - isEmpty && viewModel.shouldRefreshTimelineSilently -> { + isEmpty && refreshSilently -> { item { ActivityTabShimmerView() } @@ -630,9 +660,11 @@ class ActivityTabFragment : Fragment() { ActivityTabScreen( isLoggedIn = true, userName = "User", + languageCode = "en", modules = ActivityTabModules(isDonationsEnabled = true), haveAtLeastOneDonation = true, areGamesAvailable = true, + refreshSilently = false, readingHistoryState = UiState.Success(ActivityTabViewModel.ReadingHistory( timeSpentThisWeek = 12345, articlesReadThisMonth = 123, @@ -659,7 +691,7 @@ class ActivityTabFragment : Fragment() { currentStreak = 15, bestStreak = 25 )), - impactUiState = UiState.Success(GrowthUserImpact(totalEditsCount = 12345)), + impactUiState = UiState.Success(Pair(GrowthUserImpact(totalEditsCount = 12345), 123456)), timelineFlow = emptyFlow() ) } @@ -672,9 +704,11 @@ class ActivityTabFragment : Fragment() { ActivityTabScreen( isLoggedIn = true, userName = "User", + languageCode = "ru", modules = ActivityTabModules(isDonationsEnabled = true), haveAtLeastOneDonation = false, areGamesAvailable = false, + refreshSilently = false, readingHistoryState = UiState.Success(ActivityTabViewModel.ReadingHistory( timeSpentThisWeek = 0, articlesReadThisMonth = 0, @@ -687,7 +721,7 @@ class ActivityTabFragment : Fragment() { )), donationUiState = UiState.Success("Unknown"), wikiGamesUiState = UiState.Success(null), - impactUiState = UiState.Success(GrowthUserImpact()), + impactUiState = UiState.Success(Pair(GrowthUserImpact(), 0)), timelineFlow = emptyFlow() ) } @@ -700,9 +734,11 @@ class ActivityTabFragment : Fragment() { ActivityTabScreen( isLoggedIn = false, userName = "User", + languageCode = "he", modules = ActivityTabModules(), haveAtLeastOneDonation = false, areGamesAvailable = false, + refreshSilently = false, readingHistoryState = UiState.Success(ActivityTabViewModel.ReadingHistory( timeSpentThisWeek = 0, articlesReadThisMonth = 0, @@ -715,7 +751,7 @@ class ActivityTabFragment : Fragment() { )), donationUiState = UiState.Success("Unknown"), wikiGamesUiState = UiState.Success(null), - impactUiState = UiState.Success(GrowthUserImpact()), + impactUiState = UiState.Success(Pair(GrowthUserImpact(), 0)), timelineFlow = emptyFlow() ) } @@ -728,6 +764,7 @@ class ActivityTabFragment : Fragment() { ActivityTabScreen( isLoggedIn = true, userName = "User", + languageCode = "zh", modules = ActivityTabModules( isTimeSpentEnabled = false, isReadingInsightsEnabled = false, @@ -739,6 +776,7 @@ class ActivityTabFragment : Fragment() { ), haveAtLeastOneDonation = true, areGamesAvailable = true, + refreshSilently = false, readingHistoryState = UiState.Success(ActivityTabViewModel.ReadingHistory( timeSpentThisWeek = 0, articlesReadThisMonth = 0, @@ -751,8 +789,8 @@ class ActivityTabFragment : Fragment() { )), donationUiState = UiState.Success("Unknown"), wikiGamesUiState = UiState.Success(null), - impactUiState = UiState.Success(GrowthUserImpact()), - timelineFlow = emptyFlow() + impactUiState = UiState.Success(Pair(GrowthUserImpact(), 0)), + timelineFlow = emptyFlow() ) } } diff --git a/app/src/main/java/org/wikipedia/activitytab/ActivityTabViewModel.kt b/app/src/main/java/org/wikipedia/activitytab/ActivityTabViewModel.kt index 04153842876..a5886508900 100644 --- a/app/src/main/java/org/wikipedia/activitytab/ActivityTabViewModel.kt +++ b/app/src/main/java/org/wikipedia/activitytab/ActivityTabViewModel.kt @@ -95,8 +95,8 @@ class ActivityTabViewModel() : ViewModel() { } } - private val _impactUiState = MutableStateFlow>(UiState.Loading) - val impactUiState: StateFlow> = _impactUiState.asStateFlow() + private val _impactUiState = MutableStateFlow>>(UiState.Loading) + val impactUiState: StateFlow>> = _impactUiState.asStateFlow() var shouldRefreshTimelineSilently: Boolean = false @@ -225,10 +225,9 @@ class ActivityTabViewModel() : ViewModel() { impact = JsonUtil.decodeFromString(impactResponse)!! } - val pagesResponse = ServiceFactory.get(wikiSite).getInfoByPageIdsOrTitles( + val pagesResponse = ServiceFactory.get(wikiSite).getInfoByTitlesWithGlobalUserInfo( titles = impact.topViewedArticles.keys.joinToString(separator = "|") ) - // Transform the response to a map of PageTitle to ArticleViews val pageMap = pagesResponse.query?.pages?.associate { page -> val pageTitle = PageTitle( @@ -243,7 +242,7 @@ class ActivityTabViewModel() : ViewModel() { impact.topViewedArticlesWithPageTitle = pageMap - _impactUiState.value = UiState.Success(impact) + _impactUiState.value = UiState.Success(Pair(impact, (pagesResponse.query?.globalUserInfo?.editCount ?: 0))) } } @@ -260,7 +259,7 @@ class ActivityTabViewModel() : ViewModel() { fun getTotalEditsCount(): Int { return when (val currentState = _impactUiState.value) { - is UiState.Success -> currentState.data.totalEditsCount + is UiState.Success -> currentState.data.first.totalEditsCount else -> 0 } } @@ -285,7 +284,7 @@ class ActivityTabViewModel() : ViewModel() { fun hasNoImpactData(): Boolean { return when (val currentState = _impactUiState.value) { is UiState.Success -> { - val data = currentState.data + val data = currentState.data.first data.totalEditsCount <= 0 && data.receivedThanksCount <= 0 && data.totalPageviewsCount <= 0 } else -> true diff --git a/app/src/main/java/org/wikipedia/activitytab/EditingInsightsModule.kt b/app/src/main/java/org/wikipedia/activitytab/EditingInsightsModule.kt index 4f24d815e50..01214b1fb3a 100644 --- a/app/src/main/java/org/wikipedia/activitytab/EditingInsightsModule.kt +++ b/app/src/main/java/org/wikipedia/activitytab/EditingInsightsModule.kt @@ -64,7 +64,7 @@ import java.util.Locale @Composable fun EditingInsightsModule( modifier: Modifier = Modifier, - uiState: UiState, + uiState: UiState>, onPageItemClick: (PageTitle) -> Unit, onContributionClick: (() -> Unit), onSuggestedEditsClick: (() -> Unit), @@ -78,10 +78,11 @@ fun EditingInsightsModule( } } is UiState.Success -> { + val impact = uiState.data.first MostViewedCard( modifier = modifier .fillMaxWidth(), - data = uiState.data.topViewedArticlesWithPageTitle, + data = impact.topViewedArticlesWithPageTitle, onClick = { onPageItemClick(it) } @@ -89,9 +90,9 @@ fun EditingInsightsModule( ContributionCard( modifier = modifier .fillMaxWidth(), - lastEditRelativeTime = uiState.data.lastEditRelativeTime, - editsThisMonth = uiState.data.editsThisMonth, - editsLastMonth = uiState.data.editsLastMonth, + lastEditRelativeTime = impact.lastEditRelativeTime, + editsThisMonth = impact.editsThisMonth, + editsLastMonth = impact.editsLastMonth, onContributionClick = { onContributionClick() }, diff --git a/app/src/main/java/org/wikipedia/activitytab/ImpactModule.kt b/app/src/main/java/org/wikipedia/activitytab/ImpactModule.kt index 6df794119aa..286cc8186f1 100644 --- a/app/src/main/java/org/wikipedia/activitytab/ImpactModule.kt +++ b/app/src/main/java/org/wikipedia/activitytab/ImpactModule.kt @@ -46,7 +46,8 @@ import java.util.Locale @Composable fun ImpactModule( modifier: Modifier = Modifier, - uiState: UiState, + uiState: UiState>, + onTotalEditsClick: () -> Unit = {}, wikiErrorClickEvents: WikiErrorClickEvents? = null ) { when (uiState) { @@ -54,16 +55,24 @@ fun ImpactModule( ActivityTabShimmerView(size = 340.dp) } is UiState.Success -> { + val impact = uiState.data.first AllTimeImpactCard( - modifier = modifier - .fillMaxWidth(), - totalEdits = uiState.data.totalEditsCount, - totalThanks = uiState.data.receivedThanksCount, - longestEditingStreak = uiState.data.longestEditingStreak?.totalEditCountForPeriod ?: 0, - lastEditTimestamp = uiState.data.lastEditTimestamp, - lastThirtyDaysEdits = uiState.data.lastThirtyDaysEdits, - totalPageviewsCount = uiState.data.totalPageviewsCount, - dailyTotalViews = uiState.data.dailyTotalViews + modifier = modifier.fillMaxWidth(), + totalEdits = impact.totalEditsCount, + totalThanks = impact.receivedThanksCount, + longestEditingStreak = impact.longestEditingStreak?.totalEditCountForPeriod ?: 0, + lastEditTimestamp = impact.lastEditTimestamp, + lastThirtyDaysEdits = impact.lastThirtyDaysEdits, + totalPageviewsCount = impact.totalPageviewsCount, + dailyTotalViews = impact.dailyTotalViews + ) + val totalEdits = uiState.data.second + TotalEditsCard( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, top = 16.dp), + totalEdits = totalEdits, + onClickListener = onTotalEditsClick, ) } @@ -300,6 +309,60 @@ fun AllTimeImpactCard( } } +@Composable +fun TotalEditsCard( + modifier: Modifier = Modifier, + totalEdits: Int, + onClickListener: () -> Unit = {} +) { + val formatter = remember { NumberFormat.getNumberInstance(Locale.getDefault()) } + WikiCard( + modifier = modifier, + elevation = 0.dp, + border = BorderStroke(width = 1.dp, color = WikipediaTheme.colors.borderColor), + onClick = onClickListener + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + ) { + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(R.drawable.ic_public_24), + tint = WikipediaTheme.colors.primaryColor, + contentDescription = null + ) + Text( + text = stringResource(R.string.activity_tab_total_edits_all_projects), + style = MaterialTheme.typography.labelMedium, + color = WikipediaTheme.colors.primaryColor, + lineHeight = MaterialTheme.typography.labelMedium.lineHeight + ) + } + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_chevron_forward_white_24dp), + tint = WikipediaTheme.colors.secondaryColor, + contentDescription = null + ) + } + Text( + modifier = Modifier.padding(top = 16.dp), + text = formatter.format(totalEdits), + style = MaterialTheme.typography.titleLarge, + color = WikipediaTheme.colors.primaryColor, + fontWeight = FontWeight.Medium + ) + } + } +} + @Composable fun ImpactStatView( modifier: Modifier, @@ -392,3 +455,16 @@ private fun AllTimeImpactCardPreview() { ) } } + +@Preview +@Composable +private fun TotalEditsCardPreview() { + BaseTheme( + currentTheme = Theme.LIGHT + ) { + TotalEditsCard( + modifier = Modifier.fillMaxWidth(), + totalEdits = 1234 + ) + } +} diff --git a/app/src/main/java/org/wikipedia/dataclient/Service.kt b/app/src/main/java/org/wikipedia/dataclient/Service.kt index c814bfc0d05..ffd6d283f33 100644 --- a/app/src/main/java/org/wikipedia/dataclient/Service.kt +++ b/app/src/main/java/org/wikipedia/dataclient/Service.kt @@ -117,6 +117,9 @@ interface Service { @GET(MW_API_PREFIX + "action=query&prop=info|description|pageimages&pilicense=any&inprop=varianttitles|displaytitle&redirects=1&pithumbsize=" + PREFERRED_THUMB_SIZE) suspend fun getInfoByPageIdsOrTitles(@Query("pageids") pageIds: String? = null, @Query("titles") titles: String? = null): MwQueryResponse + @GET(MW_API_PREFIX + "action=query&meta=globaluserinfo&guiprop=editcount&prop=info|description|pageimages&pilicense=any&inprop=varianttitles|displaytitle&redirects=1&pithumbsize=" + PREFERRED_THUMB_SIZE) + suspend fun getInfoByTitlesWithGlobalUserInfo(@Query("titles") titles: String? = null): MwQueryResponse + @GET(MW_API_PREFIX + "action=query&meta=siteinfo&siprop=general|autocreatetempuser") suspend fun getPageIds(@Query("titles") titles: String): MwQueryResponse diff --git a/app/src/main/res/drawable/ic_public_24.xml b/app/src/main/res/drawable/ic_public_24.xml new file mode 100644 index 00000000000..972abc9c381 --- /dev/null +++ b/app/src/main/res/drawable/ic_public_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values-qq/strings.xml b/app/src/main/res/values-qq/strings.xml index fcbf7c3d29b..c8dad2594e5 100644 --- a/app/src/main/res/values-qq/strings.xml +++ b/app/src/main/res/values-qq/strings.xml @@ -1181,6 +1181,7 @@ Label encouraging the user to discover new articles. Label on card that shows the last donation time. Subtitle of the donation card that indicates last donation. + Title of card indicating the total edits across all Wikimedia projects. Title of the game stats card. Label of the game stats that indicates the best streak. Button label to go to the WikiGames. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 891249604cf..595a463067c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1203,6 +1203,7 @@ Looking for something new to read? Unknown Last donation in app]]> + Total edits across projects Game stats Best streak Play WikiGames