Skip to content

Commit

Permalink
NT-2003:UX – Show pending comment dialog (#1329)
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
  • Loading branch information
hadia committed Jul 19, 2021
1 parent 68094ac commit 644a907
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 6 deletions.
@@ -1,6 +1,7 @@
@file:JvmName("ContextExt")
package com.kickstarter.libs.utils.extensions

import android.app.AlertDialog
import android.app.Application
import android.content.Context
import com.kickstarter.KSApplication
Expand All @@ -18,3 +19,41 @@ fun Context.registerActivityLifecycleCallbacks(callbacks: Application.ActivityLi
this.registerActivityLifecycleCallbacks(callbacks)
}
}

fun Context.showAlertDialog(
title: String? = "",
message: String? = "",
positiveActionTitle: String? = null,
negativeActionTitle: String? = null,
isCancelable: Boolean = true,
positiveAction: (() -> Unit)? = null,
negativeAction: (() -> Unit)? = null
) {

// setup the alert builder
val builder = AlertDialog.Builder(this).apply {
setTitle(title)
setMessage(message)

// add a button
positiveActionTitle?.let {
setPositiveButton(positiveActionTitle) { dialog, _ ->
dialog.dismiss()
positiveAction?.invoke()
}
}

negativeActionTitle?.let {
setNegativeButton(negativeActionTitle) { dialog, _ ->
dialog.dismiss()
negativeAction?.invoke()
}
}

setCancelable(isCancelable)
}

// create and show the alert dialog
val dialog: AlertDialog = builder.create()
dialog.show()
}
Expand Up @@ -14,6 +14,7 @@ import com.kickstarter.libs.qualifiers.RequiresActivityViewModel
import com.kickstarter.libs.utils.ApplicationUtils
import com.kickstarter.libs.utils.TransitionUtils
import com.kickstarter.libs.utils.UrlUtils
import com.kickstarter.libs.utils.extensions.showAlertDialog
import com.kickstarter.libs.utils.extensions.toVisibility
import com.kickstarter.models.Comment
import com.kickstarter.ui.IntentKey
Expand Down Expand Up @@ -44,7 +45,9 @@ class CommentsActivity :
setContentView(view)
binding.commentsRecyclerView.adapter = adapter

binding.backButton.setOnClickListener { viewModel.inputs.backPressed() }
binding.backButton.setOnClickListener {
viewModel.inputs.checkIfThereAnyPendingComments(true)
}

setupPagination()

Expand Down Expand Up @@ -130,6 +133,47 @@ class CommentsActivity :
.subscribe {
ApplicationUtils.openUrlExternally(this, UrlUtils.appendPath(environment().webEndpoint(), COMMENT_KICKSTARTER_GUIDELINES))
}

viewModel.outputs.hasPendingComments()
.compose(bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it.first) {
handleBackAction(it.second)
} else {
executeActions(it.second)
}
}
}

private fun handleBackAction(isBackAction: Boolean) {
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 = {
if (!isBackAction) {
binding.commentsSwipeRefreshLayout.isRefreshing = false
}
},
negativeAction = {
executeActions(isBackAction)
}
)
}

fun executeActions(isBackAction: Boolean) {
if (!isBackAction) {
viewModel.inputs.refresh()
} else {
viewModel.inputs.backPressed()
}
}

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

private fun closeCommentsActivity() {
Expand All @@ -142,7 +186,10 @@ class CommentsActivity :
recyclerViewPaginator = RecyclerViewPaginator(binding.commentsRecyclerView, { viewModel.inputs.nextPage() }, viewModel.outputs.isFetchingComments())

swipeRefresher = SwipeRefresher(
this, binding.commentsSwipeRefreshLayout, { viewModel.inputs.refresh() }
this, binding.commentsSwipeRefreshLayout,
{
viewModel.inputs.checkIfThereAnyPendingComments(false)
}
) { viewModel.outputs.isFetchingComments() }

viewModel.outputs.isFetchingComments()
Expand Down Expand Up @@ -205,6 +252,7 @@ class CommentsActivity :
}

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

