edit checkbox messages directly

Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
This commit is contained in:
sowjanyakch 2025-03-13 15:43:50 +01:00 committed by Marcel Hibbe
parent 4abb28e445
commit 8b44882e78
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
3 changed files with 184 additions and 48 deletions

View File

@ -41,6 +41,7 @@ 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)
@ -77,13 +78,10 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
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 user = currentUserProvider.currentUser.blockingGet()
val hasCheckboxes = processCheckboxes( val hasCheckboxes = processCheckboxes(
message, message,
user user
@ -122,40 +120,46 @@ 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 = processedMessageText binding.messageText.text = processedMessageText
if (message.lastEditTimestamp != 0L && !message.isDeleted) { }else{
binding.messageEditIndicator.visibility = View.VISIBLE binding.messageText.text = ""
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp!!)
} else {
binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
}
// parent message handling
if (!message.isDeleted && message.parentMessageId != null) {
processParentMessage(message)
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
Reaction().showReactions(
message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions,
binding.messageText.context,
false,
viewThemeUtils
)
} }
if (message.lastEditTimestamp != 0L && !message.isDeleted) {
binding.messageEditIndicator.visibility = View.VISIBLE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp!!)
} else {
binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
}
// parent message handling
if (!message.isDeleted && message.parentMessageId != null) {
processParentMessage(message)
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
Reaction().showReactions(
message,
::clickOnReaction,
::longClickOnReaction,
binding.reactions,
binding.messageText.context,
false,
viewThemeUtils
)
} }
private fun processCheckboxes(chatMessage: ChatMessage, user: User): Boolean { private fun processCheckboxes(chatMessage: ChatMessage, user: User): Boolean {
val chatActivity = commonMessageInterface as ChatActivity
val message = chatMessage.message!!.toSpanned() val message = chatMessage.message!!.toSpanned()
val messageTextView = binding.messageText val messageTextView = binding.messageText
val checkBoxContainer = binding.checkboxContainer val checkBoxContainer = binding.checkboxContainer
val isOlderThanTwentyFourHours = chatMessage
.createdAt
.before(Date(System.currentTimeMillis() - AGE_THRESHOLD_FOR_EDIT_MESSAGE))
checkBoxContainer.removeAllViews() checkBoxContainer.removeAllViews()
val regex = """(- \[(X|x| )])\s*(.+)""".toRegex(RegexOption.MULTILINE) val regex = """(- \[(X|x| )])\s*(.+)""".toRegex(RegexOption.MULTILINE)
@ -178,6 +182,8 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
val checkBox = CheckBox(checkBoxContainer.context).apply { val checkBox = CheckBox(checkBoxContainer.context).apply {
text = taskText text = taskText
this.isChecked = isChecked this.isChecked = isChecked
this.isEnabled = !isOlderThanTwentyFourHours && chatMessage.actorType == "bots" || chatActivity
.userAllowedByPrivilages(chatMessage)
setOnCheckedChangeListener { _, _ -> setOnCheckedChangeListener { _, _ ->
updateCheckboxStates(chatMessage, user, checkboxList) updateCheckboxStates(chatMessage, user, checkboxList)
} }
@ -186,7 +192,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
checkboxList.add(checkBox) checkboxList.add(checkBox)
viewThemeUtils.platform.themeCheckbox(checkBox) viewThemeUtils.platform.themeCheckbox(checkBox)
} }
checkBoxContainer.visibility = View.VISIBLE checkBoxContainer.visibility = View.VISIBLE
return true return true
} }
@ -347,5 +352,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
private val TAG = IncomingTextMessageViewHolder::class.java.simpleName private val TAG = IncomingTextMessageViewHolder::class.java.simpleName
private const val CHECKED_GROUP_INDEX = 2 private const val CHECKED_GROUP_INDEX = 2
private const val TASK_TEXT_GROUP_INDEX = 3 private const val TASK_TEXT_GROUP_INDEX = 3
private const val AGE_THRESHOLD_FOR_EDIT_MESSAGE: Long = 86400000
} }
} }

View File

@ -13,31 +13,39 @@ 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.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.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.DateUtils import com.nextcloud.talk.utils.DateUtils
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,30 +73,32 @@ 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)
val user = currentUserProvider.currentUser.blockingGet()
val hasCheckboxes = processCheckboxes(
message,
user
)
processMessage(message, hasCheckboxes)
}
private fun processMessage(message: ChatMessage, hasCheckboxes: Boolean) {
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)
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT) viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
var textSize = context.resources.getDimension(R.dimen.chat_text_size)
var processedMessageText = messageUtils.enrichChatMessageText(
binding.messageText.context,
message,
false,
viewThemeUtils
)
processedMessageText = messageUtils.processMessageParameters(
binding.messageText.context,
viewThemeUtils,
processedMessageText!!,
message,
itemView
)
var isBubbled = true var isBubbled = true
if ( if (
(message.messageParameters == null || message.messageParameters!!.size <= 0) && (message.messageParameters == null || message.messageParameters!!.size <= 0) &&
@ -105,7 +115,25 @@ class OutcomingTextMessageViewHolder(itemView: View) :
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
binding.messageTime.layoutParams = layoutParams binding.messageTime.layoutParams = layoutParams
viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT) viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT)
binding.messageText.text = processedMessageText
if (!hasCheckboxes) {
var processedMessageText = messageUtils.enrichChatMessageText(
binding.messageText.context,
message,
false,
viewThemeUtils
)
processedMessageText = messageUtils.processMessageParameters(
binding.messageText.context,
viewThemeUtils,
processedMessageText!!,
message,
itemView
)
binding.messageText.text = processedMessageText
}else{
binding.messageText.text = ""
}
if (message.lastEditTimestamp != 0L && !message.isDeleted) { if (message.lastEditTimestamp != 0L && !message.isDeleted) {
binding.messageEditIndicator.visibility = View.VISIBLE binding.messageEditIndicator.visibility = View.VISIBLE
@ -161,6 +189,94 @@ class OutcomingTextMessageViewHolder(itemView: View) :
) )
} }
private fun processCheckboxes(chatMessage: ChatMessage, user: User): Boolean {
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))
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 = !isOlderThanTwentyFourHours
setOnCheckedChangeListener { _, _ ->
updateCheckboxStates(chatMessage, user, checkboxList)
}
}
checkBoxContainer.addView(checkBox)
checkboxList.add(checkBox)
viewThemeUtils.platform.themeCheckbox(checkBox)
}
checkBoxContainer.visibility = View.VISIBLE
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 { checkBox ->
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
@ -248,5 +364,8 @@ class OutcomingTextMessageViewHolder(itemView: View) :
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

@ -43,6 +43,17 @@
android:textIsSelectable="false" android:textIsSelectable="false"
tools:text="Talk to you later!" /> tools:text="Talk to you later!" />
<LinearLayout
android:id="@+id/checkboxContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_below="@id/messageText"
android:layout_marginTop="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"