Skip to content

Commit

Permalink
feat: handle legal hold failure result when sending connection [WPB-4…
Browse files Browse the repository at this point in the history
…395] (#2636)
  • Loading branch information
saleniuk authored Jan 31, 2024
1 parent 652898a commit 90ff364
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 151 deletions.
101 changes: 72 additions & 29 deletions app/src/main/kotlin/com/wire/android/ui/common/AddContactButton.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,49 +18,92 @@

package com.wire.android.ui.common

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.wire.android.R
import com.wire.android.ui.common.button.IconAlignment
import com.wire.android.ui.common.button.WireSecondaryButton
import com.wire.android.di.hiltViewModelScoped
import com.wire.android.ui.common.button.WireSecondaryIconButton
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
import com.wire.android.ui.common.snackbar.collectAndShowSnackbar
import com.wire.android.ui.connection.ConnectionActionButtonArgs
import com.wire.android.ui.connection.ConnectionActionButtonViewModel
import com.wire.android.ui.connection.ConnectionActionButtonViewModelImpl
import com.wire.android.ui.connection.ConnectionActionState
import com.wire.android.ui.connection.MissingLegalHoldConsentDialogState
import com.wire.android.ui.legalhold.dialog.connectionfailed.LegalHoldSubjectConnectionFailedDialog
import com.wire.android.ui.theme.WireTheme
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.kalium.logic.data.user.UserId

@Composable
fun AddContactButton(
onIconClicked: () -> Unit,
modifier: Modifier = Modifier
userId: UserId,
userName: String,
modifier: Modifier = Modifier,
viewModel: ConnectionActionButtonViewModel =
hiltViewModelScoped<ConnectionActionButtonViewModelImpl, ConnectionActionButtonViewModel, ConnectionActionButtonArgs>(
ConnectionActionButtonArgs(userId, userName)
),
) {
WireSecondaryButton(
onClick = { onIconClicked() },
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_add_contact),
contentDescription = stringResource(R.string.content_description_add_contact),
)
},
leadingIconAlignment = IconAlignment.Center,
fillMaxWidth = false,
minSize = MaterialTheme.wireDimensions.buttonSmallMinSize,
minClickableSize = MaterialTheme.wireDimensions.buttonMinClickableSize,
shape = RoundedCornerShape(12.dp),
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
modifier = modifier
)
val state = viewModel.actionableState()
LocalSnackbarHostState.current.collectAndShowSnackbar(snackbarFlow = viewModel.infoMessage)

with(state) {
if (missingLegalHoldConsentDialogState is MissingLegalHoldConsentDialogState.Visible) {
LegalHoldSubjectConnectionFailedDialog(viewModel::onMissingLegalHoldConsentDismissed)
}

WireSecondaryIconButton(
onButtonClicked = remember(viewModel) { { if (!isPerformingAction) viewModel.onSendConnectionRequest() } },
iconResource = R.drawable.ic_add_contact,
contentDescription = R.string.content_description_add_contact,
loading = isPerformingAction,
modifier = modifier
)
}
}

@PreviewMultipleThemes
@Composable
fun PreviewAddContactButton() {
WireTheme {
AddContactButton(onIconClicked = {})
AddContactButton(
userId = UserId("value", "domain"),
userName = "Username",
viewModel = object : ConnectionActionButtonViewModel {
override fun actionableState() = ConnectionActionState(isPerformingAction = false)
}
)
}
}

@PreviewMultipleThemes
@Composable
fun PreviewAddContactButtonLoading() {
WireTheme {
AddContactButton(
userId = UserId("value", "domain"),
userName = "Username",
viewModel = object : ConnectionActionButtonViewModel {
override fun actionableState() = ConnectionActionState(isPerformingAction = true)
}
)
}
}

