Notify a user when they link a device.

This commit is contained in:
Michelle Tang
2025-01-10 17:27:32 -05:00
committed by Greyson Parrelli
parent 919648b94b
commit d4c8c16df3
22 changed files with 382 additions and 91 deletions

View File

@@ -137,7 +137,7 @@ class LinkDeviceFragment : ComposeFragment() {
Log.i(TAG, "Acquiring wake lock for linked device")
linkDeviceWakeLock.acquire()
}
DialogState.Unlinking -> Unit
DialogState.Unlinking, is DialogState.DeviceUnlinked -> Unit
}
}
@@ -206,7 +206,8 @@ class LinkDeviceFragment : ComposeFragment() {
onEditDevice = { device ->
viewModel.setDeviceToEdit(device)
navController.safeNavigate(R.id.action_linkDeviceFragment_to_editDeviceNameFragment)
}
},
onDialogDismissed = { viewModel.onDialogDismissed() }
)
}
}
@@ -257,7 +258,8 @@ fun DeviceListScreen(
onSyncFailureRetryRequested: () -> Unit = {},
onSyncFailureIgnored: () -> Unit = {},
onSyncCancelled: () -> Unit = {},
onEditDevice: (Device) -> Unit = {}
onEditDevice: (Device) -> Unit = {},
onDialogDismissed: () -> Unit = {}
) {
// If a bottom sheet is showing, we don't want the spinner underneath
if (!state.bottomSheetVisible) {
@@ -291,6 +293,15 @@ fun DeviceListScreen(
onDeny = onSyncFailureIgnored
)
}
is DialogState.DeviceUnlinked -> {
val createdAt = DateUtils.getOnlyTimeString(LocalContext.current, state.dialogState.deviceCreatedAt)
Dialogs.SimpleMessageDialog(
title = stringResource(id = R.string.LinkDeviceFragment__device_unlinked),
message = stringResource(id = R.string.LinkDeviceFragment__the_device_that_was, createdAt),
dismiss = stringResource(id = R.string.LinkDeviceFragment__ok),
onDismiss = onDialogDismissed
)
}
}
}
@@ -595,3 +606,17 @@ private fun DeviceListScreenSyncingFailedPreview() {
)
}
}
@SignalPreview
@Composable
private fun DeviceListScreenDeviceUnlinkedPreview() {
Previews.Preview {
DeviceListScreen(
state = LinkDeviceSettingsState(
dialogState = DialogState.DeviceUnlinked(1736454440342),
seenBioAuthEducationSheet = true,
seenQrEducationSheet = true
)
)
}
}

View File

@@ -31,6 +31,7 @@ data class LinkDeviceSettingsState(
data class SyncingMessages(val deviceId: Int, val deviceCreatedAt: Long) : DialogState
data object SyncingTimedOut : DialogState
data class SyncingFailed(val deviceId: Int, val deviceCreatedAt: Long) : DialogState
data class DeviceUnlinked(val deviceCreatedAt: Long) : DialogState
}
sealed interface OneTimeEvent {

View File

@@ -9,14 +9,18 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob
import org.thoughtcrime.securesms.jobs.NewLinkedDeviceNotificationJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.LinkDeviceResult
import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.getPlaintextDeviceName
import org.thoughtcrime.securesms.linkdevice.LinkDeviceSettingsState.DialogState
import org.thoughtcrime.securesms.linkdevice.LinkDeviceSettingsState.OneTimeEvent
import org.thoughtcrime.securesms.linkdevice.LinkDeviceSettingsState.QrCodeState
import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.backup.MessageBackupKey
import org.whispersystems.signalservice.api.link.TransferArchiveError
@@ -36,7 +40,32 @@ class LinkDeviceViewModel : ViewModel() {
val state = _state.asStateFlow()
fun initialize() {
loadDevices()
loadDevices(initialLoad = true)
}
/**
* Checks for the existence of a newly linked device and shows a dialog if it has since been unlinked
*/
private fun checkForNewDevice(devices: List<Device>) {
val newLinkedDeviceId = SignalStore.misc.newLinkedDeviceId
val newLinkedDeviceCreatedAt = SignalStore.misc.newLinkedDeviceCreatedTime
val hasNewLinkedDevice = newLinkedDeviceId > 0
if (hasNewLinkedDevice) {
ServiceUtil.getNotificationManager(AppDependencies.application).cancel(NotificationIds.NEW_LINKED_DEVICE)
SignalStore.misc.newLinkedDeviceId = 0
SignalStore.misc.newLinkedDeviceCreatedTime = 0
}
val isMissingNewLinkedDevice = devices.none { device -> device.id == newLinkedDeviceId && device.createdMillis == newLinkedDeviceCreatedAt }
val dialogState = if (hasNewLinkedDevice && isMissingNewLinkedDevice) {
DialogState.DeviceUnlinked(newLinkedDeviceCreatedAt)
} else {
DialogState.None
}
_state.update { it.copy(dialogState = dialogState) }
}
fun setDeviceToRemove(device: Device?) {
@@ -66,7 +95,7 @@ class LinkDeviceViewModel : ViewModel() {
}
}
private fun loadDevices() {
private fun loadDevices(initialLoad: Boolean = false) {
_state.value = _state.value.copy(
deviceListLoading = true,
showFrontCamera = null
@@ -80,6 +109,9 @@ class LinkDeviceViewModel : ViewModel() {
deviceListLoading = false
)
} else {
if (initialLoad) {
checkForNewDevice(devices)
}
_state.update {
it.copy(
oneTimeEvent = OneTimeEvent.None,
@@ -143,6 +175,10 @@ class LinkDeviceViewModel : ViewModel() {
}
}
fun onDialogDismissed() {
_state.update { it.copy(dialogState = DialogState.None) }
}
fun addDevice(shouldSync: Boolean) = viewModelScope.launch(Dispatchers.IO) {
val linkUri: Uri = _state.value.linkUri!!
@@ -243,7 +279,8 @@ class LinkDeviceViewModel : ViewModel() {
return
}
Log.d(TAG, "[addDeviceWithSync] Found a linked device!")
Log.d(TAG, "[addDeviceWithSync] Found a linked device! Creating notification job.")
NewLinkedDeviceNotificationJob.enqueue(waitResult.id, waitResult.created)
_state.update {
it.copy(
@@ -319,6 +356,8 @@ class LinkDeviceViewModel : ViewModel() {
if (waitResult == null) {
Log.i(TAG, "No linked device found!")
} else {
Log.i(TAG, "Found a linked device! Creating notification job.")
NewLinkedDeviceNotificationJob.enqueue(waitResult.id, waitResult.created)
_state.update {
it.copy(oneTimeEvent = OneTimeEvent.ToastLinked(waitResult.getPlaintextDeviceName()))
}