mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-28 23:25:23 +01:00
Most things simply work
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
19fff9689d
commit
7102f4ea54
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
@ -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())
|
||||
|
@ -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"
|
||||
|
@ -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"/>
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user