override fun onCommentRepliesClicked(comment: Comment) {
Expand Down
46 changes: 42 additions & 4 deletions app/src/main/java/com/kickstarter/viewmodels/CommentsViewModel.kt
Expand Up @@ -9,12 +9,14 @@ import com.kickstarter.libs.Environment
import com.kickstarter.libs.loadmore.ApolloPaginate
import com.kickstarter.libs.rx.transformers.Transformers
import com.kickstarter.libs.rx.transformers.Transformers.combineLatestPair
import com.kickstarter.libs.rx.transformers.Transformers.takePairWhen
import com.kickstarter.libs.utils.ProjectUtils
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.models.extensions.updateCommentFailedToPost
import com.kickstarter.services.ApiClientType
import com.kickstarter.services.ApolloClientType
import com.kickstarter.services.apiresponses.commentresponse.CommentEnvelope
Expand All @@ -37,9 +39,11 @@ interface CommentsViewModel {
fun insertNewCommentToList(comment: String, createdAt: DateTime)
fun onReplyClicked(comment: Comment, openKeyboard: Boolean)
fun onShowGuideLinesLinkClicked()
fun checkIfThereAnyPendingComments(isBackAction: Boolean)

/** Will be called with the successful response when calling the `postComment` Mutation **/
fun refreshComment(comment: Comment)
fun refreshCommentCardInCaseFailedPosted(comment: Comment)
}

interface Outputs {
Expand All @@ -56,6 +60,7 @@ interface CommentsViewModel {
fun paginateCommentsError(): Observable<Throwable>
fun pullToRefreshError(): Observable<Throwable>
fun startThreadActivity(): Observable<Pair<CommentCardData, Boolean>>
fun hasPendingComments(): Observable<Pair<Boolean, Boolean>>

/** Emits a boolean indicating whether comments are being fetched from the API. */
fun isFetchingComments(): Observable<Boolean>
Expand All @@ -76,6 +81,8 @@ interface CommentsViewModel {
private val nextPage = PublishSubject.create<Void>()
private val onShowGuideLinesLinkClicked = PublishSubject.create<Void>()
private val onReplyClicked = PublishSubject.create<Pair<Comment, Boolean>>()
private val checkIfThereAnyPendingComments = PublishSubject.create<Boolean>()
private val failedCommentCardToRefresh = PublishSubject.create<Comment>()

private val closeCommentsPage = BehaviorSubject.create<Void>()
private val currentUserAvatar = BehaviorSubject.create<String?>()
Expand All @@ -93,6 +100,7 @@ interface CommentsViewModel {
private val displayPaginationError = BehaviorSubject.create<Boolean>()
private val commentToRefresh = PublishSubject.create<Comment>()
private val startThreadActivity = BehaviorSubject.create<Pair<CommentCardData, Boolean>>()
private val hasPendingComments = BehaviorSubject.create<Pair<Boolean, Boolean>>()

// - Error observables to handle the 3 different use cases
private val internalError = BehaviorSubject.create<Throwable>()
Expand Down Expand Up @@ -220,6 +228,21 @@ interface CommentsViewModel {
this.displayPaginationError.onNext(true)
}

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

this.backPressed
.compose(bindToLifecycle())
.subscribe { this.closeCommentsPage.onNext(it) }
Expand All @@ -241,10 +264,10 @@ interface CommentsViewModel {
}

// - Update internal mutable list with the latest state after successful response
this.commentToRefresh
.compose(combineLatestPair(this.commentsList))
this.commentsList
.compose(takePairWhen(this.commentToRefresh))
.map {
it.first.updateCommentAfterSuccessfulPost(it.second)
it.second.updateCommentAfterSuccessfulPost(it.first)
}
.distinctUntilChanged()
.compose(bindToLifecycle())
Expand All @@ -259,6 +282,17 @@ interface CommentsViewModel {
.subscribe {
this.outputCommentList.onNext(it)
}
// - Update internal mutable list with the latest state after failed response
this.commentsList
.compose(takePairWhen(this.failedCommentCardToRefresh))
.map {
it.second.updateCommentFailedToPost(it.first)
}
.distinctUntilChanged()
.compose(bindToLifecycle())
.subscribe {
this.commentsList.onNext(it)
}
}

private fun loadCommentListFromProjectOrUpdate(projectOrUpdate: Observable<Pair<Project, Update?>>) {
Expand Down Expand Up @@ -396,7 +430,9 @@ interface CommentsViewModel {
override fun onShowGuideLinesLinkClicked() = onShowGuideLinesLinkClicked.onNext(null)
override fun refreshComment(comment: Comment) = this.commentToRefresh.onNext(comment)
override fun onReplyClicked(comment: Comment, openKeyboard: Boolean) = onReplyClicked.onNext(Pair(comment, openKeyboard))

override fun checkIfThereAnyPendingComments(isBackAction: Boolean) = checkIfThereAnyPendingComments.onNext(isBackAction)
override fun refreshCommentCardInCaseFailedPosted(comment: Comment) =
this.failedCommentCardToRefresh.onNext(comment)
// - Outputs
override fun closeCommentsPage(): Observable<Void> = closeCommentsPage
override fun currentUserAvatar(): Observable<String?> = currentUserAvatar
Expand All @@ -415,5 +451,7 @@ interface CommentsViewModel {

override fun startThreadActivity(): Observable<Pair<CommentCardData, Boolean>> = this.startThreadActivity
override fun isFetchingComments(): Observable<Boolean> = this.isFetchingComments

override fun hasPendingComments(): Observable<Pair<Boolean, Boolean>> = this.hasPendingComments
}
}
6 changes: 6 additions & 0 deletions app/src/main/res/values/strings.xml
Expand Up @@ -78,4 +78,10 @@
<string name="email_verification_interstitial" formatted="false">Email Verification Interstitial</string>

<string name="estimated_delivery_data" formatted="false">%{title} %{estimated_delivery_data}</string>

<!-- Replies Screen -->
<string name="Your_comment_wasnt_posted">Your comment wasn\'t posted</string>
<string name="You_will_lose_the_comment">You\'ll lose the comment if you leave this page.</string>
<string name="cancel">Cancel</string>
<string name="leave_page">Leave page</string>
</resources>

0 comments on commit 644a907

Please sign in to comment.