Enforce change number post registration delay.

This commit is contained in:
Cody Henthorne
2026-05-15 15:26:05 -04:00
committed by jeffrey-signal
parent 4decae274b
commit a9649fd017
10 changed files with 325 additions and 29 deletions
@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.registration.sms.ReceivedSmsEvent
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.visible
import kotlin.math.ceil
import kotlin.time.Duration.Companion.milliseconds
/**
@@ -168,7 +169,7 @@ class ChangeNumberEnterCodeFragment : LoggingFragment(R.layout.fragment_change_n
is ChangeNumberResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
is ChangeNumberResult.AuthorizationFailed -> presentIncorrectCodeDialog()
is ChangeNumberResult.AttemptsExhausted -> presentAccountLocked()
is ChangeNumberResult.RateLimited -> presentRateLimitedDialog()
is ChangeNumberResult.RateLimited -> presentRateLimitedDialog(result.timeRemaining)
else -> presentGenericError(result)
}
@@ -195,13 +196,25 @@ class ChangeNumberEnterCodeFragment : LoggingFragment(R.layout.fragment_change_n
)
}
private fun presentRateLimitedDialog() {
private fun presentRateLimitedDialog(retryAfterSeconds: Long = 0) {
binding.codeEntryLayout.keyboard.displayFailure().addListener(
object : AssertedSuccessListener<Boolean?>() {
override fun onSuccess(result: Boolean?) {
MaterialAlertDialogBuilder(requireContext()).apply {
setTitle(R.string.RegistrationActivity_too_many_attempts)
setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later)
if (retryAfterSeconds > 0) {
val minutes = ceil(retryAfterSeconds / 60.0).toInt().coerceAtLeast(1)
setMessage(
if (minutes >= 60) {
val hours = ceil(minutes / 60.0).toInt()
resources.getQuantityString(R.plurals.ChangeNumberEnterCodeFragment__too_many_attempts_try_again_in_hours, hours, hours)
} else {
resources.getQuantityString(R.plurals.ChangeNumberEnterCodeFragment__too_many_attempts_try_again_in_minutes, minutes, minutes)
}
)
} else {
setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later)
}
setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
binding.codeEntryLayout.callMeCountDown.visibility = View.VISIBLE
binding.codeEntryLayout.resendSmsCountDown.visibility = View.VISIBLE
@@ -23,7 +23,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
@@ -34,7 +33,10 @@ import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import kotlin.time.Duration.Companion.milliseconds
class ChangeNumberFragment : ComposeFragment() {
@@ -46,10 +48,25 @@ class ChangeNumberFragment : ComposeFragment() {
navController.popBackStack()
},
onContinueClick = {
navController.safeNavigate(ChangeNumberFragmentDirections.actionChangePhoneNumberFragmentToEnterPhoneNumberChangeFragment())
val remainingWaitSeconds = remainingPostRegistrationWaitSeconds()
if (remainingWaitSeconds > 0) {
ChangeNumberPostRegistrationWaitSheet.show(parentFragmentManager, remainingWaitSeconds)
} else {
navController.safeNavigate(ChangeNumberFragmentDirections.actionChangePhoneNumberFragmentToEnterPhoneNumberChangeFragment())
}
}
)
}
private fun remainingPostRegistrationWaitSeconds(): Long {
val registeredAt = SignalStore.account.registeredAtTimestamp
if (registeredAt <= 0) {
return 0
}
val waitingPeriodSeconds = RemoteConfig.changeNumberPostRegistrationWaitingPeriodSeconds
val elapsedSeconds = (System.currentTimeMillis() - registeredAt).milliseconds.inWholeSeconds
return (waitingPeriodSeconds - elapsedSeconds).coerceAtLeast(0)
}
}
@Composable
@@ -73,7 +90,7 @@ fun ChangeNumberScreen(
.padding(horizontal = 32.dp)
) {
Image(
painter = painterResource(id = R.drawable.change_number_hero_image),
painter = painterResource(id = R.drawable.change_number),
contentDescription = null,
modifier = Modifier
.padding(top = 20.dp)
@@ -83,7 +100,6 @@ fun ChangeNumberScreen(
text = stringResource(id = R.string.AccountSettingsFragment__change_phone_number),
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(top = 24.dp)
)
@@ -0,0 +1,151 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.changenumber
import android.os.Bundle
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.res.painterResource
import androidx.compose.ui.res.pluralStringResource
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.signal.core.ui.compose.horizontalGutters
import org.thoughtcrime.securesms.R
import kotlin.math.ceil
/**
* Sheet shown when the user attempts to change their phone number before the
* post-registration waiting period has elapsed.
*/
class ChangeNumberPostRegistrationWaitSheet : ComposeBottomSheetDialogFragment() {
companion object {
private const val ARG_REMAINING_SECONDS = "arg.remaining_seconds"
@JvmStatic
fun show(fragmentManager: FragmentManager, remainingSeconds: Long) {
ChangeNumberPostRegistrationWaitSheet().apply {
arguments = Bundle().apply {
putLong(ARG_REMAINING_SECONDS, remainingSeconds)
}
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
}
}
private val remainingSeconds: Long
get() = requireArguments().getLong(ARG_REMAINING_SECONDS)
@Composable
override fun SheetContent() {
SheetContent(
remainingSeconds = remainingSeconds,
onDismiss = { dismissAllowingStateLoss() }
)
}
}
@Composable
private fun SheetContent(
remainingSeconds: Long,
onDismiss: () -> Unit
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.horizontalGutters(gutterSize = 36.dp)
) {
BottomSheets.Handle()
Image(
painter = painterResource(R.drawable.change_number_error),
contentDescription = null,
modifier = Modifier
.padding(top = 26.dp)
)
Text(
text = stringResource(R.string.ChangeNumberPostRegistrationWaitSheet__title),
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.padding(top = 16.dp)
)
Text(
text = stringResource(R.string.ChangeNumberPostRegistrationWaitSheet__body),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 12.dp)
)
Text(
text = formatTryAgainIn(remainingSeconds),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 16.dp)
)
Buttons.LargeTonal(
onClick = onDismiss,
modifier = Modifier
.fillMaxWidth()
.padding(top = 32.dp, bottom = 24.dp, start = 12.dp, end = 12.dp)
) {
Text(stringResource(R.string.ChangeNumberPostRegistrationWaitSheet__ok))
}
}
}
@DayNightPreviews
@Composable
private fun SheetContentMinutesPreview() {
Previews.BottomSheetContentPreview {
SheetContent(
remainingSeconds = 25 * 60,
onDismiss = {}
)
}
}
@DayNightPreviews
@Composable
private fun SheetContentHoursPreview() {
Previews.BottomSheetContentPreview {
SheetContent(
remainingSeconds = 2 * 60 * 60,
onDismiss = {}
)
}
}
@Composable
private fun formatTryAgainIn(remainingSeconds: Long): String {
val minutes = ceil(remainingSeconds / 60.0).toInt().coerceAtLeast(1)
return if (minutes >= 60) {
val hours = ceil(minutes / 60.0).toInt()
pluralStringResource(R.plurals.ChangeNumberPostRegistrationWaitSheet__try_again_in_hours, hours, hours)
} else {
pluralStringResource(R.plurals.ChangeNumberPostRegistrationWaitSheet__try_again_in_minutes, minutes, minutes)
}
}
@@ -434,7 +434,7 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
val isRegistered: Boolean
get() = getBoolean(KEY_IS_REGISTERED, false)
fun setRegistered(registered: Boolean) {
fun setRegistered(registered: Boolean, isAciChanged: Boolean = false) {
Log.i(TAG, "Setting push registered: $registered", Throwable())
val previous = isRegistered
@@ -451,7 +451,7 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
clearLocalCredentials()
}
if (!previous && registered) {
if (registered && (!previous || isAciChanged)) {
registeredAtTimestamp = System.currentTimeMillis()
} else if (!registered) {
registeredAtTimestamp = -1
@@ -184,6 +184,7 @@ object RegistrationRepository {
val aci: ACI = ACI.parseOrThrow(data.aci)
val pni: PNI = PNI.parseOrThrow(data.pni)
val hasPin: Boolean = data.hasPin
val isAciChanged: Boolean = SignalStore.account.aci != aci
SignalStore.account.setAci(aci)
SignalStore.account.setPni(pni)
@@ -232,7 +233,7 @@ object RegistrationRepository {
}
SignalStore.account.setServicePassword(data.servicePassword)
SignalStore.account.setRegistered(true)
SignalStore.account.setRegistered(registered = true, isAciChanged = isAciChanged)
TextSecurePreferences.setPromptedPushRegistration(context, true)
TextSecurePreferences.setUnauthorizedReceived(context, false)
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID)
@@ -1411,6 +1411,15 @@ object RemoteConfig {
hotSwappable = true
)
/** Seconds after registration during which change-number is blocked. */
@JvmStatic
@get:JvmName("changeNumberPostRegistrationWaitingPeriodSeconds")
val changeNumberPostRegistrationWaitingPeriodSeconds: Long by remoteLong(
key = "global.changeNumber.postRegistrationWaitingPeriodSeconds",
defaultValue = 3600,
hotSwappable = true
)
/**
* A ratio between 0 and 1, where 0 means that a session is never archived due
* to a lack of PQ, and 1 means that a session is always archived due to a
@@ -0,0 +1,58 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="160dp"
android:height="120dp"
android:viewportWidth="160"
android:viewportHeight="120">
<path
android:fillColor="#FF585B7F"
android:pathData="M66.15 68.52c-0.26 0.26-0.4 0.61-0.4 0.98s0.14 0.72 0.4 0.98l7.28 7.36c0.54 0.55 1.4 0.55 1.94 0 0.53-0.54 0.53-1.41 0-1.95l-5.04-5.1 1.25 0.1h0.01 21.29c0.76 0 1.37-0.63 1.37-1.39 0-0.76-0.61-1.38-1.37-1.38H71.59l-1.26 0.09 5.04-5.1c0.53-0.54 0.53-1.41 0-1.95-0.54-0.55-1.4-0.55-1.94 0l-7.28 7.36Z"/>
<path
android:fillColor="#FF585B7F"
android:pathData="M93.85 49.52c0.26 0.26 0.4 0.61 0.4 0.98s-0.14 0.72-0.4 0.98l-7.28 7.36c-0.54 0.55-1.4 0.55-1.94 0-0.53-0.54-0.53-1.41 0-1.95l5.04-5.1-1.25 0.1H88.4 67.12c-0.76 0-1.37-0.63-1.37-1.39 0-0.76 0.61-1.38 1.37-1.38h21.29l1.26 0.09-5.04-5.1c-0.53-0.54-0.53-1.41 0-1.95 0.54-0.55 1.4-0.55 1.94 0l7.28 7.36Z"/>
<path
android:fillColor="#FFC3C6FA"
android:pathData="M16.5 31c0-3.87 3.13-7 7-7h22c3.87 0 7 3.13 7 7v58c0 3.87-3.13 7-7 7h-22c-3.87 0-7-3.13-7-7V31Z"/>
<path
android:fillColor="#FF3B45FD"
android:pathData="M14 89V31c0-5.25 4.25-9.5 9.5-9.5V24c-3.87 0-7 3.13-7 7v58c0 3.75 2.94 6.8 6.64 7h0.36 22c3.87 0 7-3.13 7-7V31c0-3.87-3.13-7-7-7v-2.5c5.25 0 9.5 4.25 9.5 9.5v58c0 5.25-4.25 9.5-9.5 9.5h-22c-5.25 0-9.5-4.25-9.5-9.5Zm31.5-67.5V24h-22v-2.5h22Z"/>
<path
android:fillColor="#FFDEE0FC"
android:pathData="M36.42 24C34.73 34.66 26.8 43.25 16.5 45.9V31c0-3.87 3.13-7 7-7h12.92Z"/>
<path
android:fillColor="#FFA9ADF8"
android:pathData="M48.86 24.86c2.17 1.19 3.64 3.5 3.64 6.14v58c0 3.87-3.13 7-7 7h-22c-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-8V26.23c0-0.47 0.05-0.93 0.13-1.37Z"/>
<path
android:fillColor="#FF3B45FD"
android:fillType="evenOdd"
android:pathData="M25.46 49.18c1.46-1.45 3.85-1.29 5.1 0.35l2.53 3.32c1.02 1.35 0.9 3.25-0.3 4.45l-1.23 1.23c0.04 0.1 0.1 0.26 0.21 0.45 0.32 0.58 0.9 1.33 1.66 2.1 0.76 0.75 1.51 1.33 2.09 1.65 0.2 0.1 0.34 0.17 0.45 0.21l1.23-1.23c1.2-1.2 3.1-1.32 4.45-0.3l3.32 2.53c1.64 1.25 1.8 3.64 0.35 5.1l-0.5 0.5c-1.84 1.83-4.57 2.84-7.2 1.94-3.28-1.13-6.37-3-8.98-5.62-2.62-2.61-4.5-5.7-5.62-8.98-0.9-2.63 0.1-5.36 1.94-7.2l0.5-0.5Zm10.7 13.8h-0.02 0.01Zm-4.64-4.63v0.01-0.01Z"/>
<path
android:fillColor="#FF3B45FD"
android:pathData="M29.5 28.5c0-0.83 0.67-1.5 1.5-1.5h7c0.83 0 1.5 0.67 1.5 1.5S38.83 30 38 30h-7c-0.83 0-1.5-0.67-1.5-1.5Z"/>
<path
android:fillColor="#FFC3C6FA"
android:pathData="M107.5 31c0-3.87 3.13-7 7-7h22c3.87 0 7 3.13 7 7v58c0 3.87-3.13 7-7 7h-22c-3.87 0-7-3.13-7-7V31Z"/>
<path
android:fillColor="#FF3B45FD"
android:pathData="M105 89V31c0-5.25 4.25-9.5 9.5-9.5V24c-3.87 0-7 3.13-7 7v58c0 3.75 2.94 6.8 6.64 7h0.36 22c3.87 0 7-3.13 7-7V31c0-3.87-3.13-7-7-7v-2.5c5.25 0 9.5 4.25 9.5 9.5v58c0 5.25-4.25 9.5-9.5 9.5h-22c-5.25 0-9.5-4.25-9.5-9.5Zm31.5-67.5V24h-22v-2.5h22Z"/>
<path
android:fillColor="#FFDEE0FC"
android:pathData="M127.42 24c-1.69 10.66-9.61 19.25-19.92 21.9V31c0-3.87 3.13-7 7-7h12.92Z"/>
<path
android:fillColor="#FFA9ADF8"
android:pathData="M139.86 24.86c2.17 1.19 3.64 3.5 3.64 6.14v58c0 3.87-3.13 7-7 7h-22c-1.6 0-3.08-0.54-4.27-1.45 1.46-2.02 3.83-3.34 6.5-3.34h15c4.43 0 8-3.58 8-8V26.23c0-0.47 0.05-0.93 0.13-1.37Z"/>
<path
android:fillColor="#FF3B45FD"
android:fillType="evenOdd"
android:pathData="M116.46 49.18c1.46-1.45 3.86-1.29 5.1 0.35l2.53 3.32c1.02 1.35 0.9 3.25-0.3 4.45l-1.23 1.23c0.04 0.1 0.1 0.26 0.21 0.45 0.32 0.58 0.9 1.33 1.66 2.1 0.76 0.75 1.51 1.33 2.09 1.65 0.2 0.1 0.34 0.17 0.45 0.21l1.23-1.23c1.2-1.2 3.1-1.32 4.45-0.3l3.32 2.53c1.64 1.25 1.8 3.64 0.35 5.1l-0.5 0.5c-1.84 1.83-4.57 2.84-7.2 1.94-3.28-1.13-6.37-3-8.98-5.62-2.62-2.61-4.5-5.7-5.62-8.98-0.9-2.63 0.1-5.36 1.94-7.2l0.5-0.5Zm10.7 13.8h-0.02 0.01Zm-4.64-4.63v0.01-0.01Z"/>
<path
android:fillColor="#FF3B45FD"
android:pathData="M120.5 28.5c0-0.83 0.67-1.5 1.5-1.5h7c0.83 0 1.5 0.67 1.5 1.5S129.83 30 129 30h-7c-0.83 0-1.5-0.67-1.5-1.5Z"/>
<path
android:fillColor="#FFFFFFFF"
android:fillType="evenOdd"
android:pathData="M28.93 50.76c-0.5-0.65-1.45-0.71-2.02-0.14l-0.51 0.51c-1.42 1.42-2.04 3.37-1.45 5.09 1.03 3 2.74 5.8 5.13 8.2 2.4 2.4 5.2 4.1 8.2 5.13 1.72 0.59 3.67-0.03 5.09-1.45l0.5-0.5c0.58-0.58 0.52-1.54-0.13-2.03l-3.33-2.53c-0.54-0.4-1.29-0.36-1.77 0.12L37.2 64.6c-0.48 0.48-1.11 0.44-1.47 0.37-0.4-0.07-0.8-0.25-1.2-0.47-0.78-0.43-1.68-1.14-2.54-2-0.85-0.85-1.56-1.75-2-2.54-0.2-0.38-0.39-0.8-0.46-1.2-0.07-0.35-0.11-0.98 0.37-1.46l1.45-1.45c0.48-0.48 0.53-1.23 0.12-1.77l-2.53-3.33Z"/>
<path
android:fillColor="#FFFFFFFF"
android:fillType="evenOdd"
android:pathData="M119.93 50.76c-0.5-0.65-1.45-0.71-2.02-0.14l-0.51 0.51c-1.42 1.42-2.04 3.37-1.45 5.09 1.03 3 2.74 5.8 5.13 8.2 2.4 2.4 5.2 4.1 8.2 5.13 1.72 0.59 3.67-0.03 5.09-1.45l0.5-0.5c0.58-0.58 0.52-1.54-0.13-2.03l-3.33-2.53c-0.54-0.4-1.29-0.36-1.77 0.12l-1.44 1.45c-0.49 0.48-1.12 0.44-1.48 0.37-0.4-0.07-0.8-0.25-1.2-0.47-0.78-0.43-1.68-1.14-2.53-2-0.86-0.85-1.57-1.75-2-2.54-0.22-0.38-0.4-0.8-0.47-1.2-0.07-0.35-0.11-0.98 0.37-1.46l1.45-1.45c0.48-0.48 0.53-1.23 0.12-1.77l-2.53-3.33Z"/>
</vector>
@@ -0,0 +1,40 @@
<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:pathData="M29 19c0-3.87 3.13-7 7-7h22c3.87 0 7 3.13 7 7v58c0 3.87-3.13 7-7 7H36c-3.87 0-7-3.13-7-7V19Z"/>
<path
android:fillColor="#FF3B45FD"
android:pathData="M26.5 77V19c0-5.25 4.25-9.5 9.5-9.5V12c-3.87 0-7 3.13-7 7v58c0 3.75 2.94 6.8 6.64 7H36h22c3.87 0 7-3.13 7-7V19c0-3.87-3.13-7-7-7V9.5c5.25 0 9.5 4.25 9.5 9.5v58c0 5.25-4.25 9.5-9.5 9.5H36c-5.25 0-9.5-4.25-9.5-9.5ZM58 9.5V12H36V9.5h22Z"/>
<path
android:fillColor="#FFDEE0FC"
android:pathData="M48.92 12C47.23 22.66 39.3 31.25 29 33.9V19c0-3.87 3.13-7 7-7h12.92Z"/>
<path
android:fillColor="#FFA9ADF8"
android:pathData="M61.36 12.86C63.53 14.05 65 16.36 65 19v58c0 3.87-3.13 7-7 7H36c-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:fillType="evenOdd"
android:pathData="M37.96 37.18c1.46-1.45 3.85-1.29 5.1 0.35l2.53 3.32c1.02 1.35 0.9 3.25-0.3 4.45l-1.23 1.23c0.04 0.1 0.1 0.26 0.21 0.45 0.32 0.58 0.9 1.33 1.66 2.1 0.76 0.75 1.51 1.33 2.09 1.65 0.2 0.1 0.34 0.17 0.45 0.21l1.23-1.23c1.2-1.2 3.1-1.32 4.45-0.3l3.32 2.53c1.64 1.25 1.8 3.64 0.35 5.1l-0.5 0.5c-1.84 1.83-4.57 2.84-7.2 1.94-3.28-1.13-6.37-3-8.98-5.62-2.62-2.61-4.5-5.7-5.62-8.98-0.9-2.63 0.1-5.36 1.94-7.2l0.5-0.5Zm10.7 13.8h-0.02 0.01Zm-4.64-4.63v0.01-0.01Z"/>
<path
android:fillColor="#FF3B45FD"
android:pathData="M42 16.5c0-0.83 0.67-1.5 1.5-1.5h7c0.83 0 1.5 0.67 1.5 1.5S51.33 18 50.5 18h-7c-0.83 0-1.5-0.67-1.5-1.5Z"/>
<path
android:fillColor="#FFFFFFFF"
android:fillType="evenOdd"
android:pathData="M41.43 38.76c-0.5-0.65-1.45-0.71-2.02-0.14l-0.51 0.51c-1.42 1.42-2.04 3.37-1.45 5.09 1.03 3 2.74 5.8 5.13 8.2 2.4 2.4 5.2 4.1 8.2 5.13 1.72 0.59 3.67-0.03 5.09-1.45l0.5-0.5c0.58-0.58 0.52-1.54-0.13-2.03l-3.33-2.53c-0.54-0.4-1.29-0.36-1.77 0.12L49.7 52.6c-0.48 0.48-1.11 0.44-1.47 0.37-0.4-0.07-0.8-0.25-1.2-0.47-0.78-0.43-1.68-1.14-2.54-2-0.85-0.85-1.56-1.75-2-2.54-0.2-0.38-0.39-0.8-0.46-1.2-0.07-0.35-0.11-0.98 0.37-1.46l1.45-1.45c0.48-0.48 0.53-1.23 0.12-1.77l-2.53-3.33Z"/>
<path
android:fillColor="#FF686FDB"
android:fillType="evenOdd"
android:pathData="M68 64c-7.73 0-14 6.27-14 14s6.27 14 14 14 14-6.27 14-14-6.27-14-14-14Z"/>
<path
android:fillColor="#FF4B53DE"
android:pathData="M68 64c7.73 0 14 6.27 14 14s-6.27 14-14 14-14-6.27-14-14 6.27-14 14-14Zm0 2c-6.63 0-12 5.37-12 12s5.37 12 12 12 12-5.37 12-12-5.37-12-12-12Z"/>
<path
android:fillColor="#FFFFFFFF"
android:fillType="evenOdd"
android:pathData="M73.54 74.54c0.5-0.51 0.5-1.33 0-1.84-0.51-0.5-1.33-0.5-1.84 0l-3.58 3.58-3.58-3.58c-0.51-0.5-1.33-0.5-1.84 0-0.5 0.5-0.5 1.33 0 1.84l3.58 3.58-3.58 3.58c-0.5 0.5-0.5 1.33 0 1.84 0.5 0.5 1.33 0.5 1.84 0l3.58-3.58 3.58 3.58c0.5 0.5 1.33 0.5 1.84 0 0.5-0.51 0.5-1.33 0-1.84l-3.58-3.58 3.58-3.58Z"/>
</vector>
@@ -1,19 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="112dp"
android:height="112dp"
android:viewportWidth="112"
android:viewportHeight="112">
<path
android:pathData="M56,56m-56,0a56,56 0,1 1,112 0a56,56 0,1 1,-112 0"
android:fillColor="#000000"
android:fillAlpha="0.05"/>
<path
android:pathData="M69.3,16.1L42.9,16C39.1,16 36,19.1 36,22.9L35.9,89C35.9,92.8 39,95.9 42.8,95.9L69.1,96C72.9,96 76,92.9 76,89.1L76.2,23C76.2,19.2 73.1,16.1 69.3,16.1ZM72.8,89.1C72.8,91.1 71.1,92.8 69.1,92.8L42.7,92.7C40.7,92.7 39,91 39,89L39.1,22.9C39.1,20.9 40.8,19.2 42.8,19.2L69.2,19.3C71.2,19.3 72.9,21 72.9,23L72.8,89.1Z"
android:fillColor="#B9B9B9"/>
<path
android:pathData="M72.8,89.1C72.8,91.1 71.1,92.8 69.1,92.8L42.7,92.7C40.7,92.7 39,91 39,89L39.1,22.9C39.1,20.9 40.8,19.2 42.8,19.2L69.2,19.3C71.2,19.3 72.9,21 72.9,23L72.8,89.1Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M46.31,47.42C45.72,48.25 45,49.8 46,52.83C47.15,55.825 48.917,58.546 51.186,60.814C53.454,63.083 56.175,64.85 59.17,66C62.17,67.07 63.75,66.3 64.58,65.71C65.309,65.209 65.887,64.517 66.25,63.71C66.496,63.238 66.562,62.692 66.435,62.175C66.308,61.658 65.997,61.204 65.56,60.9L62.13,58.5C61.701,58.195 61.176,58.055 60.653,58.106C60.129,58.158 59.641,58.397 59.28,58.78C58.89,59.19 58.64,59.47 58.28,59.78C58.102,59.996 57.849,60.138 57.571,60.177C57.293,60.216 57.011,60.149 56.78,59.99C55.86,59.307 54.991,58.558 54.18,57.75C53.393,56.943 52.664,56.081 52,55.17C51.841,54.939 51.774,54.657 51.813,54.379C51.852,54.101 51.994,53.848 52.21,53.67C52.56,53.36 52.84,53.11 53.21,52.72C53.593,52.359 53.832,51.871 53.884,51.347C53.935,50.824 53.795,50.299 53.49,49.87L51.12,46.44C50.816,46.003 50.362,45.692 49.845,45.565C49.328,45.438 48.782,45.504 48.31,45.75C47.503,46.113 46.811,46.691 46.31,47.42Z"
android:fillColor="#2C6BED"/>
</vector>
+27
View File
@@ -5728,6 +5728,33 @@
<!-- Confirmation button to dismiss number changed dialog -->
<string name="ChangeNumber__okay">Okay</string>
<!-- ChangeNumberPostRegistrationWaitSheet: title of the sheet shown when the user tries to change number too soon after registering -->
<string name="ChangeNumberPostRegistrationWaitSheet__title">Can\'t change number</string>
<!-- ChangeNumberPostRegistrationWaitSheet: explanation body shown above the wait-time line -->
<string name="ChangeNumberPostRegistrationWaitSheet__body">Because you recently registered this account, you can\'t change phone numbers right now. A short waiting period helps protect your account.</string>
<!-- ChangeNumberPostRegistrationWaitSheet: remaining-wait line when wait is one hour or more, %1$d is the number of hours, rounded up -->
<plurals name="ChangeNumberPostRegistrationWaitSheet__try_again_in_hours">
<item quantity="one">Try again in %1$d hour.</item>
<item quantity="other">Try again in %1$d hours.</item>
</plurals>
<!-- ChangeNumberPostRegistrationWaitSheet: remaining-wait line when wait is under one hour, %1$d is the number of minutes, rounded up -->
<plurals name="ChangeNumberPostRegistrationWaitSheet__try_again_in_minutes">
<item quantity="one">Try again in %1$d minute.</item>
<item quantity="other">Try again in %1$d minutes.</item>
</plurals>
<!-- ChangeNumberPostRegistrationWaitSheet: dismiss button -->
<string name="ChangeNumberPostRegistrationWaitSheet__ok">OK</string>
<!-- ChangeNumberEnterCodeFragment: rate-limit dialog message when retry-after is one hour or more, %1$d is hours rounded up -->
<plurals name="ChangeNumberEnterCodeFragment__too_many_attempts_try_again_in_hours">
<item quantity="one">You have made too many attempts. Please try again in %1$d hour.</item>
<item quantity="other">You have made too many attempts. Please try again in %1$d hours.</item>
</plurals>
<!-- ChangeNumberEnterCodeFragment: rate-limit dialog message when retry-after is under one hour, %1$d is minutes rounded up -->
<plurals name="ChangeNumberEnterCodeFragment__too_many_attempts_try_again_in_minutes">
<item quantity="one">You have made too many attempts. Please try again in %1$d minute.</item>
<item quantity="other">You have made too many attempts. Please try again in %1$d minutes.</item>
</plurals>
<!-- ChangeNumberEnterPhoneNumberFragment -->
<string name="ChangeNumberEnterPhoneNumberFragment__change_number">Change Number</string>
<string name="ChangeNumberEnterPhoneNumberFragment__your_old_number">Your old number</string>