Loading app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt +4 −0 Original line number Diff line number Diff line Loading @@ -463,6 +463,10 @@ class MessageListAdapter internal constructor( item.messageUid == activeMessage.uid } fun isSelected(item: MessageListItem): Boolean { return item.uniqueId in selected } fun toggleSelection(item: MessageListItem) { if (messagesMap[item.uniqueId] == null) { // MessageListItem is no longer in the list Loading app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt +1 −1 Original line number Diff line number Diff line Loading @@ -264,7 +264,7 @@ class MessageListFragment : val itemTouchHelper = ItemTouchHelper( MessageListSwipeCallback( resources, requireContext(), resourceProvider = SwipeResourceProvider(requireActivity().theme), swipeActionSupportProvider, swipeRightAction = K9.swipeRightAction, Loading app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListSwipeCallback.kt +72 −68 Original line number Diff line number Diff line package com.fsck.k9.ui.messagelist import android.content.res.Resources import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.view.LayoutInflater import android.view.View import androidx.core.graphics.withSave import android.view.View.MeasureSpec import android.widget.ImageView import android.widget.TextView import androidx.core.graphics.withTranslation import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder Loading @@ -12,8 +17,9 @@ import com.fsck.k9.SwipeAction import com.fsck.k9.ui.R import kotlin.math.abs @SuppressLint("InflateParams") class MessageListSwipeCallback( resources: Resources, context: Context, private val resourceProvider: SwipeResourceProvider, private val swipeActionSupportProvider: SwipeActionSupportProvider, private val swipeRightAction: SwipeAction, Loading @@ -21,10 +27,19 @@ class MessageListSwipeCallback( private val adapter: MessageListAdapter, private val listener: MessageListSwipeListener ) : ItemTouchHelper.Callback() { private val iconPadding = resources.getDimension(R.dimen.messageListSwipeIconPadding).toInt() private val swipeThreshold = resources.getDimension(R.dimen.messageListSwipeThreshold) private val swipeThreshold = context.resources.getDimension(R.dimen.messageListSwipeThreshold) private val backgroundColorPaint = Paint() private val swipeRightLayout: View private val swipeLeftLayout: View init { val layoutInflater = LayoutInflater.from(context) swipeRightLayout = layoutInflater.inflate(R.layout.swipe_right_action, null, false) swipeLeftLayout = layoutInflater.inflate(R.layout.swipe_left_action, null, false) } override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: ViewHolder): Int { if (viewHolder !is MessageViewHolder) return 0 Loading Loading @@ -82,89 +97,78 @@ class MessageListSwipeCallback( actionState: Int, isCurrentlyActive: Boolean ) { canvas.withSave { val view = viewHolder.itemView val viewWidth = view.width val viewHeight = view.height val holder = viewHolder as MessageViewHolder val item = adapter.getItemById(holder.uniqueId) ?: return@withSave val isViewAnimatingBack = !isCurrentlyActive && abs(dX).toInt() >= viewWidth val swipeThreshold = recyclerView.width * getSwipeThreshold(holder) val swipeThresholdReached = abs(dX) > swipeThreshold if (swipeThresholdReached) { val action = if (dX > 0) swipeRightAction else swipeLeftAction val backgroundColor = resourceProvider.getBackgroundColor(action) drawBackground(view, backgroundColor) canvas.withTranslation(x = view.left.toFloat(), y = view.top.toFloat()) { if (isViewAnimatingBack) { drawBackground(dX, viewWidth, viewHeight) } else { val backgroundColor = resourceProvider.getBackgroundColor(SwipeAction.None) drawBackground(view, backgroundColor) } // Stop drawing the icon when the view has been animated all the way off the screen by ItemTouchHelper. // We do this so the icon doesn't switch state when RecyclerView's ItemAnimator animates the view back after // a toggle action (mark as read/unread, add/remove star) was used. if (isCurrentlyActive || abs(dX).toInt() < view.width) { drawIcon(dX, view, item, swipeThresholdReached) val holder = viewHolder as MessageViewHolder drawLayout(dX, viewWidth, viewHeight, holder) } } super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) } private fun Canvas.drawBackground(view: View, color: Int) { backgroundColorPaint.color = color private fun Canvas.drawBackground(dX: Float, width: Int, height: Int) { val swipeAction = if (dX > 0) swipeRightAction else swipeLeftAction val backgroundColor = resourceProvider.getBackgroundColor(swipeAction) backgroundColorPaint.color = backgroundColor drawRect( view.left.toFloat(), view.top.toFloat(), view.right.toFloat(), view.bottom.toFloat(), 0F, 0F, width.toFloat(), height.toFloat(), backgroundColorPaint ) } private fun Canvas.drawIcon(dX: Float, view: View, item: MessageListItem, swipeThresholdReached: Boolean) { if (dX > 0) { drawSwipeRightIcon(view, item, swipeThresholdReached) } else { drawSwipeLeftIcon(view, item, swipeThresholdReached) } } private fun Canvas.drawLayout(dX: Float, width: Int, height: Int, viewHolder: MessageViewHolder) { val item = adapter.getItemById(viewHolder.uniqueId) ?: return val isSelected = adapter.isSelected(item) val swipeRight = dX > 0 val swipeThresholdReached = abs(dX) > swipeThreshold private fun Canvas.drawSwipeRightIcon(view: View, item: MessageListItem, swipeThresholdReached: Boolean) { resourceProvider.getIcon(item, swipeRightAction)?.let { icon -> val iconLeft = iconPadding val iconTop = view.top + ((view.height - icon.intrinsicHeight) / 2) val iconRight = iconLeft + icon.intrinsicWidth val iconBottom = iconTop + icon.intrinsicHeight icon.setBounds(iconLeft, iconTop, iconRight, iconBottom) val swipeLayout = if (swipeRight) swipeRightLayout else swipeLeftLayout val swipeAction = if (swipeRight) swipeRightAction else swipeLeftAction val iconTint = if (swipeThresholdReached) { resourceProvider.iconTint val foregroundColor: Int val backgroundColor: Int if (swipeThresholdReached) { foregroundColor = resourceProvider.iconTint backgroundColor = resourceProvider.getBackgroundColor(swipeAction) } else { resourceProvider.getBackgroundColor(swipeRightAction) foregroundColor = resourceProvider.getBackgroundColor(swipeAction) backgroundColor = resourceProvider.getBackgroundColor(SwipeAction.None) } icon.setTint(iconTint) icon.draw(this) } } swipeLayout.setBackgroundColor(backgroundColor) private fun Canvas.drawSwipeLeftIcon(view: View, item: MessageListItem, swipeThresholdReached: Boolean) { resourceProvider.getIcon(item, swipeLeftAction)?.let { icon -> val iconRight = view.right - iconPadding val iconLeft = iconRight - icon.intrinsicWidth val iconTop = view.top + ((view.height - icon.intrinsicHeight) / 2) val iconBottom = iconTop + icon.intrinsicHeight icon.setBounds(iconLeft, iconTop, iconRight, iconBottom) val icon = resourceProvider.getIcon(item, swipeAction) icon.setTint(foregroundColor) val iconTint = if (swipeThresholdReached) { resourceProvider.iconTint } else { resourceProvider.getBackgroundColor(swipeLeftAction) } icon.setTint(iconTint) val iconView = swipeLayout.findViewById<ImageView>(R.id.swipe_action_icon) iconView.setImageDrawable(icon) icon.draw(this) val textView = swipeLayout.findViewById<TextView>(R.id.swipe_action_text) textView.setTextColor(foregroundColor) textView.text = resourceProvider.getActionName(item, swipeAction, isSelected) if (swipeLayout.isDirty) { val widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY) val heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) swipeLayout.measure(widthMeasureSpec, heightMeasureSpec) swipeLayout.layout(0, 0, width, height) } swipeLayout.draw(this) } } Loading app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/SwipeResourceProvider.kt +26 −2 Original line number Diff line number Diff line Loading @@ -30,9 +30,20 @@ class SwipeResourceProvider(val theme: Theme) { private val spamColor = theme.resolveColorAttribute(R.attr.messageListSwipeSpamBackgroundColor) private val moveColor = theme.resolveColorAttribute(R.attr.messageListSwipeMoveBackgroundColor) fun getIcon(item: MessageListItem, action: SwipeAction): Drawable? { private val selectText = theme.resources.getString(R.string.swipe_action_select) private val deselectText = theme.resources.getString(R.string.swipe_action_deselect) private val markAsReadText = theme.resources.getString(R.string.swipe_action_mark_as_read) private val markAsUnreadText = theme.resources.getString(R.string.swipe_action_mark_as_unread) private val addStarText = theme.resources.getString(R.string.swipe_action_add_star) private val removeStarText = theme.resources.getString(R.string.swipe_action_remove_star) private val archiveText = theme.resources.getString(R.string.swipe_action_archive) private val deleteText = theme.resources.getString(R.string.swipe_action_delete) private val spamText = theme.resources.getString(R.string.swipe_action_spam) private val moveText = theme.resources.getString(R.string.swipe_action_move) fun getIcon(item: MessageListItem, action: SwipeAction): Drawable { return when (action) { SwipeAction.None -> null SwipeAction.None -> error("action == SwipeAction.None") SwipeAction.ToggleSelection -> selectIcon SwipeAction.ToggleRead -> if (item.isRead) markAsUnreadIcon else markAsReadIcon SwipeAction.ToggleStar -> if (item.isStarred) removeStarIcon else addStarIcon Loading @@ -55,6 +66,19 @@ class SwipeResourceProvider(val theme: Theme) { SwipeAction.Move -> moveColor } } fun getActionName(item: MessageListItem, action: SwipeAction, isSelected: Boolean): String { return when (action) { SwipeAction.None -> error("action == SwipeAction.None") SwipeAction.ToggleSelection -> if (isSelected) deselectText else selectText SwipeAction.ToggleRead -> if (item.isRead) markAsUnreadText else markAsReadText SwipeAction.ToggleStar -> if (item.isStarred) removeStarText else addStarText SwipeAction.Archive -> archiveText SwipeAction.Delete -> deleteText SwipeAction.Spam -> spamText SwipeAction.Move -> moveText } } } private fun Theme.loadDrawable(@AttrRes attributeId: Int): Drawable { Loading app/ui/legacy/src/main/res/layout/swipe_left_action.xml 0 → 100644 +47 −0 Original line number Diff line number Diff line <?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="match_parent" android:gravity="center_vertical" android:orientation="horizontal" tools:background="?attr/messageListSwipeSelectBackgroundColor" tools:ignore="RtlHardcoded" tools:layout_height="?android:listPreferredItemHeight"> <ImageView android:id="@+id/swipe_action_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:layout_marginRight="@dimen/messageListSwipeIconPadding" android:layout_marginBottom="16dp" android:contentDescription="@null" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" tools:src="?attr/messageListSwipeMarkAsReadIcon" tools:tint="?attr/messageListSwipeIconTint" /> <TextView android:id="@+id/swipe_action_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:layout_marginRight="@dimen/messageListSwipeTextPadding" android:layout_marginBottom="16dp" android:ellipsize="end" android:gravity="right" android:maxLines="1" android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textColor="?attr/messageListSwipeIconTint" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/swipe_action_icon" app:layout_constraintTop_toTopOf="parent" tools:text="@string/swipe_action_mark_as_read" /> </androidx.constraintlayout.widget.ConstraintLayout> Loading
app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListAdapter.kt +4 −0 Original line number Diff line number Diff line Loading @@ -463,6 +463,10 @@ class MessageListAdapter internal constructor( item.messageUid == activeMessage.uid } fun isSelected(item: MessageListItem): Boolean { return item.uniqueId in selected } fun toggleSelection(item: MessageListItem) { if (messagesMap[item.uniqueId] == null) { // MessageListItem is no longer in the list Loading
app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListFragment.kt +1 −1 Original line number Diff line number Diff line Loading @@ -264,7 +264,7 @@ class MessageListFragment : val itemTouchHelper = ItemTouchHelper( MessageListSwipeCallback( resources, requireContext(), resourceProvider = SwipeResourceProvider(requireActivity().theme), swipeActionSupportProvider, swipeRightAction = K9.swipeRightAction, Loading
app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/MessageListSwipeCallback.kt +72 −68 Original line number Diff line number Diff line package com.fsck.k9.ui.messagelist import android.content.res.Resources import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.view.LayoutInflater import android.view.View import androidx.core.graphics.withSave import android.view.View.MeasureSpec import android.widget.ImageView import android.widget.TextView import androidx.core.graphics.withTranslation import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder Loading @@ -12,8 +17,9 @@ import com.fsck.k9.SwipeAction import com.fsck.k9.ui.R import kotlin.math.abs @SuppressLint("InflateParams") class MessageListSwipeCallback( resources: Resources, context: Context, private val resourceProvider: SwipeResourceProvider, private val swipeActionSupportProvider: SwipeActionSupportProvider, private val swipeRightAction: SwipeAction, Loading @@ -21,10 +27,19 @@ class MessageListSwipeCallback( private val adapter: MessageListAdapter, private val listener: MessageListSwipeListener ) : ItemTouchHelper.Callback() { private val iconPadding = resources.getDimension(R.dimen.messageListSwipeIconPadding).toInt() private val swipeThreshold = resources.getDimension(R.dimen.messageListSwipeThreshold) private val swipeThreshold = context.resources.getDimension(R.dimen.messageListSwipeThreshold) private val backgroundColorPaint = Paint() private val swipeRightLayout: View private val swipeLeftLayout: View init { val layoutInflater = LayoutInflater.from(context) swipeRightLayout = layoutInflater.inflate(R.layout.swipe_right_action, null, false) swipeLeftLayout = layoutInflater.inflate(R.layout.swipe_left_action, null, false) } override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: ViewHolder): Int { if (viewHolder !is MessageViewHolder) return 0 Loading Loading @@ -82,89 +97,78 @@ class MessageListSwipeCallback( actionState: Int, isCurrentlyActive: Boolean ) { canvas.withSave { val view = viewHolder.itemView val viewWidth = view.width val viewHeight = view.height val holder = viewHolder as MessageViewHolder val item = adapter.getItemById(holder.uniqueId) ?: return@withSave val isViewAnimatingBack = !isCurrentlyActive && abs(dX).toInt() >= viewWidth val swipeThreshold = recyclerView.width * getSwipeThreshold(holder) val swipeThresholdReached = abs(dX) > swipeThreshold if (swipeThresholdReached) { val action = if (dX > 0) swipeRightAction else swipeLeftAction val backgroundColor = resourceProvider.getBackgroundColor(action) drawBackground(view, backgroundColor) canvas.withTranslation(x = view.left.toFloat(), y = view.top.toFloat()) { if (isViewAnimatingBack) { drawBackground(dX, viewWidth, viewHeight) } else { val backgroundColor = resourceProvider.getBackgroundColor(SwipeAction.None) drawBackground(view, backgroundColor) } // Stop drawing the icon when the view has been animated all the way off the screen by ItemTouchHelper. // We do this so the icon doesn't switch state when RecyclerView's ItemAnimator animates the view back after // a toggle action (mark as read/unread, add/remove star) was used. if (isCurrentlyActive || abs(dX).toInt() < view.width) { drawIcon(dX, view, item, swipeThresholdReached) val holder = viewHolder as MessageViewHolder drawLayout(dX, viewWidth, viewHeight, holder) } } super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) } private fun Canvas.drawBackground(view: View, color: Int) { backgroundColorPaint.color = color private fun Canvas.drawBackground(dX: Float, width: Int, height: Int) { val swipeAction = if (dX > 0) swipeRightAction else swipeLeftAction val backgroundColor = resourceProvider.getBackgroundColor(swipeAction) backgroundColorPaint.color = backgroundColor drawRect( view.left.toFloat(), view.top.toFloat(), view.right.toFloat(), view.bottom.toFloat(), 0F, 0F, width.toFloat(), height.toFloat(), backgroundColorPaint ) } private fun Canvas.drawIcon(dX: Float, view: View, item: MessageListItem, swipeThresholdReached: Boolean) { if (dX > 0) { drawSwipeRightIcon(view, item, swipeThresholdReached) } else { drawSwipeLeftIcon(view, item, swipeThresholdReached) } } private fun Canvas.drawLayout(dX: Float, width: Int, height: Int, viewHolder: MessageViewHolder) { val item = adapter.getItemById(viewHolder.uniqueId) ?: return val isSelected = adapter.isSelected(item) val swipeRight = dX > 0 val swipeThresholdReached = abs(dX) > swipeThreshold private fun Canvas.drawSwipeRightIcon(view: View, item: MessageListItem, swipeThresholdReached: Boolean) { resourceProvider.getIcon(item, swipeRightAction)?.let { icon -> val iconLeft = iconPadding val iconTop = view.top + ((view.height - icon.intrinsicHeight) / 2) val iconRight = iconLeft + icon.intrinsicWidth val iconBottom = iconTop + icon.intrinsicHeight icon.setBounds(iconLeft, iconTop, iconRight, iconBottom) val swipeLayout = if (swipeRight) swipeRightLayout else swipeLeftLayout val swipeAction = if (swipeRight) swipeRightAction else swipeLeftAction val iconTint = if (swipeThresholdReached) { resourceProvider.iconTint val foregroundColor: Int val backgroundColor: Int if (swipeThresholdReached) { foregroundColor = resourceProvider.iconTint backgroundColor = resourceProvider.getBackgroundColor(swipeAction) } else { resourceProvider.getBackgroundColor(swipeRightAction) foregroundColor = resourceProvider.getBackgroundColor(swipeAction) backgroundColor = resourceProvider.getBackgroundColor(SwipeAction.None) } icon.setTint(iconTint) icon.draw(this) } } swipeLayout.setBackgroundColor(backgroundColor) private fun Canvas.drawSwipeLeftIcon(view: View, item: MessageListItem, swipeThresholdReached: Boolean) { resourceProvider.getIcon(item, swipeLeftAction)?.let { icon -> val iconRight = view.right - iconPadding val iconLeft = iconRight - icon.intrinsicWidth val iconTop = view.top + ((view.height - icon.intrinsicHeight) / 2) val iconBottom = iconTop + icon.intrinsicHeight icon.setBounds(iconLeft, iconTop, iconRight, iconBottom) val icon = resourceProvider.getIcon(item, swipeAction) icon.setTint(foregroundColor) val iconTint = if (swipeThresholdReached) { resourceProvider.iconTint } else { resourceProvider.getBackgroundColor(swipeLeftAction) } icon.setTint(iconTint) val iconView = swipeLayout.findViewById<ImageView>(R.id.swipe_action_icon) iconView.setImageDrawable(icon) icon.draw(this) val textView = swipeLayout.findViewById<TextView>(R.id.swipe_action_text) textView.setTextColor(foregroundColor) textView.text = resourceProvider.getActionName(item, swipeAction, isSelected) if (swipeLayout.isDirty) { val widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY) val heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) swipeLayout.measure(widthMeasureSpec, heightMeasureSpec) swipeLayout.layout(0, 0, width, height) } swipeLayout.draw(this) } } Loading
app/ui/legacy/src/main/java/com/fsck/k9/ui/messagelist/SwipeResourceProvider.kt +26 −2 Original line number Diff line number Diff line Loading @@ -30,9 +30,20 @@ class SwipeResourceProvider(val theme: Theme) { private val spamColor = theme.resolveColorAttribute(R.attr.messageListSwipeSpamBackgroundColor) private val moveColor = theme.resolveColorAttribute(R.attr.messageListSwipeMoveBackgroundColor) fun getIcon(item: MessageListItem, action: SwipeAction): Drawable? { private val selectText = theme.resources.getString(R.string.swipe_action_select) private val deselectText = theme.resources.getString(R.string.swipe_action_deselect) private val markAsReadText = theme.resources.getString(R.string.swipe_action_mark_as_read) private val markAsUnreadText = theme.resources.getString(R.string.swipe_action_mark_as_unread) private val addStarText = theme.resources.getString(R.string.swipe_action_add_star) private val removeStarText = theme.resources.getString(R.string.swipe_action_remove_star) private val archiveText = theme.resources.getString(R.string.swipe_action_archive) private val deleteText = theme.resources.getString(R.string.swipe_action_delete) private val spamText = theme.resources.getString(R.string.swipe_action_spam) private val moveText = theme.resources.getString(R.string.swipe_action_move) fun getIcon(item: MessageListItem, action: SwipeAction): Drawable { return when (action) { SwipeAction.None -> null SwipeAction.None -> error("action == SwipeAction.None") SwipeAction.ToggleSelection -> selectIcon SwipeAction.ToggleRead -> if (item.isRead) markAsUnreadIcon else markAsReadIcon SwipeAction.ToggleStar -> if (item.isStarred) removeStarIcon else addStarIcon Loading @@ -55,6 +66,19 @@ class SwipeResourceProvider(val theme: Theme) { SwipeAction.Move -> moveColor } } fun getActionName(item: MessageListItem, action: SwipeAction, isSelected: Boolean): String { return when (action) { SwipeAction.None -> error("action == SwipeAction.None") SwipeAction.ToggleSelection -> if (isSelected) deselectText else selectText SwipeAction.ToggleRead -> if (item.isRead) markAsUnreadText else markAsReadText SwipeAction.ToggleStar -> if (item.isStarred) removeStarText else addStarText SwipeAction.Archive -> archiveText SwipeAction.Delete -> deleteText SwipeAction.Spam -> spamText SwipeAction.Move -> moveText } } } private fun Theme.loadDrawable(@AttrRes attributeId: Int): Drawable { Loading
app/ui/legacy/src/main/res/layout/swipe_left_action.xml 0 → 100644 +47 −0 Original line number Diff line number Diff line <?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="match_parent" android:gravity="center_vertical" android:orientation="horizontal" tools:background="?attr/messageListSwipeSelectBackgroundColor" tools:ignore="RtlHardcoded" tools:layout_height="?android:listPreferredItemHeight"> <ImageView android:id="@+id/swipe_action_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:layout_marginRight="@dimen/messageListSwipeIconPadding" android:layout_marginBottom="16dp" android:contentDescription="@null" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" tools:src="?attr/messageListSwipeMarkAsReadIcon" tools:tint="?attr/messageListSwipeIconTint" /> <TextView android:id="@+id/swipe_action_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:layout_marginRight="@dimen/messageListSwipeTextPadding" android:layout_marginBottom="16dp" android:ellipsize="end" android:gravity="right" android:maxLines="1" android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textColor="?attr/messageListSwipeIconTint" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/swipe_action_icon" app:layout_constraintTop_toTopOf="parent" tools:text="@string/swipe_action_mark_as_read" /> </androidx.constraintlayout.widget.ConstraintLayout>