Skip to content

Commit

Permalink
Merge pull request #20839 from wordpress-mobile/issue/20823-reader-ta…
Browse files Browse the repository at this point in the history
…gs-feed-retry

[Tags Feed] Implement retry action for error state
  • Loading branch information
Thomas Horta authored May 20, 2024
2 parents 8255e5b + b028ecd commit f5bf3b1
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class ReaderTagsFeedUiStateMapper @Inject constructor(
errorType: ReaderTagsFeedViewModel.ErrorType,
onTagChipClick: (ReaderTag) -> Unit,
onMoreFromTagClick: (ReaderTag) -> Unit,
onRetryClick: () -> Unit,
onRetryClick: (ReaderTag) -> Unit,
onItemEnteredView: (ReaderTagsFeedViewModel.TagFeedItem) -> Unit,
): ReaderTagsFeedViewModel.TagFeedItem =
ReaderTagsFeedViewModel.TagFeedItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,9 @@ class ReaderTagsFeedViewModel @Inject constructor(
updatedLoadedData[existingIndex] = updatedItem
}

(uiState as? UiState.Loaded)?.copy(data = updatedLoadedData) ?: UiState.Loaded(updatedLoadedData)
(uiState as? UiState.Loaded)?.copy(data = updatedLoadedData) ?: UiState.Loaded(
updatedLoadedData
)
}
}

Expand Down Expand Up @@ -247,16 +249,25 @@ class ReaderTagsFeedViewModel @Inject constructor(
_actionEvents.value = ActionEvent.OpenTagPostList(readerTag)
}

