diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/AddLinkDeviceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/AddLinkDeviceFragment.kt
index c13e9555cf..706e8d4235 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/AddLinkDeviceFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/AddLinkDeviceFragment.kt
@@ -62,7 +62,7 @@ class AddLinkDeviceFragment : ComposeFragment() {
onQrCodeScanned = { data -> viewModel.onQrCodeScanned(data) },
onQrCodeApproved = {
navController.popBackStack()
- viewModel.addDevice()
+ viewModel.addDevice(shouldSync = false)
},
onQrCodeDismissed = { viewModel.onQrCodeDismissed() },
onQrCodeRetry = { viewModel.onQrCodeScanned(state.linkUri.toString()) },
@@ -125,6 +125,7 @@ private fun MainScreen(
linkDeviceResult = state.linkDeviceResult,
onLinkDeviceSuccess = onLinkDeviceSuccess,
onLinkDeviceFailure = onLinkDeviceFailure,
+ navController = navController,
modifier = Modifier.padding(contentPadding)
)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceQrScanScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceQrScanScreen.kt
index f4f68be868..5773d6fbcf 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceQrScanScreen.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceQrScanScreen.kt
@@ -12,12 +12,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavController
import org.signal.core.ui.Dialogs
import org.signal.qr.QrScannerView
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.LinkDeviceResult
import org.thoughtcrime.securesms.mediasend.camerax.CameraXModelBlocklist
import org.thoughtcrime.securesms.qr.QrScanScreens
+import org.thoughtcrime.securesms.util.navigation.safeNavigate
import java.util.concurrent.TimeUnit
/**
@@ -36,6 +38,7 @@ fun LinkDeviceQrScanScreen(
linkDeviceResult: LinkDeviceResult,
onLinkDeviceSuccess: () -> Unit,
onLinkDeviceFailure: () -> Unit,
+ navController: NavController?,
modifier: Modifier = Modifier
) {
val lifecycleOwner = LocalLifecycleOwner.current
@@ -45,7 +48,10 @@ fun LinkDeviceQrScanScreen(
LinkDeviceSettingsState.QrCodeState.NONE -> {
Unit
}
- LinkDeviceSettingsState.QrCodeState.VALID -> {
+ LinkDeviceSettingsState.QrCodeState.VALID_WITH_SYNC -> {
+ navController?.safeNavigate(R.id.action_addLinkDeviceFragment_to_linkDeviceSyncBottomSheet)
+ }
+ LinkDeviceSettingsState.QrCodeState.VALID_WITHOUT_SYNC -> {
Dialogs.SimpleAlertDialog(
title = stringResource(id = R.string.DeviceProvisioningActivity_link_this_device),
body = stringResource(id = R.string.AddLinkDeviceFragment__this_device_will_see_your_groups_contacts),
diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceSettingsState.kt
index bea16668b7..c97795cf38 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceSettingsState.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceSettingsState.kt
@@ -40,6 +40,6 @@ data class LinkDeviceSettingsState(
}
enum class QrCodeState {
- NONE, VALID, INVALID
+ NONE, VALID_WITH_SYNC, VALID_WITHOUT_SYNC, INVALID
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceSyncBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceSyncBottomSheet.kt
new file mode 100644
index 0000000000..d44f475f48
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceSyncBottomSheet.kt
@@ -0,0 +1,127 @@
+package org.thoughtcrime.securesms.linkdevice
+
+import android.content.DialogInterface
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.fragment.findNavController
+import org.signal.core.ui.BottomSheets
+import org.signal.core.ui.Previews
+import org.signal.core.ui.SignalPreview
+import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
+
+/**
+ * Bottom sheet dialog allowing users to choose whether to transfer their message history
+ */
+class LinkDeviceSyncBottomSheet : ComposeBottomSheetDialogFragment() {
+
+ private val viewModel: LinkDeviceViewModel by activityViewModels()
+
+ @Composable
+ override fun SheetContent() {
+ SyncSheet(
+ onLink = { shouldSync ->
+ viewModel.addDevice(shouldSync)
+ findNavController().popBackStack(R.id.linkDeviceFragment, false)
+ }
+ )
+ }
+
+ override fun onDismiss(dialog: DialogInterface) {
+ super.onDismiss(dialog)
+ viewModel.onQrCodeDismissed()
+ }
+}
+
+@Composable
+fun SyncSheet(
+ onLink: (Boolean) -> Unit
+) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.padding(horizontal = 24.dp)
+ ) {
+ BottomSheets.Handle()
+
+ Spacer(modifier = Modifier.size(12.dp))
+ SheetOption(
+ painterResource(R.drawable.symbol_chat_check),
+ stringResource(R.string.LinkDeviceSyncBottomSheet_transfer),
+ stringResource(R.string.LinkDeviceSyncBottomSheet_transfer_your_text)
+ ) { onLink(true) }
+
+ SheetOption(
+ painterResource(R.drawable.symbol_chat_x),
+ stringResource(R.string.LinkDeviceSyncBottomSheet_dont_transfer),
+ stringResource(R.string.LinkDeviceSyncBottomSheet_no_old_messages)
+ ) { onLink(false) }
+
+ Spacer(modifier = Modifier.size(60.dp))
+ }
+}
+
+@Composable
+private fun SheetOption(
+ icon: Painter,
+ title: String,
+ description: String,
+ onLink: () -> Unit
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 16.dp)
+ .defaultMinSize(minHeight = 96.dp)
+ .background(color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(18.dp))
+ .clickable { onLink() },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ icon,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.padding(start = 24.dp, end = 16.dp).size(44.dp)
+ )
+ Column(
+ modifier = Modifier.padding(end = 24.dp)
+ ) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.onSurface
+ )
+ Text(
+ text = description,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+ }
+}
+
+@SignalPreview
+@Composable
+fun SyncSheetSheetSheetPreview() {
+ Previews.BottomSheetPreview {
+ SyncSheet(onLink = {})
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceViewModel.kt
index 0257728ae8..6937d83afd 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/linkdevice/LinkDeviceViewModel.kt
@@ -116,7 +116,7 @@ class LinkDeviceViewModel : ViewModel() {
if (LinkDeviceRepository.isValidQr(uri)) {
_state.update {
it.copy(
- qrCodeState = QrCodeState.VALID,
+ qrCodeState = if (uri.supportsLinkAndSync() && RemoteConfig.linkAndSync) QrCodeState.VALID_WITH_SYNC else QrCodeState.VALID_WITHOUT_SYNC,
linkUri = uri,
showFrontCamera = null
)
@@ -140,7 +140,7 @@ class LinkDeviceViewModel : ViewModel() {
}
}
- fun addDevice() = viewModelScope.launch(Dispatchers.IO) {
+ fun addDevice(shouldSync: Boolean) = viewModelScope.launch(Dispatchers.IO) {
val linkUri: Uri = _state.value.linkUri!!
_state.update {
@@ -151,11 +151,11 @@ class LinkDeviceViewModel : ViewModel() {
)
}
- if (linkUri.supportsLinkAndSync() && RemoteConfig.linkAndSync) {
- Log.i(TAG, "Link+Sync supported.")
+ if (shouldSync) {
+ Log.i(TAG, "Adding device with sync.")
addDeviceWithSync(linkUri)
} else {
- Log.i(TAG, "Link+Sync not supported. (uri: ${linkUri.supportsLinkAndSync()}, remoteConfig: ${RemoteConfig.linkAndSync})")
+ Log.i(TAG, "Adding device without sync. (uri: ${linkUri.supportsLinkAndSync()}, remoteConfig: ${RemoteConfig.linkAndSync})")
addDeviceWithoutSync(linkUri)
}
}
@@ -210,6 +210,7 @@ class LinkDeviceViewModel : ViewModel() {
}
if (result !is LinkDeviceResult.Success) {
+ Log.w(TAG, "Unable to link device $result")
return
}
@@ -244,6 +245,7 @@ class LinkDeviceViewModel : ViewModel() {
dialogState = DialogState.None
)
}
+ loadDevices()
}
is LinkDeviceRepository.LinkUploadArchiveResult.BackupCreationFailure,
is LinkDeviceRepository.LinkUploadArchiveResult.BadRequest,
@@ -269,6 +271,7 @@ class LinkDeviceViewModel : ViewModel() {
}
if (result !is LinkDeviceResult.Success) {
+ Log.w(TAG, "Unable to link device $result")
return
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/about/AboutSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/about/AboutSheet.kt
index 3e41e53287..e804de036f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/about/AboutSheet.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/about/AboutSheet.kt
@@ -238,7 +238,7 @@ private fun Content(
)
} else {
AboutRow(
- startIcon = painterResource(id = R.drawable.chat_x),
+ startIcon = painterResource(id = R.drawable.symbol_chat_x),
text = stringResource(id = R.string.AboutSheet__no_direct_message, model.shortName),
modifier = Modifier.align(alignment = Alignment.Start),
onClick = onClickSignalConnections
diff --git a/app/src/main/res/drawable/chat_x.xml b/app/src/main/res/drawable/symbol_chat_x.xml
similarity index 100%
rename from app/src/main/res/drawable/chat_x.xml
rename to app/src/main/res/drawable/symbol_chat_x.xml
diff --git a/app/src/main/res/navigation/app_settings_with_change_number.xml b/app/src/main/res/navigation/app_settings_with_change_number.xml
index 06e667385f..b105eff295 100644
--- a/app/src/main/res/navigation/app_settings_with_change_number.xml
+++ b/app/src/main/res/navigation/app_settings_with_change_number.xml
@@ -271,10 +271,16 @@
+
+
Retry
+
+
+ Transfer message history
+
+ Transfer your text messages and recent media to your desktop
+
+ Don\'t transfer
+
+ No old messages or media will be transferred to your desktop
+
Unlink \"%s\"?
By unlinking this device, it will no longer be able to send or receive messages.