Merge pull request #4747 from nextcloud/fix_edit_bot_messages

Edit checkbox messages directly
This commit is contained in:
Marcel Hibbe 2025-04-04 13:00:51 +00:00 committed by GitHub
commit 5d78fed901
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 369 additions and 71 deletions

View File

@ -13,27 +13,38 @@ import android.content.Context
import android.util.Log import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.CheckBox
import androidx.core.content.ContextCompat
import androidx.core.text.toSpanned
import autodagger.AutoInjector import autodagger.AutoInjector
import coil.load import coil.load
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.ChatMessageUtils import com.nextcloud.talk.utils.ChatMessageUtils
import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.TextMatchers import com.nextcloud.talk.utils.TextMatchers
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.message.MessageUtils import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders import com.stfalcon.chatkit.messages.MessageHolders
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.Date
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
@ -57,47 +68,64 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
@Inject @Inject
lateinit var dateUtils: DateUtils lateinit var dateUtils: DateUtils
@Inject
lateinit var currentUserProvider: CurrentUserProviderNew
lateinit var commonMessageInterface: CommonMessageInterface lateinit var commonMessageInterface: CommonMessageInterface
@Inject
lateinit var chatRepository: ChatMessageRepository
private var job: Job? = null
override fun onBind(message: ChatMessage) { override fun onBind(message: ChatMessage) {
super.onBind(message) super.onBind(message)
sharedApplication!!.componentApplication.inject(this) sharedApplication!!.componentApplication.inject(this)
setAvatarAndAuthorOnMessageItem(message) setAvatarAndAuthorOnMessageItem(message)
colorizeMessageBubble(message) colorizeMessageBubble(message)
itemView.isSelected = false itemView.isSelected = false
val user = currentUserProvider.currentUser.blockingGet()
val hasCheckboxes = processCheckboxes(
message,
user
)
processMessage(message, hasCheckboxes)
}
private fun processMessage(message: ChatMessage, hasCheckboxes: Boolean) {
var textSize = context.resources!!.getDimension(R.dimen.chat_text_size) var textSize = context.resources!!.getDimension(R.dimen.chat_text_size)
if (!hasCheckboxes) {
var processedMessageText = messageUtils.enrichChatMessageText( binding.messageText.visibility = View.VISIBLE
binding.messageText.context, binding.checkboxContainer.visibility = View.GONE
message, var processedMessageText = messageUtils.enrichChatMessageText(
true, binding.messageText.context,
viewThemeUtils message,
) true,
viewThemeUtils
processedMessageText = messageUtils.processMessageParameters( )
binding.messageText.context, processedMessageText = messageUtils.processMessageParameters(
viewThemeUtils, binding.messageText.context,
processedMessageText!!, viewThemeUtils,
message, processedMessageText!!,
itemView message,
) itemView
)
val messageParameters = message.messageParameters val messageParameters = message.messageParameters
if ( if (
(messageParameters == null || messageParameters.size <= 0) && (messageParameters == null || messageParameters.size <= 0) &&
TextMatchers.isMessageWithSingleEmoticonOnly(message.text) TextMatchers.isMessageWithSingleEmoticonOnly(message.text)
) { ) {
textSize = (textSize * TEXT_SIZE_MULTIPLIER).toFloat() textSize = (textSize * TEXT_SIZE_MULTIPLIER).toFloat()
itemView.isSelected = true itemView.isSelected = true
binding.messageAuthor.visibility = View.GONE binding.messageAuthor.visibility = View.GONE
}
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
binding.messageText.text = processedMessageText
} else {
binding.messageText.visibility = View.GONE
binding.checkboxContainer.visibility = View.VISIBLE
} }
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
binding.messageText.text = processedMessageText
if (message.lastEditTimestamp != 0L && !message.isDeleted) { if (message.lastEditTimestamp != 0L && !message.isDeleted) {
binding.messageEditIndicator.visibility = View.VISIBLE binding.messageEditIndicator.visibility = View.VISIBLE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp!!) binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp!!)
@ -105,7 +133,7 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
binding.messageEditIndicator.visibility = View.GONE binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
} }
binding.messageTime.setTextColor(ContextCompat.getColor(context, R.color.no_emphasis_text))
// parent message handling // parent message handling
if (!message.isDeleted && message.parentMessageId != null) { if (!message.isDeleted && message.parentMessageId != null) {
processParentMessage(message) processParentMessage(message)
@ -127,6 +155,105 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
) )
} }
private fun processCheckboxes(chatMessage: ChatMessage, user: User): Boolean {
val chatActivity = commonMessageInterface as ChatActivity
val message = chatMessage.message!!.toSpanned()
val messageTextView = binding.messageText
val checkBoxContainer = binding.checkboxContainer
val isOlderThanTwentyFourHours = chatMessage
.createdAt
.before(Date(System.currentTimeMillis() - AGE_THRESHOLD_FOR_EDIT_MESSAGE))
val messageIsEditable = hasSpreedFeatureCapability(
user.capabilities?.spreedCapability!!,
SpreedFeatures.EDIT_MESSAGES
) &&
!isOlderThanTwentyFourHours
checkBoxContainer.removeAllViews()
val regex = """(- \[(X|x| )])\s*(.+)""".toRegex(RegexOption.MULTILINE)
val matches = regex.findAll(message)
if (matches.none()) return false
val firstPart = message.toString().substringBefore("\n- [")
messageTextView.text = messageUtils.enrichChatMessageText(
binding.messageText.context, firstPart, true, viewThemeUtils
)
val checkboxList = mutableListOf<CheckBox>()
matches.forEach { matchResult ->
val isChecked = matchResult.groupValues[CHECKED_GROUP_INDEX] == "X" ||
matchResult.groupValues[CHECKED_GROUP_INDEX] == "x"
val taskText = matchResult.groupValues[TASK_TEXT_GROUP_INDEX].trim()
val checkBox = CheckBox(checkBoxContainer.context).apply {
text = taskText
this.isChecked = isChecked
this.isEnabled = (
chatMessage.actorType == "bots" ||
chatActivity.userAllowedByPrivilages(chatMessage)
) && messageIsEditable
setTextColor(ContextCompat.getColor(context, R.color.no_emphasis_text))
setOnCheckedChangeListener { _, _ ->
updateCheckboxStates(chatMessage, user, checkboxList)
}
}
checkBoxContainer.addView(checkBox)
checkboxList.add(checkBox)
viewThemeUtils.platform.themeCheckbox(checkBox)
}
return true
}
private fun updateCheckboxStates(chatMessage: ChatMessage, user: User, checkboxes: List<CheckBox>) {
job = CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.IO) {
val apiVersion: Int = ApiUtils.getChatApiVersion(
user.capabilities?.spreedCapability!!,
intArrayOf(1)
)
val updatedMessage = updateMessageWithCheckboxStates(chatMessage.message!!, checkboxes)
chatRepository.editChatMessage(
user.getCredentials(),
ApiUtils.getUrlForChatMessage(apiVersion, user.baseUrl!!, chatMessage.token!!, chatMessage.id),
updatedMessage
).collect { result ->
withContext(Dispatchers.Main) {
if (result.isSuccess) {
val editedMessage = result.getOrNull()?.ocs?.data!!.parentMessage!!
Log.d(TAG, "EditedMessage: $editedMessage")
binding.messageEditIndicator.apply {
visibility = View.VISIBLE
}
binding.messageTime.text =
dateUtils.getLocalTimeStringFromTimestamp(editedMessage.lastEditTimestamp!!)
} else {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
}
}
}
}
}
private fun updateMessageWithCheckboxStates(originalMessage: String, checkboxes: List<CheckBox>): String {
var updatedMessage = originalMessage
val regex = """(- \[(X|x| )])\s*(.+)""".toRegex(RegexOption.MULTILINE)
checkboxes.forEach { _ ->
updatedMessage = regex.replace(updatedMessage) { matchResult ->
val taskText = matchResult.groupValues[TASK_TEXT_GROUP_INDEX].trim()
val checkboxState = if (checkboxes.find { it.text == taskText }?.isChecked == true) "X" else " "
"- [$checkboxState] $taskText"
}
}
return updatedMessage
}
private fun longClickOnReaction(chatMessage: ChatMessage) { private fun longClickOnReaction(chatMessage: ChatMessage) {
commonMessageInterface.onLongClickReactions(chatMessage) commonMessageInterface.onLongClickReactions(chatMessage)
} }
@ -231,8 +358,16 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
this.commonMessageInterface = commonMessageInterface this.commonMessageInterface = commonMessageInterface
} }
override fun viewDetached() {
super.viewDetached()
job?.cancel()
}
companion object { companion object {
const val TEXT_SIZE_MULTIPLIER = 2.5 const val TEXT_SIZE_MULTIPLIER = 2.5
private val TAG = IncomingTextMessageViewHolder::class.java.simpleName private val TAG = IncomingTextMessageViewHolder::class.java.simpleName
private const val CHECKED_GROUP_INDEX = 2
private const val TASK_TEXT_GROUP_INDEX = 3
private const val AGE_THRESHOLD_FOR_EDIT_MESSAGE: Long = 86400000
} }
} }

