Skip to content

Commit

Permalink
Merge pull request #20916 from wordpress-mobile/issue/comment-details…
Browse files Browse the repository at this point in the history
…-approve-moderation

Comment details approve moderation redesign
  • Loading branch information
jarvislin authored May 31, 2024
2 parents e3c7f16 + 0b495ec commit 4f00ccc
Show file tree
Hide file tree
Showing 14 changed files with 553 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public abstract class CommentDetailFragment extends ViewPagerFragment implements
@Nullable private OnPostClickListener mOnPostClickListener;
@Nullable private OnCommentActionListener mOnCommentActionListener;
@Nullable private OnNoteCommentActionListener mOnNoteCommentActionListener;
@Nullable private CommentSource mCommentSource; // this will be non-null when onCreate()
@NonNull protected CommentSource mCommentSource; // this will be non-null when onCreate()

/*
* these determine which actions (moderation, replying, marking as spam) to enable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@file:Suppress("DEPRECATION")

package org.wordpress.android.ui.comments

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import org.wordpress.android.fluxc.generated.CommentActionBuilder
import org.wordpress.android.fluxc.model.CommentModel
import org.wordpress.android.fluxc.model.CommentStatus
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.model.comments.CommentsMapper
import org.wordpress.android.fluxc.store.CommentStore
import org.wordpress.android.fluxc.store.CommentStore.RemoteLikeCommentPayload
import org.wordpress.android.fluxc.store.CommentsStore
import org.wordpress.android.models.Note
import org.wordpress.android.modules.BG_THREAD
import org.wordpress.android.ui.comments.unified.CommentsStoreAdapter
import org.wordpress.android.ui.notifications.NotificationEvents.OnNoteCommentLikeChanged
import org.wordpress.android.util.EventBusWrapper
import org.wordpress.android.viewmodel.ScopedViewModel
import javax.inject.Inject
import javax.inject.Named

@HiltViewModel
class CommentDetailViewModel @Inject constructor(
@Named(BG_THREAD) bgDispatcher: CoroutineDispatcher,
private val commentsStore: CommentsStore,
private val commentsStoreAdapter: CommentsStoreAdapter,
private val eventBusWrapper: EventBusWrapper,
private val commentsMapper: CommentsMapper
) : ScopedViewModel(bgDispatcher) {
private val _updatedComment = MutableLiveData<CommentModel>()
val updatedComment: LiveData<CommentModel> = _updatedComment

/**
* Like or unlike a comment
* @param comment the comment to like or unlike
* @param site the site the comment belongs to
* @param note the note the comment belongs to, non-null if the comment is from a notification
*/
fun likeComment(comment: CommentModel, site: SiteModel, note: Note? = null) = launch {
val liked = comment.iLike.not()
comment.apply { iLike = liked }
.let { _updatedComment.postValue(it) }

commentsStoreAdapter.dispatch(
CommentActionBuilder.newLikeCommentAction(
RemoteLikeCommentPayload(site, comment, liked)
)
)

note?.let {
eventBusWrapper.postSticky(OnNoteCommentLikeChanged(note, liked))
}
}

/**
* Dispatch a moderation action to the server, it does not include [CommentStatus.DELETED] status
*/
fun dispatchModerationAction(site: SiteModel, comment: CommentModel, status: CommentStatus) {
commentsStoreAdapter.dispatch(
CommentActionBuilder.newPushCommentAction(CommentStore.RemoteCommentPayload(site, comment))
)

comment.apply { this.status = status.toString() }
.let { _updatedComment.postValue(it) }
}

