diff --git a/feature/registration/src/main/java/org/signal/registration/screens/quickrestore/QuickRestoreQrScreen.kt b/feature/registration/src/main/java/org/signal/registration/screens/quickrestore/QuickRestoreQrScreen.kt
index 5ae57f56ee..650cef5f86 100644
--- a/feature/registration/src/main/java/org/signal/registration/screens/quickrestore/QuickRestoreQrScreen.kt
+++ b/feature/registration/src/main/java/org/signal/registration/screens/quickrestore/QuickRestoreQrScreen.kt
@@ -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 = {}
+ )
+ }
+}
diff --git a/feature/registration/src/main/res/values/strings.xml b/feature/registration/src/main/res/values/strings.xml
index 646c0e7523..bea4b1e8d3 100644
--- a/feature/registration/src/main/res/values/strings.xml
+++ b/feature/registration/src/main/res/values/strings.xml
@@ -287,21 +287,21 @@
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.
- On your old phone, open Signal
+ Open Signal on your old device
- Go to Settings > Transfer account
+ Tap the camera icon
- Scan this QR code
+ Scan this code with the camera
Registering…
An error occurred during registration
- Scan from old device
+ Scan this code with your old phone
QR code scanned
Retry
- "Failed to generate QR code"
+ Unable to generate QR code