From a6be3e098c8bb74bfc9ad9703612b40e325f174b Mon Sep 17 00:00:00 2001
From: Mario Danic <mario@lovelyhq.com>
Date: Sun, 19 Apr 2020 03:17:37 +0200
Subject: [PATCH] Offline works amazingly well

Signed-off-by: Mario Danic <mario@lovelyhq.com>
---
 app/build.gradle                              |   2 +-
 .../1.json                                    |  18 +++-
 .../MentionAutocompleteCallback.java          |  15 ++-
 .../talk/models/json/chat/ChatMessage.kt      |  10 +-
 .../talk/models/json/chat/ChatUtils.java      |   5 +-
 .../offline/ConversationsRepositoryImpl.kt    |   2 +-
 .../offline/MessagesRepositoryImpl.kt         |  31 +++++-
 .../online/NextcloudTalkRepositoryImpl.kt     |   5 +
 .../newarch/data/source/remote/ApiService.kt  |  14 +++
 .../talk/newarch/di/module/ServiceModule.kt   |  11 +-
 .../domain/di/module/UseCasesModule.kt        |   5 +
 .../offline/ConversationsRepository.kt        |   2 +-
 .../repository/offline/MessagesRepository.kt  |   6 +-
 .../online/NextcloudTalkRepository.kt         |   1 +
 .../domain/usecases/SendChatMessageUseCase.kt |  20 ++++
 .../account/loginentry/LoginEntryViewModel.kt |   2 +-
 .../serverentry/ServerEntryViewModel.kt       |   2 +-
 .../newarch/features/chat/ChatPresenter.kt    |   3 +
 .../talk/newarch/features/chat/ChatView.kt    |  68 ++----------
 .../features/chat/ChatViewLiveDataSource.kt   |   2 +-
 .../newarch/features/chat/ChatViewModel.kt    | 102 +++++++++++++++---
 .../contacts/ContactsViewModel.kt             |   2 +-
 .../GroupConversationViewModel.kt             |   2 +-
 .../ConversationsListViewModel.kt             |   5 +-
 .../converters/ChatMessageStatusConverter.kt  |  45 ++++++++
 .../newarch/local/dao/ConversationsDao.kt     |  20 ++--
 .../talk/newarch/local/dao/MessagesDao.kt     |  67 ++++++++++--
 .../talk/newarch/local/db/TalkDatabase.kt     |  10 +-
 .../newarch/local/models/MessageEntity.kt     |  19 ++--
 .../local/models/other/ChatMessageStatus.kt   |  10 ++
 .../talk/newarch/mvvm/BaseViewModel.kt        |   2 +-
 .../talk/newarch/services/GlobalService.kt    |  83 ++++++++++++--
 .../com/nextcloud/talk/utils/DisplayUtils.kt  |   8 +-
 .../com/nextcloud/talk/utils/text/Spans.java  |   4 +-
 app/src/main/res/layout/rv_chat_item.xml      |  25 ++++-
 app/src/main/res/values/strings.xml           |   1 +
 36 files changed, 489 insertions(+), 140 deletions(-)
 create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/SendChatMessageUseCase.kt
 create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/converters/ChatMessageStatusConverter.kt
 create mode 100644 app/src/main/java/com/nextcloud/talk/newarch/local/models/other/ChatMessageStatus.kt

diff --git a/app/build.gradle b/app/build.gradle
index 503b3eb4b..d486f35dd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -158,7 +158,7 @@ ext {
   koin_version = "2.1.4"
   lifecycle_version = '2.2.0'
   coil_version = "0.9.5"
-  room_version = "2.2.4"
+  room_version = "2.2.5"
 }
 
 configurations.all {
diff --git a/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json b/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json
index bb2cd2235..3f745d5a6 100644
--- a/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json
+++ b/app/schemas/com.nextcloud.talk.newarch.local.db.TalkDatabase/1.json
@@ -2,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 1,
-    "identityHash": "4e8c1ae6a440d8491937afe33a3ab085",
+    "identityHash": "4623fd40c40300731b8871e7d43e5f65",
     "entities": [
       {
         "tableName": "conversations",
@@ -212,7 +212,7 @@
       },
       {
         "tableName": "messages",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `messageParameters` TEXT, `parent` TEXT, `replyable` INTEGER NOT NULL, `system_message_type` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `messageParameters` TEXT, `parent` TEXT, `replyable` INTEGER NOT NULL, `system_message_type` TEXT, `reference_id` TEXT, `message_status` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
         "fields": [
           {
             "fieldPath": "id",
@@ -285,6 +285,18 @@
             "columnName": "system_message_type",
             "affinity": "TEXT",
             "notNull": false
+          },
+          {
+            "fieldPath": "referenceId",
+            "columnName": "reference_id",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "chatMessageStatus",
+            "columnName": "message_status",
+            "affinity": "INTEGER",
+            "notNull": true
           }
         ],
         "primaryKey": {
@@ -401,7 +413,7 @@
     "views": [],
     "setupQueries": [
       "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4e8c1ae6a440d8491937afe33a3ab085')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4623fd40c40300731b8871e7d43e5f65')"
     ]
   }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java b/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java
index 465f7adae..130c16c65 100644
--- a/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java
+++ b/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java
@@ -63,12 +63,21 @@ public class MentionAutocompleteCallback implements AutocompleteCallback<Mention
         }
 
         editable.replace(start, end, replacementStringBuilder.toString() + " ");
+
+        String type = "user";
+
+        if (item.source.equals("users")) {
+            // do nothing
+        } else if (item.source.equals("guests")) {
+            type = "guests";
+        } else if (item.source.equals("calls")) {
+            type = "call";
+        }
+
         Spans.MentionChipSpan mentionChipSpan =
                 new Spans.MentionChipSpan(DisplayUtils.INSTANCE.getDrawableForMentionChipSpan(context,
                         item.id, item.label, conversationUser, item.source,
-                        R.xml.chip_you, editText),
-                        BetterImageSpan.ALIGN_CENTER,
-                        item.id, item.label);
+                        R.xml.chip_you, editText), BetterImageSpan.ALIGN_CENTER, item.id, item.label, type);
         editable.setSpan(mentionChipSpan, start, start + replacementStringBuilder.toString().length(),
                 Spanned.SPAN_INCLUSIVE_INCLUSIVE);
         return true;
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt
index 508a80266..6806e0ed6 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt
@@ -28,6 +28,7 @@ import com.nextcloud.talk.R
 import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
 import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter
 import com.nextcloud.talk.newarch.local.models.UserNgEntity
+import com.nextcloud.talk.newarch.local.models.other.ChatMessageStatus
 import com.nextcloud.talk.utils.ApiUtils
 import com.nextcloud.talk.utils.TextMatchers
 import com.stfalcon.chatkit.commons.models.IMessage
