Skip to content

Commit

Permalink
NT-2001:UX – Show pending reply dialog (#1330)
Browse files Browse the repository at this point in the history
* Adding alert dialog

* Handling Alert dialog in comment activity

* Handle Pending comment dialog

* Handle empty title action

* add test case for pull and back

* Changes in bind failed state

* Fix commit

* handling showing dialog on back

* handling showing dialog on back

Co-authored-by: Isabel Martin <arkariang@gmail.com>
  • Loading branch information
hadia and Arkariang committed Jul 20, 2021
1 parent 0f6eea3 commit c5dc291
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 11 deletions.
50 changes: 45 additions & 5 deletions app/src/main/java/com/kickstarter/ui/activities/ThreadActivity.kt
Expand Up @@ -13,6 +13,7 @@ import com.kickstarter.libs.RecyclerViewPaginator
import com.kickstarter.libs.qualifiers.RequiresActivityViewModel
import com.kickstarter.libs.utils.ApplicationUtils
import com.kickstarter.libs.utils.UrlUtils
import com.kickstarter.libs.utils.extensions.showAlertDialog
import com.kickstarter.models.Comment
import com.kickstarter.ui.adapters.RepliesAdapter
import com.kickstarter.ui.adapters.RepliesStatusAdapter
Expand Down Expand Up @@ -71,13 +72,14 @@ class ThreadActivity :
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
linearLayoutManager.stackFromEnd = false
/** bind View more cell if the replies more than 7 or update after refresh initial error state **/
this.repliesStatusAdapter.addViewMoreCell(it.second)
}
.filter { it.first.isNotEmpty() }
.subscribe {
/** bind replies list to adapter as reversed as the layout is reversed **/
this.repliesAdapter.takeData(it.first.reversed())
/** bind View more cell if the replies more than 7 or update after refresh initial error state **/
this.repliesStatusAdapter.addViewMoreCell(it.second)
if (it.first.isNotEmpty()) {
/** bind replies list to adapter as reversed as the layout is reversed **/
this.repliesAdapter.takeData(it.first.reversed())
}
}

viewModel.outputs.shouldShowPaginationErrorUI()
Expand Down Expand Up @@ -167,6 +169,44 @@ class ThreadActivity :
)
)
}

viewModel.outputs.hasPendingComments()
.compose(bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it) handleBackAction() else viewModel.inputs.backPressed()
}

viewModel.outputs.closeThreadActivity()
.compose(bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
closeCommentsActivity()
}
}

private fun handleBackAction() {
this.showAlertDialog(
getString(R.string.Your_comment_wasnt_posted),
getString(R.string.You_will_lose_the_comment),
getString(R.string.cancel),
getString(R.string.leave_page),
false,
positiveAction = {
},
negativeAction = {
viewModel.inputs.backPressed()
}
)
}

override fun back() {
viewModel.inputs.checkIfThereAnyPendingComments()
}

private fun closeCommentsActivity() {
super.back()
this.finishActivity(taskId)
}

