mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Add contact support option within link+sync.
This commit is contained in:
@@ -68,13 +68,14 @@ class EditDeviceNameFragment : ComposeFragment() {
|
||||
LinkDeviceSettingsState.OneTimeEvent.SnackbarNameChangeFailure -> {
|
||||
Snackbar.make(requireView(), context.getString(R.string.EditDeviceNameFragment__unable_to_change), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
LinkDeviceSettingsState.OneTimeEvent.HideFinishedSheet -> Unit
|
||||
LinkDeviceSettingsState.OneTimeEvent.LaunchQrCodeScanner -> Unit
|
||||
LinkDeviceSettingsState.OneTimeEvent.None -> Unit
|
||||
LinkDeviceSettingsState.OneTimeEvent.ShowFinishedSheet -> Unit
|
||||
is LinkDeviceSettingsState.OneTimeEvent.ToastLinked -> Unit
|
||||
LinkDeviceSettingsState.OneTimeEvent.ToastNetworkFailed -> Unit
|
||||
is LinkDeviceSettingsState.OneTimeEvent.ToastUnlinked -> Unit
|
||||
LinkDeviceSettingsState.OneTimeEvent.HideFinishedSheet,
|
||||
LinkDeviceSettingsState.OneTimeEvent.LaunchQrCodeScanner,
|
||||
LinkDeviceSettingsState.OneTimeEvent.None,
|
||||
LinkDeviceSettingsState.OneTimeEvent.ShowFinishedSheet,
|
||||
is LinkDeviceSettingsState.OneTimeEvent.ToastLinked,
|
||||
LinkDeviceSettingsState.OneTimeEvent.ToastNetworkFailed,
|
||||
is LinkDeviceSettingsState.OneTimeEvent.ToastUnlinked,
|
||||
LinkDeviceSettingsState.OneTimeEvent.LaunchEmail,
|
||||
LinkDeviceSettingsState.OneTimeEvent.SnackbarLinkCancelled -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceSettingsState.DialogState
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.SupportEmailUtil
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import java.util.Locale
|
||||
|
||||
@@ -138,7 +139,7 @@ class LinkDeviceFragment : ComposeFragment() {
|
||||
Log.i(TAG, "Acquiring wake lock for linked device")
|
||||
linkDeviceWakeLock.acquire()
|
||||
}
|
||||
DialogState.Unlinking, is DialogState.DeviceUnlinked -> Unit
|
||||
DialogState.Unlinking, is DialogState.DeviceUnlinked, DialogState.ContactSupport, DialogState.LoadingDebugLog -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +173,11 @@ class LinkDeviceFragment : ComposeFragment() {
|
||||
}
|
||||
LinkDeviceSettingsState.OneTimeEvent.SnackbarNameChangeFailure -> Unit
|
||||
LinkDeviceSettingsState.OneTimeEvent.SnackbarNameChangeSuccess -> Unit
|
||||
LinkDeviceSettingsState.OneTimeEvent.LaunchEmail -> {
|
||||
val subject = getString(R.string.LinkDeviceFragment__link_sync_failure_support_email)
|
||||
val body = getEmailBody(state.debugLogUrl)
|
||||
CommunicationActions.openEmail(requireContext(), SupportEmailUtil.getSupportEmailAddress(requireContext()), subject, body)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.oneTimeEvent != LinkDeviceSettingsState.OneTimeEvent.None) {
|
||||
@@ -210,16 +216,29 @@ class LinkDeviceFragment : ComposeFragment() {
|
||||
viewModel.onSyncErrorIgnored()
|
||||
CommunicationActions.openBrowserLink(requireContext(), requireContext().getString(R.string.LinkDeviceFragment__learn_more_url))
|
||||
},
|
||||
onSyncFailureContactSupport = { viewModel.onSyncErrorContactSupport() },
|
||||
onSyncCancelled = { viewModel.onSyncCancelled() },
|
||||
onEditDevice = { device ->
|
||||
viewModel.setDeviceToEdit(device)
|
||||
navController.safeNavigate(R.id.action_linkDeviceFragment_to_editDeviceNameFragment)
|
||||
},
|
||||
onDialogDismissed = { viewModel.onDialogDismissed() }
|
||||
onDialogDismissed = { viewModel.onDialogDismissed() },
|
||||
onContactWithLogs = { viewModel.onContactSupport(includeLogs = true) },
|
||||
onContactWithoutLogs = { viewModel.onContactSupport(includeLogs = false) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEmailBody(debugLog: String?): String {
|
||||
val filter = R.string.LinkDeviceFragment__link_sync_failure_support_email_filter
|
||||
val prefix = StringBuilder()
|
||||
if (debugLog != null) {
|
||||
prefix.append("\n")
|
||||
prefix.append(getString(R.string.HelpFragment__debug_log)).append(" ").append(debugLog).append("\n\n")
|
||||
}
|
||||
return SupportEmailUtil.generateSupportEmailBody(requireContext(), filter, prefix.toString(), null)
|
||||
}
|
||||
|
||||
private fun NavController.navigateToQrScannerIfAuthed(seenEducation: Boolean) {
|
||||
if (seenEducation && biometricAuth.canAuthenticate(requireContext())) {
|
||||
if (!biometricAuth.authenticate(requireContext(), true) { biometricDeviceLockLauncher.launch(getString(R.string.LinkDeviceFragment__unlock_to_link)) }) {
|
||||
@@ -266,9 +285,12 @@ fun DeviceListScreen(
|
||||
onSyncFailureRetryRequested: () -> Unit = {},
|
||||
onSyncFailureIgnored: () -> Unit = {},
|
||||
onSyncFailureLearnMore: () -> Unit = {},
|
||||
onSyncFailureContactSupport: () -> Unit = {},
|
||||
onSyncCancelled: () -> Unit = {},
|
||||
onEditDevice: (Device) -> Unit = {},
|
||||
onDialogDismissed: () -> Unit = {}
|
||||
onDialogDismissed: () -> Unit = {},
|
||||
onContactWithLogs: () -> Unit = {},
|
||||
onContactWithoutLogs: () -> Unit = {}
|
||||
) {
|
||||
// If a bottom sheet is showing, we don't want the spinner underneath
|
||||
if (!state.bottomSheetVisible) {
|
||||
@@ -302,14 +324,15 @@ fun DeviceListScreen(
|
||||
onDeny = onSyncFailureIgnored
|
||||
)
|
||||
} else {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
Dialogs.AdvancedAlertDialog(
|
||||
title = stringResource(R.string.LinkDeviceFragment__sync_failure_title),
|
||||
body = stringResource(R.string.LinkDeviceFragment__sync_failure_body_unretryable),
|
||||
confirm = stringResource(R.string.LinkDeviceFragment__continue),
|
||||
onConfirm = onSyncFailureIgnored,
|
||||
dismiss = stringResource(R.string.LinkDeviceFragment__learn_more),
|
||||
onDismissRequest = onSyncFailureIgnored,
|
||||
onDeny = onSyncFailureLearnMore
|
||||
positive = stringResource(R.string.LinkDeviceFragment__contact_support),
|
||||
onPositive = onSyncFailureContactSupport,
|
||||
neutral = stringResource(R.string.LinkDeviceFragment__learn_more),
|
||||
onNeutral = onSyncFailureLearnMore,
|
||||
negative = stringResource(R.string.LinkDeviceFragment__continue),
|
||||
onNegative = onSyncFailureIgnored
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -333,6 +356,19 @@ fun DeviceListScreen(
|
||||
onDismiss = onDialogDismissed
|
||||
)
|
||||
}
|
||||
DialogState.LoadingDebugLog -> { Dialogs.IndeterminateProgressDialog() }
|
||||
DialogState.ContactSupport -> {
|
||||
Dialogs.AdvancedAlertDialog(
|
||||
title = stringResource(R.string.LinkDeviceFragment__submit_debug_log),
|
||||
body = stringResource(R.string.LinkDeviceFragment__your_debug_logs),
|
||||
positive = stringResource(R.string.LinkDeviceFragment__submit_with_debug),
|
||||
onPositive = onContactWithLogs,
|
||||
neutral = stringResource(R.string.LinkDeviceFragment__submit_without_debug),
|
||||
onNeutral = onContactWithoutLogs,
|
||||
negative = stringResource(R.string.LinkDeviceFragment__cancel),
|
||||
onNegative = onDialogDismissed
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,7 +662,7 @@ private fun DeviceListScreenSyncingMessagesPreview() {
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun DeviceListScreenSyncingFailedPreview() {
|
||||
private fun DeviceListScreenSyncingFailedRetryPreview() {
|
||||
Previews.Preview {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
@@ -638,6 +674,34 @@ private fun DeviceListScreenSyncingFailedPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun DeviceListScreenSyncingFailedPreview() {
|
||||
Previews.Preview {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.SyncingFailed(1, 1, false),
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun DeviceListScreenContactSupportPreview() {
|
||||
Previews.Preview {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.ContactSupport,
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun DeviceListScreenDeviceUnlinkedPreview() {
|
||||
|
||||
@@ -23,7 +23,8 @@ data class LinkDeviceSettingsState(
|
||||
val needsBioAuthEducationSheet: Boolean = !seenBioAuthEducationSheet && SignalStore.uiHints.lastSeenLinkDeviceAuthSheetTime < System.currentTimeMillis() - 30.days.inWholeMilliseconds,
|
||||
val bottomSheetVisible: Boolean = false,
|
||||
val deviceToEdit: Device? = null,
|
||||
val shouldCancelArchiveUpload: Boolean = false
|
||||
val shouldCancelArchiveUpload: Boolean = false,
|
||||
val debugLogUrl: String? = null
|
||||
) {
|
||||
sealed interface DialogState {
|
||||
data object None : DialogState
|
||||
@@ -33,6 +34,8 @@ data class LinkDeviceSettingsState(
|
||||
data object SyncingTimedOut : DialogState
|
||||
data class SyncingFailed(val deviceId: Int, val deviceCreatedAt: Long, val canRetry: Boolean) : DialogState
|
||||
data class DeviceUnlinked(val deviceCreatedAt: Long) : DialogState
|
||||
data object LoadingDebugLog : DialogState
|
||||
data object ContactSupport : DialogState
|
||||
}
|
||||
|
||||
sealed interface OneTimeEvent {
|
||||
@@ -46,6 +49,7 @@ data class LinkDeviceSettingsState(
|
||||
data object ShowFinishedSheet : OneTimeEvent
|
||||
data object HideFinishedSheet : OneTimeEvent
|
||||
data object LaunchQrCodeScanner : OneTimeEvent
|
||||
data object LaunchEmail : OneTimeEvent
|
||||
}
|
||||
|
||||
enum class QrCodeState {
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.getPlaintextDe
|
||||
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.logsubmit.SubmitDebugLogRepository
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
@@ -27,6 +28,7 @@ import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.backup.MessageBackupKey
|
||||
import org.whispersystems.signalservice.api.link.TransferArchiveError
|
||||
import org.whispersystems.signalservice.api.link.WaitForLinkedDeviceResponse
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
@@ -40,6 +42,7 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow(LinkDeviceSettingsState())
|
||||
val state = _state.asStateFlow()
|
||||
private val submitDebugLogRepository: SubmitDebugLogRepository = SubmitDebugLogRepository()
|
||||
|
||||
private var pollJob: Job? = null
|
||||
|
||||
@@ -433,6 +436,14 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun onSyncErrorContactSupport() {
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialogState = DialogState.ContactSupport
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onSyncErrorRetryRequested() = viewModelScope.launch(Dispatchers.IO) {
|
||||
val dialogState = _state.value.dialogState
|
||||
if (dialogState is DialogState.SyncingFailed) {
|
||||
@@ -500,4 +511,33 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onContactSupport(includeLogs: Boolean) {
|
||||
viewModelScope.launch {
|
||||
if (includeLogs) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
dialogState = DialogState.LoadingDebugLog
|
||||
)
|
||||
}
|
||||
submitDebugLogRepository.buildAndSubmitLog { result ->
|
||||
val url = result.getOrNull()
|
||||
_state.update {
|
||||
it.copy(
|
||||
debugLogUrl = url,
|
||||
oneTimeEvent = OneTimeEvent.LaunchEmail,
|
||||
dialogState = DialogState.None
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_state.update {
|
||||
it.copy(
|
||||
oneTimeEvent = OneTimeEvent.LaunchEmail,
|
||||
dialogState = DialogState.None
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user