@@ -81,6 +82,10 @@ class ChatMessage : IMessage, MessageContentType, MessageContentType.Image {
     @Ignore
     var jsonMessageId: Long? = null
 
+    @JvmField
+    @JsonField(name = ["referenceId"])
+    var referenceId: String? = null
+
     @JvmField
     @JsonField(name = ["token"])
     var token: String? = null
@@ -132,6 +137,9 @@ class ChatMessage : IMessage, MessageContentType, MessageContentType.Image {
             MessageType.SYSTEM_MESSAGE, MessageType.SINGLE_LINK_VIDEO_MESSAGE,
             MessageType.SINGLE_LINK_AUDIO_MESSAGE, MessageType.SINGLE_LINK_MESSAGE)
 
+    @JsonIgnore
+    var chatMessageStatus: ChatMessageStatus = ChatMessageStatus.RECEIVED
+
     override fun equals(o: Any?): Boolean {
         if (this === o) return true
         if (o !is ChatMessage) return false
@@ -289,7 +297,7 @@ class ChatMessage : IMessage, MessageContentType, MessageContentType.Image {
                         ApiUtils.getUrlForAvatarWithName(activeUser!!.baseUrl, actorId,
                                 R.dimen.avatar_size)
                     }
-                    actorType.equals("guests") -> {
+                    actorType.equals("guests") || actorType.equals("bots") -> {
                         var apiId: String? = sharedApplication!!.getString(R.string.nc_guest)
                         if (!TextUtils.isEmpty(actorDisplayName)) {
                             apiId = actorDisplayName
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.java b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.java
index 0e9e1f90e..f5188d795 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.java
+++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.java
@@ -31,10 +31,9 @@ public class ChatUtils {
                 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.replaceAll("\\{" + key + "\\}", "@" +
-                            messageParameters.get(key).get("name"));
+                    message = message.replace("{" + key + "}", "@" + messageParameters.get(key).get("name"));
                 } else if (individualHashMap.get("type").equals("file")) {
-                    message = message.replaceAll("\\{" + key + "\\}", messageParameters.get(key).get("name"));
+                    message = message.replace("{" + key + "}", messageParameters.get(key).get("name"));
                 }
             }
         }
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/ConversationsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/ConversationsRepositoryImpl.kt
index 35b733a20..d54b206eb 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/ConversationsRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/ConversationsRepositoryImpl.kt
@@ -101,7 +101,7 @@ class ConversationsRepositoryImpl(val conversationsDao: ConversationsDao) :
             userId: Long,
             conversations: List<Conversation>,
             deleteOutdated: Boolean
-    ): List<Long> {
+    ) {
         val map = conversations.map {
             it.toConversationEntity()
         }
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/MessagesRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/MessagesRepositoryImpl.kt
index 4b9ece57c..89ee062e7 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/MessagesRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/MessagesRepositoryImpl.kt
@@ -28,6 +28,9 @@ import androidx.lifecycle.map
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.newarch.domain.repository.offline.MessagesRepository
 import com.nextcloud.talk.newarch.local.dao.MessagesDao
+import com.nextcloud.talk.newarch.local.models.User
+import com.nextcloud.talk.newarch.local.models.hasSpreedFeatureCapability
+import com.nextcloud.talk.newarch.local.models.other.ChatMessageStatus
 import com.nextcloud.talk.newarch.local.models.toChatMessage
 import com.nextcloud.talk.newarch.local.models.toMessageEntity
 
@@ -42,6 +45,18 @@ class MessagesRepositoryImpl(private val messagesDao: MessagesDao) : MessagesRep
         }
     }
 
+    override fun getPendingMessagesForConversation(conversationId: String): LiveData<List<ChatMessage>> {
+        return messagesDao.getPendingMessagesLive(conversationId).distinctUntilChanged().map {
+            it.map { messageEntity ->
+                messageEntity.toChatMessage()
+            }
+        }
+    }
+
+    override suspend fun getMessageForConversation(conversationId: String, messageId: Long): ChatMessage? {
+        return messagesDao.getMessageForConversation(conversationId, messageId)?.toChatMessage()
+    }
+
     override fun getMessagesWithUserForConversationSince(conversationId: String, messageId: Long): LiveData<List<ChatMessage>> {
         return messagesDao.getMessagesWithUserForConversationSince(conversationId, messageId).distinctUntilChanged().map {
             it.map { messageEntity ->
@@ -50,11 +65,23 @@ class MessagesRepositoryImpl(private val messagesDao: MessagesDao) : MessagesRep
         }
     }
 
-    override suspend fun saveMessagesForConversation(messages: List<ChatMessage>): List<Long> {
+    override suspend fun saveMessagesForConversation(user: User, messages: List<ChatMessage>, sendingMessages: Boolean){
+        val shouldInsert = !user.hasSpreedFeatureCapability("chat-reference-id") || sendingMessages
         val updatedMessages = messages.map {
+            if (!user.hasSpreedFeatureCapability("chat-reference-id")) {
+                it.chatMessageStatus = ChatMessageStatus.RECEIVED
+            }
             it.toMessageEntity()
         }
 
-        return messagesDao.saveMessages(*updatedMessages.toTypedArray())
+        if (shouldInsert) {
+            messagesDao.saveMessages(*updatedMessages.toTypedArray())
+        } else {
+            messagesDao.updateMessages(user, updatedMessages.toTypedArray())
+        }
+    }
+
+    override suspend fun updateMessageStatus(status: Int, conversationId: String, messageId: Long) {
+        messagesDao.updateMessageStatus(status, conversationId, messageId)
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/online/NextcloudTalkRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/online/NextcloudTalkRepositoryImpl.kt
index 809e0f779..500cf0ea1 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/online/NextcloudTalkRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/online/NextcloudTalkRepositoryImpl.kt
@@ -98,6 +98,11 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou
         }
     }
 
+    override suspend fun sendChatMessage(user: User, conversationToken: String, message: CharSequence, authorDisplayName: String?, replyTo: Int?, referenceId: String?): Response<ChatOverall> {
+        return apiService.sendChatMessage(user.getCredentials(), ApiUtils.getUrlForChat(user.baseUrl, conversationToken), message, authorDisplayName, replyTo, referenceId)
+    }
+
+
     override suspend fun getChatMessagesForConversation(user: User, conversationToken: String, lookIntoFuture: Int, lastKnownMessageId: Int, includeLastKnown: Int): Response<ChatOverall> {
         val mutableMap = mutableMapOf<String, Int>()
         mutableMap["lookIntoFuture"] = lookIntoFuture
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt
index cf54aea30..051d7b819 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/data/source/remote/ApiService.kt
@@ -39,6 +39,20 @@ import retrofit2.Response
 import retrofit2.http.*
 
 interface ApiService {
+    /*
+        Fieldmap items are as follows:
+          - "message": ,
+          - "actorDisplayName"
+    */
+    @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("referenceId") referenceId: String?): Response<ChatOverall>
+
     /*
     QueryMap items are as follows:
      - "lookIntoFuture": int (0 or 1),
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/di/module/ServiceModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/di/module/ServiceModule.kt
index e57825a87..2852ee087 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/di/module/ServiceModule.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/di/module/ServiceModule.kt
@@ -23,25 +23,28 @@
 package com.nextcloud.talk.newarch.di.module
 
 import android.content.Context
+import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
 import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
+import com.nextcloud.talk.newarch.domain.repository.offline.MessagesRepository
 import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
 import com.nextcloud.talk.newarch.domain.usecases.GetConversationUseCase
 import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase
 import com.nextcloud.talk.newarch.services.GlobalService
 import com.nextcloud.talk.newarch.services.shortcuts.ShortcutService
+import com.nextcloud.talk.newarch.utils.NetworkComponents
 import okhttp3.OkHttpClient
 import org.koin.dsl.module
 import java.net.CookieManager
 
 val ServiceModule = module {
-    single { createGlobalService(get(), get(), get(), get(), get(), get()) }
+    single { createGlobalService(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
     single { createShortcutService(get(), get(), get()) }
 }
 
 fun createGlobalService(usersRepository: UsersRepository, cookieManager: CookieManager,
-                        okHttpClient: OkHttpClient, conversationsRepository: ConversationsRepository,
-                        getConversationUseCase: GetConversationUseCase, joinConversationUseCase: JoinConversationUseCase): GlobalService {
-    return GlobalService(usersRepository, cookieManager, okHttpClient, conversationsRepository, joinConversationUseCase, getConversationUseCase)
+                        okHttpClient: OkHttpClient, apiErrorHandler: ApiErrorHandler, conversationsRepository: ConversationsRepository,
+                        messagesRepository: MessagesRepository, networkComponents: NetworkComponents, getConversationUseCase: GetConversationUseCase, joinConversationUseCase: JoinConversationUseCase): GlobalService {
+    return GlobalService(usersRepository, cookieManager, okHttpClient, apiErrorHandler, conversationsRepository, messagesRepository, networkComponents, joinConversationUseCase, getConversationUseCase)
 }
 
 fun createShortcutService(context: Context, conversationsRepository: ConversationsRepository, conversationsService: GlobalService): ShortcutService {
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt
index 50e7a2740..fd92bb199 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt
@@ -54,10 +54,15 @@ val UseCasesModule = module {
     factory { setConversationPasswordUseCase(get(), get()) }
     factory { getParticipantsForCallUseCase(get(), get()) }
     factory { createGetChatMessagesUseCase(get(), get()) }
+    factory { createSendChatMessageUseCase(get(), get()) }
     factory { getNotificationUseCase(get(), get()) }
     factory { createChatViewModelFactory(get(), get(), get(), get(), get(), get()) }
 }
 
+fun createSendChatMessageUseCase(nextcloudTalkRepository: NextcloudTalkRepository, apiErrorHandler: ApiErrorHandler): SendChatMessageUseCase {
+    return SendChatMessageUseCase(nextcloudTalkRepository, apiErrorHandler)
+}
+
 fun createGetChatMessagesUseCase(nextcloudTalkRepository: NextcloudTalkRepository, apiErrorHandler: ApiErrorHandler): GetChatMessagesUseCase {
     return GetChatMessagesUseCase(nextcloudTalkRepository, apiErrorHandler)
 }
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/ConversationsRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/ConversationsRepository.kt
index c45cfb400..2a3038b34 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/ConversationsRepository.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/ConversationsRepository.kt
@@ -35,7 +35,7 @@ interface ConversationsRepository {
             userId: Long,
             conversations: List<Conversation>,
             deleteOutdated: Boolean
-    ): List<Long>
+    )
 
     suspend fun setChangingValueForConversation(
             userId: Long,
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/MessagesRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/MessagesRepository.kt
index 1e881dcd3..d159fa177 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/MessagesRepository.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/MessagesRepository.kt
@@ -24,9 +24,13 @@ package com.nextcloud.talk.newarch.domain.repository.offline
 
 import androidx.lifecycle.LiveData
 import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.newarch.local.models.User
 
 interface MessagesRepository {
     fun getMessagesWithUserForConversation(conversationId: String): LiveData<List<ChatMessage>>
+    fun getPendingMessagesForConversation(conversationId: String): LiveData<List<ChatMessage>>
+    suspend fun getMessageForConversation(conversationId: String, messageId: Long): ChatMessage?
     fun getMessagesWithUserForConversationSince(conversationId: String, messageId: Long): LiveData<List<ChatMessage>>
-    suspend fun saveMessagesForConversation(messages: List<ChatMessage>): List<Long>
+    suspend fun saveMessagesForConversation(user: User, messages: List<ChatMessage>, sendingMessages: Boolean)
+    suspend fun updateMessageStatus(status: Int, conversationId: String, messageId: Long)
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt
index c4df6c747..2a88d16ba 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt
@@ -39,6 +39,7 @@ import com.nextcloud.talk.newarch.local.models.UserNgEntity
 import retrofit2.Response
 
 interface NextcloudTalkRepository {
+    suspend fun sendChatMessage(user: User, conversationToken: String, message: CharSequence, authorDisplayName: String?, replyTo: Int?, referenceId: String?): Response<ChatOverall>
     suspend fun getChatMessagesForConversation(user: User, conversationToken: String, lookIntoFuture: Int, lastKnownMessageId: Int, includeLastKnown: Int = 0): Response<ChatOverall>
     suspend fun getNotificationForUser(user: UserNgEntity, notificationId: String): NotificationOverall
     suspend fun getParticipantsForCall(user: UserNgEntity, conversationToken: String): ParticipantsOverall
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/SendChatMessageUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/SendChatMessageUseCase.kt
new file mode 100644
index 000000000..f5a8fe17c
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/SendChatMessageUseCase.kt
@@ -0,0 +1,20 @@
+package com.nextcloud.talk.newarch.domain.usecases
+
+import com.nextcloud.talk.models.json.chat.ChatOverall
+import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
+import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository
+import com.nextcloud.talk.newarch.domain.usecases.base.UseCase
+import com.nextcloud.talk.newarch.local.models.User
+import org.koin.core.parameter.DefinitionParameters
+import retrofit2.Response
+
+class SendChatMessageUseCase constructor(
+        private val nextcloudTalkRepository: NextcloudTalkRepository,
+        apiErrorHandler: ApiErrorHandler?
+) : UseCase<Response<ChatOverall>, Any?>(apiErrorHandler) {
+    override suspend fun run(params: Any?): Response<ChatOverall> {
+        val definitionParameters = params as DefinitionParameters
+        val user: User = definitionParameters[0]
+        return nextcloudTalkRepository.sendChatMessage(definitionParameters[0], definitionParameters[1], definitionParameters[2], user.displayName, definitionParameters[3], definitionParameters[4])
+    }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModel.kt
index 44f1b0be4..ce30488fe 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModel.kt
@@ -33,7 +33,7 @@ import com.nextcloud.talk.models.json.push.PushConfigurationStateWrapper
 import com.nextcloud.talk.models.json.push.PushRegistrationOverall
 import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall
 import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
-import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
+import com.nextcloud.talk.newarch.mvvm.BaseViewModel
 import com.nextcloud.talk.newarch.data.model.ErrorModel
 import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
 import com.nextcloud.talk.newarch.domain.usecases.*
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/account/serverentry/ServerEntryViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/account/serverentry/ServerEntryViewModel.kt
index 26b2fcc76..f85e39dcc 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/features/account/serverentry/ServerEntryViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/features/account/serverentry/ServerEntryViewModel.kt
@@ -26,7 +26,7 @@ import android.app.Application
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.viewModelScope
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
-import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
+import com.nextcloud.talk.newarch.mvvm.BaseViewModel
 import com.nextcloud.talk.newarch.data.model.ErrorModel
 import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase
 import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatPresenter.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatPresenter.kt
index f6c79f31b..18d94b8a0 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatPresenter.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatPresenter.kt
@@ -13,6 +13,7 @@ import com.amulyakhare.textdrawable.TextDrawable
 import com.nextcloud.talk.R
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.newarch.features.chat.interfaces.ImageLoaderInterface
+import com.nextcloud.talk.newarch.local.models.other.ChatMessageStatus
 import com.nextcloud.talk.utils.DisplayUtils
 import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType
 import com.nextcloud.talk.utils.TextMatchers
@@ -80,6 +81,8 @@ open class ChatPresenter<T : Any>(context: Context, private val onElementClickPa
                         }
 
                         holder.itemView.messageTime?.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME)
+                        holder.itemView.sendingProgressBar.isVisible = it.chatMessageStatus != ChatMessageStatus.RECEIVED
+                        holder.itemView.failedToSendNotice.isVisible = it.chatMessageStatus == ChatMessageStatus.FAILED
                         holder.itemView.chatMessage.text = it.text
                         if (TextMatchers.isMessageWithSingleEmoticonOnly(it.text)) {
                             holder.itemView.chatMessage.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24f)
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt
index 6ef4125f4..d1b559e7e 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatView.kt
@@ -93,7 +93,6 @@ import com.stfalcon.chatkit.utils.DateFormatter
 import com.uber.autodispose.lifecycle.LifecycleScopeProvider
 import com.vanniktech.emoji.EmojiPopup
 import kotlinx.android.synthetic.main.controller_chat.view.*
-import kotlinx.android.synthetic.main.conversations_list_view.view.*
 import kotlinx.android.synthetic.main.item_message_quote.view.*
 import kotlinx.android.synthetic.main.lobby_view.view.*
 import kotlinx.android.synthetic.main.view_message_input.view.*
@@ -105,7 +104,6 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
     override val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
     override val lifecycleOwner = ControllerLifecycleOwner(this)
 
-
     private lateinit var viewModel: ChatViewModel
     val factory: ChatViewModelFactory by inject()
     private val networkComponents: NetworkComponents by inject()
@@ -211,12 +209,6 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
             conversation.observe(this@ChatView) { conversation ->
                 setTitle()
 
-                if (Conversation.ConversationType.ONE_TO_ONE_CONVERSATION == conversation?.type) {
-                    loadAvatar()
-                } else {
-                    actionBar?.setIcon(null)
-                }
-
                 shouldShowLobby = conversation!!.shouldShowLobby(user)
                 isReadOnlyConversation = conversation.conversationReadOnlyState == Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY
 
@@ -375,6 +367,7 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
     private fun hideReplyView() {
         view?.messageInputView?.let {
             with (it) {
+                quotedMessageLayout.tag = null
                 quotedMessageLayout.isVisible = false
                 attachmentButton.isVisible = true
                 attachmentButtonSpace.isVisible = true
@@ -393,7 +386,6 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
                 quotedChatText.text = chatMessage.text
                 quotedAuthor.text = chatMessage.user.name
                 quotedMessageTime.text = DateFormatter.format(chatMessage.createdAt, DateFormatter.Template.TIME)
-
                 loadImage(quotedUserAvatar, chatMessage.user.avatar)
 
                 chatMessage.imageUrl?.let { previewImageUrl ->
@@ -493,10 +485,6 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
             conversationVoiceCallMenuItem?.isVisible = true
             conversationVideoMenuItem?.isVisible = true
         }
-
-        if (Conversation.ConversationType.ONE_TO_ONE_CONVERSATION == viewModel.conversation.value?.type) {
-            loadAvatar()
-        }
     }
 
     private fun setupViews() {
@@ -622,25 +610,12 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
     private fun submitMessage() {
         val editable = view?.messageInput?.editableText
         editable?.let {
-            val mentionSpans = it.getSpans(
-                    0, it.length,
-                    Spans.MentionChipSpan::class.java
-            )
-            var mentionSpan: Spans.MentionChipSpan
-            for (i in mentionSpans.indices) {
-                mentionSpan = mentionSpans[i]
-                var mentionId = mentionSpan.id
-                if (mentionId.contains(" ") || mentionId.startsWith("guest/")) {
-                    mentionId = "\"" + mentionId + "\""
-                }
-                it.replace(
-                        it.getSpanStart(mentionSpan), it.getSpanEnd(mentionSpan), "@$mentionId"
-                )
-            }
-
+            val replyMessageId= view?.messageInputView?.quotedMessageLayout?.tag as Long?
             view?.messageInput?.setText("")
-            viewModel.sendMessage(it)
-
+            viewModel.sendMessage(it, replyMessageId)
+            if (replyMessageId != null) {
+                hideReplyView()
+            }
         }
     }
 
@@ -696,37 +671,6 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
         }
     }
 
-    private fun loadAvatar() {
-        val imageLoader = networkComponents.getImageLoader(viewModel.user)
-        conversationVoiceCallMenuItem?.let {
-            val avatarSize = DisplayUtils.convertDpToPixel(
-                    it.icon!!.intrinsicWidth.toFloat(), activity!!
-            )
-                    .toInt()
-
-            avatarSize.let {
-                val target = object : Target {
-                    override fun onSuccess(result: Drawable) {
-                        super.onSuccess(result)
-                        actionBar?.setIcon(result)
-                    }
-                }
-
-                viewModel.conversation.value?.let {
-                    val avatarRequest = Images().getRequestForUrl(
-                            imageLoader, context, ApiUtils.getUrlForAvatarWithNameAndPixels(
-                            viewModel.user.baseUrl,
-                            it.name, avatarSize / 2
-                    ), viewModel.user, target, this,
-                            CircleCropTransformation()
-                    )
-                    imageLoader.load(avatarRequest)
-                }
-            }
-
-        }
-    }
-
     override fun getLayoutId(): Int {
         return R.layout.controller_chat
     }
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewLiveDataSource.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewLiveDataSource.kt
index 11f888de6..8c8f5d12d 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewLiveDataSource.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewLiveDataSource.kt
@@ -33,7 +33,7 @@ class ChatViewLiveDataSource<T : ChatElement>(private val data: LiveData<List<T>
         }
 
         if (first.data is ChatMessage && second.data is ChatMessage) {
-            return first.data.jsonMessageId == second.data.jsonMessageId
+            return first.data.jsonMessageId == second.data.jsonMessageId || first.data.referenceId == second.data.referenceId
         }
 
         return false
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModel.kt
index 0727fd068..dea7b9dd1 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/features/chat/ChatViewModel.kt
@@ -23,7 +23,7 @@
 package com.nextcloud.talk.newarch.features.chat
 
 import android.app.Application
-import android.text.TextUtils
+import android.text.Editable
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.Transformations
 import androidx.lifecycle.map
@@ -32,23 +32,31 @@ import com.bluelinelabs.conductor.Controller
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.models.json.chat.ChatOverall
 import com.nextcloud.talk.models.json.conversations.Conversation
-import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
 import com.nextcloud.talk.newarch.data.model.ErrorModel
 import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
 import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
 import com.nextcloud.talk.newarch.domain.repository.offline.MessagesRepository
 import com.nextcloud.talk.newarch.domain.usecases.GetChatMessagesUseCase
+import com.nextcloud.talk.newarch.domain.usecases.SendChatMessageUseCase
 import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
-import com.nextcloud.talk.newarch.local.models.User
-import com.nextcloud.talk.newarch.local.models.UserNgEntity
-import com.nextcloud.talk.newarch.local.models.toUser
-import com.nextcloud.talk.newarch.local.models.toUserEntity
+import com.nextcloud.talk.newarch.local.models.*
+import com.nextcloud.talk.newarch.local.models.other.ChatMessageStatus
+import com.nextcloud.talk.newarch.mvvm.BaseViewModel
 import com.nextcloud.talk.newarch.services.GlobalService
 import com.nextcloud.talk.newarch.services.GlobalServiceInterface
 import com.nextcloud.talk.newarch.utils.NetworkComponents
+import com.nextcloud.talk.newarch.utils.hashWithAlgorithm
+import com.nextcloud.talk.utils.text.Spans
 import kotlinx.coroutines.launch
 import org.koin.core.parameter.parametersOf
 import retrofit2.Response
+import kotlin.collections.HashMap
+import kotlin.collections.hashMapOf
+import kotlin.collections.indices
+import kotlin.collections.listOf
+import kotlin.collections.map
+import kotlin.collections.mutableListOf
+import kotlin.collections.set
 
 class ChatViewModel constructor(application: Application,
                                 private val networkComponents: NetworkComponents,
@@ -62,7 +70,7 @@ class ChatViewModel constructor(application: Application,
     val futureStartingPoint: MutableLiveData<Long> = MutableLiveData()
     private var initConversation: Conversation? = null
 
-    val messagesLiveData = Transformations.switchMap(futureStartingPoint) {futureStartingPoint ->
+    val messagesLiveData = Transformations.switchMap(futureStartingPoint) { futureStartingPoint ->
         conversation.value?.let {
             messagesRepository.getMessagesWithUserForConversationSince(it.databaseId!!, futureStartingPoint).map { chatMessagesList ->
                 chatMessagesList.map { chatMessage ->
@@ -92,8 +100,75 @@ class ChatViewModel constructor(application: Application,
         }
     }
 
-    fun sendMessage(message: CharSequence) {
+    fun sendMessage(editable: Editable, replyTo: Long?) {
+        val messageParameters = hashMapOf<String, HashMap<String, String>>()
+        val mentionSpans = editable.getSpans(
+                0, editable.length,
+                Spans.MentionChipSpan::class.java
+        )
+        var mentionSpan: Spans.MentionChipSpan
+        val ids = mutableListOf<String>()
+        for (i in mentionSpans.indices) {
+            mentionSpan = mentionSpans[i]
+            var mentionId = mentionSpan.id
+            if (mentionId.contains(" ") || mentionId.startsWith("guest/")) {
+                mentionId = "\"" + mentionId + "\""
+            }
 
+            val mentionNo = if (ids.contains("mentionId")) ids.indexOf("mentionId") + 1 else ids.size + 1
+            val mentionReplace = "mention-${mentionSpan.type}$mentionNo"
+            if (!ids.contains(mentionId)) {
+                ids.add(mentionId)
+                messageParameters[mentionReplace] = hashMapOf("type" to mentionSpan.type, "id" to mentionId.toString(), "name" to mentionSpan.label.toString())
+            }
+
+            val start = editable.getSpanStart(mentionSpan)
+            editable.replace(start, editable.getSpanEnd(mentionSpan), "")
+            editable.insert(start, "{$mentionReplace}")
+        }
+
+        if (user.hasSpreedFeatureCapability("chat-reference-id")) {
+            ioScope.launch {
+                val chatMessage = ChatMessage()
+                val timestamp = System.currentTimeMillis()
+                val sha1 = timestamp.toString().hashWithAlgorithm("SHA-1")
+                conversation.value?.databaseId?.let { conversationDatabaseId ->
+                    chatMessage.internalMessageId = sha1
+                    chatMessage.internalConversationId = conversationDatabaseId
+                    chatMessage.timestamp = timestamp / 1000
+                    chatMessage.referenceId = sha1
+                    chatMessage.replyable = false
+                    // can also be "guests", but not now
+                    chatMessage.actorId = user.userId
+                    chatMessage.actorType = "users"
+                    chatMessage.actorDisplayName = user.displayName
+                    chatMessage.message = editable.toString()
+                    chatMessage.systemMessageType = null
+                    chatMessage.chatMessageStatus = ChatMessageStatus.PENDING_MESSAGE_SEND
+                    if (replyTo != null) {
+                        chatMessage.parentMessage = messagesRepository.getMessageForConversation(conversationDatabaseId, replyTo)
+                    } else {
+                        chatMessage.parentMessage = null
+                    }
+                    chatMessage.messageParameters = messageParameters
+                    messagesRepository.saveMessagesForConversation(user, listOf(chatMessage), true)
+                }
+            }
+        } else {
+            val sendChatMessageUseCase = SendChatMessageUseCase(networkComponents.getRepository(false, user), apiErrorHandler)
+            // No reference id needed here
+            initConversation?.let {
+                sendChatMessageUseCase.invoke(viewModelScope, parametersOf(user, it.token, editable, replyTo, null), object : UseCaseResponse<Response<ChatOverall>> {
+                    override suspend fun onSuccess(result: Response<ChatOverall>) {
+                        // also do nothing, we did it - time to celebrate1
+                    }
+
+                    override suspend fun onError(errorModel: ErrorModel?) {
+                        // Do nothing, error - tough luck
+                    }
+                })
+            }
+        }
     }
 
     override suspend fun gotConversationInfoForUser(userNgEntity: UserNgEntity, conversation: Conversation?, operationStatus: GlobalServiceInterface.OperationStatus) {
@@ -124,11 +199,10 @@ class ChatViewModel constructor(application: Application,
                     val messages = result.body()?.ocs?.data
                     messages?.let {
                         for (message in it) {
-                            message.activeUser = userNgEntity
                             message.internalConversationId = conversation.databaseId
                         }
 
-                        messagesRepository.saveMessagesForConversation(it)
+                        messagesRepository.saveMessagesForConversation(user, it, false)
                     }
 
                     val xChatLastGivenHeader: String? = result.headers().get("X-Chat-Last-Given")
@@ -159,25 +233,19 @@ class ChatViewModel constructor(application: Application,
                     val messages = result.body()?.ocs?.data
                     messages?.let {
                         for (message in it) {
-                            message.activeUser = userNgEntity
                             message.internalConversationId = conversation.databaseId
                         }
 
-                       messagesRepository.saveMessagesForConversation(it)
+                        messagesRepository.saveMessagesForConversation(user, it, false)
                     }
 
-                    if (result.code() == 200) {
                         val xChatLastGivenHeader: String? = result.headers().get("X-Chat-Last-Given")
                         if (xChatLastGivenHeader != null) {
                             pullFutureMessagesForUserAndConversation(userNgEntity, conversation, xChatLastGivenHeader.toInt())
                         }
-                    } else {
-                        pullFutureMessagesForUserAndConversation(userNgEntity, conversation, lastKnownMessageId)
-                    }
                 }
 
                 override suspend fun onError(errorModel: ErrorModel?) {
-                    pullFutureMessagesForUserAndConversation(userNgEntity, conversation)
                 }
             })
         }
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/contacts/ContactsViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/contacts/ContactsViewModel.kt
index b8f31e141..c056ef3b8 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/contacts/ContactsViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/contacts/ContactsViewModel.kt
@@ -32,7 +32,7 @@ import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.ConversationOverall
 import com.nextcloud.talk.models.json.participants.AddParticipantOverall
 import com.nextcloud.talk.models.json.participants.Participant
-import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
+import com.nextcloud.talk.newarch.mvvm.BaseViewModel
 import com.nextcloud.talk.newarch.data.model.ErrorModel
 import com.nextcloud.talk.newarch.domain.usecases.AddParticipantToConversationUseCase
 import com.nextcloud.talk.newarch.domain.usecases.CreateConversationUseCase
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/groupconversation/GroupConversationViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/groupconversation/GroupConversationViewModel.kt
index 8506d3507..d575e20ae 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/groupconversation/GroupConversationViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/features/contactsflow/groupconversation/GroupConversationViewModel.kt
@@ -29,7 +29,7 @@ import androidx.lifecycle.distinctUntilChanged
 import androidx.lifecycle.viewModelScope
 import com.nextcloud.talk.models.json.conversations.ConversationOverall
 import com.nextcloud.talk.models.json.generic.GenericOverall
-import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
+import com.nextcloud.talk.newarch.mvvm.BaseViewModel
 import com.nextcloud.talk.newarch.data.model.ErrorModel
 import com.nextcloud.talk.newarch.domain.usecases.CreateConversationUseCase
 import com.nextcloud.talk.newarch.domain.usecases.SetConversationPasswordUseCase
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt
index 2676fee51..e2c1963db 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/features/conversationsList/ConversationsListViewModel.kt
@@ -34,7 +34,7 @@ import coil.transform.CircleCropTransformation
 import com.nextcloud.talk.R
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.generic.GenericOverall
-import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
+import com.nextcloud.talk.newarch.mvvm.BaseViewModel
 import com.nextcloud.talk.newarch.data.model.ErrorModel
 import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
 import com.nextcloud.talk.newarch.domain.usecases.DeleteConversationUseCase
@@ -200,9 +200,6 @@ class ConversationsListViewModel (
                     networkStateLiveData.postValue(ConversationsListViewNetworkState.LOADED)
                     val mutableList = result.toMutableList()
                     val internalUserId = globalService.currentUserLiveData.value!!.id
-                    mutableList.forEach {
-                        it.databaseUserId = internalUserId
-                    }
 
                     conversationsRepository.saveConversationsForUser(
                             internalUserId,
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ChatMessageStatusConverter.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ChatMessageStatusConverter.kt
new file mode 100644
index 000000000..b27d7884a
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/ChatMessageStatusConverter.kt
@@ -0,0 +1,45 @@
+/*
+ *
+ *  * Nextcloud Talk application
+ *  *
+ *  * @author Mario Danic
+ *  * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
+ *  *
+ *  * This program is free software: you can redistribute it and/or modify
+ *  * it under the terms of the GNU General Public License as published by
+ *  * the Free Software Foundation, either version 3 of the License, or
+ *  * at your option) any later version.
+ *  *
+ *  * This program is distributed in the hope that it will be useful,
+ *  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  * GNU General Public License for more details.
+ *  *
+ *  * You should have received a copy of the GNU General Public License
+ *  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.nextcloud.talk.newarch.local.converters
+
+import androidx.room.TypeConverter
+import com.nextcloud.talk.newarch.local.models.other.ChatMessageStatus
+
+class ChatMessageStatusConverter {
+    @TypeConverter
+    fun fromChatMessageStatusToInt(chatMessageStatus: ChatMessageStatus): Int {
+        return chatMessageStatus.ordinal
+    }
+
+    @TypeConverter
+    fun fromIntToChatMessageStatus(value: Int): ChatMessageStatus {
+        return when (value) {
+            0 -> ChatMessageStatus.SENT
+            1 -> ChatMessageStatus.RECEIVED
+            2 -> ChatMessageStatus.PENDING_MESSAGE_SEND
+            3 -> ChatMessageStatus.PENDING_FILE_UPLOAD
+            4 -> ChatMessageStatus.PENDING_FILE_SHARE
+            else -> ChatMessageStatus.FAILED
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt
index 09413444f..ce7c574a7 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/ConversationsDao.kt
@@ -41,11 +41,11 @@ abstract class ConversationsDao {
     @Query("DELETE FROM conversations WHERE user_id = :userId")
     abstract suspend fun clearConversationsForUser(userId: Long)
 
-    @Insert(onConflict = OnConflictStrategy.REPLACE)
-    abstract suspend fun saveConversationWithInsert(conversation: ConversationEntity): Long
+    @Update(onConflict = OnConflictStrategy.IGNORE)
+    abstract suspend fun update(conversation: ConversationEntity): Int
 
     @Insert(onConflict = OnConflictStrategy.REPLACE)
-    abstract suspend fun saveConversationsWithInsert(vararg conversations: ConversationEntity): List<Long>
+    abstract suspend fun insert(conversation: ConversationEntity)
 
     @Query(
             "UPDATE conversations SET changing = :changing WHERE user_id = :userId AND conversation_id = :conversationId"
@@ -88,18 +88,26 @@ abstract class ConversationsDao {
             userId: Long,
             newConversations: Array<ConversationEntity>,
             deleteOutdated: Boolean
-    ): List<Long> {
+    ) {
         val timestamp = System.currentTimeMillis()
 
         val conversationsWithTimestampApplied = newConversations.map {
             it.modifiedAt = timestamp
+            it.userId = userId
+            it.id = it.userId.toString() + "@" + it.token
             it
         }
 
-        val list = saveConversationsWithInsert(*conversationsWithTimestampApplied.toTypedArray())
+        conversationsWithTimestampApplied.forEach { internalUpsert(it) }
         if (deleteOutdated) {
             deleteConversationsForUserWithTimestamp(userId, timestamp)
         }
-        return list
+    }
+
+    private suspend fun internalUpsert(conversationEntity: ConversationEntity) {
+        val count = update(conversationEntity)
+        if (count == 0) {
+            insert(conversationEntity)
+        }
     }
 }
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/MessagesDao.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/MessagesDao.kt
index cf85cf1fa..aeadfb866 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/MessagesDao.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/MessagesDao.kt
@@ -23,21 +23,76 @@
 package com.nextcloud.talk.newarch.local.dao
 
 import androidx.lifecycle.LiveData
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
+import androidx.room.*
+import com.nextcloud.talk.newarch.local.models.ConversationEntity
 import com.nextcloud.talk.newarch.local.models.MessageEntity
+import com.nextcloud.talk.newarch.local.models.User
 
 @Dao
 abstract class MessagesDao {
-    @Query("SELECT * FROM messages WHERE conversation_id = :conversationId ORDER BY message_id ASC")
+    @Query("SELECT * FROM messages WHERE conversation_id = :conversationId ORDER BY timestamp ASC")
     abstract fun getMessagesWithUserForConversation(conversationId: String):
             LiveData<List<MessageEntity>>
 
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     abstract suspend fun saveMessages(vararg messages: MessageEntity): List<Long>
 
-    @Query("SELECT * FROM messages WHERE conversation_id = :conversationId AND message_id >= :messageId ORDER BY message_id ASC")
+    @Query("SELECT * FROM messages WHERE conversation_id = :conversationId AND id = reference_id")
+    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")
+    abstract fun getPendingMessagesLive(conversationId: String): LiveData<List<MessageEntity>>
+
+    @Query(
+            "UPDATE messages SET id = :newId WHERE conversation_id = :conversationId AND reference_id = :referenceId"
+    )
+    abstract suspend fun updateMessageId(newId: String, conversationId: String, referenceId: String)
+
+    @Query("SELECT * FROM messages WHERE conversation_id = :conversationId AND (message_id >= :messageId OR message_id = 0) ORDER BY timestamp ASC")
     abstract fun getMessagesWithUserForConversationSince(conversationId: String, messageId: Long): LiveData<List<MessageEntity>>
+
+    @Query("SELECT * FROM messages WHERE conversation_id = :conversationId AND message_id = :messageId")
+    abstract fun getMessageForConversation(conversationId: String, messageId: Long): MessageEntity?
+
+    @Query(
+            "UPDATE messages SET message_status = :status WHERE conversation_id = :conversationId AND message_id = :messageId"
+    )
+    abstract suspend fun updateMessageStatus(
+            status: Int,
+            conversationId: String,
+            messageId: Long
+    )
+
+    @Update(onConflict = OnConflictStrategy.IGNORE)
+    abstract suspend fun update(message: MessageEntity): Int
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    abstract suspend fun insert(message: MessageEntity)
+
+    @Transaction
+    open suspend fun updateMessages(user: User, messages: Array<MessageEntity>) {
+        val messagesToUpdate = messages.toMutableList()
+        if (messagesToUpdate.size > 0) {
+            val conversationId = messagesToUpdate[0].conversationId
+            val pendingMessages = getPendingMessages(conversationId)
+            val pendingMessagesReferenceIds = pendingMessages.map { it.referenceId }
+            messagesToUpdate.forEach {
+                it.referenceId?.let { referenceId ->
+                    if (pendingMessagesReferenceIds.contains(referenceId)) {
+                        updateMessageId(it.id, it.conversationId, referenceId)
+                    }
+                }
+            }
+
+            messagesToUpdate.forEach { internalUpsert(it) }
+        }
+    }
+
+    private suspend fun internalUpsert(message: MessageEntity) {
+        val count = update(message)
+        if (count == 0) {
+            insert(message)
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/db/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/db/TalkDatabase.kt
index 476655201..7b94daa86 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/local/db/TalkDatabase.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/local/db/TalkDatabase.kt
@@ -27,6 +27,8 @@ import androidx.room.Database
 import androidx.room.Room
 import androidx.room.RoomDatabase
 import androidx.room.TypeConverters
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.db.SupportSQLiteQuery
 import com.nextcloud.talk.newarch.local.converters.*
 import com.nextcloud.talk.newarch.local.dao.ConversationsDao
 import com.nextcloud.talk.newarch.local.dao.MessagesDao
@@ -48,7 +50,7 @@ import org.parceler.converter.HashMapParcelConverter
         PushConfigurationConverter::class, CapabilitiesConverter::class,
         SignalingSettingsConverter::class,
         UserStatusConverter::class, SystemMessageTypeConverter::class, ParticipantMapConverter::class,
-        HashMapHashMapConverter::class
+        HashMapHashMapConverter::class, ChatMessageStatusConverter::class
 )
 
 abstract class TalkDatabase : RoomDatabase() {
@@ -71,6 +73,12 @@ abstract class TalkDatabase : RoomDatabase() {
         private fun build(context: Context) =
                 Room.databaseBuilder(context.applicationContext, TalkDatabase::class.java, DB_NAME)
                         .fallbackToDestructiveMigration()
+                        .addCallback(object : RoomDatabase.Callback() {
+                            override fun onOpen(db: SupportSQLiteDatabase) {
+                                super.onOpen(db)
+                                db.execSQL("PRAGMA defer_foreign_keys = 1")
+                            }
+                        })
                         .build()
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt
index c43fceca5..652d0b043 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/MessageEntity.kt
@@ -26,17 +26,18 @@ import androidx.room.*
 import androidx.room.ForeignKey.CASCADE
 import com.nextcloud.talk.models.json.chat.ChatMessage
 import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType
+import com.nextcloud.talk.newarch.local.models.other.ChatMessageStatus
 
 @Entity(
         tableName = "messages",
         indices = [Index(value = ["conversation_id"])],
         foreignKeys = [ForeignKey(
                 entity = ConversationEntity::class,
+                deferred = true,
                 parentColumns = arrayOf("id"),
                 childColumns = arrayOf("conversation_id"),
                 onDelete = CASCADE,
-                onUpdate = CASCADE,
-                deferred = true
+                onUpdate = CASCADE
         )]
 )
 data class MessageEntity(
@@ -51,7 +52,9 @@ data class MessageEntity(
         @ColumnInfo(name = "messageParameters") var messageParameters: HashMap<String, HashMap<String, String>>? = null,
         @ColumnInfo(name = "parent") var parentMessage: ChatMessage? = null,
         @ColumnInfo(name = "replyable") var replyable: Boolean = false,
-        @ColumnInfo(name = "system_message_type") var systemMessageType: SystemMessageType? = null
+        @ColumnInfo(name = "system_message_type") var systemMessageType: SystemMessageType? = null,
+        @ColumnInfo(name = "reference_id") var referenceId: String? = null,
+        @ColumnInfo(name = "message_status") var chatMessageStatus: ChatMessageStatus = ChatMessageStatus.RECEIVED
 )
 
 fun MessageEntity.toChatMessage(): ChatMessage {
@@ -68,12 +71,15 @@ fun MessageEntity.toChatMessage(): ChatMessage {
     chatMessage.systemMessageType = this.systemMessageType
     chatMessage.replyable = this.replyable
     chatMessage.parentMessage = this.parentMessage
+    chatMessage.referenceId = this.referenceId
+    chatMessage.chatMessageStatus = this.chatMessageStatus
     return chatMessage
 }
 
 fun ChatMessage.toMessageEntity(): MessageEntity {
-    val messageEntity = MessageEntity(this.internalConversationId + "@" + this.jsonMessageId, this.internalConversationId!!)
-    messageEntity.messageId = this.jsonMessageId!!
+    val messageEntityId = if (this.internalMessageId != null) internalMessageId else this.internalConversationId + "@" + this.jsonMessageId
+    val messageEntity = MessageEntity(messageEntityId!!, this.internalConversationId!!)
+    messageEntity.messageId = this.jsonMessageId ?: 0
     messageEntity.actorType = this.actorType
     messageEntity.actorId = this.actorId
     messageEntity.actorDisplayName = this.actorDisplayName
@@ -83,6 +89,7 @@ fun ChatMessage.toMessageEntity(): MessageEntity {
     messageEntity.replyable = this.replyable
     messageEntity.messageParameters = this.messageParameters
     messageEntity.parentMessage = this.parentMessage
-
+    messageEntity.referenceId = this.referenceId
+    messageEntity.chatMessageStatus = this.chatMessageStatus
     return messageEntity
 }
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/other/ChatMessageStatus.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/other/ChatMessageStatus.kt
new file mode 100644
index 000000000..2ec337207
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/other/ChatMessageStatus.kt
@@ -0,0 +1,10 @@
+package com.nextcloud.talk.newarch.local.models.other
+
+enum class ChatMessageStatus {
+    SENT,
+    RECEIVED,
+    PENDING_MESSAGE_SEND,
+    PENDING_FILE_UPLOAD,
+    PENDING_FILE_SHARE,
+    FAILED
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/mvvm/BaseViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/mvvm/BaseViewModel.kt
index d38d8471d..d1b22ed1a 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/mvvm/BaseViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/mvvm/BaseViewModel.kt
@@ -20,7 +20,7 @@
  *
  */
 
-package com.nextcloud.talk.newarch.conversationsList.mvp
+package com.nextcloud.talk.newarch.mvvm
 
 import android.app.Application
 import android.content.Context
diff --git a/app/src/main/java/com/nextcloud/talk/newarch/services/GlobalService.kt b/app/src/main/java/com/nextcloud/talk/newarch/services/GlobalService.kt
index c3e3d112e..37d446729 100644
--- a/app/src/main/java/com/nextcloud/talk/newarch/services/GlobalService.kt
+++ b/app/src/main/java/com/nextcloud/talk/newarch/services/GlobalService.kt
@@ -23,39 +23,106 @@
 package com.nextcloud.talk.newarch.services
 
 import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
+import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.models.json.chat.ChatOverall
 import com.nextcloud.talk.models.json.conversations.Conversation
 import com.nextcloud.talk.models.json.conversations.ConversationOverall
 import com.nextcloud.talk.newarch.data.model.ErrorModel
+import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
 import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
+import com.nextcloud.talk.newarch.domain.repository.offline.MessagesRepository
 import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
 import com.nextcloud.talk.newarch.domain.usecases.GetConversationUseCase
 import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase
+import com.nextcloud.talk.newarch.domain.usecases.SendChatMessageUseCase
 import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
 import com.nextcloud.talk.newarch.local.models.UserNgEntity
+import com.nextcloud.talk.newarch.local.models.other.ChatMessageStatus
+import com.nextcloud.talk.newarch.local.models.toUser
+import com.nextcloud.talk.newarch.utils.NetworkComponents
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 import okhttp3.OkHttpClient
 import org.koin.core.KoinComponent
 import org.koin.core.parameter.parametersOf
+import retrofit2.Response
 import java.net.CookieManager
+import java.util.concurrent.ConcurrentHashMap
 
 class GlobalService constructor(usersRepository: UsersRepository,
                                 cookieManager: CookieManager,
-                                okHttpClient: OkHttpClient,
+                                private val okHttpClient: OkHttpClient,
+                                private val apiErrorHandler: ApiErrorHandler,
                                 private val conversationsRepository: ConversationsRepository,
+                                private val messagesRepository: MessagesRepository,
+                                private val networkComponents: NetworkComponents,
                                 private val joinConversationUseCase: JoinConversationUseCase,
                                 private val getConversationUseCase: GetConversationUseCase) : KoinComponent {
     private val applicationScope = CoroutineScope(Dispatchers.Default)
     private val previousUser: UserNgEntity? = null
     val currentUserLiveData: LiveData<UserNgEntity?> = usersRepository.getActiveUserLiveData()
-    private var currentConversation: Conversation? = null
+    private var currentConversation: MutableLiveData<Conversation?> = MutableLiveData<Conversation?>(null)
+    private val pendingMessages: LiveData<List<ChatMessage>> = Transformations.switchMap(currentConversation) { conversation ->
+        conversation?.let {
+            messagesRepository.getPendingMessagesForConversation(it.databaseId!!)
+        }
+    }
+
+    private var messagesOperations: ConcurrentHashMap<String, Pair<ChatMessage, Int>> = ConcurrentHashMap<String, Pair<ChatMessage, Int>>()
 
     init {
+        pendingMessages.observeForever { chatMessages ->
+            for (chatMessage in chatMessages) {
+                if (!messagesOperations.contains(chatMessage.internalMessageId) || messagesOperations[chatMessage.internalMessageId]?.first != chatMessage) {
+                    messagesOperations[chatMessage.internalMessageId!!] = Pair(chatMessage, 0)
+                    applicationScope.launch {
+                        sendMessage(chatMessage)
+                    }
+                }
+            }
+        }
+
         currentUserLiveData.observeForever { user ->
             user?.let {
                 if (it.id != previousUser?.id) {
                     cookieManager.cookieStore.removeAll()
-                    currentConversation = null
+                    currentConversation.postValue(null)
+                }
+            }
+        }
+    }
+
+    suspend fun sendMessage(chatMessage: ChatMessage) {
+        val currentUser = currentUserLiveData.value?.toUser()
+        val conversation = currentConversation.value
+        val operationChatMessage = messagesOperations[chatMessage.internalMessageId]
+
+        operationChatMessage?.let { pair ->
+            conversation?.let { conversation ->
+                if (pair.second == 4) {
+                    messagesOperations.remove(pair.first.internalMessageId)
+                    messagesRepository.updateMessageStatus(ChatMessageStatus.FAILED.ordinal, conversation.databaseId!!, pair.first.jsonMessageId!!)
+                } else {
+                    currentUser?.let { user ->
+                        if (chatMessage.internalConversationId == conversation.databaseId && conversation.databaseUserId == currentUser.id) {
+                            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>> {
+                                override suspend fun onSuccess(result: Response<ChatOverall>) {
+                                    messagesOperations.remove(pair.first.internalMessageId!!)
+                                    messagesRepository.updateMessageStatus(ChatMessageStatus.SENT.ordinal, conversation.databaseId!!, pair.first.jsonMessageId!!)
+                                }
+
+                                override suspend fun onError(errorModel: ErrorModel?) {
+                                    val newValue = operationChatMessage.second + 1
+                                    messagesOperations[pair.first.internalMessageId!!] = Pair(chatMessage, newValue)
+                                    sendMessage(chatMessage)
+                                }
+                            })
+                        }
+                    }
                 }
             }
         }
@@ -63,6 +130,7 @@ class GlobalService constructor(usersRepository: UsersRepository,
 
     suspend fun getConversation(conversationToken: String, globalServiceInterface: GlobalServiceInterface) {
         val currentUser = currentUserLiveData.value
+        val getConversationUseCase = GetConversationUseCase(networkComponents.getRepository(true, currentUser!!.toUser()), apiErrorHandler)
         getConversationUseCase.invoke(applicationScope, parametersOf(
                 currentUser,
                 conversationToken
@@ -72,6 +140,7 @@ class GlobalService constructor(usersRepository: UsersRepository,
                         currentUser?.let {
                             conversationsRepository.saveConversationsForUser(it.id, listOf(result.ocs.data), false)
                             globalServiceInterface.gotConversationInfoForUser(it, result.ocs.data, GlobalServiceInterface.OperationStatus.STATUS_OK)
+
                         }
                     }
 
@@ -80,11 +149,13 @@ class GlobalService constructor(usersRepository: UsersRepository,
                             globalServiceInterface.gotConversationInfoForUser(it, null, GlobalServiceInterface.OperationStatus.STATUS_FAILED)
                         }
                     }
+
                 })
     }
 
     suspend fun joinConversation(conversationToken: String, conversationPassword: String?, globalServiceInterface: GlobalServiceInterface) {
         val currentUser = currentUserLiveData.value
+        val joinConversationUseCase = JoinConversationUseCase(networkComponents.getRepository(true, currentUser!!.toUser()), apiErrorHandler)
         joinConversationUseCase.invoke(applicationScope, parametersOf(
                 currentUser,
                 conversationToken,
@@ -94,14 +165,14 @@ class GlobalService constructor(usersRepository: UsersRepository,
                     override suspend fun onSuccess(result: ConversationOverall) {
                         currentUser?.let {
                             conversationsRepository.saveConversationsForUser(it.id, listOf(result.ocs.data), false)
-                            currentConversation = conversationsRepository.getConversationForUserWithToken(it.id, result.ocs!!.data!!.token!!)
-                            globalServiceInterface.joinedConversationForUser(it, currentConversation, GlobalServiceInterface.OperationStatus.STATUS_OK)
+                            currentConversation.postValue(conversationsRepository.getConversationForUserWithToken(it.id, result.ocs!!.data!!.token!!))
+                            globalServiceInterface.joinedConversationForUser(it, currentConversation.value, GlobalServiceInterface.OperationStatus.STATUS_OK)
                         }
                     }
 
                     override suspend fun onError(errorModel: ErrorModel?) {
                         currentUser?.let {
-                            globalServiceInterface.joinedConversationForUser(it, currentConversation, GlobalServiceInterface.OperationStatus.STATUS_FAILED)
+                            globalServiceInterface.joinedConversationForUser(it, currentConversation.value, GlobalServiceInterface.OperationStatus.STATUS_FAILED)
                         }
                     }
                 })
diff --git a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt
index 3469b0e48..f99686e85 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.kt
@@ -291,15 +291,15 @@ object DisplayUtils {
             val start = stringText.indexOf(m.group(), lastStartIndex)
             val end = start + m.group().length
             lastStartIndex = end
-            mentionChipSpan = Spans.MentionChipSpan(
+            /*mentionChipSpan = Spans.MentionChipSpan(
                     getDrawableForMentionChipSpan(
                             context,
-                            id, label, conversationUser, type, chipXmlRes, null
+                            id, label, conversationUser, chipXmlRes, null
                     ),
                     BetterImageSpan.ALIGN_CENTER, id,
                     label
-            )
-            spannableString.setSpan(mentionChipSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+            )*/
+            //spannableString.setSpan(mentionChipSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
             if ("user" == type && conversationUser.userId != id) {
                 spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
             }
diff --git a/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java b/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java
index 093cb16e4..ae4c66a4c 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java
@@ -34,12 +34,14 @@ public class Spans {
     public static class MentionChipSpan extends BetterImageSpan {
         public String id;
         public CharSequence label;
+        public String type;
 
         public MentionChipSpan(@NonNull Drawable drawable, int verticalAlignment, String id,
-                               CharSequence label) {
+                               CharSequence label, String type) {
             super(drawable, verticalAlignment);
             this.id = id;
             this.label = label;
+            this.type = type;
         }
     }
 }
diff --git a/app/src/main/res/layout/rv_chat_item.xml b/app/src/main/res/layout/rv_chat_item.xml
index 85afe4019..3cd66d3d4 100644
--- a/app/src/main/res/layout/rv_chat_item.xml
+++ b/app/src/main/res/layout/rv_chat_item.xml
@@ -64,14 +64,37 @@
         android:layout_below="@id/previewImage"
         tools:text="Just another chat message"/>
 
+    <ProgressBar
+        android:layout_width="12dp"
+        android:layout_height="12dp"
+        android:id="@+id/sendingProgressBar"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginEnd="8dp"
+        android:visibility="gone"
+        android:progressBackgroundTint="@color/colorPrimary"/>
+
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignParentEnd="true"
         android:layout_below="@id/chatMessage"
         android:textSize="10sp"
+        android:layout_alignParentBottom="true"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_toStartOf="@id/sendingProgressBar"
         android:id="@+id/messageTime"
         android:layout_marginEnd="8dp"
         tools:text="12:30"/>
 
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="8dp"
+        android:text="@string/nc_failed_to_send"
+        android:layout_below="@id/messageTime"
+        android:id="@+id/failedToSendNotice"
+        android:visibility="gone"
+        android:layout_alignParentEnd="true"/>
+
+
 </RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7493c0310..3a1d3e960 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -346,4 +346,5 @@
     <string name="nc_search_empty_contacts">Where did they all hide?</string>
     <string name="nc_reject_call">Reject</string>
     <string name="silenced_by_moderator">You were silenced by a moderator</string>
+    <string name="nc_failed_to_send">Failed to sent - tap to retry sending.</string>
 </resources>