Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3ae80e57 authored by Fahim's avatar Fahim
Browse files

Update messageList item view

issue: https://gitlab.e.foundation/e/backlog/-/issues/4723

- set default value for disPlay author name before subject to true
- set default value for disPlay preview size line to 1
- move star button to right of the user avatar
- add right-cheveron icon after date field
- use separate view for preview & disPlayName field
- implement custom EllipsizingTextView for preview text to show elipsize
parent 507ffeb6
Loading
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -169,13 +169,13 @@ object K9 : EarlyInit {
    var isShowMessageListStars = true

    @JvmStatic
    var messageListPreviewLines = 2
    var messageListPreviewLines = 1

    @JvmStatic
    var isShowCorrespondentNames = true

    @JvmStatic
    var isMessageListSenderAboveSubject = false
    var isMessageListSenderAboveSubject = true

    @JvmStatic
    var isShowContactName = false
@@ -316,9 +316,9 @@ object K9 : EarlyInit {
        isUseVolumeKeysForListNavigation = storage.getBoolean("useVolumeKeysForListNavigation", false)
        isShowUnifiedInbox = storage.getBoolean("showUnifiedInbox", true)
        isShowStarredCount = storage.getBoolean("showStarredCount", false)
        isMessageListSenderAboveSubject = storage.getBoolean("messageListSenderAboveSubject", false)
        isMessageListSenderAboveSubject = storage.getBoolean("messageListSenderAboveSubject", true)
        isShowMessageListStars = storage.getBoolean("messageListStars", true)
        messageListPreviewLines = storage.getInt("messageListPreviewLines", 2)
        messageListPreviewLines = storage.getInt("messageListPreviewLines", 1)

        isAutoFitWidth = storage.getBoolean("autofitWidth", true)

+19 −14
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.widget.BaseAdapter
@@ -118,8 +119,8 @@ class MessageListAdapter internal constructor(

        appearance.fontSizes.setViewTextSize(holder.date, appearance.fontSizes.messageListDate)

        // 1 preview line is needed even if it is set to 0, because subject is part of the same text view
        holder.preview.setLines(max(appearance.previewLines, 1))
        holder.preview.setLines(max(appearance.previewLines, 0))
        appearance.fontSizes.setViewTextSize(holder.displayName, appearance.fontSizes.messageListSender)
        appearance.fontSizes.setViewTextSize(holder.preview, appearance.fontSizes.messageListPreview)
        appearance.fontSizes.setViewTextSize(holder.threadCount, appearance.fontSizes.messageListSubject) // thread count is next to subject

@@ -163,13 +164,15 @@ class MessageListAdapter internal constructor(
            val sigil = recipientSigil(toMe, ccMe)
            val messageStringBuilder = SpannableStringBuilder(sigil)
                .append(beforePreviewText)
            holder.displayName.setText(messageStringBuilder, TextView.BufferType.SPANNABLE)
            formatDisplayText(holder.displayName, isRead)
            if (appearance.previewLines > 0) {
                val preview = getPreview(isMessageEncrypted, previewText)
                messageStringBuilder.append(" ").append(preview)
                holder.preview.setText(preview, TextView.BufferType.SPANNABLE)
                formatPreviewText(holder.preview)
            } else {
                holder.preview.visibility = GONE
            }
            holder.preview.setText(messageStringBuilder, TextView.BufferType.SPANNABLE)

            formatPreviewText(holder.preview, beforePreviewText, sigil, isRead)

            holder.subject.typeface = Typeface.create(holder.subject.typeface, maybeBoldTypeface)
            if (appearance.senderAboveSubject) {
@@ -191,21 +194,23 @@ class MessageListAdapter internal constructor(
        }
    }

    private fun formatPreviewText(
        preview: TextView,
        beforePreviewText: CharSequence,
        sigil: String,
    private fun formatDisplayText(
        display: TextView,
        messageRead: Boolean
    ) {
        val previewText = preview.text as Spannable
        val displayText = display.text as Spannable
        addBeforePreviewSpan(displayText, displayText.length, messageRead)
    }

        val beforePreviewLength = beforePreviewText.length + sigil.length
        addBeforePreviewSpan(previewText, beforePreviewLength, messageRead)
    private fun formatPreviewText(
        preview: TextView
    ) {
        val previewText = preview.text as Spannable

        // Set span (color) for preview message
        previewText.setSpan(
            ForegroundColorSpan(previewTextColor),
            beforePreviewLength,
            0,
            previewText.length,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        )
+1 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ class MessageViewHolder(view: View) {
    val selected: View = view.findViewById(R.id.selected)
    val contactPicture: ImageView = view.findViewById(R.id.contact_picture)
    val subject: TextView = view.findViewById(R.id.subject)
    val displayName: TextView = view.findViewById(R.id.displayName)
    val preview: TextView = view.findViewById(R.id.preview)
    val date: TextView = view.findViewById(R.id.date)
    val chip: ImageView = view.findViewById(R.id.account_color_chip)
+184 −0
Original line number Diff line number Diff line
package com.fsck.k9.view

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.text.Layout
import android.text.StaticLayout
import android.text.TextUtils.TruncateAt
import android.util.AttributeSet
import android.widget.TextView
import androidx.appcompat.widget.AppCompatTextView

class EllipsizingTextView : AppCompatTextView {
    interface EllipsizeListener {
        fun ellipsizeStateChanged(ellipsized: Boolean)
    }

    private val ellipsizeListeners: MutableList<EllipsizeListener> = ArrayList()
    var isEllipsized = false
        private set
    private var isStale = true
    private var programmaticChange = false
    private var fullText: String? = null
    private var customLineSpacingMultiplier = 1.0f
    private var lineAdditionalVerticalPadding = 0.0f

    constructor(context: Context?) : super(context!!) {}
    constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {}
    constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(
        context!!, attrs, defStyle
    ) {
    }

    fun addEllipsizeListener(listener: EllipsizeListener?) {
        if (listener == null) {
            throw NullPointerException()
        }
        ellipsizeListeners.add(listener)
    }

    fun removeEllipsizeListener(listener: EllipsizeListener) {
        ellipsizeListeners.remove(listener)
    }

    override fun setLineSpacing(add: Float, mult: Float) {
        lineAdditionalVerticalPadding = add
        customLineSpacingMultiplier = mult
        super.setLineSpacing(add, mult)
    }

    override fun onTextChanged(text: CharSequence, start: Int, before: Int, after: Int) {
        super.onTextChanged(text, start, before, after)
        if (!programmaticChange) {
            fullText = text.toString()
            isStale = true
        }
    }

    override fun onDraw(canvas: Canvas) {
        if (isStale) {
            resetText()
        }
        super.onDraw(canvas)
    }

    @SuppressLint("DiscouragedPrivateApi")
    override fun getMaxLines(): Int {
        val textViewClassInstance = TextView::class.java
        try {
            val MaxMode = textViewClassInstance.getDeclaredField("mMaxMode")
            MaxMode.isAccessible = true
            val mMaxMode = MaxMode.getInt(this)
            val Maximum = textViewClassInstance.getDeclaredField("mMaximum")
            Maximum.isAccessible = true
            val mMaximum = Maximum.getInt(this)
            val LINES = textViewClassInstance.getDeclaredField("LINES")
            LINES.isAccessible = true
            val mLINES = LINES.getInt(this)
            return if (mMaxMode == mLINES) mMaximum else -1
        } catch (e: NoSuchFieldException) {
            e.printStackTrace()
        } catch (e: IllegalAccessException) {
            e.printStackTrace()
        } catch (e: IllegalArgumentException) {
            e.printStackTrace()
        }
        return -1
    }

    private fun resetText() {
        val maxLines = maxLines
        var workingText = fullText
        var ellipsized = false
        if (maxLines != -1) {
            val layout = createWorkingLayout(workingText)
            val originalLineCount = layout.lineCount
            if (originalLineCount > maxLines) {
                if (this.ellipsize == TruncateAt.START) {
                    workingText =
                        fullText!!.substring(layout.getLineStart(originalLineCount - maxLines - 1)).trim { it <= ' ' }
                    while (createWorkingLayout(ELLIPSIS + workingText).lineCount > maxLines) {
                        val firstSpace = workingText!!.indexOf(' ')
                        workingText = if (firstSpace == -1) {
                            workingText.substring(1)
                        } else {
                            workingText.substring(firstSpace + 1)
                        }
                    }
                    workingText = ELLIPSIS + workingText
                } else if (this.ellipsize == TruncateAt.END) {
                    workingText = fullText!!.substring(0, layout.getLineEnd(maxLines - 1)).trim { it <= ' ' }
                    while (createWorkingLayout(workingText + ELLIPSIS).lineCount > maxLines) {
                        val lastSpace = workingText!!.lastIndexOf(' ')
                        workingText = if (lastSpace == -1) {
                            workingText.substring(0, workingText.length - 1)
                        } else {
                            workingText.substring(0, lastSpace)
                        }
                    }
                    workingText = workingText + ELLIPSIS
                } else if (this.ellipsize == TruncateAt.MIDDLE) {
                    var shrinkLeft = false
                    val firstOffset = layout.getLineEnd(maxLines / 2)
                    val secondOffset = layout.getLineEnd(originalLineCount - 1) - firstOffset + 1
                    var firstWorkingText = fullText!!.substring(0, firstOffset).trim { it <= ' ' }
                    var secondWorkingText = fullText!!.substring(secondOffset).trim { it <= ' ' }
                    while (createWorkingLayout(firstWorkingText + ELLIPSIS + secondWorkingText).lineCount > maxLines) {
                        if (shrinkLeft) {
                            shrinkLeft = false
                            val lastSpace = firstWorkingText.lastIndexOf(' ')
                            firstWorkingText = if (lastSpace == -1) {
                                firstWorkingText.substring(
                                    0, firstWorkingText.length - 1
                                )
                            } else {
                                firstWorkingText.substring(
                                    0, lastSpace
                                )
                            }
                        } else {
                            shrinkLeft = true
                            val firstSpace = secondWorkingText.indexOf(' ')
                            secondWorkingText = if (firstSpace == -1) {
                                secondWorkingText
                                    .substring(1)
                            } else {
                                secondWorkingText
                                    .substring(firstSpace + 1)
                            }
                        }
                    }
                    workingText = firstWorkingText + ELLIPSIS + secondWorkingText
                }
                ellipsized = true
            }
        }
        if (workingText != text) {
            programmaticChange = true
            text = try {
                workingText
            } finally {
                programmaticChange = false
            }
        }
        isStale = false
        if (ellipsized != isEllipsized) {
            isEllipsized = ellipsized
            for (listener in ellipsizeListeners) {
                listener.ellipsizeStateChanged(ellipsized)
            }
        }
    }

    private fun createWorkingLayout(workingText: String?): Layout {
        return StaticLayout(
            workingText, paint, width - paddingLeft - paddingRight,
            Layout.Alignment.ALIGN_NORMAL, customLineSpacingMultiplier, lineAdditionalVerticalPadding, false
        )
    }

    companion object {
        private const val ELLIPSIS = "..."
    }
}
 No newline at end of file
+58 −27
Original line number Diff line number Diff line
@@ -49,22 +49,47 @@
            android:paddingBottom="6dp">

        <TextView
                android:id="@+id/preview"
            android:id="@+id/displayName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
                android:layout_toStartOf="@+id/star"
            android:layout_alignParentEnd="true"
            android:layout_marginStart="1dp"
            android:layout_marginEnd="3dp"
            android:bufferType="spannable"
            android:layout_below="@+id/subject_wrapper"
            android:singleLine="true"
            android:ellipsize="end"
            android:textAppearance="@style/TextAppearance.K9.Small"
            android:textColor="@color/color_default_primary_text"
            android:longClickable="false"
            android:layout_alignWithParentIfMissing="false"
            android:gravity="top"
            android:layout_alignParentBottom="false"
            android:paddingStart="0dp"
            android:paddingEnd="10dp"
            tools:text="Message preview"/>

        <com.fsck.k9.view.EllipsizingTextView
                android:id="@+id/preview"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentStart="true"
                android:layout_alignParentEnd="true"
                android:layout_marginStart="1dp"
                android:layout_marginEnd="3dp"
                android:bufferType="spannable"
                android:layout_below="@+id/displayName"
                android:singleLine="false"
                android:textAppearance="@style/TextAppearance.K9.Small"
                android:textColor="?android:attr/textColorPrimary"
                android:textColor="@color/color_default_primary_text"
                android:longClickable="false"
                android:layout_alignWithParentIfMissing="false"
                android:gravity="top"
                android:layout_alignParentBottom="false"
                android:paddingStart="0dp"
                android:paddingEnd="10dp"
                android:ellipsize="end"
                tools:text="Message preview"/>

        <LinearLayout
@@ -75,6 +100,19 @@
                android:layout_alignParentStart="true"
                android:layout_toStartOf="@+id/attachment">

            <CheckBox
                android:id="@+id/star"
                style="@style/MessageStarStyle"
                android:buttonTint="@color/star_color"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingTop="2dp"
                android:paddingBottom="2dp"
                android:focusable="false"
                android:visibility="visible"
                android:gravity="center_vertical"
                tools:text="&#x200B;"/>

            <ImageView
                android:id="@+id/account_color_chip"
                android:layout_width="wrap_content"
@@ -82,7 +120,7 @@
                android:layout_gravity="center_vertical"
                android:layout_marginEnd="4dp"
                app:srcCompat="@drawable/ic_account_color"
                tools:tint="#FF1976D2"/>
                tools:tint="@color/color_default_accent"/>

            <ImageView
                android:id="@+id/status"
@@ -98,10 +136,10 @@
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="1dp"
                    android:layout_marginStart="1dp"
                    android:ellipsize="marquee"
                    android:ellipsize="end"
                    android:singleLine="true"
                    android:textAppearance="@style/TextAppearance.K9.MediumSmall"
                    android:textColor="?android:attr/textColorPrimary"
                    android:textColor="@color/color_default_primary_text"
                    tools:text="Subject"
                    />

@@ -139,31 +177,24 @@
                android:layout_alignTop="@+id/subject_wrapper"
                android:layout_alignWithParentIfMissing="true"
                android:layout_centerVertical="true"
                android:layout_alignParentEnd="true"
                android:paddingStart="3dp"
                android:paddingEnd="8dp"
                android:layout_toStartOf="@id/right_chevron"
                android:layout_marginTop="2dp"
                android:singleLine="true"
                android:textAppearance="@style/TextAppearance.K9.Small"
                android:textColor="?android:attr/textColorSecondary"
                android:textColor="@color/color_default_secondary_text"
                tools:text="Oct 27"/>

        <CheckBox
                android:id="@+id/star"
                style="@style/MessageStarStyle"
                android:buttonTint="@color/star_color"
        <ImageView
            android:id="@+id/right_chevron"
            android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:layout_below="@+id/date"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_chevron_right"
            android:layout_alignTop="@+id/subject_wrapper"
            android:layout_alignWithParentIfMissing="true"
            android:layout_centerVertical="true"
            android:layout_alignParentEnd="true"
                android:paddingTop="5dip"
                android:paddingStart="2dp"
                android:paddingEnd="4dp"
                android:focusable="false"
                android:visibility="visible"
                android:gravity="center_vertical"
                tools:text="&#x200B;"/>


            android:paddingStart="3dp"
            android:paddingEnd="5dp" />
    </RelativeLayout>