WIP add options to temp messages

TODO:
check id type --> see TODO "currentTimeMillies fails as id because later on in the model it's not Long but Int!!!!" in OfflineFirstChatRepository.kt

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2024-12-20 18:06:30 +01:00
parent ec466e58f0
commit 1bfb3ba027
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
14 changed files with 402 additions and 366 deletions

View File

@ -121,21 +121,23 @@ class OutcomingTextMessageViewHolder(itemView: View) :
CoroutineScope(Dispatchers.Main).launch {
// if (message.sendingFailed) {
// updateStatus(
// R.drawable.baseline_report_problem_24,
// "failed"
// )
// } else
if (message.isTempMessage && !networkMonitor.isOnline.first()) {
if (message.isTemporary && !networkMonitor.isOnline.first()) {
updateStatus(
R.drawable.ic_signal_wifi_off_white_24dp,
"offline"
)
} else if (message.isTempMessage) {
} else if (message.sendingFailed) {
updateStatus(
R.drawable.baseline_report_problem_24,
"failed"
)
binding.bubble.setOnClickListener {
commonMessageInterface.onOpenMessageActionsDialog(message)
}
} else if (message.isTemporary) {
showSendingSpinner()
} else if(message.readStatus == ReadStatus.READ){
} else if(message.readStatus == ReadStatus.READ) {
updateStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read))
} else if(message.readStatus == ReadStatus.SENT) {
updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent))

View File

