Update devices screen after linking a new device.

This commit is contained in:
Michelle Tang 2024-06-07 09:34:44 -07:00 committed by Alex Hart
parent 7bd5ad8c0b
commit 7086709082
7 changed files with 103 additions and 39 deletions

View file

@ -26,7 +26,8 @@ import java.util.Optional;
public class MultiDeviceConfigurationUpdateJob extends BaseJob {
public static final String KEY = "MultiDeviceConfigurationUpdateJob";
public static final String KEY = "MultiDeviceConfigurationUpdateJob";
public static final String QUEUE = "__MULTI_DEVICE_CONFIGURATION_UPDATE_JOB__";
private static final String TAG = Log.tag(MultiDeviceConfigurationUpdateJob.class);
@ -46,7 +47,7 @@ public class MultiDeviceConfigurationUpdateJob extends BaseJob {
boolean linkPreviewsEnabled)
{
this(new Job.Parameters.Builder()
.setQueue("__MULTI_DEVICE_CONFIGURATION_UPDATE_JOB__")
.setQueue(QUEUE)
.addConstraint(NetworkConstraint.KEY)
.setMaxAttempts(10)
.build(),

View file

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.linkdevice
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@ -23,6 +25,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -36,6 +40,7 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.fragment.app.activityViewModels
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import org.signal.core.ui.Buttons
import org.signal.core.ui.Dialogs
@ -45,7 +50,6 @@ import org.signal.core.ui.Scaffolds
import org.signal.core.ui.SignalPreview
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import java.util.Locale
@ -57,69 +61,54 @@ class LinkDeviceFragment : ComposeFragment() {
private val viewModel: LinkDeviceViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.initialize(requireContext())
}
@Composable
override fun FragmentContent() {
val state by viewModel.state.collectAsState()
val navController: NavController by remember { mutableStateOf(findNavController()) }
LaunchedEffect(state.toastDialog) {
if (state.toastDialog.isNotEmpty()) {
Toast.makeText(requireContext(), state.toastDialog, Toast.LENGTH_LONG).show()
viewModel.clearToast()
}
}
LaunchedEffect(state.showFinishedSheet) {
if (state.showFinishedSheet) {
onShowFinishedSheet()
findNavController().safeNavigate(R.id.action_linkDeviceFragment_to_linkDeviceFinishedSheet)
viewModel.markFinishedSheetSeen()
}
}
Scaffolds.Settings(
title = stringResource(id = R.string.preferences__linked_devices),
onNavigationClick = { findNavController().popBackStack() },
onNavigationClick = { navController.popBackStack() },
navigationIconPainter = painterResource(id = R.drawable.ic_arrow_left_24),
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close)
) { contentPadding: PaddingValues ->
DeviceDescriptionScreen(
state = state,
navController = navController,
modifier = Modifier.padding(contentPadding),
onLearnMore = this::openLearnMore,
onLinkDevice = this::openLinkNewDevice,
setDeviceToRemove = this::setDeviceToRemove,
onRemoveDevice = this::onRemoveDevice
onLearnMore = { navController.safeNavigate(R.id.action_linkDeviceFragment_to_linkDeviceLearnMoreBottomSheet) },
onLinkDevice = { navController.safeNavigate(R.id.action_linkDeviceFragment_to_addLinkDeviceFragment) },
setDeviceToRemove = { device -> viewModel.setDeviceToRemove(device) },
onRemoveDevice = { device -> viewModel.removeDevice(requireContext(), device) }
)
}
}
override fun onResume() {
super.onResume()
viewModel.loadDevices(requireContext())
}
private fun openLearnMore() {
LinkDeviceLearnMoreBottomSheetFragment().show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
}
private fun openLinkNewDevice() {
findNavController().safeNavigate(R.id.action_linkDeviceFragment_to_addLinkDeviceFragment)
}
private fun setDeviceToRemove(device: Device?) {
viewModel.setDeviceToRemove(device)
}
private fun onRemoveDevice(device: Device) {
viewModel.removeDevice(requireContext(), device)
}
private fun onShowFinishedSheet() {
LinkDeviceFinishedSheet().show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
viewModel.markFinishedSheetSeen()
}
}
@Composable
fun DeviceDescriptionScreen(
state: LinkDeviceSettingsState,
navController: NavController? = null,
modifier: Modifier = Modifier,
onLearnMore: () -> Unit = {},
onLinkDevice: () -> Unit = {},
@ -127,6 +116,9 @@ fun DeviceDescriptionScreen(
onRemoveDevice: (Device) -> Unit = {}
) {
if (state.progressDialogMessage != -1) {
if (navController?.currentDestination?.id == R.id.linkDeviceFinishedSheet) {
navController?.popBackStack()
}
Dialogs.IndeterminateProgressDialog(stringResource(id = state.progressDialogMessage))
}
if (state.deviceToRemove != null) {

View file

@ -17,5 +17,6 @@ data class LinkDeviceSettingsState(
val linkDeviceResult: LinkDeviceRepository.LinkDeviceResult = LinkDeviceRepository.LinkDeviceResult.UNKNOWN,
val showFinishedSheet: Boolean = false,
val seenIntroSheet: Boolean = false,
val pendingBiometrics: Boolean = false
val pendingBiometrics: Boolean = false,
val pendingNewDevice: Boolean = false
)

View file

@ -14,7 +14,11 @@ import kotlinx.coroutines.launch
import org.signal.core.util.toOptional
import org.signal.qr.QrProcessor
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.JobTracker
import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob
/**
* Maintains the state of the [LinkDeviceFragment]
@ -24,6 +28,26 @@ class LinkDeviceViewModel : ViewModel() {
private val _state = MutableStateFlow(LinkDeviceSettingsState())
val state = _state.asStateFlow()
private lateinit var listener: JobTracker.JobListener
fun initialize(context: Context) {
listener = JobTracker.JobListener { _, jobState ->
if (jobState.isComplete) {
loadDevices(context = context, isPotentialNewDevice = true)
}
}
AppDependencies.jobManager.addListener(
{ job: Job -> job.parameters.queue?.startsWith(MultiDeviceConfigurationUpdateJob.QUEUE) ?: false },
listener
)
loadDevices(context)
}
override fun onCleared() {
super.onCleared()
AppDependencies.jobManager.removeListener(listener)
}
fun setDeviceToRemove(device: Device?) {
_state.update { it.copy(deviceToRemove = device) }
}
@ -47,7 +71,14 @@ class LinkDeviceViewModel : ViewModel() {
}
}
fun loadDevices(context: Context) {
private fun loadDevices(context: Context, isPotentialNewDevice: Boolean = false) {
if (isPotentialNewDevice && !_state.value.pendingNewDevice) {
return
}
_state.value = _state.value.copy(
progressDialogMessage = if (isPotentialNewDevice) R.string.LinkDeviceFragment__linking_device else -1,
pendingNewDevice = if (isPotentialNewDevice) false else _state.value.pendingNewDevice
)
viewModelScope.launch(Dispatchers.IO) {
val devices = LinkDeviceRepository.loadDevices()
if (devices == null) {
@ -58,7 +89,7 @@ class LinkDeviceViewModel : ViewModel() {
} else {
_state.update {
it.copy(
toastDialog = "",
toastDialog = if (isPotentialNewDevice) context.getString(R.string.LinkDeviceFragment__device_approved) else "",
devices = devices,
progressDialogMessage = -1
)
@ -160,7 +191,8 @@ class LinkDeviceViewModel : ViewModel() {
it.copy(
showFinishedSheet = showSheet,
linkDeviceResult = LinkDeviceRepository.LinkDeviceResult.UNKNOWN,
toastDialog = ""
toastDialog = "",
pendingNewDevice = true
)
}
}
@ -192,4 +224,12 @@ class LinkDeviceViewModel : ViewModel() {
}
}
}
fun clearToast() {
_state.update {
it.copy(
toastDialog = ""
)
}
}
}

View file

@ -243,7 +243,20 @@
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
<action
android:id="@+id/action_linkDeviceFragment_to_linkDeviceFinishedSheet"
app:destination="@id/linkDeviceFinishedSheet" />
<action
android:id="@+id/action_linkDeviceFragment_to_linkDeviceLearnMoreBottomSheet"
app:destination="@id/linkDeviceLearnMoreBottomSheet" />
</fragment>
<dialog
android:id="@+id/linkDeviceFinishedSheet"
android:name="org.thoughtcrime.securesms.linkdevice.LinkDeviceFinishedSheet" />
<dialog
android:id="@+id/linkDeviceLearnMoreBottomSheet"
android:name="org.thoughtcrime.securesms.linkdevice.LinkDeviceLearnMoreBottomSheetFragment" />
<fragment
android:id="@+id/addLinkDeviceFragment"
android:name="org.thoughtcrime.securesms.linkdevice.AddLinkDeviceFragment"

View file

@ -243,7 +243,20 @@
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
<action
android:id="@+id/action_linkDeviceFragment_to_linkDeviceFinishedSheet"
app:destination="@id/linkDeviceFinishedSheet" />
<action
android:id="@+id/action_linkDeviceFragment_to_linkDeviceLearnMoreBottomSheet"
app:destination="@id/linkDeviceLearnMoreBottomSheet" />
</fragment>
<dialog
android:id="@+id/linkDeviceFinishedSheet"
android:name="org.thoughtcrime.securesms.linkdevice.LinkDeviceFinishedSheet" />
<dialog
android:id="@+id/linkDeviceLearnMoreBottomSheet"
android:name="org.thoughtcrime.securesms.linkdevice.LinkDeviceLearnMoreBottomSheetFragment" />
<fragment
android:id="@+id/addLinkDeviceFragment"
android:name="org.thoughtcrime.securesms.linkdevice.AddLinkDeviceFragment"

View file

@ -881,6 +881,10 @@
<string name="LinkDeviceFragment__unlink">Unlink</string>
<!-- Toast message indicating a device has been unlinked where %s is the name of the device -->
<string name="LinkDeviceFragment__s_unlinked">%s unlinked</string>
<!-- Progress dialog message indicating that a device is currently being linked with an account -->
<string name="LinkDeviceFragment__linking_device">Linking device…</string>
<!-- Toast message shown after a device has been linked -->
<string name="LinkDeviceFragment__device_approved">Device approved</string>
<!-- AddLinkDeviceFragment -->
<!-- Description text shown on the QR code scanner when linking a device -->