From c2c153785810f6f6780426f52ee03685e13dadfc Mon Sep 17 00:00:00 2001 From: Clark Date: Fri, 5 May 2023 13:24:00 -0400 Subject: [PATCH] Disable interactions while user is unregistered or expired. --- .../reminder/UnauthorizedReminder.java | 11 ++- .../components/settings/DSLSettingsAdapter.kt | 1 + .../settings/app/AppSettingsFragment.kt | 94 +++++++++++++++++-- .../settings/app/AppSettingsState.kt | 10 +- .../settings/app/AppSettingsViewModel.kt | 9 +- .../app/account/AccountSettingsFragment.kt | 52 +++++++++- .../app/account/AccountSettingsState.kt | 10 +- .../app/account/AccountSettingsViewModel.kt | 6 +- .../ConversationSettingsFragment.kt | 18 +++- .../conversation/ConversationSettingsState.kt | 1 + .../ConversationSettingsViewModel.kt | 5 +- .../preferences/ButtonStripPreference.kt | 7 ++ .../preferences/LargeIconClickPreference.kt | 1 + .../ConversationParentFragment.java | 43 ++++++++- .../conversation/ConversationRepository.java | 5 +- .../conversation/ConversationSecurityInfo.kt | 4 +- .../ConversationListFragment.java | 9 +- .../securesms/groups/GroupManagerV1.java | 2 +- .../preferences/PaymentsHomeFragment.java | 3 + .../RecipientBottomSheetDialogFragment.java | 23 ++++- .../bottomsheet/RecipientDialogViewModel.java | 21 +++-- .../stories/landing/StoriesLandingFragment.kt | 68 ++++++++++++++ .../thoughtcrime/securesms/util/Dialogs.java | 23 +++++ ...on_tint_color_primary_enabled_selector.xml | 5 + .../main/res/layout/conversation_activity.xml | 7 ++ .../conversation_activity_logged_out_stub.xml | 27 ++++++ .../dsl_settings_fragment_with_reminder.xml | 30 ++++++ .../res/layout/recipient_bottom_sheet.xml | 10 +- .../res/layout/stories_landing_fragment.xml | 7 ++ .../layout/stories_landing_reminder_view.xml | 8 ++ app/src/main/res/values-night/dark_colors.xml | 1 + .../res/values-night/material3_colors.xml | 1 + app/src/main/res/values/core_colors.xml | 2 + app/src/main/res/values/ids.xml | 1 + app/src/main/res/values/light_colors.xml | 1 + app/src/main/res/values/material3_colors.xml | 1 + app/src/main/res/values/strings.xml | 44 ++++++++- 37 files changed, 527 insertions(+), 44 deletions(-) create mode 100644 app/src/main/res/color/icon_tint_color_primary_enabled_selector.xml create mode 100644 app/src/main/res/layout/conversation_activity_logged_out_stub.xml create mode 100644 app/src/main/res/layout/dsl_settings_fragment_with_reminder.xml create mode 100644 app/src/main/res/layout/stories_landing_reminder_view.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/reminder/UnauthorizedReminder.java b/app/src/main/java/org/thoughtcrime/securesms/components/reminder/UnauthorizedReminder.java index 9e942ce870..6a82dbd852 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/reminder/UnauthorizedReminder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/reminder/UnauthorizedReminder.java @@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.components.reminder; import android.content.Context; +import androidx.annotation.NonNull; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -9,12 +11,14 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; public class UnauthorizedReminder extends Reminder { public UnauthorizedReminder(final Context context) { - super(context.getString(R.string.UnauthorizedReminder_device_no_longer_registered), + super(null, context.getString(R.string.UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device)); setOkListener(v -> { context.startActivity(RegistrationNavigationActivity.newIntentForReRegistration(context)); }); + + addAction(new Action(context.getString(R.string.UnauthorizedReminder_reregister_action), R.id.reminder_action_re_register)); } @Override @@ -22,6 +26,11 @@ public class UnauthorizedReminder extends Reminder { return false; } + @Override + public @NonNull Importance getImportance() { + return Importance.ERROR; + } + public static boolean isEligible(Context context) { return TextSecurePreferences.isUnauthorizedReceived(context); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt index e2bd065d77..3882af86cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsAdapter.kt @@ -64,6 +64,7 @@ abstract class PreferenceViewHolder>(itemView: View) : Ma val icon = model.icon?.resolve(context) iconView.setImageDrawable(icon) iconView.visible = icon != null + iconView.alpha = if (model.isEnabled) 1f else 0.5f val iconEnd = model.iconEnd?.resolve(context) iconEndView?.setImageDrawable(iconEnd) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt index 5ed18119b3..279a78163b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt @@ -1,13 +1,22 @@ package org.thoughtcrime.securesms.components.settings.app +import android.os.Bundle import android.view.View import android.widget.TextView import android.widget.Toast +import androidx.annotation.IdRes import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.badges.BadgeImageView import org.thoughtcrime.securesms.components.AvatarImageView +import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder +import org.thoughtcrime.securesms.components.reminder.Reminder +import org.thoughtcrime.securesms.components.reminder.ReminderView +import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder import org.thoughtcrime.securesms.components.settings.DSLConfiguration import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon @@ -15,20 +24,37 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsText import org.thoughtcrime.securesms.components.settings.PreferenceModel import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder import org.thoughtcrime.securesms.components.settings.configure +import org.thoughtcrime.securesms.events.ReminderUpdateEvent import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity import org.thoughtcrime.securesms.util.FeatureFlags +import org.thoughtcrime.securesms.util.PlayStoreUtil import org.thoughtcrime.securesms.util.Util +import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder import org.thoughtcrime.securesms.util.navigation.safeNavigate +import org.thoughtcrime.securesms.util.views.Stub -class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__menu_settings) { +class AppSettingsFragment : DSLSettingsFragment( + titleId = R.string.text_secure_normal__menu_settings, + layoutId = R.layout.dsl_settings_fragment_with_reminder +) { private val viewModel: AppSettingsViewModel by viewModels() + private lateinit var reminderView: Stub + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + reminderView = ViewUtil.findStubById(view, R.id.reminder_stub) + + updateReminders() + } + override fun bindAdapter(adapter: MappingAdapter) { adapter.registerFactory(BioPreference::class.java, LayoutFactory(::BioPreferenceViewHolder, R.layout.bio_preference_item)) adapter.registerFactory(PaymentsPreference::class.java, LayoutFactory(::PaymentsPreferenceViewHolder, R.layout.dsl_payments_preference)) @@ -39,9 +65,60 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men } } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEvent(event: ReminderUpdateEvent?) { + updateReminders() + } + + private fun updateReminders() { + if (ExpiredBuildReminder.isEligible()) { + showReminder(ExpiredBuildReminder(context)) + } else if (UnauthorizedReminder.isEligible(context)) { + showReminder(UnauthorizedReminder(context)) + } else { + hideReminders() + } + viewModel.refreshDeprecatedOrUnregistered() + } + + private fun showReminder(reminder: Reminder) { + if (!reminderView.resolved()) { + reminderView.get().addOnLayoutChangeListener { _, _, top, _, bottom, _, _, _, _ -> + recyclerView?.setPadding(0, bottom - top, 0, 0) + } + recyclerView?.clipToPadding = false + } + reminderView.get().showReminder(reminder) + reminderView.get().setOnActionClickListener { reminderActionId: Int -> this.handleReminderAction(reminderActionId) } + } + + private fun hideReminders() { + if (reminderView.resolved()) { + reminderView.get().hide() + recyclerView?.clipToPadding = true + } + } + + private fun handleReminderAction(@IdRes reminderActionId: Int) { + when (reminderActionId) { + R.id.reminder_action_update_now -> { + PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()) + } + R.id.reminder_action_re_register -> { + startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext())) + } + } + } + override fun onResume() { super.onResume() viewModel.refreshExpiredGiftBadge() + EventBus.getDefault().register(this) + } + + override fun onPause() { + super.onPause() + EventBus.getDefault().unregister(this) } private fun getConfiguration(state: AppSettingsState): DSLConfiguration { @@ -75,7 +152,8 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men icon = DSLSettingsIcon.from(R.drawable.symbol_devices_24), onClick = { findNavController().safeNavigate(R.id.action_appSettingsFragment_to_deviceActivity) - } + }, + isEnabled = state.isDeprecatedOrUnregistered() ) if (state.allowUserToGoToDonationManagementScreen) { @@ -111,7 +189,8 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men icon = DSLSettingsIcon.from(R.drawable.symbol_chat_24), onClick = { findNavController().safeNavigate(R.id.action_appSettingsFragment_to_chatsSettingsFragment) - } + }, + isEnabled = state.isDeprecatedOrUnregistered() ) clickPref( @@ -119,7 +198,8 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men icon = DSLSettingsIcon.from(R.drawable.symbol_stories_24), onClick = { findNavController().safeNavigate(AppSettingsFragmentDirections.actionAppSettingsFragmentToStoryPrivacySettings(R.string.preferences__stories)) - } + }, + isEnabled = state.isDeprecatedOrUnregistered() ) clickPref( @@ -127,7 +207,8 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men icon = DSLSettingsIcon.from(R.drawable.symbol_bell_24), onClick = { findNavController().safeNavigate(R.id.action_appSettingsFragment_to_notificationsSettingsFragment) - } + }, + isEnabled = state.isDeprecatedOrUnregistered() ) clickPref( @@ -135,7 +216,8 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men icon = DSLSettingsIcon.from(R.drawable.symbol_lock_24), onClick = { findNavController().safeNavigate(R.id.action_appSettingsFragment_to_privacySettingsFragment) - } + }, + isEnabled = state.isDeprecatedOrUnregistered() ) clickPref( diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt index ac77c422ac..51ea0727c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt @@ -6,5 +6,11 @@ data class AppSettingsState( val self: Recipient, val unreadPaymentsCount: Int, val hasExpiredGiftBadge: Boolean, - val allowUserToGoToDonationManagementScreen: Boolean -) + val allowUserToGoToDonationManagementScreen: Boolean, + val userUnregistered: Boolean, + val clientDeprecated: Boolean +) { + fun isDeprecatedOrUnregistered(): Boolean { + return !(userUnregistered || clientDeprecated) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt index e578a923fb..ab19ce97e2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.livedata.Store class AppSettingsViewModel( @@ -22,7 +23,9 @@ class AppSettingsViewModel( Recipient.self(), 0, SignalStore.donationsValues().getExpiredGiftBadge() != null, - SignalStore.donationsValues().isLikelyASustainer() || InAppDonations.hasAtLeastOnePaymentMethodAvailable() + SignalStore.donationsValues().isLikelyASustainer() || InAppDonations.hasAtLeastOnePaymentMethodAvailable(), + TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication()), + SignalStore.misc().isClientDeprecated ) ) @@ -50,6 +53,10 @@ class AppSettingsViewModel( disposables.clear() } + fun refreshDeprecatedOrUnregistered() { + store.update { it.copy(clientDeprecated = SignalStore.misc().isClientDeprecated, userUnregistered = TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication())) } + } + fun refreshExpiredGiftBadge() { store.update { it.copy(hasExpiredGiftBadge = SignalStore.donationsValues().getExpiredGiftBadge() != null) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt index 7d4502a2c9..1fee2c1952 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsFragment.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.components.settings.app.account import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.graphics.Typeface import android.text.InputType @@ -9,6 +10,7 @@ import android.view.ViewGroup import android.widget.Button import android.widget.EditText import android.widget.TextView +import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.autofill.HintConstants import androidx.core.app.DialogCompat @@ -24,12 +26,16 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment import org.thoughtcrime.securesms.components.settings.DSLSettingsText import org.thoughtcrime.securesms.components.settings.configure import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.lock.PinHashing import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity import org.thoughtcrime.securesms.lock.v2.KbsConstants import org.thoughtcrime.securesms.lock.v2.PinKeyboardType import org.thoughtcrime.securesms.pin.RegistrationLockV2Dialog +import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity +import org.thoughtcrime.securesms.util.PlayStoreUtil +import org.thoughtcrime.securesms.util.ServiceUtil import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.navigation.safeNavigate @@ -64,6 +70,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag @Suppress("DEPRECATION") clickPref( title = DSLSettingsText.from(if (state.hasPin) R.string.preferences_app_protection__change_your_pin else R.string.preferences_app_protection__create_a_pin), + isEnabled = state.isDeprecatedOrUnregistered(), onClick = { if (state.hasPin) { startActivityForResult(CreateKbsPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN) @@ -77,7 +84,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag title = DSLSettingsText.from(R.string.preferences_app_protection__pin_reminders), summary = DSLSettingsText.from(R.string.AccountSettingsFragment__youll_be_asked_less_frequently), isChecked = state.hasPin && state.pinRemindersEnabled, - isEnabled = state.hasPin, + isEnabled = state.hasPin && state.isDeprecatedOrUnregistered(), onClick = { setPinRemindersEnabled(!state.pinRemindersEnabled) } @@ -87,7 +94,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag title = DSLSettingsText.from(R.string.preferences_app_protection__registration_lock), summary = DSLSettingsText.from(R.string.AccountSettingsFragment__require_your_signal_pin), isChecked = state.registrationLockEnabled, - isEnabled = state.hasPin, + isEnabled = state.hasPin && state.isDeprecatedOrUnregistered(), onClick = { setRegistrationLockEnabled(!state.registrationLockEnabled) } @@ -95,6 +102,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag clickPref( title = DSLSettingsText.from(R.string.preferences__advanced_pin_settings), + isEnabled = state.isDeprecatedOrUnregistered(), onClick = { Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_advancedPinSettingsActivity) } @@ -107,6 +115,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag if (SignalStore.account().isRegistered) { clickPref( title = DSLSettingsText.from(R.string.AccountSettingsFragment__change_phone_number), + isEnabled = state.isDeprecatedOrUnregistered(), onClick = { Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_changePhoneNumberFragment) } @@ -116,6 +125,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag clickPref( title = DSLSettingsText.from(R.string.preferences_chats__transfer_account), summary = DSLSettingsText.from(R.string.preferences_chats__transfer_account_to_a_new_android_device), + isEnabled = state.isDeprecatedOrUnregistered(), onClick = { Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_oldDeviceTransferActivity) } @@ -123,13 +133,49 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag clickPref( title = DSLSettingsText.from(R.string.AccountSettingsFragment__request_account_data), + isEnabled = state.isDeprecatedOrUnregistered(), onClick = { Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_exportAccountFragment) } ) + if (!state.isDeprecatedOrUnregistered()) { + if (state.clientDeprecated) { + clickPref( + title = DSLSettingsText.from(R.string.preferences_account_update_signal), + onClick = { + PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()) + } + ) + } else if (state.userUnregistered) { + clickPref( + title = DSLSettingsText.from(R.string.preferences_account_reregister), + onClick = { + startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext())) + } + ) + } + + clickPref( + title = DSLSettingsText.from(R.string.preferences_account_delete_all_data, ContextCompat.getColor(requireContext(), R.color.signal_alert_primary)), + onClick = { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.preferences_account_delete_all_data_confirmation_title) + .setMessage(R.string.preferences_account_delete_all_data_confirmation_message) + .setPositiveButton(R.string.preferences_account_delete_all_data_confirmation_proceed) { _: DialogInterface, _: Int -> + if (!ServiceUtil.getActivityManager(ApplicationDependencies.getApplication()).clearApplicationUserData()) { + Toast.makeText(requireContext(), R.string.preferences_account_delete_all_data_failed, Toast.LENGTH_LONG).show() + } + } + .setNegativeButton(R.string.preferences_account_delete_all_data_confirmation_cancel, null) + .show() + } + ) + } + clickPref( - title = DSLSettingsText.from(R.string.preferences__delete_account, ContextCompat.getColor(requireContext(), R.color.signal_alert_primary)), + title = DSLSettingsText.from(R.string.preferences__delete_account, ContextCompat.getColor(requireContext(), if (state.isDeprecatedOrUnregistered()) R.color.signal_alert_primary else R.color.signal_alert_primary_50)), + isEnabled = state.isDeprecatedOrUnregistered(), onClick = { Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_deleteAccountFragment) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsState.kt index 76d69cfa41..b7d733ac93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsState.kt @@ -3,5 +3,11 @@ package org.thoughtcrime.securesms.components.settings.app.account data class AccountSettingsState( val hasPin: Boolean, val pinRemindersEnabled: Boolean, - val registrationLockEnabled: Boolean -) + val registrationLockEnabled: Boolean, + val userUnregistered: Boolean, + val clientDeprecated: Boolean +) { + fun isDeprecatedOrUnregistered(): Boolean { + return !(userUnregistered || clientDeprecated) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt index 0af83d8aba..b4d3975cea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/account/AccountSettingsViewModel.kt @@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.components.settings.app.account import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.livedata.Store class AccountSettingsViewModel : ViewModel() { @@ -18,7 +20,9 @@ class AccountSettingsViewModel : ViewModel() { return AccountSettingsState( hasPin = SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut(), pinRemindersEnabled = SignalStore.pinValues().arePinRemindersEnabled(), - registrationLockEnabled = SignalStore.kbsValues().isV2RegistrationLockEnabled + registrationLockEnabled = SignalStore.kbsValues().isV2RegistrationLockEnabled, + userUnregistered = TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication()), + clientDeprecated = SignalStore.misc().isClientDeprecated ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt index 0b2f77050e..b78b828666 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt @@ -109,6 +109,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( private val args: ConversationSettingsFragmentArgs by navArgs() private val alertTint by lazy { ContextCompat.getColor(requireContext(), R.color.signal_alert_primary) } + private val alertDisabledTint by lazy { ContextCompat.getColor(requireContext(), R.color.signal_alert_primary_50) } private val blockIcon by lazy { ContextUtil.requireDrawable(requireContext(), R.drawable.ic_block_tinted_24).apply { colorFilter = PorterDuffColorFilter(alertTint, PorterDuff.Mode.SRC_IN) @@ -383,6 +384,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( customPref( ButtonStripPreference.Model( state = state.buttonStripState, + enabled = !state.isDeprecatedOrUnregistered, onMessageClick = { val intent = ConversationIntents .createBuilder(requireContext(), state.recipient.id, state.threadId) @@ -470,7 +472,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( title = DSLSettingsText.from(R.string.ConversationSettingsFragment__disappearing_messages), summary = summary, icon = DSLSettingsIcon.from(icon), - isEnabled = enabled, + isEnabled = enabled && !state.isDeprecatedOrUnregistered, onClick = { val action = ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToAppSettingsExpireTimer() .setInitialValue(state.disappearingMessagesLifespan) @@ -496,6 +498,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( clickPref( title = DSLSettingsText.from(R.string.ConversationSettingsFragment__sounds_and_notifications), icon = DSLSettingsIcon.from(R.drawable.ic_speaker_24), + isEnabled = !state.isDeprecatedOrUnregistered, onClick = { val action = ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToSoundsAndNotificationsSettingsFragment(state.recipient.id) @@ -540,6 +543,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( clickPref( title = DSLSettingsText.from(R.string.ConversationSettingsFragment__view_safety_number), icon = DSLSettingsIcon.from(R.drawable.ic_safety_number_24), + isEnabled = !state.isDeprecatedOrUnregistered, onClick = { startActivity(VerifyIdentityActivity.newIntent(requireActivity(), recipientState.identityRecord)) } @@ -615,6 +619,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( LargeIconClickPreference.Model( title = DSLSettingsText.from(R.string.ConversationSettingsFragment__add_to_a_group), icon = DSLSettingsIcon.from(R.drawable.add_to_a_group, NO_TINT), + isEnabled = !state.isDeprecatedOrUnregistered, onClick = { viewModel.onAddToGroup() } @@ -657,7 +662,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( sectionHeaderPref(DSLSettingsText.from(resources.getQuantityString(R.plurals.ContactSelectionListFragment_d_members, memberCount, memberCount))) } - if (groupState.canAddToGroup) { + if (groupState.canAddToGroup && !state.isDeprecatedOrUnregistered) { customPref( LargeIconClickPreference.Model( title = DSLSettingsText.from(R.string.ConversationSettingsFragment__add_members), @@ -700,6 +705,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( title = DSLSettingsText.from(R.string.ConversationSettingsFragment__group_link), summary = DSLSettingsText.from(if (groupState.groupLinkEnabled) R.string.preferences_on else R.string.preferences_off), icon = DSLSettingsIcon.from(R.drawable.ic_link_16), + isEnabled = !state.isDeprecatedOrUnregistered, onClick = { navController.safeNavigate(ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToShareableGroupLinkFragment(groupState.groupId.requireV2().toString())) } @@ -708,6 +714,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( clickPref( title = DSLSettingsText.from(R.string.ConversationSettingsFragment__requests_and_invites), icon = DSLSettingsIcon.from(R.drawable.ic_update_group_add_16), + isEnabled = !state.isDeprecatedOrUnregistered, onClick = { startActivity(ManagePendingAndRequestingMembersActivity.newIntent(requireContext(), groupState.groupId.requireV2())) } @@ -717,6 +724,7 @@ class ConversationSettingsFragment : DSLSettingsFragment( clickPref( title = DSLSettingsText.from(R.string.ConversationSettingsFragment__permissions), icon = DSLSettingsIcon.from(R.drawable.ic_lock_24), + isEnabled = !state.isDeprecatedOrUnregistered, onClick = { val action = ConversationSettingsFragmentDirections.actionConversationSettingsFragmentToPermissionsSettingsFragment(ParcelableGroupId.from(groupState.groupId)) navController.safeNavigate(action) @@ -729,8 +737,9 @@ class ConversationSettingsFragment : DSLSettingsFragment( dividerPref() clickPref( - title = DSLSettingsText.from(R.string.conversation__menu_leave_group, alertTint), + title = DSLSettingsText.from(R.string.conversation__menu_leave_group, if (state.isDeprecatedOrUnregistered) alertDisabledTint else alertTint), icon = DSLSettingsIcon.from(leaveIcon), + isEnabled = !state.isDeprecatedOrUnregistered, onClick = { LeaveGroupDialog.handleLeavePushGroup(requireActivity(), groupState.groupId.requirePush(), null) } @@ -759,12 +768,13 @@ class ConversationSettingsFragment : DSLSettingsFragment( else -> R.string.ConversationSettingsFragment__block } - val titleTint = if (isBlocked) null else alertTint + val titleTint = if (isBlocked) null else if (state.isDeprecatedOrUnregistered) alertDisabledTint else alertTint val blockUnblockIcon = if (isBlocked) unblockIcon else blockIcon clickPref( title = if (titleTint != null) DSLSettingsText.from(title, titleTint) else DSLSettingsText.from(title), icon = DSLSettingsIcon.from(blockUnblockIcon), + isEnabled = !state.isDeprecatedOrUnregistered, onClick = { if (state.recipient.isBlocked) { BlockUnblockDialog.showUnblockFor(requireContext(), viewLifecycleOwner.lifecycle, state.recipient) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt index 8195a3f85e..3f787313c1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsState.kt @@ -14,6 +14,7 @@ data class ConversationSettingsState( val threadId: Long = -1, val storyViewState: StoryViewState = StoryViewState.NONE, val recipient: Recipient = Recipient.UNKNOWN, + val isDeprecatedOrUnregistered: Boolean = false, val buttonStripState: ButtonStripPreference.State = ButtonStripPreference.State(), val disappearingMessagesLifespan: Int = 0, val canModifyBlockedState: Boolean = false, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt index f53b86e441..a5f7905543 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsViewModel.kt @@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.components.settings.conversation.preferences.L import org.thoughtcrime.securesms.database.AttachmentTable import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.model.StoryViewState +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.LiveGroup import org.thoughtcrime.securesms.groups.v2.GroupAddMembersResult @@ -29,6 +30,7 @@ import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientUtil import org.thoughtcrime.securesms.util.FeatureFlags +import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.livedata.LiveDataUtil import org.thoughtcrime.securesms.util.livedata.Store import java.util.Optional @@ -46,7 +48,8 @@ sealed class ConversationSettingsViewModel( protected val store = Store( ConversationSettingsState( - specificSettingsState = specificSettingsState + specificSettingsState = specificSettingsState, + isDeprecatedOrUnregistered = SignalStore.misc().isClientDeprecated || TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication()) ) ) protected val internalEvents: Subject = PublishSubject.create() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/ButtonStripPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/ButtonStripPreference.kt index e4d7d298ea..ec666b43e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/ButtonStripPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/ButtonStripPreference.kt @@ -7,6 +7,7 @@ import androidx.appcompat.content.res.AppCompatResources import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon import org.thoughtcrime.securesms.components.settings.PreferenceModel +import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder @@ -24,6 +25,7 @@ object ButtonStripPreference { class Model( val state: State, val background: DSLSettingsIcon? = null, + val enabled: Boolean = true, val onAddToStoryClick: () -> Unit = {}, val onMessageClick: () -> Unit = {}, val onVideoClick: () -> Unit = {}, @@ -87,6 +89,11 @@ object ButtonStripPreference { } } + listOf(messageContainer, videoContainer, audioContainer, muteContainer, addToStoryContainer, searchContainer).forEach { + it.alpha = if (model.enabled) 1.0f else 0.5f + ViewUtil.setEnabledRecursive(it, model.enabled) + } + message.setOnClickListener { model.onMessageClick() } videoCall.setOnClickListener { model.onVideoClick() } audioCall.setOnClickListener { model.onAudioClick() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/LargeIconClickPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/LargeIconClickPreference.kt index e00bd32f2f..66dcc3b1fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/LargeIconClickPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/LargeIconClickPreference.kt @@ -22,6 +22,7 @@ object LargeIconClickPreference { override val title: DSLSettingsText?, override val icon: DSLSettingsIcon, override val summary: DSLSettingsText? = null, + override val isEnabled: Boolean = true, val onClick: () -> Unit ) : PreferenceModel() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index 31b9418e24..0f06428538 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -399,6 +399,7 @@ public class ConversationParentFragment extends Fragment private ConversationFragment fragment; private Button unblockButton; private Stub smsExportStub; + private Stub loggedOutStub; private Button registerButton; private InputAwareLayout container; protected Stub reminderView; @@ -1728,11 +1729,12 @@ public class ConversationParentFragment extends Fragment Integer actionableRequestingMembers = groupViewModel.getActionableRequestingMembers().getValue(); List gv1MigrationSuggestions = groupViewModel.getGroupV1MigrationSuggestions().getValue(); - if (UnauthorizedReminder.isEligible(context)) { - reminderView.get().showReminder(new UnauthorizedReminder(context)); - } else if (ExpiredBuildReminder.isEligible()) { + if (ExpiredBuildReminder.isEligible()) { reminderView.get().showReminder(new ExpiredBuildReminder(context)); reminderView.get().setOnActionClickListener(this::handleReminderAction); + } else if (UnauthorizedReminder.isEligible(context)) { + reminderView.get().showReminder(new UnauthorizedReminder(context)); + reminderView.get().setOnActionClickListener(this::handleReminderAction); } else if (ServiceOutageReminder.isEligible(context)) { ApplicationDependencies.getJobManager().add(new ServiceOutageDetectionJob()); reminderView.get().showReminder(new ServiceOutageReminder(context)); @@ -1788,6 +1790,8 @@ public class ConversationParentFragment extends Fragment InsightsLauncher.showInsightsDashboard(getChildFragmentManager()); } else if (reminderActionId == R.id.reminder_action_update_now) { PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()); + } else if (reminderActionId == R.id.reminder_action_re_register) { + startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext())); } else { throw new IllegalArgumentException("Unknown ID: " + reminderActionId); } @@ -1872,6 +1876,7 @@ public class ConversationParentFragment extends Fragment attachmentKeyboardStub = ViewUtil.findStubById(view, R.id.attachment_keyboard_stub); unblockButton = view.findViewById(R.id.unblock_button); smsExportStub = ViewUtil.findStubById(view, R.id.sms_export_stub); + loggedOutStub = ViewUtil.findStubById(view, R.id.logged_out_stub); registerButton = view.findViewById(R.id.register_button); container = view.findViewById(R.id.layout_container); reminderView = ViewUtil.findStubById(view, R.id.reminder_stub); @@ -2608,16 +2613,46 @@ public class ConversationParentFragment extends Fragment return; } - if (!conversationSecurityInfo.isPushAvailable() && isPushGroupConversation()) { + if (conversationSecurityInfo.isClientExpired() || conversationSecurityInfo.isUnauthorized()) { unblockButton.setVisibility(View.GONE); inputPanel.setHideForBlockedState(true); smsExportStub.setVisibility(View.GONE); + registerButton.setVisibility(View.GONE); + loggedOutStub.setVisibility(View.VISIBLE); + messageRequestBottomView.setVisibility(View.GONE); + + int color = ContextCompat.getColor(requireContext(), recipient.hasWallpaper() ? R.color.wallpaper_bubble_color : R.color.signal_colorBackground); + loggedOutStub.get().setBackgroundColor(color); + WindowUtil.setNavigationBarColor(requireActivity(), color); + + TextView message = loggedOutStub.get().findViewById(R.id.logged_out_message); + MaterialButton actionButton = loggedOutStub.get().findViewById(R.id.logged_out_button); + + if (conversationSecurityInfo.isClientExpired()) { + message.setText(R.string.ExpiredBuildReminder_this_version_of_signal_has_expired); + actionButton.setText(R.string.ConversationFragment__update_build); + actionButton.setOnClickListener(v -> { + PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()); + }); + } else if (conversationSecurityInfo.isUnauthorized()) { + message.setText(R.string.UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device); + actionButton.setText(R.string.ConversationFragment__reregister_signal); + actionButton.setOnClickListener(v -> { + startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext())); + }); + } + } else if (!conversationSecurityInfo.isPushAvailable() && isPushGroupConversation()) { + unblockButton.setVisibility(View.GONE); + inputPanel.setHideForBlockedState(true); + smsExportStub.setVisibility(View.GONE); + loggedOutStub.setVisibility(View.GONE); registerButton.setVisibility(View.VISIBLE); } else if (!conversationSecurityInfo.isPushAvailable() && !(SignalStore.misc().getSmsExportPhase().isSmsSupported() && conversationSecurityInfo.isDefaultSmsApplication()) && (recipient.hasSmsAddress() || recipient.isMmsGroup())) { unblockButton.setVisibility(View.GONE); inputPanel.setHideForBlockedState(true); smsExportStub.setVisibility(View.VISIBLE); registerButton.setVisibility(View.GONE); + loggedOutStub.setVisibility(View.GONE); int color = ContextCompat.getColor(requireContext(), recipient.hasWallpaper() ? R.color.wallpaper_bubble_color : R.color.signal_colorBackground); smsExportStub.get().setBackgroundColor(color); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java index 012b4b830d..ba3078af86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.util.BubbleUtil; import org.thoughtcrime.securesms.util.ConversationUtil; import org.thoughtcrime.securesms.util.MessageRecordUtil; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import java.io.IOException; @@ -187,7 +188,9 @@ public class ConversationRepository { registeredState == RecipientTable.RegisteredState.REGISTERED && signalEnabled, Util.isDefaultSmsProvider(context), true, - hasUnexportedInsecureMessages); + hasUnexportedInsecureMessages, + SignalStore.misc().isClientDeprecated(), + TextSecurePreferences.isUnauthorizedReceived(context)); }).subscribeOn(Schedulers.io()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSecurityInfo.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSecurityInfo.kt index d92ee665f3..8a6754b2a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSecurityInfo.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSecurityInfo.kt @@ -7,5 +7,7 @@ data class ConversationSecurityInfo( val isPushAvailable: Boolean = false, val isDefaultSmsApplication: Boolean = false, val isInitialized: Boolean = false, - val hasUnexportedInsecureMessages: Boolean = false + val hasUnexportedInsecureMessages: Boolean = false, + val isClientExpired: Boolean = false, + val isUnauthorized: Boolean = false ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 293c0385f9..69221f7ff9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -158,6 +158,7 @@ import org.thoughtcrime.securesms.profiles.manage.ManageProfileActivity; import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity; import org.thoughtcrime.securesms.search.MessageResult; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.MessageSender; @@ -803,6 +804,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode CdsPermanentErrorBottomSheet.show(getChildFragmentManager()); } else if (reminderActionId == R.id.reminder_action_fix_username) { startActivity(ManageProfileActivity.getIntentForUsernameEdit(requireContext())); + } else if (reminderActionId == R.id.reminder_action_re_register) { + startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext())); } } @@ -1042,10 +1045,10 @@ public class ConversationListFragment extends MainFragment implements ActionMode Context context = requireContext(); SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> { - if (UnauthorizedReminder.isEligible(context)) { - return Optional.of(new UnauthorizedReminder(context)); - } else if (ExpiredBuildReminder.isEligible()) { + if (ExpiredBuildReminder.isEligible()) { return Optional.of(new ExpiredBuildReminder(context)); + } else if (UnauthorizedReminder.isEligible(context)) { + return Optional.of(new UnauthorizedReminder(context)); } else if (ServiceOutageReminder.isEligible(context)) { ApplicationDependencies.getJobManager().add(new ServiceOutageDetectionJob()); return Optional.of(new ServiceOutageReminder(context)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java index aca1840fdc..3bf846a979 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV1.java @@ -176,7 +176,7 @@ final class GroupManagerV1 { OutgoingMessage outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, new MessageGroupContext(groupContext), - Collections.singletonList(avatarAttachment), + avatarAttachment != null ? Collections.singletonList(avatarAttachment) : Collections.emptyList(), System.currentTimeMillis(), 0, false, diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java index 9055c350f8..f8b0c1da02 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/preferences/PaymentsHomeFragment.java @@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.payments.backup.RecoveryPhraseStates; import org.thoughtcrime.securesms.payments.backup.confirm.PaymentsRecoveryPhraseConfirmFragment; import org.thoughtcrime.securesms.payments.preferences.model.InfoCard; import org.thoughtcrime.securesms.payments.preferences.model.PaymentItem; +import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity; import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.PlayStoreUtil; import org.thoughtcrime.securesms.util.SpanUtil; @@ -261,6 +262,8 @@ public class PaymentsHomeFragment extends LoggingFragment { reminderView.get().setOnActionClickListener(actionId -> { if (actionId == R.id.reminder_action_update_now) { PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()); + } else if (actionId == R.id.reminder_action_re_register) { + startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext())); } }); } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java index e7e9325447..064f1d0fdd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java @@ -49,6 +49,8 @@ import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.WindowUtil; +import java.util.Arrays; +import java.util.List; import java.util.Objects; import kotlin.Unit; @@ -87,6 +89,8 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF private BadgeImageView badgeImageView; private Callback callback; + private ButtonStripPreference.ViewHolder buttonStripViewHolder; + public static BottomSheetDialogFragment create(@NonNull RecipientId recipientId, @Nullable GroupId groupId) { @@ -135,6 +139,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF interactionsContainer = view.findViewById(R.id.interactions_container); badgeImageView = view.findViewById(R.id.rbs_badge); + buttonStripViewHolder = new ButtonStripPreference.ViewHolder(buttonStrip); return view; } @@ -245,6 +250,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF ButtonStripPreference.Model buttonStripModel = new ButtonStripPreference.Model( buttonStripState, DSLSettingsIcon.from(ContextUtil.requireDrawable(requireContext(), R.drawable.selectable_recipient_bottom_sheet_icon_button)), + !viewModel.isDeprecatedOrUnregistered(), () -> Unit.INSTANCE, () -> { dismiss(); @@ -267,7 +273,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF () -> Unit.INSTANCE ); - new ButtonStripPreference.ViewHolder(buttonStrip).bind(buttonStripModel); + buttonStripViewHolder.bind(buttonStripModel); if (recipient.isReleaseNotes()) { buttonStrip.setVisibility(View.GONE); @@ -342,12 +348,21 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF viewModel.getAdminActionBusy().observe(getViewLifecycleOwner(), busy -> { adminActionBusy.setVisibility(busy ? View.VISIBLE : View.GONE); - makeGroupAdminButton.setEnabled(!busy); - removeAdminButton.setEnabled(!busy); - removeFromGroupButton.setEnabled(!busy); + boolean userLoggedOut = viewModel.isDeprecatedOrUnregistered(); + makeGroupAdminButton.setEnabled(!busy && !userLoggedOut); + removeAdminButton.setEnabled(!busy && !userLoggedOut); + removeFromGroupButton.setEnabled(!busy && !userLoggedOut); }); callback = getParentFragment() != null && getParentFragment() instanceof Callback ? (Callback) getParentFragment() : null; + + if (viewModel.isDeprecatedOrUnregistered()) { + List viewsToDisable = Arrays.asList(blockButton, unblockButton, removeFromGroupButton, makeGroupAdminButton, removeAdminButton, addToGroupButton, viewSafetyNumberButton); + for (TextView view : viewsToDisable) { + view.setEnabled(false); + view.setAlpha(0.5f); + } + } } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java index 8cf18ae30f..fcfa3af804 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java @@ -29,12 +29,14 @@ import org.thoughtcrime.securesms.groups.LiveGroup; import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason; import org.thoughtcrime.securesms.groups.ui.GroupErrors; import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupsActivity; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.stories.StoryViewerArgs; import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity; import org.thoughtcrime.securesms.util.CommunicationActions; +import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.verify.VerifyIdentityActivity; @@ -54,16 +56,17 @@ final class RecipientDialogViewModel extends ViewModel { private final MutableLiveData adminActionBusy; private final MutableLiveData storyViewState; private final CompositeDisposable disposables; - + private final boolean isDeprecatedOrUnregistered; private RecipientDialogViewModel(@NonNull Context context, @NonNull RecipientDialogRepository recipientDialogRepository) { - this.context = context; - this.recipientDialogRepository = recipientDialogRepository; - this.identity = new MutableLiveData<>(); - this.adminActionBusy = new MutableLiveData<>(false); - this.storyViewState = new MutableLiveData<>(); - this.disposables = new CompositeDisposable(); + this.context = context; + this.recipientDialogRepository = recipientDialogRepository; + this.identity = new MutableLiveData<>(); + this.adminActionBusy = new MutableLiveData<>(false); + this.storyViewState = new MutableLiveData<>(); + this.disposables = new CompositeDisposable(); + this.isDeprecatedOrUnregistered = SignalStore.misc().isClientDeprecated() || TextSecurePreferences.isUnauthorizedReceived(context); boolean recipientIsSelf = recipientDialogRepository.getRecipientId().equals(Recipient.self().getId()); @@ -113,6 +116,10 @@ final class RecipientDialogViewModel extends ViewModel { disposables.clear(); } + boolean isDeprecatedOrUnregistered() { + return isDeprecatedOrUnregistered; + } + LiveData getStoryViewState() { return storyViewState; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt index 338a5d02a1..e715ee5adf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt @@ -10,6 +10,7 @@ import android.view.MenuItem import android.view.View import android.widget.Toast import androidx.activity.OnBackPressedCallback +import androidx.annotation.IdRes import androidx.core.app.ActivityOptionsCompat import androidx.core.app.SharedElementCallback import androidx.core.view.ViewCompat @@ -21,9 +22,16 @@ import com.google.android.material.snackbar.Snackbar import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.kotlin.subscribeBy +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import org.signal.core.util.concurrent.LifecycleDisposable import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.Material3SearchToolbar +import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder +import org.thoughtcrime.securesms.components.reminder.Reminder +import org.thoughtcrime.securesms.components.reminder.ReminderView +import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder import org.thoughtcrime.securesms.components.settings.DSLConfiguration import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment import org.thoughtcrime.securesms.components.settings.DSLSettingsText @@ -35,10 +43,12 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.StoryViewState import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.events.ReminderUpdateEvent import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder import org.thoughtcrime.securesms.main.SearchBinder import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity import org.thoughtcrime.securesms.permissions.Permissions +import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet import org.thoughtcrime.securesms.stories.StoryTextPostModel import org.thoughtcrime.securesms.stories.StoryViewerArgs @@ -49,8 +59,11 @@ import org.thoughtcrime.securesms.stories.settings.StorySettingsActivity import org.thoughtcrime.securesms.stories.tabs.ConversationListTab import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity +import org.thoughtcrime.securesms.util.PlayStoreUtil +import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.fragments.requireListener +import org.thoughtcrime.securesms.util.views.Stub import org.thoughtcrime.securesms.util.visible import java.util.concurrent.TimeUnit @@ -66,6 +79,8 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l private lateinit var emptyNotice: View private lateinit var cameraFab: FloatingActionButton + private lateinit var reminderView: Stub + private val lifecycleDisposable = LifecycleDisposable() private val viewModel: StoriesLandingViewModel by viewModels( @@ -95,11 +110,13 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l viewModel.markStoriesRead() ApplicationDependencies.getExpireStoriesManager().scheduleIfNecessary() + EventBus.getDefault().register(this) } override fun onPause() { super.onPause() requireListener().getSearchAction().setOnClickListener(null) + EventBus.getDefault().unregister(this) } private fun initializeSearchAction() { @@ -121,6 +138,57 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l } } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + reminderView = ViewUtil.findStubById(view, R.id.reminder) + updateReminders() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEvent(event: ReminderUpdateEvent?) { + updateReminders() + } + + private fun updateReminders() { + if (ExpiredBuildReminder.isEligible()) { + showReminder(ExpiredBuildReminder(context)) + } else if (UnauthorizedReminder.isEligible(context)) { + showReminder(UnauthorizedReminder(context)) + } else { + hideReminders() + } + } + + private fun showReminder(reminder: Reminder) { + if (!reminderView.resolved()) { + reminderView.get().addOnLayoutChangeListener { _, _, top, _, bottom, _, _, _, _ -> + recyclerView?.setPadding(0, bottom - top, 0, 0) + } + recyclerView?.clipToPadding = false + } + reminderView.get().showReminder(reminder) + reminderView.get().setOnActionClickListener { reminderActionId: Int -> this.handleReminderAction(reminderActionId) } + } + + private fun hideReminders() { + if (reminderView.resolved()) { + reminderView.get().hide() + recyclerView?.clipToPadding = true + } + } + + private fun handleReminderAction(@IdRes reminderActionId: Int) { + when (reminderActionId) { + R.id.reminder_action_update_now -> { + PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext()) + } + R.id.reminder_action_re_register -> { + startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext())) + } + } + } + override fun bindAdapter(adapter: MappingAdapter) { this.adapter = adapter diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Dialogs.java b/app/src/main/java/org/thoughtcrime/securesms/util/Dialogs.java index d486ccb945..77ddd15c06 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Dialogs.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Dialogs.java @@ -24,6 +24,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity; public class Dialogs { public static void showAlertDialog(Context context, String title, String message) { @@ -54,4 +55,26 @@ public class Dialogs { }) .show(); } + + public static void showUpgradeSignalDialog(@NonNull Context context) { + new MaterialAlertDialogBuilder(context) + .setTitle(R.string.UpdateSignalExpiredDialog__title) + .setMessage(R.string.UpdateSignalExpiredDialog__message) + .setNegativeButton(R.string.UpdateSignalExpiredDialog__cancel_action, null) + .setPositiveButton(R.string.UpdateSignalExpiredDialog__update_action, (d, w) -> { + PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(context); + }) + .show(); + } + + public static void showReregisterSignalDialog(@NonNull Context context) { + new MaterialAlertDialogBuilder(context) + .setTitle(R.string.ReregisterSignalDialog__title) + .setMessage(R.string.ReregisterSignalDialog__message) + .setNegativeButton(R.string.ReregisterSignalDialog__cancel_action, null) + .setPositiveButton(R.string.ReregisterSignalDialog__reregister_action, (d, w) -> { + context.startActivity(RegistrationNavigationActivity.newIntentForReRegistration(context)); + }) + .show(); + } } diff --git a/app/src/main/res/color/icon_tint_color_primary_enabled_selector.xml b/app/src/main/res/color/icon_tint_color_primary_enabled_selector.xml new file mode 100644 index 0000000000..2322d3b5a6 --- /dev/null +++ b/app/src/main/res/color/icon_tint_color_primary_enabled_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conversation_activity.xml b/app/src/main/res/layout/conversation_activity.xml index 6aad0a3ff3..59034c4b81 100644 --- a/app/src/main/res/layout/conversation_activity.xml +++ b/app/src/main/res/layout/conversation_activity.xml @@ -148,6 +148,13 @@ android:inflatedId="@+id/sms_export_view" android:layout="@layout/conversation_activity_sms_export_stub" /> + + + + + + + + + diff --git a/app/src/main/res/layout/dsl_settings_fragment_with_reminder.xml b/app/src/main/res/layout/dsl_settings_fragment_with_reminder.xml new file mode 100644 index 0000000000..f25e4c0b19 --- /dev/null +++ b/app/src/main/res/layout/dsl_settings_fragment_with_reminder.xml @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recipient_bottom_sheet.xml b/app/src/main/res/layout/recipient_bottom_sheet.xml index 6c830241b2..042e55410e 100644 --- a/app/src/main/res/layout/recipient_bottom_sheet.xml +++ b/app/src/main/res/layout/recipient_bottom_sheet.xml @@ -136,6 +136,7 @@ android:textAppearance="@style/Signal.Text.BodyLarge" android:visibility="gone" app:drawableStartCompat="@drawable/ic_block_tinted_24" + app:drawableTint="@color/icon_tint_color_primary_enabled_selector" tools:visibility="visible" /> @@ -232,6 +238,7 @@ android:textAppearance="@style/Signal.Text.BodyLarge" android:visibility="gone" app:drawableStartCompat="@drawable/ic_plus_24" + app:drawableTint="@color/icon_tint_color_primary_enabled_selector" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/stories_landing_fragment.xml b/app/src/main/res/layout/stories_landing_fragment.xml index 6819d4313c..74763c85ea 100644 --- a/app/src/main/res/layout/stories_landing_fragment.xml +++ b/app/src/main/res/layout/stories_landing_fragment.xml @@ -100,5 +100,12 @@ app:layout_constraintTop_toTopOf="parent" tools:visibility="visible" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/stories_landing_reminder_view.xml b/app/src/main/res/layout/stories_landing_reminder_view.xml new file mode 100644 index 0000000000..1973a1ce52 --- /dev/null +++ b/app/src/main/res/layout/stories_landing_reminder_view.xml @@ -0,0 +1,8 @@ + + diff --git a/app/src/main/res/values-night/dark_colors.xml b/app/src/main/res/values-night/dark_colors.xml index 25caade833..520f19baa2 100644 --- a/app/src/main/res/values-night/dark_colors.xml +++ b/app/src/main/res/values-night/dark_colors.xml @@ -51,6 +51,7 @@ @color/core_grey_60 @color/core_red + @color/core_red_50 @color/transparent_black @color/transparent_black_40 diff --git a/app/src/main/res/values-night/material3_colors.xml b/app/src/main/res/values-night/material3_colors.xml index 9811d2bc60..ed3e98cb2b 100644 --- a/app/src/main/res/values-night/material3_colors.xml +++ b/app/src/main/res/values-night/material3_colors.xml @@ -53,6 +53,7 @@ #61303133 #A3303133 #1FE2E1E5 + #80E2E1E5 #99BEBFC5 #99E2E1E5 #615C5E65 diff --git a/app/src/main/res/values/core_colors.xml b/app/src/main/res/values/core_colors.xml index 43fb00e9f9..50cdeb6e41 100644 --- a/app/src/main/res/values/core_colors.xml +++ b/app/src/main/res/values/core_colors.xml @@ -10,7 +10,9 @@ #4caf50 #ffd624 #f44336 + #80f44336 #ef5350 + #80ef5350 #e51d0e #ffffff diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index c735047a1e..d2339f73c1 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -7,6 +7,7 @@ + diff --git a/app/src/main/res/values/light_colors.xml b/app/src/main/res/values/light_colors.xml index 953ac96dca..3ded13a313 100644 --- a/app/src/main/res/values/light_colors.xml +++ b/app/src/main/res/values/light_colors.xml @@ -50,6 +50,7 @@ @color/core_grey_25 @color/core_red_highlight + @color/core_red_highlight_50 @color/transparent @color/transparent_white_40 diff --git a/app/src/main/res/values/material3_colors.xml b/app/src/main/res/values/material3_colors.xml index 2e456a8f7f..b027ee86a0 100644 --- a/app/src/main/res/values/material3_colors.xml +++ b/app/src/main/res/values/material3_colors.xml @@ -53,6 +53,7 @@ #61E7EBF3 #A3E7EBF3 #1F1B1B1D + #801B1B1D #99545863 #991B1D1D #61808389 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b61269bef4..342702cf3d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -482,6 +482,10 @@ Cancel Blocked + + Update Signal + + Re-register Signal Clear filter @@ -2251,7 +2255,10 @@ Device no longer registered - This is likely because you registered your phone number with Signal on a different device. Tap to re-register. + + This device is no longer registered. This is likely because you registered your phone number with Signal on a different device. + + Re-register device You have been logged out of Signal on this device. @@ -3580,6 +3587,23 @@ TAP TO UNLOCK Unknown + + Re-register account + + Update Signal + + Delete all data + + Delete all data? + + This will reset the app and delete all messages. The app will close after this process is complete. + + Proceed + + Cancel + + Failed to delete data + Transfer or restore account If you have previously registered a Signal account, you can transfer or restore your account and messages @@ -4637,6 +4661,24 @@ Couldn\'t add badge. %1$s Please contact support. + + Update Signal + + This version of Signal has expired. Update now to continue using Signal. + + Update + + Cancel + + + Device not registered + + This device is no longer registered. Re-register to continue using Signal on this device. + + Re-register + + Cancel + Boost Badge Expired