View File

@ -13,31 +13,43 @@ import android.content.Context
import android.util.Log import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.CheckBox
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.text.toSpanned
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector import autodagger.AutoInjector
import coil.load import coil.load
import com.google.android.flexbox.FlexboxLayout import com.google.android.flexbox.FlexboxLayout
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.data.network.NetworkMonitor import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ItemCustomOutcomingTextMessageBinding import com.nextcloud.talk.databinding.ItemCustomOutcomingTextMessageBinding
import com.nextcloud.talk.models.json.chat.ReadStatus import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.TextMatchers import com.nextcloud.talk.utils.TextMatchers
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.message.MessageUtils import com.nextcloud.talk.utils.message.MessageUtils
import com.stfalcon.chatkit.messages.MessageHolders.OutcomingTextMessageViewHolder import com.stfalcon.chatkit.messages.MessageHolders.OutcomingTextMessageViewHolder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.Date
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
@ -65,48 +77,71 @@ class OutcomingTextMessageViewHolder(itemView: View) :
lateinit var commonMessageInterface: CommonMessageInterface lateinit var commonMessageInterface: CommonMessageInterface
@Inject
lateinit var chatRepository: ChatMessageRepository
@Inject
lateinit var currentUserProvider: CurrentUserProviderNew
private var job: Job? = null
@Suppress("Detekt.LongMethod") @Suppress("Detekt.LongMethod")
override fun onBind(message: ChatMessage) { override fun onBind(message: ChatMessage) {
super.onBind(message) super.onBind(message)
sharedApplication!!.componentApplication.inject(this) sharedApplication!!.componentApplication.inject(this)
realView.isSelected = false val user = currentUserProvider.currentUser.blockingGet()
val layoutParams = binding.messageTime.layoutParams as FlexboxLayout.LayoutParams val hasCheckboxes = processCheckboxes(
layoutParams.isWrapBefore = false
var textSize = context.resources.getDimension(R.dimen.chat_text_size)
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
var processedMessageText = messageUtils.enrichChatMessageText(
binding.messageText.context,
message, message,
false, user
viewThemeUtils
)
processedMessageText = messageUtils.processMessageParameters(
binding.messageText.context,
viewThemeUtils,
processedMessageText!!,
message,
itemView
) )
processMessage(message, hasCheckboxes)
}
@Suppress("Detekt.LongMethod")
private fun processMessage(message: ChatMessage, hasCheckboxes: Boolean) {
var isBubbled = true var isBubbled = true
if ( val layoutParams = binding.messageTime.layoutParams as FlexboxLayout.LayoutParams
(message.messageParameters == null || message.messageParameters!!.size <= 0) && var textSize = context.resources.getDimension(R.dimen.chat_text_size)
TextMatchers.isMessageWithSingleEmoticonOnly(message.text) if (!hasCheckboxes) {
) { realView.isSelected = false
textSize = (textSize * TEXT_SIZE_MULTIPLIER).toFloat() layoutParams.isWrapBefore = false
layoutParams.isWrapBefore = true viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
realView.isSelected = true
isBubbled = false binding.messageText.visibility = View.VISIBLE
binding.checkboxContainer.visibility = View.GONE
var processedMessageText = messageUtils.enrichChatMessageText(
binding.messageText.context,
message,
false,
viewThemeUtils
)
processedMessageText = messageUtils.processMessageParameters(
binding.messageText.context,
viewThemeUtils,
processedMessageText!!,
message,
itemView
)
if (
(message.messageParameters == null || message.messageParameters!!.size <= 0) &&
TextMatchers.isMessageWithSingleEmoticonOnly(message.text)
) {
textSize = (textSize * TEXT_SIZE_MULTIPLIER).toFloat()
layoutParams.isWrapBefore = true
realView.isSelected = true
isBubbled = false
}
binding.messageTime.layoutParams = layoutParams
viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT)
binding.messageText.text = processedMessageText
} else {
binding.messageText.visibility = View.GONE
binding.checkboxContainer.visibility = View.VISIBLE
} }
setBubbleOnChatMessage(message)
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
binding.messageTime.layoutParams = layoutParams
viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT)
binding.messageText.text = processedMessageText
if (message.lastEditTimestamp != 0L && !message.isDeleted) { if (message.lastEditTimestamp != 0L && !message.isDeleted) {
binding.messageEditIndicator.visibility = View.VISIBLE binding.messageEditIndicator.visibility = View.VISIBLE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp!!) binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp!!)
@ -114,7 +149,8 @@ class OutcomingTextMessageViewHolder(itemView: View) :
binding.messageEditIndicator.visibility = View.GONE binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
} }
binding.messageTime.setTextColor(ContextCompat.getColor(context, R.color.no_emphasis_text))
setBubbleOnChatMessage(message)
// parent message handling // parent message handling
if (!message.isDeleted && message.parentMessageId != null) { if (!message.isDeleted && message.parentMessageId != null) {
processParentMessage(message) processParentMessage(message)
@ -161,6 +197,106 @@ class OutcomingTextMessageViewHolder(itemView: View) :
) )
} }
private fun processCheckboxes(chatMessage: ChatMessage, user: User): Boolean {
val chatActivity = commonMessageInterface as ChatActivity
val message = chatMessage.message!!.toSpanned()
val messageTextView = binding.messageText
val checkBoxContainer = binding.checkboxContainer
val isOlderThanTwentyFourHours = chatMessage
.createdAt
.before(Date(System.currentTimeMillis() - AGE_THRESHOLD_FOR_EDIT_MESSAGE))
val messageIsEditable = hasSpreedFeatureCapability(
user.capabilities?.spreedCapability!!,
SpreedFeatures.EDIT_MESSAGES
) && !isOlderThanTwentyFourHours
val isNoTimeLimitOnNoteToSelf = hasSpreedFeatureCapability(
user.capabilities?.spreedCapability!!,
SpreedFeatures
.EDIT_MESSAGES_NOTE_TO_SELF
) && chatActivity.currentConversation?.type == ConversationEnums.ConversationType.NOTE_TO_SELF
checkBoxContainer.removeAllViews()
val regex = """(- \[(X|x| )])\s*(.+)""".toRegex(RegexOption.MULTILINE)
val matches = regex.findAll(message)
if (matches.none()) return false
val firstPart = message.toString().substringBefore("\n- [")
messageTextView.text = messageUtils.enrichChatMessageText(
binding.messageText.context, firstPart, true, viewThemeUtils
)
val checkboxList = mutableListOf<CheckBox>()
matches.forEach { matchResult ->
val isChecked = matchResult.groupValues[CHECKED_GROUP_INDEX] == "X" ||
matchResult.groupValues[CHECKED_GROUP_INDEX] == "x"
val taskText = matchResult.groupValues[TASK_TEXT_GROUP_INDEX].trim()
val checkBox = CheckBox(checkBoxContainer.context).apply {
text = taskText
this.isChecked = isChecked
this.isEnabled = messageIsEditable || isNoTimeLimitOnNoteToSelf
setTextColor(ContextCompat.getColor(context, R.color.no_emphasis_text))
setOnCheckedChangeListener { _, _ ->
updateCheckboxStates(chatMessage, user, checkboxList)
}
}
checkBoxContainer.addView(checkBox)
checkboxList.add(checkBox)
viewThemeUtils.platform.themeCheckbox(checkBox)
}
return true
}
private fun updateCheckboxStates(chatMessage: ChatMessage, user: User, checkboxes: List<CheckBox>) {
job = CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.IO) {
val apiVersion: Int = ApiUtils.getChatApiVersion(
user.capabilities?.spreedCapability!!,
intArrayOf(1)
)
val updatedMessage = updateMessageWithCheckboxStates(chatMessage.message!!, checkboxes)
chatRepository.editChatMessage(
user.getCredentials(),
ApiUtils.getUrlForChatMessage(apiVersion, user.baseUrl!!, chatMessage.token!!, chatMessage.id),
updatedMessage
).collect { result ->
withContext(Dispatchers.Main) {
if (result.isSuccess) {
val editedMessage = result.getOrNull()?.ocs?.data!!.parentMessage!!
Log.d(TAG, "EditedMessage: $editedMessage")
binding.messageEditIndicator.apply {
visibility = View.VISIBLE
}
binding.messageTime.text =
dateUtils.getLocalTimeStringFromTimestamp(editedMessage.lastEditTimestamp!!)
} else {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
}
}
}
}
}
private fun updateMessageWithCheckboxStates(originalMessage: String, checkboxes: List<CheckBox>): String {
var updatedMessage = originalMessage
val regex = """(- \[(X|x| )])\s*(.+)""".toRegex(RegexOption.MULTILINE)
checkboxes.forEach { _ ->
updatedMessage = regex.replace(updatedMessage) { matchResult ->
val taskText = matchResult.groupValues[TASK_TEXT_GROUP_INDEX].trim()
val checkboxState = if (checkboxes.find { it.text == taskText }?.isChecked == true) "X" else " "
"- [$checkboxState] $taskText"
}
}
return updatedMessage
}
private fun updateStatus(readStatusDrawableInt: Int, description: String?) { private fun updateStatus(readStatusDrawableInt: Int, description: String?) {
binding.sendingProgress.visibility = View.GONE binding.sendingProgress.visibility = View.GONE
binding.checkMark.visibility = View.VISIBLE binding.checkMark.visibility = View.VISIBLE
@ -245,8 +381,16 @@ class OutcomingTextMessageViewHolder(itemView: View) :
this.commonMessageInterface = commonMessageInterface this.commonMessageInterface = commonMessageInterface
} }
override fun viewDetached() {
super.viewDetached()
job?.cancel()
}
companion object { companion object {
const val TEXT_SIZE_MULTIPLIER = 2.5 const val TEXT_SIZE_MULTIPLIER = 2.5
private val TAG = OutcomingTextMessageViewHolder::class.java.simpleName private val TAG = OutcomingTextMessageViewHolder::class.java.simpleName
private const val CHECKED_GROUP_INDEX = 2
private const val TASK_TEXT_GROUP_INDEX = 3
private const val AGE_THRESHOLD_FOR_EDIT_MESSAGE: Long = 86400000
} }
} }

