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;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
public class ChatUtils {
@ -40,4 +43,21 @@ public class ChatUtils {
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.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.chatMessage.text = 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.mention.Mention
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.getMaxMessageLength
import com.nextcloud.talk.newarch.local.models.toUserEntity
@ -125,6 +126,10 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
showConversationInfoScreen()
}
private lateinit var user: User
private lateinit var conversationToken: String
private var conversationPassword: String? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup
@ -134,8 +139,6 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
viewModel = viewModelProvider(factory).get(ChatViewModel::class.java)
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)
.addSource(ChatViewLiveDataSource(viewModel.messagesLiveData))
.addSource(ChatDateHeaderSource(activity as Context, ChatElementTypes.DATE_HEADER.ordinal))
@ -218,6 +221,7 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
if (shouldShowLobby) {
view.messagesRecyclerView?.visibility = View.GONE
view.messageInputView?.visibility = View.GONE
view.separator?.visibility = View.GONE
view.lobbyView?.visibility = View.VISIBLE
val timer = conversation.lobbyTimer
val unit = if (timer != null && timer != 0L) {
@ -235,8 +239,10 @@ class ChatView(private val bundle: Bundle) : BaseView(), ImageLoaderInterface {
if (isReadOnlyConversation) {
view.messageInputView?.visibility = View.GONE
view.separator?.visibility = View.GONE
} else {
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) {
super.onAttach(view)
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()
toolbar?.setOnClickListener(toolbarOnClickListener)
viewModel.joinConversation()
viewModel.joinConversation(user, conversationToken, conversationPassword)
}
override fun onDetach(view: View) {

View File

@ -65,14 +65,14 @@ class ChatViewModel constructor(application: Application,
private val messagesRepository: MessagesRepository,
private val globalService: GlobalService) : BaseViewModel<ChatView>(application), GlobalServiceInterface {
lateinit var user: User
val conversation: MutableLiveData<Conversation?> = MutableLiveData()
val conversation: MutableLiveData<Conversation?> = MutableLiveData<Conversation?>()
var pastStartingPoint: Long = -1
val futureStartingPoint: MutableLiveData<Long> = MutableLiveData()
private var initConversation: Conversation? = null
val messagesLiveData = Transformations.switchMap(futureStartingPoint) { futureStartingPoint ->
conversation.value?.let {
messagesRepository.getMessagesWithUserForConversationSince(it.databaseId!!, futureStartingPoint).map { chatMessagesList ->
val messagesLiveData = Transformations.switchMap(conversation) { conversation ->
conversation?.let {
messagesRepository.getMessagesWithUserForConversation(it.databaseId!!).map { chatMessagesList ->
chatMessagesList.map { chatMessage ->
chatMessage.activeUser = user.toUserEntity()
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
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?) {
val messageParameters = hashMapOf<String, HashMap<String, String>>()
val mentionSpans = editable.getSpans(
@ -143,7 +134,7 @@ class ChatViewModel constructor(application: Application,
chatMessage.actorDisplayName = user.displayName
chatMessage.message = editable.toString()
chatMessage.systemMessageType = null
chatMessage.chatMessageStatus = ChatMessageStatus.PENDING_MESSAGE_SEND
chatMessage.chatMessageStatus = ChatMessageStatus.PENDING
if (replyTo != null) {
chatMessage.parentMessage = messagesRepository.getMessageForConversation(conversationDatabaseId, replyTo)
} else {
@ -170,11 +161,12 @@ class ChatViewModel constructor(application: Application,
}
}
fun joinConversation() {
initConversation?.token?.let {
viewModelScope.launch {
globalService.getConversation(it, this@ChatViewModel)
}
fun joinConversation(user: User, conversationToken: String, conversationPassword: String?) {
viewModelScope.launch {
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) {
0 -> ChatMessageStatus.SENT
1 -> ChatMessageStatus.RECEIVED
2 -> ChatMessageStatus.PENDING_MESSAGE_SEND
3 -> ChatMessageStatus.PENDING_FILE_UPLOAD
4 -> ChatMessageStatus.PENDING_FILE_SHARE
2 -> ChatMessageStatus.PENDING
3 -> ChatMessageStatus.PROCESSING
4 -> ChatMessageStatus.FAILED_ONCE
5 -> ChatMessageStatus.FAILED_TWICE
else -> ChatMessageStatus.FAILED
}
}

View File

@ -40,7 +40,7 @@ abstract class MessagesDao {
@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")
@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>>
@Query(

View File

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

View File

@ -22,11 +22,13 @@
package com.nextcloud.talk.newarch.services
import android.util.Log
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.chat.ChatUtils
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.ConversationOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
@ -74,16 +76,12 @@ class GlobalService constructor(usersRepository: UsersRepository,
}
}
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)
}
applicationScope.launch {
sendMessage(chatMessage)
}
}
}
@ -101,30 +99,36 @@ class GlobalService constructor(usersRepository: UsersRepository,
suspend fun sendMessage(chatMessage: ChatMessage) {
val currentUser = currentUserLiveData.value?.toUser()
val conversation = currentConversation.value
val operationChatMessage = messagesOperations[chatMessage.internalMessageId]
operationChatMessage?.let { pair ->
chatMessage.let { chatMessage ->
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!!)
}
val currentStatus = chatMessage.chatMessageStatus
applicationScope.launch {
//messagesRepository.updateMessageStatus(ChatMessageStatus.PROCESSING.ordinal, conversation.databaseId!!, chatMessage.jsonMessageId!!)
}
currentUser?.let { user ->
if (chatMessage.internalConversationId == conversation.databaseId && conversation.databaseUserId == currentUser.id) {
val sendChatMessageUseCase = SendChatMessageUseCase(networkComponents.getRepository(false, user), ApiErrorHandler())
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>) {
messagesRepository.updateMessageStatus(ChatMessageStatus.SENT.ordinal, conversation.databaseId!!, chatMessage.jsonMessageId!!)
}
override suspend fun onError(errorModel: ErrorModel?) {
val newValue = operationChatMessage.second + 1
messagesOperations[pair.first.internalMessageId!!] = Pair(chatMessage, newValue)
sendMessage(chatMessage)
override suspend fun onError(errorModel: ErrorModel?) {
when (currentStatus) {
ChatMessageStatus.PENDING -> {
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!!)
}
}
})
}
}
})
}
}
}
@ -134,7 +138,7 @@ class GlobalService constructor(usersRepository: UsersRepository,
suspend fun exitConversation(conversationToken: String, globalServiceInterface: GlobalServiceInterface) {
val currentUser = currentUserLiveData.value!!.toUser()
val exitConversationUseCase = ExitConversationUseCase(networkComponents.getRepository(true, currentUser), ApiErrorHandler())
exitConversationUseCase.invoke(applicationScope, parametersOf(currentUser, conversationToken), object: UseCaseResponse<GenericOverall> {
exitConversationUseCase.invoke(applicationScope, parametersOf(currentUser, conversationToken), object : UseCaseResponse<GenericOverall> {
override suspend fun onSuccess(result: GenericOverall) {
globalServiceInterface.leftConversationForUser(currentUser, currentConversation.value, GlobalServiceInterface.OperationStatus.STATUS_OK)
withContext(Dispatchers.Main) {
@ -147,6 +151,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())

View File

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

View File

@ -79,9 +79,9 @@
android:layout_height="wrap_content"
android:layout_below="@id/chatMessage"
android:textSize="10sp"
android:layout_alignParentBottom="true"
android:layout_alignWithParentIfMissing="true"
android:layout_above="@id/failedToSendNotice"
android:layout_toStartOf="@id/sendingProgressBar"
android:layout_alignWithParentIfMissing="true"
android:id="@+id/messageTime"
android:layout_marginEnd="8dp"
tools:text="12:30"/>
@ -91,9 +91,11 @@
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
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:visibility="gone"
android:visibility="visible"
android:layout_alignParentEnd="true"/>