Disable interactions while user is unregistered or expired.

This commit is contained in:
Clark 2023-05-05 13:24:00 -04:00 committed by Cody Henthorne
parent 65d5f4c426
commit c2c1537858
37 changed files with 527 additions and 44 deletions

View file

@ -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);
}

View file

@ -64,6 +64,7 @@ abstract class PreferenceViewHolder<T : PreferenceModel<T>>(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)

View file

@ -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<ReminderView>
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(

View file

@ -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)
}
}

View file

@ -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) }
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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
)
}
}

View file

@ -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) {

View file

@ -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,

View file

@ -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<ConversationSettingsEvent> = PublishSubject.create()

View file

@ -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() }

View file

@ -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<Model>()

View file

@ -399,6 +399,7 @@ public class ConversationParentFragment extends Fragment
private ConversationFragment fragment;
private Button unblockButton;
private Stub<View> smsExportStub;
private Stub<View> loggedOutStub;
private Button registerButton;
private InputAwareLayout container;
protected Stub<ReminderView> reminderView;
@ -1728,11 +1729,12 @@ public class ConversationParentFragment extends Fragment
Integer actionableRequestingMembers = groupViewModel.getActionableRequestingMembers().getValue();
List<RecipientId> 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);

View file

@ -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());
}

View file

@ -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
)

View file

@ -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));

View file

@ -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,

View file

@ -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 {

View file

@ -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<TextView> viewsToDisable = Arrays.asList(blockButton, unblockButton, removeFromGroupButton, makeGroupAdminButton, removeAdminButton, addToGroupButton, viewSafetyNumberButton);
for (TextView view : viewsToDisable) {
view.setEnabled(false);
view.setAlpha(0.5f);
}
}
}
@Override

View file

@ -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<Boolean> adminActionBusy;
private final MutableLiveData<StoryViewState> 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<StoryViewState> getStoryViewState() {
return storyViewState;
}

View file

@ -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<ReminderView>
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<SearchBinder>().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

View file

@ -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();
}
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/signal_colorOnSurface_50" android:state_enabled="false" />
<item android:color="@color/signal_colorOnSurface" />
</selector>

View file

@ -148,6 +148,13 @@
android:inflatedId="@+id/sms_export_view"
android:layout="@layout/conversation_activity_sms_export_stub" />
<ViewStub
android:id="@+id/logged_out_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/logged_out_view"
android:layout="@layout/conversation_activity_logged_out_stub" />
<TextView
android:id="@+id/space_left"
android:layout_width="fill_parent"

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/logged_out_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textAppearance="@style/Signal.Text.BodyMedium"
android:textColor="@color/signal_colorOnSurfaceVariant"
tools:text="This device is no longer registered. Re-register to continue using Signal on this device." />
<com.google.android.material.button.MaterialButton
android:id="@+id/logged_out_button"
style="@style/Signal.Widget.Button.Large.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
tools:text="Re-register" />
</LinearLayout>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:viewBindingIgnore="true"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/dsl_settings_toolbar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
<ViewStub
android:id="@+id/reminder_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/reminder"
android:layout="@layout/conversation_activity_reminderview_stub"
app:layout_constraintTop_toTopOf="@id/recycler"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -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" />
<TextView
@ -152,6 +153,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" />
<TextView
@ -168,6 +170,7 @@
android:textAppearance="@style/Signal.Text.BodyLarge"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_leave_tinted_24"
app:drawableTint="@color/icon_tint_color_primary_enabled_selector"
tools:visibility="visible" />
<TextView
@ -184,6 +187,7 @@
android:textAppearance="@style/Signal.Text.BodyLarge"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_group_24"
app:drawableTint="@color/icon_tint_color_primary_enabled_selector"
tools:visibility="visible" />
<TextView
@ -200,6 +204,7 @@
android:textAppearance="@style/Signal.Text.BodyLarge"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_group_24"
app:drawableTint="@color/icon_tint_color_primary_enabled_selector"
tools:visibility="visible" />
<TextView
@ -215,6 +220,7 @@
android:textAppearance="@style/Signal.Text.BodyLarge"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_group_24"
app:drawableTint="@color/icon_tint_color_primary_enabled_selector"
tools:text="@string/RecipientBottomSheet_add_to_a_group"
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" />
<TextView
@ -248,6 +255,7 @@
android:textAppearance="@style/Signal.Text.BodyLarge"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_profile_circle_24"
app:drawableTint="@color/icon_tint_color_primary_enabled_selector"
tools:visibility="visible" />
<TextView
@ -264,7 +272,7 @@
android:textAppearance="@style/Signal.Text.BodyLarge"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_safety_number_24"
app:drawableTint="@color/signal_icon_tint_primary"
app:drawableTint="@color/icon_tint_color_primary_enabled_selector"
tools:visibility="visible" />
</LinearLayout>

