Skip to content

Commit

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

[Reader] Implement tags feed card actions (part 1)
  • Loading branch information
RenanLukas committed May 7, 2024
2 parents 6b45cfb + 848dc0a commit 5e7c436
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 219 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@ import dagger.hilt.android.AndroidEntryPoint
import org.wordpress.android.R
import org.wordpress.android.databinding.ReaderTagFeedFragmentLayoutBinding
import org.wordpress.android.models.ReaderTag
import org.wordpress.android.ui.ActivityLauncher
import org.wordpress.android.ui.ViewPagerFragment
import org.wordpress.android.ui.compose.theme.AppThemeWithoutBackground
import org.wordpress.android.ui.main.WPMainActivity
import org.wordpress.android.ui.reader.comments.ThreadedCommentsActionSource
import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents
import org.wordpress.android.ui.reader.subfilter.SubFilterViewModel
import org.wordpress.android.ui.reader.subfilter.SubFilterViewModelProvider
import org.wordpress.android.ui.reader.subfilter.SubfilterListItem
import org.wordpress.android.ui.reader.tracker.ReaderTracker
import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper
import org.wordpress.android.ui.reader.viewmodels.tagsfeed.ReaderTagsFeedViewModel
import org.wordpress.android.ui.reader.viewmodels.tagsfeed.ReaderTagsFeedViewModel.ActionEvent
import org.wordpress.android.ui.reader.views.compose.tagsfeed.ReaderTagsFeed
import org.wordpress.android.util.extensions.getSerializableCompat
import org.wordpress.android.viewmodel.observeEvent
import javax.inject.Inject

