Merge pull request #1509 from cvuorinen/904-chat-markdown-images

904 chat markdown images
This commit is contained in:
Phillip Thelen 2021-06-01 10:38:30 +02:00 committed by GitHub
commit 69a1322b10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 82 deletions

View file

@ -1,6 +1,7 @@
package com.habitrpg.android.habitica.models.social
import android.renderscript.BaseObj
import android.text.Spanned
import com.habitrpg.android.habitica.models.BaseObject
import com.habitrpg.android.habitica.models.user.Backer
import com.habitrpg.android.habitica.models.user.ContributorInfo
@ -32,7 +33,7 @@ open class ChatMessage : RealmObject(), BaseObject {
var text: String? = null
@Ignore
var parsedText: CharSequence? = null
var parsedText: Spanned? = null
var timestamp: Long? = null

View file

@ -1,80 +1,114 @@
package com.habitrpg.android.habitica.ui.helpers
import android.content.Context
import android.text.SpannableString
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.widget.TextView
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import io.noties.markwon.Markwon
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.image.ImagesPlugin
import io.noties.markwon.image.file.FileSchemeHandler
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
import io.noties.markwon.movement.MovementMethodPlugin
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.schedulers.Schedulers
object MarkdownParser {
internal var markwon: Markwon? = null
fun setup(context: Context) {
markwon = Markwon.builder(context)
.usePlugin(StrikethroughPlugin.create())
.usePlugin(ImagesPlugin.create {
it.addSchemeHandler(OkHttpNetworkSchemeHandler.create())
.addSchemeHandler(FileSchemeHandler.createWithAssets(context.assets))
})
.usePlugin(MovementMethodPlugin.create(LinkMovementMethod.getInstance()))
.build()
}
/**
* Parses formatted markdown and returns it as styled CharSequence
*
* @param input Markdown formatted String
* @return Stylized CharSequence
*/
fun parseMarkdown(input: String?): Spanned {
if (input == null) {
return SpannableString("")
}
val text = EmojiParser.parseEmojis(input) ?: input
return markwon?.toMarkdown(text) ?: SpannableString(text)
}
fun parseMarkdownAsync(input: String?, onSuccess: Consumer<Spanned>) {
Single.just(input ?: "")
.map { this.parseMarkdown(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(onSuccess, RxErrorHandler.handleEmptyError())
}
/**
* Converts stylized CharSequence into markdown
*
* @param input Stylized CharSequence
* @return Markdown formatted String
*/
fun parseCompiled(input: CharSequence): String? {
return EmojiParser.convertToCheatCode(input.toString())
}
}
fun TextView.setMarkdown(input: String?) {
MarkdownParser.markwon?.setParsedMarkdown(this, MarkdownParser.parseMarkdown(input))
}
fun TextView.setParsedMarkdown(input: Spanned?) {
if (input != null) {
MarkdownParser.markwon?.setParsedMarkdown(this, input)
} else {
text = null
}
package com.habitrpg.android.habitica.ui.helpers
import android.content.Context
import android.graphics.Rect
import android.text.SpannableString
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.widget.TextView
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.MarkwonPlugin
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.image.ImageSize
import io.noties.markwon.image.ImageSizeResolverDef
import io.noties.markwon.image.ImagesPlugin
import io.noties.markwon.image.file.FileSchemeHandler
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
import io.noties.markwon.movement.MovementMethodPlugin
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.schedulers.Schedulers
object MarkdownParser {
internal var markwon: Markwon? = null
fun setup(context: Context) {
markwon = Markwon.builder(context)
.usePlugin(StrikethroughPlugin.create())
.usePlugin(ImagesPlugin.create {
it.addSchemeHandler(OkHttpNetworkSchemeHandler.create())
.addSchemeHandler(FileSchemeHandler.createWithAssets(context.assets))
})
.usePlugin(this.createImageSizeResolverScaleDpiPlugin(context))
.usePlugin(MovementMethodPlugin.create(LinkMovementMethod.getInstance()))
.build()
}
/**
* Custom markwon plugin to scale image size according to dpi
*/
private fun createImageSizeResolverScaleDpiPlugin(context: Context): MarkwonPlugin {
return object : AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.imageSizeResolver(object : ImageSizeResolverDef() {
override fun resolveImageSize(imageSize: ImageSize?, imageBounds: Rect, canvasWidth: Int, textSize: Float): Rect {
val dpi = context.resources.displayMetrics.density
var width = imageBounds.width()
if (dpi > 1) {
width = (dpi * width.toFloat()).toInt()
}
if (width > canvasWidth) {
width = canvasWidth
}
val ratio = imageBounds.width().toFloat() / imageBounds.height()
val height = (width / ratio + .5f).toInt()
return Rect(0, 0, width, height)
}
})
}
}
}
/**
* Parses formatted markdown and returns it as styled CharSequence
*
* @param input Markdown formatted String
* @return Stylized CharSequence
*/
fun parseMarkdown(input: String?): Spanned {
if (input == null) {
return SpannableString("")
}
val text = EmojiParser.parseEmojis(input) ?: input
return markwon?.toMarkdown(text) ?: SpannableString(text)
}
fun parseMarkdownAsync(input: String?, onSuccess: Consumer<Spanned>) {
Single.just(input ?: "")
.map { this.parseMarkdown(it) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(onSuccess, RxErrorHandler.handleEmptyError())
}
/**
* Converts stylized CharSequence into markdown
*
* @param input Stylized CharSequence
* @return Markdown formatted String
*/
fun parseCompiled(input: CharSequence): String? {
return EmojiParser.convertToCheatCode(input.toString())
}
}
fun TextView.setMarkdown(input: String?) {
MarkdownParser.markwon?.setParsedMarkdown(this, MarkdownParser.parseMarkdown(input))
}
fun TextView.setParsedMarkdown(input: Spanned?) {
if (input != null) {
MarkdownParser.markwon?.setParsedMarkdown(this, input)
} else {
text = null
}
}

View file

@ -26,6 +26,7 @@ import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.helpers.setParsedMarkdown
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import org.greenrobot.eventbus.EventBus
@ -172,7 +173,7 @@ class ChatRecyclerMessageViewHolder(itemView: View, private var userId: String,
itemView.setPadding(16.dpToPx(context), itemView.paddingTop, itemView.paddingRight, itemView.paddingBottom)
}
binding.messageText.text = chatMessage?.parsedText
binding.messageText.setParsedMarkdown(chatMessage?.parsedText)
if (msg.parsedText == null) {
binding.messageText.text = chatMessage?.text
Maybe.just(chatMessage?.text ?: "")
@ -181,7 +182,7 @@ class ChatRecyclerMessageViewHolder(itemView: View, private var userId: String,
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ parsedText ->
chatMessage?.parsedText = parsedText
binding.messageText.text = chatMessage?.parsedText
binding.messageText.setParsedMarkdown(parsedText)
}, { it.printStackTrace() })
}