Skip to content

Commit

Permalink
NT-2000: Retrying posting a failed reply Scrolling bug (#1327)
Browse files Browse the repository at this point in the history
* Fix reply bug

* Update failing tests

* More broken tests

* Linter

* Fix scrolling to top issue and remove TODO

* Merge comment and postsuccessful streams

* Fix broken test

* Add scrolling

* Update success post

* Update test

Co-authored-by: Leigh Douglas <leighcdouglas1@gmail.com>
  • Loading branch information
hadia and leighdouglas committed Jul 16, 2021
1 parent 031e392 commit 1fb18b6
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 29 deletions.
60 changes: 60 additions & 0 deletions app/src/main/java/com/kickstarter/models/extensions/CommentExt.kt
@@ -0,0 +1,60 @@
package com.kickstarter.models.extensions

import com.kickstarter.models.Comment
import com.kickstarter.ui.data.CommentCardData
import com.kickstarter.ui.views.CommentCardStatus

/**
* Update the internal persisted list of comments with the successful response
* from calling the Post Mutation
*/
fun Comment.updateCommentAfterSuccessfulPost(
listOfComments: List<CommentCardData>
): List<CommentCardData> {

val position = listOfComments.indexOfFirst { commentCardData ->
(
commentCardData.commentCardState == CommentCardStatus.TRYING_TO_POST.commentCardStatus ||
commentCardData.commentCardState == CommentCardStatus.FAILED_TO_SEND_COMMENT.commentCardStatus
) &&
commentCardData.comment?.body() == this.body() &&
commentCardData.comment?.author()?.id() == this.author().id()
}

if (position >= 0 && position < listOfComments.size) {
return listOfComments.toMutableList().apply {
this[position] = listOfComments[position].toBuilder()
.commentCardState(CommentCardStatus.COMMENT_FOR_LOGIN_BACKED_USERS.commentCardStatus)
.comment(this@updateCommentAfterSuccessfulPost)
.build()
}
}

return listOfComments
}

/**
* Update the internal persisted list of comments with the failed response
* from calling the Post Mutation
*/
fun Comment.updateCommentFailedToPost(
listOfComments: List<CommentCardData>
): List<CommentCardData> {

val position = listOfComments.indexOfFirst { commentCardData ->
commentCardData.commentCardState == CommentCardStatus.TRYING_TO_POST.commentCardStatus &&
commentCardData.comment?.body() == this.body() &&
commentCardData.comment?.author()?.id() == this.author().id()
}

if (position >= 0 && position < listOfComments.size) {
return listOfComments.toMutableList().apply {
this[position] = listOfComments[position].toBuilder()
.commentCardState(CommentCardStatus.FAILED_TO_SEND_COMMENT.commentCardStatus)
.comment(this@updateCommentFailedToPost)
.build()
}
}

return listOfComments
}
Expand Up @@ -204,6 +204,9 @@ class CommentsActivity :
viewModel.inputs.refreshComment(comment)
}

override fun onCommentPostedFailed(comment: Comment) {
}

override fun onCommentRepliesClicked(comment: Comment) {
viewModel.inputs.onReplyClicked(comment, false)
}
Expand Down
Expand Up @@ -198,7 +198,12 @@ class ThreadActivity :
override fun onCommentRepliesClicked(comment: Comment) {
}

override fun onCommentPostedFailed(comment: Comment) {
viewModel.inputs.refreshCommentCardInCaseFailedPosted(comment)
}

override fun onCommentPostedSuccessFully(comment: Comment) {
viewModel.inputs.refreshCommentCardInCaseSuccessPosted(comment)
}