@ -68,9 +68,6 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
} else if (holder instanceof SystemMessageViewHolder holderInstance) {
holderInstance.assignSystemMessageInterface(chatActivity);
} else if (holder instanceof TemporaryMessageViewHolder holderInstance) {
holderInstance.assignTemporaryMessageInterface(chatActivity);
} else if (holder instanceof IncomingDeckCardViewHolder holderInstance) {
holderInstance.assignCommonMessageInterface(chatActivity);
} else if (holder instanceof OutcomingDeckCardViewHolder holderInstance) {

View File

@ -1,13 +0,0 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.adapters.messages
interface TemporaryMessageInterface {
fun editTemporaryMessage(id: Int, newMessage: String)
fun deleteTemporaryMessage(id: Int)
}

View File

@ -1,206 +0,0 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.adapters.messages
import android.content.Context
import android.util.Log
import android.view.View
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import autodagger.AutoInjector
import coil.load
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.databinding.ItemTemporaryMessageBinding
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.stfalcon.chatkit.messages.MessagesListAdapter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class TemporaryMessageViewHolder(outgoingView: View, payload: Any) :
MessagesListAdapter.OutcomingMessageViewHolder<ChatMessage>(outgoingView) {
private val binding: ItemTemporaryMessageBinding = ItemTemporaryMessageBinding.bind(outgoingView)
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
@Inject
lateinit var context: Context
@Inject
lateinit var messageUtils: MessageUtils
lateinit var temporaryMessageInterface: TemporaryMessageInterface
var isEditing = false
override fun onBind(message: ChatMessage) {
super.onBind(message)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
viewThemeUtils.platform.colorImageView(binding.tempMsgEdit, ColorRole.PRIMARY)
viewThemeUtils.platform.colorImageView(binding.tempMsgDelete, ColorRole.PRIMARY)
binding.bubble.setOnClickListener {
if (binding.tempMsgActions.isVisible) {
binding.tempMsgActions.visibility = View.GONE
} else {
binding.tempMsgActions.visibility = View.VISIBLE
}
}
binding.tempMsgEdit.setOnClickListener {
isEditing = !isEditing
if (isEditing) {
binding.tempMsgEdit.setImageDrawable(
ResourcesCompat.getDrawable(
context.resources,
R.drawable.ic_check,
null
)
)
binding.messageEdit.visibility = View.VISIBLE
binding.messageEdit.requestFocus()
ViewCompat.getWindowInsetsController(binding.root)?.show(WindowInsetsCompat.Type.ime())
binding.messageEdit.setText(binding.messageText.text)
binding.messageText.visibility = View.GONE
} else {
binding.tempMsgEdit.setImageDrawable(
ResourcesCompat.getDrawable(
context.resources,
R.drawable.ic_edit,
null
)
)
binding.messageEdit.visibility = View.GONE
binding.messageText.visibility = View.VISIBLE
val newMessage = binding.messageEdit.text.toString()
message.message = newMessage
temporaryMessageInterface.editTemporaryMessage(message.tempMessageId, newMessage)
}
}
binding.tempMsgDelete.setOnClickListener {
temporaryMessageInterface.deleteTemporaryMessage(message.tempMessageId)
}
// parent message handling
if (message.parentMessageId != null && message.parentMessageId!! > 0) {
processParentMessage(message)
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
val bgBubbleColor = bubble.resources.getColor(R.color.bg_message_list_incoming_bubble, null)
val layout = R.drawable.shape_outcoming_message
val bubbleDrawable = DisplayUtils.getMessageSelector(
bgBubbleColor,
ResourcesCompat.getColor(bubble.resources, R.color.transparent, null),
bgBubbleColor,
layout
)
ViewCompat.setBackground(bubble, bubbleDrawable)
}
@Suppress("Detekt.TooGenericExceptionCaught")
private fun processParentMessage(message: ChatMessage) {
if (message.parentMessageId != null && !message.isDeleted) {
CoroutineScope(Dispatchers.Main).launch {
try {
val chatActivity = temporaryMessageInterface as ChatActivity
val urlForChatting = ApiUtils.getUrlForChat(
chatActivity.chatApiVersion,
chatActivity.conversationUser?.baseUrl,
chatActivity.roomToken
)
val parentChatMessage = withContext(Dispatchers.IO) {
chatActivity.chatViewModel.getMessageById(
urlForChatting,
chatActivity.currentConversation!!,
message.parentMessageId!!
).first()
}
parentChatMessage.activeUser = message.activeUser
parentChatMessage.imageUrl?.let {
binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
val placeholder = ResourcesCompat.getDrawable(
context.resources,
R.drawable.ic_mimetype_image,
null
)
binding.messageQuote.quotedMessageImage.setImageDrawable(placeholder)
binding.messageQuote.quotedMessageImage.load(it) {
addHeader(
"Authorization",
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
)
}
} ?: run {
binding.messageQuote.quotedMessageImage.visibility = View.GONE
}
binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName
?: context.getText(R.string.nc_nick_guest)
binding.messageQuote.quotedMessage.text = messageUtils
.enrichChatReplyMessageText(
binding.messageQuote.quotedMessage.context,
parentChatMessage,
false,
viewThemeUtils
)
viewThemeUtils.talk.colorOutgoingQuoteText(binding.messageQuote.quotedMessage)
viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor)
viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView)
binding.messageQuote.quotedChatMessageView.setOnClickListener {
val chatActivity = temporaryMessageInterface as ChatActivity
chatActivity.jumpToQuotedMessage(parentChatMessage)
}
} catch (e: Exception) {
Log.d(TAG, "Error when processing parent message in view holder", e)
}
}
}
}
fun assignTemporaryMessageInterface(temporaryMessageInterface: TemporaryMessageInterface) {
this.temporaryMessageInterface = temporaryMessageInterface
}
override fun viewDetached() {
// unused atm
}
override fun viewAttached() {
// unused atm
}
override fun viewRecycled() {
// unused atm
}
companion object {
private val TAG = TemporaryMessageViewHolder::class.java.simpleName
}
}

View File

