Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mls): navigate to migrated conversation [WPB-4705] #2327

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/src/main/kotlin/com/wire/android/WireApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class WireApplication : Application(), Configuration.Provider {
.detectDiskReads()
.detectDiskWrites()
.penaltyLog()
.penaltyDeath()
// .penaltyDeath()
.build()
)
StrictMode.setVmPolicy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.media.audiomessage.AudioState
import com.wire.android.model.SnackBarMessage
import com.wire.android.navigation.BackStackMode
import com.wire.android.navigation.NavigationCommand
import com.wire.android.navigation.Navigator
import com.wire.android.ui.calling.common.MicrophoneBTPermissionsDeniedDialog
Expand All @@ -75,6 +76,7 @@ import com.wire.android.ui.common.dialogs.calling.JoinAnywayDialog
import com.wire.android.ui.common.dialogs.calling.OngoingActiveCallDialog
import com.wire.android.ui.common.error.CoreFailureErrorDialog
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
import com.wire.android.ui.destinations.ConversationScreenDestination
import com.wire.android.ui.destinations.GroupConversationDetailsScreenDestination
import com.wire.android.ui.destinations.InitiatingCallScreenDestination
import com.wire.android.ui.destinations.MediaGalleryScreenDestination
Expand All @@ -95,6 +97,7 @@ import com.wire.android.ui.home.conversations.info.ConversationInfoViewModel
import com.wire.android.ui.home.conversations.info.ConversationInfoViewState
import com.wire.android.ui.home.conversations.messages.ConversationMessagesViewModel
import com.wire.android.ui.home.conversations.messages.ConversationMessagesViewState
import com.wire.android.ui.home.conversations.migration.ConversationMigrationViewModel
import com.wire.android.ui.home.conversations.model.ExpirationStatus
import com.wire.android.ui.home.conversations.model.UIMessage
import com.wire.android.ui.home.conversations.selfdeletion.SelfDeletionMapper.toSelfDeletionDuration
Expand Down Expand Up @@ -151,6 +154,7 @@ fun ConversationScreen(
conversationCallViewModel: ConversationCallViewModel = hiltViewModel(),
conversationMessagesViewModel: ConversationMessagesViewModel = hiltViewModel(),
messageComposerViewModel: MessageComposerViewModel = hiltViewModel(),
conversationMigrationViewModel: ConversationMigrationViewModel = hiltViewModel(),
groupDetailsScreenResultRecipient: ResultRecipient<GroupConversationDetailsScreenDestination, GroupConversationDetailsNavBackArgs>,
mediaGalleryScreenResultRecipient: ResultRecipient<MediaGalleryScreenDestination, MediaGalleryNavBackArgs>,
resultNavigator: ResultBackNavigator<GroupConversationDetailsNavBackArgs>,
Expand All @@ -177,6 +181,15 @@ fun ConversationScreen(
}
val context = LocalContext.current

conversationMigrationViewModel.migratedConversationId?.let { migratedConversationId ->
navigator.navigate(
NavigationCommand(
ConversationScreenDestination(migratedConversationId),
BackStackMode.REMOVE_CURRENT
)
)
}

with(conversationCallViewModel) {
if (conversationCallViewState.shouldShowJoinAnywayDialog) {
appLogger.i("showing showJoinAnywayDialog..")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import kotlinx.coroutines.launch
import javax.inject.Inject

@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("LongParameterList")
@HiltViewModel
class ConversationBannerViewModel @Inject constructor(
override val savedStateHandle: SavedStateHandle,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.conversations.migration

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.wire.android.navigation.SavedStateViewModel
import com.wire.android.ui.home.conversations.ConversationNavArgs
import com.wire.android.ui.navArgs
import com.wire.kalium.logic.data.conversation.ConversationDetails
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class ConversationMigrationViewModel @Inject constructor(
override val savedStateHandle: SavedStateHandle,
private val observeConversationDetails: ObserveConversationDetailsUseCase
) : SavedStateViewModel(savedStateHandle) {

/**
* Represents the target conversation, after a conversation migration.
* The target conversation is the active one-on-one conversation ID if the current conversation
* is migrated to a different conversation.
* If this conversation was not migrated to another one, the target conversation is null.
*/
var migratedConversationId by mutableStateOf<ConversationId?>(null)
private set

private val conversationNavArgs = savedStateHandle.navArgs<ConversationNavArgs>()
private val conversationId: QualifiedID = conversationNavArgs.conversationId

init {
viewModelScope.launch {
observeConversationDetails(conversationId)
.filterIsInstance<ObserveConversationDetailsUseCase.Result.Success>()
.map { it.conversationDetails }
.filterIsInstance<ConversationDetails.OneOne>()
.collectLatest {
val activeOneOnOneConversationId = it.otherUser.activeOneOnOneConversationId
val wasThisConversationMigrated = activeOneOnOneConversationId != conversationId
if (wasThisConversationMigrated) {
migratedConversationId = activeOneOnOneConversationId
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.conversations.migration

import androidx.lifecycle.SavedStateHandle
import com.wire.android.config.CoroutineTestExtension
import com.wire.android.config.NavigationTestExtension
import com.wire.android.framework.TestConversation
import com.wire.android.framework.TestUser
import com.wire.android.ui.home.conversations.ConversationNavArgs
import com.wire.android.ui.navArgs
import com.wire.kalium.logic.data.conversation.ConversationDetails
import com.wire.kalium.logic.data.conversation.LegalHoldStatus
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.type.UserType
import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldBeEqualTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(CoroutineTestExtension::class)
@ExtendWith(NavigationTestExtension::class)
class ConversationMigrationViewModelTest {

@Test
fun givenActiveOneOnOneMatchesCurrentConversation_thenMigratedConversationShouldBeNull() = runTest {
val (_, conversationMigrationViewModel) = arrange {
withConversationDetailsReturning(
ConversationDetails.OneOne(
conversation = TestConversation.ONE_ON_ONE,
otherUser = TestUser.OTHER_USER.copy(activeOneOnOneConversationId = conversationId),
legalHoldStatus = LegalHoldStatus.ENABLED,
userType = UserType.NONE,
unreadEventCount = mapOf(),
lastMessage = null
)
)
}

conversationMigrationViewModel.migratedConversationId shouldBe null
}

@Test
fun givenActiveOneOnOneDiffersFromCurrentConversation_thenMigratedConversationShouldBeTheOneInDetails() = runTest {
val expectedActiveOneOnOneId = ConversationId("expectedActiveOneOnOneId", "testDomain")
val (_, conversationMigrationViewModel) = arrange {
withConversationDetailsReturning(
ConversationDetails.OneOne(
conversation = TestConversation.ONE_ON_ONE,
otherUser = TestUser.OTHER_USER.copy(activeOneOnOneConversationId = expectedActiveOneOnOneId),
legalHoldStatus = LegalHoldStatus.ENABLED,
userType = UserType.NONE,
unreadEventCount = mapOf(),
lastMessage = null
)
)
}

conversationMigrationViewModel.migratedConversationId shouldBeEqualTo expectedActiveOneOnOneId
}

private class Arrangement(private val configure: Arrangement.() -> Unit) {

@MockK
lateinit var observeConversationDetailsUseCase: ObserveConversationDetailsUseCase

@MockK
lateinit var savedStateHandle: SavedStateHandle

init {
MockKAnnotations.init(this)
every { savedStateHandle.navArgs<ConversationNavArgs>() } returns ConversationNavArgs(conversationId)
}

fun withConversationDetailsReturning(conversationDetails: ConversationDetails) = apply {
coEvery { observeConversationDetailsUseCase(conversationId) } returns
flowOf(ObserveConversationDetailsUseCase.Result.Success(conversationDetails))
}

fun arrange(): Pair<Arrangement, ConversationMigrationViewModel> = run {
configure()
this@Arrangement to ConversationMigrationViewModel(savedStateHandle, observeConversationDetailsUseCase)
}
}

private companion object {
val conversationId = TestConversation.ID

fun arrange(configure: Arrangement.() -> Unit) = Arrangement(configure).arrange()
}
}
Loading