From 7114d3cc708e83406bb7af34944c78bf883866f9 Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Tue, 7 Feb 2023 14:31:44 -0300 Subject: [PATCH] Allow user to wipe mailbox after confirmation dialog --- .../desktop/mailbox/MailboxErrorDialog.kt | 6 +- .../briar/desktop/mailbox/MailboxScreen.kt | 8 +- .../desktop/mailbox/MailboxStatusScreen.kt | 75 ++++++++++++++++++- .../briar/desktop/mailbox/MailboxViewModel.kt | 20 ++++- .../resources/strings/BriarDesktop.properties | 6 ++ 5 files changed, 104 insertions(+), 11 deletions(-) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxErrorDialog.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxErrorDialog.kt index 6f64ae07cc..6cdf895cef 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxErrorDialog.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxErrorDialog.kt @@ -34,6 +34,8 @@ import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.rememberDialogState import org.briarproject.bramble.api.mailbox.MailboxPairingState import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.OfflineWhenPairing +import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.Pairing +import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.WasUnpaired import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n import org.briarproject.briar.desktop.utils.PreviewUtils.preview import java.awt.Dimension @@ -86,7 +88,7 @@ fun MailboxErrorDialog( private fun MailboxPairingUiState.getError(): String = when (this) { is OfflineWhenPairing -> i18n("mailbox.setup.offline_error_title") + "\n\n" + i18n("mailbox.setup.offline_error_description") - is MailboxPairingUiState.Pairing -> when (val s = pairingState) { + is Pairing -> when (val s = pairingState) { is MailboxPairingState.InvalidQrCode -> i18n("mailbox.setup.link.error") is MailboxPairingState.MailboxAlreadyPaired -> i18n("mailbox.setup.already_paired_title") + "\n\n" + i18n("mailbox.setup.already_paired_description") @@ -95,5 +97,7 @@ private fun MailboxPairingUiState.getError(): String = when (this) { is MailboxPairingState.UnexpectedError -> i18n("mailbox.setup.assertion_error_description") else -> error("Unhandled pairing state: ${s::class.simpleName}") } + is WasUnpaired -> if (tellUserToWipeMailbox) i18n("mailbox.unlink.no_wipe.title") + + "\n\n" + i18n("mailbox.unlink.no_wipe.message") else i18n("mailbox.unlink.no_wipe.title") else -> error("Unhandled state: ${this::class.simpleName}") } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxScreen.kt index 27b2a02a58..c9920763af 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxScreen.kt @@ -32,7 +32,6 @@ import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.Pairing import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.Unknown import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.WasUnpaired import org.briarproject.briar.desktop.ui.Loader -import org.briarproject.briar.desktop.ui.UiPlaceholder import org.briarproject.briar.desktop.viewmodel.viewModel @Composable @@ -49,6 +48,8 @@ fun MailboxScreen(viewModel: MailboxViewModel = viewModel()) { status = viewModel.status.value, isCheckingConnection = false, // we just paired, there was no time to trigger a connection check onCheckConnection = viewModel::checkConnection, + onWipe = viewModel::unlink, + isWiping = false, // same as above, just paired ) } OfflineWhenPairing -> MailboxSetupScreen(viewModel, true) @@ -56,7 +57,10 @@ fun MailboxScreen(viewModel: MailboxViewModel = viewModel()) { status = viewModel.status.value, isCheckingConnection = state.connectionCheckRunning, onCheckConnection = viewModel::checkConnection, + onWipe = viewModel::unlink, + isWiping = state.isWiping, ) - is WasUnpaired -> UiPlaceholder() // TODO + // the unpair success message will be shown like an error dialog + is WasUnpaired -> MailboxSetupScreen(viewModel, true) } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxStatusScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxStatusScreen.kt index 7464fa4601..e23f2ab7d5 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxStatusScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxStatusScreen.kt @@ -20,12 +20,19 @@ package org.briarproject.briar.desktop.mailbox import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize.Max import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.AlertDialog import androidx.compose.material.Button +import androidx.compose.material.ButtonType.DESTRUCTIVE +import androidx.compose.material.ButtonType.NEUTRAL import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.DialogButton +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedButton @@ -35,6 +42,8 @@ import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Error import androidx.compose.material.icons.filled.QuestionMark import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -62,6 +71,7 @@ fun main() = preview( listOf("error", "problem", "mailbox too old", "briar too old", "ok") ), "isCheckingConnection" to false, + "isWiping" to false, ) { val now = System.currentTimeMillis() val status = when (getStringParameter("status")) { @@ -71,7 +81,13 @@ fun main() = preview( "briar too old" -> MailboxStatus(now, now, 0, listOf(MailboxVersion(42, 0))) else -> MailboxStatus(now, now - 18_354, 0, CLIENT_SUPPORTS) } - MailboxStatusScreen(status, getBooleanParameter("isCheckingConnection")) {} + MailboxStatusScreen( + status = status, + isCheckingConnection = getBooleanParameter("isCheckingConnection"), + onCheckConnection = {}, + isWiping = getBooleanParameter("isWiping"), + onWipe = {}, + ) } @Composable @@ -79,8 +95,11 @@ fun MailboxStatusScreen( status: MailboxStatus?, isCheckingConnection: Boolean, onCheckConnection: () -> Unit, + isWiping: Boolean, + onWipe: () -> Unit, ) { if (status == null) return // not expected to happen (for a noticeable amount of time) + val deleteGroupDialogVisible = remember { mutableStateOf(false) } Column( verticalArrangement = spacedBy(16.dp), horizontalAlignment = CenterHorizontally, @@ -129,20 +148,27 @@ fun MailboxStatusScreen( } if (isCheckingConnection) CircularProgressIndicator() else Button( - onClick = onCheckConnection + onClick = onCheckConnection, + enabled = !isWiping, ) { Text( text = i18n("mailbox.status.check.connection.button"), ) } Spacer(modifier = Modifier.weight(1f)) - OutlinedButton( - onClick = { /* TODO */ }, + if (isWiping) CircularProgressIndicator() + else OutlinedButton( + onClick = { deleteGroupDialogVisible.value = true }, + enabled = !isCheckingConnection, ) { Text( text = i18n("mailbox.status.unlink.button"), ) } + if (deleteGroupDialogVisible.value) MailboxWipeDialog( + close = { deleteGroupDialogVisible.value = false }, + onWipe = onWipe, + ) } } @@ -174,3 +200,44 @@ private fun MailboxStatusView( modifier = Modifier.alpha(0.56f) ) } + +@Composable +@OptIn(ExperimentalMaterialApi::class) +fun MailboxWipeDialog( + close: () -> Unit, + onWipe: () -> Unit = {}, +) { + AlertDialog( + onDismissRequest = close, + title = { + Text( + text = i18n("mailbox.unlink.dialog.title"), + modifier = Modifier.width(Max), + style = MaterialTheme.typography.h6, + ) + }, + text = { + Column(verticalArrangement = spacedBy(16.dp)) { + Text(i18n("mailbox.unlink.dialog.question")) + Text(i18n("mailbox.unlink.dialog.warning")) + } + }, + dismissButton = { + DialogButton( + onClick = { close() }, + text = i18n("cancel"), + type = NEUTRAL, + ) + }, + confirmButton = { + DialogButton( + onClick = { + close() + onWipe() + }, + text = i18n("mailbox.unlink.dialog.button"), + type = DESTRUCTIVE, + ) + }, + ) +} diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxViewModel.kt index 8e293a210c..50fd5e3ada 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/mailbox/MailboxViewModel.kt @@ -40,6 +40,7 @@ import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.NotSetup import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.OfflineWhenPairing import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.Pairing import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.Unknown +import org.briarproject.briar.desktop.mailbox.MailboxPairingUiState.WasUnpaired import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.threading.UiExecutor import org.briarproject.briar.desktop.utils.KLoggerUtils.i @@ -53,7 +54,7 @@ sealed class MailboxPairingUiState { object NotSetup : MailboxPairingUiState() class Pairing(val pairingState: MailboxPairingState) : MailboxPairingUiState() object OfflineWhenPairing : MailboxPairingUiState() - class IsPaired(val connectionCheckRunning: Boolean) : MailboxPairingUiState() + class IsPaired(val connectionCheckRunning: Boolean, val isWiping: Boolean) : MailboxPairingUiState() class WasUnpaired(val tellUserToWipeMailbox: Boolean) : MailboxPairingUiState() } @@ -93,7 +94,7 @@ class MailboxViewModel @Inject constructor( if (isPaired) { val mailboxStatus = mailboxManager.getMailboxStatus(txn) briarExecutors.onUiThread { - _pairingState.value = IsPaired(false) + _pairingState.value = IsPaired(connectionCheckRunning = false, isWiping = false) _status.value = mailboxStatus } } else briarExecutors.onUiThread { @@ -160,16 +161,27 @@ class MailboxViewModel @Inject constructor( @UiExecutor fun checkConnection() { // we can only check the connection when we are already paired (or just finished pairing) - _pairingState.value = IsPaired(true) + _pairingState.value = IsPaired(connectionCheckRunning = true, isWiping = false) briarExecutors.onIoThread { // this check updates _status state via an Event val success = mailboxManager.checkConnection() LOG.i { "Got result from connection check: $success" } briarExecutors.onUiThread { val s = pairingState.value - if (s is IsPaired) _pairingState.value = IsPaired(false) + if (s is IsPaired) _pairingState.value = IsPaired(connectionCheckRunning = false, isWiping = false) else LOG.w { "Unexpected state: ${s::class.simpleName}" } } } } + + @UiExecutor + fun unlink() { + _pairingState.value = IsPaired(connectionCheckRunning = false, isWiping = true) + briarExecutors.onIoThread { + val wasWiped = mailboxManager.unPair() + briarExecutors.onUiThread { + _pairingState.value = WasUnpaired(!wasWiped) + } + } + } } diff --git a/briar-desktop/src/main/resources/strings/BriarDesktop.properties b/briar-desktop/src/main/resources/strings/BriarDesktop.properties index 58fd197421..a317839da5 100644 --- a/briar-desktop/src/main/resources/strings/BriarDesktop.properties +++ b/briar-desktop/src/main/resources/strings/BriarDesktop.properties @@ -261,6 +261,12 @@ mailbox.setup.io_error_description=Ensure that both devices are connected to the mailbox.setup.assertion_error_description=Please create a bug report if the issue persists. mailbox.setup.offline_error_title=Offline mailbox.setup.offline_error_description=Ensure that you are online and try again after a while. +mailbox.unlink.no_wipe.title=Your Mailbox has been unlinked +mailbox.unlink.no_wipe.message=Next time you have access to your Mailbox device, please open the Mailbox app and tap the "Unlink" button to complete the process.\n\nIf you no longer have access to your Mailbox device, don't worry. Your data is encrypted so it will remain secure even if you don't complete the process. +mailbox.unlink.dialog.title=Unlink mailbox? +mailbox.unlink.dialog.question=Are you sure you want to unlink your Mailbox? +mailbox.unlink.dialog.warning=If you unlink your Mailbox, you won't be able to receive messages while Briar is offline. +mailbox.unlink.dialog.button=Unlink mailbox.status.error=Mailbox is unavailable mailbox.status.problem=Briar is having trouble connecting to the Mailbox mailbox.status.app_too_old.title=Briar is too old -- GitLab