private fun onRetryClick() {
// TODO
@VisibleForTesting
fun onRetryClick(readerTag: ReaderTag) {
launch {
fetchTag(readerTag)
}
}

@VisibleForTesting
fun onSiteClick(postItem: TagsFeedPostItem) {
launch {
findPost(postItem.postId, postItem.blogId)?.let {
_navigationEvents.postValue(
Event(ReaderNavigationEvents.ShowBlogPreview(it.blogId, it.feedId, it.isFollowedByCurrentUser))
Event(
ReaderNavigationEvents.ShowBlogPreview(
it.blogId,
it.feedId,
it.isFollowedByCurrentUser
)
)
)
}
}
Expand All @@ -268,7 +279,10 @@ class ReaderTagsFeedViewModel @Inject constructor(
findPost(postItem.postId, postItem.blogId)?.let {
readerTracker.trackBlog(
AnalyticsTracker.Stat.READER_POST_CARD_TAPPED,
it.blogId, it.feedId, it.isFollowedByCurrentUser, ReaderTracker.SOURCE_TAGS_FEED,
it.blogId,
it.feedId,
it.isFollowedByCurrentUser,
ReaderTracker.SOURCE_TAGS_FEED,
)
readerPostCardActionsHandler.handleOnItemClicked(
it,
Expand Down Expand Up @@ -349,7 +363,10 @@ class ReaderTagsFeedViewModel @Inject constructor(
}
}

private fun findTagFeedItemToUpdate(uiState: UiState.Loaded, postItemToUpdate: TagsFeedPostItem) =
private fun findTagFeedItemToUpdate(
uiState: UiState.Loaded,
postItemToUpdate: TagsFeedPostItem
) =
uiState.data.firstOrNull { tagFeedItem ->
tagFeedItem.postList is PostList.Loaded && tagFeedItem.postList.items.firstOrNull {
it.postId == postItemToUpdate.postId && it.blogId == postItemToUpdate.blogId
Expand All @@ -359,7 +376,11 @@ class ReaderTagsFeedViewModel @Inject constructor(
private fun likePostRemote(postItem: TagsFeedPostItem, isPostLikedUpdated: Boolean) {
launch {
findPost(postItem.postId, postItem.blogId)?.let { post ->
postLikeUseCase.perform(post, !post.isLikedByCurrentUser, ReaderTracker.SOURCE_TAGS_FEED).collect {
postLikeUseCase.perform(
post,
!post.isLikedByCurrentUser,
ReaderTracker.SOURCE_TAGS_FEED
).collect {
when (it) {
is PostLikeUseCase.PostLikeState.Success -> {
// Re-enable like button without changing the current post item UI.
Expand Down Expand Up @@ -407,7 +428,8 @@ class ReaderTagsFeedViewModel @Inject constructor(
includeBookmark = true,
onButtonClicked = ::onMoreMenuButtonClicked,
)
val photonWidth = (displayUtilsWrapper.getDisplayPixelWidth() * PHOTON_WIDTH_QUALITY_RATION).toInt()
val photonWidth =
(displayUtilsWrapper.getDisplayPixelWidth() * PHOTON_WIDTH_QUALITY_RATION).toInt()
val photonHeight = (photonWidth * FEATURED_IMAGE_HEIGHT_WIDTH_RATION).toInt()
_openMoreMenuEvents.postValue(
MoreMenuUiState(
Expand All @@ -432,7 +454,11 @@ class ReaderTagsFeedViewModel @Inject constructor(
}
}

private fun onMoreMenuButtonClicked(postId: Long, blogId: Long, type: ReaderPostCardActionType) {
private fun onMoreMenuButtonClicked(
postId: Long,
blogId: Long,
type: ReaderPostCardActionType
) {
launch {
findPost(postId, blogId)?.let {
readerPostCardActionsHandler.onAction(
Expand Down Expand Up @@ -502,7 +528,7 @@ class ReaderTagsFeedViewModel @Inject constructor(

data class Error(
val type: ErrorType,
val onRetryClick: () -> Unit
val onRetryClick: (ReaderTag) -> Unit
) : PostList()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
Expand Down Expand Up @@ -373,13 +375,14 @@ private fun PostListError(
) {
Column(
modifier = Modifier
.height(250.dp)
.heightIn(min = ReaderTagsFeedComposeUtils.PostItemHeight)
.fillMaxWidth()
.semantics(mergeDescendants = true) {}
.padding(start = 60.dp, end = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Spacer(modifier = Modifier.height(Margin.ExtraLarge.value))
Spacer(modifier = Modifier.height(Margin.Medium.value))
Icon(
modifier = Modifier
.drawBehind {
Expand All @@ -392,15 +395,15 @@ private fun PostListError(
tint = MaterialTheme.colors.onSurface,
contentDescription = null
)
Spacer(modifier = Modifier.height(Margin.ExtraExtraMediumLarge.value))
Spacer(modifier = Modifier.height(Margin.ExtraMediumLarge.value))
val tagName = tagChip.tag.tagDisplayName
Text(
text = stringResource(id = R.string.reader_tags_feed_error_title, tagName),
style = androidx.compose.material3.MaterialTheme.typography.labelLarge,
color = MaterialTheme.colors.onSurface,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(Margin.Medium.value))
Spacer(modifier = Modifier.height(Margin.Small.value))
val errorMessage = when (postList.type) {
is ErrorType.Default -> stringResource(R.string.reader_tags_feed_loading_error_description)
is ErrorType.NoContent -> stringResource(R.string.reader_tags_feed_no_content_error_description, tagName)
Expand All @@ -415,12 +418,12 @@ private fun PostListError(
},
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(Margin.ExtraLarge.value))
Spacer(modifier = Modifier.height(Margin.Large.value))
Button(
onClick = { postList.onRetryClick() },
onClick = { postList.onRetryClick(tagChip.tag) },
modifier = Modifier
.height(36.dp)
.width(114.dp),
.widthIn(min = 114.dp),
elevation = ButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,52 @@ class ReaderTagsFeedViewModelTest : BaseUnitTest() {
)
}

@Test
fun `should fetch again when onRetryClick is called`() = testCollectingUiStates {
// Given
val tag = ReaderTestUtils.createTag("tag")
val posts = ReaderPostList().apply {
add(ReaderPost())
}
val error = ReaderPostFetchException("error")
whenever(readerPostRepository.fetchNewerPostsForTag(tag)).doSuspendableAnswer {
delay(100)
throw error
}.doSuspendableAnswer {
delay(100)
posts
}

mockMapInitialTagFeedItems()
mockMapLoadingTagFeedItems()
mockMapLoadedTagFeedItems()
mockMapErrorTagFeedItems()

viewModel.onTagsChanged(listOf(tag))
advanceUntilIdle()
viewModel.onItemEnteredView(getInitialTagFeedItem(tag))
advanceUntilIdle()

assertThat(collectedUiStates.last()).isEqualTo(
ReaderTagsFeedViewModel.UiState.Loaded(
data = listOf(getErrorTagFeedItem(tag))
)
)

// When
viewModel.onRetryClick(tag)
advanceUntilIdle()
viewModel.onItemEnteredView(getInitialTagFeedItem(tag))
advanceUntilIdle()

// Then
assertThat(collectedUiStates).contains(
ReaderTagsFeedViewModel.UiState.Loaded(
data = listOf(getLoadedTagFeedItem(tag))
)
)
}

private fun mockMapInitialTagFeedItems() {
whenever(readerTagsFeedUiStateMapper.mapInitialPostsUiState(any(), any(), any(), any(), any(), any()))
.thenAnswer {
Expand Down Expand Up @@ -704,9 +750,7 @@ class ReaderTagsFeedViewModelTest : BaseUnitTest() {

private fun getErrorTagFeedItem(tag: ReaderTag) = ReaderTagsFeedViewModel.TagFeedItem(
ReaderTagsFeedViewModel.TagChip(tag, {}, {}),
ReaderTagsFeedViewModel.PostList.Error(
ReaderTagsFeedViewModel.ErrorType.Default, {}
),
ReaderTagsFeedViewModel.PostList.Error(ReaderTagsFeedViewModel.ErrorType.Default, {}),
)

private fun testCollectingUiStates(block: suspend TestScope.() -> Unit) = test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ class ReaderTagsFeedUiStateMapperTest : BaseUnitTest() {
val errorType = ReaderTagsFeedViewModel.ErrorType.Default
val onTagChipClick: (ReaderTag) -> Unit = {}
val onMoreFromTagClick: (ReaderTag) -> Unit = {}
val onRetryClick = {}
val onRetryClick: (ReaderTag) -> Unit = {}
val onItemEnteredView: (ReaderTagsFeedViewModel.TagFeedItem) -> Unit = {}
// When
val actual = classToTest.mapErrorTagFeedItem(
Expand Down

0 comments on commit f5bf3b1

Please sign in to comment.