fun postReply(comment: String) {
Expand Down
41 changes: 35 additions & 6 deletions app/src/main/java/com/kickstarter/viewmodels/ThreadViewModel.kt
Expand Up @@ -36,6 +36,8 @@ interface ThreadViewModel {
fun onShowGuideLinesLinkClicked()
fun refreshCommentCardInCaseFailedPosted(comment: Comment)
fun refreshCommentCardInCaseSuccessPosted(comment: Comment)
fun checkIfThereAnyPendingComments()
fun backPressed()
}

interface Outputs {
Expand Down Expand Up @@ -64,6 +66,8 @@ interface ThreadViewModel {
fun refresh(): Observable<Void>

fun showCommentGuideLinesLink(): Observable<Void>
fun hasPendingComments(): Observable<Boolean>
fun closeThreadActivity(): Observable<Void>
}

class ViewModel(@NonNull val environment: Environment) : ActivityViewModel<ThreadActivity>(environment), Inputs, Outputs {
Expand All @@ -76,6 +80,8 @@ interface ThreadViewModel {
private val onShowGuideLinesLinkClicked = PublishSubject.create<Void>()
private val failedCommentCardToRefresh = PublishSubject.create<Comment>()
private val successfullyPostedCommentCardToRefresh = PublishSubject.create<Comment>()
private val checkIfThereAnyPendingComments = PublishSubject.create<Void>()
private val backPressed = PublishSubject.create<Void>()

private val rootComment = BehaviorSubject.create<Comment>()
private val focusOnCompose = BehaviorSubject.create<Boolean>()
Expand All @@ -90,6 +96,8 @@ interface ThreadViewModel {
private val displayPaginationError = BehaviorSubject.create<Boolean>()
private val initialLoadCommentsError = BehaviorSubject.create<Boolean>()
private val showGuideLinesLink = BehaviorSubject.create<Void>()
private val hasPendingComments = BehaviorSubject.create<Boolean>()
private val closeThreadActivity = BehaviorSubject.create<Void>()

private val onCommentReplies =
BehaviorSubject.create<Pair<List<CommentCardData>, Boolean>>()
Expand Down Expand Up @@ -228,10 +236,10 @@ interface ThreadViewModel {
}

// - Update internal mutable list with the latest state after failed response
this.failedCommentCardToRefresh
.compose(Transformers.combineLatestPair(this.onCommentReplies))
this.onCommentReplies
.compose(Transformers.combineLatestPair(this.failedCommentCardToRefresh))
.map {
Pair(it.first.updateCommentFailedToPost(it.second.first), it.second.second)
Pair(it.second.updateCommentFailedToPost(it.first.first), it.first.second)
}
.distinctUntilChanged()
.compose(bindToLifecycle())
Expand All @@ -240,16 +248,32 @@ interface ThreadViewModel {
}

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

this.onCommentReplies
.compose(Transformers.takePairWhen(checkIfThereAnyPendingComments))
.compose(bindToLifecycle())
.subscribe { pair ->
this.hasPendingComments.onNext(
pair.first.first.any {
it.commentCardState == CommentCardStatus.TRYING_TO_POST.commentCardStatus ||
it.commentCardState == CommentCardStatus.FAILED_TO_SEND_COMMENT.commentCardStatus
}
)
}

this.backPressed
.compose(bindToLifecycle())
.subscribe { this.closeThreadActivity.onNext(it) }
}

private fun loadCommentListFromProjectOrUpdate(comment: Observable<Comment>) {
Expand Down Expand Up @@ -382,8 +406,13 @@ interface ThreadViewModel {
override fun isFetchingReplies(): Observable<Boolean> = this.isFetchingReplies
override fun loadMoreReplies(): Observable<Void> = this.loadMoreReplies
override fun showCommentGuideLinesLink(): Observable<Void> = showGuideLinesLink
override fun checkIfThereAnyPendingComments() = checkIfThereAnyPendingComments.onNext(null)
override fun backPressed() = backPressed.onNext(null)

override fun shouldShowPaginationErrorUI(): Observable<Boolean> = this.displayPaginationError
override fun initialLoadCommentsError(): Observable<Boolean> = this.initialLoadCommentsError
override fun refresh(): Observable<Void> = this.refresh
override fun hasPendingComments(): Observable<Boolean> = this.hasPendingComments
override fun closeThreadActivity(): Observable<Void> = this.closeThreadActivity
}
}
132 changes: 132 additions & 0 deletions app/src/test/java/com/kickstarter/viewmodels/ThreadViewModelTest.kt
Expand Up @@ -39,6 +39,8 @@ class ThreadViewModelTest : KSRobolectricTestCase() {
private val loadMoreReplies = TestSubscriber<Void>()
private val openCommentGuideLines = TestSubscriber<Void>()
private val refresh = TestSubscriber<Void>()
private val hasPendingComments = TestSubscriber<Boolean>()
private val closeThreadActivity = TestSubscriber<Void>()

private fun setUpEnvironment() {
setUpEnvironment(environment())
Expand Down Expand Up @@ -516,4 +518,134 @@ class ThreadViewModelTest : KSRobolectricTestCase() {
assertTrue(newList[2].commentCardState == commentCardData2.commentCardState)
}
}

@Test
fun backButtonPressed_whenEmits_shouldEmitToCloseActivityStream() {
setUpEnvironment()
vm.outputs.closeThreadActivity().subscribe(closeThreadActivity)

vm.inputs.backPressed()
closeThreadActivity.assertValueCount(1)
}

@Test
fun testReplies_BackWithPendingComment() {
val currentUser = UserFactory.user()
.toBuilder()
.id(1)
.build()

val comment1 = CommentFactory.commentToPostWithUser(currentUser).toBuilder().id(1).body("comment1").build()
val comment2 = CommentFactory.commentToPostWithUser(currentUser).toBuilder().id(2).body("comment2").build()
val newPostedComment = CommentFactory.commentToPostWithUser(currentUser).toBuilder().id(3).body("comment3").build()

val commentEnvelope = CommentEnvelopeFactory.commentsEnvelope().toBuilder()
.comments(listOf(comment1, comment2))
.build()

val testScheduler = TestScheduler()

val env = environment().toBuilder()
.apolloClient(object : MockApolloClient() {
override fun getRepliesForComment(
comment: Comment,
cursor: String?,
pageSize: Int
): Observable<CommentEnvelope> {
return Observable.just(commentEnvelope)
}
})
.currentUser(MockCurrentUser(currentUser))
.scheduler(testScheduler)
.build()

val commentCardData1 = CommentCardData.builder()
.comment(comment1)
.commentCardState(CommentCardStatus.COMMENT_FOR_LOGIN_BACKED_USERS.commentCardStatus)
.build()
val commentCardData2 = CommentCardData.builder()
.comment(comment2)
.commentCardState(CommentCardStatus.COMMENT_FOR_LOGIN_BACKED_USERS.commentCardStatus)
.build()
val commentCardData3 = CommentCardData.builder()
.comment(newPostedComment)
.commentCardState(CommentCardStatus.TRYING_TO_POST.commentCardStatus)
.build()

val commentCardData3Failed = CommentCardData.builder()
.comment(newPostedComment)
.commentCardState(CommentCardStatus.FAILED_TO_SEND_COMMENT.commentCardStatus)
.build()

val commentCardData3Updated = CommentCardData.builder()
.comment(newPostedComment)
.commentCardState(CommentCardStatus.COMMENT_FOR_LOGIN_BACKED_USERS.commentCardStatus)
.build()

val vm = ThreadViewModel.ViewModel(env)
// Start the view model with a backed project and comment.
vm.intent(Intent().putExtra(IntentKey.COMMENT_CARD_DATA, CommentCardDataFactory.commentCardData()))
vm.outputs.onCommentReplies().subscribe(onReplies)

vm.outputs.onCommentReplies().subscribe()
vm.outputs.hasPendingComments().subscribe(hasPendingComments)

onReplies.assertValueCount(1)
vm.outputs.onCommentReplies().take(0).subscribe {
val newList = it.first
assertTrue(newList.size == 2)
assertTrue(newList[0].comment?.body() == commentCardData1.comment?.body())
assertTrue(newList[0].commentCardState == commentCardData1.commentCardState)

assertTrue(newList[1].comment?.body() == commentCardData2.comment?.body())
assertTrue(newList[1].commentCardState == commentCardData2.commentCardState)
}

vm.inputs.checkIfThereAnyPendingComments()

this.hasPendingComments.assertValue(false)
// - New posted comment with status "TRYING_TO_POST"
vm.inputs.insertNewReplyToList(newPostedComment.body(), DateTime.now())
testScheduler.advanceTimeBy(3, TimeUnit.SECONDS)
onReplies.assertValueCount(2)
vm.outputs.onCommentReplies().take(0).subscribe {
val newList = it.first
assertTrue(newList.size == 3)
assertTrue(newList[0].comment?.body() == commentCardData3.comment?.body())
assertTrue(newList[0].commentCardState == commentCardData3.commentCardState)

assertTrue(newList[1].comment?.body() == commentCardData1.comment?.body())
assertTrue(newList[1].commentCardState == commentCardData1.commentCardState)

assertTrue(newList[2].comment?.body() == commentCardData2.comment?.body())
assertTrue(newList[2].commentCardState == commentCardData2.commentCardState)
}

vm.inputs.checkIfThereAnyPendingComments()
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
this.hasPendingComments.assertValues(false, true)

// - Check the status of the newly posted comment
vm.inputs.refreshCommentCardInCaseFailedPosted(newPostedComment)
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)

onReplies.assertValueCount(3)
vm.outputs.onCommentReplies().take(0).subscribe {
val newList = it.first
assertTrue(newList.size == 3)
assertTrue(newList[0].comment?.body() == commentCardData3Failed.comment?.body())
assertTrue(newList[0].commentCardState == commentCardData3Failed.commentCardState)

assertTrue(newList[1].comment?.body() == commentCardData1.comment?.body())
assertTrue(newList[1].commentCardState == commentCardData1.commentCardState)

assertTrue(newList[2].comment?.body() == commentCardData2.comment?.body())
assertTrue(newList[2].commentCardState == commentCardData2.commentCardState)
}

// - Check Pull to refresh
vm.inputs.checkIfThereAnyPendingComments()
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
this.hasPendingComments.assertValues(false, true, true)
}
}

0 comments on commit c5dc291

Please sign in to comment.