/**
* Fetch the latest comment from the server
* @param site the site the comment belongs to
* @param remoteCommentId the remote ID of the comment to fetch
*/
fun fetchComment(site: SiteModel, remoteCommentId: Long) = launch {
val result = commentsStore.fetchComment(site, remoteCommentId, null)
result.data?.comments?.firstOrNull()?.let {
_updatedComment.postValue(commentsMapper.commentEntityToLegacyModel(it))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
package org.wordpress.android.ui.comments

import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.isVisible
import com.google.android.material.R
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.parcelize.Parcelize
import org.wordpress.android.databinding.CommentModerationBinding
import org.wordpress.android.util.extensions.getParcelableCompat

class ModerationBottomSheetDialogFragment : BottomSheetDialogFragment() {
private var binding: CommentModerationBinding? = null
private val state by lazy {
arguments?.getParcelableCompat<CommentState>(KEY_STATE)
?: throw IllegalArgumentException("CommentState not provided")
}

var onApprovedClicked = {}
var onPendingClicked = {}
var onSpamClicked = {}
var onTrashClicked = {}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =
CommentModerationBinding.inflate(inflater, container, false).apply {
Expand All @@ -36,6 +49,34 @@ class ModerationBottomSheetDialogFragment : BottomSheetDialogFragment() {
behavior.peekHeight = metrics.heightPixels
}
}

binding?.setupLayout()
}

private fun CommentModerationBinding.setupLayout() {
// handle visibilities
buttonApprove.isVisible = state.canModerate
buttonPending.isVisible = state.canModerate
buttonSpam.isVisible = state.canMarkAsSpam
buttonTrash.isVisible = state.canTrash

// handle clicks
buttonApprove.setOnClickListener {
onApprovedClicked()
dismiss()
}
buttonPending.setOnClickListener {
onPendingClicked()
dismiss()
}
buttonSpam.setOnClickListener {
onSpamClicked()
dismiss()
}
buttonTrash.setOnClickListener {
onTrashClicked()
dismiss()
}
}

override fun onDestroyView() {
Expand All @@ -45,6 +86,20 @@ class ModerationBottomSheetDialogFragment : BottomSheetDialogFragment() {

companion object {
const val TAG = "ModerationBottomSheetDialogFragment"
fun newInstance() = ModerationBottomSheetDialogFragment()
private const val KEY_STATE = "state"
fun newInstance(state: CommentState) = ModerationBottomSheetDialogFragment()
.apply {
arguments = Bundle().apply { putParcelable(KEY_STATE, state) }
}
}

/**
* For handling the UI state of the comment moderation bottom sheet
*/
@Parcelize
data class CommentState(
val canModerate: Boolean,
val canTrash: Boolean,
val canMarkAsSpam: Boolean,
) : Parcelable
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,45 @@ import android.os.Bundle
import android.view.View
import androidx.core.view.isGone
import org.wordpress.android.R
import org.wordpress.android.analytics.AnalyticsTracker
import org.wordpress.android.analytics.AnalyticsTracker.Stat
import org.wordpress.android.datasets.NotificationsTable
import org.wordpress.android.fluxc.tools.FormattableRangeType
import org.wordpress.android.models.Note
import org.wordpress.android.ui.comments.unified.CommentIdentifier
import org.wordpress.android.ui.comments.unified.CommentSource
import org.wordpress.android.ui.engagement.BottomSheetUiState
import org.wordpress.android.ui.reader.tracker.ReaderTracker.Companion.SOURCE_NOTIF_COMMENT_USER_PROFILE
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.ToastUtils
import java.util.EnumSet

/**
* Used when called from notification list for a comment notification
* [CommentDetailFragment] is too big to be reused
* It'd be better to have multiple fragments for different sources for different purposes
*/
class NotificationCommentDetailFragment : SharedCommentDetailFragment() {
override val enabledActions: EnumSet<Note.EnabledActions>
get() = note.enabledCommentActions

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (savedInstanceState?.getString(KEY_NOTE_ID) != null) {
handleNote(savedInstanceState.getString(KEY_NOTE_ID)!!)
} else {
handleNote(requireArguments().getString(KEY_NOTE_ID)!!)
}

viewModel.fetchComment(site, note.commentId)
}

override fun sendLikeCommentEvent(liked: Boolean) {
super.sendLikeCommentEvent(liked)
// it should also track the notification liked/unliked event
AnalyticsTracker.track(
if (liked) Stat.NOTIFICATION_LIKED else Stat.NOTIFICATION_UNLIKED
)
}

override fun getUserProfileUiState(): BottomSheetUiState.UserProfileUiState {
Expand Down
Loading

0 comments on commit 4f00ccc

Please sign in to comment.