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

fix: Handle 1o1 conversations when no key packages [WPB-6936] #2938

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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.wire.kalium.logic.feature.conversation.CreateGroupConversationUseCase
import com.wire.kalium.logic.feature.conversation.GetConversationUnreadEventsCountUseCase
import com.wire.kalium.logic.feature.conversation.GetOneToOneConversationUseCase
import com.wire.kalium.logic.feature.conversation.GetOrCreateOneToOneConversationUseCase
import com.wire.kalium.logic.feature.conversation.IsOneToOneConversationCreatedUseCase
import com.wire.kalium.logic.feature.conversation.JoinConversationViaCodeUseCase
import com.wire.kalium.logic.feature.conversation.LeaveConversationUseCase
import com.wire.kalium.logic.feature.conversation.NotifyConversationIsOpenUseCase
Expand All @@ -38,9 +39,9 @@ import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseC
import com.wire.kalium.logic.feature.conversation.ObserveConversationInteractionAvailabilityUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationListDetailsUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationMembersUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationUnderLegalHoldNotifiedUseCase
import com.wire.kalium.logic.feature.conversation.ObserveDegradedConversationNotifiedUseCase
import com.wire.kalium.logic.feature.conversation.ObserveIsSelfUserMemberUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationUnderLegalHoldNotifiedUseCase
import com.wire.kalium.logic.feature.conversation.ObserveUserListByIdUseCase
import com.wire.kalium.logic.feature.conversation.ObserveUsersTypingUseCase
import com.wire.kalium.logic.feature.conversation.RefreshConversationsWithoutMetadataUseCase
Expand Down Expand Up @@ -294,4 +295,9 @@ class ConversationModule {
@Provides
fun provideSyncConversationCodeUseCase(conversationScope: ConversationScope): SyncConversationCodeUseCase =
conversationScope.syncConversationCode

@ViewModelScoped
@Provides
fun provideIsOneToOneConversationCreatedUseCase(conversationScope: ConversationScope): IsOneToOneConversationCreatedUseCase =
conversationScope.isOneToOneConversationCreatedUseCase
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,35 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.wire.android.R
import com.wire.android.di.hiltViewModelScoped
import com.wire.android.model.ClickBlockParams
import com.wire.android.ui.common.VisibilityState
import com.wire.android.ui.common.WireDialog
import com.wire.android.ui.common.WireDialogButtonProperties
import com.wire.android.ui.common.WireDialogButtonType
import com.wire.android.ui.common.button.WireButtonState
import com.wire.android.ui.common.button.WirePrimaryButton
import com.wire.android.ui.common.button.WireSecondaryButton
import com.wire.android.ui.common.colorsScheme
import com.wire.android.ui.common.dialogs.UnblockUserDialogContent
import com.wire.android.ui.common.dialogs.UnblockUserDialogState
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
import com.wire.android.ui.common.snackbar.collectAndShowSnackbar
import com.wire.android.ui.common.visbility.VisibilityState
import com.wire.android.ui.common.visbility.rememberVisibilityState
import com.wire.android.ui.legalhold.dialog.connectionfailed.LegalHoldSubjectConnectionFailedDialog
import com.wire.android.ui.theme.WireTheme
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.android.util.ui.stringWithStyledArgs
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.ConnectionState
import com.wire.kalium.logic.data.user.UserId
Expand All @@ -50,7 +60,9 @@ import com.wire.kalium.logic.data.user.UserId
fun ConnectionActionButton(
userId: UserId,
userName: String,
fullName: String,
connectionStatus: ConnectionState,
isConversationStarted: Boolean,
onConnectionRequestIgnored: (String) -> Unit = {},
onOpenConversation: (ConversationId) -> Unit = {},
viewModel: ConnectionActionButtonViewModel =
Expand All @@ -60,13 +72,16 @@ fun ConnectionActionButton(
) {
LocalSnackbarHostState.current.collectAndShowSnackbar(snackbarFlow = viewModel.infoMessage)
val unblockUserDialogState = rememberVisibilityState<UnblockUserDialogState>()
val unableStartConversationDialogState = rememberVisibilityState<UnableStartConversationDialogState>()

UnblockUserDialogContent(
dialogState = unblockUserDialogState,
onUnblock = { viewModel.onUnblockUser() },
isLoading = viewModel.actionableState().isPerformingAction,
)

UnableStartConversationDialogContent(dialogState = unableStartConversationDialogState)

if (!viewModel.actionableState().isPerformingAction) {
unblockUserDialogState.dismiss()
}
Expand All @@ -86,9 +101,13 @@ fun ConnectionActionButton(
)

ConnectionState.ACCEPTED -> WirePrimaryButton(
text = stringResource(R.string.label_open_conversation),
text = stringResource(if (isConversationStarted) R.string.label_open_conversation else R.string.label_start_conversation),
loading = viewModel.actionableState().isPerformingAction,
onClick = { viewModel.onOpenConversation(onOpenConversation) },
onClick = {
viewModel.onOpenConversation(onOpenConversation) {
unableStartConversationDialogState.show(UnableStartConversationDialogState(fullName))
}
},
)

ConnectionState.IGNORED -> WirePrimaryButton(
Expand Down Expand Up @@ -174,14 +193,41 @@ fun ConnectionActionButton(
}
}

@Composable
fun UnableStartConversationDialogContent(dialogState: VisibilityState<UnableStartConversationDialogState>) {
VisibilityState(dialogState) { state ->
WireDialog(
title = stringResource(id = R.string.missing_keypackage_dialog_title),
text = LocalContext.current.resources.stringWithStyledArgs(
R.string.missing_keypackage_dialog_body,
MaterialTheme.wireTypography.body01,
MaterialTheme.wireTypography.body02,
colorsScheme().onBackground,
colorsScheme().onBackground,
state.userName
),
onDismiss = dialogState::dismiss,
optionButton1Properties = WireDialogButtonProperties(
onClick = dialogState::dismiss,
text = stringResource(id = R.string.label_ok),
type = WireDialogButtonType.Primary,
),
)
}
}

data class UnableStartConversationDialogState(val userName: String)

@Composable
@PreviewMultipleThemes
fun PreviewOtherUserConnectionActionButtonPending() {
WireTheme {
ConnectionActionButton(
userId = UserId("value", "domain"),
userName = "Username",
fullName = "some user",
connectionStatus = ConnectionState.PENDING,
isConversationStarted = false
)
}
}
Expand All @@ -193,7 +239,9 @@ fun PreviewOtherUserConnectionActionButtonNotConnected() {
ConnectionActionButton(
userId = UserId("value", "domain"),
userName = "Username",
fullName = "some user",
connectionStatus = ConnectionState.NOT_CONNECTED,
isConversationStarted = false
)
}
}
Expand All @@ -205,7 +253,9 @@ fun PreviewOtherUserConnectionActionButtonBlocked() {
ConnectionActionButton(
userId = UserId("value", "domain"),
userName = "Username",
fullName = "some user",
connectionStatus = ConnectionState.BLOCKED,
isConversationStarted = false
)
}
}
Expand All @@ -217,7 +267,9 @@ fun PreviewOtherUserConnectionActionButtonCanceled() {
ConnectionActionButton(
userId = UserId("value", "domain"),
userName = "Username",
fullName = "some user",
connectionStatus = ConnectionState.CANCELLED,
isConversationStarted = false
)
}
}
Expand All @@ -229,7 +281,9 @@ fun PreviewOtherUserConnectionActionButtonAccepted() {
ConnectionActionButton(
userId = UserId("value", "domain"),
userName = "Username",
fullName = "some user",
connectionStatus = ConnectionState.ACCEPTED,
isConversationStarted = false
)
}
}
Expand All @@ -241,7 +295,9 @@ fun PreviewOtherUserConnectionActionButtonSent() {
ConnectionActionButton(
userId = UserId("value", "domain"),
userName = "Username",
fullName = "some user",
connectionStatus = ConnectionState.SENT,
isConversationStarted = false
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.wire.android.di.ViewModelScopedPreview
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.android.util.ui.UIText
import com.wire.kalium.logger.obfuscateId
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.feature.connection.AcceptConnectionRequestUseCase
Expand Down Expand Up @@ -65,6 +66,7 @@ interface ConnectionActionButtonViewModel {
fun onUnblockUser() {}
fun onOpenConversation(onSuccess: (conversationId: ConversationId) -> Unit) {}
fun onMissingLegalHoldConsentDismissed() {}
fun onOpenConversation(onSuccess: (conversationId: ConversationId) -> Unit, onMissingKeyPackages: () -> Unit) {}
}

@Suppress("LongParameterList", "TooManyFunctions")
Expand Down Expand Up @@ -190,17 +192,19 @@ class ConnectionActionButtonViewModelImpl @Inject constructor(
}
}

override fun onOpenConversation(onSuccess: (conversationId: ConversationId) -> Unit) {
override fun onOpenConversation(onSuccess: (conversationId: ConversationId) -> Unit, onMissingKeyPackages: () -> Unit) {
viewModelScope.launch {
state = state.performAction()
when (val result = withContext(dispatchers.io()) { getOrCreateOneToOneConversation(userId) }) {
is CreateConversationResult.Failure -> {
appLogger.d(("Couldn't retrieve or create the conversation"))
state = state.finishAction()
if (result.coreFailure is CoreFailure.MissingKeyPackages) onMissingKeyPackages()
}

is CreateConversationResult.Success -> onSuccess(result.conversation.id)
}
state.finishAction()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,9 @@ private fun ContentFooter(
ConnectionActionButton(
state.userId,
state.userName,
state.fullName,
state.connectionState,
state.isConversationStarted,
onIgnoreConnectionRequest,
onOpenConversation
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import com.wire.kalium.logic.feature.conversation.ArchiveStatusUpdateResult
import com.wire.kalium.logic.feature.conversation.ClearConversationContentUseCase
import com.wire.kalium.logic.feature.conversation.ConversationUpdateStatusResult
import com.wire.kalium.logic.feature.conversation.GetOneToOneConversationUseCase
import com.wire.kalium.logic.feature.conversation.IsOneToOneConversationCreatedUseCase
import com.wire.kalium.logic.feature.conversation.RemoveMemberFromConversationUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationArchivedStatusUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationMemberRoleResult
Expand Down Expand Up @@ -108,6 +109,7 @@ class OtherUserProfileScreenViewModel @Inject constructor(
private val updateConversationArchivedStatus: UpdateConversationArchivedStatusUseCase,
private val getUserE2eiCertificateStatus: GetUserE2eiCertificateStatusUseCase,
private val getUserE2eiCertificates: GetUserE2eiCertificatesUseCase,
private val isOneToOneConversationCreated: IsOneToOneConversationCreatedUseCase,
savedStateHandle: SavedStateHandle
) : ViewModel(), OtherUserProfileEventsHandler, OtherUserProfileBottomSheetEventsHandler {

Expand Down Expand Up @@ -135,6 +137,13 @@ class OtherUserProfileScreenViewModel @Inject constructor(
observeUserInfoAndUpdateViewState()
persistClients()
getMLSVerificationStatus()
getIfConversationExist()
}

private fun getIfConversationExist() {
viewModelScope.launch {
state = state.copy(isConversationStarted = isOneToOneConversationCreated(userId))
}
}

private fun getMLSVerificationStatus() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ data class OtherUserProfileState(
val isProteusVerified: Boolean = false,
val isMLSVerified: Boolean = false,
val isUnderLegalHold: Boolean = false,
val isConversationStarted: Boolean = false
) {
fun updateMuteStatus(status: MutedConversationStatus): OtherUserProfileState {
return conversationSheetContent?.let {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,7 @@
<string name="animation_label_typing_indicator_horizontal_transition" translatable="false">HorizontalBouncingWritingPen transition</string>
<string name="animation_label_button_rotation_degree_transition" translatable="false">Collapse button rotation degree transition</string>
<string name="label_open_conversation">Open Conversation</string>
<string name="label_start_conversation">Start Conversation</string>
<string name="email_label">Email</string>
<string name="phone_label">Phone</string>
<!-- Notifications -->
Expand Down Expand Up @@ -885,6 +886,9 @@
<string name="connection_label_ignore">Ignore</string>
<string name="connection_label_send_unverified_warning">Get certainty about the identity of %s\'s before connecting.</string>
<string name="connection_label_received_unverified_warning">Please verify the person\'s identity before accepting the connection request.</string>
<!-- Missing keyPackages dialog -->
<string name="missing_keypackage_dialog_title">Unable to start conversation</string>
<string name="missing_keypackage_dialog_body">You can’t start the conversation with %1$s right now. %1$s needs to open Wire or log in again first. Please try again later.</string>
<!-- Gallery -->
<string name="media_gallery_default_title_name">Media Gallery</string>
<string name="media_gallery_on_image_downloaded">Saved to Downloads folder</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,14 @@ class ConnectionActionButtonViewModelTest {
.arrange()

// when
viewModel.onOpenConversation(arrangement.onOpenConversation)
viewModel.onOpenConversation(arrangement.onOpenConversation, arrangement.onMissingKeyPackages)

// then
coVerify {
arrangement.getOrCreateOneToOneConversation(TestUser.USER_ID)
}
verify { arrangement.onOpenConversation(any()) }
verify { arrangement.onMissingKeyPackages wasNot Called }
}

@Test
Expand All @@ -285,13 +286,33 @@ class ConnectionActionButtonViewModelTest {
.arrange()

// when
viewModel.onOpenConversation(arrangement.onOpenConversation)
viewModel.onOpenConversation(arrangement.onOpenConversation, arrangement.onMissingKeyPackages)

// then
coVerify {
arrangement.getOrCreateOneToOneConversation(TestUser.USER_ID)
}
verify { arrangement.onOpenConversation wasNot Called }
verify { arrangement.onMissingKeyPackages wasNot Called }
}

@Test
fun `given a conversationId, when trying to open the conversation and fails with MissingKeyPackages, then call MissingKeyPackage()`() =
runTest {
// given
val (arrangement, viewModel) = ConnectionActionButtonHiltArrangement()
.withGetOneToOneConversation(CreateConversationResult.Failure(CoreFailure.MissingKeyPackages(setOf())))
.arrange()

// when
viewModel.onOpenConversation(arrangement.onOpenConversation, arrangement.onMissingKeyPackages)

// then
coVerify {
arrangement.getOrCreateOneToOneConversation(TestUser.USER_ID)
}
verify { arrangement.onOpenConversation wasNot Called }
verify { arrangement.onMissingKeyPackages() }
}

companion object {
Expand Down Expand Up @@ -334,6 +355,9 @@ internal class ConnectionActionButtonHiltArrangement {
@MockK(relaxed = true)
lateinit var onOpenConversation: (conversationId: ConversationId) -> Unit

@MockK(relaxed = true)
lateinit var onMissingKeyPackages: () -> Unit

private val viewModel by lazy {
ConnectionActionButtonViewModelImpl(
TestDispatcherProvider(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.wire.kalium.logic.feature.connection.UnblockUserUseCase
import com.wire.kalium.logic.feature.conversation.ArchiveStatusUpdateResult
import com.wire.kalium.logic.feature.conversation.ClearConversationContentUseCase
import com.wire.kalium.logic.feature.conversation.GetOneToOneConversationUseCase
import com.wire.kalium.logic.feature.conversation.IsOneToOneConversationCreatedUseCase
import com.wire.kalium.logic.feature.conversation.RemoveMemberFromConversationUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationArchivedStatusUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationMemberRoleResult
Expand Down Expand Up @@ -112,6 +113,9 @@ internal class OtherUserProfileViewModelArrangement {
@MockK
lateinit var getUserE2eiCertificates: GetUserE2eiCertificatesUseCase

@MockK
lateinit var isOneToOneConversationCreated: IsOneToOneConversationCreatedUseCase

private val viewModel by lazy {
OtherUserProfileScreenViewModel(
TestDispatcherProvider(),
Expand All @@ -131,6 +135,7 @@ internal class OtherUserProfileViewModelArrangement {
updateConversationArchivedStatus,
getUserE2eiCertificateStatus,
getUserE2eiCertificates,
isOneToOneConversationCreated,
savedStateHandle,
)
}
Expand Down Expand Up @@ -161,6 +166,7 @@ internal class OtherUserProfileViewModelArrangement {
)
coEvery { getUserE2eiCertificateStatus.invoke(any()) } returns GetUserE2eiCertificateStatusResult.Success(CertificateStatus.VALID)
coEvery { getUserE2eiCertificates.invoke(any()) } returns mapOf()
coEvery { isOneToOneConversationCreated.invoke(any()) } returns true
}

suspend fun withBlockUserResult(result: BlockUserResult) = apply {
Expand Down
Loading
Loading