Most things simply work

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2020-05-03 16:43:30 +02:00
parent 19fff9689d
commit 7102f4ea54
No known key found for this signature in database
GPG Key ID: CDE0BBD2738C4CC0
10 changed files with 95 additions and 61 deletions

View File

@ -20,6 +20,9 @@
package com.nextcloud.talk.models.json.chat; package com.nextcloud.talk.models.json.chat;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap; import java.util.HashMap;
public class ChatUtils { public class ChatUtils {
@ -40,4 +43,21 @@ public class ChatUtils {
return message; return message;
} }
@NotNull
public static Object getParsedMessageForSending(String message, HashMap<String, HashMap<String, String>> messageParameters) {
if (messageParameters != null && messageParameters.size() > 0) {
for (String key : messageParameters.keySet()) {
HashMap<String, String> individualHashMap = messageParameters.get(key);
if (individualHashMap.get("type").equals("user") || individualHashMap.get("type")
.equals("guest") || individualHashMap.get("type").equals("call")) {
message = message.replace("{" + key + "}", "@" + messageParameters.get(key).get("id"));
} else if (individualHashMap.get("type").equals("file")) {
message = message.replace("{" + key + "}", messageParameters.get(key).get("id"));
}
}
}
return message;
}
} }

View File

@ -81,7 +81,7 @@ open class ChatPresenter<T : Any>(context: Context, private val onElementClickPa
} }
holder.itemView.messageTime?.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME) holder.itemView.messageTime?.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME)
holder.itemView.sendingProgressBar.isVisible = it.chatMessageStatus != ChatMessageStatus.RECEIVED holder.itemView.sendingProgressBar.isVisible = it.chatMessageStatus != ChatMessageStatus.RECEIVED && it.chatMessageStatus != ChatMessageStatus.FAILED
holder.itemView.failedToSendNotice.isVisible = it.chatMessageStatus == ChatMessageStatus.FAILED holder.itemView.failedToSendNotice.isVisible = it.chatMessageStatus == ChatMessageStatus.FAILED
holder.itemView.chatMessage.text = it.text holder.itemView.chatMessage.text = it.text
if (TextMatchers.isMessageWithSingleEmoticonOnly(it.text)) { if (TextMatchers.isMessageWithSingleEmoticonOnly(it.text)) {

View File

@ -63,6 +63,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.mention.Mention import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.newarch.features.chat.interfaces.ImageLoaderInterface import com.nextcloud.talk.newarch.features.chat.interfaces.ImageLoaderInterface
import com.nextcloud.talk.newarch.local.models.User
import com.nextcloud.talk.newarch.local.models.getCredentials import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.local.models.getMaxMessageLength import com.nextcloud.talk.newarch.local.models.getMaxMessageLength
import com.nextcloud.talk.newarch.local.models.toUserEntity import com.nextcloud.talk.newarch.local.models.toUserEntity
@ -125,6 +126,10 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
showConversationInfoScreen() showConversationInfoScreen()
} }
private lateinit var user: User
private lateinit var conversationToken: String
private var conversationPassword: String? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup container: ViewGroup
@ -134,8 +139,6 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
viewModel = viewModelProvider(factory).get(ChatViewModel::class.java) viewModel = viewModelProvider(factory).get(ChatViewModel::class.java)
val view = super.onCreateView(inflater, container) val view = super.onCreateView(inflater, container)
viewModel.init(bundle.getParcelable(BundleKeys.KEY_USER)!!, bundle.getString(KEY_CONVERSATION_TOKEN)!!, bundle.getString(KEY_CONVERSATION_PASSWORD))
messagesAdapter = Adapter.builder(this) messagesAdapter = Adapter.builder(this)
.addSource(ChatViewLiveDataSource(viewModel.messagesLiveData)) .addSource(ChatViewLiveDataSource(viewModel.messagesLiveData))
.addSource(ChatDateHeaderSource(activity as Context, ChatElementTypes.DATE_HEADER.ordinal)) .addSource(ChatDateHeaderSource(activity as Context, ChatElementTypes.DATE_HEADER.ordinal))
@ -218,6 +221,7 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
if (shouldShowLobby) { if (shouldShowLobby) {
view.messagesRecyclerView?.visibility = View.GONE view.messagesRecyclerView?.visibility = View.GONE
view.messageInputView?.visibility = View.GONE view.messageInputView?.visibility = View.GONE
view.separator?.visibility = View.GONE
view.lobbyView?.visibility = View.VISIBLE view.lobbyView?.visibility = View.VISIBLE
val timer = conversation.lobbyTimer val timer = conversation.lobbyTimer
val unit = if (timer != null && timer != 0L) { val unit = if (timer != null && timer != 0L) {
@ -235,8 +239,10 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
if (isReadOnlyConversation) { if (isReadOnlyConversation) {
view.messageInputView?.visibility = View.GONE view.messageInputView?.visibility = View.GONE
view.separator?.visibility = View.GONE
} else { } else {
view.messageInputView?.visibility = View.VISIBLE view.messageInputView?.visibility = View.VISIBLE
view.separator?.visibility = View.VISIBLE
} }
} }
} }
@ -454,9 +460,14 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
override fun onAttach(view: View) { override fun onAttach(view: View) {
super.onAttach(view) super.onAttach(view)
viewModel.view = this viewModel.view = this
user = bundle.getParcelable(BundleKeys.KEY_USER)!!
conversationToken = bundle.getString(BundleKeys.KEY_CONVERSATION_TOKEN)!!
conversationPassword = bundle.getString(KEY_CONVERSATION_PASSWORD)
viewModel.user = user
viewModel.conversationPassword = conversationPassword
setupViews() setupViews()
toolbar?.setOnClickListener(toolbarOnClickListener) toolbar?.setOnClickListener(toolbarOnClickListener)
viewModel.joinConversation() viewModel.joinConversation(user, conversationToken, conversationPassword)
} }
override fun onDetach(view: View) { override fun onDetach(view: View) {

View File

@ -65,14 +65,14 @@ class ChatViewModel constructor(application: Application,
private val messagesRepository: MessagesRepository, private val messagesRepository: MessagesRepository,
private val globalService: GlobalService) : BaseViewModel<ChatView>(application), GlobalServiceInterface { private val globalService: GlobalService) : BaseViewModel<ChatView>(application), GlobalServiceInterface {
lateinit var user: User lateinit var user: User
val conversation: MutableLiveData<Conversation?> = MutableLiveData() val conversation: MutableLiveData<Conversation?> = MutableLiveData<Conversation?>()
var pastStartingPoint: Long = -1 var pastStartingPoint: Long = -1
val futureStartingPoint: MutableLiveData<Long> = MutableLiveData() val futureStartingPoint: MutableLiveData<Long> = MutableLiveData()
private var initConversation: Conversation? = null private var initConversation: Conversation? = null
val messagesLiveData = Transformations.switchMap(futureStartingPoint) { futureStartingPoint -> val messagesLiveData = Transformations.switchMap(conversation) { conversation ->
conversation.value?.let { conversation?.let {
messagesRepository.getMessagesWithUserForConversationSince(it.databaseId!!, futureStartingPoint).map { chatMessagesList -> messagesRepository.getMessagesWithUserForConversation(it.databaseId!!).map { chatMessagesList ->
chatMessagesList.map { chatMessage -> chatMessagesList.map { chatMessage ->
chatMessage.activeUser = user.toUserEntity() chatMessage.activeUser = user.toUserEntity()
chatMessage.parentMessage?.activeUser = chatMessage.activeUser chatMessage.parentMessage?.activeUser = chatMessage.activeUser
@ -83,7 +83,6 @@ class ChatViewModel constructor(application: Application,
} }
} }
} }
} }
} }
@ -91,14 +90,6 @@ class ChatViewModel constructor(application: Application,
var view: Controller? = null var view: Controller? = null
fun init(user: User, conversationToken: String, conversationPassword: String?) {
viewModelScope.launch {
this@ChatViewModel.user = user
this@ChatViewModel.initConversation = conversationsRepository.getConversationForUserWithToken(user.id!!, conversationToken)
this@ChatViewModel.conversationPassword = conversationPassword
}
}
fun sendMessage(editable: Editable, replyTo: Long?) { fun sendMessage(editable: Editable, replyTo: Long?) {
val messageParameters = hashMapOf<String, HashMap<String, String>>() val messageParameters = hashMapOf<String, HashMap<String, String>>()
val mentionSpans = editable.getSpans( val mentionSpans = editable.getSpans(
@ -143,7 +134,7 @@ class ChatViewModel constructor(application: Application,
chatMessage.actorDisplayName = user.displayName chatMessage.actorDisplayName = user.displayName
chatMessage.message = editable.toString() chatMessage.message = editable.toString()
chatMessage.systemMessageType = null chatMessage.systemMessageType = null
chatMessage.chatMessageStatus = ChatMessageStatus.PENDING_MESSAGE_SEND chatMessage.chatMessageStatus = ChatMessageStatus.PENDING
if (replyTo != null) { if (replyTo != null) {
chatMessage.parentMessage = messagesRepository.getMessageForConversation(conversationDatabaseId, replyTo) chatMessage.parentMessage = messagesRepository.getMessageForConversation(conversationDatabaseId, replyTo)
} else { } else {
@ -170,11 +161,12 @@ class ChatViewModel constructor(application: Application,
} }
} }
fun joinConversation() { fun joinConversation(user: User, conversationToken: String, conversationPassword: String?) {
initConversation?.token?.let {
viewModelScope.launch { viewModelScope.launch {
globalService.getConversation(it, this@ChatViewModel) this@ChatViewModel.user = user
} this@ChatViewModel.initConversation = conversationsRepository.getConversationForUserWithToken(user.id!!, conversationToken)
this@ChatViewModel.conversationPassword = conversationPassword
globalService.getConversation(conversationToken, this@ChatViewModel)
} }
} }

View File

@ -36,9 +36,10 @@ class ChatMessageStatusConverter {
return when (value) { return when (value) {
0 -> ChatMessageStatus.SENT 0 -> ChatMessageStatus.SENT
1 -> ChatMessageStatus.RECEIVED 1 -> ChatMessageStatus.RECEIVED
2 -> ChatMessageStatus.PENDING_MESSAGE_SEND 2 -> ChatMessageStatus.PENDING
3 -> ChatMessageStatus.PENDING_FILE_UPLOAD 3 -> ChatMessageStatus.PROCESSING
4 -> ChatMessageStatus.PENDING_FILE_SHARE 4 -> ChatMessageStatus.FAILED_ONCE
5 -> ChatMessageStatus.FAILED_TWICE
else -> ChatMessageStatus.FAILED else -> ChatMessageStatus.FAILED
} }
} }

View File

@ -40,7 +40,7 @@ abstract class MessagesDao {
@Query("SELECT * FROM messages WHERE conversation_id = :conversationId AND id = reference_id") @Query("SELECT * FROM messages WHERE conversation_id = :conversationId AND id = reference_id")
abstract suspend fun getPendingMessages(conversationId: String): List<MessageEntity> abstract suspend fun getPendingMessages(conversationId: String): List<MessageEntity>
@Query("SELECT * FROM messages WHERE conversation_id = :conversationId and id = reference_id and message_status != 5 and message_status != 0") @Query("SELECT * FROM messages WHERE conversation_id = :conversationId and id = reference_id and message_status NOT IN (0, 3, 6)")
abstract fun getPendingMessagesLive(conversationId: String): LiveData<List<MessageEntity>> abstract fun getPendingMessagesLive(conversationId: String): LiveData<List<MessageEntity>>
@Query( @Query(

View File

@ -3,8 +3,9 @@ package com.nextcloud.talk.newarch.local.models.other
enum class ChatMessageStatus { enum class ChatMessageStatus {
SENT, SENT,
RECEIVED, RECEIVED,
PENDING_MESSAGE_SEND, PENDING,
PENDING_FILE_UPLOAD, PROCESSING,
PENDING_FILE_SHARE, FAILED_ONCE,
FAILED_TWICE,
FAILED FAILED
} }

View File

@ -22,11 +22,13 @@
package com.nextcloud.talk.newarch.services package com.nextcloud.talk.newarch.services
import android.util.Log
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.chat.ChatUtils
import com.nextcloud.talk.models.json.conversations.Conversation import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.ConversationOverall import com.nextcloud.talk.models.json.conversations.ConversationOverall
import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.generic.GenericOverall
@ -74,19 +76,15 @@ class GlobalService constructor(usersRepository: UsersRepository,
} }
} }
private var messagesOperations: ConcurrentHashMap<String, Pair<ChatMessage, Int>> = ConcurrentHashMap<String, Pair<ChatMessage, Int>>()
init { init {
pendingMessages.observeForever { chatMessages -> pendingMessages.observeForever { chatMessages ->
for (chatMessage in chatMessages) { for (chatMessage in chatMessages) {
if (!messagesOperations.contains(chatMessage.internalMessageId) || messagesOperations[chatMessage.internalMessageId]?.first != chatMessage) {
messagesOperations[chatMessage.internalMessageId!!] = Pair(chatMessage, 0)
applicationScope.launch { applicationScope.launch {
sendMessage(chatMessage) sendMessage(chatMessage)
} }
} }
} }
}
currentUserLiveData.observeForever { user -> currentUserLiveData.observeForever { user ->
user?.let { user?.let {
@ -101,27 +99,34 @@ class GlobalService constructor(usersRepository: UsersRepository,
suspend fun sendMessage(chatMessage: ChatMessage) { suspend fun sendMessage(chatMessage: ChatMessage) {
val currentUser = currentUserLiveData.value?.toUser() val currentUser = currentUserLiveData.value?.toUser()
val conversation = currentConversation.value val conversation = currentConversation.value
val operationChatMessage = messagesOperations[chatMessage.internalMessageId]
operationChatMessage?.let { pair -> chatMessage.let { chatMessage ->
conversation?.let { conversation -> conversation?.let { conversation ->
if (pair.second == 4) { val currentStatus = chatMessage.chatMessageStatus
messagesOperations.remove(pair.first.internalMessageId) applicationScope.launch {
messagesRepository.updateMessageStatus(ChatMessageStatus.FAILED.ordinal, conversation.databaseId!!, pair.first.jsonMessageId!!) //messagesRepository.updateMessageStatus(ChatMessageStatus.PROCESSING.ordinal, conversation.databaseId!!, chatMessage.jsonMessageId!!)
} else { }
currentUser?.let { user -> currentUser?.let { user ->
if (chatMessage.internalConversationId == conversation.databaseId && conversation.databaseUserId == currentUser.id) { if (chatMessage.internalConversationId == conversation.databaseId && conversation.databaseUserId == currentUser.id) {
val sendChatMessageUseCase = SendChatMessageUseCase(networkComponents.getRepository(false, user), ApiErrorHandler()) val sendChatMessageUseCase = SendChatMessageUseCase(networkComponents.getRepository(false, user), ApiErrorHandler())
sendChatMessageUseCase.invoke(applicationScope, parametersOf(user, conversation.token, chatMessage.message, chatMessage.parentMessage?.jsonMessageId, chatMessage.referenceId), object : UseCaseResponse<Response<ChatOverall>> { val messageToSend = ChatUtils.getParsedMessageForSending(chatMessage.message, chatMessage.messageParameters)
sendChatMessageUseCase.invoke(applicationScope, parametersOf(user, conversation.token, messageToSend, chatMessage.parentMessage?.jsonMessageId, chatMessage.referenceId), object : UseCaseResponse<Response<ChatOverall>> {
override suspend fun onSuccess(result: Response<ChatOverall>) { override suspend fun onSuccess(result: Response<ChatOverall>) {
messagesOperations.remove(pair.first.internalMessageId!!) messagesRepository.updateMessageStatus(ChatMessageStatus.SENT.ordinal, conversation.databaseId!!, chatMessage.jsonMessageId!!)
messagesRepository.updateMessageStatus(ChatMessageStatus.SENT.ordinal, conversation.databaseId!!, pair.first.jsonMessageId!!)
} }
override suspend fun onError(errorModel: ErrorModel?) { override suspend fun onError(errorModel: ErrorModel?) {
val newValue = operationChatMessage.second + 1 when (currentStatus) {
messagesOperations[pair.first.internalMessageId!!] = Pair(chatMessage, newValue) ChatMessageStatus.PENDING -> {
sendMessage(chatMessage) messagesRepository.updateMessageStatus(ChatMessageStatus.FAILED_ONCE.ordinal, conversation.databaseId!!, chatMessage.jsonMessageId!!)
}
ChatMessageStatus.FAILED_ONCE -> {
messagesRepository.updateMessageStatus(ChatMessageStatus.FAILED_TWICE.ordinal, conversation.databaseId!!, chatMessage.jsonMessageId!!)
}
else -> {
messagesRepository.updateMessageStatus(ChatMessageStatus.FAILED.ordinal, conversation.databaseId!!, chatMessage.jsonMessageId!!)
}
}
} }
}) })
} }
@ -129,7 +134,6 @@ class GlobalService constructor(usersRepository: UsersRepository,
} }
} }
} }
}
suspend fun exitConversation(conversationToken: String, globalServiceInterface: GlobalServiceInterface) { suspend fun exitConversation(conversationToken: String, globalServiceInterface: GlobalServiceInterface) {
val currentUser = currentUserLiveData.value!!.toUser() val currentUser = currentUserLiveData.value!!.toUser()
@ -147,6 +151,7 @@ class GlobalService constructor(usersRepository: UsersRepository,
} }
}) })
} }
suspend fun getConversation(conversationToken: String, globalServiceInterface: GlobalServiceInterface) { suspend fun getConversation(conversationToken: String, globalServiceInterface: GlobalServiceInterface) {
val currentUser = currentUserLiveData.value val currentUser = currentUserLiveData.value
val getConversationUseCase = GetConversationUseCase(networkComponents.getRepository(true, currentUser!!.toUser()), ApiErrorHandler()) val getConversationUseCase = GetConversationUseCase(networkComponents.getRepository(true, currentUser!!.toUser()), ApiErrorHandler())

View File

@ -42,11 +42,13 @@
android:layout_above="@+id/messageInputView" android:layout_above="@+id/messageInputView"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:visibility="gone"
android:background="@color/controller_chat_separator" /> android:background="@color/controller_chat_separator" />
<com.stfalcon.chatkit.messages.MessageInput <com.stfalcon.chatkit.messages.MessageInput
android:id="@+id/messageInputView" android:id="@+id/messageInputView"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"

View File

@ -79,9 +79,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/chatMessage" android:layout_below="@id/chatMessage"
android:textSize="10sp" android:textSize="10sp"
android:layout_alignParentBottom="true" android:layout_above="@id/failedToSendNotice"
android:layout_alignWithParentIfMissing="true"
android:layout_toStartOf="@id/sendingProgressBar" android:layout_toStartOf="@id/sendingProgressBar"
android:layout_alignWithParentIfMissing="true"
android:id="@+id/messageTime" android:id="@+id/messageTime"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
tools:text="12:30"/> tools:text="12:30"/>
@ -91,9 +91,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp" android:layout_marginHorizontal="8dp"
android:text="@string/nc_failed_to_send" android:text="@string/nc_failed_to_send"
android:layout_below="@id/messageTime" android:layout_alignParentBottom="true"
android:textAlignment="viewEnd"
android:textColor="@color/nc_darkRed"
android:id="@+id/failedToSendNotice" android:id="@+id/failedToSendNotice"
android:visibility="gone" android:visibility="visible"
android:layout_alignParentEnd="true"/> android:layout_alignParentEnd="true"/>