From dcc8fc6954914dcb11ee29f70d00574dea4950aa Mon Sep 17 00:00:00 2001 From: Julius Linus Date: Tue, 6 Feb 2024 07:30:27 -0600 Subject: [PATCH] Major refactoring, implements ChatActivity in MVVM to prepare for federation and offline chatting. Signed-off-by: Julius Linus --- .../com/nextcloud/talk/chat/ChatActivity.kt | 823 ++++++++---------- .../talk/chat/data/ChatRepository.kt | 18 + .../NetworkChatRepositoryImpl.kt} | 53 +- .../talk/chat/viewmodels/ChatViewModel.kt | 377 +++++++- .../talk/dagger/modules/RepositoryModule.kt | 4 +- .../talk/models/json/chat/ChatUtils.kt | 1 + 6 files changed, 774 insertions(+), 502 deletions(-) rename app/src/main/java/com/nextcloud/talk/chat/data/{ChatRepositoryImpl.kt => network/NetworkChatRepositoryImpl.kt} (70%) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index d3cb4165d..9dc649dec 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -167,20 +167,14 @@ import com.nextcloud.talk.models.domain.ConversationReadOnlyState import com.nextcloud.talk.models.domain.ConversationType import com.nextcloud.talk.models.domain.LobbyState import com.nextcloud.talk.models.domain.ObjectType -import com.nextcloud.talk.models.domain.ReactionAddedModel -import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.json.chat.ChatMessage import com.nextcloud.talk.models.json.chat.ChatOverall -import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.chat.ReadStatus -import com.nextcloud.talk.models.json.conversations.RoomOverall -import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.mention.Mention import com.nextcloud.talk.models.json.signaling.NCSignalingMessage import com.nextcloud.talk.polls.ui.PollCreateDialogFragment import com.nextcloud.talk.presenters.MentionAutocompletePresenter import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity -import com.nextcloud.talk.repositories.reactions.ReactionsRepository import com.nextcloud.talk.shareditems.activities.SharedItemsActivity import com.nextcloud.talk.signaling.SignalingMessageReceiver import com.nextcloud.talk.signaling.SignalingMessageSender @@ -240,10 +234,6 @@ import com.stfalcon.chatkit.messages.MessageHolders.ContentChecker import com.stfalcon.chatkit.messages.MessagesListAdapter import com.stfalcon.chatkit.utils.DateFormatter import com.vanniktech.emoji.EmojiPopup -import io.reactivex.Observer -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.BehaviorSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -290,9 +280,6 @@ class ChatActivity : @Inject lateinit var currentUserProvider: CurrentUserProviderNew - @Inject - lateinit var reactionsRepository: ReactionsRepository - @Inject lateinit var permissionUtil: PlatformPermissionUtil @@ -553,6 +540,15 @@ class ChatActivity : binding.messageInputView.messageInput.setSelection(cursor) } this.lifecycle.addObserver(AudioUtils) + this.lifecycle.addObserver(ChatViewModel.LifeCycleObserver) + + chatViewModel.refreshChatParams( + setupFieldsForPullChatMessages( + false, + 0, + false + ) + ) } override fun onStop() { @@ -579,10 +575,13 @@ class ChatActivity : } } this.lifecycle.removeObserver(AudioUtils) + this.lifecycle.removeObserver(ChatViewModel.LifeCycleObserver) } + @SuppressLint("NotifyDataSetChanged") @Suppress("LongMethod") private fun initObservers() { + Log.d(TAG, "initObservers Called") chatViewModel.getRoomViewState.observe(this) { state -> when (state) { is ChatViewModel.GetRoomSuccessState -> { @@ -650,12 +649,6 @@ class ChatActivity : logConversationInfos("joinRoomWithPassword#onNext") - if (isFirstMessagesProcessing) { - pullChatMessages(false) - } else { - pullChatMessages(true, false) - } - if (webSocketInstance != null) { webSocketInstance?.joinRoomWithRoomTokenAndSession( roomToken, @@ -680,6 +673,299 @@ class ChatActivity : else -> {} } } + + chatViewModel.leaveRoomViewState.observe(this) { state -> + when (state) { + is ChatViewModel.LeaveRoomSuccessState -> { + logConversationInfos("leaveRoom#onNext") + + sendStopTypingMessage() + + checkingLobbyStatus = false + + if (getRoomInfoTimerHandler != null) { + getRoomInfoTimerHandler?.removeCallbacksAndMessages(null) + } + + if (webSocketInstance != null && currentConversation != null) { + webSocketInstance?.joinRoomWithRoomTokenAndSession( + "", + sessionIdAfterRoomJoined + ) + } + + sessionIdAfterRoomJoined = "0" + + if (state.funToCallWhenLeaveSuccessful != null) { + Log.d(TAG, "a callback action was set and is now executed because room was left successfully") + state.funToCallWhenLeaveSuccessful.invoke() + } + } + + else -> {} + } + } + + chatViewModel.sendChatMessageViewState.observe(this) { state -> + when (state) { + is ChatViewModel.SendChatMessageSuccessState -> { + myFirstMessage = state.message + + if (binding.popupBubbleView.isShown == true) { + binding.popupBubbleView.hide() + } + binding.messagesListView.smoothScrollToPosition(0) + } + is ChatViewModel.SendChatMessageErrorState -> { + if (state.e is HttpException) { + val code = state.e.code() + if (code.toString().startsWith("2")) { + myFirstMessage = state.message + + if (binding.popupBubbleView.isShown == true) { + binding.popupBubbleView.hide() + } + + binding.messagesListView.smoothScrollToPosition(0) + } + } + } + else -> {} + } + } + + chatViewModel.deleteChatMessageViewState.observe(this) { state -> + when (state) { + is ChatViewModel.DeleteChatMessageSuccessState -> { + if (state.msg.ocs!!.meta!!.statusCode == HttpURLConnection.HTTP_ACCEPTED) { + Snackbar.make( + binding.root, + R.string.nc_delete_message_leaked_to_matterbridge, + Snackbar.LENGTH_LONG + ).show() + } + + chatViewModel.refreshChatParams( + setupFieldsForPullChatMessages( + true, + globalLastKnownFutureMessageId, + true + ) + ) + } + is ChatViewModel.DeleteChatMessageErrorState -> { + Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() + } + else -> {} + } + } + + chatViewModel.createRoomViewState.observe(this) { state -> + when (state) { + is ChatViewModel.CreateRoomSuccessState -> { + val bundle = Bundle() + bundle.putString(KEY_ROOM_TOKEN, state.roomOverall.ocs!!.data!!.token) + bundle.putString(KEY_ROOM_ID, state.roomOverall.ocs!!.data!!.roomId) + + leaveRoom { + val chatIntent = Intent(context, ChatActivity::class.java) + chatIntent.putExtras(bundle) + chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(chatIntent) + } + } + is ChatViewModel.CreateRoomErrorState -> { + Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() + } + else -> {} + } + } + + var apiVersion = 1 + // FIXME this is a best guess, guests would need to get the capabilities themselves + if (conversationUser != null) { + apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) + } + + chatViewModel.getFieldMapForChat.observe(this) { _ -> + chatViewModel.pullChatMessages( + credentials!!, + ApiUtils.getUrlForChat(apiVersion, conversationUser?.baseUrl, roomToken) + ) + } + + chatViewModel.pullChatMessageViewState.observe(this) { state -> + when (state) { + is ChatViewModel.PullChatMessageSuccessState -> { + Log.d(TAG, "PullChatMessageSuccess: Code: ${state.response.code()}") + when (state.response.code()) { + HTTP_CODE_OK -> { + Log.d(TAG, "lookIntoFuture: ${state.lookIntoFuture}") + + processHeaderChatLastGiven(state.response, state.lookIntoFuture) + + val chatOverall = state.response.body() as ChatOverall? + var chatMessageList = chatOverall?.ocs!!.data!! + chatMessageList = handleSystemMessages(chatMessageList) + + if (chatMessageList.size == 0) { + chatViewModel.refreshChatParams( + setupFieldsForPullChatMessages( + true, + globalLastKnownFutureMessageId, + true + ) + ) + return@observe + } + + determinePreviousMessageIds(chatMessageList) + + handleExpandableSystemMessages(chatMessageList) + + if (chatMessageList.isNotEmpty() && + ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType + ) { + adapter?.clear() + adapter?.notifyDataSetChanged() + } + + var lastAdapterId = 0 + if (adapter?.items?.size != 0) { + val item = adapter?.items?.get(0)?.item + if (item != null) { + lastAdapterId = (item as ChatMessage).jsonMessageId + } else { + lastAdapterId = 0 + } + } + + if ( + state.lookIntoFuture && + lastAdapterId != 0 && + chatMessageList[0].jsonMessageId > lastAdapterId + ) { + processMessagesFromTheFuture(chatMessageList) + } else if (!state.lookIntoFuture) { + processMessagesNotFromTheFuture(chatMessageList) + collapseSystemMessages() + } + + updateReadStatusOfAllMessages(chatMessageList[0].jsonMessageId) + + processCallStartedMessages(chatMessageList) + + adapter?.notifyDataSetChanged() + + chatViewModel.refreshChatParams( + setupFieldsForPullChatMessages( + true, + chatMessageList[0].jsonMessageId, + true + ) + ) + } + HTTP_CODE_NOT_MODIFIED -> { + processHeaderChatLastGiven(state.response, state.lookIntoFuture) + chatViewModel.refreshChatParams( + setupFieldsForPullChatMessages( + state.lookIntoFuture, + globalLastKnownFutureMessageId, + true + ) + ) + } + HTTP_CODE_PRECONDITION_FAILED -> { + processHeaderChatLastGiven(state.response, state.lookIntoFuture) + chatViewModel.refreshChatParams( + setupFieldsForPullChatMessages( + state.lookIntoFuture, + globalLastKnownFutureMessageId, + true + ) + ) + } + else -> {} + } + + processExpiredMessages() + if (isFirstMessagesProcessing) { + cancelNotificationsForCurrentConversation() + isFirstMessagesProcessing = false + binding.progressBar.visibility = View.GONE + binding.messagesListView.visibility = View.VISIBLE + + collapseSystemMessages() + } + } + is ChatViewModel.PullChatMessageCompleteState -> { + Log.d(TAG, "PullChatMessageCompleted") + } + is ChatViewModel.PullChatMessageErrorState -> { + Log.d(TAG, "PullChatMessageError") + } + else -> {} + } + } + + chatViewModel.reactionDeletedViewState.observe(this) { state -> + when (state) { + is ChatViewModel.ReactionDeletedSuccessState -> { + updateUiToDeleteReaction( + state.reactionDeletedModel.chatMessage, + state.reactionDeletedModel.emoji + ) + } + else -> {} + } + } + + chatViewModel.reactionAddedViewState.observe(this) { state -> + when (state) { + is ChatViewModel.ReactionAddedSuccessState -> { + updateUiToAddReaction( + state.reactionAddedModel.chatMessage, + state.reactionAddedModel.emoji + ) + } + else -> {} + } + } + + chatViewModel.editMessageViewState.observe(this) { state -> + when (state) { + is ChatViewModel.EditMessageSuccessState -> { + when (state.messageEdited.ocs?.meta?.statusCode) { + HTTP_BAD_REQUEST -> { + Snackbar.make( + binding.root, + getString(R.string.edit_error_24_hours_old_message), + Snackbar.LENGTH_LONG + ).show() + } + HTTP_FORBIDDEN -> { + Snackbar.make( + binding.root, + getString(R.string.conversation_is_read_only), + Snackbar.LENGTH_LONG + ).show() + } + HTTP_NOT_FOUND -> { + Snackbar.make( + binding.root, + "Conversation not found", + Snackbar.LENGTH_LONG + ).show() + } + } + clearEditUI() + } + is ChatViewModel.EditMessageErrorState -> { + Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() + } + else -> {} + } + } } @Suppress("Detekt.TooGenericExceptionCaught") @@ -770,7 +1056,6 @@ class ChatActivity : initMessageInputView() loadAvatarForStatusBar() setActionBarTitle() - viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar) } @@ -893,57 +1178,16 @@ class ChatActivity : apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) } - ncApi.editChatMessage( - credentials, + chatViewModel.editChatMessage( + credentials!!, ApiUtils.getUrlForChatMessage( apiVersion, conversationUser?.baseUrl, roomToken, - message?.id + message.id ), editedMessageText - )?.subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } - - override fun onNext(messageEdited: ChatOverallSingleMessage) { - when (messageEdited.ocs?.meta?.statusCode) { - HTTP_BAD_REQUEST -> { - Snackbar.make( - binding.root, - getString(R.string.edit_error_24_hours_old_message), - Snackbar.LENGTH_LONG - ).show() - } - HTTP_FORBIDDEN -> { - Snackbar.make( - binding.root, - getString(R.string.conversation_is_read_only), - Snackbar.LENGTH_LONG - ).show() - } - HTTP_NOT_FOUND -> { - Snackbar.make( - binding.root, - "Conversation not found", - Snackbar.LENGTH_LONG - ).show() - } - } - clearEditUI() - } - - override fun onError(e: Throwable) { - Log.e(TAG, "failed to edit message", e) - Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() - } - - override fun onComplete() { - } - }) + ) } private fun setEditUI() { @@ -2631,13 +2875,6 @@ class ChatActivity : binding.lobby.lobbyView.visibility = View.GONE binding.messagesListView.visibility = View.VISIBLE binding.messageInputView.inputEditText?.visibility = View.VISIBLE - if (isFirstMessagesProcessing && pastPreconditionFailed) { - pastPreconditionFailed = false - pullChatMessages(false) - } else if (futurePreconditionFailed) { - futurePreconditionFailed = false - pullChatMessages(true) - } } } else { binding.lobby.lobbyView.visibility = View.GONE @@ -2818,7 +3055,7 @@ class ChatActivity : it.item is ChatMessage && (it.item as ChatMessage).id == messageId } if (position != null && position >= 0) { - binding.messagesListView.smoothScrollToPosition(position) + binding.messagesListView.scrollToPosition(position) } else { // TODO show error that we don't have that message? } @@ -3212,12 +3449,6 @@ class ChatActivity : sessionIdAfterRoomJoined ) } - - if (isFirstMessagesProcessing) { - pullChatMessages(false) - } else { - pullChatMessages(true) - } } } @@ -3232,57 +3463,15 @@ class ChatActivity : val startNanoTime = System.nanoTime() Log.d(TAG, "leaveRoom - leaveRoom - calling: $startNanoTime") - ncApi.leaveRoom( - credentials, + chatViewModel.leaveRoom( + credentials!!, ApiUtils.getUrlForParticipantsActive( apiVersion, conversationUser?.baseUrl, roomToken - ) + ), + funToCallWhenLeaveSuccessful ) - ?.subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - disposables.add(d) - } - - override fun onNext(genericOverall: GenericOverall) { - Log.d(TAG, "leaveRoom - leaveRoom - got response: $startNanoTime") - logConversationInfos("leaveRoom#onNext") - - sendStopTypingMessage() - - checkingLobbyStatus = false - - if (getRoomInfoTimerHandler != null) { - getRoomInfoTimerHandler?.removeCallbacksAndMessages(null) - } - - if (webSocketInstance != null && currentConversation != null) { - webSocketInstance?.joinRoomWithRoomTokenAndSession( - "", - sessionIdAfterRoomJoined - ) - } - - sessionIdAfterRoomJoined = "0" - - if (funToCallWhenLeaveSuccessful != null) { - Log.d(TAG, "a callback action was set and is now executed because room was left successfully") - funToCallWhenLeaveSuccessful() - } - } - - override fun onError(e: Throwable) { - Log.e(TAG, "leaveRoom - leaveRoom - ERROR", e) - } - - override fun onComplete() { - Log.d(TAG, "leaveRoom - leaveRoom - completed: $startNanoTime") - disposables.dispose() - } - }) } private fun submitMessage(sendWithoutNotification: Boolean) { @@ -3326,50 +3515,14 @@ class ChatActivity : if (conversationUser != null) { val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) - ncApi.sendChatMessage( - credentials, + chatViewModel.sendChatMessage( + credentials!!, ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl, roomToken), message, - conversationUser!!.displayName, - replyTo, + conversationUser!!.displayName ?: "", + replyTo ?: 0, sendWithoutNotification ) - ?.subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } - - @Suppress("Detekt.TooGenericExceptionCaught") - override fun onNext(genericOverall: GenericOverall) { - myFirstMessage = message - - if (binding.popupBubbleView.isShown == true) { - binding.popupBubbleView.hide() - } - binding.messagesListView.smoothScrollToPosition(0) - } - - override fun onError(e: Throwable) { - if (e is HttpException) { - val code = e.code() - if (code.toString().startsWith("2")) { - myFirstMessage = message - - if (binding.popupBubbleView.isShown == true) { - binding.popupBubbleView.hide() - } - - binding.messagesListView.smoothScrollToPosition(0) - } - } - } - - override fun onComplete() { - // unused atm - } - }) } showMicrophoneButton(true) } @@ -3387,141 +3540,6 @@ class ChatActivity : signalingMessageSender = webSocketInstance?.signalingMessageSender } - fun pullChatMessages(lookIntoFuture: Boolean, setReadMarker: Boolean = true, xChatLastCommonRead: Int? = null) { - if (!validSessionId()) { - return - } - - Log.d(TAG, "pullChatMessages. lookIntoFuture= $lookIntoFuture") - - if (pullChatMessagesPending) { - // Sometimes pullChatMessages may be called before response to a previous call is received. - // In such cases just ignore the second call. Message processing will continue when response to the - // earlier call is received. - // More details: https://github.com/nextcloud/talk-android/pull/1766 - Log.d(TAG, "pullChatMessages - pullChatMessagesPending is true, exiting") - return - } - pullChatMessagesPending = true - - val pullChatMessagesFieldMap = setupFieldsForPullChatMessages( - lookIntoFuture, - xChatLastCommonRead, - setReadMarker - ) - - var apiVersion = 1 - // FIXME this is a best guess, guests would need to get the capabilities themselves - if (conversationUser != null) { - apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) - } - - ncApi.pullChatMessages( - credentials, - ApiUtils.getUrlForChat(apiVersion, conversationUser?.baseUrl, roomToken), - pullChatMessagesFieldMap - ) - ?.subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer> { - override fun onSubscribe(d: Disposable) { - disposables.add(d) - } - - @SuppressLint("NotifyDataSetChanged") - @Suppress("Detekt.TooGenericExceptionCaught") - override fun onNext(response: Response<*>) { - pullChatMessagesPending = false - - when (response.code()) { - HTTP_CODE_NOT_MODIFIED -> { - Log.d(TAG, "pullChatMessages - HTTP_CODE_NOT_MODIFIED.") - - if (lookIntoFuture) { - Log.d(TAG, "recursive call to pullChatMessages.") - pullChatMessages(true, setReadMarker, xChatLastCommonRead) - } - } - - HTTP_CODE_PRECONDITION_FAILED -> { - Log.d(TAG, "pullChatMessages - HTTP_CODE_PRECONDITION_FAILED.") - - if (lookIntoFuture) { - futurePreconditionFailed = true - } else { - pastPreconditionFailed = true - } - } - - HTTP_CODE_OK -> { - Log.d(TAG, "pullChatMessages - HTTP_CODE_OK.") - - val chatOverall = response.body() as ChatOverall? - - var chatMessageList = chatOverall?.ocs!!.data!! - - chatMessageList = handleSystemMessages(chatMessageList) - - determinePreviousMessageIds(chatMessageList) - - handleExpandableSystemMessages(chatMessageList) - - processHeaderChatLastGiven(response, lookIntoFuture) - - if (chatMessageList.isNotEmpty() && - ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType - ) { - adapter?.clear() - adapter?.notifyDataSetChanged() - } - - if (lookIntoFuture) { - processMessagesFromTheFuture(chatMessageList) - } else { - processMessagesNotFromTheFuture(chatMessageList) - - collapseSystemMessages() - } - - val newXChatLastCommonRead = response.headers()["X-Chat-Last-Common-Read"]?.let { - Integer.parseInt(it) - } - - processCallStartedMessages(chatMessageList) - - updateReadStatusOfAllMessages(newXChatLastCommonRead) - adapter?.notifyDataSetChanged() - - if (isFirstMessagesProcessing || lookIntoFuture) { - Log.d(TAG, "recursive call to pullChatMessages") - pullChatMessages(true, true, newXChatLastCommonRead) - } - } - } - - processExpiredMessages() - - if (isFirstMessagesProcessing) { - cancelNotificationsForCurrentConversation() - isFirstMessagesProcessing = false - binding.progressBar.visibility = View.GONE - binding.messagesListView.visibility = View.VISIBLE - - collapseSystemMessages() - } - } - - override fun onError(e: Throwable) { - Log.e(TAG, "pullChatMessages - pullChatMessages ERROR", e) - pullChatMessagesPending = false - } - - override fun onComplete() { - pullChatMessagesPending = false - } - }) - } - private fun processCallStartedMessages(chatMessageList: List) { try { val mostRecentCallSystemMessage = adapter?.items?.first { @@ -3813,7 +3831,15 @@ class ChatActivity : } override fun onLoadMore(page: Int, totalItemsCount: Int) { - pullChatMessages(false) + if (page > 1) { + chatViewModel.refreshChatParams( + setupFieldsForPullChatMessages( + false, + null, + true + ) + ) + } } override fun format(date: Date): String { @@ -4083,15 +4109,9 @@ class ChatActivity : override fun onClickReaction(chatMessage: ChatMessage, emoji: String) { VibrationUtils.vibrateShort(context) if (chatMessage.reactionsSelf?.contains(emoji) == true) { - reactionsRepository.deleteReaction(roomToken, chatMessage, emoji) - .subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(ReactionDeletedObserver()) + chatViewModel.deleteReaction(roomToken, chatMessage, emoji) } else { - reactionsRepository.addReaction(roomToken, chatMessage, emoji) - .subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(ReactionAddedObserver()) + chatViewModel.addReaction(roomToken, chatMessage, emoji) } } @@ -4106,54 +4126,6 @@ class ChatActivity : ).show() } - inner class ReactionAddedObserver : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } - - override fun onNext(reactionAddedModel: ReactionAddedModel) { - Log.d(TAG, "onNext") - if (reactionAddedModel.success) { - updateUiToAddReaction( - reactionAddedModel.chatMessage, - reactionAddedModel.emoji - ) - } - } - - override fun onError(e: Throwable) { - Log.d(TAG, "onError") - } - - override fun onComplete() { - Log.d(TAG, "onComplete") - } - } - - inner class ReactionDeletedObserver : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } - - override fun onNext(reactionDeletedModel: ReactionDeletedModel) { - Log.d(TAG, "onNext") - if (reactionDeletedModel.success) { - updateUiToDeleteReaction( - reactionDeletedModel.chatMessage, - reactionDeletedModel.emoji - ) - } - } - - override fun onError(e: Throwable) { - Log.d(TAG, "onError") - } - - override fun onComplete() { - Log.d(TAG, "onComplete") - } - } - override fun onOpenMessageActionsDialog(chatMessage: ChatMessage) { openMessageActionsDialog(chatMessage) } @@ -4199,45 +4171,16 @@ class ChatActivity : apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) } - ncApi.deleteChatMessage( - credentials, + chatViewModel.deleteChatMessages( + credentials!!, ApiUtils.getUrlForChatMessage( apiVersion, conversationUser?.baseUrl, roomToken, message?.id - ) - )?.subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } - - override fun onNext(t: ChatOverallSingleMessage) { - if (t.ocs!!.meta!!.statusCode == HttpURLConnection.HTTP_ACCEPTED) { - Snackbar.make( - binding.root, - R.string.nc_delete_message_leaked_to_matterbridge, - Snackbar.LENGTH_LONG - ).show() - } - } - - override fun onError(e: Throwable) { - Log.e( - TAG, - "Something went wrong when trying to delete message with id " + - message?.id, - e - ) - Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() - } - - override fun onComplete() { - // unused atm - } - }) + ), + message?.id!! + ) } } @@ -4252,39 +4195,11 @@ class ChatActivity : message?.user?.id?.substring(INVITE_LENGTH), null ) - ncApi.createRoom( - credentials, - retrofitBucket.url, - retrofitBucket.queryMap + chatViewModel.createRoom( + credentials!!, + retrofitBucket.url!!, + retrofitBucket.queryMap!! ) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } - - override fun onNext(roomOverall: RoomOverall) { - val bundle = Bundle() - bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token) - bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId) - - leaveRoom { - val chatIntent = Intent(context, ChatActivity::class.java) - chatIntent.putExtras(bundle) - chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - startActivity(chatIntent) - } - } - - override fun onError(e: Throwable) { - Log.e(TAG, e.message, e) - } - - override fun onComplete() { - // unused atm - } - }) } fun forwardMessage(message: IMessage?) { @@ -4311,8 +4226,8 @@ class ChatActivity : fun markAsUnread(message: IMessage?) { val chatMessage = message as ChatMessage? if (chatMessage!!.previousMessageId > NO_PREVIOUS_MESSAGE_ID) { - ncApi.setChatReadMarker( - credentials, + chatViewModel.setChatReadMarker( + credentials!!, ApiUtils.getUrlForChatReadMarker( ApiUtils.getChatApiVersion(conversationUser, intArrayOf(ApiUtils.APIv1)), conversationUser?.baseUrl, @@ -4320,25 +4235,6 @@ class ChatActivity : ), chatMessage.previousMessageId ) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } - - override fun onNext(t: GenericOverall) { - // unused atm - } - - override fun onError(e: Throwable) { - Log.e(TAG, e.message, e) - } - - override fun onComplete() { - // unused atm - } - }) } } @@ -4744,40 +4640,11 @@ class ChatActivity : null ) - ncApi.createRoom( - credentials, - retrofitBucket.url, - retrofitBucket.queryMap + chatViewModel.createRoom( + credentials!!, + retrofitBucket.url!!, + retrofitBucket.queryMap!! ) - ?.subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } - - override fun onNext(roomOverall: RoomOverall) { - val bundle = Bundle() - bundle.putString(KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token) - bundle.putString(KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId) - - leaveRoom { - val chatIntent = Intent(context, ChatActivity::class.java) - chatIntent.putExtras(bundle) - chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - startActivity(chatIntent) - } - } - - override fun onError(e: Throwable) { - Log.e(TAG, "error after clicking on user mention chip", e) - Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() - } - - override fun onComplete() { - // unused atm - } - }) } } @@ -4872,7 +4739,7 @@ class ChatActivity : } companion object { - private val TAG = ChatActivity::class.simpleName + val TAG = ChatActivity::class.simpleName private const val CONTENT_TYPE_CALL_STARTED: Byte = 1 private const val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 2 private const val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 3 diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt index 1cd1860d7..88eae5d4e 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt @@ -22,11 +22,15 @@ package com.nextcloud.talk.chat.data import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage +import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomsOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.reminder.Reminder import io.reactivex.Observable +import retrofit2.Response +@Suppress("LongParameterList", "TooManyFunctions") interface ChatRepository { fun getRoom(user: User, roomToken: String): Observable fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable @@ -47,4 +51,18 @@ interface ChatRepository { objectId: String, metadata: String ): Observable + fun leaveRoom(credentials: String, url: String): Observable + fun sendChatMessage( + credentials: String, + url: String, + message: CharSequence, + displayName: String, + replyTo: Int, + sendWithoutNotification: Boolean + ): Observable + fun pullChatMessages(credentials: String, url: String, fieldMap: HashMap): Observable> + fun deleteChatMessage(credentials: String, url: String): Observable + fun createRoom(credentials: String, url: String, map: Map): Observable + fun setChatReadMarker(credentials: String, url: String, previousMessageId: Int): Observable + fun editChatMessage(credentials: String, url: String, text: String): Observable } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/NetworkChatRepositoryImpl.kt similarity index 70% rename from app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt rename to app/src/main/java/com/nextcloud/talk/chat/data/network/NetworkChatRepositoryImpl.kt index 5a2d22485..95edcb21c 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/NetworkChatRepositoryImpl.kt @@ -18,18 +18,22 @@ * along with this program. If not, see . */ -package com.nextcloud.talk.chat.data +package com.nextcloud.talk.chat.data.network import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.chat.data.ChatRepository import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage +import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomsOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.reminder.Reminder import com.nextcloud.talk.utils.ApiUtils import io.reactivex.Observable +import retrofit2.Response -class ChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository { +class NetworkChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository { override fun getRoom(user: User, roomToken: String): Observable { val credentials: String = ApiUtils.getCredentials(user.username, user.token) val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1)) @@ -120,4 +124,49 @@ class ChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository { ): Observable { return ncApi.sendLocation(credentials, url, objectType, objectId, metadata).map { it } } + + override fun leaveRoom(credentials: String, url: String): Observable { + return ncApi.leaveRoom(credentials, url).map { it } + } + + override fun sendChatMessage( + credentials: String, + url: String, + message: CharSequence, + displayName: String, + replyTo: Int, + sendWithoutNotification: Boolean + ): Observable { + return ncApi.sendChatMessage(credentials, url, message, displayName, replyTo, sendWithoutNotification).map { + it + } + } + + override fun pullChatMessages( + credentials: String, + url: String, + fieldMap: HashMap + ): Observable> { + return ncApi.pullChatMessages(credentials, url, fieldMap).map { it } + } + + override fun deleteChatMessage(credentials: String, url: String): Observable { + return ncApi.deleteChatMessage(credentials, url).map { it } + } + + override fun createRoom(credentials: String, url: String, map: Map): Observable { + return ncApi.createRoom(credentials, url, map).map { it } + } + + override fun setChatReadMarker( + credentials: String, + url: String, + previousMessageId: Int + ): Observable { + return ncApi.setChatReadMarker(credentials, url, previousMessageId).map { it } + } + + override fun editChatMessage(credentials: String, url: String, text: String): Observable { + return ncApi.editChatMessage(credentials, url, text).map { it } + } } diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index 3bad3aad9..cbb392bb2 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -21,29 +21,63 @@ package com.nextcloud.talk.chat.viewmodels import android.util.Log +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.nextcloud.talk.chat.data.ChatRepository import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.domain.ReactionAddedModel +import com.nextcloud.talk.models.domain.ReactionDeletedModel +import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage +import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomsOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.reminder.Reminder +import com.nextcloud.talk.repositories.reactions.ReactionsRepository import com.nextcloud.talk.utils.ConversationUtils import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import retrofit2.Response import javax.inject.Inject -class ChatViewModel @Inject constructor(private val repository: ChatRepository) : - ViewModel() { +@Suppress("TooManyFunctions", "LongParameterList") +class ChatViewModel @Inject constructor( + private val chatRepository: ChatRepository, + private val reactionsRepository: ReactionsRepository +) : ViewModel() { + object LifeCycleObserver : DefaultLifecycleObserver { + enum class LifeCycleFlag { + PAUSED, + RESUMED + } + lateinit var currentLifeCycleFlag: LifeCycleFlag + public val disposableSet = mutableSetOf() + + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + currentLifeCycleFlag = LifeCycleFlag.RESUMED + } + + override fun onPause(owner: LifecycleOwner) { + super.onPause(owner) + currentLifeCycleFlag = LifeCycleFlag.PAUSED + disposableSet.forEach { disposable -> disposable.dispose() } + disposableSet.clear() + } + } + + private val _getFieldMapForChat: MutableLiveData> = MutableLiveData() + val getFieldMapForChat: LiveData> + get() = _getFieldMapForChat sealed interface ViewState - object GetRoomStartState : ViewState - object GetRoomErrorState : ViewState object GetReminderStartState : ViewState open class GetReminderExistState(val reminder: Reminder) : ViewState @@ -63,6 +97,8 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) val getNoteToSelfAvaliability: LiveData get() = _getNoteToSelfAvaliability + object GetRoomStartState : ViewState + object GetRoomErrorState : ViewState open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState private val _getRoomViewState: MutableLiveData = MutableLiveData(GetRoomStartState) @@ -77,9 +113,72 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) val joinRoomViewState: LiveData get() = _joinRoomViewState + object LeaveRoomStartState : ViewState + class LeaveRoomSuccessState(val funToCallWhenLeaveSuccessful: (() -> Unit)?) : ViewState + private val _leaveRoomViewState: MutableLiveData = MutableLiveData(LeaveRoomStartState) + val leaveRoomViewState: LiveData + get() = _leaveRoomViewState + + object SendChatMessageStartState : ViewState + class SendChatMessageSuccessState(val message: CharSequence) : ViewState + class SendChatMessageErrorState(val e: Throwable, val message: CharSequence) : ViewState + private val _sendChatMessageViewState: MutableLiveData = MutableLiveData(SendChatMessageStartState) + val sendChatMessageViewState: LiveData + get() = _sendChatMessageViewState + + object PullChatMessageStartState : ViewState + class PullChatMessageSuccessState(val response: Response<*>, val lookIntoFuture: Boolean) : ViewState + object PullChatMessageErrorState : ViewState + object PullChatMessageCompleteState : ViewState + private val _pullChatMessageViewState: MutableLiveData = MutableLiveData(PullChatMessageStartState) + val pullChatMessageViewState: LiveData + get() = _pullChatMessageViewState + + object DeleteChatMessageStartState : ViewState + class DeleteChatMessageSuccessState(val msg: ChatOverallSingleMessage) : ViewState + object DeleteChatMessageErrorState : ViewState + private val _deleteChatMessageViewState: MutableLiveData = MutableLiveData(DeleteChatMessageStartState) + val deleteChatMessageViewState: LiveData + get() = _deleteChatMessageViewState + + object CreateRoomStartState : ViewState + object CreateRoomErrorState : ViewState + class CreateRoomSuccessState(val roomOverall: RoomOverall) : ViewState + + private val _createRoomViewState: MutableLiveData = MutableLiveData(CreateRoomStartState) + val createRoomViewState: LiveData + get() = _createRoomViewState + + object ReactionAddedStartState : ViewState + class ReactionAddedSuccessState(val reactionAddedModel: ReactionAddedModel) : ViewState + private val _reactionAddedViewState: MutableLiveData = MutableLiveData(ReactionAddedStartState) + val reactionAddedViewState: LiveData + get() = _reactionAddedViewState + + object ReactionDeletedStartState : ViewState + class ReactionDeletedSuccessState(val reactionDeletedModel: ReactionDeletedModel) : ViewState + private val _reactionDeletedViewState: MutableLiveData = MutableLiveData(ReactionDeletedStartState) + val reactionDeletedViewState: LiveData + get() = _reactionDeletedViewState + + object EditMessageStartState : ViewState + object EditMessageErrorState : ViewState + class EditMessageSuccessState(val messageEdited: ChatOverallSingleMessage) : ViewState + + private val _editMessageViewState: MutableLiveData = MutableLiveData(EditMessageStartState) + val editMessageViewState: LiveData + get() = _editMessageViewState + + fun refreshChatParams(pullChatMessagesFieldMap: HashMap) { + if (pullChatMessagesFieldMap != _getFieldMapForChat.value) { + _getFieldMapForChat.postValue(pullChatMessagesFieldMap) + Log.d(TAG, "FieldMap Refreshed with $pullChatMessagesFieldMap vs ${_getFieldMapForChat.value}") + } + } + fun getRoom(user: User, token: String) { _getRoomViewState.value = GetRoomStartState - repository.getRoom(user, token) + chatRepository.getRoom(user, token) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(GetRoomObserver()) @@ -87,34 +186,198 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) fun joinRoom(user: User, token: String, roomPassword: String) { _joinRoomViewState.value = JoinRoomStartState - repository.joinRoom(user, token, roomPassword) + chatRepository.joinRoom(user, token, roomPassword) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.retry(JOIN_ROOM_RETRY_COUNT) ?.subscribe(JoinRoomObserver()) } + fun leaveRoom(credentials: String, url: String, funToCallWhenLeaveSuccessful: (() -> Unit)?) { + val startNanoTime = System.nanoTime() + chatRepository.leaveRoom(credentials, url) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + LifeCycleObserver.disposableSet.add(d) + } + + override fun onError(e: Throwable) { + Log.e(TAG, "leaveRoom - leaveRoom - ERROR", e) + } + + override fun onComplete() { + Log.d(TAG, "leaveRoom - leaveRoom - completed: $startNanoTime") + } + + override fun onNext(t: GenericOverall) { + _leaveRoomViewState.value = LeaveRoomSuccessState(funToCallWhenLeaveSuccessful) + } + }) + } + + fun createRoom(credentials: String, url: String, queryMap: Map) { + chatRepository.createRoom(credentials, url, queryMap) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + LifeCycleObserver.disposableSet.add(d) + } + + override fun onError(e: Throwable) { + _createRoomViewState.value = CreateRoomErrorState + Log.e(TAG, e.message, e) + } + + override fun onComplete() { + // unused atm + } + + override fun onNext(t: RoomOverall) { + _createRoomViewState.value = CreateRoomSuccessState(t) + } + }) + } + + fun sendChatMessage( + credentials: String, + url: String, + message: CharSequence, + displayName: String, + replyTo: Int, + sendWithoutNotification: Boolean + ) { + chatRepository.sendChatMessage( + credentials, + url, + message, + displayName, + replyTo, + sendWithoutNotification + ).subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + LifeCycleObserver.disposableSet.add(d) + } + + override fun onError(e: Throwable) { + _sendChatMessageViewState.value = SendChatMessageErrorState(e, message) + } + + override fun onComplete() { + // unused atm + } + + override fun onNext(t: GenericOverall) { + _sendChatMessageViewState.value = SendChatMessageSuccessState(message) + } + }) + } + + fun pullChatMessages(credentials: String, url: String) { + chatRepository.pullChatMessages(credentials, url, _getFieldMapForChat.value!!) + .subscribeOn(Schedulers.io()) + .takeUntil { (LifeCycleObserver.currentLifeCycleFlag == LifeCycleObserver.LifeCycleFlag.PAUSED) } + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer> { + override fun onSubscribe(d: Disposable) { + Log.d(TAG, "pullChatMessages - pullChatMessages SUBSCRIBE") + LifeCycleObserver.disposableSet.add(d) + } + + override fun onError(e: Throwable) { + Log.e(TAG, "pullChatMessages - pullChatMessages ERROR", e) + _pullChatMessageViewState.value = PullChatMessageErrorState + } + + override fun onComplete() { + Log.d(TAG, "pullChatMessages - pullChatMessages COMPLETE") + _pullChatMessageViewState.value = PullChatMessageCompleteState + } + + override fun onNext(response: Response<*>) { + val lookIntoFuture = getFieldMapForChat.value?.get("lookIntoFuture") == 1 + _pullChatMessageViewState.value = PullChatMessageSuccessState(response, lookIntoFuture) + } + }) + } + + fun deleteChatMessages(credentials: String, url: String, messageId: String) { + chatRepository.deleteChatMessage(credentials, url) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + LifeCycleObserver.disposableSet.add(d) + } + + override fun onError(e: Throwable) { + Log.e( + TAG, + "Something went wrong when trying to delete message with id " + + messageId, + e + ) + _deleteChatMessageViewState.value = DeleteChatMessageErrorState + } + + override fun onComplete() { + // unused atm + } + + override fun onNext(t: ChatOverallSingleMessage) { + _deleteChatMessageViewState.value = DeleteChatMessageSuccessState(t) + } + }) + } + + fun setChatReadMarker(credentials: String, url: String, previousMessageId: Int) { + chatRepository.setChatReadMarker(credentials, url, previousMessageId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + LifeCycleObserver.disposableSet.add(d) + } + + override fun onError(e: Throwable) { + Log.e(TAG, e.message, e) + } + + override fun onComplete() { + // unused atm + } + + override fun onNext(t: GenericOverall) { + // unused atm + } + }) + } + fun setReminder(user: User, roomToken: String, messageId: String, timestamp: Int) { - repository.setReminder(user, roomToken, messageId, timestamp) + chatRepository.setReminder(user, roomToken, messageId, timestamp) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(SetReminderObserver()) } fun getReminder(user: User, roomToken: String, messageId: String) { - repository.getReminder(user, roomToken, messageId) + chatRepository.getReminder(user, roomToken, messageId) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(GetReminderObserver()) } fun deleteReminder(user: User, roomToken: String, messageId: String) { - repository.deleteReminder(user, roomToken, messageId) + chatRepository.deleteReminder(user, roomToken, messageId) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(object : Observer { override fun onSubscribe(d: Disposable) { - // unused atm + LifeCycleObserver.disposableSet.add(d) } override fun onNext(genericOverall: GenericOverall) { @@ -132,12 +395,12 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) } fun shareToNotes(credentials: String, url: String, message: String, displayName: String) { - repository.shareToNotes(credentials, url, message, displayName) + chatRepository.shareToNotes(credentials, url, message, displayName) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(object : Observer { override fun onSubscribe(d: Disposable) { - // unused atm + LifeCycleObserver.disposableSet.add(d) } override fun onNext(genericOverall: GenericOverall) { @@ -155,18 +418,18 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) } fun checkForNoteToSelf(credentials: String, baseUrl: String, includeStatus: Boolean) { - repository.checkForNoteToSelf(credentials, baseUrl, includeStatus).subscribeOn(Schedulers.io()) + chatRepository.checkForNoteToSelf(credentials, baseUrl, includeStatus).subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(CheckForNoteToSelfObserver()) } fun shareLocationToNotes(credentials: String, url: String, objectType: String, objectId: String, metadata: String) { - repository.shareLocationToNotes(credentials, url, objectType, objectId, metadata) + chatRepository.shareLocationToNotes(credentials, url, objectType, objectId, metadata) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(object : Observer { override fun onSubscribe(d: Disposable) { - // unused atm + LifeCycleObserver.disposableSet.add(d) } override fun onNext(genericOverall: GenericOverall) { @@ -183,9 +446,83 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) }) } + fun deleteReaction(roomToken: String, chatMessage: ChatMessage, emoji: String) { + reactionsRepository.deleteReaction(roomToken, chatMessage, emoji) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + LifeCycleObserver.disposableSet.add(d) + } + + override fun onError(e: Throwable) { + Log.d(TAG, "$e") + } + + override fun onComplete() { + // unused atm + } + + override fun onNext(reactionDeletedModel: ReactionDeletedModel) { + if (reactionDeletedModel.success) { + _reactionDeletedViewState.value = ReactionDeletedSuccessState(reactionDeletedModel) + } + } + }) + } + + fun addReaction(roomToken: String, chatMessage: ChatMessage, emoji: String) { + reactionsRepository.addReaction(roomToken, chatMessage, emoji) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + LifeCycleObserver.disposableSet.add(d) + } + + override fun onError(e: Throwable) { + Log.d(TAG, "$e") + } + + override fun onComplete() { + // unused atm + } + + override fun onNext(reactionAddedModel: ReactionAddedModel) { + if (reactionAddedModel.success) { + _reactionAddedViewState.value = ReactionAddedSuccessState(reactionAddedModel) + } + } + }) + } + + fun editChatMessage(credentials: String, url: String, text: String) { + chatRepository.editChatMessage(credentials, url, text) + .subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + LifeCycleObserver.disposableSet.add(d) + } + + override fun onError(e: Throwable) { + Log.e(TAG, "failed to edit message", e) + _editMessageViewState.value = EditMessageErrorState + } + + override fun onComplete() { + // unused atm + } + + override fun onNext(messageEdited: ChatOverallSingleMessage) { + _editMessageViewState.value = EditMessageSuccessState(messageEdited) + } + }) + } + inner class GetRoomObserver : Observer { override fun onSubscribe(d: Disposable) { - // unused atm + LifeCycleObserver.disposableSet.add(d) } override fun onNext(conversationModel: ConversationModel) { @@ -204,7 +541,7 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) inner class JoinRoomObserver : Observer { override fun onSubscribe(d: Disposable) { - // unused atm + LifeCycleObserver.disposableSet.add(d) } override fun onNext(conversationModel: ConversationModel) { @@ -223,7 +560,7 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) inner class SetReminderObserver : Observer { override fun onSubscribe(d: Disposable) { - // unused atm + LifeCycleObserver.disposableSet.add(d) } override fun onNext(reminder: Reminder) { @@ -241,7 +578,7 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) inner class GetReminderObserver : Observer { override fun onSubscribe(d: Disposable) { - // unused atm + LifeCycleObserver.disposableSet.add(d) } override fun onNext(reminder: Reminder) { @@ -260,7 +597,7 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository) inner class CheckForNoteToSelfObserver : Observer { override fun onSubscribe(d: Disposable) { - // unused atm + LifeCycleObserver.disposableSet.add(d) } override fun onNext(roomsOverall: RoomsOverall) { diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index d1e891216..bb2558f8f 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -27,7 +27,7 @@ package com.nextcloud.talk.dagger.modules import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.chat.data.ChatRepository -import com.nextcloud.talk.chat.data.ChatRepositoryImpl +import com.nextcloud.talk.chat.data.network.NetworkChatRepositoryImpl import com.nextcloud.talk.conversation.repository.ConversationRepository import com.nextcloud.talk.conversation.repository.ConversationRepositoryImpl import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepository @@ -146,7 +146,7 @@ class RepositoryModule { @Provides fun provideChatRepository(ncApi: NcApi): ChatRepository { - return ChatRepositoryImpl(ncApi) + return NetworkChatRepositoryImpl(ncApi) } @Provides diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.kt index d09ccd680..5edea1225 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.kt @@ -24,6 +24,7 @@ package com.nextcloud.talk.models.json.chat +@Suppress("UtilityClassWithPublicConstructor") class ChatUtils { companion object { fun getParsedMessage(