Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/4378/add temporary messages while sending #4422

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
743 changes: 743 additions & 0 deletions app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import android.util.Log
import android.util.TypedValue
import android.view.View
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
import com.google.android.flexbox.FlexboxLayout
Expand All @@ -23,6 +24,7 @@ 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.model.ChatMessage
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.databinding.ItemCustomOutcomingTextMessageBinding
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.ui.theme.ViewThemeUtils
Expand Down Expand Up @@ -58,6 +60,9 @@ class OutcomingTextMessageViewHolder(itemView: View) :
@Inject
lateinit var dateUtils: DateUtils

@Inject
lateinit var networkMonitor: NetworkMonitor

lateinit var commonMessageInterface: CommonMessageInterface

override fun onBind(message: ChatMessage) {
Expand Down Expand Up @@ -115,27 +120,27 @@ class OutcomingTextMessageViewHolder(itemView: View) :
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}

val readStatusDrawableInt = when (message.readStatus) {
ReadStatus.READ -> R.drawable.ic_check_all
ReadStatus.SENT -> R.drawable.ic_check
else -> null
}

val readStatusContentDescriptionString = when (message.readStatus) {
ReadStatus.READ -> context.resources?.getString(R.string.nc_message_read)
ReadStatus.SENT -> context.resources?.getString(R.string.nc_message_sent)
else -> null
}

readStatusDrawableInt?.let { drawableInt ->
ResourcesCompat.getDrawable(context.resources, drawableInt, null)?.let {
binding.checkMark.setImageDrawable(it)
viewThemeUtils.talk.themeMessageCheckMark(binding.checkMark)
CoroutineScope(Dispatchers.Main).launch {
if (message.sendingFailed) {
updateStatus(
R.drawable.baseline_report_problem_24,
"failed"
)
} else if (message.isTempMessage && !networkMonitor.isOnline.first()) {
updateStatus(
R.drawable.ic_signal_wifi_off_white_24dp,
"offline"
)
} else if (message.isTempMessage) {
updateSendingStatus()
} 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))
}
}

binding.checkMark.contentDescription = readStatusContentDescriptionString

itemView.setTag(R.string.replyable_message_view_tag, message.replyable)

Reaction().showReactions(
Expand All @@ -149,6 +154,27 @@ class OutcomingTextMessageViewHolder(itemView: View) :
)
}

private fun updateStatus(readStatusDrawableInt: Int, description: String?) {
binding.sendingProgress.visibility = View.GONE
binding.checkMark.visibility = View.VISIBLE
readStatusDrawableInt.let { drawableInt ->
ResourcesCompat.getDrawable(context.resources, drawableInt, null)?.let {
binding.checkMark.setImageDrawable(it)
viewThemeUtils.talk.themeMessageCheckMark(binding.checkMark)
}
}
binding.checkMark.contentDescription = description
}

private fun updateSendingStatus() {
binding.sendingProgress.visibility = View.VISIBLE
binding.checkMark.visibility = View.GONE

viewThemeUtils.material.colorProgressBar(binding.sendingProgress)
}