override fun loadMoreCallback() {
Expand Down
Expand Up @@ -24,6 +24,7 @@ class CommentCardViewHolder(
fun onCommentGuideLinesClicked(comment: Comment)
fun onCommentRepliesClicked(comment: Comment)
fun onCommentPostedSuccessFully(comment: Comment)
fun onCommentPostedFailed(comment: Comment)
}

private val vm: CommentsViewHolderViewModel.ViewModel = CommentsViewHolderViewModel.ViewModel(environment())
Expand Down Expand Up @@ -106,6 +107,11 @@ class CommentCardViewHolder(
.compose(Transformers.observeForUI())
.subscribe { this.delegate.onCommentPostedSuccessFully(it) }

this.vm.outputs.isFailedToPost()
.compose(bindToLifecycle())
.compose(Transformers.observeForUI())
.subscribe { this.delegate.onCommentPostedFailed(it) }

binding.commentsCardView.setCommentCardClickedListener(object : OnCommentCardClickedListener {
override fun onRetryViewClicked(view: View) {
vm.inputs.onRetryViewClicked()
Expand Down
Expand Up @@ -90,6 +90,9 @@ interface CommentsViewHolderViewModel {

/** Emits when the execution of the post mutation is successful, it will be used to update the main list state for this comment**/
fun isSuccessfullyPosted(): Observable<Comment>

/** Emits when the execution of the post mutation is error, it will be used to update the main list state for this comment**/
fun isFailedToPost(): Observable<Comment>
}

class ViewModel(environment: Environment) :
Expand All @@ -116,6 +119,7 @@ interface CommentsViewHolderViewModel {
private val isCommentEnableThreads = PublishSubject.create<Boolean>()
private val internalError = BehaviorSubject.create<Throwable>()
private val postedSuccessfully = BehaviorSubject.create<Comment>()
private val failedToPosted = BehaviorSubject.create<Comment>()

private val isCommentReply = BehaviorSubject.create<Void>()

Expand Down Expand Up @@ -155,10 +159,12 @@ interface CommentsViewHolderViewModel {
postComment(commentData, internalError, environment)

this.internalError
.compose(combineLatestPair(commentData))
.compose(bindToLifecycle())
.delay(1, TimeUnit.SECONDS, environment.scheduler())
.subscribe {
this.commentCardStatus.onNext(CommentCardStatus.FAILED_TO_SEND_COMMENT)
this.failedToPosted.onNext(it.second.first.comment)
}
}

Expand Down Expand Up @@ -335,7 +341,7 @@ interface CommentsViewHolderViewModel {
shouldPost = it.id() < 0 && it.author() == currentUser
}

shouldPost = shouldPost && status == CommentCardStatus.TRYING_TO_POST.commentCardStatus
shouldPost = shouldPost && (status == CommentCardStatus.TRYING_TO_POST.commentCardStatus || status == CommentCardStatus.FAILED_TO_SEND_COMMENT.commentCardStatus)

return shouldPost
}
Expand Down Expand Up @@ -438,5 +444,7 @@ interface CommentsViewHolderViewModel {
override fun isCommentReply(): Observable<Void> = this.isCommentReply

override fun isSuccessfullyPosted(): Observable<Comment> = this.postedSuccessfully

override fun isFailedToPost(): Observable<Comment> = this.failedToPosted
}
}
30 changes: 2 additions & 28 deletions app/src/main/java/com/kickstarter/viewmodels/CommentsViewModel.kt
Expand Up @@ -14,6 +14,7 @@ import com.kickstarter.models.Comment
import com.kickstarter.models.Project
import com.kickstarter.models.Update
import com.kickstarter.models.User
import com.kickstarter.models.extensions.updateCommentAfterSuccessfulPost
import com.kickstarter.services.ApiClientType
import com.kickstarter.services.ApolloClientType
import com.kickstarter.services.apiresponses.commentresponse.CommentEnvelope
Expand Down Expand Up @@ -243,7 +244,7 @@ interface CommentsViewModel {
this.commentToRefresh
.compose(combineLatestPair(this.commentsList))
.map {
updateCommentAfterSuccessfulPost(it.first, it.second)
it.first.updateCommentAfterSuccessfulPost(it.second)
}
.distinctUntilChanged()
.compose(bindToLifecycle())
Expand All @@ -260,33 +261,6 @@ interface CommentsViewModel {
}
}

/**
* Update the internal persisted list of comments with the successful response
* from calling the Post Mutation
*/
private fun updateCommentAfterSuccessfulPost(
commentToUpdate: Comment,
listOfComments: List<CommentCardData>
): List<CommentCardData> {

val position = listOfComments.indexOfFirst { commentCardData ->
commentCardData.commentCardState == CommentCardStatus.TRYING_TO_POST.commentCardStatus &&
commentCardData.comment?.body() == commentToUpdate.body() &&
commentCardData.comment?.author()?.id() == commentToUpdate.author().id()
}

if (position >= 0 && position < listOfComments.size) {
return listOfComments.toMutableList().apply {
this[position] = listOfComments[position].toBuilder()
.commentCardState(CommentCardStatus.COMMENT_FOR_LOGIN_BACKED_USERS.commentCardStatus)
.comment(commentToUpdate)
.build()
}
}

return listOfComments
}

private fun loadCommentListFromProjectOrUpdate(projectOrUpdate: Observable<Pair<Project, Update?>>) {
val startOverWith =
Observable.merge(
Expand Down
35 changes: 35 additions & 0 deletions app/src/main/java/com/kickstarter/viewmodels/ThreadViewModel.kt
Expand Up @@ -13,6 +13,8 @@ import com.kickstarter.libs.utils.extensions.toCommentCardList
import com.kickstarter.models.Comment
import com.kickstarter.models.Project
import com.kickstarter.models.User
import com.kickstarter.models.extensions.updateCommentAfterSuccessfulPost
import com.kickstarter.models.extensions.updateCommentFailedToPost
import com.kickstarter.services.ApolloClientType
import com.kickstarter.services.apiresponses.commentresponse.CommentEnvelope
import com.kickstarter.ui.IntentKey
Expand All @@ -32,6 +34,8 @@ interface ThreadViewModel {
fun reloadRepliesPage()
fun insertNewReplyToList(comment: String, createdAt: DateTime)
fun onShowGuideLinesLinkClicked()
fun refreshCommentCardInCaseFailedPosted(comment: Comment)
fun refreshCommentCardInCaseSuccessPosted(comment: Comment)
}

interface Outputs {
Expand Down Expand Up @@ -70,6 +74,8 @@ interface ThreadViewModel {
private val onLoadingReplies = PublishSubject.create<Void>()
private val insertNewReplyToList = PublishSubject.create<Pair<String, DateTime>>()
private val onShowGuideLinesLinkClicked = PublishSubject.create<Void>()
private val failedCommentCardToRefresh = PublishSubject.create<Comment>()
private val successfullyPostedCommentCardToRefresh = PublishSubject.create<Comment>()

private val rootComment = BehaviorSubject.create<Comment>()
private val focusOnCompose = BehaviorSubject.create<Boolean>()
Expand Down Expand Up @@ -220,6 +226,30 @@ interface ThreadViewModel {
.subscribe {
showGuideLinesLink.onNext(null)
}

// - Update internal mutable list with the latest state after failed response
this.failedCommentCardToRefresh
.compose(Transformers.combineLatestPair(this.onCommentReplies))
.map {
Pair(it.first.updateCommentFailedToPost(it.second.first), it.second.second)
}
.distinctUntilChanged()
.compose(bindToLifecycle())
.subscribe {
this.onCommentReplies.onNext(it)
}

// - Update internal mutable list with the latest state after successful response
this.successfullyPostedCommentCardToRefresh
.compose(Transformers.combineLatestPair(this.onCommentReplies))
.map {
Pair(it.first.updateCommentAfterSuccessfulPost(it.second.first), it.second.second)
}
.distinctUntilChanged()
.compose(bindToLifecycle())
.subscribe {
this.onCommentReplies.onNext(it)
}
}

private fun loadCommentListFromProjectOrUpdate(comment: Observable<Comment>) {
Expand Down Expand Up @@ -327,6 +357,11 @@ interface ThreadViewModel {

override fun nextPage() = nextPage.onNext(null)
override fun reloadRepliesPage() = onLoadingReplies.onNext(null)
override fun refreshCommentCardInCaseFailedPosted(comment: Comment) =
this.failedCommentCardToRefresh.onNext(comment)

override fun refreshCommentCardInCaseSuccessPosted(comment: Comment) =
this.successfullyPostedCommentCardToRefresh.onNext(comment)

override fun getRootComment(): Observable<Comment> = this.rootComment
override fun onCommentReplies(): Observable<Pair<List<CommentCardData>, Boolean>> =
Expand Down

0 comments on commit 1fb18b6

Please sign in to comment.