/**
Expand All @@ -48,6 +54,12 @@ class ReaderTagsFeedFragment : ViewPagerFragment(R.layout.reader_tag_feed_fragme

private val viewModel: ReaderTagsFeedViewModel by viewModels()

@Inject
lateinit var readerUtilsWrapper: ReaderUtilsWrapper

@Inject
lateinit var readerTracker: ReaderTracker

// binding
private lateinit var binding: ReaderTagFeedFragmentLayoutBinding

Expand All @@ -61,9 +73,9 @@ class ReaderTagsFeedFragment : ViewPagerFragment(R.layout.reader_tag_feed_fragme
ReaderTagsFeed(uiState)
}
}

observeSubFilterViewModel(savedInstanceState)
observeActionEvents()
observeNavigationEvents()
}

private fun observeSubFilterViewModel(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -160,6 +172,105 @@ class ReaderTagsFeedFragment : ViewPagerFragment(R.layout.reader_tag_feed_fragme
.alpha(0f)
}

@Suppress("LongMethod")
private fun observeNavigationEvents() {
viewModel.navigationEvents.observeEvent(viewLifecycleOwner) { event ->
when (event) {
is ReaderNavigationEvents.ShowPostDetail -> ReaderActivityLauncher.showReaderPostDetail(
context,
event.post.blogId,
event.post.postId
)

is ReaderNavigationEvents.SharePost -> ReaderActivityLauncher.sharePost(context, event.post)
is ReaderNavigationEvents.OpenPost -> ReaderActivityLauncher.openPost(context, event.post)
is ReaderNavigationEvents.ShowReaderComments -> ReaderActivityLauncher.showReaderComments(
context,
event.blogId,
event.postId,
ThreadedCommentsActionSource.READER_POST_CARD.sourceDescription
)

is ReaderNavigationEvents.ShowNoSitesToReblog -> ReaderActivityLauncher.showNoSiteToReblog(activity)
is ReaderNavigationEvents.ShowSitePickerForResult -> ActivityLauncher.showSitePickerForResult(
this@ReaderTagsFeedFragment,
event.preselectedSite,
event.mode
)

is ReaderNavigationEvents.OpenEditorForReblog -> ActivityLauncher.openEditorForReblog(
activity,
event.site,
event.post,
event.source
)

is ReaderNavigationEvents.ShowBookmarkedTab -> ActivityLauncher.viewSavedPostsListInReader(activity)
is ReaderNavigationEvents.ShowBookmarkedSavedOnlyLocallyDialog -> {
showBookmarkSavedLocallyDialog(event)
}
is ReaderNavigationEvents.ShowPostsByTag -> ReaderActivityLauncher.showReaderTagPreview(
context,
event.tag,
ReaderTracker.SOURCE_DISCOVER,
readerTracker
)

is ReaderNavigationEvents.ShowVideoViewer -> ReaderActivityLauncher.showReaderVideoViewer(
context,
event.videoUrl
)

is ReaderNavigationEvents.ShowBlogPreview -> ReaderActivityLauncher.showReaderBlogOrFeedPreview(
context,
event.siteId,
event.feedId,
event.isFollowed,
ReaderTracker.SOURCE_DISCOVER,
readerTracker
)

is ReaderNavigationEvents.ShowReportPost -> ReaderActivityLauncher.openUrl(
context,
readerUtilsWrapper.getReportPostUrl(event.url),
ReaderActivityLauncher.OpenUrlType.INTERNAL
)

is ReaderNavigationEvents.ShowReportUser -> ReaderActivityLauncher.openUrl(
context,
readerUtilsWrapper.getReportUserUrl(event.url, event.authorId),
ReaderActivityLauncher.OpenUrlType.INTERNAL
)

is ReaderNavigationEvents.ShowReaderSubs -> ReaderActivityLauncher.showReaderSubs(context)
else -> Unit // Do Nothing
}
}
}

private fun showBookmarkSavedLocallyDialog(
bookmarkDialog: ReaderNavigationEvents.ShowBookmarkedSavedOnlyLocallyDialog
) {
bookmarkDialog.buttonLabel
// if (bookmarksSavedLocallyDialog == null) {
// MaterialAlertDialogBuilder(requireActivity())
// .setTitle(getString(bookmarkDialog.title))
// .setMessage(getString(bookmarkDialog.message))
// .setPositiveButton(getString(bookmarkDialog.buttonLabel)) { _, _ ->
// bookmarkDialog.okButtonAction.invoke()
// }
// .setOnDismissListener {
// bookmarksSavedLocallyDialog = null
// }
// .setCancelable(false)
// .create()
// .let {
// bookmarksSavedLocallyDialog = it
// it.show()
// }
// }
}

override fun getScrollableViewForUniqueIdProvision(): View {
return binding.composeView
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ class ReaderTracker @Inject constructor(
const val SOURCE_SEARCH = "search"
const val SOURCE_SITE_PREVIEW = "site_preview"
const val SOURCE_TAG_PREVIEW = "tag_preview"
const val SOURCE_TAGS_FEED = "tags_feed"
const val SOURCE_POST_DETAIL = "post_detail"
const val SOURCE_POST_DETAIL_TOOLBAR = "post_detail_toolbar"
const val SOURCE_POST_DETAIL_COMMENT_SNIPPET = "post_detail_comment_snippet"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class ReaderTagsFeedUiStateMapper @Inject constructor(
tag: ReaderTag,
posts: ReaderPostList,
onTagClick: (ReaderTag) -> Unit,
onSiteClick: () -> Unit,
onPostImageClick: () -> Unit,
onSiteClick: (TagsFeedPostItem) -> Unit,
onPostCardClick: (TagsFeedPostItem) -> Unit,
onPostLikeClick: () -> Unit,
onPostMoreMenuClick: () -> Unit,
) = ReaderTagsFeedViewModel.TagFeedItem(
Expand All @@ -35,15 +35,17 @@ class ReaderTagsFeedUiStateMapper @Inject constructor(
postTitle = it.title,
postExcerpt = it.excerpt,
postImageUrl = it.blogImageUrl,
postNumberOfLikesText = readerUtilsWrapper.getShortLikeLabelText(
postNumberOfLikesText = if (it.numLikes > 0) readerUtilsWrapper.getShortLikeLabelText(
numLikes = it.numLikes
),
postNumberOfCommentsText = readerUtilsWrapper.getShortCommentLabelText(
) else "",
postNumberOfCommentsText = if (it.numReplies > 0) readerUtilsWrapper.getShortCommentLabelText(
numComments = it.numReplies
),
) else "",
isPostLiked = it.isLikedByCurrentUser,
postId = it.postId,
blogId = it.blogId,
onSiteClick = onSiteClick,
onPostImageClick = onPostImageClick,
onPostCardClick = onPostCardClick,
onPostLikeClick = onPostLikeClick,
onPostMoreMenuClick = onPostMoreMenuClick,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package org.wordpress.android.ui.reader.viewmodels.tagsfeed

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import org.wordpress.android.datasets.wrappers.ReaderPostTableWrapper
import org.wordpress.android.models.ReaderPost
import org.wordpress.android.models.ReaderTag
import org.wordpress.android.modules.BG_THREAD
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.tracker.ReaderTracker
import org.wordpress.android.ui.reader.views.compose.tagsfeed.TagsFeedPostItem
import org.wordpress.android.viewmodel.Event
import org.wordpress.android.viewmodel.ScopedViewModel
import org.wordpress.android.viewmodel.SingleLiveEvent
import javax.inject.Inject
Expand All @@ -21,13 +29,20 @@ class ReaderTagsFeedViewModel @Inject constructor(
@Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher,
private val readerPostRepository: ReaderPostRepository,
private val readerTagsFeedUiStateMapper: ReaderTagsFeedUiStateMapper,
private val readerPostCardActionsHandler: ReaderPostCardActionsHandler,
private val readerPostTableWrapper: ReaderPostTableWrapper,
) : ScopedViewModel(bgDispatcher) {
private val _uiStateFlow: MutableStateFlow<UiState> = MutableStateFlow(UiState.Initial)
val uiStateFlow: StateFlow<UiState> = _uiStateFlow

private val _actionEvents = SingleLiveEvent<ActionEvent>()
val actionEvents: LiveData<ActionEvent> = _actionEvents

private val _navigationEvents = MediatorLiveData<Event<ReaderNavigationEvents>>()
val navigationEvents: LiveData<Event<ReaderNavigationEvents>> = _navigationEvents

private var hasInitialized = false

/**
* Fetch multiple tag posts in parallel. Each tag load causes a new state to be emitted, so multiple emissions of
* [uiStateFlow] are expected when calling this method for each tag, since each can go through the following
Expand All @@ -45,6 +60,12 @@ class ReaderTagsFeedViewModel @Inject constructor(
_uiStateFlow.value = UiState.Empty(::onOpenTagsListClick)
return
}

if (!hasInitialized) {
hasInitialized = true
initNavigationEvents()
}

// Initially add all tags to the list with the posts loading UI
_uiStateFlow.update {
readerTagsFeedUiStateMapper.mapLoadingPostsUiState(tags, ::onTagClick)
Expand All @@ -57,6 +78,17 @@ class ReaderTagsFeedViewModel @Inject constructor(
}
}

private fun initNavigationEvents() {
_navigationEvents.addSource(readerPostCardActionsHandler.navigationEvents) { event ->
// TODO reblog supported in this screen? See ReaderPostDetailViewModel and ReaderDiscoverViewModel
// val target = event.peekContent()
// if (target is ReaderNavigationEvents.ShowSitePickerForResult) {
// pendingReblogPost = target.post
// }
_navigationEvents.value = event
}
}

/**
* Fetch posts for a single tag. This method will emit a new state to [uiStateFlow] for different [UiState]s:
* [UiState.Initial], [UiState.Loaded], [UiState.Loading], [UiState.Empty], but only for the tag being fetched.
Expand All @@ -83,7 +115,7 @@ class ReaderTagsFeedViewModel @Inject constructor(
posts = posts,
onTagClick = ::onTagClick,
onSiteClick = ::onSiteClick,
onPostImageClick = ::onPostImageClick,
onPostCardClick = ::onPostCardClick,
onPostLikeClick = ::onPostLikeClick,
onPostMoreMenuClick = ::onPostMoreMenuClick,
)
Expand Down Expand Up @@ -127,20 +159,35 @@ class ReaderTagsFeedViewModel @Inject constructor(
// TODO
}

private fun onTagClick(readerTag: ReaderTag) {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun onTagClick(readerTag: ReaderTag) {
_actionEvents.value = ActionEvent.OpenTagPostsFeed(readerTag)
}

private fun onRetryClick() {
// TODO
}

private fun onSiteClick() {
// TODO
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun onSiteClick(postItem: TagsFeedPostItem) {
launch {
findPost(postItem.postId, postItem.blogId)?.let {
_navigationEvents.postValue(
Event(ReaderNavigationEvents.ShowBlogPreview(it.blogId, it.feedId, it.isFollowedByCurrentUser))
)
}
}
}

private fun onPostImageClick() {
// TODO
private fun onPostCardClick(postItem: TagsFeedPostItem) {
launch {
findPost(postItem.postId, postItem.blogId)?.let {
readerPostCardActionsHandler.handleOnItemClicked(
it,
ReaderTracker.SOURCE_TAGS_FEED
)
}
}
}

private fun onPostLikeClick() {
Expand All @@ -151,6 +198,14 @@ class ReaderTagsFeedViewModel @Inject constructor(
// TODO
}

private fun findPost(postId: Long, blogId: Long): ReaderPost? {
return readerPostTableWrapper.getBlogPost(
blogId = blogId,
postId = postId,
excludeTextColumn = true,
)
}

sealed class ActionEvent {
data class OpenTagPostsFeed(val readerTag: ReaderTag) : ActionEvent()
}
Expand Down
Loading

0 comments on commit 5e7c436

Please sign in to comment.