diff --git a/app/src/main/java/com/kickstarter/ui/activities/ThreadActivity.kt b/app/src/main/java/com/kickstarter/ui/activities/ThreadActivity.kt index 381a8efe4f..06d4877ad5 100644 --- a/app/src/main/java/com/kickstarter/ui/activities/ThreadActivity.kt +++ b/app/src/main/java/com/kickstarter/ui/activities/ThreadActivity.kt @@ -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 @@ -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() @@ -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) { diff --git a/app/src/main/java/com/kickstarter/viewmodels/ThreadViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/ThreadViewModel.kt index ab48aea7ba..e08ef06e5a 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/ThreadViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/ThreadViewModel.kt @@ -36,6 +36,8 @@ interface ThreadViewModel { fun onShowGuideLinesLinkClicked() fun refreshCommentCardInCaseFailedPosted(comment: Comment) fun refreshCommentCardInCaseSuccessPosted(comment: Comment) + fun checkIfThereAnyPendingComments() + fun backPressed() } interface Outputs { @@ -64,6 +66,8 @@ interface ThreadViewModel { fun refresh(): Observable fun showCommentGuideLinesLink(): Observable + fun hasPendingComments(): Observable + fun closeThreadActivity(): Observable } class ViewModel(@NonNull val environment: Environment) : ActivityViewModel(environment), Inputs, Outputs { @@ -76,6 +80,8 @@ interface ThreadViewModel { private val onShowGuideLinesLinkClicked = PublishSubject.create() private val failedCommentCardToRefresh = PublishSubject.create() private val successfullyPostedCommentCardToRefresh = PublishSubject.create() + private val checkIfThereAnyPendingComments = PublishSubject.create() + private val backPressed = PublishSubject.create() private val rootComment = BehaviorSubject.create() private val focusOnCompose = BehaviorSubject.create() @@ -90,6 +96,8 @@ interface ThreadViewModel { private val displayPaginationError = BehaviorSubject.create() private val initialLoadCommentsError = BehaviorSubject.create() private val showGuideLinesLink = BehaviorSubject.create() + private val hasPendingComments = BehaviorSubject.create() + private val closeThreadActivity = BehaviorSubject.create() private val onCommentReplies = BehaviorSubject.create, Boolean>>() @@ -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()) @@ -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) { @@ -382,8 +406,13 @@ interface ThreadViewModel { override fun isFetchingReplies(): Observable = this.isFetchingReplies override fun loadMoreReplies(): Observable = this.loadMoreReplies override fun showCommentGuideLinesLink(): Observable = showGuideLinesLink + override fun checkIfThereAnyPendingComments() = checkIfThereAnyPendingComments.onNext(null) + override fun backPressed() = backPressed.onNext(null) + override fun shouldShowPaginationErrorUI(): Observable = this.displayPaginationError override fun initialLoadCommentsError(): Observable = this.initialLoadCommentsError override fun refresh(): Observable = this.refresh + override fun hasPendingComments(): Observable = this.hasPendingComments + override fun closeThreadActivity(): Observable = this.closeThreadActivity } } diff --git a/app/src/test/java/com/kickstarter/viewmodels/ThreadViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/ThreadViewModelTest.kt index c04edafecc..c7071ae064 100644 --- a/app/src/test/java/com/kickstarter/viewmodels/ThreadViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/viewmodels/ThreadViewModelTest.kt @@ -39,6 +39,8 @@ class ThreadViewModelTest : KSRobolectricTestCase() { private val loadMoreReplies = TestSubscriber() private val openCommentGuideLines = TestSubscriber() private val refresh = TestSubscriber() + private val hasPendingComments = TestSubscriber() + private val closeThreadActivity = TestSubscriber() private fun setUpEnvironment() { setUpEnvironment(environment()) @@ -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 { + 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) + } }