Skip to content

Commit

Permalink
NT-2043:UI – Handle URLs in comments and Fix post comment crash (#1316)
Browse files Browse the repository at this point in the history
* Handle url in comments

* Fix replies adapter root commmnt binding crash

* handle adapter click actions

* Reformate code

* Use same cell to show error and view more action

* Refactor root comment in separate layout to solve sections binding issue

* Remove unneeded   displayPaginationError

* Fix crash for first reply

* Fix crash for first reply

* ConcatAdapter and create 3 different adapters

* Fix Scroll issue

* Fix Scroll issue

* Rename variables

* add code comments

* add code comments

* Add test  testCommentsViewModel_openCommentGuidelinesLink
  • Loading branch information
hadia committed Jul 13, 2021
1 parent bc8063e commit 82ada47
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 19 deletions.
19 changes: 16 additions & 3 deletions app/src/main/java/com/kickstarter/ui/activities/ThreadActivity.kt
Expand Up @@ -11,6 +11,8 @@ import com.kickstarter.libs.BaseActivity
import com.kickstarter.libs.KSString
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.models.Comment
import com.kickstarter.ui.adapters.RepliesAdapter
import com.kickstarter.ui.adapters.RepliesStatusAdapter
Expand Down Expand Up @@ -146,6 +148,19 @@ class ThreadActivity :
hideKeyboard()
}
})

viewModel.outputs.showCommentGuideLinesLink()
.compose(bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
ApplicationUtils.openUrlExternally(
this,
UrlUtils.appendPath(
environment().webEndpoint(),
CommentsActivity.COMMENT_KICKSTARTER_GUIDELINES
)
)
}
}

fun postReply(comment: String) {
Expand All @@ -167,19 +182,17 @@ class ThreadActivity :
}

override fun onReplyButtonClicked(comment: Comment) {
TODO("Not yet implemented")
}

override fun onFlagButtonClicked(comment: Comment) {
TODO("Not yet implemented")
}

override fun onCommentGuideLinesClicked(comment: Comment) {
TODO("Not yet implemented")
viewModel.inputs.onShowGuideLinesLinkClicked()
}

override fun onCommentRepliesClicked(comment: Comment) {
TODO("Not yet implemented")
}

override fun onCommentPostedSuccessFully(comment: Comment) {
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/com/kickstarter/ui/extensions/TextViewExt.kt
Expand Up @@ -7,6 +7,7 @@ import android.text.Spanned
import android.text.TextPaint
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.URLSpan
import android.view.View
import android.widget.TextView
import androidx.annotation.ColorRes
Expand Down Expand Up @@ -57,3 +58,20 @@ Parse HTML into a Document. As no base URI is specified, absolute URL detection
fun TextView.parseHtmlTag() {
this.text = Jsoup.parse(this.text.toString()).text()
}

fun TextView.urlSpanWithoutUnderlines() {
val spannable: Spannable = SpannableString(this.text)
spannable.getSpans(0, spannable.length, URLSpan::class.java).forEach { span ->
val start = spannable.getSpanStart(span)
val end = spannable.getSpanEnd(span)
spannable.removeSpan(span)
val newSpan = object : URLSpan(span.url) {
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.isUnderlineText = false
}
}
spannable.setSpan(newSpan, start, end, 0)
}
text = spannable
}
4 changes: 4 additions & 0 deletions app/src/main/java/com/kickstarter/ui/views/CommentCard.kt
@@ -1,6 +1,7 @@
package com.kickstarter.ui.views

import android.content.Context
import android.text.util.Linkify
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
Expand All @@ -17,6 +18,7 @@ import com.kickstarter.libs.utils.extensions.setAllOnClickListener
import com.kickstarter.ui.extensions.loadCircleImage
import com.kickstarter.ui.extensions.makeLinks
import com.kickstarter.ui.extensions.parseHtmlTag
import com.kickstarter.ui.extensions.urlSpanWithoutUnderlines