View file

@ -100,5 +100,12 @@
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<ViewStub
android:id="@+id/reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/reminder"
android:layout="@layout/stories_landing_reminder_view"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.reminder.ReminderView
xmlns:tools="http://schemas.android.com/tools"
tools:viewBindingIgnore="true"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View file

@ -51,6 +51,7 @@
<color name="signal_divider_major">@color/core_grey_60</color>
<color name="signal_alert_primary">@color/core_red</color>
<color name="signal_alert_primary_50">@color/core_red_50</color>
<color name="signal_transparent">@color/transparent_black</color>
<color name="signal_transparent_40">@color/transparent_black_40</color>

View file

@ -53,6 +53,7 @@
<color name="signal_colorSurfaceVariant_38">#61303133</color>
<color name="signal_colorSurfaceVariant_64">#A3303133</color>
<color name="signal_colorOnSurface_12">#1FE2E1E5</color>
<color name="signal_colorOnSurface_50">#80E2E1E5</color>
<color name="signal_colorOnSurfaceVariant_60">#99BEBFC5</color>
<color name="signal_colorOnBackground_60">#99E2E1E5</color>
<color name="signal_colorOutline_38">#615C5E65</color>

View file

@ -10,7 +10,9 @@
<color name="core_green">#4caf50</color>
<color name="core_yellow">#ffd624</color>
<color name="core_red">#f44336</color>
<color name="core_red_50">#80f44336</color>
<color name="core_red_highlight">#ef5350</color>
<color name="core_red_highlight_50">#80ef5350</color>
<color name="core_red_shade">#e51d0e</color>
<color name="core_white">#ffffff</color>

View file

@ -7,6 +7,7 @@
<item name="reminder_action_view_insights" type="id" />
<item name="reminder_action_invite" type="id" />
<item name="reminder_action_update_now" type="id" />
<item name="reminder_action_re_register" type="id" />
<item name="reminder_action_review_join_requests" type="id" />
<item name="reminder_action_gv1_suggestion_no_thanks" type="id" />

View file

@ -50,6 +50,7 @@
<color name="signal_divider_major">@color/core_grey_25</color>
<color name="signal_alert_primary">@color/core_red_highlight</color>
<color name="signal_alert_primary_50">@color/core_red_highlight_50</color>
<color name="signal_transparent">@color/transparent</color>
<color name="signal_transparent_40">@color/transparent_white_40</color>

View file

@ -53,6 +53,7 @@
<color name="signal_colorSurfaceVariant_38">#61E7EBF3</color>
<color name="signal_colorSurfaceVariant_64">#A3E7EBF3</color>
<color name="signal_colorOnSurface_12">#1F1B1B1D</color>
<color name="signal_colorOnSurface_50">#801B1B1D</color>
<color name="signal_colorOnSurfaceVariant_60">#99545863</color>
<color name="signal_colorOnBackground_60">#991B1D1D</color>
<color name="signal_colorOutline_38">#61808389</color>

View file

