mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-16 20:10:04 +03:00
Timeline: render inline and block code
This commit is contained in:
parent
3517873156
commit
b4ae331086
6 changed files with 97 additions and 95 deletions
|
@ -16,17 +16,15 @@
|
|||
|
||||
package im.vector.riotx.core.platform
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ScrollView
|
||||
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import im.vector.riotx.R
|
||||
|
||||
private const val DEFAULT_MAX_HEIGHT = 200
|
||||
|
||||
class MaxHeightScrollView : ScrollView {
|
||||
class MaxHeightScrollView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
|
||||
: NestedScrollView(context, attrs, defStyle) {
|
||||
|
||||
var maxHeight: Int = 0
|
||||
set(value) {
|
||||
|
@ -34,28 +32,7 @@ class MaxHeightScrollView : ScrollView {
|
|||
requestLayout()
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context) {}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
if (!isInEditMode) {
|
||||
init(context, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
if (!isInEditMode) {
|
||||
init(context, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
|
||||
if (!isInEditMode) {
|
||||
init(context, attrs)
|
||||
}
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
init {
|
||||
if (attrs != null) {
|
||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView)
|
||||
maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.MaxHeightScrollView_maxHeight, DEFAULT_MAX_HEIGHT)
|
||||
|
|
|
@ -480,7 +480,7 @@ class RoomDetailFragment :
|
|||
jumpToReadMarkerView.render(show, readMarkerId)
|
||||
}
|
||||
}
|
||||
recyclerView.setController(timelineEventController)
|
||||
recyclerView.adapter = timelineEventController.adapter
|
||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
if (recyclerView.scrollState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
|
|
|
@ -99,16 +99,8 @@ class MessageItemFactory @Inject constructor(
|
|||
// val all = event.root.toContent()
|
||||
// val ev = all.toModel<Event>()
|
||||
return when (messageContent) {
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
|
||||
informationData,
|
||||
highlight,
|
||||
callback,
|
||||
attributes)
|
||||
is MessageTextContent -> buildTextMessageItem(messageContent,
|
||||
informationData,
|
||||
highlight,
|
||||
callback,
|
||||
attributes)
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
|
@ -231,29 +223,43 @@ class MessageItemFactory @Inject constructor(
|
|||
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
|
||||
}
|
||||
|
||||
private fun buildTextMessageItem(messageContent: MessageTextContent,
|
||||
private fun buildItemForTextContent(messageContent: MessageTextContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
|
||||
|
||||
val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
|
||||
return if (isFormatted) {
|
||||
val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document
|
||||
val codeVisitor = CodeVisitor()
|
||||
codeVisitor.visit(localFormattedBody)
|
||||
when (codeVisitor.codeKind) {
|
||||
CodeVisitor.Kind.BLOCK -> {
|
||||
val codeFormattedBlock = htmlRenderer.get().render(localFormattedBody)
|
||||
buildCodeBlockItem(codeFormattedBlock, informationData, highlight, callback, attributes)
|
||||
}
|
||||
CodeVisitor.Kind.INLINE -> {
|
||||
val codeFormatted = htmlRenderer.get().render(localFormattedBody)
|
||||
buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
|
||||
}
|
||||
CodeVisitor.Kind.NONE -> {
|
||||
val formattedBody = htmlRenderer.get().render(messageContent.formattedBody!!)
|
||||
buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildMessageTextItem(body: CharSequence,
|
||||
isFormatted: Boolean,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
|
||||
val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
|
||||
val bodyToUse = if (isFormatted) {
|
||||
val formattedBody = htmlRenderer.get().parse(messageContent.body) as Document
|
||||
val codeVisitor = CodeVisitor()
|
||||
codeVisitor.visit(formattedBody)
|
||||
if (codeVisitor.codeKind == CodeVisitor.Kind.NONE) {
|
||||
messageContent.formattedBody.let {
|
||||
htmlRenderer.get().render(it!!.trim())
|
||||
}
|
||||
} else {
|
||||
htmlRenderer.get().render(formattedBody)
|
||||
}
|
||||
} else {
|
||||
messageContent.body
|
||||
}
|
||||
|
||||
val linkifiedBody = linkifyBody(bodyToUse, callback)
|
||||
val linkifiedBody = linkifyBody(body, callback)
|
||||
|
||||
return MessageTextItem_().apply {
|
||||
if (informationData.hasBeenEdited) {
|
||||
|
@ -269,7 +275,26 @@ class MessageItemFactory @Inject constructor(
|
|||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.urlClickCallback(callback)
|
||||
// click on the text
|
||||
}
|
||||
|
||||
private fun buildCodeBlockItem(formattedBody: CharSequence,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageBlockCodeItem? {
|
||||
|
||||
|
||||
return MessageBlockCodeItem_()
|
||||
.apply {
|
||||
if (informationData.hasBeenEdited) {
|
||||
val spannable = annotateWithEdited("", callback, informationData)
|
||||
editedSpan(spannable)
|
||||
}
|
||||
}
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.attributes(attributes)
|
||||
.highlighted(highlight)
|
||||
.message(formattedBody)
|
||||
}
|
||||
|
||||
private fun annotateWithEdited(linkifiedBody: CharSequence,
|
||||
|
|
|
@ -16,26 +16,38 @@
|
|||
|
||||
package im.vector.riotx.features.home.room.detail.timeline.item
|
||||
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import me.saket.bettermovementmethod.BetterLinkMovementMethod
|
||||
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||
abstract class MessageBlockCodeItem : AbsMessageItem<MessageBlockCodeItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var message: CharSequence? = null
|
||||
@EpoxyAttribute
|
||||
var editedSpan: CharSequence? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
|
||||
holder.messageView.text = message
|
||||
renderSendState(holder.messageView, holder.messageView)
|
||||
holder.messageView.setOnClickListener(attributes.itemClickListener)
|
||||
holder.messageView.setOnLongClickListener(attributes.itemLongClickListener)
|
||||
holder.editedView.movementMethod = BetterLinkMovementMethod.getInstance()
|
||||
holder.editedView.setTextOrHide(editedSpan)
|
||||
}
|
||||
|
||||
override fun getViewType() = STUB_ID
|
||||
|
||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||
|
||||
val messageView by bind<TextView>(R.id.codeBlockTextView)
|
||||
val editedView by bind<TextView>(R.id.codeBlockEditedView)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/roomToolbar" />
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
|
|
@ -1,40 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/messagesAdapter_body_hsv"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxHeight="500dp">
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/codeBlockScrollView"
|
||||
android:layout_width="0dp"
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codeBlockTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:fontFamily="monospace"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codeBlockEditedView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical|horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codeBlockTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:autoLink="none"
|
||||
android:fontFamily="monospace"
|
||||
android:textSize="14sp"
|
||||
tools:text="" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
Loading…
Add table
Reference in a new issue