Improve handling of unregistered states in profile screen.

This commit is contained in:
Greyson Parrelli
2024-03-12 11:37:59 -04:00
committed by Cody Henthorne
parent ce778be895
commit 5027159ed8
12 changed files with 162 additions and 111 deletions

View File

@@ -1,25 +0,0 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
public class PushRegistrationReminder extends Reminder {
public PushRegistrationReminder(final Context context) {
super(R.string.reminder_header_push_title, R.string.reminder_header_push_text);
setOkListener(v -> context.startActivity(RegistrationNavigationActivity.newIntentForReRegistration(context)));
}
@Override
public boolean isDismissable() {
return false;
}
public static boolean isEligible() {
return !SignalStore.account().isRegistered();
}
}

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -26,6 +27,6 @@ public class UnauthorizedReminder extends Reminder {
}
public static boolean isEligible(Context context) {
return TextSecurePreferences.isUnauthorizedReceived(context);
return TextSecurePreferences.isUnauthorizedReceived(context) || !SignalStore.account().isRegistered();
}
}

View File

@@ -160,7 +160,7 @@ class AppSettingsFragment : DSLSettingsFragment(
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_deviceActivity)
},
isEnabled = state.isDeprecatedOrUnregistered()
isEnabled = state.isRegisteredAndUpToDate()
)
if (state.allowUserToGoToDonationManagementScreen) {
@@ -197,7 +197,7 @@ class AppSettingsFragment : DSLSettingsFragment(
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_chatsSettingsFragment)
},
isEnabled = state.isDeprecatedOrUnregistered()
isEnabled = state.isRegisteredAndUpToDate()
)
clickPref(
@@ -206,7 +206,7 @@ class AppSettingsFragment : DSLSettingsFragment(
onClick = {
findNavController().safeNavigate(AppSettingsFragmentDirections.actionAppSettingsFragmentToStoryPrivacySettings(R.string.preferences__stories))
},
isEnabled = state.isDeprecatedOrUnregistered()
isEnabled = state.isRegisteredAndUpToDate()
)
clickPref(
@@ -215,7 +215,7 @@ class AppSettingsFragment : DSLSettingsFragment(
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_notificationsSettingsFragment)
},
isEnabled = state.isDeprecatedOrUnregistered()
isEnabled = state.isRegisteredAndUpToDate()
)
clickPref(
@@ -224,7 +224,7 @@ class AppSettingsFragment : DSLSettingsFragment(
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_privacySettingsFragment)
},
isEnabled = state.isDeprecatedOrUnregistered()
isEnabled = state.isRegisteredAndUpToDate()
)
clickPref(

View File

@@ -10,7 +10,7 @@ data class AppSettingsState(
val userUnregistered: Boolean,
val clientDeprecated: Boolean
) {
fun isDeprecatedOrUnregistered(): Boolean {
return !(userUnregistered || clientDeprecated)
fun isRegisteredAndUpToDate(): Boolean {
return !userUnregistered && !clientDeprecated
}
}

View File

@@ -24,7 +24,7 @@ class AppSettingsViewModel(
0,
SignalStore.donationsValues().getExpiredGiftBadge() != null,
SignalStore.donationsValues().isLikelyASustainer() || InAppDonations.hasAtLeastOnePaymentMethodAvailable(),
TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication()),
TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication()) || !SignalStore.account().isRegistered,
SignalStore.misc().isClientDeprecated
)
)
@@ -54,7 +54,12 @@ class AppSettingsViewModel(
}
fun refreshDeprecatedOrUnregistered() {
store.update { it.copy(clientDeprecated = SignalStore.misc().isClientDeprecated, userUnregistered = TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication())) }
store.update {
it.copy(
clientDeprecated = SignalStore.misc().isClientDeprecated,
userUnregistered = TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication()) || !SignalStore.account().isRegistered
)
}
}
fun refreshExpiredGiftBadge() {

View File

@@ -12,6 +12,7 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.signal.core.util.AppUtil
import org.signal.core.util.ThreadUtil
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.concurrent.SimpleTask
import org.signal.core.util.logging.Log
@@ -24,6 +25,7 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.app.privacy.advanced.AdvancedPrivacySettingsRepository
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.database.JobDatabase
import org.thoughtcrime.securesms.database.LocalMetricsDatabase
@@ -140,6 +142,14 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
}
)
clickPref(
title = DSLSettingsText.from("Unregister"),
summary = DSLSettingsText.from("This will unregister your account without deleting it."),
onClick = {
onUnregisterClicked()
}
)
dividerPref()
sectionHeaderPref(DSLSettingsText.from("Miscellaneous"))
@@ -803,6 +813,32 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
}
}
private fun onUnregisterClicked() {
MaterialAlertDialogBuilder(requireContext())
.setTitle("Unregister?")
.setMessage("Are you sure? You'll have to re-register to use Signal again -- no promises that the process will go smoothly.")
.setPositiveButton(android.R.string.ok) { _, _ ->
AdvancedPrivacySettingsRepository(requireContext()).disablePushMessages {
ThreadUtil.runOnMain {
when (it) {
AdvancedPrivacySettingsRepository.DisablePushMessagesResult.SUCCESS -> {
SignalStore.account().setRegistered(false)
SignalStore.registrationValues().clearRegistrationComplete()
SignalStore.registrationValues().clearHasUploadedProfile()
Toast.makeText(context, "Unregistered!", Toast.LENGTH_SHORT).show()
}
AdvancedPrivacySettingsRepository.DisablePushMessagesResult.NETWORK_ERROR -> {
Toast.makeText(context, "Network error!", Toast.LENGTH_SHORT).show()
}
}
}
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun copyPaymentsDataToClipboard() {
MaterialAlertDialogBuilder(requireContext())
.setMessage(

View File

@@ -6,16 +6,12 @@ import android.content.Intent
import android.content.IntentFilter
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
import android.text.SpannableStringBuilder
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.widget.TextViewCompat
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.SignalProgressDialog
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
@@ -24,7 +20,6 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.ViewUtil
@@ -107,40 +102,6 @@ class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences
private fun getConfiguration(state: AdvancedPrivacySettingsState): DSLConfiguration {
return configure {
switchPref(
title = DSLSettingsText.from(R.string.preferences__signal_messages_and_calls),
summary = DSLSettingsText.from(getPushToggleSummary(state.isPushEnabled)),
isChecked = state.isPushEnabled
) {
if (state.isPushEnabled) {
val builder = MaterialAlertDialogBuilder(requireContext()).apply {
setMessage(R.string.ApplicationPreferencesActivity_disable_signal_messages_and_calls_by_unregistering)
setNegativeButton(android.R.string.cancel, null)
setPositiveButton(
android.R.string.ok
) { _, _ -> viewModel.disablePushMessages() }
}
val icon: Drawable = requireNotNull(ContextCompat.getDrawable(builder.context, R.drawable.symbol_info_24))
icon.setBounds(0, 0, ViewUtil.dpToPx(32), ViewUtil.dpToPx(32))
val title = TextView(builder.context)
val padding = ViewUtil.dpToPx(16)
title.setText(R.string.ApplicationPreferencesActivity_disable_signal_messages_and_calls)
title.setPadding(padding, padding, padding, padding)
title.compoundDrawablePadding = padding / 2
TextViewCompat.setTextAppearance(title, R.style.TextAppearance_Signal_Title2_MaterialDialog)
TextViewCompat.setCompoundDrawablesRelative(title, icon, null, null, null)
builder
.setCustomTitle(title)
.setOnDismissListener { viewModel.refresh() }
.show()
} else {
startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext()))
}
}
switchPref(
title = DSLSettingsText.from(R.string.preferences_advanced__always_relay_calls),
summary = DSLSettingsText.from(R.string.preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address),

View File

@@ -38,25 +38,6 @@ class AdvancedPrivacySettingsViewModel(
)
}
fun disablePushMessages() {
store.update { getState().copy(showProgressSpinner = true) }
repository.disablePushMessages {
when (it) {
AdvancedPrivacySettingsRepository.DisablePushMessagesResult.SUCCESS -> {
SignalStore.account().setRegistered(false)
SignalStore.registrationValues().clearRegistrationComplete()
SignalStore.registrationValues().clearHasUploadedProfile()
}
AdvancedPrivacySettingsRepository.DisablePushMessagesResult.NETWORK_ERROR -> {
singleEvents.postValue(Event.DISABLE_PUSH_FAILED)
}
}
store.update { getState().copy(showProgressSpinner = false) }
}
}
fun setAlwaysRelayCalls(enabled: Boolean) {
sharedPreferences.edit().putBoolean(TextSecurePreferences.ALWAYS_RELAY_CALLS_PREF, enabled).apply()
refresh()

View File

@@ -105,7 +105,6 @@ import org.thoughtcrime.securesms.components.reminder.CdsTemporaryErrorReminder;
import org.thoughtcrime.securesms.components.reminder.DozeReminder;
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.reminder.OutdatedBuildReminder;
import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.reminder.Reminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
@@ -1047,8 +1046,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
return Optional.of(new ServiceOutageReminder());
} else if (OutdatedBuildReminder.isEligible()) {
return Optional.of(new OutdatedBuildReminder(context));
} else if (PushRegistrationReminder.isEligible()) {
return Optional.of((new PushRegistrationReminder(context)));
} else if (DozeReminder.isEligible(context)) {
return Optional.of(new DozeReminder(context));
} else if (CdsTemporaryErrorReminder.isEligible()) {

View File

@@ -37,7 +37,9 @@ import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.profiles.manage.EditProfileViewModel.AvatarState
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameDeleteResult
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity
import org.thoughtcrime.securesms.util.NameUtil.getAbbreviation
import org.thoughtcrime.securesms.util.PlayStoreUtil
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog
@@ -55,6 +57,8 @@ class EditProfileFragment : LoggingFragment() {
private lateinit var binding: EditProfileFragmentBinding
private lateinit var disposables: LifecycleDisposable
private val DISABLED_ALPHA = 0.4f
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = EditProfileFragmentBinding.inflate(inflater, container, false)
return binding.root
@@ -75,11 +79,27 @@ class EditProfileFragment : LoggingFragment() {
initializeViewModel()
binding.toolbar.setNavigationOnClickListener { requireActivity().finish() }
binding.manageProfileEditPhoto.setOnClickListener { onEditAvatarClicked() }
binding.manageProfileNameContainer.setOnClickListener { v: View -> findNavController(v).safeNavigate(EditProfileFragmentDirections.actionManageProfileName()) }
binding.manageProfileEditPhoto.setOnClickListener {
if (!viewModel.isRegisteredAndUpToDate) {
onClickWhenUnregisteredOrDeprecated()
} else {
onEditAvatarClicked()
}
}
binding.manageProfileNameContainer.setOnClickListener { v: View ->
if (!viewModel.isRegisteredAndUpToDate) {
onClickWhenUnregisteredOrDeprecated()
} else {
findNavController(v).safeNavigate(EditProfileFragmentDirections.actionManageProfileName())
}
}
binding.manageProfileUsernameContainer.setOnClickListener { v: View ->
if (SignalStore.account().username != null) {
if (!viewModel.isRegisteredAndUpToDate) {
onClickWhenUnregisteredOrDeprecated()
} else if (SignalStore.account().username != null) {
MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Signal_MaterialAlertDialog_List)
.setItems(R.array.username_edit_entries) { _: DialogInterface?, w: Int ->
when (w) {
@@ -94,10 +114,18 @@ class EditProfileFragment : LoggingFragment() {
}
}
binding.manageProfileAboutContainer.setOnClickListener { v: View -> findNavController(v).safeNavigate(EditProfileFragmentDirections.actionManageAbout()) }
binding.manageProfileAboutContainer.setOnClickListener { v: View ->
if (!viewModel.isRegisteredAndUpToDate) {
onClickWhenUnregisteredOrDeprecated()
} else {
findNavController(v).safeNavigate(EditProfileFragmentDirections.actionManageAbout())
}
}
parentFragmentManager.setFragmentResultListener(AvatarPickerFragment.REQUEST_KEY_SELECT_AVATAR, viewLifecycleOwner) { _: String?, bundle: Bundle ->
if (bundle.getBoolean(AvatarPickerFragment.SELECT_AVATAR_CLEAR)) {
if (!viewModel.isRegisteredAndUpToDate) {
onClickWhenUnregisteredOrDeprecated()
} else if (bundle.getBoolean(AvatarPickerFragment.SELECT_AVATAR_CLEAR)) {
viewModel.onAvatarSelected(requireContext(), null)
} else {
val result = bundle.getParcelable<Media>(AvatarPickerFragment.SELECT_AVATAR_MEDIA)
@@ -113,7 +141,9 @@ class EditProfileFragment : LoggingFragment() {
}
binding.manageProfileBadgesContainer.setOnClickListener { v: View ->
if (Recipient.self().badges.isEmpty()) {
if (!viewModel.isRegisteredAndUpToDate) {
onClickWhenUnregisteredOrDeprecated()
} else if (Recipient.self().badges.isEmpty()) {
show(parentFragmentManager)
} else {
findNavController(v).safeNavigate(EditProfileFragmentDirections.actionManageProfileFragmentToBadgeManageFragment())
@@ -121,10 +151,14 @@ class EditProfileFragment : LoggingFragment() {
}
binding.manageProfileAvatar.setOnClickListener {
startActivity(
AvatarPreviewActivity.intentFromRecipientId(requireContext(), Recipient.self().id),
AvatarPreviewActivity.createTransitionBundle(requireActivity(), binding.manageProfileAvatar)
)
if (!viewModel.isRegisteredAndUpToDate) {
onClickWhenUnregisteredOrDeprecated()
} else {
startActivity(
AvatarPreviewActivity.intentFromRecipientId(requireContext(), Recipient.self().id),
AvatarPreviewActivity.createTransitionBundle(requireActivity(), binding.manageProfileAvatar)
)
}
}
}
@@ -154,6 +188,10 @@ class EditProfileFragment : LoggingFragment() {
} else {
Glide.with(this).load(null as Drawable?).into(binding.manageProfileAvatar)
}
binding.manageProfileAvatar.alpha = if (viewModel.isRegisteredAndUpToDate) 1.0f else DISABLED_ALPHA
binding.manageProfileAvatarInitials.alpha = if (viewModel.isRegisteredAndUpToDate) 1.0f else DISABLED_ALPHA
binding.manageProfileEditPhoto.isEnabled = viewModel.isRegisteredAndUpToDate
}
private fun presentAvatarPlaceholder(avatarState: AvatarState) {
@@ -205,6 +243,9 @@ class EditProfileFragment : LoggingFragment() {
} else {
binding.manageProfileName.text = profileName.toString()
}
binding.manageProfileName.isEnabled = viewModel.isRegisteredAndUpToDate
binding.manageProfileNameIcon.alpha = if (viewModel.isRegisteredAndUpToDate) 1.0f else DISABLED_ALPHA
}
private fun presentUsername(username: String?) {
@@ -244,6 +285,9 @@ class EditProfileFragment : LoggingFragment() {
binding.usernameLinkContainer.visibility = View.GONE
binding.usernameInfoText.setText(R.string.ManageProfileFragment__username_footer_no_username)
}
binding.manageProfileUsername.isEnabled = viewModel.isRegisteredAndUpToDate
binding.manageProfileUsernameIcon.alpha = if (viewModel.isRegisteredAndUpToDate) 1.0f else DISABLED_ALPHA
}
private fun presentAbout(about: String?) {
@@ -252,6 +296,9 @@ class EditProfileFragment : LoggingFragment() {
} else {
binding.manageProfileAbout.text = about
}
binding.manageProfileAbout.isEnabled = viewModel.isRegisteredAndUpToDate
binding.manageProfileAboutIcon.alpha = if (viewModel.isRegisteredAndUpToDate) 1.0f else DISABLED_ALPHA
}
private fun presentAboutEmoji(aboutEmoji: String?) {
@@ -273,6 +320,14 @@ class EditProfileFragment : LoggingFragment() {
} else {
binding.manageProfileBadge.setBadge(null)
}
binding.manageProfileBadges.isEnabled = viewModel.isRegisteredAndUpToDate
binding.manageProfileBadge.alpha = if (viewModel.isRegisteredAndUpToDate) 1.0f else DISABLED_ALPHA
binding.manageProfileBadgesIcon.alpha = if (viewModel.isRegisteredAndUpToDate) 1.0f else DISABLED_ALPHA
if (!viewModel.isRegisteredAndUpToDate) {
binding.manageProfileBadge.setOnClickListener { onClickWhenUnregisteredOrDeprecated() }
}
}
private fun presentEvent(event: EditProfileViewModel.Event) {
@@ -316,4 +371,28 @@ class EditProfileFragment : LoggingFragment() {
UsernameDeleteResult.NETWORK_ERROR -> Snackbar.make(requireView(), R.string.ManageProfileFragment__couldnt_delete_username, Snackbar.LENGTH_SHORT).show()
}
}
private fun onClickWhenUnregisteredOrDeprecated() {
if (viewModel.isDeprecated) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.EditProfileFragment_deprecated_dialog_title)
.setMessage(R.string.EditProfileFragment_deprecated_dialog_body)
.setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
.setPositiveButton(R.string.EditProfileFragment_deprecated_dialog_update_button) { d, _ ->
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext())
d.dismiss()
}
.show()
} else {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.EditProfileFragment_unregistered_dialog_title)
.setMessage(R.string.EditProfileFragment_unregistered_dialog_body)
.setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
.setPositiveButton(R.string.EditProfileFragment_unregistered_dialog_reregister_button) { d, _ ->
startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext()))
d.dismiss()
}
.show()
}
}
}

View File

@@ -24,8 +24,8 @@ import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.whispersystems.signalservice.api.util.StreamDetails;
@@ -107,6 +107,14 @@ class EditProfileViewModel extends ViewModel {
return UsernameRepository.deleteUsernameAndLink().observeOn(AndroidSchedulers.mainThread());
}
public boolean isRegisteredAndUpToDate() {
return !TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication()) && SignalStore.account().isRegistered() && !SignalStore.misc().isClientDeprecated();
}
public boolean isDeprecated() {
return SignalStore.misc().isClientDeprecated();
}
public void onAvatarSelected(@NonNull Context context, @Nullable Media media) {
previousAvatar = internalAvatarState.getValue() != null ? internalAvatarState.getValue().getAvatar() : null;

View File

@@ -45,8 +45,6 @@
<string name="ApplicationPreferencesActivity_disable_passphrase">Disable passphrase?</string>
<string name="ApplicationPreferencesActivity_this_will_permanently_unlock_signal_and_message_notifications">This will permanently unlock Signal and message notifications.</string>
<string name="ApplicationPreferencesActivity_disable">Disable</string>
<string name="ApplicationPreferencesActivity_disable_signal_messages_and_calls">Disable Signal messages and calls?</string>
<string name="ApplicationPreferencesActivity_disable_signal_messages_and_calls_by_unregistering">Disable Signal messages and calls by unregistering from the server. You will need to re-register your phone number to use them again in the future.</string>
<string name="ApplicationPreferencesActivity_error_connecting_to_server">Error connecting to server!</string>
<string name="ApplicationPreferencesActivity_pins_are_required_for_registration_lock">PINs are required for registration lock. To disable PINs, please first disable registration lock.</string>
<string name="ApplicationPreferencesActivity_pin_created">PIN created.</string>
@@ -2928,6 +2926,18 @@
<string name="EditProfileFragment__group_name">Group name</string>
<string name="EditProfileFragment__group_description">Group description</string>
<string name="EditProfileFragment__support_link" translatable="false">https://support.signal.org/hc/articles/360007459591</string>
<!-- The title of a dialog prompting user to update to the latest version of Signal. -->
<string name="EditProfileFragment_deprecated_dialog_title">Update Signal</string>
<!-- The body of a dialog prompting user to update to the latest version of Signal. -->
<string name="EditProfileFragment_deprecated_dialog_body">This version of Signal has expired. Update now to continue using Signal.</string>
<!-- The button on a dialog prompting user to update to the latest version of Signal. When clicked, the user will be taken to the store to update their app. -->
<string name="EditProfileFragment_deprecated_dialog_update_button">Update</string>
<!-- The title of a dialog informing the user that they cannot use this app feature when they are unregistered. -->
<string name="EditProfileFragment_unregistered_dialog_title">Device not registered</string>
<!-- The body of a dialog informing the user that they cannot use this app feature when they are unregistered. -->
<string name="EditProfileFragment_unregistered_dialog_body">This device is no longer registered. Re-register to make changes to your account.</string>
<!-- The button on a dialog informing the user that they cannot use this app feature when they are unregistered. When clicked, the user will be taken to a screen to help them re-register. -->
<string name="EditProfileFragment_unregistered_dialog_reregister_button">Re-register</string>
<!-- EditProfileNameFragment -->
<string name="EditProfileNameFragment_your_name">Your name</string>
@@ -3666,8 +3676,6 @@
<string name="verify_display_fragment_context_menu__compare_with_clipboard">Compare with clipboard</string>
<!-- reminder_header -->
<string name="reminder_header_push_title">Enable Signal messages and calls</string>
<string name="reminder_header_push_text">Upgrade your communication experience.</string>
<string name="reminder_header_service_outage_text">Signal is experiencing technical difficulties. We are working hard to restore service as quickly as possible.</string>
<string name="reminder_header_progress">%1$d%%</string>
<!-- Body text of a banner that will show at the top of the chat list when we temporarily cannot process the user\'s contacts -->