Improve remote delete handling in group story threads.
This commit is contained in:
parent
8b1552952c
commit
944c8530d8
8 changed files with 246 additions and 33 deletions
|
@ -36,13 +36,22 @@ class StoryGroupReplyDataSource(private val parentStoryId: Long) : PagedDataSour
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readRowFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
|
private fun readRowFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
|
||||||
return if (MmsSmsColumns.Types.isStoryReaction(record.type)) {
|
return when {
|
||||||
readReactionFromRecord(record)
|
record.isRemoteDelete -> readRemoteDeleteFromRecord(record)
|
||||||
} else {
|
MmsSmsColumns.Types.isStoryReaction(record.type) -> readReactionFromRecord(record)
|
||||||
readTextFromRecord(record)
|
else -> readTextFromRecord(record)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun readRemoteDeleteFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
|
||||||
|
return StoryGroupReplyItemData(
|
||||||
|
key = StoryGroupReplyItemData.Key.RemoteDelete(record.id),
|
||||||
|
sender = if (record.isOutgoing) Recipient.self() else record.individualRecipient.resolve(),
|
||||||
|
sentAtMillis = record.dateSent,
|
||||||
|
replyBody = StoryGroupReplyItemData.ReplyBody.RemoteDelete(record)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun readReactionFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
|
private fun readReactionFromRecord(record: MmsMessageRecord): StoryGroupReplyItemData {
|
||||||
return StoryGroupReplyItemData(
|
return StoryGroupReplyItemData(
|
||||||
key = StoryGroupReplyItemData.Key.Reaction(record.id),
|
key = StoryGroupReplyItemData.Key.Reaction(record.id),
|
||||||
|
|
|
@ -181,9 +181,6 @@ class StoryGroupReplyFragment :
|
||||||
requireContext(),
|
requireContext(),
|
||||||
it.sender
|
it.sender
|
||||||
),
|
),
|
||||||
onPrivateReplyClick = { model ->
|
|
||||||
requireListener<Callback>().onStartDirectReply(model.storyGroupReplyItemData.sender.id)
|
|
||||||
},
|
|
||||||
onCopyClick = { model ->
|
onCopyClick = { model ->
|
||||||
val clipData = ClipData.newPlainText(requireContext().getString(R.string.app_name), model.text.message.getDisplayBody(requireContext()))
|
val clipData = ClipData.newPlainText(requireContext().getString(R.string.app_name), model.text.message.getDisplayBody(requireContext()))
|
||||||
ServiceUtil.getClipboardManager(requireContext()).setPrimaryClip(clipData)
|
ServiceUtil.getClipboardManager(requireContext()).setPrimaryClip(clipData)
|
||||||
|
@ -216,6 +213,25 @@ class StoryGroupReplyFragment :
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is StoryGroupReplyItemData.ReplyBody.RemoteDelete -> {
|
||||||
|
customPref(
|
||||||
|
StoryGroupReplyItem.RemoteDeleteModel(
|
||||||
|
storyGroupReplyItemData = it,
|
||||||
|
remoteDelete = it.replyBody,
|
||||||
|
nameColor = colorizer.getIncomingGroupSenderColor(
|
||||||
|
requireContext(),
|
||||||
|
it.sender
|
||||||
|
),
|
||||||
|
onDeleteClick = { model ->
|
||||||
|
lifecycleDisposable += DeleteDialog.show(requireActivity(), setOf(model.remoteDelete.messageRecord)).subscribe { didDeleteThread ->
|
||||||
|
if (didDeleteThread) {
|
||||||
|
throw AssertionError("We should never end up deleting a Group Thread like this.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.util.DateUtils
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
||||||
import org.thoughtcrime.securesms.util.visible
|
import org.thoughtcrime.securesms.util.visible
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -38,13 +39,13 @@ object StoryGroupReplyItem {
|
||||||
fun register(mappingAdapter: MappingAdapter) {
|
fun register(mappingAdapter: MappingAdapter) {
|
||||||
mappingAdapter.registerFactory(TextModel::class.java, LayoutFactory(::TextViewHolder, R.layout.stories_group_text_reply_item))
|
mappingAdapter.registerFactory(TextModel::class.java, LayoutFactory(::TextViewHolder, R.layout.stories_group_text_reply_item))
|
||||||
mappingAdapter.registerFactory(ReactionModel::class.java, LayoutFactory(::ReactionViewHolder, R.layout.stories_group_reaction_reply_item))
|
mappingAdapter.registerFactory(ReactionModel::class.java, LayoutFactory(::ReactionViewHolder, R.layout.stories_group_reaction_reply_item))
|
||||||
|
mappingAdapter.registerFactory(RemoteDeleteModel::class.java, LayoutFactory(::RemoteDeleteViewHolder, R.layout.stories_group_remote_delete_item))
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextModel(
|
class TextModel(
|
||||||
val storyGroupReplyItemData: StoryGroupReplyItemData,
|
val storyGroupReplyItemData: StoryGroupReplyItemData,
|
||||||
val text: StoryGroupReplyItemData.ReplyBody.Text,
|
val text: StoryGroupReplyItemData.ReplyBody.Text,
|
||||||
@ColorInt val nameColor: Int,
|
@ColorInt val nameColor: Int,
|
||||||
val onPrivateReplyClick: (TextModel) -> Unit,
|
|
||||||
val onCopyClick: (TextModel) -> Unit,
|
val onCopyClick: (TextModel) -> Unit,
|
||||||
val onDeleteClick: (TextModel) -> Unit,
|
val onDeleteClick: (TextModel) -> Unit,
|
||||||
val onMentionClick: (RecipientId) -> Unit
|
val onMentionClick: (RecipientId) -> Unit
|
||||||
|
@ -74,6 +75,35 @@ object StoryGroupReplyItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RemoteDeleteModel(
|
||||||
|
val storyGroupReplyItemData: StoryGroupReplyItemData,
|
||||||
|
val remoteDelete: StoryGroupReplyItemData.ReplyBody.RemoteDelete,
|
||||||
|
val onDeleteClick: (RemoteDeleteModel) -> Unit,
|
||||||
|
@ColorInt val nameColor: Int
|
||||||
|
) : MappingModel<RemoteDeleteModel> {
|
||||||
|
override fun areItemsTheSame(newItem: RemoteDeleteModel): Boolean {
|
||||||
|
return storyGroupReplyItemData.sender == newItem.storyGroupReplyItemData.sender &&
|
||||||
|
storyGroupReplyItemData.sentAtMillis == newItem.storyGroupReplyItemData.sentAtMillis
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(newItem: RemoteDeleteModel): Boolean {
|
||||||
|
return storyGroupReplyItemData == newItem.storyGroupReplyItemData &&
|
||||||
|
storyGroupReplyItemData.sender.hasSameContent(newItem.storyGroupReplyItemData.sender) &&
|
||||||
|
nameColor == newItem.nameColor
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChangePayload(newItem: RemoteDeleteModel): Any? {
|
||||||
|
return if (nameColor != newItem.nameColor &&
|
||||||
|
storyGroupReplyItemData == newItem.storyGroupReplyItemData &&
|
||||||
|
storyGroupReplyItemData.sender.hasSameContent(newItem.storyGroupReplyItemData.sender)
|
||||||
|
) {
|
||||||
|
NAME_COLOR_CHANGED
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ReactionModel(
|
class ReactionModel(
|
||||||
val storyGroupReplyItemData: StoryGroupReplyItemData,
|
val storyGroupReplyItemData: StoryGroupReplyItemData,
|
||||||
val reaction: StoryGroupReplyItemData.ReplyBody.Reaction,
|
val reaction: StoryGroupReplyItemData.ReplyBody.Reaction,
|
||||||
|
@ -104,13 +134,12 @@ object StoryGroupReplyItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TextViewHolder(itemView: View) : MappingViewHolder<TextModel>(itemView) {
|
private abstract class BaseViewHolder<T>(itemView: View) : MappingViewHolder<T>(itemView) {
|
||||||
|
protected val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
||||||
private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
protected val name: FromTextView = itemView.findViewById(R.id.name)
|
||||||
private val name: FromTextView = itemView.findViewById(R.id.name)
|
protected val body: EmojiTextView = itemView.findViewById(R.id.body)
|
||||||
private val body: EmojiTextView = itemView.findViewById(R.id.body)
|
protected val date: TextView = itemView.findViewById(R.id.viewed_at)
|
||||||
private val date: TextView = itemView.findViewById(R.id.viewed_at)
|
protected val dateBelow: TextView = itemView.findViewById(R.id.viewed_at_below)
|
||||||
private val dateBelow: TextView = itemView.findViewById(R.id.viewed_at_below)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
body.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
body.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
||||||
|
@ -123,6 +152,9 @@ object StoryGroupReplyItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TextViewHolder(itemView: View) : BaseViewHolder<TextModel>(itemView) {
|
||||||
|
|
||||||
override fun bind(model: TextModel) {
|
override fun bind(model: TextModel) {
|
||||||
itemView.setOnLongClickListener {
|
itemView.setOnLongClickListener {
|
||||||
|
@ -179,6 +211,39 @@ object StoryGroupReplyItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class RemoteDeleteViewHolder(itemView: View) : BaseViewHolder<RemoteDeleteModel>(itemView) {
|
||||||
|
|
||||||
|
override fun bind(model: RemoteDeleteModel) {
|
||||||
|
itemView.setOnLongClickListener {
|
||||||
|
displayContextMenu(model)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
name.setTextColor(model.nameColor)
|
||||||
|
if (payload.contains(NAME_COLOR_CHANGED)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarUtil.loadIconIntoImageView(model.storyGroupReplyItemData.sender, avatar, DimensionUnit.DP.toPixels(28f).toInt())
|
||||||
|
name.text = resolveName(context, model.storyGroupReplyItemData.sender)
|
||||||
|
|
||||||
|
date.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.storyGroupReplyItemData.sentAtMillis)
|
||||||
|
dateBelow.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.storyGroupReplyItemData.sentAtMillis)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun displayContextMenu(model: RemoteDeleteModel) {
|
||||||
|
itemView.isSelected = true
|
||||||
|
SignalContextMenu.Builder(itemView, itemView.rootView as ViewGroup)
|
||||||
|
.preferredHorizontalPosition(SignalContextMenu.HorizontalPosition.START)
|
||||||
|
.onDismiss { itemView.isSelected = false }
|
||||||
|
.show(
|
||||||
|
listOf(
|
||||||
|
ActionItem(R.drawable.ic_trash_24_solid_tinted, context.getString(R.string.StoryGroupReplyItem__delete)) { model.onDeleteClick(model) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ReactionViewHolder(itemView: View) : MappingViewHolder<ReactionModel>(itemView) {
|
private class ReactionViewHolder(itemView: View) : MappingViewHolder<ReactionModel>(itemView) {
|
||||||
private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
||||||
private val name: FromTextView = itemView.findViewById(R.id.name)
|
private val name: FromTextView = itemView.findViewById(R.id.name)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.thoughtcrime.securesms.stories.viewer.reply.group
|
package org.thoughtcrime.securesms.stories.viewer.reply.group
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
data class StoryGroupReplyItemData(
|
data class StoryGroupReplyItemData(
|
||||||
|
@ -12,10 +13,12 @@ data class StoryGroupReplyItemData(
|
||||||
sealed class ReplyBody {
|
sealed class ReplyBody {
|
||||||
data class Text(val message: ConversationMessage) : ReplyBody()
|
data class Text(val message: ConversationMessage) : ReplyBody()
|
||||||
data class Reaction(val emoji: CharSequence) : ReplyBody()
|
data class Reaction(val emoji: CharSequence) : ReplyBody()
|
||||||
|
data class RemoteDelete(val messageRecord: MessageRecord) : ReplyBody()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Key {
|
sealed class Key {
|
||||||
data class Text(val messageId: Long) : Key()
|
data class Text(val messageId: Long) : Key()
|
||||||
data class Reaction(val reactionId: Long) : Key()
|
data class Reaction(val reactionId: Long) : Key()
|
||||||
|
data class RemoteDelete(val messageId: Long) : Key()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ class StoryGroupReplyRepository {
|
||||||
|
|
||||||
val threadId = SignalDatabase.mms.getThreadIdForMessage(parentStoryId)
|
val threadId = SignalDatabase.mms.getThreadIdForMessage(parentStoryId)
|
||||||
|
|
||||||
|
ApplicationDependencies.getDatabaseObserver().registerMessageUpdateObserver(messageObserver)
|
||||||
ApplicationDependencies.getDatabaseObserver().registerMessageInsertObserver(threadId, messageObserver)
|
ApplicationDependencies.getDatabaseObserver().registerMessageInsertObserver(threadId, messageObserver)
|
||||||
ApplicationDependencies.getDatabaseObserver().registerConversationObserver(threadId, observer)
|
ApplicationDependencies.getDatabaseObserver().registerConversationObserver(threadId, observer)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||||
import org.thoughtcrime.securesms.database.model.StoryType
|
import org.thoughtcrime.securesms.database.model.StoryType
|
||||||
import org.thoughtcrime.securesms.mediasend.v2.UntrustedRecords
|
import org.thoughtcrime.securesms.mediasend.v2.UntrustedRecords
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage
|
||||||
|
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender
|
import org.thoughtcrime.securesms.sms.MessageSender
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,24 +41,26 @@ object StoryGroupReplySender {
|
||||||
Completable.create {
|
Completable.create {
|
||||||
MessageSender.send(
|
MessageSender.send(
|
||||||
context,
|
context,
|
||||||
OutgoingMediaMessage(
|
OutgoingSecureMediaMessage(
|
||||||
recipient,
|
OutgoingMediaMessage(
|
||||||
body.toString(),
|
recipient,
|
||||||
emptyList(),
|
body.toString(),
|
||||||
System.currentTimeMillis(),
|
emptyList(),
|
||||||
0,
|
System.currentTimeMillis(),
|
||||||
0L,
|
0,
|
||||||
false,
|
0L,
|
||||||
0,
|
false,
|
||||||
StoryType.NONE,
|
0,
|
||||||
ParentStoryId.GroupReply(message.id),
|
StoryType.NONE,
|
||||||
isReaction,
|
ParentStoryId.GroupReply(message.id),
|
||||||
null,
|
isReaction,
|
||||||
emptyList(),
|
null,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
mentions,
|
emptyList(),
|
||||||
emptySet(),
|
mentions,
|
||||||
emptySet()
|
emptySet(),
|
||||||
|
emptySet()
|
||||||
|
)
|
||||||
),
|
),
|
||||||
message.threadId,
|
message.threadId,
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -15,6 +15,17 @@ import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask
|
||||||
|
|
||||||
object DeleteDialog {
|
object DeleteDialog {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a deletion dialog for the given set of message records.
|
||||||
|
*
|
||||||
|
* @param context Android Context
|
||||||
|
* @param messageRecords The message records to delete
|
||||||
|
* @param title The dialog title
|
||||||
|
* @param message The dialog message, or null
|
||||||
|
* @param forceRemoteDelete Allow remote deletion, even if it would normally be disallowed
|
||||||
|
*
|
||||||
|
* @return a Single, who's value notes whether or not a thread deletion occurred.
|
||||||
|
*/
|
||||||
fun show(
|
fun show(
|
||||||
context: Context,
|
context: Context,
|
||||||
messageRecords: Set<MessageRecord>,
|
messageRecords: Set<MessageRecord>,
|
||||||
|
|
105
app/src/main/res/layout/stories_group_remote_delete_item.xml
Normal file
105
app/src/main/res/layout/stories_group_remote_delete_item.xml
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="8dp"
|
||||||
|
android:background="@drawable/selectable_list_item_background"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingHorizontal="8dp"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
android:paddingBottom="6dp">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp"
|
||||||
|
app:fallbackImageSize="small"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/bubble"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:background="@drawable/rounded_rectangle_secondary_18"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.FromTextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="7dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:textAppearance="@style/TextAppearance.Signal.Subtitle.Bold"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Miles Morales" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
|
android:id="@+id/body"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="1dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_marginBottom="1dp"
|
||||||
|
android:text="@string/ThreadRecord_this_message_was_deleted"
|
||||||
|
android:textAppearance="@style/Signal.Text.Body"
|
||||||
|
android:textStyle="italic"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/viewed_at_below"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/name"
|
||||||
|
app:layout_goneMarginBottom="7dp"
|
||||||
|
app:measureLastLine="true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/viewed_at"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:textAppearance="@style/Signal.Text.Caption"
|
||||||
|
android:textColor="@color/transparent_white_60"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="1"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/body"
|
||||||
|
tools:text="15m"
|
||||||
|
tools:textColor="@color/signal_text_secondary"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/viewed_at_below"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:textAppearance="@style/Signal.Text.Caption"
|
||||||
|
android:textColor="@color/transparent_white_60"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/bubble"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/bubble"
|
||||||
|
tools:text="15m"
|
||||||
|
tools:textColor="@color/signal_text_secondary" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Add table
Reference in a new issue