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 cc9d3e75253..ea05b5e7a49 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -400,6 +400,11 @@ class ChatActivity : var myFirstMessage: CharSequence? = null private var isLeavingRoom: Boolean = false + // The room is only joined while the chat is resumed, as onPause leaves it. A join requested in between + // (e.g. by a GetCapabilitiesInitialLoadState re-emitted after the leave reset it) is deferred until onResume. + private var isChatResumed: Boolean = false + private var isJoinRoomPending: Boolean = false + private var lastHandledHighlightNonce: Long? = null private var pendingHighlightedMessageId: Long? = null private var lastNoMoreResultsToastTime: Long = 0L @@ -2089,6 +2094,8 @@ class ChatActivity : logConversationInfos("onResume") + isChatResumed = true + pullChatMessagesPending = false // reset in case a previously started leave failed (success already resets this in leaveRoomObserver) @@ -2104,6 +2111,11 @@ class ChatActivity : binding.let { viewThemeUtils.material.themeFAB(it.voiceRecordingLock) } updateToolbarState() + + if (isJoinRoomPending) { + isJoinRoomPending = false + joinRoomWithPassword() + } } private fun setupChatToolbarView() { @@ -2942,6 +2954,8 @@ class ChatActivity : logConversationInfos("onPause") + isChatResumed = false + eventBus.unregister(this) webSocketInstance?.getSignalingMessageReceiver()?.removeListener(localParticipantMessageListener) @@ -2958,6 +2972,10 @@ class ChatActivity : Log.d(TAG, "not leaving room (leave already in progress)") } else if (validSessionId()) { leaveRoom(null) + } else if (chatViewModel.requestLeaveAfterJoin()) { + Log.d(TAG, "join in progress -> leaving room once it completes") + isJoinRoomPending = true + ApplicationWideCurrentRoomHolder.getInstance().clear() } else { Log.d(TAG, "not leaving room (validSessionId is false)") ApplicationWideCurrentRoomHolder.getInstance().clear() @@ -3016,6 +3034,12 @@ class ChatActivity : } private fun joinRoomWithPassword() { + if (!isChatResumed) { + Log.d(TAG, "joinRoomWithPassword - chat is not resumed, deferring join until onResume") + isJoinRoomPending = true + return + } + // if ApplicationWideCurrentRoomHolder contains a session (because a call is active), then keep the sessionId if (ApplicationWideCurrentRoomHolder.getInstance().isCurrentRoom( currentConversation!!.token, @@ -3052,6 +3076,8 @@ class ChatActivity : webSocketInstance?.joinRoomWithRoomTokenAndSession("", sessionIdAfterRoomJoined) } sessionIdAfterRoomJoined = "0" + // rejoin on the next resume, even if no conversation update re-emits GetCapabilitiesInitialLoadState + isJoinRoomPending = true // FIXME Fix API checking with guests? val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1)) 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 4a87b23ec2b..2fd74c7de02 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 @@ -373,6 +373,12 @@ class ChatViewModel @AssistedInject constructor( lateinit var currentLifeCycleFlag: LifeCycleFlag val disposableSet = mutableSetOf() + // A join request is not disposed when the chat is paused: the server may already have created the session, + // so if the chat was left in the meantime it is left again once the join completes (see [requestLeaveAfterJoin]). + var isJoinRoomInFlight: Boolean = false + private set + private var isLeaveAfterJoinRequested: Boolean = false + var messageDraft: MessageDraft = MessageDraft() var hiddenUpcomingEvent: String? = null lateinit var participantPermissions: ParticipantPermissions @@ -1861,12 +1867,47 @@ class ChatViewModel @AssistedInject constructor( } fun joinRoom(user: User, token: String, roomPassword: String) { + if (isJoinRoomInFlight) { + // the chat was resumed again before the running join completed, so it keeps the joined session + Log.d(TAG, "joinRoom - join already in progress, not leaving the room after it") + isLeaveAfterJoinRequested = false + return + } + isJoinRoomInFlight = true + isLeaveAfterJoinRequested = false _joinRoomViewState.value = JoinRoomStartState chatNetworkDataSource.joinRoom(user, token, roomPassword) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.retry(JOIN_ROOM_RETRY_COUNT) - ?.subscribe(JoinRoomObserver()) + ?.subscribe(JoinRoomObserver(user, token)) + } + + /** + * Requests leaving the room as soon as the running join request completes, instead of reporting the join. + * Returns false if no join request is running. + */ + fun requestLeaveAfterJoin(): Boolean { + if (!isJoinRoomInFlight) { + return false + } + isLeaveAfterJoinRequested = true + return true + } + + @Suppress("Detekt.TooGenericExceptionCaught") + private fun leaveRoomAfterJoin(user: User, token: String) { + val credentials = ApiUtils.getCredentials(user.username, user.token) ?: return + val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, 1)) + val url = ApiUtils.getUrlForParticipantsActive(apiVersion, user.baseUrl!!, token) + appScope.launch { + try { + val result = chatNetworkDataSource.leaveRoom(credentials, url) + Log.d(TAG, "leaveRoomAfterJoin completed with OCS status: ${result.ocs?.meta?.statusCode}") + } catch (e: Exception) { + Log.e(TAG, "leaveRoomAfterJoin - ERROR", e) + } + } } fun setReminder(user: User, roomToken: String, messageId: String, timestamp: Int, chatApiVersion: Int) { @@ -2542,17 +2583,26 @@ class ChatViewModel @AssistedInject constructor( return chatRepository.getMessage(messageId, bundle) } - inner class JoinRoomObserver : Observer { + inner class JoinRoomObserver(private val user: User, private val token: String) : Observer { override fun onSubscribe(d: Disposable) { - disposableSet.add(d) + // not added to disposableSet, see isJoinRoomInFlight } override fun onNext(conversationModel: ConversationModel) { + isJoinRoomInFlight = false + if (isLeaveAfterJoinRequested) { + isLeaveAfterJoinRequested = false + Log.d(TAG, "joinRoom completed after the chat was left -> leaving the room again") + leaveRoomAfterJoin(user, token) + return + } _joinRoomViewState.value = JoinRoomSuccessState(conversationModel) } override fun onError(e: Throwable) { Log.e(TAG, "Error when joining room") + isJoinRoomInFlight = false + isLeaveAfterJoinRequested = false _joinRoomViewState.value = JoinRoomErrorState }