Add review banner to CFv2.
This commit is contained in:
parent
b785b3f887
commit
64ddd982fe
10 changed files with 261 additions and 111 deletions
|
@ -6,15 +6,17 @@
|
|||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.content.Context
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.transition.ChangeBounds
|
||||
import android.transition.Slide
|
||||
import android.transition.TransitionManager
|
||||
import android.transition.TransitionSet
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.transition.addListener
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.identity.UnverifiedBannerView
|
||||
|
@ -22,7 +24,15 @@ import org.thoughtcrime.securesms.components.reminder.Reminder
|
|||
import org.thoughtcrime.securesms.components.reminder.ReminderView
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewBannerView
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.ContextUtil
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil
|
||||
import org.thoughtcrime.securesms.util.SpanUtil
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.views.Stub
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
/**
|
||||
* Responsible for showing the various "banner" views at the top of a conversation
|
||||
|
@ -39,11 +49,9 @@ class ConversationBannerView @JvmOverloads constructor(
|
|||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayoutCompat(context, attrs, defStyleAttr) {
|
||||
|
||||
private val inflater: LayoutInflater by lazy { LayoutInflater.from(context) }
|
||||
|
||||
private var reminderView: ReminderView? = null
|
||||
private var unverifiedBannerView: UnverifiedBannerView? = null
|
||||
private val unverifiedBannerStub: Stub<UnverifiedBannerView> by lazy { ViewUtil.findStubById(this, R.id.unverified_banner_stub) }
|
||||
private val reminderStub: Stub<ReminderView> by lazy { ViewUtil.findStubById(this, R.id.reminder_stub) }
|
||||
private val reviewBannerStub: Stub<ReviewBannerView> by lazy { ViewUtil.findStubById(this, R.id.review_banner_stub) }
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
|
@ -52,114 +60,125 @@ class ConversationBannerView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
fun showReminder(reminder: Reminder) {
|
||||
reminderView = show(
|
||||
position = -1,
|
||||
existingView = reminderView,
|
||||
create = { ReminderView(context) },
|
||||
bind = {
|
||||
showReminder(reminder)
|
||||
setOnActionClickListener {
|
||||
when (it) {
|
||||
R.id.reminder_action_update_now -> listener?.updateAppAction()
|
||||
R.id.reminder_action_re_register -> listener?.reRegisterAction()
|
||||
R.id.reminder_action_review_join_requests -> listener?.reviewJoinRequestsAction()
|
||||
R.id.reminder_action_gv1_suggestion_no_thanks -> listener?.gv1SuggestionsAction(it)
|
||||
R.id.reminder_action_bubble_not_now, R.id.reminder_action_bubble_turn_off -> {
|
||||
listener?.changeBubbleSettingAction(disableSetting = it == R.id.reminder_action_bubble_turn_off)
|
||||
}
|
||||
show(
|
||||
stub = reminderStub
|
||||
) {
|
||||
showReminder(reminder)
|
||||
setOnActionClickListener {
|
||||
when (it) {
|
||||
R.id.reminder_action_update_now -> listener?.updateAppAction()
|
||||
R.id.reminder_action_re_register -> listener?.reRegisterAction()
|
||||
R.id.reminder_action_review_join_requests -> listener?.reviewJoinRequestsAction()
|
||||
R.id.reminder_action_gv1_suggestion_no_thanks -> listener?.gv1SuggestionsAction(it)
|
||||
R.id.reminder_action_bubble_not_now, R.id.reminder_action_bubble_turn_off -> {
|
||||
listener?.changeBubbleSettingAction(disableSetting = it == R.id.reminder_action_bubble_turn_off)
|
||||
}
|
||||
}
|
||||
setOnHideListener {
|
||||
clearReminder()
|
||||
true
|
||||
}
|
||||
}
|
||||
)
|
||||
setOnHideListener {
|
||||
clearReminder()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearReminder() {
|
||||
removeIfNotNull(reminderView)
|
||||
reminderView = null
|
||||
hide(reminderStub)
|
||||
}
|
||||
|
||||
fun showUnverifiedBanner(identityRecords: IdentityRecordList) {
|
||||
unverifiedBannerView = show(
|
||||
position = 0,
|
||||
existingView = null,
|
||||
create = { UnverifiedBannerView(context) },
|
||||
bind = {
|
||||
setOnHideListener {
|
||||
clearUnverifiedBanner()
|
||||
true
|
||||
}
|
||||
display(
|
||||
IdentityUtil.getUnverifiedBannerDescription(context, identityRecords.unverifiedRecipients)!!,
|
||||
identityRecords.unverifiedRecords,
|
||||
{ listener?.onUnverifiedBannerClicked(identityRecords.unverifiedRecords) },
|
||||
{ listener?.onUnverifiedBannerDismissed(identityRecords.unverifiedRecords) }
|
||||
)
|
||||
show(
|
||||
stub = unverifiedBannerStub
|
||||
) {
|
||||
setOnHideListener {
|
||||
clearUnverifiedBanner()
|
||||
true
|
||||
}
|
||||
)
|
||||
display(
|
||||
IdentityUtil.getUnverifiedBannerDescription(context, identityRecords.unverifiedRecipients)!!,
|
||||
identityRecords.unverifiedRecords,
|
||||
{ listener?.onUnverifiedBannerClicked(identityRecords.unverifiedRecords) },
|
||||
{ listener?.onUnverifiedBannerDismissed(identityRecords.unverifiedRecords) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearUnverifiedBanner() {
|
||||
removeIfNotNull(unverifiedBannerView)
|
||||
unverifiedBannerView = null
|
||||
hide(unverifiedBannerStub)
|
||||
}
|
||||
|
||||
private fun <V : View> show(position: Int, existingView: V?, create: () -> V, bind: V.() -> Unit = {}): V {
|
||||
val view: V = if (existingView != null) {
|
||||
existingView
|
||||
} else {
|
||||
val newView: V = create()
|
||||
fun showReviewBanner(requestReviewState: RequestReviewState) {
|
||||
show(
|
||||
stub = reviewBannerStub
|
||||
) {
|
||||
if (requestReviewState.individualReviewState != null) {
|
||||
val message: CharSequence = SpannableStringBuilder()
|
||||
.append(SpanUtil.bold(context.getString(R.string.ConversationFragment__review_requests_carefully)))
|
||||
.append(" ")
|
||||
.append(context.getString(R.string.ConversationFragment__signal_found_another_contact_with_the_same_name))
|
||||
|
||||
TransitionManager.beginDelayedTransition(this, Slide(Gravity.TOP))
|
||||
if (position in 0..childCount) {
|
||||
addView(newView, position, defaultLayoutParams())
|
||||
} else {
|
||||
addView(newView, defaultLayoutParams())
|
||||
setBannerMessage(message)
|
||||
|
||||
val drawable = ContextUtil.requireDrawable(context, R.drawable.symbol_info_24).mutate()
|
||||
DrawableCompat.setTint(drawable, ContextCompat.getColor(context, R.color.signal_icon_tint_primary))
|
||||
setBannerIcon(drawable)
|
||||
setOnClickListener { listener?.onRequestReviewIndividual(requestReviewState.individualReviewState.recipient.id) }
|
||||
} else if (requestReviewState.groupReviewState != null) {
|
||||
setBannerMessage(context.getString(R.string.ConversationFragment__d_group_members_have_the_same_name, requestReviewState.groupReviewState.count))
|
||||
setBannerRecipient(requestReviewState.groupReviewState.recipient)
|
||||
setOnClickListener { listener?.onReviewGroupMembers(requestReviewState.groupReviewState.groupId) }
|
||||
}
|
||||
newView
|
||||
|
||||
setOnHideListener {
|
||||
clearRequestReview()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearRequestReview() {
|
||||
hide(reviewBannerStub)
|
||||
}
|
||||
|
||||
private fun <V : View> show(stub: Stub<V>, bind: V.() -> Unit = {}) {
|
||||
TransitionManager.beginDelayedTransition(this, Slide(Gravity.TOP))
|
||||
stub.get().bind()
|
||||
stub.get().visible = true
|
||||
}
|
||||
|
||||
private fun hide(stub: Stub<*>) {
|
||||
if (!stub.isVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
view.bind()
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun defaultLayoutParams(): LayoutParams {
|
||||
return LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
private fun removeIfNotNull(view: View?) {
|
||||
if (view != null) {
|
||||
val slideTransition = Slide(Gravity.TOP).apply {
|
||||
addListener(
|
||||
onEnd = {
|
||||
layoutParams = layoutParams.apply { height = LayoutParams.WRAP_CONTENT }
|
||||
}
|
||||
)
|
||||
val slideTransition = Slide(Gravity.TOP)
|
||||
val changeTransition = ChangeBounds().apply {
|
||||
if (reminderStub.isVisible) {
|
||||
addTarget(reminderStub.get())
|
||||
}
|
||||
|
||||
val changeTransition = ChangeBounds().apply {
|
||||
if (reminderView != null) {
|
||||
addTarget(reminderView)
|
||||
}
|
||||
|
||||
if (unverifiedBannerView != null) {
|
||||
addTarget(unverifiedBannerView)
|
||||
}
|
||||
if (unverifiedBannerStub.isVisible) {
|
||||
addTarget(unverifiedBannerStub.get())
|
||||
}
|
||||
|
||||
val transition = TransitionSet().apply {
|
||||
addTransition(slideTransition)
|
||||
addTransition(changeTransition)
|
||||
if (reviewBannerStub.isVisible) {
|
||||
addTarget(reviewBannerStub.get())
|
||||
}
|
||||
|
||||
layoutParams = layoutParams.apply { height = this@ConversationBannerView.height }
|
||||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
removeView(view)
|
||||
}
|
||||
|
||||
val transition = TransitionSet().apply {
|
||||
addTransition(slideTransition)
|
||||
addTransition(changeTransition)
|
||||
addListener(
|
||||
onEnd = {
|
||||
layoutParams = layoutParams.apply { height = LayoutParams.WRAP_CONTENT }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
layoutParams = layoutParams.apply { height = this@ConversationBannerView.height }
|
||||
TransitionManager.beginDelayedTransition(this, transition)
|
||||
stub.get().visible = false
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
|
@ -170,5 +189,7 @@ class ConversationBannerView @JvmOverloads constructor(
|
|||
fun changeBubbleSettingAction(disableSetting: Boolean)
|
||||
fun onUnverifiedBannerClicked(unverifiedIdentities: List<IdentityRecord>)
|
||||
fun onUnverifiedBannerDismissed(unverifiedIdentities: List<IdentityRecord>)
|
||||
fun onRequestReviewIndividual(recipientId: RecipientId)
|
||||
fun onReviewGroupMembers(groupId: GroupId.V2)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,6 +175,7 @@ import org.thoughtcrime.securesms.mms.SlideDeck
|
|||
import org.thoughtcrime.securesms.notifications.v2.ConversationId
|
||||
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment
|
||||
import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment
|
||||
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
@ -381,11 +382,6 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
|||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
|
||||
private fun observeConversationThread() {
|
||||
var firstRender = true
|
||||
disposables += viewModel
|
||||
|
@ -523,18 +519,11 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy { presentIdentityRecordsState(it) }
|
||||
.addTo(disposables)
|
||||
}
|
||||
|
||||
private fun presentIdentityRecordsState(identityRecordsState: IdentityRecordsState) {
|
||||
if (!identityRecordsState.isGroup) {
|
||||
binding.conversationTitleView.root.setVerified(identityRecordsState.isVerified)
|
||||
}
|
||||
|
||||
if (identityRecordsState.isUnverified) {
|
||||
binding.conversationBanner.showUnverifiedBanner(identityRecordsState.identityRecords)
|
||||
} else {
|
||||
binding.conversationBanner.clearUnverifiedBanner()
|
||||
}
|
||||
viewModel
|
||||
.getRequestReviewState()
|
||||
.subscribeBy { presentRequestReviewState(it) }
|
||||
.addTo(disposables)
|
||||
}
|
||||
|
||||
private fun presentInputReadyState(inputReadyState: InputReadyState) {
|
||||
|
@ -561,6 +550,26 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
|||
}
|
||||
}
|
||||
|
||||
private fun presentIdentityRecordsState(identityRecordsState: IdentityRecordsState) {
|
||||
if (!identityRecordsState.isGroup) {
|
||||
binding.conversationTitleView.root.setVerified(identityRecordsState.isVerified)
|
||||
}
|
||||
|
||||
if (identityRecordsState.isUnverified) {
|
||||
binding.conversationBanner.showUnverifiedBanner(identityRecordsState.identityRecords)
|
||||
} else {
|
||||
binding.conversationBanner.clearUnverifiedBanner()
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentRequestReviewState(requestReviewState: RequestReviewState) {
|
||||
if (requestReviewState.shouldShowReviewBanner()) {
|
||||
binding.conversationBanner.showReviewBanner(requestReviewState)
|
||||
} else {
|
||||
binding.conversationBanner.clearRequestReview()
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateCharactersRemaining() {
|
||||
val messageBody: String = binding.conversationInputPanel.embeddedTextEditor.textTrimmed.toString()
|
||||
val charactersLeftView: TextView = binding.conversationInputSpaceLeft
|
||||
|
@ -1969,6 +1978,14 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
|||
override fun onUnverifiedBannerDismissed(unverifiedIdentities: List<IdentityRecord>) {
|
||||
viewModel.resetVerifiedStatusToDefault(unverifiedIdentities)
|
||||
}
|
||||
|
||||
override fun onRequestReviewIndividual(recipientId: RecipientId) {
|
||||
ReviewCardDialogFragment.createForReviewRequest(recipientId).show(childFragmentManager, null)
|
||||
}
|
||||
|
||||
override fun onReviewGroupMembers(groupId: GroupId.V2) {
|
||||
ReviewCardDialogFragment.createForReviewMembers(groupId).show(childFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
|
|
@ -33,6 +33,8 @@ import org.thoughtcrime.securesms.conversation.ConversationMessage
|
|||
import org.thoughtcrime.securesms.conversation.colors.GroupAuthorNameColorHelper
|
||||
import org.thoughtcrime.securesms.conversation.colors.NameColor
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
|
||||
import org.thoughtcrime.securesms.conversation.v2.RequestReviewState.GroupReviewState
|
||||
import org.thoughtcrime.securesms.conversation.v2.RequestReviewState.IndividualReviewState
|
||||
import org.thoughtcrime.securesms.conversation.v2.data.ConversationDataSource
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock
|
||||
import org.thoughtcrime.securesms.database.GroupTable
|
||||
|
@ -53,10 +55,12 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
|||
import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob
|
||||
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestState
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException
|
||||
|
@ -285,6 +289,35 @@ class ConversationRepository(
|
|||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getRequestReviewState(recipient: Recipient, group: GroupRecord?, messageRequest: MessageRequestState): Single<RequestReviewState> {
|
||||
return Single.fromCallable {
|
||||
if (group == null && messageRequest != MessageRequestState.INDIVIDUAL) {
|
||||
return@fromCallable RequestReviewState()
|
||||
}
|
||||
|
||||
if (group == null && ReviewUtil.isRecipientReviewSuggested(recipient.id)) {
|
||||
return@fromCallable RequestReviewState(individualReviewState = IndividualReviewState(recipient))
|
||||
}
|
||||
|
||||
if (group != null && group.isV2Group) {
|
||||
val groupId = group.id.requireV2()
|
||||
val duplicateRecipients: List<Recipient> = ReviewUtil.getDuplicatedRecipients(groupId).map { it.recipient }
|
||||
|
||||
if (duplicateRecipients.isNotEmpty()) {
|
||||
return@fromCallable RequestReviewState(
|
||||
groupReviewState = GroupReviewState(
|
||||
groupId,
|
||||
duplicateRecipients[0],
|
||||
duplicateRecipients.size
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
RequestReviewState()
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getTemporaryViewOnceUri(mmsMessageRecord: MmsMessageRecord): Maybe<Uri> {
|
||||
return Maybe.fromCallable<Uri> {
|
||||
Log.i(TAG, "Copying the view-once photo to temp storage and deleting underlying media.")
|
||||
|
|
|
@ -94,6 +94,7 @@ class ConversationViewModel(
|
|||
val wallpaperSnapshot: ChatWallpaper?
|
||||
get() = recipientSnapshot?.wallpaper
|
||||
|
||||
private val _inputReadyState: Observable<InputReadyState>
|
||||
val inputReadyState: Observable<InputReadyState>
|
||||
|
||||
private val hasMessageRequestStateSubject: BehaviorSubject<Boolean> = BehaviorSubject.createDefault(false)
|
||||
|
@ -157,7 +158,7 @@ class ConversationViewModel(
|
|||
)
|
||||
}
|
||||
|
||||
inputReadyState = Observable.combineLatest(
|
||||
_inputReadyState = Observable.combineLatest(
|
||||
recipientRepository.conversationRecipient,
|
||||
recipientRepository.groupRecord
|
||||
) { recipient, groupRecord ->
|
||||
|
@ -170,7 +171,8 @@ class ConversationViewModel(
|
|||
)
|
||||
}.doOnNext {
|
||||
hasMessageRequestStateSubject.onNext(it.messageRequestState != MessageRequestState.NONE)
|
||||
}.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
inputReadyState = _inputReadyState.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
recipientRepository.conversationRecipient.map { Unit }.subscribeWithSubject(refreshReminder, disposables)
|
||||
|
||||
|
@ -264,4 +266,11 @@ class ConversationViewModel(
|
|||
fun copyToClipboard(context: Context, messageParts: Set<MultiselectPart>): Maybe<CharSequence> {
|
||||
return repository.copyToClipboard(context, messageParts)
|
||||
}
|
||||
|
||||
fun getRequestReviewState(): Observable<RequestReviewState> {
|
||||
return _inputReadyState
|
||||
.flatMapSingle { (recipient, messageRequestState, group) -> repository.getRequestReviewState(recipient, group, messageRequestState) }
|
||||
.distinctUntilChanged()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||
data class InputReadyState(
|
||||
val conversationRecipient: Recipient,
|
||||
val messageRequestState: MessageRequestState,
|
||||
private val groupRecord: GroupRecord?,
|
||||
val groupRecord: GroupRecord?,
|
||||
val isClientExpired: Boolean,
|
||||
val isUnauthorized: Boolean
|
||||
) {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
/**
|
||||
* Indicates if we should present an additional review warning banner
|
||||
* for an individual or group.
|
||||
*/
|
||||
data class RequestReviewState(
|
||||
val individualReviewState: IndividualReviewState? = null,
|
||||
val groupReviewState: GroupReviewState? = null
|
||||
) {
|
||||
|
||||
fun shouldShowReviewBanner(): Boolean {
|
||||
return individualReviewState != null || groupReviewState != null
|
||||
}
|
||||
|
||||
/** Recipient is in message request state and has similar name as someone else */
|
||||
data class IndividualReviewState(val recipient: Recipient)
|
||||
|
||||
/** Group has multiple members with similar names */
|
||||
data class GroupReviewState(val groupId: GroupId.V2, val recipient: Recipient, val count: Int)
|
||||
}
|
|
@ -38,7 +38,6 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import kotlin.Unit;
|
||||
|
|
|
@ -30,6 +30,7 @@ public class ReviewBannerView extends LinearLayout {
|
|||
private AvatarImageView topLeftAvatar;
|
||||
private AvatarImageView bottomRightAvatar;
|
||||
private View stroke;
|
||||
private OnHideListener onHideListener;
|
||||
|
||||
public ReviewBannerView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
@ -55,7 +56,17 @@ public class ReviewBannerView extends LinearLayout {
|
|||
topLeftAvatar.setFallbackPhotoProvider(provider);
|
||||
bottomRightAvatar.setFallbackPhotoProvider(provider);
|
||||
|
||||
bannerClose.setOnClickListener(v -> setVisibility(GONE));
|
||||
bannerClose.setOnClickListener(v -> {
|
||||
if (onHideListener != null && onHideListener.onHide()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setVisibility(GONE);
|
||||
});
|
||||
}
|
||||
|
||||
public void setOnHideListener(@Nullable OnHideListener onHideListener) {
|
||||
this.onHideListener = onHideListener;
|
||||
}
|
||||
|
||||
public void setBannerMessage(@Nullable CharSequence charSequence) {
|
||||
|
@ -121,4 +132,8 @@ public class ReviewBannerView extends LinearLayout {
|
|||
return new FallbackPhoto20dp(getFallbackResId()).asDrawable(context, color, inverted);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnHideListener {
|
||||
boolean onHide();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,4 +43,8 @@ public class Stub<T extends View> {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -114,7 +114,30 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="@id/parent_end_guideline"
|
||||
app:layout_constraintStart_toStartOf="@id/parent_start_guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar">
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/unverified_banner_stub"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/unverified_banner"
|
||||
android:layout="@layout/conversation_activity_unverified_banner_stub" />
|
||||
|
||||
<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" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/review_banner_stub"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/review_banner"
|
||||
android:layout="@layout/review_banner_view" />
|
||||
|
||||
</org.thoughtcrime.securesms.conversation.v2.ConversationBannerView>
|
||||
|
||||
<org.thoughtcrime.securesms.components.ConversationScrollToView
|
||||
android:id="@+id/scroll_to_mention"
|
||||
|
|
Loading…
Add table
Reference in a new issue