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.launch
import kotlinx.coroutines.withContext
import java.util.Date
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@ -77,13 +78,10 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
override fun onBind(message: ChatMessage) {
super.onBind(message)
sharedApplication!!.componentApplication.inject(this)
setAvatarAndAuthorOnMessageItem(message)
colorizeMessageBubble(message)
itemView.isSelected = false
val user = currentUserProvider.currentUser.blockingGet()
val hasCheckboxes = processCheckboxes(
message,
user
@ -122,40 +120,46 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
binding.messageText.text = processedMessageText
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
)
}else{
binding.messageText.text = ""
}
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 {
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))
checkBoxContainer.removeAllViews()
val regex = """(- \[(X|x| )])\s*(.+)""".toRegex(RegexOption.MULTILINE)
@ -178,6 +182,8 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
val checkBox = CheckBox(checkBoxContainer.context).apply {
text = taskText
this.isChecked = isChecked
this.isEnabled = !isOlderThanTwentyFourHours && chatMessage.actorType == "bots" || chatActivity
.userAllowedByPrivilages(chatMessage)
setOnCheckedChangeListener { _, _ ->
updateCheckboxStates(chatMessage, user, checkboxList)
}
@ -186,7 +192,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
checkboxList.add(checkBox)
viewThemeUtils.platform.themeCheckbox(checkBox)
}
checkBoxContainer.visibility = View.VISIBLE
return true
}
@ -347,5 +352,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
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,39 @@ import android.content.Context
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.CheckBox
import androidx.core.content.res.ResourcesCompat
import androidx.core.text.toSpanned
import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
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.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
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.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ItemCustomOutcomingTextMessageBinding
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.TextMatchers
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.message.MessageUtils
import com.stfalcon.chatkit.messages.MessageHolders.OutcomingTextMessageViewHolder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Date
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@ -65,30 +73,32 @@ class OutcomingTextMessageViewHolder(itemView: View) :
lateinit var commonMessageInterface: CommonMessageInterface
@Inject
lateinit var chatRepository: ChatMessageRepository
@Inject
lateinit var currentUserProvider: CurrentUserProviderNew
private var job: Job? = null
@Suppress("Detekt.LongMethod")
override fun onBind(message: ChatMessage) {
super.onBind(message)
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
val layoutParams = binding.messageTime.layoutParams as FlexboxLayout.LayoutParams
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,
false,
viewThemeUtils
)
processedMessageText = messageUtils.processMessageParameters(
binding.messageText.context,
viewThemeUtils,
processedMessageText!!,
message,
itemView
)
var textSize = context.resources.getDimension(R.dimen.chat_text_size)
var isBubbled = true
if (
(message.messageParameters == null || message.messageParameters!!.size <= 0) &&
@ -105,7 +115,25 @@ class OutcomingTextMessageViewHolder(itemView: View) :
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 (!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) {
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?) {
binding.sendingProgress.visibility = View.GONE
binding.checkMark.visibility = View.VISIBLE
@ -248,5 +364,8 @@ class OutcomingTextMessageViewHolder(itemView: View) :
companion object {
const val TEXT_SIZE_MULTIPLIER = 2.5
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"
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
android:id="@id/messageTime"
android:layout_width="wrap_content"