diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt index 5f806bb6ad8b..eefb58a67f82 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt @@ -217,7 +217,15 @@ class SubFilterViewModel @Inject constructor( } fun setDefaultSubfilter(isClearingFilter: Boolean) { - readerTracker.track(Stat.READER_FILTER_SHEET_CLEARED) + val filterItemType = FilterItemType.fromSubfilterListItem(getCurrentSubfilterValue()) + if (filterItemType != null) { + readerTracker.track( + Stat.READER_FILTER_SHEET_CLEARED, + mutableMapOf(FilterItemType.trackingEntry(filterItemType)) + ) + } else { + readerTracker.track(Stat.READER_FILTER_SHEET_CLEARED) + } updateSubfilter( filter = SiteAll( onClickAction = ::onSubfilterClicked, @@ -317,7 +325,15 @@ class SubFilterViewModel @Inject constructor( fun onSubfilterSelected(subfilterListItem: SubfilterListItem) { // We should not track subfilter selected if we're clearing a filter that is currently applied. if (!subfilterListItem.isClearingFilter) { - readerTracker.track(Stat.READER_FILTER_SHEET_ITEM_SELECTED) + val filterItemType = FilterItemType.fromSubfilterListItem(subfilterListItem) + if (filterItemType != null) { + readerTracker.track( + Stat.READER_FILTER_SHEET_ITEM_SELECTED, + mutableMapOf(FilterItemType.trackingEntry(filterItemType)) + ) + } else { + readerTracker.track(Stat.READER_FILTER_SHEET_ITEM_SELECTED,) + } } changeSubfilter(subfilterListItem, true, mTagFragmentStartedWith) } @@ -425,4 +441,22 @@ class SubFilterViewModel @Inject constructor( return SUBFILTER_VM_BASE_KEY + tag.keyString } } + + sealed class FilterItemType(val trackingValue: String) { + data object Tag : FilterItemType("topic") + + data object Blog : FilterItemType("site") + + companion object { + fun fromSubfilterListItem(subfilterListItem: SubfilterListItem): FilterItemType? = + when(subfilterListItem.type) { + SubfilterListItem.ItemType.SITE -> Blog + SubfilterListItem.ItemType.TAG -> Tag + else -> null + } + + fun trackingEntry(filterItemType: FilterItemType): Pair = + "type" to filterItemType.trackingValue + } + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/tracker/ReaderTracker.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/tracker/ReaderTracker.kt index c6fc4e1006b9..5cc3c683b3a8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/tracker/ReaderTracker.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/tracker/ReaderTracker.kt @@ -88,6 +88,7 @@ class ReaderTracker @Inject constructor( ReaderTab.A8C -> analyticsTrackerWrapper.track(AnalyticsTracker.Stat.READER_A8C_SHOWN) ReaderTab.P2 -> analyticsTrackerWrapper.track(AnalyticsTracker.Stat.READER_P2_SHOWN) ReaderTab.CUSTOM -> analyticsTrackerWrapper.track(AnalyticsTracker.Stat.READER_CUSTOM_TAB_SHOWN) + ReaderTab.TAGS_FEED -> analyticsTrackerWrapper.track(AnalyticsTracker.Stat.READER_TAGS_FEED_SHOWN) } appPrefsWrapper.setReaderActiveTab(readerTab) } @@ -404,6 +405,7 @@ class ReaderTracker @Inject constructor( readerTag.isA8C -> "a8c" readerTag.isListTopic -> "list" readerTag.isP2 -> "p2" + readerTag.isTags -> "tags" else -> null }?.let { trackingId -> analyticsTrackerWrapper.track( @@ -515,7 +517,8 @@ enum class ReaderTab( SAVED(4, ReaderTracker.SOURCE_SAVED), CUSTOM(5, ReaderTracker.SOURCE_CUSTOM), A8C(6, ReaderTracker.SOURCE_A8C), - P2(7, ReaderTracker.SOURCE_P2); + P2(7, ReaderTracker.SOURCE_P2), + TAGS_FEED(8, ReaderTracker.SOURCE_TAGS_FEED); companion object { fun fromId(id: Int): ReaderTab { @@ -527,6 +530,7 @@ enum class ReaderTab( A8C.id -> A8C P2.id -> P2 CUSTOM.id -> CUSTOM + TAGS_FEED.id -> TAGS_FEED else -> throw RuntimeException("Unexpected ReaderTab id") } } @@ -540,6 +544,7 @@ enum class ReaderTab( readerTag.isDiscover -> DISCOVER readerTag.isA8C -> A8C readerTag.isP2 -> P2 + readerTag.isTags -> TAGS_FEED else -> CUSTOM } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedUiStateMapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedUiStateMapper.kt index eb1b31ac932a..283341978ef4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedUiStateMapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedUiStateMapper.kt @@ -5,11 +5,13 @@ import org.wordpress.android.models.ReaderTag import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper import org.wordpress.android.ui.reader.views.compose.tagsfeed.TagsFeedPostItem import org.wordpress.android.util.DateTimeUtilsWrapper +import org.wordpress.android.util.UrlUtilsWrapper import javax.inject.Inject class ReaderTagsFeedUiStateMapper @Inject constructor( private val dateTimeUtilsWrapper: DateTimeUtilsWrapper, private val readerUtilsWrapper: ReaderUtilsWrapper, + private val urlUtilsWrapper: UrlUtilsWrapper, ) { @Suppress("LongParameterList") fun mapLoadedTagFeedItem( @@ -29,25 +31,26 @@ class ReaderTagsFeedUiStateMapper @Inject constructor( onMoreFromTagClick = onMoreFromTagClick, ), postList = ReaderTagsFeedViewModel.PostList.Loaded( - posts.map { + posts.map { post -> TagsFeedPostItem( - siteName = it.blogName, + siteName = post.blogName.takeIf { it.isNotBlank() } + ?: post.blogUrl.let { urlUtilsWrapper.removeScheme(it) }, postDateLine = dateTimeUtilsWrapper.javaDateToTimeSpan( - it.getDisplayDate(dateTimeUtilsWrapper) + post.getDisplayDate(dateTimeUtilsWrapper) ), - postTitle = it.title, - postExcerpt = it.excerpt, - postImageUrl = it.featuredImage, - postNumberOfLikesText = if (it.numLikes > 0) readerUtilsWrapper.getShortLikeLabelText( - numLikes = it.numLikes + postTitle = post.title, + postExcerpt = post.excerpt, + postImageUrl = post.featuredImage, + postNumberOfLikesText = if (post.numLikes > 0) readerUtilsWrapper.getShortLikeLabelText( + numLikes = post.numLikes ) else "", - postNumberOfCommentsText = if (it.numReplies > 0) readerUtilsWrapper.getShortCommentLabelText( - numComments = it.numReplies + postNumberOfCommentsText = if (post.numReplies > 0) readerUtilsWrapper.getShortCommentLabelText( + numComments = post.numReplies ) else "", - isPostLiked = it.isLikedByCurrentUser, + isPostLiked = post.isLikedByCurrentUser, isLikeButtonEnabled = true, - postId = it.postId, - blogId = it.blogId, + postId = post.postId, + blogId = post.blogId, onSiteClick = onSiteClick, onPostCardClick = onPostCardClick, onPostLikeClick = onPostLikeClick, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedViewModel.kt index 3e01330a1114..525270715e31 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedViewModel.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import org.wordpress.android.R +import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.datasets.wrappers.ReaderPostTableWrapper import org.wordpress.android.models.ReaderPost import org.wordpress.android.models.ReaderTag @@ -48,6 +49,7 @@ class ReaderTagsFeedViewModel @Inject constructor( private val readerPostMoreButtonUiStateBuilder: ReaderPostMoreButtonUiStateBuilder, private val readerPostUiStateBuilder: ReaderPostUiStateBuilder, private val displayUtilsWrapper: DisplayUtilsWrapper, + private val readerTracker: ReaderTracker, ) : ScopedViewModel(bgDispatcher) { private val _uiStateFlow: MutableStateFlow = MutableStateFlow(UiState.Initial) val uiStateFlow: StateFlow = _uiStateFlow @@ -235,11 +237,13 @@ class ReaderTagsFeedViewModel @Inject constructor( @VisibleForTesting fun onTagChipClick(readerTag: ReaderTag) { + readerTracker.track(AnalyticsTracker.Stat.READER_TAGS_FEED_HEADER_TAPPED) _actionEvents.value = ActionEvent.FilterTagPostsFeed(readerTag) } @VisibleForTesting fun onMoreFromTagClick(readerTag: ReaderTag) { + readerTracker.track(AnalyticsTracker.Stat.READER_TAGS_FEED_MORE_FROM_TAG_TAPPED) _actionEvents.value = ActionEvent.OpenTagPostList(readerTag) } @@ -258,9 +262,14 @@ class ReaderTagsFeedViewModel @Inject constructor( } } - private fun onPostCardClick(postItem: TagsFeedPostItem) { + @VisibleForTesting + fun onPostCardClick(postItem: TagsFeedPostItem) { launch { findPost(postItem.postId, postItem.blogId)?.let { + readerTracker.trackBlog( + AnalyticsTracker.Stat.READER_POST_CARD_TAPPED, + it.blogId, it.feedId, it.isFollowedByCurrentUser, ReaderTracker.SOURCE_TAGS_FEED, + ) readerPostCardActionsHandler.handleOnItemClicked( it, ReaderTracker.SOURCE_TAGS_FEED @@ -430,7 +439,7 @@ class ReaderTagsFeedViewModel @Inject constructor( it, type, isBookmarkList = false, - source = ReaderTracker.SOURCE_DISCOVER + source = ReaderTracker.SOURCE_TAGS_FEED, ) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeed.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeed.kt index d866c039dd4b..45996523486b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeed.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeed.kt @@ -41,6 +41,9 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -61,8 +64,6 @@ import org.wordpress.android.ui.reader.viewmodels.tagsfeed.ReaderTagsFeedViewMod import org.wordpress.android.ui.reader.views.compose.filter.ReaderFilterChip import org.wordpress.android.ui.utils.UiString -private const val LOADING_POSTS_COUNT = 5 - @Composable fun ReaderTagsFeed(uiState: UiState) { Box( @@ -147,8 +148,14 @@ private fun Loaded(uiState: UiState.Loaded) { @Composable private fun Loading() { + val fetchingPostsLabel = stringResource(id = R.string.posts_fetching) + LazyColumn( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .clearAndSetSemantics { + contentDescription = fetchingPostsLabel + }, userScrollEnabled = false, ) { val numberOfLoadingRows = 3 @@ -177,7 +184,7 @@ private fun Loading() { horizontalArrangement = Arrangement.spacedBy(Margin.Large.value), contentPadding = PaddingValues(horizontal = Margin.Large.value), ) { - items(LOADING_POSTS_COUNT) { + items(ReaderTagsFeedComposeUtils.LOADING_POSTS_COUNT) { ReaderTagsFeedPostListItemLoading() } } @@ -259,9 +266,13 @@ private fun Empty(uiState: UiState.Empty) { @Composable private fun PostListLoading() { + val loadingLabel = stringResource(id = R.string.loading) LazyRow( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .clearAndSetSemantics { + contentDescription = loadingLabel + }, userScrollEnabled = false, horizontalArrangement = Arrangement.spacedBy(Margin.ExtraMediumLarge.value), contentPadding = PaddingValues( @@ -269,7 +280,7 @@ private fun PostListLoading() { end = Margin.Large.value ), ) { - items(LOADING_POSTS_COUNT) { + items(ReaderTagsFeedComposeUtils.LOADING_POSTS_COUNT) { ReaderTagsFeedPostListItemLoading() } } @@ -304,7 +315,7 @@ private fun PostListLoaded( ) Box( modifier = Modifier - .height(340.dp) + .height(ReaderTagsFeedComposeUtils.PostItemHeight) .padding( start = Margin.ExtraLarge.value, end = Margin.ExtraLarge.value, @@ -364,6 +375,7 @@ private fun PostListError( modifier = Modifier .height(250.dp) .fillMaxWidth() + .semantics(mergeDescendants = true) {} .padding(start = 60.dp, end = 60.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedComposeUtils.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedComposeUtils.kt new file mode 100644 index 000000000000..1549deb7ee34 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedComposeUtils.kt @@ -0,0 +1,38 @@ +package org.wordpress.android.ui.reader.views.compose.tagsfeed + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.min +import androidx.compose.ui.unit.sp + +object ReaderTagsFeedComposeUtils { + const val LOADING_POSTS_COUNT = 5 + + const val POST_ITEM_TITLE_MAX_LINES = 2 + val POST_ITEM_IMAGE_SIZE = 64.dp + private val POST_ITEM_HEIGHT = 150.sp // use SP to scale with text size, which is the main content of the item + private val POST_ITEM_MAX_WIDTH = 320.dp + private const val POST_ITEM_WIDTH_PERCENTAGE = 0.8f + + val PostItemHeight: Dp + @Composable + get() { + with(LocalDensity.current) { + return POST_ITEM_HEIGHT.toDp() + } + } + + val PostItemWidth: Dp + @Composable + get() { + val localConfiguration = LocalConfiguration.current + val screenWidth = remember(localConfiguration) { + localConfiguration.screenWidthDp.dp + } + return min((screenWidth * POST_ITEM_WIDTH_PERCENTAGE), POST_ITEM_MAX_WIDTH) + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedPostListItem.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedPostListItem.kt index 17068f352705..d7ed424ef3cd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedPostListItem.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedPostListItem.kt @@ -7,7 +7,9 @@ import android.widget.ImageView import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -16,6 +18,7 @@ import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -30,26 +33,37 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.composed import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.CustomAccessibilityAction +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.customActions +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import coil.compose.AsyncImage import coil.request.ImageRequest import org.wordpress.android.R -import org.wordpress.android.ui.compose.modifiers.conditionalThen import org.wordpress.android.ui.compose.theme.AppColor import org.wordpress.android.ui.compose.theme.AppTheme import org.wordpress.android.ui.compose.unit.Margin import org.wordpress.android.util.extensions.getColorResIdFromAttribute import org.wordpress.android.util.extensions.getDrawableResIdFromAttribute +private const val CONTENT_TOTAL_LINES = 3 + @SuppressLint("ResourceType") @Composable fun ReaderTagsFeedPostListItem( @@ -62,13 +76,20 @@ fun ReaderTagsFeedPostListItem( val secondaryElementColor = baseColor.copy( alpha = 0.6F ) + + val hasInteractions = postNumberOfLikesText.isNotBlank() || postNumberOfCommentsText.isNotBlank() + Column( modifier = Modifier - .width(240.dp) - .height(340.dp) + .width(ReaderTagsFeedComposeUtils.PostItemWidth) + .height(ReaderTagsFeedComposeUtils.PostItemHeight) + .itemSemanticsModifier(item), + verticalArrangement = Arrangement.spacedBy(Margin.Small.value), ) { Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .heightIn(min = 24.dp) + .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { // Site name @@ -102,88 +123,79 @@ fun ReaderTagsFeedPostListItem( color = secondaryElementColor, ) } - // Post title - Text( - modifier = Modifier - .padding(top = Margin.Medium.value) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { onPostCardClick(item) }, - ), - text = postTitle, - style = MaterialTheme.typography.titleMedium, - color = baseColor, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - // Post excerpt - Text( - modifier = Modifier - .padding( - top = Margin.Small.value, - bottom = Margin.Medium.value, - ) - .conditionalThen( - predicate = postImageUrl.isBlank(), - other = Modifier.height(180.dp) - ) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = { onPostCardClick(item) }, - ), - text = postExcerpt, - style = MaterialTheme.typography.bodySmall, - color = primaryElementColor, - maxLines = if (!postImageUrl.isBlank()) 2 else 10, - overflow = TextOverflow.Ellipsis, - ) - // Post image - if (!postImageUrl.isBlank()) { - PostImage( - imageUrl = postImageUrl, - onClick = { onPostCardClick(item) }, - ) - } - Spacer(Modifier.weight(1f)) + + // Post content row Row( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .weight(1f), + horizontalArrangement = Arrangement.spacedBy(Margin.Medium.value), verticalAlignment = Alignment.CenterVertically, ) { - // Number of likes - Text( - text = postNumberOfLikesText, - style = MaterialTheme.typography.bodyMedium, - color = secondaryElementColor, - maxLines = 1, + // Post text content + PostTextContent( + title = postTitle, + excerpt = postExcerpt, + onClick = { onPostCardClick(item) }, + titleColor = baseColor, + excerptColor = primaryElementColor, + modifier = Modifier + .weight(1f), ) - Spacer(Modifier.height(Margin.Medium.value)) - // "•" separator. We should only show it if likes *and* comments text is not empty. - if (postNumberOfLikesText.isNotBlank() && postNumberOfCommentsText.isNotBlank()) { + + // Post image + if (postImageUrl.isNotBlank()) { + PostImage( + imageUrl = postImageUrl, + onClick = { onPostCardClick(item) }, + ) + } + } + + // Likes and comments row + if (hasInteractions) { + val interactionTextStyle = MaterialTheme.typography.bodySmall + + Row( + modifier = Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + // Number of likes Text( - modifier = Modifier.padding( - horizontal = Margin.Small.value - ), - text = "•", - style = MaterialTheme.typography.bodyMedium, + text = postNumberOfLikesText, + style = interactionTextStyle, color = secondaryElementColor, + maxLines = 1, + ) + Spacer(Modifier.height(Margin.Medium.value)) + // "•" separator. We should only show it if likes *and* comments text is not empty. + if (postNumberOfLikesText.isNotBlank() && postNumberOfCommentsText.isNotBlank()) { + Text( + modifier = Modifier.padding( + horizontal = Margin.Small.value + ), + text = "•", + style = interactionTextStyle, + color = secondaryElementColor, + ) + } + // Number of comments + Text( + text = postNumberOfCommentsText, + style = interactionTextStyle, + color = secondaryElementColor, + maxLines = 1, ) } - // Number of comments - Text( - text = postNumberOfCommentsText, - style = MaterialTheme.typography.bodyMedium, - color = secondaryElementColor, - maxLines = 1, - ) } - Spacer(Modifier.height(Margin.Medium.value)) + + // Actions row Row( modifier = Modifier .fillMaxWidth() .height(24.dp), + verticalAlignment = Alignment.CenterVertically, ) { // Like action TextButton( @@ -255,6 +267,43 @@ fun ReaderTagsFeedPostListItem( } } +private fun Modifier.itemSemanticsModifier(item: TagsFeedPostItem): Modifier = composed { + val openPostActionLabel = stringResource(R.string.reader_tags_feed_action_label_open_post) + val openBlogActionLabel = stringResource(R.string.reader_tags_feed_action_label_open_blog) + + val likeStateDescription = if (item.isPostLiked) stringResource(R.string.mnu_comment_liked) else null + val likeActionLabel = if (item.isPostLiked) { + stringResource(R.string.reader_tags_feed_action_label_unlike_post) + } else { + stringResource(R.string.reader_tags_feed_action_label_like_post) + } + + val openMenuActionLabel = stringResource(R.string.reader_tags_feed_action_label_open_menu) + + clearAndSetSemantics { + contentDescription = "${item.siteName}, ${item.postDateLine}, ${item.postTitle}" + customActions = listOf( + CustomAccessibilityAction(openPostActionLabel) { + item.onPostCardClick(item) + true + }, + CustomAccessibilityAction(openBlogActionLabel) { + item.onSiteClick(item) + true + }, + CustomAccessibilityAction(likeActionLabel) { + item.onPostLikeClick(item) + true + }, + CustomAccessibilityAction(openMenuActionLabel) { + item.onPostMoreMenuClick(item) + true + }, + ) + likeStateDescription?.let { stateDescription = it } + } +} + @Composable fun PostImage( imageUrl: String, @@ -263,8 +312,7 @@ fun PostImage( ) { AsyncImage( modifier = modifier - .fillMaxWidth() - .height(150.dp) + .size(ReaderTagsFeedComposeUtils.POST_ITEM_IMAGE_SIZE) .clip(RoundedCornerShape(corner = CornerSize(8.dp))) .clickable { onClick() }, model = ImageRequest.Builder(LocalContext.current) @@ -276,6 +324,77 @@ fun PostImage( ) } +// Post title and excerpt Column +@Composable +fun PostTextContent( + title: String, + excerpt: String, + titleColor: Color, + excerptColor: Color, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + BoxWithConstraints( + modifier = modifier, + ) { + val density = LocalDensity.current + val maxWidthPx = with(density) { + maxWidth.toPx().toInt() + } + + val textMeasurer = rememberTextMeasurer() + val titleStyle = MaterialTheme.typography.titleMedium + + val excerptMaxLines = remember(title, titleStyle, maxWidthPx) { + val titleLayoutResult = textMeasurer.measure( + text = title, + style = titleStyle, + maxLines = ReaderTagsFeedComposeUtils.POST_ITEM_TITLE_MAX_LINES, + overflow = TextOverflow.Ellipsis, + constraints = Constraints(maxWidth = maxWidthPx), + ) + + val titleLines = titleLayoutResult.lineCount + CONTENT_TOTAL_LINES - titleLines + } + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(Margin.Small.value), + ) { + // Post title + Text( + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = onClick, + ), + text = title, + style = MaterialTheme.typography.titleMedium, + color = titleColor, + maxLines = ReaderTagsFeedComposeUtils.POST_ITEM_TITLE_MAX_LINES, + overflow = TextOverflow.Ellipsis, + ) + + // Post excerpt + Text( + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = onClick, + ), + text = excerpt, + style = MaterialTheme.typography.bodySmall, + color = excerptColor, + maxLines = excerptMaxLines, + overflow = TextOverflow.Ellipsis, + ) + } + } +} + @Preview @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable @@ -285,12 +404,12 @@ fun ReaderTagsFeedPostListItemPreview() { modifier = Modifier .fillMaxWidth() .fillMaxHeight() - .padding(top = 16.dp, bottom = 16.dp) ) { LazyRow( modifier = Modifier .fillMaxWidth(), - contentPadding = PaddingValues(horizontal = 24.dp), + contentPadding = PaddingValues(24.dp), + horizontalArrangement = Arrangement.spacedBy(Margin.ExtraMediumLarge.value), ) { item { ReaderTagsFeedPostListItem( @@ -327,7 +446,8 @@ fun ReaderTagsFeedPostListItemPreview() { onPostMoreMenuClick = {}, ) ) - Spacer(Modifier.width(24.dp)) + } + item { ReaderTagsFeedPostListItem( item = TagsFeedPostItem( siteName = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pellentesque" + @@ -362,7 +482,8 @@ fun ReaderTagsFeedPostListItemPreview() { onPostMoreMenuClick = {}, ) ) - Spacer(Modifier.width(24.dp)) + } + item { ReaderTagsFeedPostListItem( item = TagsFeedPostItem( siteName = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pellentesque" + @@ -383,7 +504,8 @@ fun ReaderTagsFeedPostListItemPreview() { onPostMoreMenuClick = {}, ) ) - Spacer(Modifier.width(24.dp)) + } + item { ReaderTagsFeedPostListItem( item = TagsFeedPostItem( siteName = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pellentesque" + @@ -404,7 +526,8 @@ fun ReaderTagsFeedPostListItemPreview() { onPostMoreMenuClick = {}, ) ) - Spacer(Modifier.width(24.dp)) + } + item { ReaderTagsFeedPostListItem( item = TagsFeedPostItem( siteName = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pellentesque" + @@ -426,7 +549,8 @@ fun ReaderTagsFeedPostListItemPreview() { onPostMoreMenuClick = {}, ) ) - Spacer(Modifier.width(24.dp)) + } + item { ReaderTagsFeedPostListItem( item = TagsFeedPostItem( siteName = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer" + @@ -448,7 +572,8 @@ fun ReaderTagsFeedPostListItemPreview() { onPostMoreMenuClick = {}, ) ) - Spacer(Modifier.width(24.dp)) + } + item { ReaderTagsFeedPostListItem( item = TagsFeedPostItem( siteName = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pellentesque" + @@ -482,7 +607,8 @@ fun ReaderTagsFeedPostListItemPreview() { onPostMoreMenuClick = {}, ) ) - Spacer(Modifier.width(24.dp)) + } + item { ReaderTagsFeedPostListItem( item = TagsFeedPostItem( siteName = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pellentesque" + diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedPostListItemLoading.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedPostListItemLoading.kt index 5f58c3164ff9..1926a5585d5f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedPostListItemLoading.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeedPostListItemLoading.kt @@ -3,14 +3,15 @@ package org.wordpress.android.ui.reader.views.compose.tagsfeed import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement 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.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.RoundedCornerShape @@ -24,76 +25,98 @@ import org.wordpress.android.ui.compose.theme.AppColor import org.wordpress.android.ui.compose.theme.AppTheme import org.wordpress.android.ui.compose.unit.Margin +private val ThinLineHeight = 10.dp +private val ThickLineHeight = 16.dp + @Composable fun ReaderTagsFeedPostListItemLoading() { - val backgroundColor = if (isSystemInDarkTheme()) { + val contentColor = if (isSystemInDarkTheme()) { AppColor.White.copy(alpha = 0.12F) } else { AppColor.Black.copy(alpha = 0.08F) } Column( modifier = Modifier - .width(240.dp) - .height(340.dp), + .width(ReaderTagsFeedComposeUtils.PostItemWidth) + .height(ReaderTagsFeedComposeUtils.PostItemHeight), + verticalArrangement = Arrangement.SpaceBetween, ) { + // Site info placeholder Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .height(24.dp), verticalAlignment = Alignment.CenterVertically, ) { Box( modifier = Modifier - .width(99.dp) - .height(8.dp) + .width(150.dp) + .height(ThinLineHeight) .clip(shape = RoundedCornerShape(16.dp)) - .background(backgroundColor), + .background(contentColor), ) } - Box( - modifier = Modifier - .padding(top = Margin.Large.value) - .width(204.dp) - .height(18.dp) - .clip(shape = RoundedCornerShape(16.dp)) - .background(backgroundColor), - ) - Box( - modifier = Modifier - .padding(top = Margin.Large.value) - .width(140.dp) - .height(18.dp) - .clip(shape = RoundedCornerShape(16.dp)) - .background(backgroundColor), - ) - Box( - modifier = Modifier - .padding(top = Margin.Large.value) - .fillMaxWidth() - .height(150.dp) - .clip(shape = RoundedCornerShape(8.dp)) - .background(backgroundColor), - ) - Box( + + // Content row placeholder + Row( modifier = Modifier - .padding( - start = Margin.Small.value, - top = Margin.Large.value, + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(Margin.Medium.value), + verticalAlignment = Alignment.CenterVertically, + ) { + // Post title and excerpt Column placeholder + Column( + modifier = Modifier + .weight(1f), + verticalArrangement = Arrangement.spacedBy(Margin.Medium.value), + ) { + // Title placeholder + Box( + modifier = Modifier + .fillMaxWidth(0.95f) + .height(ThickLineHeight) + .clip(shape = RoundedCornerShape(16.dp)) + .background(contentColor), ) - .width(170.dp) - .height(8.dp) - .clip(shape = RoundedCornerShape(16.dp)) - .background(backgroundColor), - ) - Box( - modifier = Modifier - .padding( - start = Margin.Small.value, - top = Margin.Large.value, + Box( + modifier = Modifier + .fillMaxWidth(0.8f) + .height(ThickLineHeight) + .clip(shape = RoundedCornerShape(16.dp)) + .background(contentColor), ) - .width(170.dp) - .height(8.dp) - .clip(shape = RoundedCornerShape(16.dp)) - .background(backgroundColor), - ) + } + + // Image placeholder + Box( + modifier = Modifier + .size(ReaderTagsFeedComposeUtils.POST_ITEM_IMAGE_SIZE) + .clip(shape = RoundedCornerShape(8.dp)) + .background(contentColor), + ) + } + + // Likes and comments + actions placeholder + Column( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(Margin.MediumLarge.value), + ) { + Box( + modifier = Modifier + .fillMaxWidth(0.6f) + .height(ThinLineHeight) + .clip(shape = RoundedCornerShape(16.dp)) + .background(contentColor), + ) + Box( + modifier = Modifier + .fillMaxWidth(0.5f) + .height(ThinLineHeight) + .clip(shape = RoundedCornerShape(16.dp)) + .background(contentColor), + ) + } } } @@ -110,14 +133,10 @@ fun ReaderTagsFeedPostListItemLoadingPreview() { LazyRow( modifier = Modifier .fillMaxWidth(), + contentPadding = PaddingValues(24.dp), + horizontalArrangement = Arrangement.spacedBy(Margin.ExtraMediumLarge.value), ) { - item { - ReaderTagsFeedPostListItemLoading() - Spacer(Modifier.width(12.dp)) - ReaderTagsFeedPostListItemLoading() - Spacer(Modifier.width(12.dp)) - ReaderTagsFeedPostListItemLoading() - Spacer(Modifier.width(12.dp)) + items(5) { ReaderTagsFeedPostListItemLoading() } } diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 91d91bd5278c..4e6d1540e5e8 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2337,6 +2337,11 @@ We couldn\'t load posts from this tag right now We couldn\'t find any posts tagged %s right now Retry + open post + open blog + like post + remove post like + open menu No connection diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/ReaderTrackerTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/ReaderTrackerTest.kt index c45df778107b..71221349351d 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/ReaderTrackerTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/ReaderTrackerTest.kt @@ -325,6 +325,23 @@ class ReaderTrackerTest { ) } + @Test + fun `Should track dropdown menu tags feed item tapped`() { + tracker.trackDropdownMenuItemTapped( + ReaderTag( + "slug", + "displayName", + "title", + null, + ReaderTagType.TAGS, + ) + ) + verify(analyticsTrackerWrapper).track( + stat = AnalyticsTracker.Stat.READER_DROPDOWN_MENU_ITEM_TAPPED, + properties = mapOf("id" to "tags"), + ) + } + @Test fun `Should track post with reading preferences returned from ReadingPreferencesTracker`() { val post = ReaderPost() diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModelTest.kt index dea51ed31922..bad452b02fd0 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModelTest.kt @@ -20,8 +20,10 @@ import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.fluxc.model.AccountModel import org.wordpress.android.fluxc.store.AccountStore import org.wordpress.android.getOrAwaitValue +import org.wordpress.android.models.ReaderBlog import org.wordpress.android.models.ReaderTag import org.wordpress.android.models.ReaderTagType.BOOKMARKED +import org.wordpress.android.models.ReaderTagType.TAGS import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.ui.reader.ReaderSubsActivity import org.wordpress.android.ui.reader.ReaderTypes.ReaderPostListType @@ -447,6 +449,34 @@ class SubFilterViewModelTest : BaseUnitTest() { verify(readerTracker).track(AnalyticsTracker.Stat.READER_FILTER_SHEET_ITEM_SELECTED) } + @Test + fun `Should track READER_FILTER_SHEET_ITEM_SELECTED with parameters if type is SITE`() { + val siteFilter = Site( + blog = ReaderBlog(), + onClickAction = {}, + ) + viewModel.onSubfilterSelected(siteFilter) + verify(readerTracker).track( + stat = AnalyticsTracker.Stat.READER_FILTER_SHEET_ITEM_SELECTED, + properties = mutableMapOf("type" to "site"), + ) + } + + @Test + fun `Should track READER_FILTER_SHEET_ITEM_SELECTED with parameters if type is TAG`() { + val tagFilter = Tag( + tag = ReaderTag( + "", "", "", "", TAGS + ), + onClickAction = {}, + ) + viewModel.onSubfilterSelected(tagFilter) + verify(readerTracker).track( + stat = AnalyticsTracker.Stat.READER_FILTER_SHEET_ITEM_SELECTED, + properties = mutableMapOf("type" to "topic"), + ) + } + @Test fun `Should propagate title container visibility state properly`() { listOf(true, false).forEach { isTitleContainerVisible -> diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderTagsFeedViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderTagsFeedViewModelTest.kt index 637e216049b5..d939380018e5 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderTagsFeedViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderTagsFeedViewModelTest.kt @@ -19,6 +19,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import org.wordpress.android.BaseUnitTest +import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.datasets.wrappers.ReaderPostTableWrapper import org.wordpress.android.getOrAwaitValue import org.wordpress.android.models.ReaderPost @@ -34,6 +35,7 @@ import org.wordpress.android.ui.reader.discover.ReaderPostUiStateBuilder import org.wordpress.android.ui.reader.exceptions.ReaderPostFetchException import org.wordpress.android.ui.reader.repository.ReaderPostRepository import org.wordpress.android.ui.reader.repository.usecases.PostLikeUseCase +import org.wordpress.android.ui.reader.tracker.ReaderTracker import org.wordpress.android.ui.reader.viewmodels.tagsfeed.ReaderTagsFeedUiStateMapper import org.wordpress.android.ui.reader.viewmodels.tagsfeed.ReaderTagsFeedViewModel import org.wordpress.android.ui.reader.viewmodels.tagsfeed.ReaderTagsFeedViewModel.ActionEvent @@ -68,6 +70,9 @@ class ReaderTagsFeedViewModelTest : BaseUnitTest() { @Mock lateinit var displayUtilsWrapper: DisplayUtilsWrapper + @Mock + lateinit var readerTracker: ReaderTracker + @Mock lateinit var navigationEvents: MediatorLiveData> @@ -101,6 +106,7 @@ class ReaderTagsFeedViewModelTest : BaseUnitTest() { readerPostMoreButtonUiStateBuilder = readerPostMoreButtonUiStateBuilder, readerPostUiStateBuilder = readerPostUiStateBuilder, displayUtilsWrapper = displayUtilsWrapper, + readerTracker = readerTracker, ) whenever(readerPostCardActionsHandler.navigationEvents) .thenReturn(navigationEvents) @@ -260,6 +266,15 @@ class ReaderTagsFeedViewModelTest : BaseUnitTest() { assertIs(actionEvents.first()) } + @Test + fun `Should track READER_TAGS_FEED_HEADER_TAPPED when onTagChipClick is called`() { + // When + viewModel.onTagChipClick(tag) + + // Then + verify(readerTracker).track(AnalyticsTracker.Stat.READER_TAGS_FEED_HEADER_TAPPED) + } + @Test fun `Should emit OpenTagPostList when onMoreFromTagClick is called`() { // When @@ -269,6 +284,16 @@ class ReaderTagsFeedViewModelTest : BaseUnitTest() { assertIs(actionEvents.first()) } + @Test + fun `Should track READER_TAGS_FEED_MORE_FROM_TAG_TAPPED when onMoreFromTagClick is called`() { + // When + viewModel.onMoreFromTagClick(tag) + + // Then + verify(readerTracker).track(AnalyticsTracker.Stat.READER_TAGS_FEED_MORE_FROM_TAG_TAPPED) + } + + @Test fun `Should emit ShowTagsList when onOpenTagsListClick is called`() { // When @@ -585,6 +610,49 @@ class ReaderTagsFeedViewModelTest : BaseUnitTest() { assertIs(actionEvents.first()) } + @Test + fun `Should track READER_POST_CARD_TAPPED when onPostCardClick is called`() = testCollectingUiStates { + // Given + val blogId = 123L + val feedId = 456L + val isFollowedByCurrentUser = true + whenever(readerPostTableWrapper.getBlogPost(any(), any(), any())) + .thenReturn(ReaderPost().apply { + this.blogId = blogId + this.feedId = feedId + this.isFollowedByCurrentUser = isFollowedByCurrentUser + }) + // When + viewModel.onPostCardClick( + postItem = TagsFeedPostItem( + siteName = "", + postDateLine = "", + postTitle = "", + postExcerpt = "", + postImageUrl = "", + postNumberOfLikesText = "", + postNumberOfCommentsText = "", + isPostLiked = true, + isLikeButtonEnabled = true, + postId = 123L, + blogId = 123L, + onSiteClick = {}, + onPostCardClick = {}, + onPostLikeClick = {}, + onPostMoreMenuClick = {} + ) + ) + + // Then + verify(readerTracker).trackBlog( + stat = AnalyticsTracker.Stat.READER_POST_CARD_TAPPED, + blogId = blogId, + feedId = feedId, + isFollowed = isFollowedByCurrentUser, + source = ReaderTracker.SOURCE_TAGS_FEED, + ) + } + private fun mockMapInitialTagFeedItems() { whenever(readerTagsFeedUiStateMapper.mapInitialPostsUiState(any(), any(), any(), any(), any(), any())) .thenAnswer { diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedUiStateMapperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedUiStateMapperTest.kt index d6da0aae93d6..c82f8e9ff3af 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedUiStateMapperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/tagsfeed/ReaderTagsFeedUiStateMapperTest.kt @@ -14,6 +14,7 @@ import org.wordpress.android.models.ReaderTagType import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper import org.wordpress.android.ui.reader.views.compose.tagsfeed.TagsFeedPostItem import org.wordpress.android.util.DateTimeUtilsWrapper +import org.wordpress.android.util.UrlUtilsWrapper import java.util.Date @OptIn(ExperimentalCoroutinesApi::class) @@ -22,9 +23,12 @@ class ReaderTagsFeedUiStateMapperTest : BaseUnitTest() { private val readerUtilsWrapper = mock() + private val urlUtilsWrapper = mock() + private val classToTest: ReaderTagsFeedUiStateMapper = ReaderTagsFeedUiStateMapper( dateTimeUtilsWrapper = dateTimeUtilsWrapper, readerUtilsWrapper = readerUtilsWrapper, + urlUtilsWrapper = urlUtilsWrapper, ) @Suppress("LongMethod") @@ -117,6 +121,99 @@ class ReaderTagsFeedUiStateMapperTest : BaseUnitTest() { assertEquals(expected, actual) } + @Suppress("LongMethod") + @Test + fun `Should map loaded TagFeedItem correctly with blank blog name`() { + // Given + val readerPost = ReaderPost().apply { + blogName = "" + blogUrl = "https://blogurl.wordpress.com" + title = "Title" + excerpt = "Excerpt" + featuredImage = "url" + numLikes = 5 + numReplies = 10 + isLikedByCurrentUser = true + datePublished = "" + } + val postList = ReaderPostList().apply { + add(readerPost) + } + val readerTag = ReaderTag( + "tag", + "tag", + "tag", + "endpoint", + ReaderTagType.FOLLOWED, + ) + val onTagChipClick = { _: ReaderTag -> } + val onMoreFromTagClick = { _: ReaderTag -> } + val onSiteClick: (TagsFeedPostItem) -> Unit = {} + val onPostCardClick: (TagsFeedPostItem) -> Unit = {} + val onPostLikeClick: (TagsFeedPostItem) -> Unit = {} + val onPostMoreMenuClick: (TagsFeedPostItem) -> Unit = {} + val onItemEnteredView: (ReaderTagsFeedViewModel.TagFeedItem) -> Unit = {} + + val dateLine = "dateLine" + val numberLikesText = "numberLikesText" + val numberCommentsText = "numberCommentsText" + + // When + whenever(dateTimeUtilsWrapper.dateFromIso8601(any())) + .thenReturn(Date(0)) + whenever(dateTimeUtilsWrapper.javaDateToTimeSpan(any())) + .thenReturn(dateLine) + whenever(readerUtilsWrapper.getShortLikeLabelText(readerPost.numLikes)) + .thenReturn(numberLikesText) + whenever(readerUtilsWrapper.getShortCommentLabelText(readerPost.numReplies)) + .thenReturn(numberCommentsText) + whenever(urlUtilsWrapper.removeScheme(readerPost.blogUrl)) + .thenReturn("blogurl.wordpress.com") + + val actual = classToTest.mapLoadedTagFeedItem( + tag = readerTag, + posts = postList, + onTagChipClick = onTagChipClick, + onMoreFromTagClick = onMoreFromTagClick, + onSiteClick = onSiteClick, + onPostCardClick = onPostCardClick, + onPostLikeClick = onPostLikeClick, + onPostMoreMenuClick = onPostMoreMenuClick, + onItemEnteredView = onItemEnteredView, + ) + // Then + val expected = ReaderTagsFeedViewModel.TagFeedItem( + tagChip = ReaderTagsFeedViewModel.TagChip( + tag = readerTag, + onTagChipClick = onTagChipClick, + onMoreFromTagClick = onMoreFromTagClick, + ), + postList = ReaderTagsFeedViewModel.PostList.Loaded( + listOf( + TagsFeedPostItem( + siteName = "blogurl.wordpress.com", + postDateLine = dateLine, + postTitle = readerPost.title, + postExcerpt = readerPost.excerpt, + postImageUrl = readerPost.featuredImage, + postNumberOfLikesText = numberLikesText, + postNumberOfCommentsText = numberCommentsText, + isPostLiked = readerPost.isLikedByCurrentUser, + isLikeButtonEnabled = true, + postId = 0L, + blogId = 0L, + onSiteClick = onSiteClick, + onPostLikeClick = onPostLikeClick, + onPostCardClick = onPostCardClick, + onPostMoreMenuClick = onPostMoreMenuClick, + ) + ) + ), + onItemEnteredView = onItemEnteredView, + ) + assertEquals(expected, actual) + } + @Test fun `Should map error TagFeedItem correctly`() { // Given diff --git a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java index 38bf1079891f..9f2d53fd5e7c 100644 --- a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java +++ b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java @@ -89,6 +89,8 @@ public enum Stat { READER_READING_PREFERENCES_ITEM_TAPPED, READER_READING_PREFERENCES_SAVED, READER_ANNOUNCEMENT_CARD_DISMISSED, + READER_TAGS_FEED_HEADER_TAPPED, + READER_TAGS_FEED_MORE_FROM_TAG_TAPPED, STATS_ACCESSED, STATS_ACCESS_ERROR, STATS_PERIOD_ACCESSED, @@ -772,6 +774,7 @@ public enum Stat { READER_LIKED_SHOWN, READER_SAVED_LIST_SHOWN, READER_CUSTOM_TAB_SHOWN, + READER_TAGS_FEED_SHOWN, READER_DISCOVER_SHOWN, READER_DISCOVER_PAGINATED, READER_DISCOVER_TOPIC_TAPPED,