mirror of
https://github.com/nextcloud/talk-android
synced 2025-03-07 06:39:45 +00:00
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:
parent
ec466e58f0
commit
1bfb3ba027
@ -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))
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 = """
|
||||
|
@ -66,7 +66,7 @@ fun ChatMessageEntity.asModel() =
|
||||
lastEditTimestamp = lastEditTimestamp,
|
||||
isDeleted = deleted,
|
||||
referenceId = referenceId,
|
||||
isTempMessage = isTemporary,
|
||||
isTemporary = isTemporary,
|
||||
sendingFailed = sendingFailed,
|
||||
readStatus = setStatus(isTemporary, sendingFailed)
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
132
app/src/main/res/layout/dialog_temp_message_actions.xml
Normal file
132
app/src/main/res/layout/dialog_temp_message_actions.xml
Normal 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>
|
Loading…
Reference in New Issue
Block a user