Skip to content

Commit

Permalink
NT-2025: Fix duplicated comments (#1280)
Browse files Browse the repository at this point in the history
Co-authored-by: hadia <hadiamohamed.iti@gmail.com>
Co-authored-by: Sunday Onoriode <sunday-onoriode@Sunday-Onoriode-MacBook-Pro.local>
Co-authored-by: Sunday Onoriode <sunday.okpoluaefe@gmail.com>
  • Loading branch information
4 people committed Jun 10, 2021
1 parent 4b8e22f commit 9aed34c
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 70 deletions.
16 changes: 16 additions & 0 deletions app/src/main/java/com/kickstarter/mock/factories/CommentFactory.kt
Expand Up @@ -9,6 +9,22 @@ import org.joda.time.DateTime
class CommentFactory {

companion object {

// - Comment created on the app that has not yet being send to the backend
fun commentToPostWithUser(user: User): Comment {
return Comment.builder()
.id(-1)
.author(user)
.body("Some text here")
.parentId(1)
.repliesCount(0)
.cursor("")
.authorBadges(listOf())
.deleted(false)
.createdAt(DateTime.now())
.build()
}

fun comment(
avatar: Avatar = AvatarFactory.avatar(),
name: String = "joe",
Expand Down
Expand Up @@ -92,11 +92,6 @@ class CommentCardViewHolder(
.compose(Transformers.observeForUI())
.subscribe { this.delegate.onFlagButtonClicked(it) }

this.vm.outputs.newCommentBind()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe { vm.inputs.postNewComment(it) }

binding.commentsCardView.setCommentCardClickedListener(object : OnCommentCardClickedListener {
override fun onRetryViewClicked(view: View) {
vm.inputs.onRetryViewClicked()
Expand Down
Expand Up @@ -5,7 +5,7 @@ import com.kickstarter.libs.ActivityViewModel
import com.kickstarter.libs.Environment
import com.kickstarter.libs.ExperimentsClientType
import com.kickstarter.libs.models.OptimizelyFeature
import com.kickstarter.libs.rx.transformers.Transformers
import com.kickstarter.libs.rx.transformers.Transformers.combineLatestPair
import com.kickstarter.libs.rx.transformers.Transformers.takeWhen
import com.kickstarter.libs.utils.ObjectUtils
import com.kickstarter.libs.utils.ProjectUtils
Expand Down Expand Up @@ -42,8 +42,6 @@ interface CommentsViewHolderViewModel {

/** Configure the view model with the [Comment]. */
fun configureWith(commentCardData: CommentCardData)

fun postNewComment(commentCardData: CommentCardData)
}

interface Outputs {
Expand Down Expand Up @@ -85,9 +83,6 @@ interface CommentsViewHolderViewModel {

/** Emits the current [OptimizelyFeature.Key.COMMENT_ENABLE_THREADS] status to the CommentCard UI*/
fun isCommentEnableThreads(): Observable<Boolean>

/** Emits the new bind comment to ui */
fun newCommentBind(): Observable<CommentCardData>
}

class ViewModel(environment: Environment) : ActivityViewModel<CommentCardViewHolder>(environment), Inputs, Outputs {
Expand All @@ -110,43 +105,72 @@ interface CommentsViewHolderViewModel {
private val replyToComment = PublishSubject.create<Comment>()
private val flagComment = PublishSubject.create<Comment>()
private val viewCommentReplies = PublishSubject.create<Comment>()
private val postNewComment = BehaviorSubject.create<CommentCardData>()
private val newCommentBind = BehaviorSubject.create<CommentCardData>()
private val isCommentEnableThreads = PublishSubject.create<Boolean>()

val inputs: Inputs = this
val outputs: Outputs = this

private val optimizely: ExperimentsClientType = environment.optimizely()
private val apolloClient: ApolloClientType = environment.apolloClient()
private val currentUser = environment.currentUser()

init {
val updatedCommentedCard = this.commentInput

val comment = this.commentInput
.map { it.comment }
.filter { ObjectUtils.isNotNull(it) }
.map { requireNotNull(it) }
configureCommentCardWithComment(comment)

val commentCardStatus = this.commentInput
.filter { ObjectUtils.isNotNull(it) }
.map {
val commentCardState = cardStatus(it)
it.toBuilder().commentCardState(commentCardState?.commentCardStatus ?: 0).build()
}
handleStatus(commentCardStatus)

updatedCommentedCard
// - CommentData will hold the information for posting a new comment if needed
val commentData = this.commentInput
.distinctUntilChanged()
.compose(combineLatestPair(currentUser.loggedInUser()))
.filter { shouldCommentBePosted(it) }
.map {
Pair(requireNotNull(it.first.comment?.body()), requireNotNull(it.first.project))
}
postComment(commentData)
}

/**
* Handles the configuration and behaviour for the comment card
* @param comment the comment observable
*/
private fun handleStatus(commentCardStatus: Observable<CommentCardData>) {
commentCardStatus
.compose(bindToLifecycle())
.subscribe {
this.commentCardStatus.onNext(cardStatus(it))
}

updatedCommentedCard
.compose(Transformers.combineLatestPair(environment.currentUser().observable()))
commentCardStatus
.compose(combineLatestPair(currentUser.observable()))
.compose(bindToLifecycle())
.subscribe {
this.isReplyButtonVisible.onNext(
shouldReplyButtonBeVisible(it.first, it.second, optimizely.isFeatureEnabled(OptimizelyFeature.Key.COMMENT_ENABLE_THREADS))
shouldReplyButtonBeVisible(
it.first,
it.second,
optimizely.isFeatureEnabled(OptimizelyFeature.Key.COMMENT_ENABLE_THREADS)
)
)
}
}

val comment = this.commentInput
.map { it.comment }
.filter { ObjectUtils.isNotNull(it) }
.map { requireNotNull(it) }

/**
* Handles the configuration and behaviour for the comment card
* @param comment the comment observable
*/
private fun configureCommentCardWithComment(comment: Observable<Comment>) {
comment
.map { it.repliesCount() }
.compose(bindToLifecycle())
Expand Down Expand Up @@ -199,48 +223,72 @@ interface CommentsViewHolderViewModel {
this.commentCardStatus.onNext(CommentCardStatus.RE_TRYING_TO_POST)
}

val commentData = this.commentInput.map {
Pair(requireNotNull(it.comment?.body()), requireNotNull(it.project))
}
comment
.compose(takeWhen(this.onFlagButtonClicked))
.compose(bindToLifecycle())
.subscribe(this.flagComment)
}

Observable
.combineLatest(postNewComment, commentData) { _, commentData ->
return@combineLatest executePostCommentMutation(commentData)
}
/**
* Handles the logic for posting comments (new ones, and the retry attempts)
* @param commentData will emmit only in case we need to post a new comment
*/
private fun postComment(commentData: Observable<Pair<String, Project>>) {
commentData
.map { executePostCommentMutation(it) }
.switchMap {
it
}.subscribe {
}
.compose(bindToLifecycle())
.subscribe {
this.commentCardStatus.onNext(CommentCardStatus.COMMENT_FOR_LOGIN_BACKED_USERS)
}

Observable
.combineLatest(onRetryViewClicked, commentData) { _, commentData ->
return@combineLatest executePostCommentMutation(commentData)
.combineLatest(onRetryViewClicked, commentData) { _, newData ->
return@combineLatest executePostCommentMutation(newData)
}
.switchMap {
it
}.doOnNext {
this.commentCardStatus.onNext(CommentCardStatus.POSTING_COMMENT_COMPLETED_SUCCESSFULLY)
}
.delay(3000, TimeUnit.MILLISECONDS)
.compose(bindToLifecycle())
.subscribe {
this.commentCardStatus.onNext(CommentCardStatus.COMMENT_FOR_LOGIN_BACKED_USERS)
}
}

comment
.compose(takeWhen(this.onFlagButtonClicked))
.compose(bindToLifecycle())
.subscribe(this.flagComment)
/**
* In order to decide if a comment needs to be posted we need to check:
* - The comment author is the current user
* - the comment id is negative, this happens when a comment is created on the app, and has not been posted
* to the backed yet, otherwise it should have a id bigger than 0
* - The state we recognize as a comment that needs to be posted is: `TRYING_TO_POST`, no other state is allowed
*/
private fun shouldCommentBePosted(dataCommentAndUser: Pair<CommentCardData, User>): Boolean {
var shouldPost = false
val currentUser = dataCommentAndUser.second
val comment = dataCommentAndUser.first?.comment?.let { return@let it }
val status = dataCommentAndUser.first.commentCardState

comment?.let {
shouldPost = it.id() < 0 && it.author() == currentUser
}

this.commentInput
.filter { ObjectUtils.isNotNull(it) }
.filter { it.commentCardState == CommentCardStatus.TRYING_TO_POST.commentCardStatus }
.compose(bindToLifecycle())
.subscribe {
this.newCommentBind.onNext(it)
}
shouldPost = shouldPost && status == CommentCardStatus.TRYING_TO_POST.commentCardStatus

return shouldPost
}

/**
* Function that will execute the PostCommentMutation
* @param commentData holds the comment body and the project to be posted
* // TODO: for the future threads wi will need to send to the mutation not just the body,
* // TODO: we will need the entire comment plus very important the [parentId]
* @return Observable<Comment>
*/
private fun executePostCommentMutation(commentData: Pair<String, Project>) =
this.apolloClient.createComment(
PostCommentData(
Expand Down Expand Up @@ -329,9 +377,5 @@ interface CommentsViewHolderViewModel {
override fun viewCommentReplies(): Observable<Comment> = this.viewCommentReplies

override fun isCommentEnableThreads(): Observable<Boolean> = this.isCommentEnableThreads

override fun newCommentBind(): Observable<CommentCardData> = this.newCommentBind

override fun postNewComment(commentCardData: CommentCardData) = postNewComment.onNext(commentCardData)
}
}

0 comments on commit 9aed34c

Please sign in to comment.