class CommentCard @JvmOverloads constructor(
context: Context,
Expand Down Expand Up @@ -187,6 +189,8 @@ class CommentCard @JvmOverloads constructor(

fun setCommentBody(comment: String) {
binding.commentBody.text = comment
Linkify.addLinks(binding.commentBody, Linkify.WEB_URLS)
binding.commentBody.urlSpanWithoutUnderlines()
}

fun setFlaggedMessage(message: String) {
Expand Down
57 changes: 41 additions & 16 deletions app/src/main/java/com/kickstarter/viewmodels/ThreadViewModel.kt
Expand Up @@ -31,6 +31,7 @@ interface ThreadViewModel {
fun nextPage()
fun onViewMoreClicked()
fun insertNewReplyToList(comment: String, createdAt: DateTime)
fun onShowGuideLinesLinkClicked()
}

interface Outputs {
Expand All @@ -53,6 +54,8 @@ interface ThreadViewModel {

/** Display the pagination Error Cell **/
fun shouldShowPaginationErrorUI(): Observable<Boolean>

fun showCommentGuideLinesLink(): Observable<Void>
}

class ViewModel(@NonNull val environment: Environment) : ActivityViewModel<ThreadActivity>(environment), Inputs, Outputs {
Expand All @@ -61,21 +64,24 @@ interface ThreadViewModel {

private val nextPage = PublishSubject.create<Void>()
private val onViewMoreClicked = PublishSubject.create<Void>()
private val insertNewReplyToList = PublishSubject.create<Pair<String, DateTime>>()
private val onShowGuideLinesLinkClicked = PublishSubject.create<Void>()

private val rootComment = BehaviorSubject.create<Comment>()
private val focusOnCompose = BehaviorSubject.create<Boolean>()
private val currentUserAvatar = BehaviorSubject.create<String?>()
private val replyComposerStatus = BehaviorSubject.create<CommentComposerStatus>()
private val showReplyComposer = BehaviorSubject.create<Boolean>()
private val scrollToBottom = BehaviorSubject.create<Void>()
private val insertNewReplyToList = PublishSubject.create<Pair<String, DateTime>>()
private val isFetchingReplies = BehaviorSubject.create<Boolean>()
private val hasPreviousElements = BehaviorSubject.create<Boolean>()
private val refresh = PublishSubject.create<Void>()
private val loadMoreReplies = PublishSubject.create<Void>()
private val displayPaginationError = BehaviorSubject.create<Boolean>()
private val showGuideLinesLink = BehaviorSubject.create<Void>()

private val onCommentReplies = BehaviorSubject.create<Pair<List<CommentCardData>, Boolean>>()
private val onCommentReplies =
BehaviorSubject.create<Pair<List<CommentCardData>, Boolean>>()
private var project: Project? = null

val inputs = this
Expand All @@ -99,7 +105,8 @@ interface ThreadViewModel {
.compose(bindToLifecycle())
.subscribe(this.focusOnCompose)

val comment = getCommentCardDataFromIntent().map { it.comment }.map { requireNotNull(it) }
val comment =
getCommentCardDataFromIntent().map { it.comment }.map { requireNotNull(it) }

val project = commentData
.map { it.project }
Expand All @@ -115,16 +122,17 @@ interface ThreadViewModel {

this.insertNewReplyToList
.distinctUntilChanged()
.withLatestFrom(this.currentUser.loggedInUser()) {
comment, user ->
.withLatestFrom(this.currentUser.loggedInUser()) { comment, user ->
Pair(comment, user)
}
.withLatestFrom(commentData) {
reply, parent ->
.withLatestFrom(commentData) { reply, parent ->
Pair(reply, parent)
}
.map {
Pair(it.second, buildReplyBody(Pair(Pair(it.second, it.first.second), it.first.first)))
Pair(
it.second,
buildReplyBody(Pair(Pair(it.second, it.first.second), it.first.first))
)
}
.map {
CommentCardData.builder()
Expand Down Expand Up @@ -189,6 +197,12 @@ interface ThreadViewModel {
.subscribe {
this.displayPaginationError.onNext(true)
}

this.onShowGuideLinesLinkClicked
.compose(bindToLifecycle())
.subscribe {
showGuideLinesLink.onNext(null)
}
}

private fun loadCommentListFromProjectOrUpdate(comment: Observable<Comment>) {
Expand Down Expand Up @@ -255,7 +269,8 @@ interface ThreadViewModel {
}
}

private fun mapListToData(it: CommentEnvelope, project: Project) = it.comments?.toCommentCardList(project)
private fun mapListToData(it: CommentEnvelope, project: Project) =
it.comments?.toCommentCardList(project)

private fun loadWithProjectReplies(
comment: Observable<Comment>,
Expand Down Expand Up @@ -286,7 +301,10 @@ interface ThreadViewModel {
private fun getCommentComposerStatus(projectAndUser: Pair<Project, User?>) =
when {
projectAndUser.second == null -> CommentComposerStatus.GONE
projectAndUser.first.isBacking || ProjectUtils.userIsCreator(projectAndUser.first, projectAndUser.second) -> CommentComposerStatus.ENABLED
projectAndUser.first.isBacking || ProjectUtils.userIsCreator(
projectAndUser.first,
projectAndUser.second
) -> CommentComposerStatus.ENABLED
else -> CommentComposerStatus.DISABLED
}

Expand All @@ -298,19 +316,26 @@ interface ThreadViewModel {
override fun onViewMoreClicked() = onViewMoreClicked.onNext(null)

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

override fun insertNewReplyToList(comment: String, createdAt: DateTime) =
this.insertNewReplyToList.onNext(
Pair(comment, createdAt)
)

override fun onShowGuideLinesLinkClicked() = onShowGuideLinesLinkClicked.onNext(null)

override fun shouldFocusOnCompose(): Observable<Boolean> = this.focusOnCompose
override fun scrollToBottom(): Observable<Void> = this.scrollToBottom

override fun currentUserAvatar(): Observable<String?> = currentUserAvatar
override fun replyComposerStatus(): Observable<CommentComposerStatus> = replyComposerStatus
override fun showReplyComposer(): Observable<Boolean> = showReplyComposer
override fun insertNewReplyToList(comment: String, createdAt: DateTime) = this.insertNewReplyToList.onNext(
Pair(comment, createdAt)
)
override fun isFetchingReplies(): Observable<Boolean> = this.isFetchingReplies
override fun loadMoreReplies(): Observable<Void> = this.loadMoreReplies
override fun shouldShowPaginationErrorUI(): Observable<Boolean> = this.displayPaginationError
override fun shouldShowPaginationErrorUI(): Observable<Boolean> =
this.displayPaginationError

override fun showCommentGuideLinesLink(): Observable<Void> = showGuideLinesLink
}
}
Expand Up @@ -32,6 +32,7 @@ class ThreadViewModelTest : KSRobolectricTestCase() {
private val replyComposerStatus = TestSubscriber<CommentComposerStatus>()
private val showReplyComposer = TestSubscriber<Boolean>()
private val loadMoreReplies = TestSubscriber<Void>()
private val openCommentGuideLines = TestSubscriber<Void>()

private fun setUpEnvironment() {
setUpEnvironment(environment())
Expand Down Expand Up @@ -245,4 +246,18 @@ class ThreadViewModelTest : KSRobolectricTestCase() {

this.loadMoreReplies.assertValueCount(1)
}

@Test
fun testThreadsViewModel_openCommentGuidelinesLink() {
setUpEnvironment()

this.vm.intent(Intent().putExtra(IntentKey.COMMENT_CARD_DATA, CommentCardDataFactory.commentCardData()))

// Start the view model with an update.
vm.outputs.showCommentGuideLinesLink().subscribe(openCommentGuideLines)

// post a comment
vm.inputs.onShowGuideLinesLinkClicked()
openCommentGuideLines.assertValueCount(1)
}
}

0 comments on commit 82ada47

Please sign in to comment.