diff --git a/app/src/main/java/org/wikipedia/util/ShareUtil.kt b/app/src/main/java/org/wikipedia/util/ShareUtil.kt index a7b38581981..0a2179985b4 100644 --- a/app/src/main/java/org/wikipedia/util/ShareUtil.kt +++ b/app/src/main/java/org/wikipedia/util/ShareUtil.kt @@ -54,7 +54,7 @@ object ShareUtil { } fun shareImage(coroutineScope: CoroutineScope, context: Context, bmp: Bitmap, - imageFileName: String, subject: String, text: String) { + imageFileName: String, subject: String, text: String, onShared: (() -> Unit)? = null) { coroutineScope.launch(CoroutineExceptionHandler { _, msg -> displayOnCatchMessage(msg, context) }) { @@ -65,6 +65,7 @@ object ShareUtil { val chooserIntent = buildImageShareChooserIntent(context, subject, text, uri) context.startActivity(chooserIntent) } + onShared?.invoke() } } diff --git a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewHighlightsScreen.kt b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewHighlightsScreen.kt new file mode 100644 index 00000000000..ba9f9065188 --- /dev/null +++ b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewHighlightsScreen.kt @@ -0,0 +1,313 @@ +package org.wikipedia.yearinreview + +import android.graphics.Bitmap +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.rememberGraphicsLayer +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastForEachIndexed +import org.wikipedia.R +import org.wikipedia.compose.ComposeColors +import org.wikipedia.compose.theme.BaseTheme +import org.wikipedia.compose.theme.WikipediaTheme +import org.wikipedia.theme.Theme + +@Composable +fun YearInReviewHighlightsScreen( + modifier: Modifier = Modifier, + screenData: YearInReviewScreenData.HighlightsScreen, + onShareHighlightsBtnClick: () -> Unit +) { + Column( + modifier = modifier + ) { + Text( + modifier = Modifier + .padding(top = 60.dp, bottom = 16.dp), + text = buildAnnotatedString { + append(stringResource(R.string.year_in_review_highlights_thank_you_message)) + withStyle( + style = SpanStyle( + fontWeight = FontWeight.Normal, + ) + ) { + append(" ") + append(stringResource(R.string.year_in_review_highlights_looking_forward_message)) + } + }, + color = WikipediaTheme.colors.paperColor, + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Medium + ) + + ShareableHighlightsCard( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .background(ComposeColors.Gray100) + .border(width = 1.dp, color = ComposeColors.Gray300) + .padding(8.dp) + .verticalScroll(rememberScrollState()), + highlights = screenData.highlights + ) + + Button( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 32.dp), + colors = ButtonDefaults.buttonColors( + containerColor = WikipediaTheme.colors.progressiveColor + ), + onClick = onShareHighlightsBtnClick + ) { + Text( + text = stringResource(R.string.year_in_review_highlights_share_button_title), + color = WikipediaTheme.colors.paperColor, + style = MaterialTheme.typography.labelLarge + ) + } + } +} + +@Composable +fun ShareableHighlightsCard( + modifier: Modifier = Modifier, + hashtag: String = stringResource(R.string.year_in_review_hashtag), + logoResource: Int = R.drawable.w_nav_mark, + logoDescription: String = stringResource(R.string.year_in_review_highlights_logo_description), + highlights: List, +) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = hashtag, + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + lineHeight = 21.sp, + color = ComposeColors.Gray700 + ) + Image( + modifier = Modifier + .size(163.dp) + .padding(vertical = 4.dp), + painter = painterResource(logoResource), + contentDescription = logoDescription + ) + Text( + modifier = Modifier + .padding(4.dp), + text = logoDescription, + fontSize = 12.sp, + lineHeight = 16.sp, + color = ComposeColors.Gray700 + ) + highlights.forEach { highlightItem -> + HighlightsContent( + modifier = Modifier + .padding(top = 12.dp), + highlightItem = highlightItem + ) + } + } +} + +@Composable +fun HighlightsContent( + modifier: Modifier = Modifier, + highlightItem: YearInReviewScreenData.HighlightItem +) { + Row( + modifier = modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + modifier = Modifier + .width(98.dp), + text = highlightItem.title, + fontWeight = FontWeight.Bold, + fontSize = 14.sp, + lineHeight = 21.sp + ) + + if (highlightItem.singleValue != null) { + Text( + text = highlightItem.singleValue, + fontSize = 14.sp, + lineHeight = 21.sp, + color = Color.Black + ) + } else { + Column { + highlightItem.items.fastForEachIndexed { index, item -> + Text( + text = buildAnnotatedString { + append("${index + 1}. ") + withStyle( + style = SpanStyle( + color = highlightItem.highlightColor + ) + ) { + append(text = item) + } + }, + lineHeight = 21.sp + ) + } + } + } + } +} + +@Composable +fun ShareHighlightsScreenCapture( + highlights: List, + onBitmapReady: (Bitmap) -> Unit +) { + val graphicsLayer = rememberGraphicsLayer() + var isReadyToCapture by remember { mutableStateOf(false) } + + if (isReadyToCapture) { + LaunchedEffect(Unit) { + val bitmap = graphicsLayer.toImageBitmap() + onBitmapReady(bitmap.asAndroidBitmap()) + } + } + + Box( + modifier = Modifier + .fillMaxSize() + .onGloballyPositioned { + isReadyToCapture = true + } + .drawWithContent { + graphicsLayer.record { + drawRect( + color = ComposeColors.White + ) + this@drawWithContent.drawContent() + } + } + .background(ComposeColors.White), + ) { + ShareableHighlightsCard( + modifier = Modifier + .align(Alignment.Center) + .padding(horizontal = 34.dp) + .background(ComposeColors.Gray100) + .border(width = 1.dp, color = ComposeColors.Gray300) + .padding(8.dp), + highlights = highlights + ) + + Text( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 16.dp), + text = stringResource(R.string.year_in_highlights_screenshot_url), + style = MaterialTheme.typography.bodyLarge + ) + } +} + +@Preview(device = Devices.PIXEL_9) +@Composable +private fun YearInReviewHighlightsScreenPreview() { + BaseTheme( + currentTheme = Theme.LIGHT + ) { + YearInReviewHighlightsScreen( + screenData = YearInReviewScreenData.HighlightsScreen( + highlights = listOf( + YearInReviewScreenData.HighlightItem( + title = "Articles I read the longest", + items = listOf( + "Pamela Anderson", + "Pamukkale", + "History of US science fiction and fantasy magazines to 1950" + ), + highlightColor = ComposeColors.Blue600 + ) + ) + ), + onShareHighlightsBtnClick = {} + ) + } +} + +@Preview +@Composable +private fun ListItemsPreview() { + BaseTheme( + currentTheme = Theme.LIGHT + ) { + HighlightsContent( + highlightItem = YearInReviewScreenData.HighlightItem( + title = "Articles I read the longest", + items = listOf( + "Pamela Anderson", + "Pamukkale", + "History of US science fiction and fantasy magazines to 1950" + ), + highlightColor = ComposeColors.Blue600 + ) + ) + } +} + +@Preview +@Composable +private fun SingleValuePreview() { + BaseTheme( + currentTheme = Theme.LIGHT + ) { + HighlightsContent( + highlightItem = YearInReviewScreenData.HighlightItem( + title = "Minutes read", + singleValue = "924", + highlightColor = ComposeColors.Blue600 + ) + ) + } +} diff --git a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenCaptureHandler.kt b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenCaptureHandler.kt new file mode 100644 index 00000000000..3f674638270 --- /dev/null +++ b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenCaptureHandler.kt @@ -0,0 +1,61 @@ +package org.wikipedia.yearinreview + +import android.graphics.Bitmap +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.LocalContext +import org.wikipedia.R +import org.wikipedia.util.ShareUtil + +@Composable +fun YearInReviewScreenCaptureHandler( + request: YearInReviewCaptureRequest, + onComplete: () -> Unit +) { + val coroutineScope = rememberCoroutineScope() + val context = LocalContext.current + + val shareImageCallback: (Bitmap) -> Unit = { bitmap -> + val googlePlayUrl = context.getString(R.string.year_in_review_share_url) + YearInReviewViewModel.YIR_TAG + val bodyText = context.getString(R.string.year_in_review_share_body, googlePlayUrl, context.getString(R.string.year_in_review_hashtag)) + ShareUtil.shareImage( + coroutineScope = coroutineScope, + context = context, + bmp = bitmap, + imageFileName = YearInReviewViewModel.YIR_TAG, + subject = context.getString(R.string.year_in_review_share_subject), + text = bodyText, + onShared = onComplete + ) + } + + when (request) { + is YearInReviewCaptureRequest.StandardScreen -> { + CreateScreenShotBitmap( + screenContent = request.screenData, + onBitmapReady = shareImageCallback, + requestScreenshotBitmap = null + ) + } + is YearInReviewCaptureRequest.GeoScreen -> { + CreateScreenShotBitmap( + screenContent = request.screenData, + onBitmapReady = shareImageCallback, + requestScreenshotBitmap = request.requestScreenshotBitmap + ) + } + + is YearInReviewCaptureRequest.HighlightsScreen -> { + ShareHighlightsScreenCapture( + highlights = request.highlights, + onBitmapReady = shareImageCallback + ) + } + } +} + +sealed class YearInReviewCaptureRequest { + data class StandardScreen(val screenData: YearInReviewScreenData) : YearInReviewCaptureRequest() + data class GeoScreen(val screenData: YearInReviewScreenData, val requestScreenshotBitmap: ((Int, Int) -> Bitmap)? = null) : YearInReviewCaptureRequest() + data class HighlightsScreen(val highlights: List) : YearInReviewCaptureRequest() +} diff --git a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenData.kt b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenData.kt index b3314a36a2f..87890dca875 100644 --- a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenData.kt +++ b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenData.kt @@ -34,12 +34,29 @@ import coil3.compose.SubcomposeAsyncImageContent import coil3.request.ImageRequest import coil3.request.allowHardware import org.wikipedia.R +import org.wikipedia.compose.ComposeColors import org.wikipedia.compose.theme.BaseTheme import org.wikipedia.compose.theme.WikipediaTheme import org.wikipedia.history.db.HistoryEntryWithImage import org.wikipedia.theme.Theme import org.wikipedia.yearinreview.YearInReviewScreenData.CustomIconScreen +fun Modifier.yearInReviewHeaderBackground(): Modifier { + return this.background( + brush = Brush.linearGradient( + colorStops = arrayOf( + 0.265f to Color(0xFF0D0D0D), + 0.385f to Color(0xFF092D60), + 0.515f to Color(0xFF1171C8), + 0.585f to Color(0xFF3DB2FF), + 0.775f to Color(0xFFD3F1F3) + ), + start = Offset(0f, 0f), + end = Offset(0f, Float.POSITIVE_INFINITY) + ) + ) +} + sealed class YearInReviewScreenData( val allowDonate: Boolean = true, val showDonateInToolbar: Boolean = true @@ -72,7 +89,7 @@ sealed class YearInReviewScreenData( modifier = Modifier .fillMaxWidth() .aspectRatio(aspectRatio) - .headerBackground(), + .yearInReviewHeaderBackground(), contentAlignment = Alignment.Center ) { SubcomposeAsyncImage( @@ -100,29 +117,18 @@ sealed class YearInReviewScreenData( } } } - - open fun Modifier.headerBackground(): Modifier { - return this.background( - brush = Brush.linearGradient( - colorStops = arrayOf( - 0.265f to Color(0xFF0D0D0D), - 0.385f to Color(0xFF092D60), - 0.515f to Color(0xFF1171C8), - 0.585f to Color(0xFF3DB2FF), - 0.775f to Color(0xFFD3F1F3) - ), - start = Offset(0f, 0f), - end = Offset(0f, Float.POSITIVE_INFINITY) - ) - ) - } } - class HighlightsScreen( - allowDonate: Boolean = true, - val highlights: List, - val headlineText: String? = null - ) : YearInReviewScreenData(allowDonate) + data class HighlightItem( + val title: String, + val singleValue: String? = null, + val items: List = emptyList(), + val highlightColor: Color = ComposeColors.Gray700 + ) + + data class HighlightsScreen( + val highlights: List + ) : YearInReviewScreenData() class GeoScreen( allowDonate: Boolean = true, diff --git a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenDeck.kt b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenDeck.kt index 36de13fb4ef..e1d1abff270 100644 --- a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenDeck.kt +++ b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewScreenDeck.kt @@ -42,7 +42,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -68,7 +67,6 @@ import org.wikipedia.compose.components.error.WikiErrorView import org.wikipedia.compose.theme.BaseTheme import org.wikipedia.compose.theme.WikipediaTheme import org.wikipedia.theme.Theme -import org.wikipedia.util.ShareUtil import org.wikipedia.util.UiState import org.wikipedia.util.UriUtil import kotlin.math.absoluteValue @@ -90,30 +88,19 @@ fun YearInReviewScreenDeck( } is UiState.Success -> { - val coroutineScope = rememberCoroutineScope() val pages = state.data val pagerState = rememberPagerState(pageCount = { pages.size }) - var startCapture by remember { mutableStateOf(false) } - val context = LocalContext.current + var captureRequest by remember { mutableStateOf(null) } - if (startCapture) { - CreateScreenShotBitmap( - screenContent = pages[pagerState.currentPage], - requestScreenshotBitmap = requestScreenshotBitmap - ) { bitmap -> - val googlePlayUrl = context.getString(R.string.year_in_review_share_url) + YearInReviewViewModel.YIR_TAG - val bodyText = context.getString(R.string.year_in_review_share_body, googlePlayUrl, context.getString(R.string.year_in_review_hashtag)) - ShareUtil.shareImage( - coroutineScope = coroutineScope, - context = context, - bmp = bitmap, - imageFileName = YearInReviewViewModel.YIR_TAG, - subject = context.getString(R.string.year_in_review_share_subject), - text = bodyText - ) - startCapture = false - } + captureRequest?.let { request -> + YearInReviewScreenCaptureHandler( + request = request, + onComplete = { + captureRequest = null + } + ) } + Scaffold( modifier = modifier .safeDrawingPadding(), @@ -121,7 +108,8 @@ fun YearInReviewScreenDeck( topBar = { TopAppBar( colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = WikipediaTheme.colors.paperColor), + containerColor = WikipediaTheme.colors.paperColor + ), title = { }, navigationIcon = { IconButton(onClick = { onCloseButtonClick() }) { @@ -169,7 +157,11 @@ fun YearInReviewScreenDeck( pagerState = pagerState, totalPages = pages.size, onShareClick = { - startCapture = true + when (pages[pagerState.currentPage]) { + is YearInReviewScreenData.GeoScreen -> { captureRequest = YearInReviewCaptureRequest.GeoScreen(pages[pagerState.currentPage], requestScreenshotBitmap) } + is YearInReviewScreenData.StandardScreen -> { captureRequest = YearInReviewCaptureRequest.StandardScreen(pages[pagerState.currentPage]) } + is YearInReviewScreenData.HighlightsScreen -> {} + } }, onDonateClick = onDonateClick ) @@ -182,10 +174,15 @@ fun YearInReviewScreenDeck( ) { page -> YearInReviewScreenContent( modifier = Modifier + .fillMaxSize() .padding(paddingValues) .verticalScroll(rememberScrollState()), requestScreenshotBitmap = requestScreenshotBitmap, - screenData = pages[page] + screenData = pages[page], + onShareHighlightsBtnClick = { highlights -> + captureRequest = + YearInReviewCaptureRequest.HighlightsScreen(highlights) + } ) } } @@ -221,6 +218,7 @@ fun MainBottomBar( onDonateClick: () -> Unit ) { val context = LocalContext.current + val currentScreen = pages[pagerState.currentPage] Column { HorizontalDivider( modifier = Modifier @@ -236,16 +234,19 @@ fun MainBottomBar( .fillMaxWidth() .wrapContentHeight() ) { - IconButton( - onClick = onShareClick, - modifier = Modifier.padding(end = 16.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_share), - tint = WikipediaTheme.colors.primaryColor, - contentDescription = stringResource(R.string.year_in_review_share_icon) - ) + if (currentScreen !is YearInReviewScreenData.HighlightsScreen) { + IconButton( + onClick = onShareClick, + modifier = Modifier.padding(end = 16.dp) + ) { + Icon( + painter = painterResource(R.drawable.ic_share), + tint = WikipediaTheme.colors.primaryColor, + contentDescription = stringResource(R.string.year_in_review_share_icon) + ) + } } + Row( modifier = Modifier .wrapContentHeight() @@ -281,19 +282,17 @@ fun MainBottomBar( ) } } - if (pagerState.currentPage + 1 < totalPages) { - IconButton( - onClick = { onNavigationRightClick() }, - modifier = Modifier - .padding(0.dp) - .align(Alignment.CenterEnd) - ) { - Icon( - painter = painterResource(R.drawable.ic_arrow_forward_black_24dp), - tint = WikipediaTheme.colors.primaryColor, - contentDescription = stringResource(R.string.year_in_review_navigate_right) - ) - } + IconButton( + onClick = { onNavigationRightClick() }, + modifier = Modifier + .padding(0.dp) + .align(Alignment.CenterEnd) + ) { + Icon( + painter = painterResource(R.drawable.ic_arrow_forward_black_24dp), + tint = WikipediaTheme.colors.primaryColor, + contentDescription = stringResource(R.string.year_in_review_navigate_right) + ) } } } @@ -376,6 +375,7 @@ fun YearInReviewScreenContent( requestScreenshotBitmap: ((Int, Int) -> Bitmap)?, screenCaptureMode: Boolean = false, isOnboardingScreen: Boolean = false, + onShareHighlightsBtnClick: ((List) -> Unit)? = null, isImageResourceLoaded: ((Boolean) -> Unit)? = null ) { when (screenData) { @@ -398,7 +398,15 @@ fun YearInReviewScreenContent( ) } is YearInReviewScreenData.HighlightsScreen -> { - // @TODO: has different layout structure based on ios slides + YearInReviewHighlightsScreen( + modifier = modifier + .yearInReviewHeaderBackground() + .padding(horizontal = 18.dp), + screenData = screenData, + onShareHighlightsBtnClick = { + onShareHighlightsBtnClick?.invoke(screenData.highlights) + } + ) } } } diff --git a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewSlides.kt b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewSlides.kt index a799c4dae74..9ef748299e1 100644 --- a/app/src/main/java/org/wikipedia/yearinreview/YearInReviewSlides.kt +++ b/app/src/main/java/org/wikipedia/yearinreview/YearInReviewSlides.kt @@ -2,9 +2,15 @@ package org.wikipedia.yearinreview import android.content.Context import org.wikipedia.R +import org.wikipedia.compose.ComposeColors import org.wikipedia.history.db.HistoryEntryWithImage import org.wikipedia.settings.Prefs import org.wikipedia.settings.RemoteConfig +import org.wikipedia.yearinreview.YearInReviewScreenData.CustomIconScreen +import org.wikipedia.yearinreview.YearInReviewScreenData.HighlightItem +import org.wikipedia.yearinreview.YearInReviewScreenData.HighlightsScreen +import org.wikipedia.yearinreview.YearInReviewScreenData.ReadingPatterns +import org.wikipedia.yearinreview.YearInReviewScreenData.StandardScreen import java.text.NumberFormat import java.time.DayOfWeek import java.time.Month @@ -22,13 +28,14 @@ class YearInReviewSlides( val pagesWithCoordinates: List, val yearInReviewModel: YearInReviewModel ) { + private val numberFormatter = NumberFormat.getNumberInstance() - private fun englishReadingHoursScreen(): YearInReviewScreenData.StandardScreen { + private fun englishReadingHoursScreen(): StandardScreen { val bodyText = context.resources.getQuantityString(R.plurals.year_in_review_slide_english_reading_hours_body_first, config.hoursReadEN.toInt(), config.hoursReadEN) + " " + context.resources.getQuantityString(R.plurals.year_in_review_slide_english_reading_hours_body_second, config.yearsReadEN, config.yearsReadEN, currentYear) - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_english_reading_hours_headline, config.hoursReadEN.toInt(), config.hoursReadEN), @@ -36,7 +43,7 @@ class YearInReviewSlides( ) } - private fun spentReadingMinutesScreen(): YearInReviewScreenData.StandardScreen { + private fun spentReadingMinutesScreen(): StandardScreen { if (yearInReviewModel.localReadingArticlesCount < YearInReviewViewModel.MIN_READING_ARTICLES || yearInReviewModel.totalReadingTimeMinutes < YearInReviewViewModel.MIN_READING_MINUTES) { return if (isEnglishWiki) { @@ -73,7 +80,7 @@ class YearInReviewSlides( config.languages, config.languages, currentYear) } - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_block_10_resize, headlineText = headlineText, @@ -81,12 +88,12 @@ class YearInReviewSlides( ) } - private fun popularEnglishArticlesScreen(): YearInReviewScreenData.StandardScreen { + private fun popularEnglishArticlesScreen(): StandardScreen { val popularEnglishArticlesText = buildListWithNumbers(config.topReadEN) val popularEnglishArticlesBlogUrl = context.getString(R.string.year_in_review_popular_english_articles_blog_url) - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.getString(R.string.year_in_review_slide_popular_english_articles_headline), @@ -95,8 +102,8 @@ class YearInReviewSlides( ) } - private fun appSavedArticlesScreen(): YearInReviewScreenData.StandardScreen { - return YearInReviewScreenData.StandardScreen( + private fun appSavedArticlesScreen(): StandardScreen { + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_global_saved_articles_headline, config.savedArticlesApps.toInt(), config.savedArticlesApps), @@ -104,12 +111,12 @@ class YearInReviewSlides( ) } - private fun availableLanguagesScreen(): YearInReviewScreenData.StandardScreen { + private fun availableLanguagesScreen(): StandardScreen { val bodyText = context.resources.getQuantityString(R.plurals.year_in_review_slide_available_languages_body_first, config.articles.toInt(), config.articles) + " " + context.resources.getQuantityString(R.plurals.year_in_review_slide_available_languages_body_second, config.languages, config.languages, currentYear) - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_available_languages_headline, config.languages, config.languages), @@ -117,8 +124,8 @@ class YearInReviewSlides( ) } - private fun viewedArticlesTimesScreen(): YearInReviewScreenData.StandardScreen { - return YearInReviewScreenData.StandardScreen( + private fun viewedArticlesTimesScreen(): StandardScreen { + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_viewed_articles_times_headline, config.viewsApps.toInt(), config.viewsApps), @@ -126,7 +133,7 @@ class YearInReviewSlides( ) } - private fun readingPatternsScreen(): YearInReviewScreenData.StandardScreen? { + private fun readingPatternsScreen(): StandardScreen? { if (yearInReviewModel.localReadingArticlesCount < YearInReviewViewModel.MIN_READING_ARTICLES) { return null } @@ -142,7 +149,7 @@ class YearInReviewSlides( .getDisplayName(TextStyle.FULL, Locale.getDefault()) val favoriteMonthText = Month.of(yearInReviewModel.favoriteMonthDidMostReading) .getDisplayName(TextStyle.FULL, Locale.getDefault()) - return YearInReviewScreenData.ReadingPatterns( + return ReadingPatterns( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.getString(R.string.year_in_review_slide_reading_patterns_headline), @@ -152,14 +159,14 @@ class YearInReviewSlides( ) } - private fun topCategoriesScreen(): YearInReviewScreenData.StandardScreen? { + private fun topCategoriesScreen(): StandardScreen? { if (yearInReviewModel.localTopCategories.isEmpty() || yearInReviewModel.localTopCategories.size < YearInReviewViewModel.MIN_TOP_CATEGORY) { return null } val topCategoriesText = buildListWithNumbers(yearInReviewModel.localTopCategories) - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.getString(R.string.year_in_review_slide_top_categories_headline), @@ -167,7 +174,7 @@ class YearInReviewSlides( ) } - private fun topArticlesScreen(): YearInReviewScreenData.StandardScreen? { + private fun topArticlesScreen(): StandardScreen? { if (yearInReviewModel.localTopVisitedArticles.isEmpty()) { return null } @@ -175,7 +182,7 @@ class YearInReviewSlides( val topArticlesText = buildListWithNumbers(yearInReviewModel.localTopVisitedArticles) val quantity = yearInReviewModel.localTopVisitedArticles.size - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_top_articles_headline, quantity), @@ -199,12 +206,12 @@ class YearInReviewSlides( ) } - private fun localSavedArticlesScreen(): YearInReviewScreenData.StandardScreen { + private fun localSavedArticlesScreen(): StandardScreen { if (yearInReviewModel.localSavedArticlesCount < YearInReviewViewModel.MIN_SAVED_ARTICLES) { return appSavedArticlesScreen() } val appSavedArticlesSize = config.savedArticlesApps.toInt() - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_saved_articles_headline, yearInReviewModel.localSavedArticlesCount, yearInReviewModel.localSavedArticlesCount), @@ -214,13 +221,13 @@ class YearInReviewSlides( ) } - private fun editedTimesScreen(): YearInReviewScreenData.StandardScreen { + private fun editedTimesScreen(): StandardScreen { val userEditsCount = yearInReviewModel.userEditsCount var formattedUserEditsNumber = NumberFormat.getNumberInstance(Locale.getDefault()).format(yearInReviewModel.userEditsCount) if (userEditsCount > YearInReviewViewModel.MAX_EDITED_TIMES) { formattedUserEditsNumber = "${YearInReviewViewModel.MAX_EDITED_TIMES}+" } - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_edited_times_headline, userEditsCount, formattedUserEditsNumber), @@ -228,12 +235,12 @@ class YearInReviewSlides( ) } - private fun editsViewedTimesScreen(): YearInReviewScreenData.StandardScreen? { + private fun editsViewedTimesScreen(): StandardScreen? { if (yearInReviewModel.userEditsViewedTimes <= 0L) { return null } val quantity = yearInReviewModel.userEditsViewedTimes.toInt() - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_edits_viewed_times_headline, quantity, yearInReviewModel.userEditsViewedTimes), @@ -241,8 +248,8 @@ class YearInReviewSlides( ) } - private fun appEditedTimesScreen(): YearInReviewScreenData.StandardScreen { - return YearInReviewScreenData.StandardScreen( + private fun appEditedTimesScreen(): StandardScreen { + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_app_edited_times_headline, config.edits.toInt(), config.edits), @@ -250,8 +257,8 @@ class YearInReviewSlides( ) } - private fun editedPerMinuteScreen(): YearInReviewScreenData.StandardScreen { - return YearInReviewScreenData.StandardScreen( + private fun editedPerMinuteScreen(): StandardScreen { + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_edited_per_minute_headline, config.editsPerMinute, config.editsPerMinute), @@ -259,12 +266,12 @@ class YearInReviewSlides( ) } - private fun englishEditedTimesScreen(): YearInReviewScreenData.StandardScreen { + private fun englishEditedTimesScreen(): StandardScreen { val bodyText = context.resources.getQuantityString(R.plurals.year_in_review_slide_english_edited_times_body_first, config.edits.toInt(), config.edits, config.editsEN) + " " + context.resources.getQuantityString(R.plurals.year_in_review_slide_english_edited_times_body_second, config.editsEN.toInt(), config.editsEN) - return YearInReviewScreenData.StandardScreen( + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_english_edited_times_headline, config.edits.toInt(), config.edits), @@ -272,8 +279,8 @@ class YearInReviewSlides( ) } - private fun addedBytesScreen(): YearInReviewScreenData.StandardScreen { - return YearInReviewScreenData.StandardScreen( + private fun addedBytesScreen(): StandardScreen { + return StandardScreen( isFundraisingAllowed, animatedImageResource = R.drawable.year_in_review_puzzle_pieces, // TODO: tbd headlineText = context.resources.getQuantityString(R.plurals.year_in_review_slide_bytes_added_headline, config.bytesAddedEN.toInt(), config.bytesAddedEN), @@ -282,16 +289,94 @@ class YearInReviewSlides( ) } - private fun highlightScreen(vararg params: Int): YearInReviewScreenData.HighlightsScreen { - // TODO: yir122 - return YearInReviewScreenData.HighlightsScreen( - isFundraisingAllowed, + private fun loggedInHighlightScreen(): HighlightsScreen { + return HighlightsScreen( + highlights = buildList { + if (yearInReviewModel.localTopVisitedArticles.isNotEmpty()) { + val topVisitedArticles = yearInReviewModel.localTopVisitedArticles.take(3) + add( + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_in_most_read_article_title, topVisitedArticles.size), + items = topVisitedArticles, + highlightColor = ComposeColors.Blue600 + ) + ) + } + add( + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_in_minutes_read_title, yearInReviewModel.totalReadingTimeMinutes.toInt()), + singleValue = numberFormatter.format(yearInReviewModel.totalReadingTimeMinutes) + ) + ) + add( + HighlightItem( + title = context.resources.getString(R.string.year_in_review_highlights_logged_in_favorite_day_title), + singleValue = DayOfWeek.of(yearInReviewModel.favoriteDayToRead) + .getDisplayName(TextStyle.FULL, Locale.getDefault()) + ) + ) + add( + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_in_articles_read_title, yearInReviewModel.localReadingArticlesCount), + singleValue = numberFormatter.format(yearInReviewModel.localReadingArticlesCount) + ) + ) + val topCategories = yearInReviewModel.localTopCategories.take(3) + add( + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_in_articles_interested_categories_title, topCategories.size), + items = topCategories + ) + ) + if (isEditor) { + add( + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_in_articles_edited_articles_title, yearInReviewModel.userEditsCount), + singleValue = numberFormatter.format(yearInReviewModel.userEditsCount) + ) + ) + } + } + ) + } + + private fun enWikiLoggedOutHighlightsScreen(): HighlightsScreen { + + return HighlightsScreen( highlights = listOf( - "You read 350 articles", - "You saved 25 articles", - "You edited Wikipedia 150 times" - ), - headlineText = "2025 highlights" + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_out_en_most_popular_title, config.topReadEN.size), + items = config.topReadEN, + highlightColor = ComposeColors.Blue600 + ), + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_out_en_hours_spent_title, config.hoursReadEN.toInt()), + singleValue = numberFormatter.format(config.hoursReadEN) + ), + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_out_en_edits_title, config.editsEN.toInt()), + singleValue = numberFormatter.format(config.editsEN) + ) + ) + ) + } + + private fun nonEnWikiLoggedOutHighlightsScreen(): HighlightsScreen { + return HighlightsScreen( + highlights = listOf( + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_out_non_en_articles_title, config.viewsApps.toInt()), + singleValue = numberFormatter.format(config.viewsApps) + ), + HighlightItem( + title = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_out_non_en_edits_title, config.editsApps.toInt()), + singleValue = numberFormatter.format(config.editsApps) + ), + HighlightItem( + title = context.resources.getString(R.string.year_in_review_highlights_logged_out_non_en_wikipedia_edited_title), + singleValue = context.resources.getQuantityString(R.plurals.year_in_review_highlights_logged_out_non_en_wikipedia_per_minute_label, config.editsPerMinute, config.editsPerMinute) + ) + ) ) } @@ -325,13 +410,13 @@ class YearInReviewSlides( } else { context.getString(R.string.year_in_review_slide_app_icon_donor) } - YearInReviewScreenData.CustomIconScreen( + CustomIconScreen( isFundraisingAllowed, headlineText = R.string.year_in_review_slide_app_icon_title_unlocked, bodyText = context.getString(R.string.year_in_review_slide_app_icon_body_unlocked, contributorType, YearInReviewViewModel.YIR_YEAR) ) } else if (isFundraisingAllowed) { - YearInReviewScreenData.CustomIconScreen( + CustomIconScreen( isFundraisingAllowed, headlineText = R.string.year_in_review_slide_app_icon_title_unlock, bodyText = context.getString(R.string.year_in_review_slide_app_icon_body_unlock, YearInReviewViewModel.YIR_YEAR, YearInReviewViewModel.YIR_YEAR + 1, @@ -346,7 +431,7 @@ class YearInReviewSlides( englishReadingHoursScreen(), popularEnglishArticlesScreen(), appSavedArticlesScreen() - ) + editorRoutes() + unlockedIconRoute() + highlightScreen()).filterNotNull() + ) + editorRoutes() + unlockedIconRoute() + enWikiLoggedOutHighlightsScreen()).filterNotNull() } private fun nonLoggedInGeneralSlides(): List { @@ -354,7 +439,7 @@ class YearInReviewSlides( availableLanguagesScreen(), viewedArticlesTimesScreen(), appSavedArticlesScreen() - ) + editorRoutes() + unlockedIconRoute() + highlightScreen()).filterNotNull() + ) + editorRoutes() + unlockedIconRoute() + nonEnWikiLoggedOutHighlightsScreen()).filterNotNull() } private fun loggedInEnglishSlides(): List { @@ -366,7 +451,7 @@ class YearInReviewSlides( topCategoriesScreen(), geoWithArticlesScreen(), localSavedArticlesScreen() - ) + editorRoutes() + unlockedIconRoute() + highlightScreen()).filterNotNull() + ) + editorRoutes() + unlockedIconRoute() + loggedInHighlightScreen()).filterNotNull() } private fun loggedInGeneralSlides(): List { @@ -378,7 +463,7 @@ class YearInReviewSlides( topCategoriesScreen(), geoWithArticlesScreen(), localSavedArticlesScreen() - ) + editorRoutes() + unlockedIconRoute() + highlightScreen()).filterNotNull() + ) + editorRoutes() + unlockedIconRoute() + loggedInHighlightScreen()).filterNotNull() } private fun buildListWithNumbers(items: List): String { diff --git a/app/src/main/res/values-qq/strings.xml b/app/src/main/res/values-qq/strings.xml index d086cdf675b..ec5b9457d8d 100644 --- a/app/src/main/res/values-qq/strings.xml +++ b/app/src/main/res/values-qq/strings.xml @@ -2034,6 +2034,27 @@ Title of the app users viewed articles times slide for the Year in Review. %,d will be replaced by the number of viewed times. Body of the app users viewed articles time slide for the Year in Review. + + Text displayed at the top of the Year in Review highlights screen. + Text shown after the thank-you message on the Year in Review highlights screen. + Title of the first highlight shown on the Year in Review highlights screen for logged-out English Wikipedia users. + Title of the second highlight shown on the Year in Review highlights screen for logged-out English Wikipedia users. + Title of the third highlight shown on the Year in Review highlights screen for logged-out English Wikipedia users. + Title of the first highlight shown on the Year in Review highlights screen for logged-out Non English Wikipedia users. + Title of the second highlight shown on the Year in Review highlights screen for logged-out Non English Wikipedia users. + Title of the third highlight shown on the Year in Review highlights screen for logged-out Non English Wikipedia users. + + Value of the third highlight shown on the Year in Review highlights screen for logged-out Non English Wikipedia users, %d will be replaced by the number of minutes. + Value of the third highlight shown on the Year in Review highlights screen for logged-out Non English Wikipedia users, %d will be replaced by the number of minutes. + + Title of the first highlight shown on the Year in Review highlights screen for logged-in Wikipedia users. + Title of the second highlight shown on the Year in Review highlights screen for logged-in Wikipedia users. + Title of the third highlight shown on the Year in Review highlights screen for logged-in Wikipedia users. + Title of the fourth highlight shown on the Year in Review highlights screen for logged-in Wikipedia users. + Title of the fifth highlight shown on the Year in Review highlights screen for logged-in Wikipedia users. + Title of the sixth highlight shown on the Year in Review highlights screen for logged-in Wikipedia users. + Title for the button that shares the Highlights screen. + Text for for the Wikipedia logo in year-in-review highlights. Title for the recommended reading list feature. Title of the onboarding card for the recommended reading list. Message on the onboarding card for the recommended reading list. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 16631eb2d2b..7d798ebcfd6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2154,6 +2154,56 @@ For people around the world, Wikipedia is the first stop when answering a question, looking up information for school or work, or learning a new fact. + Thank you for spending your year with Wikipedia. + We look forward to next year! + + Most popular article on English Wikipedia + Most popular articles on English Wikipedia + + + Hour spent reading + Hours spent reading + + + Change editors made + Changes editors made + + + Number of viewed article + Number of viewed articles + + + Edit on-app + Edits on-app + + How often Wikipedia was edited + + %d time per minute + %d times per minute + + + Article I read the most + Articles I read the most + + + Minute read + Minutes read + + Favorite day to read + + Article read + Articles read + + + Category that interested me + Categories that interested me + + + Article edited + Articles edited + + Share highlights + Wikipedia logo Discover Discover articles picked just for you diff --git a/app/src/main/res/values/strings_no_translate.xml b/app/src/main/res/values/strings_no_translate.xml index 2a76b08ed78..f47cca55c0d 100644 --- a/app/src/main/res/values/strings_no_translate.xml +++ b/app/src/main/res/values/strings_no_translate.xml @@ -29,6 +29,7 @@ https://www.mediawiki.org/wiki/Special:MyLanguage/Wikimedia_Apps/Team/Wikipedia_Year_in_Review/Frequently_Asked_Questions#Frequently_asked_questions #WikipediaYearInReview + wikipedia.org/year-in-review https://www.mediawiki.org/wiki/Wikimedia_Apps/Team/Android/Rabbit_Holes https://www.mediawiki.org/wiki/Wikimedia_Apps/Team/Android/Customizable_Donation_Reminder_Experiment https://www.mediawiki.org/wiki/Wikimedia_Apps/Team/Android/Activity_Tab_Experiment