mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-07-20 20:44:44 +01:00
Add contact support capability to regV5.
This commit is contained in:
committed by
Michelle Tang
parent
99b89743a7
commit
db899e92f5
@@ -119,6 +119,7 @@ import org.thoughtcrime.securesms.service.webrtc.AndroidTelecomUtil;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.BatterySnapshotTracker;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.DeviceProperties;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.Environment;
|
||||
@@ -127,6 +128,7 @@ import org.thoughtcrime.securesms.util.RemoteConfig;
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
|
||||
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
||||
import org.thoughtcrime.securesms.util.SqlCipherLogTarget;
|
||||
import org.thoughtcrime.securesms.util.SupportEmailUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
@@ -438,6 +440,11 @@ public class ApplicationContext extends Application implements AppForegroundObse
|
||||
context -> {
|
||||
context.startActivity(AppSettingsActivity.proxy(context));
|
||||
return Unit.INSTANCE;
|
||||
},
|
||||
(context, subject) -> {
|
||||
String body = SupportEmailUtil.generateSupportEmailBody(context, subject, null, null);
|
||||
CommunicationActions.openEmail(context, SupportEmailUtil.getSupportEmailAddress(context), subject, body);
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -48,18 +47,28 @@ public final class SupportEmailUtil {
|
||||
@Nullable String suffix)
|
||||
{
|
||||
filterSuffix = Util.emptyIfNull(filterSuffix);
|
||||
prefix = Util.emptyIfNull(prefix);
|
||||
suffix = Util.emptyIfNull(suffix);
|
||||
|
||||
return String.format("%s\n%s\n%s", prefix, buildSystemInfo(context, filter, filterSuffix), suffix);
|
||||
return generateSupportEmailBody(context, ResourceUtil.getEnglishResources(context).getString(filter) + filterSuffix, prefix, suffix);
|
||||
}
|
||||
|
||||
private static @NonNull String buildSystemInfo(@NonNull Context context, @StringRes int filter, @NonNull String filterSuffix) {
|
||||
Resources englishResources = ResourceUtil.getEnglishResources(context);
|
||||
/**
|
||||
* Generates a support email body with system info near the top, using the given already-resolved filter text.
|
||||
*/
|
||||
public static @NonNull String generateSupportEmailBody(@NonNull Context context,
|
||||
@NonNull String filter,
|
||||
@Nullable String prefix,
|
||||
@Nullable String suffix)
|
||||
{
|
||||
prefix = Util.emptyIfNull(prefix);
|
||||
suffix = Util.emptyIfNull(suffix);
|
||||
|
||||
return String.format("%s\n%s\n%s", prefix, buildSystemInfo(context, filter), suffix);
|
||||
}
|
||||
|
||||
private static @NonNull String buildSystemInfo(@NonNull Context context, @NonNull String filter) {
|
||||
return "--- " + context.getString(R.string.HelpFragment__support_info) + " ---" +
|
||||
"\n" +
|
||||
context.getString(R.string.SupportEmailUtil_filter) + " " + englishResources.getString(filter) + filterSuffix +
|
||||
context.getString(R.string.SupportEmailUtil_filter) + " " + filter +
|
||||
"\n" +
|
||||
context.getString(R.string.SupportEmailUtil_device_info) + " " + getDeviceInfo() +
|
||||
"\n" +
|
||||
|
||||
+6
@@ -69,6 +69,12 @@ class RegistrationApplication : Application() {
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
},
|
||||
contactSupportCallback = { context, subject ->
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage("Contact support not supported in the demo. Subject: $subject")
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
},
|
||||
isLinkAndSyncAvailable = true
|
||||
)
|
||||
)
|
||||
|
||||
+4
-1
@@ -16,6 +16,8 @@ import org.signal.registration.util.SensitiveLog
|
||||
* the actual app would just pass null.
|
||||
* @param debugLogCallback Callback to launch the debug log viewer. The actual app provides the real implementation.
|
||||
* @param proxyConfigCallback Callback to launch the proxy configuration settings. The actual app provides the real implementation.
|
||||
* @param contactSupportCallback Callback to let the user contact support, using the provided email subject. The actual app provides the real
|
||||
* implementation.
|
||||
*/
|
||||
class RegistrationDependencies(
|
||||
val networkController: NetworkController,
|
||||
@@ -23,7 +25,8 @@ class RegistrationDependencies(
|
||||
val isLinkAndSyncAvailable: Boolean,
|
||||
val sensitiveLogger: Log.Logger?,
|
||||
val debugLogCallback: ((Context) -> Unit)?,
|
||||
val proxyConfigCallback: ((Context) -> Unit)?
|
||||
val proxyConfigCallback: ((Context) -> Unit)?,
|
||||
val contactSupportCallback: ((Context, subject: String) -> Unit)?
|
||||
) {
|
||||
companion object {
|
||||
lateinit var dependencies: RegistrationDependencies
|
||||
|
||||
+1
-2
@@ -70,8 +70,7 @@ class PinEntryForRegistrationLockViewModel(
|
||||
}
|
||||
is PinEntryScreenEvents.CreateNewPin,
|
||||
is PinEntryScreenEvents.ContactSupport -> Unit
|
||||
is PinEntryScreenEvents.ToggleKeyboard,
|
||||
is PinEntryScreenEvents.NeedHelp -> {
|
||||
is PinEntryScreenEvents.ToggleKeyboard -> {
|
||||
stateEmitter(PinEntryScreenEventHandler.applyEvent(state, event))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -77,8 +77,7 @@ class PinEntryForSmsBypassViewModel(
|
||||
}
|
||||
is PinEntryScreenEvents.CreateNewPin,
|
||||
is PinEntryScreenEvents.ContactSupport -> Unit
|
||||
is PinEntryScreenEvents.ToggleKeyboard,
|
||||
is PinEntryScreenEvents.NeedHelp -> {
|
||||
is PinEntryScreenEvents.ToggleKeyboard -> {
|
||||
stateEmitter(PinEntryScreenEventHandler.applyEvent(state, event))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -80,8 +80,7 @@ class PinEntryForSvrRestoreViewModel(
|
||||
Log.i(TAG, "[ContactSupport] User opted to contact support after no data was found.")
|
||||
stateEmitter(state.copy(showNoDataToRestoreDialog = false))
|
||||
}
|
||||
is PinEntryScreenEvents.ToggleKeyboard,
|
||||
is PinEntryScreenEvents.NeedHelp -> {
|
||||
is PinEntryScreenEvents.ToggleKeyboard -> {
|
||||
stateEmitter(PinEntryScreenEventHandler.applyEvent(state, event))
|
||||
}
|
||||
}
|
||||
|
||||
+15
-3
@@ -39,6 +39,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
@@ -52,6 +53,7 @@ import org.signal.core.ui.compose.Dialogs
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.RegistrationDependencies
|
||||
import org.signal.registration.screens.PinVisualTransformation
|
||||
import org.signal.registration.screens.RegistrationScaffold
|
||||
import org.signal.registration.screens.TwoPaneRegistrationScaffold
|
||||
@@ -67,10 +69,13 @@ fun PinEntryScreen(
|
||||
onEvent: (PinEntryScreenEvents) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var pin by rememberSaveable { mutableStateOf("") }
|
||||
var showSkipDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val canSubmitPin = pin.isNotEmpty()
|
||||
val supportEmailSubject = stringResource(R.string.PinEntryScreen__contact_support_email_subject)
|
||||
val onContactSupport: () -> Unit = { RegistrationDependencies.get().contactSupportCallback?.invoke(context, supportEmailSubject) }
|
||||
|
||||
when (val params = RegistrationScaffold.rememberLayoutParams()) {
|
||||
is RegistrationScaffold.Params.OnePane -> OnePaneLayout(
|
||||
@@ -81,6 +86,7 @@ fun PinEntryScreen(
|
||||
focusRequester = focusRequester,
|
||||
onPinChanged = { pin = it },
|
||||
onSkip = { showSkipDialog = true },
|
||||
onContactSupport = onContactSupport,
|
||||
onEvent = onEvent,
|
||||
modifier = modifier
|
||||
)
|
||||
@@ -93,6 +99,7 @@ fun PinEntryScreen(
|
||||
focusRequester = focusRequester,
|
||||
onPinChanged = { pin = it },
|
||||
onSkip = { showSkipDialog = true },
|
||||
onContactSupport = onContactSupport,
|
||||
onEvent = onEvent,
|
||||
modifier = modifier
|
||||
)
|
||||
@@ -119,7 +126,10 @@ fun PinEntryScreen(
|
||||
confirm = stringResource(R.string.PinEntryScreen__create_new_pin),
|
||||
dismiss = stringResource(R.string.PinEntryScreen__contact_support),
|
||||
onConfirm = { onEvent(PinEntryScreenEvents.CreateNewPin) },
|
||||
onDeny = { onEvent(PinEntryScreenEvents.ContactSupport) },
|
||||
onDeny = {
|
||||
onContactSupport()
|
||||
onEvent(PinEntryScreenEvents.ContactSupport)
|
||||
},
|
||||
onDismissRequest = { onEvent(PinEntryScreenEvents.ContactSupport) },
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = false,
|
||||
@@ -143,6 +153,7 @@ private fun OnePaneLayout(
|
||||
focusRequester: FocusRequester,
|
||||
onPinChanged: (String) -> Unit,
|
||||
onSkip: () -> Unit,
|
||||
onContactSupport: () -> Unit,
|
||||
onEvent: (PinEntryScreenEvents) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
@@ -174,7 +185,7 @@ private fun OnePaneLayout(
|
||||
focusRequester = focusRequester,
|
||||
onPinChanged = onPinChanged,
|
||||
onSubmit = { onEvent(PinEntryScreenEvents.PinEntered(pin)) },
|
||||
onNeedsHelp = { onEvent(PinEntryScreenEvents.NeedHelp) },
|
||||
onNeedsHelp = onContactSupport,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
@@ -214,6 +225,7 @@ private fun TwoPaneLayout(
|
||||
focusRequester: FocusRequester,
|
||||
onPinChanged: (String) -> Unit,
|
||||
onSkip: () -> Unit,
|
||||
onContactSupport: () -> Unit,
|
||||
onEvent: (PinEntryScreenEvents) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
@@ -255,7 +267,7 @@ private fun TwoPaneLayout(
|
||||
focusRequester = focusRequester,
|
||||
onPinChanged = onPinChanged,
|
||||
onSubmit = { onEvent(PinEntryScreenEvents.PinEntered(pin)) },
|
||||
onNeedsHelp = { onEvent(PinEntryScreenEvents.NeedHelp) },
|
||||
onNeedsHelp = onContactSupport,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
|
||||
-1
@@ -10,7 +10,6 @@ sealed class PinEntryScreenEvents {
|
||||
override fun toString(): String = "PinEntered(pin=${pin.length} chars)"
|
||||
}
|
||||
data object ToggleKeyboard : PinEntryScreenEvents()
|
||||
data object NeedHelp : PinEntryScreenEvents()
|
||||
data object Skip : PinEntryScreenEvents()
|
||||
data object CreateNewPin : PinEntryScreenEvents()
|
||||
data object ContactSupport : PinEntryScreenEvents()
|
||||
|
||||
+8
-1
@@ -46,6 +46,7 @@ import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalIcons
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.signal.registration.R
|
||||
import org.signal.registration.RegistrationDependencies
|
||||
import org.signal.registration.screens.OnePaneRegistrationScaffold
|
||||
import org.signal.registration.screens.RegistrationScaffold
|
||||
import org.signal.registration.screens.TwoPaneRegistrationScaffold
|
||||
@@ -296,6 +297,9 @@ private fun RestoreStateDialogs(
|
||||
state: RemoteBackupRestoreState,
|
||||
onEvent: (RemoteBackupRestoreScreenEvents) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val contactSupportEmailSubject = stringResource(R.string.RemoteRestoreScreen__contact_support_email_subject)
|
||||
|
||||
when (state.restoreState) {
|
||||
RemoteBackupRestoreState.RestoreState.None -> Unit
|
||||
RemoteBackupRestoreState.RestoreState.InProgress -> {
|
||||
@@ -330,7 +334,10 @@ private fun RestoreStateDialogs(
|
||||
body = stringResource(R.string.RemoteRestoreScreen__your_backup_is_not_recoverable),
|
||||
confirm = stringResource(R.string.RemoteRestoreScreen__contact_support),
|
||||
dismiss = stringResource(android.R.string.ok),
|
||||
onConfirm = { onEvent(RemoteBackupRestoreScreenEvents.DismissError) },
|
||||
onConfirm = {
|
||||
RegistrationDependencies.get().contactSupportCallback?.invoke(context, contactSupportEmailSubject)
|
||||
onEvent(RemoteBackupRestoreScreenEvents.DismissError)
|
||||
},
|
||||
onDismiss = { onEvent(RemoteBackupRestoreScreenEvents.DismissError) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -270,6 +270,8 @@
|
||||
<string name="RemoteRestoreScreen__your_backup_is_not_recoverable">An error occurred while restoring your backup. Your backup is not recoverable. Please contact support for help.</string>
|
||||
<!-- Button to contact support -->
|
||||
<string name="RemoteRestoreScreen__contact_support">Contact support</string>
|
||||
<!-- Subject line of the support email composed when the user contacts support about an unrecoverable backup. Also used to route the request, so it is not translated. -->
|
||||
<string name="RemoteRestoreScreen__contact_support_email_subject" translatable="false">Signal Android Backup restore permanent failure</string>
|
||||
<!-- Shown while downloading the backup from the server -->
|
||||
<string name="RemoteRestoreScreen__downloading_backup">Downloading backup…</string>
|
||||
<!-- Shown while restoring messages from the downloaded backup -->
|
||||
@@ -379,6 +381,8 @@
|
||||
<string name="PinEntryScreen__no_data_could_be_found">No data could be found to restore your account. Please create a new PIN.</string>
|
||||
<!-- Labels the button in the no-data-to-restore dialog that lets the user contact support. -->
|
||||
<string name="PinEntryScreen__contact_support">Contact support</string>
|
||||
<!-- Subject line of the support email composed when the user contacts support about PIN entry. Also used to route the request, so it is not translated. -->
|
||||
<string name="PinEntryScreen__contact_support_email_subject" translatable="false">Signal Registration - Need Help with PIN for Android</string>
|
||||
|
||||
<!-- Title for the screen asking the user to grant the notification permission -->
|
||||
<string name="AllowNotificationsScreen__allow_notifications">Allow Notifications</string>
|
||||
|
||||
Reference in New Issue
Block a user