Skip to content

Commit

Permalink
Merge pull request #20779 from wordpress-mobile/issue/20670-implement…
Browse files Browse the repository at this point in the history
…-tags-feed-actions-part-2

[Reader] Implement tags feed card actions (part 2)
  • Loading branch information
RenanLukas committed May 8, 2024
2 parents 5e7c436 + 2a41ebc commit abbc0c6
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.commitNow
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import org.wordpress.android.R
import org.wordpress.android.databinding.ReaderTagFeedFragmentLayoutBinding
Expand All @@ -29,6 +30,7 @@ import org.wordpress.android.ui.reader.viewmodels.tagsfeed.ReaderTagsFeedViewMod
import org.wordpress.android.ui.reader.views.compose.tagsfeed.ReaderTagsFeed
import org.wordpress.android.util.extensions.getSerializableCompat
import org.wordpress.android.viewmodel.observeEvent
import org.wordpress.android.widgets.WPSnackbar
import javax.inject.Inject

/**
Expand Down Expand Up @@ -76,6 +78,7 @@ class ReaderTagsFeedFragment : ViewPagerFragment(R.layout.reader_tag_feed_fragme
observeSubFilterViewModel(savedInstanceState)
observeActionEvents()
observeNavigationEvents()
observeErrorMessageEvents()
}

private fun observeSubFilterViewModel(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -248,9 +251,18 @@ class ReaderTagsFeedFragment : ViewPagerFragment(R.layout.reader_tag_feed_fragme
}
}

private fun observeErrorMessageEvents() {
viewModel.errorMessageEvents.observeEvent(viewLifecycleOwner) { stringRes ->
activity?.findViewById<View?>(android.R.id.content)?.let { view ->
WPSnackbar.make(view, getString(stringRes), Snackbar.LENGTH_LONG).show()
}
}
}

private fun showBookmarkSavedLocallyDialog(
bookmarkDialog: ReaderNavigationEvents.ShowBookmarkedSavedOnlyLocallyDialog
) {
// TODO show bookmark saved dialog?
bookmarkDialog.buttonLabel
// if (bookmarksSavedLocallyDialog == null) {
// MaterialAlertDialogBuilder(requireActivity())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ReaderTagsFeedUiStateMapper @Inject constructor(
onTagClick: (ReaderTag) -> Unit,
onSiteClick: (TagsFeedPostItem) -> Unit,
onPostCardClick: (TagsFeedPostItem) -> Unit,
onPostLikeClick: () -> Unit,
onPostLikeClick: (TagsFeedPostItem) -> Unit,
onPostMoreMenuClick: () -> Unit,
) = ReaderTagsFeedViewModel.TagFeedItem(
tagChip = ReaderTagsFeedViewModel.TagChip(
Expand All @@ -42,6 +42,7 @@ class ReaderTagsFeedUiStateMapper @Inject constructor(
numComments = it.numReplies
) else "",
isPostLiked = it.isLikedByCurrentUser,
isLikeButtonEnabled = true,
postId = it.postId,
blogId = it.blogId,
onSiteClick = onSiteClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import org.wordpress.android.R
import org.wordpress.android.datasets.wrappers.ReaderPostTableWrapper
import org.wordpress.android.models.ReaderPost
import org.wordpress.android.models.ReaderTag
Expand All @@ -16,6 +17,7 @@ import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents
import org.wordpress.android.ui.reader.discover.ReaderPostCardActionsHandler
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.views.compose.tagsfeed.TagsFeedPostItem
import org.wordpress.android.viewmodel.Event
Expand All @@ -30,6 +32,7 @@ class ReaderTagsFeedViewModel @Inject constructor(
private val readerPostRepository: ReaderPostRepository,
private val readerTagsFeedUiStateMapper: ReaderTagsFeedUiStateMapper,
private val readerPostCardActionsHandler: ReaderPostCardActionsHandler,
private val postLikeUseCase: PostLikeUseCase,
private val readerPostTableWrapper: ReaderPostTableWrapper,
) : ScopedViewModel(bgDispatcher) {
private val _uiStateFlow: MutableStateFlow<UiState> = MutableStateFlow(UiState.Initial)
Expand All @@ -41,6 +44,9 @@ class ReaderTagsFeedViewModel @Inject constructor(
private val _navigationEvents = MediatorLiveData<Event<ReaderNavigationEvents>>()
val navigationEvents: LiveData<Event<ReaderNavigationEvents>> = _navigationEvents

private val _errorMessageEvents = MediatorLiveData<Event<Int>>()
val errorMessageEvents: LiveData<Event<Int>> = _errorMessageEvents

private var hasInitialized = false

/**
Expand Down Expand Up @@ -190,8 +196,125 @@ class ReaderTagsFeedViewModel @Inject constructor(
}
}

private fun onPostLikeClick() {
// TODO
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun onPostLikeClick(postItem: TagsFeedPostItem) {
// Immediately update the UI and disable the like button. If the request fails, show error and revert UI state.
// If the request fails or succeeds, the like button is enabled again.
val isPostLikedUpdated = !postItem.isPostLiked
updatePostItemUI(
postItemToUpdate = postItem,
isPostLikedUpdated = isPostLikedUpdated,
isLikeButtonEnabled = false,
)

// After updating the like button UI to the intended state and disabling the like button, send a request to the
// like endpoint by using the PostLikeUseCase
likePostRemote(postItem, isPostLikedUpdated)
}

private fun updatePostItemUI(
postItemToUpdate: TagsFeedPostItem,
isPostLikedUpdated: Boolean,
isLikeButtonEnabled: Boolean,
) {
val uiState = _uiStateFlow.value as? UiState.Loaded ?: return
// Finds the TagFeedItem associated with the post that should be updated. Return if the item is
// not found.
val tagFeedItemToUpdate = findTagFeedItemToUpdate(uiState, postItemToUpdate) ?: return

// Finds the index associated with the TagFeedItem to be updated found above. Return if the index is not found.
val tagFeedItemToUpdateIndex = uiState.data.indexOf(tagFeedItemToUpdate)
if (tagFeedItemToUpdateIndex != -1 && tagFeedItemToUpdate.postList is PostList.Loaded) {
// Creates a new post list items collection with the post item updated values
val updatedTagFeedItemPostListItems = getPostListWithUpdatedPostItem(
postList = tagFeedItemToUpdate.postList,
postItemToUpdate = postItemToUpdate,
isPostLikedUpdated = isPostLikedUpdated,
isLikeButtonEnabled = isLikeButtonEnabled,
)
// Creates a copy of the TagFeedItem with the updated post list items collection
val updatedTagFeedItem = tagFeedItemToUpdate.copy(
postList = tagFeedItemToUpdate.postList.copy(
items = updatedTagFeedItemPostListItems
)
)
// Creates a new TagFeedItem collection with the updated TagFeedItem
val updatedUiStateData = mutableListOf<TagFeedItem>().apply {
addAll(uiState.data)
this[tagFeedItemToUpdateIndex] = updatedTagFeedItem
}
// Updates the UI state value with the updated TagFeedItem collection
_uiStateFlow.value = uiState.copy(data = updatedUiStateData)
}
}

private fun getPostListWithUpdatedPostItem(
postList: PostList.Loaded,
postItemToUpdate: TagsFeedPostItem,
isPostLikedUpdated: Boolean,
isLikeButtonEnabled: Boolean
) =
postList.items.toMutableList().apply {
val postItemToUpdateIndex =
indexOfFirst {
it.postId == postItemToUpdate.postId && it.blogId == postItemToUpdate.blogId
}
if (postItemToUpdateIndex != -1) {
this[postItemToUpdateIndex] = postItemToUpdate.copy(
isPostLiked = isPostLikedUpdated,
isLikeButtonEnabled = isLikeButtonEnabled,
)
}
}

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
} != null
}

private fun likePostRemote(postItem: TagsFeedPostItem, isPostLikedUpdated: Boolean) {
launch {
findPost(postItem.postId, postItem.blogId)?.let {
postLikeUseCase.perform(it, !it.isLikedByCurrentUser, ReaderTracker.SOURCE_TAGS_FEED).collect {
when (it) {
is PostLikeUseCase.PostLikeState.Success -> {
// Re-enable like button without changing the current post item UI.
updatePostItemUI(
postItemToUpdate = postItem,
isPostLikedUpdated = isPostLikedUpdated,
isLikeButtonEnabled = true,
)
}

is PostLikeUseCase.PostLikeState.Failed.NoNetwork -> {
// Revert post item like button UI to the previous state and re-enable like button.
updatePostItemUI(
postItemToUpdate = postItem,
isPostLikedUpdated = !isPostLikedUpdated,
isLikeButtonEnabled = true,
)
_errorMessageEvents.postValue(Event(R.string.no_network_message))
}

is PostLikeUseCase.PostLikeState.Failed.RequestFailed -> {
// Revert post item like button UI to the previous state and re-enable like button.
updatePostItemUI(
postItemToUpdate = postItem,
isPostLikedUpdated = !isPostLikedUpdated,
isLikeButtonEnabled = true,
)
_errorMessageEvents.postValue(Event(R.string.reader_error_request_failed_title))
}

else -> {
// no-op
}
}
}
}
}
}

private fun onPostMoreMenuClick() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,11 +412,12 @@ data class TagsFeedPostItem(
val postNumberOfLikesText: String,
val postNumberOfCommentsText: String,
val isPostLiked: Boolean,
val isLikeButtonEnabled: Boolean,
val postId: Long,
val blogId: Long,
val onSiteClick: (TagsFeedPostItem) -> Unit,
val onPostCardClick: (TagsFeedPostItem) -> Unit,
val onPostLikeClick: () -> Unit,
val onPostLikeClick: (TagsFeedPostItem) -> Unit,
val onPostMoreMenuClick: () -> Unit,
)

Expand All @@ -436,6 +437,7 @@ fun ReaderTagsFeedLoaded() {
postNumberOfLikesText = "15 likes",
postNumberOfCommentsText = "",
isPostLiked = true,
isLikeButtonEnabled = true,
postId = 123L,
blogId = 123L,
onSiteClick = {},
Expand All @@ -452,6 +454,7 @@ fun ReaderTagsFeedLoaded() {
postNumberOfLikesText = "",
postNumberOfCommentsText = "3 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
postId = 456L,
blogId = 456L,
onSiteClick = {},
Expand All @@ -468,6 +471,7 @@ fun ReaderTagsFeedLoaded() {
postNumberOfLikesText = "123 likes",
postNumberOfCommentsText = "9 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
postId = 789L,
blogId = 789L,
onSiteClick = {},
Expand All @@ -484,6 +488,7 @@ fun ReaderTagsFeedLoaded() {
postNumberOfLikesText = "1234 likes",
postNumberOfCommentsText = "91 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
postId = 1234L,
blogId = 1234L,
onSiteClick = {},
Expand All @@ -500,6 +505,7 @@ fun ReaderTagsFeedLoaded() {
postNumberOfLikesText = "12 likes",
postNumberOfCommentsText = "34 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
postId = 5678L,
blogId = 5678L,
onSiteClick = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ fun ReaderTagsFeedPostListItem(
TextButton(
modifier = Modifier.defaultMinSize(minWidth = 1.dp),
contentPadding = PaddingValues(0.dp),
onClick = { onPostLikeClick() },
onClick = { onPostLikeClick(item) },
enabled = isLikeButtonEnabled,
) {
Icon(
modifier = Modifier.size(24.dp),
Expand All @@ -200,11 +201,19 @@ fun ReaderTagsFeedPostListItem(
R.string.reader_label_like
}
),
tint = secondaryElementColor,
tint = if (isPostLiked) {
androidx.compose.material.MaterialTheme.colors.primary
} else {
secondaryElementColor
},
)
Text(
text = stringResource(R.string.reader_label_like),
color = secondaryElementColor,
color = if (isPostLiked) {
androidx.compose.material.MaterialTheme.colors.primary
} else {
secondaryElementColor
},
)
}
Spacer(Modifier.weight(1f))
Expand Down Expand Up @@ -288,6 +297,7 @@ fun ReaderTagsFeedPostListItemPreview() {
postNumberOfLikesText = "15 likes",
postNumberOfCommentsText = "4 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
blogId = 123L,
postId = 123L,
onSiteClick = {},
Expand Down Expand Up @@ -322,6 +332,7 @@ fun ReaderTagsFeedPostListItemPreview() {
postNumberOfLikesText = "15 likes",
postNumberOfCommentsText = "4 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
blogId = 123L,
postId = 123L,
onSiteClick = {},
Expand All @@ -342,6 +353,7 @@ fun ReaderTagsFeedPostListItemPreview() {
postNumberOfLikesText = "15 likes",
postNumberOfCommentsText = "4 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
blogId = 123L,
postId = 123L,
onSiteClick = {},
Expand All @@ -362,6 +374,7 @@ fun ReaderTagsFeedPostListItemPreview() {
postNumberOfLikesText = "15 likes",
postNumberOfCommentsText = "4 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
blogId = 123L,
postId = 123L,
onSiteClick = {},
Expand All @@ -383,6 +396,7 @@ fun ReaderTagsFeedPostListItemPreview() {
postNumberOfLikesText = "15 likes",
postNumberOfCommentsText = "4 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
blogId = 123L,
postId = 123L,
onSiteClick = {},
Expand All @@ -404,6 +418,7 @@ fun ReaderTagsFeedPostListItemPreview() {
postNumberOfLikesText = "15 likes",
postNumberOfCommentsText = "4 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
blogId = 123L,
postId = 123L,
onSiteClick = {},
Expand Down Expand Up @@ -437,6 +452,7 @@ fun ReaderTagsFeedPostListItemPreview() {
postNumberOfLikesText = "15 likes",
postNumberOfCommentsText = "4 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
blogId = 123L,
postId = 123L,
onSiteClick = {},
Expand Down Expand Up @@ -469,6 +485,7 @@ fun ReaderTagsFeedPostListItemPreview() {
postNumberOfLikesText = "15 likes",
postNumberOfCommentsText = "4 comments",
isPostLiked = true,
isLikeButtonEnabled = true,
blogId = 123L,
postId = 123L,
onSiteClick = {},
Expand Down

0 comments on commit abbc0c6

Please sign in to comment.