Provide retry UX for tier restore network failures.

This commit is contained in:
Cody Henthorne
2025-03-31 11:30:49 -04:00
committed by Greyson Parrelli
parent 9b527f7c6c
commit eb44dd4318
11 changed files with 363 additions and 36 deletions

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.contactsupport
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import org.signal.core.ui.compose.Dialogs
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.SignalPreview
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.SupportEmailUtil
interface ContactSupportCallbacks {
fun submitWithDebuglog()
fun submitWithoutDebuglog()
fun cancel()
object Empty : ContactSupportCallbacks {
override fun submitWithDebuglog() = Unit
override fun submitWithoutDebuglog() = Unit
override fun cancel() = Unit
}
}
/**
* Three-option contact support dialog.
*/
@Composable
fun ContactSupportDialog(
showInProgress: Boolean,
callbacks: ContactSupportCallbacks
) {
if (showInProgress) {
Dialogs.IndeterminateProgressDialog()
} else {
Dialogs.AdvancedAlertDialog(
title = stringResource(R.string.ContactSupportDialog_submit_debug_log),
body = stringResource(R.string.ContactSupportDialog_your_debug_logs),
positive = stringResource(R.string.ContactSupportDialog_submit_with_debug),
onPositive = { callbacks.submitWithDebuglog() },
neutral = stringResource(R.string.ContactSupportDialog_submit_without_debug),
onNeutral = { callbacks.submitWithoutDebuglog() },
negative = stringResource(android.R.string.cancel),
onNegative = { callbacks.cancel() }
)
}
}
/**
* Used in conjunction with [ContactSupportDialog] and [ContactSupportViewModel] to trigger
* sending an email when ready.
*/
@Composable
fun SendSupportEmailEffect(
contactSupportState: ContactSupportViewModel.ContactSupportState,
@StringRes subjectRes: Int,
@StringRes filterRes: Int,
hide: () -> Unit
) {
val context = LocalContext.current
LaunchedEffect(contactSupportState.sendEmail) {
if (contactSupportState.sendEmail) {
val subject = context.getString(subjectRes)
val prefix = if (contactSupportState.debugLogUrl != null) {
"\n${context.getString(R.string.HelpFragment__debug_log)} ${contactSupportState.debugLogUrl}\n\n"
} else {
""
}
val body = SupportEmailUtil.generateSupportEmailBody(context, filterRes, prefix, null)
CommunicationActions.openEmail(context, SupportEmailUtil.getSupportEmailAddress(context), subject, body)
hide()
}
}
}
@SignalPreview
@Composable
private fun ContactSupportDialogPreview() {
Previews.Preview {
ContactSupportDialog(
false,
ContactSupportCallbacks.Empty
)
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.contactsupport
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.signal.core.util.orNull
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogRepository
/**
* Intended to be used to drive [ContactSupportDialog].
*/
class ContactSupportViewModel : ViewModel(), ContactSupportCallbacks {
private val submitDebugLogRepository: SubmitDebugLogRepository = SubmitDebugLogRepository()
private val store: MutableStateFlow<ContactSupportState> = MutableStateFlow(ContactSupportState())
val state: StateFlow<ContactSupportState> = store.asStateFlow()
fun showContactSupport() {
store.update { it.copy(show = true) }
}
fun hideContactSupport() {
store.update { ContactSupportState() }
}
fun contactSupport(includeLogs: Boolean) {
viewModelScope.launch {
if (includeLogs) {
store.update { it.copy(showAsProgress = true) }
submitDebugLogRepository.buildAndSubmitLog { result ->
store.update { ContactSupportState(sendEmail = true, debugLogUrl = result.orNull()) }
}
} else {
store.update { ContactSupportState(sendEmail = true) }
}
}
}
override fun submitWithDebuglog() {
contactSupport(true)
}
override fun submitWithoutDebuglog() {
contactSupport(false)
}
override fun cancel() {
hideContactSupport()
}
data class ContactSupportState(
val show: Boolean = false,
val showAsProgress: Boolean = false,
val sendEmail: Boolean = false,
val debugLogUrl: String? = null
)
}