Add verification code requested alert handling.

This commit is contained in:
Cody Henthorne
2026-05-20 12:58:29 -04:00
committed by jeffrey-signal
parent 6722a28f98
commit 3b93edcdaf
13 changed files with 596 additions and 2 deletions
@@ -116,6 +116,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.GooglePay
import org.thoughtcrime.securesms.components.snackbars.LocalSnackbarStateConsumerRegistry
import org.thoughtcrime.securesms.components.snackbars.SnackbarHostKey
import org.thoughtcrime.securesms.components.snackbars.SnackbarState
import org.thoughtcrime.securesms.components.verificationrequested.VerificationCodeRequestedBottomSheet
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
import org.thoughtcrime.securesms.conversation.ConversationIntents
@@ -195,6 +196,7 @@ import org.thoughtcrime.securesms.window.AppScaffoldNavigator
import org.thoughtcrime.securesms.window.NavigationType
import org.thoughtcrime.securesms.window.rememberThreePaneScaffoldNavigatorDelegate
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
import kotlin.time.Duration.Companion.minutes
import org.signal.core.ui.R as CoreUiR
class MainActivity :
@@ -357,6 +359,25 @@ class MainActivity :
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
SignalStore
.account
.verificationCodeRequestedAtMsFlow
.filter { it > 0L }
.collect { requestedAt ->
val notificationThreshold = requestedAt + 10.minutes.inWholeMilliseconds
if (System.currentTimeMillis() < notificationThreshold) {
VerificationCodeRequestedBottomSheet.show(supportFragmentManager, requestedAt)
} else {
Log.i(TAG, "Verification code requested but is older than 10 minutes, not showing sheet")
}
SignalStore.account.verificationCodeRequestedAtMs = 0L
}
}
}
}
supportFragmentManager.setFragmentResultListener(
@@ -86,6 +86,7 @@ class AppSettingsActivity : DSLSettingsActivity(), GooglePayComponent {
is AppSettingsRoute.BackupsRoute.Backups -> AppSettingsFragmentDirections.actionDirectToBackupsSettingsFragment().setLaunchCheckoutFlow(appSettingsRoute.launchCheckoutFlow)
AppSettingsRoute.Invite -> AppSettingsFragmentDirections.actionDirectToInviteFragment()
AppSettingsRoute.DataAndStorageRoute.DataAndStorage -> AppSettingsFragmentDirections.actionDirectToStoragePreferenceFragment()
AppSettingsRoute.AccountRoute.Account -> AppSettingsFragmentDirections.actionDirectToAccountSettingsFragment()
else -> error("Unsupported start location: ${appSettingsRoute?.javaClass?.name}")
}
}
@@ -177,6 +178,9 @@ class AppSettingsActivity : DSLSettingsActivity(), GooglePayComponent {
@JvmStatic
fun changeNumber(context: Context): Intent = getIntentForStartLocation(context, AppSettingsRoute.ChangeNumberRoute.Start)
@JvmStatic
fun account(context: Context): Intent = getIntentForStartLocation(context, AppSettingsRoute.AccountRoute.Account)
@JvmStatic
fun subscriptions(context: Context): Intent = getIntentForStartLocation(context, AppSettingsRoute.DonationsRoute.Donations(directToCheckoutType = InAppPaymentType.RECURRING_DONATION))
@@ -0,0 +1,195 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.verificationrequested
import android.os.Bundle
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentManager
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.DateUtils
import java.util.Locale
/**
* Sheet shown when the server has pushed a notification telling us a verification code was
* requested for the user's account.
*/
class VerificationCodeRequestedBottomSheet : ComposeBottomSheetDialogFragment() {
override val peekHeightPercentage: Float = 1f
companion object {
private const val ARG_REQUESTED_AT = "requested_at"
@JvmStatic
fun show(fragmentManager: FragmentManager, requestedAtMs: Long) {
VerificationCodeRequestedBottomSheet().apply {
arguments = Bundle().apply { putLong(ARG_REQUESTED_AT, requestedAtMs) }
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
}
}
@Composable
override fun SheetContent() {
val context = LocalContext.current
val resources = LocalResources.current
val requestedAt = requireArguments().getLong(ARG_REQUESTED_AT)
val formattedTime = remember(requestedAt) {
val time = DateUtils.getOnlyTimeString(context, requestedAt)
val day = DateUtils.getDayPrecisionTimeString(context, Locale.getDefault(), requestedAt)
resources.getString(R.string.VerificationCodeRequestedBottomSheet__time_with_day, time, day)
}
val nestedScrollInterop = rememberNestedScrollInteropConnection()
val scrollModifier = Modifier.nestedScroll(nestedScrollInterop)
VerificationCodeRequestedContent(
formattedTime = formattedTime,
onSafetyTipsClicked = {
val fragmentManager = parentFragmentManager
dismissAllowingStateLoss()
VerificationCodeRequestedSafetyTipsBottomSheet.show(fragmentManager)
},
onOkClicked = { dismissAllowingStateLoss() },
modifier = scrollModifier
)
}
}
@Composable
private fun VerificationCodeRequestedContent(
formattedTime: String,
onSafetyTipsClicked: () -> Unit,
onOkClicked: () -> Unit,
modifier: Modifier = Modifier
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier.fillMaxWidth()
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxWidth()
) {
BottomSheets.Handle()
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.weight(weight = 1f, fill = false)
.verticalScroll(state = scrollState)
.padding(horizontal = 36.dp)
.padding(bottom = 36.dp)
) {
Spacer(modifier = Modifier.height(26.dp))
Image(
painter = painterResource(id = R.drawable.verificationcode_alert_96),
contentDescription = null,
modifier = Modifier.size(96.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(id = R.string.VerificationCodeRequestedBottomSheet__title),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = formattedTime,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(20.dp))
Text(
text = stringResource(id = R.string.VerificationCodeRequestedBottomSheet__body_1),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(id = R.string.VerificationCodeRequestedBottomSheet__body_2),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(32.dp))
Buttons.LargeTonal(
onClick = onSafetyTipsClicked,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
) {
Text(text = stringResource(id = R.string.VerificationCodeRequestedBottomSheet__safety_tips))
}
Spacer(modifier = Modifier.height(8.dp))
Buttons.LargeTonal(
onClick = onOkClicked,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
) {
Text(text = stringResource(id = R.string.VerificationCodeRequestedBottomSheet__ok))
}
}
}
}
@DayNightPreviews
@Composable
private fun VerificationCodeRequestedContentPreview() {
Previews.BottomSheetContentPreview {
VerificationCodeRequestedContent(
formattedTime = "3:25 PM Today",
onSafetyTipsClicked = {},
onOkClicked = {}
)
}
}
@@ -0,0 +1,182 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.verificationrequested
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentManager
import org.signal.core.ui.BottomSheetUtil
import org.signal.core.ui.compose.BottomSheets
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
/**
* Sheet showing safety tips related to a verification code alert.
*/
class VerificationCodeRequestedSafetyTipsBottomSheet : ComposeBottomSheetDialogFragment() {
override val peekHeightPercentage: Float = 1f
companion object {
@JvmStatic
fun show(fragmentManager: FragmentManager) {
VerificationCodeRequestedSafetyTipsBottomSheet().show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
}
}
@Composable
override fun SheetContent() {
val nestedScrollInterop = rememberNestedScrollInteropConnection()
val scrollModifier = Modifier.nestedScroll(nestedScrollInterop)
SafetyTipsContent(
onOpenAccountSettings = {
startActivity(AppSettingsActivity.account(requireContext()))
dismissAllowingStateLoss()
},
modifier = scrollModifier
)
}
}
@Composable
private fun SafetyTipsContent(
onOpenAccountSettings: () -> Unit,
modifier: Modifier = Modifier
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier.fillMaxWidth()
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxWidth()
) {
BottomSheets.Handle()
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.weight(weight = 1f, fill = false)
.verticalScroll(state = scrollState)
.padding(horizontal = 36.dp)
.padding(bottom = 36.dp)
) {
Spacer(modifier = Modifier.height(26.dp))
Text(
text = stringResource(id = R.string.SafetyTipsBottomSheet__title),
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(36.dp))
SafetyTipRow(
iconRes = R.drawable.safetytip_48_message,
titleRes = R.string.SafetyTipsBottomSheet__tip_1_title,
bodyRes = R.string.SafetyTipsBottomSheet__tip_1_body
)
Spacer(modifier = Modifier.height(40.dp))
SafetyTipRow(
iconRes = R.drawable.safetytip_48_pin,
titleRes = R.string.SafetyTipsBottomSheet__tip_2_title,
bodyRes = R.string.SafetyTipsBottomSheet__tip_2_body
)
Spacer(modifier = Modifier.height(40.dp))
SafetyTipRow(
iconRes = R.drawable.safetytip_48_lock,
titleRes = R.string.SafetyTipsBottomSheet__tip_3_title,
bodyRes = R.string.SafetyTipsBottomSheet__tip_3_body
)
Spacer(modifier = Modifier.height(40.dp))
Buttons.LargeTonal(
onClick = onOpenAccountSettings,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
) {
Text(text = stringResource(id = R.string.SafetyTipsBottomSheet__open_account_settings))
}
}
}
}
@Composable
private fun SafetyTipRow(
iconRes: Int,
titleRes: Int,
bodyRes: Int
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(24.dp)
) {
Image(
painter = painterResource(id = iconRes),
contentDescription = null,
modifier = Modifier.size(48.dp)
)
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = stringResource(id = titleRes),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(id = bodyRes),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
@DayNightPreviews
@Composable
private fun SafetyTipsContentPreview() {
Previews.BottomSheetContentPreview {
SafetyTipsContent(onOpenAccountSettings = {})
}
}
@@ -35,13 +35,16 @@ public class FcmReceiveService extends FirebaseMessagingService {
remoteMessage.getOriginalPriority(),
NetworkUtil.getNetworkStatus(this)));
String registrationChallenge = remoteMessage.getData().get("challenge");
String rateLimitChallenge = remoteMessage.getData().get("rateLimitChallenge");
String registrationChallenge = remoteMessage.getData().get("challenge");
String rateLimitChallenge = remoteMessage.getData().get("rateLimitChallenge");
String verificationCodeRequest = remoteMessage.getData().get("verificationCodeRequested");
if (registrationChallenge != null) {
handleRegistrationPushChallenge(registrationChallenge);
} else if (rateLimitChallenge != null) {
handleRateLimitPushChallenge(rateLimitChallenge);
} else if (verificationCodeRequest != null && SignalStore.account().isPrimaryDevice()) {
handleVerificationCodeRequested(verificationCodeRequest, remoteMessage.getSentTime());
} else {
handleReceivedNotification(AppDependencies.getApplication(), remoteMessage);
}
@@ -102,4 +105,20 @@ public class FcmReceiveService extends FirebaseMessagingService {
Log.d(TAG, "Got a rate limit push challenge.");
AppDependencies.getJobManager().add(new SubmitRateLimitPushChallengeJob(challenge));
}
private static void handleVerificationCodeRequested(String verificationCodeRequestJson, long sentTime) {
Log.i(TAG, "Got a verification code requested push.");
VerificationCodeRequestedPush verificationRequestedPush = VerificationCodeRequestedPush.fromJson(verificationCodeRequestJson);
long requestedAt;
if (verificationRequestedPush != null && verificationRequestedPush.getTimestamp() != null) {
requestedAt = verificationRequestedPush.getTimestamp();
} else {
Log.w(TAG, "Unable to parse requested at timestamp from server, using sent time instead");
requestedAt = sentTime;
}
SignalStore.account().setVerificationCodeRequestedAtMs(requestedAt);
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.gcm
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import org.signal.core.util.logging.Log
@Serializable
data class VerificationCodeRequestedPush(val timestamp: Long?) {
companion object {
private val TAG = Log.tag(VerificationCodeRequestedPush::class)
private val json = Json { ignoreUnknownKeys = true }
@JvmStatic
fun fromJson(jsonString: String): VerificationCodeRequestedPush? {
return try {
json.decodeFromString(jsonString)
} catch (e: Throwable) {
Log.w(TAG, "Unable to parse verification code request", e)
null
}
}
}
}
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
import kotlinx.coroutines.flow.Flow
import org.signal.core.models.AccountEntropyPool
import org.signal.core.models.ServiceId.ACI
import org.signal.core.models.ServiceId.PNI
@@ -86,6 +87,8 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
private const val KEY_HAS_LINKED_DEVICES = "account.has_linked_devices"
private const val KEY_HAS_INACTIVE_PRIMARY_DEVICE_ALERT = "account.has_inactive_primary_device_alert"
private const val KEY_VERIFICATION_CODE_REQUESTED_AT = "account.verification_code_requested_at"
private const val KEY_ACCOUNT_ENTROPY_POOL = "account.account_entropy_pool"
private const val KEY_RESTORED_ACCOUNT_ENTROPY_KEY = "account.restored_account_entropy_pool"
private const val KEY_RESTORED_ACCOUNT_ENTROPY_KEY_FROM_PRIMARY = "account.restore_account_entropy_pool_primary"
@@ -562,6 +565,11 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
@get:JvmName("isMultiDevice")
var isMultiDevice by booleanValue(KEY_HAS_LINKED_DEVICES, false)
/** Server has indicated a verification code was requested for the account at this timestamp (ms since epoch) */
private val verificationCodeRequestedAtMsValue = longValue(KEY_VERIFICATION_CODE_REQUESTED_AT, 0)
var verificationCodeRequestedAtMs: Long by verificationCodeRequestedAtMsValue
val verificationCodeRequestedAtMsFlow: Flow<Long> by lazy { verificationCodeRequestedAtMsValue.toFlow() }
/** Do not alter. If you need to migrate more stuff, create a new method. */
private fun migrateFromSharedPrefsV1(context: Context) {
Log.i(TAG, "[V1] Migrating account values from shared prefs.")
@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#FFF8E0D9"
android:pathData="M24 0A24 24 0 1 0 24 48 24 24 0 1 0 24 0z"/>
<path
android:fillColor="#FFFDEEEB"
android:fillType="evenOdd"
android:pathData="M24 10.58c-4.03 0-7.3 3.27-7.3 7.3v3.58c-0.73 0.41-1.33 1.03-1.72 1.79-0.25 0.5-0.36 1.03-0.4 1.66-0.06 0.61-0.06 1.37-0.06 2.32v3.45c0 0.96 0 1.71 0.05 2.33 0.05 0.63 0.16 1.17 0.41 1.66 0.4 0.8 1.05 1.44 1.85 1.85 0.5 0.25 1.03 0.36 1.66 0.4 0.62 0.06 1.38 0.06 2.33 0.06h6.36c0.95 0 1.71 0 2.33-0.05 0.63-0.05 1.17-0.16 1.66-0.41 0.8-0.4 1.44-1.05 1.85-1.85 0.25-0.5 0.36-1.03 0.4-1.66 0.06-0.62 0.06-1.37 0.06-2.33v-3.45c0-0.95 0-1.7-0.05-2.32-0.05-0.63-0.16-1.17-0.41-1.66-0.39-0.76-0.99-1.38-1.73-1.79v-3.59c0-4.02-3.26-7.29-7.29-7.29Zm4.96 10.38v-3.09c0-2.73-2.22-4.95-4.96-4.95-2.74 0-4.96 2.22-4.96 4.96v3.08c0.5-0.02 1.1-0.02 1.78-0.02h6.36c0.69 0 1.27 0 1.78 0.02Zm-7 6.69c0-1.13 0.91-2.05 2.04-2.05s2.04 0.92 2.04 2.05c0 0.69-0.34 1.3-0.87 1.67v1.97c0 0.65-0.53 1.17-1.17 1.17-0.64 0-1.17-0.52-1.17-1.17v-1.97c-0.53-0.37-0.87-0.98-0.87-1.67Z"/>
<path
android:fillColor="#FFC34922"
android:pathData="M22.83 29.32c-0.53-0.37-0.87-0.98-0.87-1.67 0-1.13 0.91-2.05 2.04-2.05s2.04 0.92 2.04 2.05c0 0.69-0.34 1.3-0.87 1.67v1.97c0 0.65-0.53 1.17-1.17 1.17-0.64 0-1.17-0.52-1.17-1.17v-1.97Z"/>
<path
android:fillColor="#FFC34922"
android:fillType="evenOdd"
android:pathData="M16.7 17.88c0-4.03 3.27-7.3 7.3-7.3 4.03 0 7.3 3.27 7.3 7.3v3.1L31.36 21c0.88 0.45 1.6 1.16 2.04 2.04 0.29 0.56 0.4 1.17 0.46 1.82 0.05 0.63 0.05 1.4 0.05 2.35v3.48c0 0.94 0 1.71-0.05 2.34-0.06 0.66-0.17 1.26-0.46 1.83-0.45 0.88-1.16 1.6-2.04 2.04-0.57 0.29-1.17 0.4-1.83 0.46-0.63 0.05-1.4 0.05-2.34 0.05h-6.4c-0.94 0-1.71 0-2.34-0.05-0.66-0.06-1.26-0.17-1.83-0.46-0.88-0.45-1.6-1.16-2.04-2.04-0.29-0.57-0.4-1.17-0.46-1.83-0.05-0.63-0.05-1.4-0.05-2.34v-3.48c0-0.94 0-1.72 0.05-2.35 0.06-0.65 0.17-1.26 0.46-1.82 0.45-0.88 1.16-1.6 2.04-2.04l0.08-0.04v-3.1Zm12.26 0v2.64c-0.51-0.02-1.1-0.02-1.76-0.02h-6.4c-0.67 0-1.25 0-1.76 0.02v-2.64c0-2.74 2.22-4.96 4.96-4.96 2.74 0 4.96 2.22 4.96 4.96Zm-11.27 5.2c0.18-0.09 0.44-0.16 0.96-0.2 0.52-0.05 1.2-0.05 2.2-0.05h6.3c1 0 1.68 0 2.2 0.05 0.52 0.04 0.78 0.11 0.96 0.2 0.44 0.23 0.8 0.59 1.02 1.03 0.1 0.18 0.17 0.44 0.21 0.95 0.04 0.53 0.04 1.2 0.04 2.2v3.39c0 1 0 1.68-0.04 2.2-0.04 0.52-0.12 0.78-0.21 0.96-0.22 0.44-0.58 0.8-1.02 1.02-0.18 0.1-0.44 0.17-0.96 0.21-0.52 0.04-1.2 0.04-2.2 0.04h-6.3c-1 0-1.68 0-2.2-0.04-0.52-0.04-0.78-0.12-0.96-0.21-0.44-0.22-0.8-0.58-1.02-1.02-0.1-0.18-0.17-0.44-0.21-0.96-0.04-0.52-0.04-1.2-0.04-2.2v-3.38c0-1 0-1.68 0.04-2.2 0.04-0.52 0.12-0.78 0.21-0.96 0.22-0.44 0.58-0.8 1.02-1.02Z"/>
</vector>
@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#FFF8E0D9"
android:pathData="M48 24c0 13.25-10.75 24-24 24S0 37.25 0 24 10.75 0 24 0s24 10.75 24 24Z"/>
<path
android:fillColor="#FFFDEEEB"
android:fillType="evenOdd"
android:pathData="M24.4 13c-6.17 0-11.17 5-11.17 11.17 0 1.9 0.48 3.7 1.32 5.27 0.24 0.45 0.3 1 0.11 1.5L13 35.56l4.62-1.66c0.5-0.18 1.06-0.13 1.5 0.11 1.57 0.85 3.37 1.32 5.28 1.32 6.16 0 11.16-5 11.16-11.16C35.56 18 30.56 13 24.4 13Z"/>
<path
android:fillColor="#FFC34922"
android:pathData="M19.63 19.63c0.4-0.4 1.05-0.4 1.45 0L24 22.56l2.92-2.93c0.4-0.4 1.05-0.4 1.45 0 0.4 0.4 0.4 1.05 0 1.45L25.44 24l2.93 2.92c0.4 0.4 0.4 1.05 0 1.45-0.4 0.4-1.05 0.4-1.45 0L24 25.44l-2.92 2.93c-0.4 0.4-1.05 0.4-1.45 0-0.4-0.4-0.4-1.05 0-1.45L22.56 24l-2.93-2.92c-0.4-0.4-0.4-1.05 0-1.45Z"/>
<path
android:fillColor="#FFC34922"
android:fillType="evenOdd"
android:pathData="M12.19 24c0-6.52 5.29-11.81 11.81-11.81 6.52 0 11.81 5.29 11.81 11.81 0 6.52-5.29 11.81-11.81 11.81-1.95 0-3.8-0.47-5.41-1.3l-4.74 1.7c-1.28 0.46-2.52-0.78-2.06-2.06l1.7-4.74c-0.83-1.62-1.3-3.46-1.3-5.41ZM24 14.23c-5.4 0-9.77 4.37-9.77 9.77 0 1.67 0.42 3.24 1.15 4.61 0.21 0.4 0.26 0.87 0.1 1.32l-1.45 4.04 4.04-1.45c0.45-0.16 0.92-0.12 1.32 0.1 1.37 0.73 2.94 1.15 4.61 1.15 5.4 0 9.77-4.37 9.77-9.77 0-5.4-4.37-9.77-9.77-9.77Z"/>
</vector>
@@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#FFF8E0D9"
android:pathData="M24 0A24 24 0 1 0 24 48 24 24 0 1 0 24 0z"/>
<path
android:fillColor="#FFFDEEEB"
android:pathData="M11 19c0-2.76 2.24-5 5-5h16c2.76 0 5 2.24 5 5v10c0 2.76-2.24 5-5 5H16c-2.76 0-5-2.24-5-5V19Z"/>
<path
android:fillColor="#FFC34922"
android:pathData="M32 31.75V34H16v-2.25h16ZM34.75 29V19c0-1.52-1.23-2.75-2.75-2.75H16c-1.52 0-2.75 1.23-2.75 2.75v10c0 1.52 1.23 2.75 2.75 2.75V34c-2.76 0-5-2.24-5-5V19c0-2.76 2.24-5 5-5h16c2.76 0 5 2.24 5 5v10c0 2.76-2.24 5-5 5v-2.25c1.52 0 2.75-1.23 2.75-2.75Z"/>
<path
android:fillColor="#FFC34922"
android:pathData="M18.01 26.21c-0.38 0-0.63-0.23-0.62-0.57v-0.1l0.16-1.1-1.03 0.7c-0.12 0.1-0.27 0.15-0.42 0.15-0.31 0-0.6-0.24-0.6-0.57 0-0.23 0.16-0.43 0.42-0.53l1.19-0.46-1.19-0.47c-0.27-0.1-0.42-0.3-0.42-0.53 0-0.31 0.29-0.56 0.6-0.56 0.16 0 0.3 0.05 0.42 0.14l1.04 0.72-0.16-1.16-0.01-0.09c0-0.33 0.25-0.57 0.62-0.57s0.6 0.23 0.6 0.57l-0.01 0.1-0.16 1.15 1.04-0.72c0.12-0.1 0.26-0.14 0.42-0.14 0.32 0 0.6 0.24 0.6 0.56 0 0.24-0.15 0.43-0.42 0.53l-1.2 0.47 1.2 0.47c0.27 0.1 0.42 0.29 0.42 0.52 0 0.33-0.29 0.57-0.6 0.57-0.16 0-0.3-0.05-0.42-0.14l-1.03-0.72 0.16 1.12 0.01 0.1c0 0.33-0.24 0.56-0.6 0.56Z"/>
<path
android:fillColor="#FFC34922"
android:pathData="M24.01 26.21c-0.38 0-0.63-0.23-0.62-0.57v-0.1l0.16-1.1-1.03 0.7c-0.12 0.1-0.27 0.15-0.42 0.15-0.31 0-0.6-0.24-0.6-0.57 0-0.23 0.16-0.43 0.42-0.53l1.19-0.46-1.19-0.47c-0.27-0.1-0.42-0.3-0.42-0.53 0-0.31 0.29-0.56 0.6-0.56 0.16 0 0.3 0.05 0.42 0.14l1.04 0.72-0.16-1.16-0.01-0.09c0-0.33 0.25-0.57 0.62-0.57s0.6 0.23 0.6 0.57l-0.01 0.1-0.16 1.15 1.04-0.72c0.12-0.1 0.26-0.14 0.42-0.14 0.32 0 0.6 0.24 0.6 0.56 0 0.24-0.15 0.43-0.42 0.53l-1.2 0.47 1.2 0.47c0.27 0.1 0.42 0.29 0.42 0.52 0 0.33-0.29 0.57-0.6 0.57-0.16 0-0.3-0.05-0.42-0.14l-1.03-0.72 0.16 1.12 0.01 0.1c0 0.33-0.24 0.56-0.6 0.56Z"/>
<path
android:fillColor="#FFC34922"
android:pathData="M30.01 26.21c-0.38 0-0.63-0.23-0.62-0.57v-0.1l0.16-1.1-1.03 0.7c-0.12 0.1-0.27 0.15-0.42 0.15-0.31 0-0.6-0.24-0.6-0.57 0-0.23 0.16-0.43 0.42-0.53l1.19-0.46-1.19-0.47c-0.27-0.1-0.42-0.3-0.42-0.53 0-0.31 0.29-0.56 0.6-0.56 0.16 0 0.3 0.05 0.42 0.14l1.04 0.72-0.16-1.16-0.01-0.09c0-0.33 0.25-0.57 0.62-0.57s0.6 0.23 0.6 0.57l-0.01 0.1-0.16 1.15 1.04-0.72c0.12-0.1 0.26-0.14 0.42-0.14 0.32 0 0.6 0.24 0.6 0.56 0 0.24-0.15 0.43-0.42 0.53l-1.2 0.47 1.2 0.47c0.27 0.1 0.42 0.29 0.42 0.52 0 0.33-0.29 0.57-0.6 0.57-0.16 0-0.3-0.05-0.42-0.14l-1.03-0.72 0.16 1.12 0.01 0.1c0 0.33-0.24 0.56-0.6 0.56Z"/>
</vector>
@@ -0,0 +1,31 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="96dp"
android:height="96dp"
android:viewportWidth="96"
android:viewportHeight="96">
<path
android:fillColor="#FFC3C6FA"
android:strokeColor="#FF3B45FD"
android:strokeWidth="2.5"
android:pathData="M59 10.75c4.56 0 8.25 3.7 8.25 8.25v58c0 4.56-3.7 8.25-8.25 8.25H37c-4.56 0-8.25-3.7-8.25-8.25V19c0-4.56 3.7-8.25 8.25-8.25h22Z"/>
<path
android:fillColor="#FFDEE0FC"
android:pathData="M49.92 12C48.23 22.66 40.3 31.25 30 33.9V19c0-3.87 3.13-7 7-7h12.92Z"/>
<path
android:fillColor="#FFA9ADF8"
android:pathData="M62.36 12.86C64.53 14.05 66 16.36 66 19v58c0 3.87-3.13 7-7 7H37c-1.6 0-3.08-0.54-4.27-1.45 1.46-2.02 3.82-3.34 6.5-3.34h15c4.43 0 8-3.58 8-8V14.23c0-0.47 0.05-0.93 0.13-1.37Z"/>
<path
android:fillColor="#FF3B45FD"
android:pathData="M43 16.5c0-0.83 0.67-1.5 1.5-1.5h7c0.83 0 1.5 0.67 1.5 1.5v0c0 0.83-0.67 1.5-1.5 1.5h-7c-0.83 0-1.5-0.67-1.5-1.5z"/>
<path
android:fillColor="#FFC34922"
android:strokeColor="#FF953516"
android:strokeWidth="2"
android:pathData="M66 61c8.28 0 15 6.72 15 15 0 8.28-6.72 15-15 15-8.28 0-15-6.72-15-15 0-8.28 6.72-15 15-15Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M60.87 73.32c0.1-2.92 2.41-5 5.13-5 2.72 0 5.02 2.08 5.13 5 0.1 2.76 0.65 3.8 1.12 4.36 0.12 0.15 0.24 0.27 0.37 0.38l0.03 0.03 0.03 0.02 0.3 0.3c0.25 0.28 0.43 0.6 0.43 1.12 0 0.84-0.68 1.53-1.52 1.53H60.11c-0.84 0-1.52-0.7-1.52-1.53 0-0.52 0.18-0.84 0.43-1.12l0.3-0.3 0.06-0.05c0.13-0.11 0.25-0.23 0.37-0.38 0.47-0.55 1.03-1.6 1.12-4.36Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M63.71 82.37c0.07-0.11 0.2-0.18 0.33-0.18h3.92c0.14 0 0.26 0.07 0.33 0.18 0.06 0.12 0.07 0.26 0 0.38-0.45 0.8-1.3 1.31-2.29 1.31-1 0-1.84-0.51-2.3-1.31-0.06-0.12-0.05-0.26 0.01-0.38Z"/>
</vector>
@@ -845,6 +845,16 @@
app:popUpTo="@id/app_settings"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_direct_to_accountSettingsFragment"
app:destination="@id/accountSettingsFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit"
app:popUpTo="@id/app_settings"
app:popUpToInclusive="true" />
<!-- endregion -->
<!-- Labs Settings -->
+30
View File
@@ -9902,5 +9902,35 @@
<!-- Body of the sheet shown when a local backup restore could not be completed, explaining how to try again -->
<string name="CouldNotCompleteBackupRestoreSheet__body_retry">To try again, uninstall and re-install Signal on this device, and choose \"Restore or transfer\".</string>
<!-- Title of the sheet shown when the server reports a verification code was requested for the user's phone number -->
<string name="VerificationCodeRequestedBottomSheet__title">A verification code was requested</string>
<!-- First paragraph of the body of the verification code requested sheet -->
<string name="VerificationCodeRequestedBottomSheet__body_1">Do not give your verification code to anyone. Signal will never message you for it. If you received a message from someone pretending to be Signal, it is a scam.</string>
<!-- Second paragraph of the body of the verification code requested sheet -->
<string name="VerificationCodeRequestedBottomSheet__body_2">You can safely ignore this message if you requested the code yourself.</string>
<!-- Button on the verification code requested sheet that opens the safety tips screen -->
<string name="VerificationCodeRequestedBottomSheet__safety_tips">Safety tips</string>
<!-- Button on the verification code requested sheet that dismisses the sheet -->
<string name="VerificationCodeRequestedBottomSheet__ok">OK</string>
<!-- Subtitle on the verification code requested sheet showing the time the code was requested followed by the relative day. %1$s is the time (e.g. "3:25 PM"), %2$s is the relative day (e.g. "Today", "Yesterday", "Jan 15") -->
<string name="VerificationCodeRequestedBottomSheet__time_with_day">%1$s %2$s</string>
<!-- Title of the safety tips screen within the verification code requested sheet -->
<string name="SafetyTipsBottomSheet__title">Safety Tips</string>
<!-- Title of the first safety tip: don't respond to chats claiming to be Signal -->
<string name="SafetyTipsBottomSheet__tip_1_title">Don\'t respond to chats from Signal</string>
<!-- Body of the first safety tip -->
<string name="SafetyTipsBottomSheet__tip_1_body">Signal will never message you for your registration code, PIN, or recovery key. Never respond to a chat pretending to be Signal.</string>
<!-- Title of the second safety tip: keep your verification code safe -->
<string name="SafetyTipsBottomSheet__tip_2_title">Keep your verification code safe</string>
<!-- Body of the second safety tip -->
<string name="SafetyTipsBottomSheet__tip_2_body">If you received a verification code you didn\'t request, someone may be attempting to access your account. Do not share your code.</string>
<!-- Title of the third safety tip: enable registration lock -->
<string name="SafetyTipsBottomSheet__tip_3_title">Turn on registration lock in account settings</string>
<!-- Body of the third safety tip -->
<string name="SafetyTipsBottomSheet__tip_3_body">Protect your account by requiring your Signal PIN, in addition to your verification code, when registering with Signal.</string>
<!-- Button on the safety tips screen that opens account settings -->
<string name="SafetyTipsBottomSheet__open_account_settings">Open account settings</string>
<!-- EOF -->
</resources>