@ -482,6 +482,10 @@
<string name="ConversationFragment__cancel">Cancel</string>
<!-- Message shown after successfully blocking join requests for a user -->
<string name="ConversationFragment__blocked">Blocked</string>
<!-- Action shown to allow a user to update their application because it has expired -->
<string name="ConversationFragment__update_build">Update Signal</string>
<!-- Action shown to allow a user to re-register as they are no longer registered -->
<string name="ConversationFragment__reregister_signal">Re-register Signal</string>
<!-- Label for a button displayed in conversation list to clear the chat filter -->
<string name="ConversationListFragment__clear_filter">Clear filter</string>
<!-- Notice on chat list when no unread chats are available, centered on display -->
@ -2251,7 +2255,10 @@
<!-- UnauthorizedReminder -->
<string name="UnauthorizedReminder_device_no_longer_registered">Device no longer registered</string>
<string name="UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device">This is likely because you registered your phone number with Signal on a different device. Tap to re-register.</string>
<!-- Message shown in a reminder banner when the user's device is no longer registered -->
<string name="UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device">This device is no longer registered. This is likely because you registered your phone number with Signal on a different device.</string>
<!-- Action in reminder banner that will take user to re-register -->
<string name="UnauthorizedReminder_reregister_action">Re-register device</string>
<!-- Push notification when the app is forcibly logged out by the server. -->
<string name="LoggedOutNotification_you_have_been_logged_out">You have been logged out of Signal on this device.</string>
@ -3580,6 +3587,23 @@
<string name="prompt_passphrase_activity__tap_to_unlock">TAP TO UNLOCK</string>
<string name="Recipient_unknown">Unknown</string>
<!-- Option in settings that will take use to re-register if they are no longer registered -->
<string name="preferences_account_reregister">Re-register account</string>
<!-- Option in settings that will take user to our website or playstore to update their expired build -->
<string name="preferences_account_update_signal">Update Signal</string>
<!-- Option in settings shown when user is no longer registered or expired client that will WIPE ALL THEIR DATA -->
<string name="preferences_account_delete_all_data">Delete all data</string>
<!-- Title for confirmation dialog confirming user wants to delete all their data -->
<string name="preferences_account_delete_all_data_confirmation_title">Delete all data?</string>
<!-- Message in confirmation dialog to delete all data explaining how it works, and that the app will be closed after deletion -->
<string name="preferences_account_delete_all_data_confirmation_message">This will reset the app and delete all messages. The app will close after this process is complete.</string>
<!-- Confirmation action to proceed with application data deletion -->
<string name="preferences_account_delete_all_data_confirmation_proceed">Proceed</string>
<!-- Confirmation action to cancel application data deletion -->
<string name="preferences_account_delete_all_data_confirmation_cancel">Cancel</string>
<!-- Error message shown when we fail to delete the data for some unknown reason -->
<string name="preferences_account_delete_all_data_failed">Failed to delete data</string>
<!-- TransferOrRestoreFragment -->
<string name="TransferOrRestoreFragment__transfer_or_restore_account">Transfer or restore account</string>
<string name="TransferOrRestoreFragment__if_you_have_previously_registered_a_signal_account">If you have previously registered a Signal account, you can transfer or restore your account and messages</string>
@ -4637,6 +4661,24 @@
<string name="MySupportPreference__couldnt_add_badge_s">Couldn\'t add badge. %1$s</string>
<string name="MySupportPreference__please_contact_support">Please contact support.</string>
<!-- Title of dialog telling user they need to update signal as it expired -->
<string name="UpdateSignalExpiredDialog__title">Update Signal</string>
<!-- Message of dialog telling user they need to update signal as it expired -->
<string name="UpdateSignalExpiredDialog__message">This version of Signal has expired. Update now to continue using Signal.</string>
<!-- Button text of expiration dialog, will take user to update the app -->
<string name="UpdateSignalExpiredDialog__update_action">Update</string>
<!-- Button text of expiration dialog to cancel the dialog. -->
<string name="UpdateSignalExpiredDialog__cancel_action">Cancel</string>
<!-- Title of dialog telling user they need to re-register signal -->
<string name="ReregisterSignalDialog__title">Device not registered</string>
<!-- Message of dialog telling user they need to re-register signal as it is no longer registered -->
<string name="ReregisterSignalDialog__message">This device is no longer registered. Re-register to continue using Signal on this device.</string>
<!-- Button text of re-registration dialog to re-register the device. -->
<string name="ReregisterSignalDialog__reregister_action">Re-register</string>
<!-- Button text of re-registration dialog to cancel the dialog. -->
<string name="ReregisterSignalDialog__cancel_action">Cancel</string>
<!-- Title of expiry sheet when boost badge falls off profile unexpectedly. -->
<string name="ExpiredBadgeBottomSheetDialogFragment__boost_badge_expired">Boost Badge Expired</string>
<!-- Displayed in the bottom sheet if a monthly donation badge unexpectedly falls off the user\'s profile -->