@ -111,8 +111,6 @@ import com.nextcloud.talk.adapters.messages.PreviewMessageViewHolder
import com.nextcloud.talk.adapters.messages.SystemMessageInterface
import com.nextcloud.talk.adapters.messages.SystemMessageViewHolder
import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter
import com.nextcloud.talk.adapters.messages.TemporaryMessageInterface
import com.nextcloud.talk.adapters.messages.TemporaryMessageViewHolder
import com.nextcloud.talk.adapters.messages.UnreadNoticeMessageViewHolder
import com.nextcloud.talk.adapters.messages.VoiceMessageInterface
import com.nextcloud.talk.api.NcApi
@ -154,6 +152,7 @@ import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment
import com.nextcloud.talk.ui.dialog.MessageActionsDialog
import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment
import com.nextcloud.talk.ui.dialog.ShowReactionsDialog
import com.nextcloud.talk.ui.dialog.TempMessageActionsDialog
import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.utils.ApiUtils
@ -230,8 +229,7 @@ class ChatActivity :
CommonMessageInterface,
PreviewMessageInterface,
SystemMessageInterface,
CallStartedMessageInterface,
TemporaryMessageInterface {
CallStartedMessageInterface {
var active = false
@ -600,16 +598,16 @@ class ChatActivity :
// }
messageInputViewModel.messageQueueSizeFlow.observe(this) { size ->
if (size == 0) {
var i = 0
var pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING)
while (pos != null && pos > -1) {
adapter?.items?.removeAt(pos)
i++
pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING)
}
adapter?.notifyDataSetChanged()
}
// if (size == 0) {
// var i = 0
// var pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING)
// while (pos != null && pos > -1) {
// adapter?.items?.removeAt(pos)
// i++
// pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING)
// }
// adapter?.notifyDataSetChanged()
// }
}
this.lifecycleScope.launch {
@ -1253,18 +1251,18 @@ class ChatActivity :
viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar)
}
private fun getLastAdapterId(): Int {
var lastId = 0
if (adapter?.items?.size != 0) {
val item = adapter?.items?.get(0)?.item
if (item != null) {
lastId = (item as ChatMessage).jsonMessageId
} else {
lastId = 0
}
}
return lastId
}
// private fun getLastAdapterId(): Int {
// var lastId = 0
// if (adapter?.items?.size != 0) {
// val item = adapter?.items?.get(0)?.item
// if (item != null) {
// lastId = (item as ChatMessage).jsonMessageId
// } else {
// lastId = 0
// }
// }
// return lastId
// }
private fun setupActionBar() {
setSupportActionBar(binding.chatToolbar)
@ -1384,17 +1382,6 @@ class ChatActivity :
R.layout.item_custom_outcoming_preview_message
)
messageHolders.registerContentType(
CONTENT_TYPE_TEMP,
TemporaryMessageViewHolder::class.java,
payload,
R.layout.item_temporary_message,
TemporaryMessageViewHolder::class.java,
payload,
R.layout.item_temporary_message,
this
)
messageHolders.registerContentType(
CONTENT_TYPE_SYSTEM_MESSAGE,
SystemMessageViewHolder::class.java,
@ -3437,9 +3424,16 @@ class ChatActivity :
private fun openMessageActionsDialog(iMessage: IMessage?) {
val message = iMessage as ChatMessage
if (hasVisibleItems(message) &&
!isSystemMessage(message) &&
message.id != TEMPORARY_MESSAGE_ID_STRING
if (message.isTemporary) {
TempMessageActionsDialog(
this,
message,
conversationUser,
currentConversation,
).show()
} else if (hasVisibleItems(message) &&
!isSystemMessage(message)
) {
MessageActionsDialog(
this,
@ -4011,30 +4005,6 @@ class ChatActivity :
startACall(false, false)
}
override fun editTemporaryMessage(id: Int, newMessage: String) {
// messageInputViewModel.editQueuedMessage(currentConversation!!.internalId, id, newMessage)
// adapter?.notifyDataSetChanged() // TODO optimize this
}
override fun deleteTemporaryMessage(id: Int) {
// messageInputViewModel.removeFromQueue(currentConversation!!.internalId, id)
// var i = 0
// val max = messageInputViewModel.messageQueueSizeFlow.value?.plus(1)
// for (item in adapter?.items!!) {
// if (i > max!! && max < 1) break
// if (item.item is ChatMessage &&
// (item.item as ChatMessage).isTempMessage &&
// (item.item as ChatMessage).tempMessageId == id
// ) {
// val index = adapter?.items!!.indexOf(item)
// adapter?.items!!.removeAt(index)
// adapter?.notifyItemRemoved(index)
// break
// }
// i++
// }
}
private fun logConversationInfos(methodName: String) {
Log.d(TAG, " |-----------------------------------------------")
Log.d(TAG, " | method: $methodName")

View File

@ -916,16 +916,23 @@ class MessageInputFragment : Fragment() {
// FIXME Fix API checking with guests?
val apiVersion: Int = ApiUtils.getChatApiVersion(chatActivity.spreedCapabilities, intArrayOf(1))
chatActivity.messageInputViewModel.editChatMessage(
chatActivity.credentials!!,
ApiUtils.getUrlForChatMessage(
apiVersion,
chatActivity.conversationUser!!.baseUrl!!,
chatActivity.roomToken,
message.id
),
editedMessageText
)
if (message.isTemporary) {
chatActivity.messageInputViewModel.editTempChatMessage(
message,
editedMessageText
)
} else {
chatActivity.messageInputViewModel.editChatMessage(
chatActivity.credentials!!,
ApiUtils.getUrlForChatMessage(
apiVersion,
chatActivity.conversationUser!!.baseUrl!!,
chatActivity.roomToken,
message.id
),
editedMessageText
)
}
}
private fun setEditUI(message: ChatMessage) {

View File

@ -14,6 +14,7 @@ import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
interface ChatMessageRepository : LifecycleAwareManager {
@ -97,4 +98,6 @@ interface ChatMessageRepository : LifecycleAwareManager {
): Flow<Result<ChatMessage?>>
suspend fun editChatMessage(credentials: String, url: String, text: String): Flow<Result<ChatOverallSingleMessage>>
suspend fun editTempChatMessage(message: ChatMessage, editedMessageText: String): Flow<Boolean>
}

View File

@ -115,9 +115,9 @@ data class ChatMessage(
var openWhenDownloaded: Boolean = true,
var isTempMessage: Boolean = false, // TODO: replace logic from message drafts with logic from temp message sending
var isTemporary: Boolean = false, // TODO: replace logic from message drafts with logic from temp message sending
var tempMessageId: Int = -1, // TODO: replace logic from message drafts with logic from temp message sending
// var tempMessageId: Int = -1, // TODO: replace logic from message drafts with logic from temp message sending
var referenceId: String? = null,

View File

@ -13,10 +13,6 @@ import android.util.Log
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.viewmodels.MessageInputViewModel
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.Companion
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.SendChatMessageErrorState
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.SendChatMessageSuccessState
import com.nextcloud.talk.data.database.dao.ChatBlocksDao
import com.nextcloud.talk.data.database.dao.ChatMessagesDao
import com.nextcloud.talk.data.database.mappers.asEntity
@ -41,10 +37,13 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.launch
import java.io.IOException
import javax.inject.Inject
class OfflineFirstChatRepository @Inject constructor(
@ -338,32 +337,6 @@ class OfflineFirstChatRepository @Inject constructor(
}
}
// TODO replace with WorkManager?
// private suspend fun tryToSendPendingMessages() {
// val tempMessages = chatDao.getTempMessagesForConversation(internalConversationId).first()
//
// tempMessages.forEach {
// Log.d(TAG, "Sending chat message ${it.message} another time!!")
//
// sendChatMessage(
// credentials,
// urlForChatting,
// it.message,
// it.actorDisplayName,
// it.parentMessageId?.toInt() ?: 0,
// false,
// it.referenceId ?: ""
// ).collect { result ->
// if (result.isSuccess) {
// Log.d(TAG, "success. received ref id: " + (result.getOrNull()?.referenceId ?: "none"))
//
// } else {
// Log.d(TAG, "fail. received ref id: " + (result.getOrNull()?.referenceId ?: "none"))
// }
// }
// }
// }
private suspend fun handleNewAndTempMessages(
receivedChatMessages : List<ChatMessage>,
lookIntoFuture: Boolean,
@ -829,35 +802,42 @@ class OfflineFirstChatRepository @Inject constructor(
referenceId: String
): Flow<Result<ChatMessage?>> =
flow {
try {
val response = network.sendChatMessage(
credentials,
url,
message,
displayName,
replyTo,
sendWithoutNotification,
referenceId
)
val response = network.sendChatMessage(
credentials,
url,
message,
displayName,
replyTo,
sendWithoutNotification,
referenceId
)
val chatMessageModel = response.ocs?.data?.asModel()
val chatMessageModel = response.ocs?.data?.asModel()
emit(Result.success(chatMessageModel))
} catch (e: Exception) {
Log.e(TAG, "Error when sending message", e)
emit(Result.success(chatMessageModel))
}
// .retryWhen { cause, attempt ->
// if (cause is IOException && attempt < 3) {
// delay(2000)
// return@retryWhen true
// } else {
// return@retryWhen false
// }
// }
.catch { e ->
Log.e(TAG, "Error when sending message", e)
val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first()
failedMessage.sendingFailed = true
chatDao.updateChatMessage(failedMessage)
val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first()
failedMessage.sendingFailed = true
chatDao.updateChatMessage(failedMessage)
// val failedMessageModel = failedMessage.asModel()
// _removeMessageFlow.emit(failedMessageModel)
//
// val tripleChatMessages = Triple(true, false, listOf(failedMessageModel))
// _messageFlow.emit(tripleChatMessages)
val failedMessageModel = failedMessage.asModel()
_removeMessageFlow.emit(failedMessageModel)
emit(Result.failure(e))
}
val tripleChatMessages = Triple(true, false, listOf(failedMessageModel))
_messageFlow.emit(tripleChatMessages)
emit(Result.failure(e))
}
override suspend fun editChatMessage(
@ -878,6 +858,30 @@ class OfflineFirstChatRepository @Inject constructor(
}
}
override suspend fun editTempChatMessage(
message: ChatMessage, editedMessageText: String
): Flow<Boolean> =
flow {
try {
val messageToEdit = chatDao.getChatMessageForConversation(internalConversationId, message.jsonMessageId
.toLong()).first()
messageToEdit.message = editedMessageText
chatDao.upsertChatMessage(messageToEdit)
val editedMessageModel = messageToEdit.asModel()
_removeMessageFlow.emit(editedMessageModel)
val tripleChatMessages = Triple(true, false, listOf(editedMessageModel))
_messageFlow.emit(tripleChatMessages)
emit(true)
} catch (e: Exception) {
emit(false)
}
}
override suspend fun addTemporaryMessage(
message: CharSequence,
displayName: String,
@ -917,7 +921,7 @@ class OfflineFirstChatRepository @Inject constructor(
val entity = ChatMessageEntity(
internalId = internalConversationId + "@_temp_" + currentTimeMillies,
internalConversationId = internalConversationId,
id = currentTimeMillies,
id = currentTimeMillies, // TODO: currentTimeMillies fails as id because later on in the model it's not Long but Int!!!!
message = message,
deleted = false,
token = conversationModel.token,

View File

@ -219,6 +219,21 @@ class MessageInputViewModel @Inject constructor(
}
}
fun editTempChatMessage(message: ChatMessage, editedMessageText: String) {
viewModelScope.launch {
chatRepository.editTempChatMessage(
message,
editedMessageText
).collect { result ->
if (true) {
// _editMessageViewState.value = EditMessageSuccessState(result)
} else {
// _editMessageViewState.value = EditMessageErrorState
}
}
}
}
fun reply(message: IMessage?) {
_getReplyChatMessage.postValue(message)
}

View File

@ -32,6 +32,7 @@ interface ChatMessagesDao {
SELECT *
FROM ChatMessages
WHERE internalConversationId = :internalConversationId
AND isTemporary = 0
ORDER BY timestamp DESC, id DESC
"""
)
@ -78,10 +79,10 @@ interface ChatMessagesDao {
@Query(
value = """
DELETE FROM ChatMessages
WHERE id in (:messageIds)
WHERE internalId in (:internalIds)
"""
)
fun deleteChatMessages(messageIds: List<Int>)
fun deleteChatMessages(internalIds: List<String>)
@Query(
value = """

View File

@ -66,7 +66,7 @@ fun ChatMessageEntity.asModel() =
lastEditTimestamp = lastEditTimestamp,
isDeleted = deleted,
referenceId = referenceId,
isTempMessage = isTemporary,
isTemporary = isTemporary,
sendingFailed = sendingFailed,
readStatus = setStatus(isTemporary, sendingFailed)
)

View File

@ -0,0 +1,124 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.ui.dialog
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.ChatActivity
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.DialogMessageActionsBinding
import com.nextcloud.talk.databinding.DialogTempMessageActionsBinding
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils
import kotlinx.coroutines.launch
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class TempMessageActionsDialog(
private val chatActivity: ChatActivity,
private val message: ChatMessage,
private val user: User?,
private val currentConversation: ConversationModel?
) : BottomSheetDialog(chatActivity) {
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
@Inject
lateinit var dateUtils: DateUtils
@Inject
lateinit var networkMonitor: NetworkMonitor
private lateinit var binding: DialogTempMessageActionsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this)
binding = DialogTempMessageActionsBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
viewThemeUtils.material.colorBottomSheetBackground(binding.root)
viewThemeUtils.material.colorBottomSheetDragHandle(binding.bottomSheetDragHandle)
initMenuItemCopy(!message.isDeleted)
val apiVersion = ApiUtils.getConversationApiVersion(user!!, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
initMenuItems()
}
private fun initMenuItems() {
this.lifecycleScope.launch {
initMenuEditMessage(true)
initMenuDeleteMessage(true)
}
}
override fun onStart() {
super.onStart()
val bottomSheet = findViewById<View>(R.id.design_bottom_sheet)
val behavior = BottomSheetBehavior.from(bottomSheet as View)
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
private fun initMenuDeleteMessage(visible: Boolean) {
if (visible) {
binding.menuDeleteMessage.setOnClickListener {
chatActivity.deleteMessage(message)
dismiss()
}
}
binding.menuDeleteMessage.visibility = getVisibility(visible)
}
private fun initMenuEditMessage(visible: Boolean) {
binding.menuEditMessage.setOnClickListener {
chatActivity.messageInputViewModel.edit(message)
dismiss()
}
binding.menuEditMessage.visibility = getVisibility(visible)
}
private fun initMenuItemCopy(visible: Boolean) {
if (visible) {
binding.menuCopyMessage.setOnClickListener {
chatActivity.copyMessage(message)
dismiss()
}
}
binding.menuCopyMessage.visibility = getVisibility(visible)
}
private fun getVisibility(visible: Boolean): Int {
return if (visible) {
View.VISIBLE
} else {
View.GONE
}
}
companion object {
private val TAG = TempMessageActionsDialog::class.java.simpleName
}
}

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/standard_half_padding">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:id="@+id/bottom_sheet_drag_handle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/message_actions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/menu_copy_message"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/menu_icon_copy_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/zero"
android:src="@drawable/ic_content_copy"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/menu_text_copy_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="@dimen/standard_double_padding"
android:paddingEnd="@dimen/standard_padding"
android:text="@string/nc_copy_message"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/menu_edit_message"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/menu_icon_edit_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/edit_message_icon_description"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/zero"
android:src="@drawable/ic_edit_24"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/menu_text_edit_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="@dimen/standard_double_padding"
android:paddingEnd="@dimen/standard_padding"
android:text="@string/nc_edit_message"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/menu_delete_message"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/menu_icon_delete_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/zero"
android:src="@drawable/ic_delete"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/menu_text_delete_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="@dimen/standard_double_padding"
android:paddingEnd="@dimen/standard_padding"
android:text="@string/nc_delete_message"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>