mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-26 19:44:08 +01:00
Update reg v5 UI for quick restore.
This commit is contained in:
+218
-130
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -23,7 +24,6 @@ import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -40,11 +40,15 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.AllDevicePreviews
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.QrCode
|
||||
import org.signal.core.ui.compose.QrCodeData
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.screens.OnePaneRegistrationScaffold
|
||||
import org.signal.registration.screens.RegistrationScaffold
|
||||
import org.signal.registration.screens.TwoPaneRegistrationScaffold
|
||||
|
||||
/**
|
||||
* Screen to display QR code for restoring from an old device.
|
||||
@@ -55,170 +59,240 @@ fun QuickRestoreQrScreen(
|
||||
state: QuickRestoreQrState,
|
||||
onEvent: (QuickRestoreQrEvents) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
when (val layoutParams = RegistrationScaffold.rememberLayoutParams()) {
|
||||
is RegistrationScaffold.Params.OnePane -> OnePaneLayout(layoutParams, state, onEvent, modifier)
|
||||
is RegistrationScaffold.Params.TwoPane -> TwoPaneLayout(layoutParams, state, onEvent, modifier)
|
||||
}
|
||||
|
||||
StateDialogs(state = state, onEvent = onEvent)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OnePaneLayout(
|
||||
params: RegistrationScaffold.Params.OnePane,
|
||||
state: QuickRestoreQrState,
|
||||
onEvent: (QuickRestoreQrEvents) -> Unit,
|
||||
modifier: Modifier
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
.padding(24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.QuickRestoreQRScreen__scan),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
// QR Code display area
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 280.dp)
|
||||
.aspectRatio(1f)
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
OnePaneRegistrationScaffold(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
params = params,
|
||||
content = { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
.verticalScroll(scrollState)
|
||||
.padding(paddingValues),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = state.qrState,
|
||||
contentKey = { it::class },
|
||||
label = "qr-code-state"
|
||||
) { qrState ->
|
||||
when (qrState) {
|
||||
is QrState.Loaded -> {
|
||||
QrCode(
|
||||
data = qrState.qrCodeData,
|
||||
foregroundColor = Color(0xFF2449C0),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
Heading()
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
QrCodePane(state = state, onEvent = onEvent)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Instructions()
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
CancelFooter(onEvent)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TwoPaneLayout(
|
||||
params: RegistrationScaffold.Params.TwoPane,
|
||||
state: QuickRestoreQrState,
|
||||
onEvent: (QuickRestoreQrEvents) -> Unit,
|
||||
modifier: Modifier
|
||||
) {
|
||||
val firstPaneScrollState = rememberScrollState()
|
||||
val secondPaneScrollState = rememberScrollState()
|
||||
|
||||
TwoPaneRegistrationScaffold(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
params = params,
|
||||
firstPane = { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(firstPaneScrollState)
|
||||
.padding(paddingValues),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Heading()
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Instructions()
|
||||
}
|
||||
},
|
||||
secondPane = { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(secondPaneScrollState)
|
||||
.padding(paddingValues),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
QrCodePane(state = state, onEvent = onEvent)
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
CancelFooter(onEvent)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Heading() {
|
||||
Text(
|
||||
text = stringResource(R.string.QuickRestoreQRScreen__scan),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun QrCodePane(
|
||||
state: QuickRestoreQrState,
|
||||
onEvent: (QuickRestoreQrEvents) -> Unit
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.widthIn(max = 280.dp)
|
||||
.aspectRatio(1f)
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = state.qrState,
|
||||
contentKey = { it::class },
|
||||
label = "qr-code-state"
|
||||
) { qrState ->
|
||||
when (qrState) {
|
||||
is QrState.Loaded -> {
|
||||
QrCode(
|
||||
data = qrState.qrCodeData,
|
||||
foregroundColor = Color(0xFF2449C0),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
|
||||
QrState.Loading -> {
|
||||
CircularProgressIndicator(modifier = Modifier.size(48.dp))
|
||||
}
|
||||
|
||||
QrState.Scanned -> {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.QuickRestoreQRScreen__scanned),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
QrState.Loading -> {
|
||||
CircularProgressIndicator(modifier = Modifier.size(48.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
QrState.Scanned -> {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.QuickRestoreQRScreen__scanned),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Button(onClick = { onEvent(QuickRestoreQrEvents.RetryQrCode) }) {
|
||||
Text(stringResource(R.string.QuickRestoreQRScreen__retry))
|
||||
}
|
||||
Button(onClick = { onEvent(QuickRestoreQrEvents.RetryQrCode) }) {
|
||||
Text(stringResource(R.string.QuickRestoreQRScreen__retry))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QrState.Failed -> {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.QuickRestoreQRScreen__failed),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
QrState.Failed -> {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.QuickRestoreQRScreen__failed),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Button(onClick = { onEvent(QuickRestoreQrEvents.RetryQrCode) }) {
|
||||
Text(stringResource(R.string.QuickRestoreQRScreen__retry))
|
||||
}
|
||||
Button(onClick = { onEvent(QuickRestoreQrEvents.RetryQrCode) }) {
|
||||
Text(stringResource(R.string.QuickRestoreQRScreen__retry))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
@Composable
|
||||
private fun Instructions() {
|
||||
Column(
|
||||
modifier = Modifier.widthIn(max = 320.dp)
|
||||
) {
|
||||
InstructionRow(
|
||||
vector = SignalIcons.Phone.imageVector,
|
||||
instruction = stringResource(R.string.QuickRestoreQRScreen__step_1)
|
||||
)
|
||||
|
||||
// Instructions
|
||||
Column(
|
||||
modifier = Modifier.widthIn(max = 320.dp)
|
||||
) {
|
||||
InstructionRow(
|
||||
vector = SignalIcons.Phone.imageVector,
|
||||
instruction = stringResource(R.string.QuickRestoreQRScreen__step_1)
|
||||
)
|
||||
InstructionRow(
|
||||
vector = SignalIcons.Camera.imageVector,
|
||||
instruction = stringResource(R.string.QuickRestoreQRScreen__step_2)
|
||||
)
|
||||
|
||||
InstructionRow(
|
||||
vector = SignalIcons.Camera.imageVector,
|
||||
instruction = stringResource(R.string.QuickRestoreQRScreen__step_2)
|
||||
)
|
||||
|
||||
InstructionRow(
|
||||
vector = SignalIcons.QrCode.imageVector,
|
||||
instruction = stringResource(R.string.QuickRestoreQRScreen__step_3)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
InstructionRow(
|
||||
vector = SignalIcons.QrCode.imageVector,
|
||||
instruction = stringResource(R.string.QuickRestoreQRScreen__step_3)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CancelFooter(onEvent: (QuickRestoreQrEvents) -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
TextButton(
|
||||
onClick = { onEvent(QuickRestoreQrEvents.Cancel) }
|
||||
) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
// Loading dialog
|
||||
@Composable
|
||||
private fun StateDialogs(
|
||||
state: QuickRestoreQrState,
|
||||
onEvent: (QuickRestoreQrEvents) -> Unit
|
||||
) {
|
||||
if (state.isRegistering) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { },
|
||||
confirmButton = { },
|
||||
text = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(stringResource(R.string.QuickRestoreQRScreen__reregister))
|
||||
}
|
||||
}
|
||||
Dialogs.IndeterminateProgressDialog(
|
||||
stringResource(R.string.QuickRestoreQRScreen__reregister)
|
||||
)
|
||||
}
|
||||
|
||||
// Error dialog
|
||||
if (state.showRegistrationError) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { onEvent(QuickRestoreQrEvents.DismissError) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { onEvent(QuickRestoreQrEvents.DismissError) }) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Text(state.errorMessage ?: stringResource(R.string.QuickRestoreQRScreen__error))
|
||||
}
|
||||
Dialogs.SimpleMessageDialog(
|
||||
message = state.errorMessage ?: stringResource(R.string.QuickRestoreQRScreen__error),
|
||||
dismiss = stringResource(android.R.string.ok),
|
||||
onDismiss = { onEvent(QuickRestoreQrEvents.DismissError) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -296,3 +370,17 @@ private fun QuickRestoreQrScreenRegisteringPreview() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AllDevicePreviews
|
||||
@Composable
|
||||
private fun QuickRestoreQrScreenRegisteringFailedPreview() {
|
||||
Previews.Preview {
|
||||
QuickRestoreQrScreen(
|
||||
state = QuickRestoreQrState(
|
||||
qrState = QrState.Scanned,
|
||||
showRegistrationError = true
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,21 +287,21 @@
|
||||
<string name="AccountLockedScreen__your_account">Your account has been locked to protect your privacy and security. After %1$d days of inactivity in your account you\'ll be able to re-register this phone number without needing your PIN. All content will be deleted.</string>
|
||||
|
||||
<!-- Step 1 to quick restore -->
|
||||
<string name="QuickRestoreQRScreen__step_1">On your old phone, open Signal</string>
|
||||
<string name="QuickRestoreQRScreen__step_1">Open Signal on your old device</string>
|
||||
<!-- Step 2 to quick restore -->
|
||||
<string name="QuickRestoreQRScreen__step_2">Go to Settings > Transfer account</string>
|
||||
<string name="QuickRestoreQRScreen__step_2">Tap the camera icon </string>
|
||||
<!-- Step 3 to quick restore -->
|
||||
<string name="QuickRestoreQRScreen__step_3">Scan this QR code</string>
|
||||
<string name="QuickRestoreQRScreen__step_3">Scan this code with the camera</string>
|
||||
<!-- Loading dialog text when registering -->
|
||||
<string name="QuickRestoreQRScreen__reregister">Registering…</string>
|
||||
<!-- Error message text shown if registering fails -->
|
||||
<string name="QuickRestoreQRScreen__error">An error occurred during registration</string>
|
||||
<!-- Title for quick restoring -->
|
||||
<string name="QuickRestoreQRScreen__scan">Scan from old device</string>
|
||||
<string name="QuickRestoreQRScreen__scan">Scan this code with your old phone</string>
|
||||
<!-- Confirmation dialog when qr code is successfully scanned -->
|
||||
<string name="QuickRestoreQRScreen__scanned">QR code scanned</string>
|
||||
<!-- Button text to retry scanning the QR code -->
|
||||
<string name="QuickRestoreQRScreen__retry">Retry</string>
|
||||
<!-- Failure text when a code could not be generated -->
|
||||
<string name="QuickRestoreQRScreen__failed">"Failed to generate QR code"</string>
|
||||
<string name="QuickRestoreQRScreen__failed">Unable to generate QR code</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user