View File

@ -65,7 +65,7 @@ class MessageUtils(val context: Context) {
} }
} }
private fun enrichChatMessageText( fun enrichChatMessageText(
context: Context, context: Context,
message: String, message: String,
incoming: Boolean, incoming: Boolean,

View File

@ -64,6 +64,17 @@
app:layout_wrapBefore="true" app:layout_wrapBefore="true"
tools:text="Talk to you later!" /> tools:text="Talk to you later!" />
<LinearLayout
android:id="@+id/checkboxContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_below="@id/messageText"
android:layout_marginBottom="8dp"
android:visibility="gone">
</LinearLayout>
<TextView <TextView
android:id="@id/messageTime" android:id="@id/messageTime"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -73,7 +84,7 @@
android:alpha="0.6" android:alpha="0.6"
android:textColor="@color/no_emphasis_text" android:textColor="@color/no_emphasis_text"
android:textIsSelectable="false" android:textIsSelectable="false"
android:gravity = "end" android:gravity="end"
app:layout_alignSelf="flex_end" app:layout_alignSelf="flex_end"
app:layout_flexGrow="1" app:layout_flexGrow="1"
app:layout_wrapBefore="false" app:layout_wrapBefore="false"
@ -86,14 +97,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/messageText" android:layout_below="@id/messageText"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:gravity="end"
android:alpha="0.6" android:alpha="0.6"
android:textColor="@color/no_emphasis_text" android:textColor="@color/no_emphasis_text"
android:textIsSelectable="false" android:textIsSelectable="false"
app:layout_alignSelf="flex_end" app:layout_alignSelf="flex_end"
android:text = "@string/hint_edited_message" android:text = "@string/hint_edited_message"
android:textSize="12sp"> android:textSize="12sp">
</TextView> </TextView>
<include <include

View File

@ -41,8 +41,21 @@
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textColorHighlight="@color/nc_grey" android:textColorHighlight="@color/nc_grey"
android:textIsSelectable="false" android:textIsSelectable="false"
app:layout_alignSelf="flex_start"
app:layout_flexGrow="1"
tools:text="Talk to you later!" /> tools:text="Talk to you later!" />
<LinearLayout
android:id="@+id/checkboxContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="8dp"
android:layout_below="@id/messageText"
android:visibility="gone">
</LinearLayout>
<TextView <TextView
android:id="@id/messageTime" android:id="@id/messageTime"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -54,7 +67,6 @@
android:textColor="@color/no_emphasis_text" android:textColor="@color/no_emphasis_text"
android:textIsSelectable="false" android:textIsSelectable="false"
app:layout_alignSelf="flex_end" app:layout_alignSelf="flex_end"
android:layout_gravity="end"
app:layout_flexGrow="1" app:layout_flexGrow="1"
app:layout_wrapBefore="false" app:layout_wrapBefore="false"
tools:text="10:35" /> tools:text="10:35" />
@ -68,7 +80,6 @@
android:alpha="0.6" android:alpha="0.6"
android:textColor="@color/no_emphasis_text" android:textColor="@color/no_emphasis_text"
android:textIsSelectable="false" android:textIsSelectable="false"
android:gravity="end"
app:layout_alignSelf="flex_end" app:layout_alignSelf="flex_end"
android:text = "@string/hint_edited_message" android:text = "@string/hint_edited_message"
android:textSize="12sp"> android:textSize="12sp">
@ -83,7 +94,6 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:contentDescription="@null" android:contentDescription="@null"
app:layout_alignSelf="flex_end" app:layout_alignSelf="flex_end"
android:gravity="end"
app:tint="@color/high_emphasis_text" app:tint="@color/high_emphasis_text"
tools:src="@drawable/ic_check_all" /> tools:src="@drawable/ic_check_all" />
@ -94,7 +104,7 @@
android:layout_below="@id/messageTime" android:layout_below="@id/messageTime"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:contentDescription="@null" android:contentDescription="@null"
app:layout_alignSelf="center" app:layout_alignSelf="flex_end"
app:tint="@color/high_emphasis_text" app:tint="@color/high_emphasis_text"
tools:src="@drawable/ic_warning_white"/> tools:src="@drawable/ic_warning_white"/>