private fun longClickOnReaction(chatMessage: ChatMessage) {
commonMessageInterface.onLongClickReactions(chatMessage)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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
Expand Down Expand Up @@ -58,6 +59,14 @@ class TemporaryMessageViewHolder(outgoingView: View, payload: Any) :
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) {
Expand Down
12 changes: 4 additions & 8 deletions app/src/main/java/com/nextcloud/talk/api/NcApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -344,18 +344,14 @@ Observable<Response<ChatOverall>> pullChatMessages(@Header("Authorization") Stri

@FormUrlEncoded
@POST
Observable<GenericOverall> sendChatMessage(@Header("Authorization") String authorization,
Observable<ChatOverallSingleMessage> sendChatMessage(@Header("Authorization") String authorization,
@Url String url,
@Field("message") CharSequence message,
@Field("actorDisplayName") String actorDisplayName,
@Field("replyTo") Integer replyTo,
@Field("silent") Boolean sendWithoutNotification);

@FormUrlEncoded
@PUT
Observable<ChatOverallSingleMessage> editChatMessage(@Header("Authorization") String authorization,
@Url String url,
@Field("message") String message);
@Field("silent") Boolean sendWithoutNotification,
@Field("referenceId") String referenceId
);

@GET
Observable<Response<ChatShareOverall>> getSharedItems(
Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.nextcloud.talk.api

import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.AddParticipantOverall
Expand Down Expand Up @@ -121,6 +122,26 @@ interface NcApiCoroutines {
@DELETE
suspend fun unarchiveConversation(@Header("Authorization") authorization: String, @Url url: String): GenericOverall

@FormUrlEncoded
@POST
suspend fun sendChatMessage(
@Header("Authorization") authorization: String,
@Url url: String,
@Field("message") message: CharSequence,
@Field("actorDisplayName") actorDisplayName: String,
@Field("replyTo") replyTo: Int,
@Field("silent") sendWithoutNotification: Boolean,
@Field("referenceId") referenceId: String
): ChatOverallSingleMessage

@FormUrlEncoded
@PUT
suspend fun editChatMessage(
@Header("Authorization") authorization: String,
@Url url: String,
@Field("message") message: String
): ChatOverallSingleMessage

@FormUrlEncoded
@POST
suspend fun banActor(
Expand Down
115 changes: 65 additions & 50 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import retrofit2.HttpException
import java.io.File
import java.io.IOException
import java.net.HttpURLConnection
Expand Down Expand Up @@ -442,6 +441,7 @@ class ChatActivity :
chatViewModel = ViewModelProvider(this, viewModelFactory)[ChatViewModel::class.java]

messageInputViewModel = ViewModelProvider(this, viewModelFactory)[MessageInputViewModel::class.java]
messageInputViewModel.setData(chatViewModel.getChatRepository())

this.lifecycleScope.launch {
delay(DELAY_TO_SHOW_PROGRESS_BAR)
Expand Down Expand Up @@ -573,21 +573,21 @@ class ChatActivity :
private fun initObservers() {
Log.d(TAG, "initObservers Called")

messageInputViewModel.messageQueueFlow.observe(this) { list ->
list.forEachIndexed { _, qMsg ->
val temporaryChatMessage = ChatMessage()
temporaryChatMessage.jsonMessageId = TEMPORARY_MESSAGE_ID_INT
temporaryChatMessage.actorId = "-3"
temporaryChatMessage.timestamp = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS
temporaryChatMessage.message = qMsg.message.toString()
temporaryChatMessage.tempMessageId = qMsg.id
temporaryChatMessage.isTempMessage = true
temporaryChatMessage.parentMessageId = qMsg.replyTo!!.toLong()
val pos = adapter?.getMessagePositionById(qMsg.replyTo.toString())
adapter?.addToStart(temporaryChatMessage, true)
adapter?.notifyDataSetChanged()
}
}
// messageInputViewModel.messageQueueFlow.observe(this) { list ->
// list.forEachIndexed { _, qMsg ->
// val temporaryChatMessage = ChatMessage()
// temporaryChatMessage.jsonMessageId = TEMPORARY_MESSAGE_ID_INT
// temporaryChatMessage.actorId = TEMPORARY_MESSAGE_ID_STRING
// temporaryChatMessage.timestamp = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS
// temporaryChatMessage.message = qMsg.message.toString()
// temporaryChatMessage.tempMessageId = qMsg.id
// temporaryChatMessage.isTempMessage = true
// temporaryChatMessage.parentMessageId = qMsg.replyTo!!.toLong()
// val pos = adapter?.getMessagePositionById(qMsg.replyTo.toString())
// adapter?.addToStart(temporaryChatMessage, true)
// adapter?.notifyDataSetChanged()
// }
// }

messageInputViewModel.messageQueueSizeFlow.observe(this) { size ->
if (size == 0) {
Expand Down Expand Up @@ -697,7 +697,6 @@ class ChatActivity :
withCredentials = credentials!!,
withUrl = urlForChatting
)
messageInputViewModel.getTempMessagesFromMessageQueue(currentConversation!!.internalId)
}
} else {
Log.w(
Expand Down Expand Up @@ -791,18 +790,20 @@ class ChatActivity :
}

is MessageInputViewModel.SendChatMessageErrorState -> {
if (state.e is HttpException) {
val code = state.e.code()
if (code.toString().startsWith("2")) {
myFirstMessage = state.message

if (binding.unreadMessagesPopup.isShown) {
binding.unreadMessagesPopup.visibility = View.GONE
}
binding.messagesListView.smoothScrollToPosition(0)

binding.messagesListView.smoothScrollToPosition(0)
}
}
// if (state.e is HttpException) {
// val code = state.e.code()
// if (code.toString().startsWith("2")) {
// myFirstMessage = state.message
//
// if (binding.unreadMessagesPopup.isShown) {
// binding.unreadMessagesPopup.visibility = View.GONE
// }
//
// binding.messagesListView.smoothScrollToPosition(0)
// }
// }
}

else -> {}
Expand Down Expand Up @@ -915,6 +916,14 @@ class ChatActivity :
.collect()
}

this.lifecycleScope.launch {
chatViewModel.getRemoveMessageFlow
.onEach {
removeMessageById(it.id)
}
.collect()
}

this.lifecycleScope.launch {
chatViewModel.getUpdateMessageFlow
.onEach {
Expand Down Expand Up @@ -1056,9 +1065,15 @@ class ChatActivity :
}

private fun removeUnreadMessagesMarker() {
val index = adapter?.getMessagePositionById(UNREAD_MESSAGES_MARKER_ID.toString())
removeMessageById(UNREAD_MESSAGES_MARKER_ID.toString())
}

// do not use adapter.deleteById() as it seems to contain a bug! Use this method instead!
private fun removeMessageById(idToDelete: String) {
val index = adapter?.getMessagePositionById(idToDelete)
if (index != null && index != -1) {
adapter?.items?.removeAt(index)
adapter?.notifyItemRemoved(index)
}
}

Expand Down Expand Up @@ -2708,7 +2723,6 @@ class ChatActivity :
) {
if (message.item is ChatMessage) {
val chatMessage = message.item as ChatMessage

if (chatMessage.jsonMessageId <= xChatLastCommonRead) {
chatMessage.readStatus = ReadStatus.READ
} else {
Expand Down Expand Up @@ -3215,7 +3229,7 @@ class ChatActivity :
val message = iMessage as ChatMessage
if (hasVisibleItems(message) &&
!isSystemMessage(message) &&
message.id != "-3"
message.id != TEMPORARY_MESSAGE_ID_STRING
) {
MessageActionsDialog(
this,
Expand Down Expand Up @@ -3619,7 +3633,8 @@ class ChatActivity :
CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage)
CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == UNREAD_MESSAGES_MARKER_ID.toString()
CONTENT_TYPE_CALL_STARTED -> message.id == "-2"
CONTENT_TYPE_TEMP -> message.id == "-3"
// CONTENT_TYPE_TEMP -> message.id == TEMPORARY_MESSAGE_ID_STRING
// CONTENT_TYPE_TEMP -> message.readStatus == ReadStatus.FAILED
CONTENT_TYPE_DECK_CARD -> message.isDeckCard()

else -> false
Expand Down Expand Up @@ -3765,27 +3780,27 @@ class ChatActivity :
}

override fun editTemporaryMessage(id: Int, newMessage: String) {
messageInputViewModel.editQueuedMessage(currentConversation!!.internalId, id, newMessage)
adapter?.notifyDataSetChanged() // TODO optimize this
// 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++
}
// 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) {
Expand Down
Loading