Update spam UX and reporting flows.

This commit is contained in:
Cody Henthorne
2024-02-09 15:25:31 -05:00
committed by Clark Chen
parent a4fde60c1c
commit aa76cefb1c
66 changed files with 1578 additions and 894 deletions

View File

@@ -28,7 +28,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.util.DimensionUnit
import org.signal.core.util.Result
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.concurrent.addTo
import org.signal.core.util.getParcelableArrayListExtraCompat
import org.thoughtcrime.securesms.AvatarPreviewActivity
import org.thoughtcrime.securesms.BlockUnblockDialog
@@ -74,6 +76,7 @@ import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupInviteSentD
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupsLearnMoreBottomSheetDialogFragment
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository
import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientExporter
@@ -112,17 +115,17 @@ class ConversationSettingsFragment : DSLSettingsFragment(
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 {
ContextUtil.requireDrawable(requireContext(), R.drawable.symbol_block_24).apply {
colorFilter = PorterDuffColorFilter(alertTint, PorterDuff.Mode.SRC_IN)
}
}
private val unblockIcon by lazy {
ContextUtil.requireDrawable(requireContext(), R.drawable.ic_block_tinted_24)
ContextUtil.requireDrawable(requireContext(), R.drawable.symbol_block_24)
}
private val leaveIcon by lazy {
ContextUtil.requireDrawable(requireContext(), R.drawable.ic_leave_tinted_24).apply {
ContextUtil.requireDrawable(requireContext(), R.drawable.symbol_leave_24).apply {
colorFilter = PorterDuffColorFilter(alertTint, PorterDuff.Mode.SRC_IN)
}
}
@@ -135,7 +138,8 @@ class ConversationSettingsFragment : DSLSettingsFragment(
recipientId = args.recipientId,
groupId = ParcelableGroupId.get(groupId),
callMessageIds = args.callMessageIds ?: longArrayOf(),
repository = ConversationSettingsRepository(requireContext())
repository = ConversationSettingsRepository(requireContext()),
messageRequestRepository = MessageRequestRepository(requireContext())
)
}
)
@@ -798,6 +802,49 @@ class ConversationSettingsFragment : DSLSettingsFragment(
}
}
)
val reportSpamTint = if (state.isDeprecatedOrUnregistered) R.color.signal_alert_primary_50 else R.color.signal_alert_primary
clickPref(
title = DSLSettingsText.from(R.string.ConversationFragment_report_spam, ContextCompat.getColor(requireContext(), reportSpamTint)),
icon = DSLSettingsIcon.from(R.drawable.symbol_spam_24, reportSpamTint),
isEnabled = !state.isDeprecatedOrUnregistered,
onClick = {
BlockUnblockDialog.showReportSpamFor(
requireContext(),
viewLifecycleOwner.lifecycle,
state.recipient,
{
viewModel
.onReportSpam()
.subscribeBy {
Toast.makeText(requireContext(), R.string.ConversationFragment_reported_as_spam, Toast.LENGTH_SHORT).show()
onToolbarNavigationClicked()
}
.addTo(lifecycleDisposable)
},
if (state.recipient.isBlocked) {
null
} else {
Runnable {
viewModel
.onBlockAndReportSpam()
.subscribeBy { result ->
when (result) {
is Result.Success -> {
Toast.makeText(requireContext(), R.string.ConversationFragment_reported_as_spam_and_blocked, Toast.LENGTH_SHORT).show()
onToolbarNavigationClicked()
}
is Result.Failure -> {
Toast.makeText(requireContext(), GroupErrors.getUserDisplayMessage(result.failure), Toast.LENGTH_SHORT).show()
}
}
}
.addTo(lifecycleDisposable)
}
}
)
}
)
}
}
}

View File