@PreviewMultipleThemes
@Composable
fun PreviewAddContactButtonDialog() {
WireTheme {
AddContactButton(
userId = UserId("value", "domain"),
userName = "Username",
viewModel = object : ConnectionActionButtonViewModel {
override fun actionableState() = ConnectionActionState(
missingLegalHoldConsentDialogState = MissingLegalHoldConsentDialogState.Visible(UserId("value", "domain"))
)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.wire.android.ui.theme.wireDimensions
@Composable
fun WirePrimaryIconButton(
onButtonClicked: () -> Unit,
loading: Boolean = false,
@DrawableRes iconResource: Int,
@StringRes contentDescription: Int,
shape: Shape = RoundedCornerShape(MaterialTheme.wireDimensions.buttonCornerSize),
Expand All @@ -54,7 +55,8 @@ fun WirePrimaryIconButton(
) {
WirePrimaryButton(
onClick = onButtonClicked,
leadingIcon = {
loading = loading,
trailingIcon = {
Icon(
painter = painterResource(id = iconResource),
contentDescription = stringResource(contentDescription),
Expand All @@ -65,7 +67,7 @@ fun WirePrimaryIconButton(
minSize = minSize,
minClickableSize = minClickableSize,
contentPadding = PaddingValues(horizontal = 0.dp, vertical = 0.dp),
leadingIconAlignment = IconAlignment.Center,
trailingIconAlignment = IconAlignment.Center,
state = state,
colors = colors,
clickBlockParams = clickBlockParams,
Expand All @@ -77,10 +79,15 @@ fun WirePrimaryIconButton(
@Preview
@Composable
fun PreviewWirePrimaryIconButton() {
WirePrimaryIconButton({}, R.drawable.ic_add, 0)
WirePrimaryIconButton({}, false, R.drawable.ic_add, 0)
}
@Preview
@Composable
fun PreviewWirePrimaryIconButtonLoading() {
WirePrimaryIconButton({}, true, R.drawable.ic_add, 0)
}
@Preview
@Composable
fun PreviewWirePrimaryIconButtonRound() {
WirePrimaryIconButton({}, R.drawable.ic_add, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp))
WirePrimaryIconButton({}, false, R.drawable.ic_add, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp))
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import com.wire.android.ui.theme.wireDimensions
@Composable
fun WireSecondaryIconButton(
onButtonClicked: () -> Unit,
loading: Boolean = false,
@DrawableRes iconResource: Int,
@StringRes contentDescription: Int,
shape: Shape = RoundedCornerShape(MaterialTheme.wireDimensions.buttonCornerSize),
Expand All @@ -57,7 +58,8 @@ fun WireSecondaryIconButton(
) {
WireSecondaryButton(
onClick = onButtonClicked,
leadingIcon = {
loading = loading,
trailingIcon = {
Icon(
painter = painterResource(id = iconResource),
contentDescription = stringResource(contentDescription),
Expand All @@ -68,7 +70,7 @@ fun WireSecondaryIconButton(
minSize = minSize,
minClickableSize = minClickableSize,
contentPadding = PaddingValues(horizontal = 0.dp, vertical = 0.dp),
leadingIconAlignment = IconAlignment.Center,
trailingIconAlignment = IconAlignment.Center,
state = state,
colors = colors,
clickBlockParams = clickBlockParams,
Expand All @@ -80,11 +82,15 @@ fun WireSecondaryIconButton(
@Preview
@Composable
fun PreviewWireSecondaryIconButton() {
WireSecondaryIconButton({}, R.drawable.ic_add, 0)
WireSecondaryIconButton({}, false, R.drawable.ic_add, 0)
}
@Preview
@Composable
fun PreviewWireSecondaryIconButtonLoading() {
WireSecondaryIconButton({}, true, R.drawable.ic_add, 0)
}

@Preview
@Composable
fun PreviewWireSecondaryIconButtonRound() {
WireSecondaryIconButton({}, R.drawable.ic_add, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp))
WireSecondaryIconButton({}, false, R.drawable.ic_add, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp))
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.wire.android.ui.theme.wireDimensions
@Composable
fun WireTertiaryIconButton(
onButtonClicked: () -> Unit,
loading: Boolean = false,
@DrawableRes iconResource: Int,
@StringRes contentDescription: Int,
shape: Shape = RoundedCornerShape(MaterialTheme.wireDimensions.buttonCornerSize),
Expand All @@ -54,7 +55,8 @@ fun WireTertiaryIconButton(
) {
WireTertiaryButton(
onClick = onButtonClicked,
leadingIcon = {
loading = loading,
trailingIcon = {
Icon(
painter = painterResource(id = iconResource),
contentDescription = stringResource(contentDescription),
Expand All @@ -65,7 +67,7 @@ fun WireTertiaryIconButton(
minSize = minSize,
minClickableSize = minClickableSize,
contentPadding = PaddingValues(horizontal = 0.dp, vertical = 0.dp),
leadingIconAlignment = IconAlignment.Center,
trailingIconAlignment = IconAlignment.Center,
state = state,
colors = colors,
clickBlockParams = clickBlockParams,
Expand All @@ -77,10 +79,15 @@ fun WireTertiaryIconButton(
@Preview
@Composable
fun PreviewWireTertiaryIconButton() {
WireTertiaryIconButton({}, R.drawable.ic_add, 0)
WireTertiaryIconButton({}, false, R.drawable.ic_add, 0)
}
@Preview
@Composable
fun PreviewWireTertiaryIconButtonLoading() {
WireTertiaryIconButton({}, true, R.drawable.ic_add, 0)
}
@Preview
@Composable
fun PreviewWireTertiaryIconButtonRound() {
WireTertiaryIconButton({}, R.drawable.ic_add, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp))
WireTertiaryIconButton({}, false, R.drawable.ic_add, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp))
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ 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.rememberVisibilityState
import com.wire.android.ui.legalhold.dialog.connectionfailed.LegalHoldSubjectConnectionFailedDialog
import com.wire.android.ui.theme.WireTheme
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.kalium.logic.data.id.ConversationId
Expand Down Expand Up @@ -70,6 +71,12 @@ fun ConnectionActionButton(
unblockUserDialogState.dismiss()
}

with(viewModel.actionableState()) {
if (missingLegalHoldConsentDialogState is MissingLegalHoldConsentDialogState.Visible) {
LegalHoldSubjectConnectionFailedDialog(viewModel::onMissingLegalHoldConsentDismissed)
}
}

when (connectionStatus) {
ConnectionState.SENT -> WireSecondaryButton(
text = stringResource(R.string.connection_label_cancel_request),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@ import androidx.lifecycle.viewModelScope
import com.wire.android.R
import com.wire.android.appLogger
import com.wire.android.di.scopedArgs
import com.wire.android.model.ActionableState
import com.wire.android.model.finishAction
import com.wire.android.model.performAction
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.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.feature.connection.AcceptConnectionRequestUseCase
Expand All @@ -59,13 +57,14 @@ import javax.inject.Inject
interface ConnectionActionButtonViewModel {
val infoMessage: SharedFlow<UIText>
get() = MutableSharedFlow()
fun actionableState(): ActionableState = ActionableState()
fun actionableState(): ConnectionActionState = ConnectionActionState()
fun onSendConnectionRequest() {}
fun onCancelConnectionRequest() {}
fun onAcceptConnectionRequest() {}
fun onIgnoreConnectionRequest(onSuccess: (userName: String) -> Unit) {}
fun onUnblockUser() {}
fun onOpenConversation(onSuccess: (conversationId: ConversationId) -> Unit) {}
fun onMissingLegalHoldConsentDismissed() {}
}

@Suppress("LongParameterList", "TooManyFunctions")
Expand All @@ -85,37 +84,37 @@ class ConnectionActionButtonViewModelImpl @Inject constructor(
private val userId: QualifiedID = args.userId
val userName: String = args.userName

private var state: ActionableState by mutableStateOf(ActionableState())
var state: ConnectionActionState by mutableStateOf(ConnectionActionState())

private val _infoMessage = MutableSharedFlow<UIText>()
override val infoMessage = _infoMessage.asSharedFlow()

override fun actionableState(): ActionableState = state
override fun actionableState(): ConnectionActionState = state

override fun onSendConnectionRequest() {
viewModelScope.launch {
state = state.performAction()
when (sendConnectionRequest(userId)) {
is SendConnectionRequestResult.Success -> {
state = state.finishAction()
_infoMessage.emit(UIText.StringResource(R.string.connection_request_sent))
}

is SendConnectionRequestResult.Failure.MissingLegalHoldConsent -> {
appLogger.d(("Couldn't send a connect request to user ${userId.value.obfuscateId()} - missing legal hold consent"))
state = state.copy(missingLegalHoldConsentDialogState = MissingLegalHoldConsentDialogState.Visible(userId))
}

is SendConnectionRequestResult.Failure.FederationDenied -> {
state = state.finishAction()
_infoMessage.emit(
UIText.StringResource(
R.string.connection_request_sent_federation_denied_error,
userName
)
)
appLogger.d(("Couldn't send a connect request to user ${userId.value.obfuscateId()} - federation denied"))
_infoMessage.emit(UIText.StringResource(R.string.connection_request_sent_federation_denied_error, userName))
}

is SendConnectionRequestResult.Failure.GenericFailure -> {
state = state.finishAction()
is SendConnectionRequestResult.Failure -> {
appLogger.d(("Couldn't send a connect request to user ${userId.value.obfuscateId()}"))
_infoMessage.emit(UIText.StringResource(R.string.connection_request_sent_error))
}
}
state = state.finishAction()
}
}

Expand Down Expand Up @@ -204,4 +203,8 @@ class ConnectionActionButtonViewModelImpl @Inject constructor(
}
}
}

override fun onMissingLegalHoldConsentDismissed() {
state = state.copy(missingLegalHoldConsentDialogState = MissingLegalHoldConsentDialogState.Hidden)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@
* 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.connection

package com.wire.android.model
import com.wire.kalium.logic.data.user.UserId

/**
* Wrapper for view model states with additional [isPerformingAction] which informs UI that action is being performed,
* like:
* - blocking button action when some action is already triggered
*/
data class ActionableState(
val isPerformingAction: Boolean = false
)
data class ConnectionActionState(
val isPerformingAction: Boolean = false,
val missingLegalHoldConsentDialogState: MissingLegalHoldConsentDialogState = MissingLegalHoldConsentDialogState.Hidden,
) {
fun performAction() = copy(isPerformingAction = true)
fun finishAction() = copy(isPerformingAction = false)
}

fun ActionableState.performAction(): ActionableState = this.copy(isPerformingAction = true)
fun ActionableState.finishAction(): ActionableState = this.copy(isPerformingAction = false)
sealed class MissingLegalHoldConsentDialogState {
data object Hidden : MissingLegalHoldConsentDialogState()
data class Visible(val userId: UserId) : MissingLegalHoldConsentDialogState()
}
Loading

0 comments on commit 90ff364

Please sign in to comment.