mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-19 11:39:42 +01:00
Implement text formatting via markdown rendering for text messages
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
This commit is contained in:
parent
09d53f6ed7
commit
d124301809
@ -144,12 +144,13 @@ ext {
|
|||||||
emojiVersion = "1.3.0"
|
emojiVersion = "1.3.0"
|
||||||
lifecycleVersion = '2.6.1'
|
lifecycleVersion = '2.6.1'
|
||||||
okhttpVersion = "4.11.0"
|
okhttpVersion = "4.11.0"
|
||||||
|
markwonVersion = "4.6.2"
|
||||||
materialDialogsVersion = "3.3.0"
|
materialDialogsVersion = "3.3.0"
|
||||||
parcelerVersion = "1.1.13"
|
parcelerVersion = "1.1.13"
|
||||||
|
prismVersion = "2.0.0"
|
||||||
retrofit2Version = "2.9.0"
|
retrofit2Version = "2.9.0"
|
||||||
roomVersion = "2.5.2"
|
roomVersion = "2.5.2"
|
||||||
workVersion = "2.8.1"
|
workVersion = "2.8.1"
|
||||||
markwonVersion = "4.6.2"
|
|
||||||
espressoVersion = "3.5.1"
|
espressoVersion = "3.5.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +158,7 @@ configurations.all {
|
|||||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
|
exclude group: 'org.jetbrains', module: 'annotations-java5' // via prism4j, already using annotations explicitly
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -262,12 +264,14 @@ dependencies {
|
|||||||
implementation "com.afollestad.material-dialogs:lifecycle:${materialDialogsVersion}"
|
implementation "com.afollestad.material-dialogs:lifecycle:${materialDialogsVersion}"
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer:2.19.0'
|
implementation 'com.google.android.exoplayer:exoplayer:2.18.7'
|
||||||
|
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.27'
|
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.27'
|
||||||
|
|
||||||
implementation "io.noties.markwon:core:$markwonVersion"
|
implementation "io.noties.markwon:core:$markwonVersion"
|
||||||
|
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
|
||||||
|
implementation "io.noties.markwon:syntax-highlight:$markwonVersion"
|
||||||
|
|
||||||
implementation 'com.github.nextcloud-deps:ImagePicker:2.1.0.2'
|
implementation 'com.github.nextcloud-deps:ImagePicker:2.1.0.2'
|
||||||
implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0'
|
implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0'
|
||||||
|
@ -29,8 +29,7 @@ package com.nextcloud.talk.adapters.messages
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.Spannable
|
import android.text.Spanned
|
||||||
import android.text.SpannableString
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -84,13 +83,13 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
|||||||
|
|
||||||
itemView.isSelected = false
|
itemView.isSelected = false
|
||||||
|
|
||||||
var messageString: Spannable = SpannableString(message.message)
|
|
||||||
|
|
||||||
var textSize = context.resources!!.getDimension(R.dimen.chat_text_size)
|
var textSize = context.resources!!.getDimension(R.dimen.chat_text_size)
|
||||||
|
|
||||||
|
var processedMessageText = DisplayUtils.getRenderedMarkdownText(context, message.message)
|
||||||
|
|
||||||
val messageParameters = message.messageParameters
|
val messageParameters = message.messageParameters
|
||||||
if (messageParameters != null && messageParameters.size > 0) {
|
if (messageParameters != null && messageParameters.size > 0) {
|
||||||
messageString = processMessageParameters(messageParameters, message, messageString)
|
processedMessageText = processMessageParameters(messageParameters, message, processedMessageText)
|
||||||
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
|
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
|
||||||
textSize = (textSize * TEXT_SIZE_MULTIPLIER).toFloat()
|
textSize = (textSize * TEXT_SIZE_MULTIPLIER).toFloat()
|
||||||
itemView.isSelected = true
|
itemView.isSelected = true
|
||||||
@ -98,7 +97,7 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||||
binding.messageText.text = messageString
|
binding.messageText.text = processedMessageText
|
||||||
|
|
||||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||||
|
|
||||||
@ -219,8 +218,8 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
|||||||
private fun processMessageParameters(
|
private fun processMessageParameters(
|
||||||
messageParameters: HashMap<String?, HashMap<String?, String?>>,
|
messageParameters: HashMap<String?, HashMap<String?, String?>>,
|
||||||
message: ChatMessage,
|
message: ChatMessage,
|
||||||
messageString: Spannable
|
messageString: Spanned
|
||||||
): Spannable {
|
): Spanned {
|
||||||
var messageStringInternal = messageString
|
var messageStringInternal = messageString
|
||||||
for (key in messageParameters.keys) {
|
for (key in messageParameters.keys) {
|
||||||
val individualHashMap = message.messageParameters!![key]
|
val individualHashMap = message.messageParameters!![key]
|
||||||
@ -253,6 +252,7 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return messageStringInternal
|
return messageStringInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.Spannable
|
import android.text.Spanned
|
||||||
import android.text.SpannableString
|
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
@ -70,15 +69,17 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH
|
|||||||
super.onBind(message)
|
super.onBind(message)
|
||||||
sharedApplication!!.componentApplication.inject(this)
|
sharedApplication!!.componentApplication.inject(this)
|
||||||
val messageParameters: HashMap<String?, HashMap<String?, String?>>? = message.messageParameters
|
val messageParameters: HashMap<String?, HashMap<String?, String?>>? = message.messageParameters
|
||||||
var messageString: Spannable = SpannableString(message.message)
|
|
||||||
realView.isSelected = false
|
realView.isSelected = false
|
||||||
val layoutParams = binding.messageTime.layoutParams as FlexboxLayout.LayoutParams
|
val layoutParams = binding.messageTime.layoutParams as FlexboxLayout.LayoutParams
|
||||||
layoutParams.isWrapBefore = false
|
layoutParams.isWrapBefore = false
|
||||||
var textSize = context!!.resources.getDimension(R.dimen.chat_text_size)
|
var textSize = context!!.resources.getDimension(R.dimen.chat_text_size)
|
||||||
val textColor = viewThemeUtils.getScheme(binding.messageText.context).onSurfaceVariant
|
val textColor = viewThemeUtils.getScheme(binding.messageText.context).onSurfaceVariant
|
||||||
|
|
||||||
|
var processedMessageText = DisplayUtils.getRenderedMarkdownText(context, message.message)
|
||||||
|
|
||||||
binding.messageTime.setTextColor(textColor)
|
binding.messageTime.setTextColor(textColor)
|
||||||
if (messageParameters != null && messageParameters.size > 0) {
|
if (messageParameters != null && messageParameters.size > 0) {
|
||||||
messageString = processMessageParameters(messageParameters, message, messageString)
|
processedMessageText = processMessageParameters(messageParameters, message, processedMessageText)
|
||||||
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
|
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
|
||||||
textSize = (textSize * TEXT_SIZE_MULTIPLIER).toFloat()
|
textSize = (textSize * TEXT_SIZE_MULTIPLIER).toFloat()
|
||||||
layoutParams.isWrapBefore = true
|
layoutParams.isWrapBefore = true
|
||||||
@ -90,7 +91,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH
|
|||||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||||
binding.messageTime.layoutParams = layoutParams
|
binding.messageTime.layoutParams = layoutParams
|
||||||
binding.messageText.setTextColor(textColor)
|
binding.messageText.setTextColor(textColor)
|
||||||
binding.messageText.text = messageString
|
binding.messageText.text = processedMessageText
|
||||||
|
|
||||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||||
|
|
||||||
@ -183,8 +184,8 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH
|
|||||||
private fun processMessageParameters(
|
private fun processMessageParameters(
|
||||||
messageParameters: HashMap<String?, HashMap<String?, String?>>,
|
messageParameters: HashMap<String?, HashMap<String?, String?>>,
|
||||||
message: ChatMessage,
|
message: ChatMessage,
|
||||||
messageString: Spannable
|
messageString: Spanned
|
||||||
): Spannable {
|
): Spanned {
|
||||||
var messageStringInternal = messageString
|
var messageStringInternal = messageString
|
||||||
for (key in messageParameters.keys) {
|
for (key in messageParameters.keys) {
|
||||||
val individualHashMap: HashMap<String?, String?>? = message.messageParameters!![key]
|
val individualHashMap: HashMap<String?, String?>? = message.messageParameters!![key]
|
||||||
|
@ -48,6 +48,7 @@ import android.text.style.AbsoluteSizeSpan;
|
|||||||
import android.text.style.ClickableSpan;
|
import android.text.style.ClickableSpan;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
@ -87,6 +88,11 @@ import coil.Coil;
|
|||||||
import coil.request.ImageRequest;
|
import coil.request.ImageRequest;
|
||||||
import coil.target.Target;
|
import coil.target.Target;
|
||||||
import coil.transform.CircleCropTransformation;
|
import coil.transform.CircleCropTransformation;
|
||||||
|
import io.noties.markwon.AbstractMarkwonPlugin;
|
||||||
|
import io.noties.markwon.Markwon;
|
||||||
|
import io.noties.markwon.MarkwonConfiguration;
|
||||||
|
import io.noties.markwon.core.MarkwonTheme;
|
||||||
|
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin;
|
||||||
import third.parties.fresco.BetterImageSpan;
|
import third.parties.fresco.BetterImageSpan;
|
||||||
|
|
||||||
import static com.nextcloud.talk.utils.FileSortOrder.sort_a_to_z_id;
|
import static com.nextcloud.talk.utils.FileSortOrder.sort_a_to_z_id;
|
||||||
@ -97,6 +103,7 @@ import static com.nextcloud.talk.utils.FileSortOrder.sort_small_to_big_id;
|
|||||||
import static com.nextcloud.talk.utils.FileSortOrder.sort_z_to_a_id;
|
import static com.nextcloud.talk.utils.FileSortOrder.sort_z_to_a_id;
|
||||||
|
|
||||||
public class DisplayUtils {
|
public class DisplayUtils {
|
||||||
|
private static final String TAG = DisplayUtils.class.getSimpleName();
|
||||||
|
|
||||||
private static final int INDEX_LUMINATION = 2;
|
private static final int INDEX_LUMINATION = 2;
|
||||||
private static final double MAX_LIGHTNESS = 0.92;
|
private static final double MAX_LIGHTNESS = 0.92;
|
||||||
@ -246,7 +253,28 @@ public class DisplayUtils {
|
|||||||
return chip;
|
return chip;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Spannable searchAndReplaceWithMentionSpan(String key, Context context, Spannable text,
|
public static Spanned getRenderedMarkdownText(Context context, String markdown) {
|
||||||
|
final Markwon markwon = Markwon.builder(context)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
|
||||||
|
builder.headingBreakHeight(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
builder.linkResolver((view, link) -> {
|
||||||
|
Log.i(TAG, "Link action not implemented" );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return markwon.toMarkdown(markdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Spannable searchAndReplaceWithMentionSpan(String key, Context context, Spanned text,
|
||||||
String id, String label, String type,
|
String id, String label, String type,
|
||||||
User conversationUser,
|
User conversationUser,
|
||||||
@XmlRes int chipXmlRes,
|
@XmlRes int chipXmlRes,
|
||||||
|
@ -47,6 +47,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
|
exclude group: 'org.jetbrains', module: 'annotations-java5' // via prism4j, already using annotations explicitly
|
||||||
// check for updates every build
|
// check for updates every build
|
||||||
resolutionStrategy.cacheChangingModulesFor 3600, 'seconds'
|
resolutionStrategy.cacheChangingModulesFor 3600, 'seconds'
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user