@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.components.settings.conversation
import android.database.Cursor
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@@ -8,11 +7,13 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.subjects.PublishSubject
import io.reactivex.rxjava3.subjects.Subject
import org.signal.core.util.Result
import org.signal.core.util.ThreadUtil
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.readToList
@@ -25,8 +26,10 @@ 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.ui.GroupChangeFailureReason
import org.thoughtcrime.securesms.groups.v2.GroupAddMembersResult
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.recipients.RecipientUtil
@@ -37,6 +40,7 @@ import org.thoughtcrime.securesms.util.livedata.Store
sealed class ConversationSettingsViewModel(
private val callMessageIds: LongArray,
private val repository: ConversationSettingsRepository,
private val messageRequestRepository: MessageRequestRepository,
specificSettingsState: SpecificSettingsState
) : ViewModel() {
@@ -90,6 +94,27 @@ sealed class ConversationSettingsViewModel(
sharedMediaUpdateTrigger.postValue(Unit)
}
fun onReportSpam(): Maybe<Unit> {
return if (store.state.threadId > 0 && store.state.recipient != Recipient.UNKNOWN) {
messageRequestRepository.reportSpamMessageRequest(store.state.recipient.id, store.state.threadId)
.observeOn(AndroidSchedulers.mainThread())
.toSingle { Unit }
.toMaybe()
} else {
Maybe.empty()
}
}
fun onBlockAndReportSpam(): Maybe<Result<Unit, GroupChangeFailureReason>> {
return if (store.state.threadId > 0 && store.state.recipient != Recipient.UNKNOWN) {
messageRequestRepository.blockAndReportSpamMessageRequest(store.state.recipient.id, store.state.threadId)
.observeOn(AndroidSchedulers.mainThread())
.toMaybe()
} else {
Maybe.empty()
}
}
open fun refreshRecipient(): Unit = error("This ViewModel does not support this interaction")
abstract fun setMuteUntil(muteUntil: Long)
@@ -112,19 +137,15 @@ sealed class ConversationSettingsViewModel(
disposable.clear()
}
private fun Cursor?.ensureClosed() {
if (this != null && !this.isClosed) {
this.close()
}
}
private class RecipientSettingsViewModel(
private val recipientId: RecipientId,
private val callMessageIds: LongArray,
private val repository: ConversationSettingsRepository
private val repository: ConversationSettingsRepository,
messageRequestRepository: MessageRequestRepository
) : ConversationSettingsViewModel(
callMessageIds,
repository,
messageRequestRepository,
SpecificSettingsState.RecipientSettingsState()
) {
@@ -252,8 +273,9 @@ sealed class ConversationSettingsViewModel(
private class GroupSettingsViewModel(
private val groupId: GroupId,
private val callMessageIds: LongArray,
private val repository: ConversationSettingsRepository
) : ConversationSettingsViewModel(callMessageIds, repository, SpecificSettingsState.GroupSettingsState(groupId)) {
private val repository: ConversationSettingsRepository,
messageRequestRepository: MessageRequestRepository
) : ConversationSettingsViewModel(callMessageIds, repository, messageRequestRepository, SpecificSettingsState.GroupSettingsState(groupId)) {
private val liveGroup = LiveGroup(groupId)
@@ -465,15 +487,16 @@ sealed class ConversationSettingsViewModel(
private val recipientId: RecipientId? = null,
private val groupId: GroupId? = null,
private val callMessageIds: LongArray,
private val repository: ConversationSettingsRepository
private val repository: ConversationSettingsRepository,
private val messageRequestRepository: MessageRequestRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return requireNotNull(
modelClass.cast(
when {
recipientId != null -> RecipientSettingsViewModel(recipientId, callMessageIds, repository)
groupId != null -> GroupSettingsViewModel(groupId, callMessageIds, repository)
recipientId != null -> RecipientSettingsViewModel(recipientId, callMessageIds, repository, messageRequestRepository)
groupId != null -> GroupSettingsViewModel(groupId, callMessageIds, repository, messageRequestRepository)
else -> error("One of RecipientId or GroupId required.")
}
)