Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comment details approve moderation redesign #20916

Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
protected CommentSource mCommentSource; // this will be non-null when onCreate()
antonis marked this conversation as resolved.
Show resolved Hide resolved

/*
* 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
Loading