diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt index f1df5ebc720..5559bf1839d 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt @@ -46,6 +46,7 @@ import okhttp3.OkHttpClient import javax.inject.Inject class ThreadController @Inject constructor( + private val context: Context, private val mailboxContentRealm: RealmDatabase.MailboxContent, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, ) { @@ -81,7 +82,6 @@ class ThreadController @Inject constructor( * @return A list of search Threads. The search only returns Messages from SPAM or TRASH if we explicitly selected those folders */ suspend fun initAndGetSearchFolderThreads( - context: Context, remoteThreads: List, filterFolder: Folder?, ): List = withContext(ioDispatcher) { @@ -115,9 +115,11 @@ class ThreadController @Inject constructor( val searchFolder = FolderController.getOrCreateSearchFolder(realm = this) remoteThreads.map { remoteThread -> ensureActive() + val firstMessageFolderId = remoteThread.messages.single().folderId if (remoteThread.messages.size == 1) { - val folderId = remoteThread.messages.first().folderId - getFolder(folderId, this@writeBlocking)?.let { folder -> remoteThread.folderName = folder.getLocalizedName(context) } + getFolder(firstMessageFolderId, this@writeBlocking)?.let { folder -> + remoteThread.folderName = folder.getLocalizedName(context) + } } remoteThread.isFromSearch = true diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt index 72ca86c8bde..c0752d795f8 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.res.ColorStateList import android.graphics.Canvas import android.os.Build +import android.text.TextUtils import android.view.HapticFeedbackConstants import android.view.LayoutInflater import android.view.View @@ -53,7 +54,7 @@ import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.databinding.* import com.infomaniak.mail.ui.main.folder.ThreadListAdapter.ThreadListViewHolder -import com.infomaniak.mail.ui.main.thread.SubjectFormatter.getFolderName +import com.infomaniak.mail.ui.main.thread.SubjectFormatter import com.infomaniak.mail.utils.RealmChangesBinding import com.infomaniak.mail.utils.Utils.runCatchingRealm import com.infomaniak.mail.utils.extensions.* @@ -87,7 +88,6 @@ class ThreadListAdapter @Inject constructor( private var swipingIsAuthorized: Boolean = true private var displaySeeAllButton = false // TODO: Manage this for intelligent mailbox private var isLoadMoreDisplayed = false - private var isFolderNameVisible: Boolean = false var onThreadClicked: ((thread: Thread) -> Unit)? = null var onFlushClicked: ((dialogTitle: String) -> Unit)? = null @@ -96,6 +96,7 @@ class ThreadListAdapter @Inject constructor( private var folderRole: FolderRole? = null private var onSwipeFinished: (() -> Unit)? = null private var multiSelection: MultiSelectionListener? = null + private var isFolderNameVisible: Boolean = false //region Tablet mode private var openedThreadPosition: Int? = null @@ -189,10 +190,18 @@ class ThreadListAdapter @Inject constructor( private fun shouldDisplayFolderName(folderName: String) = isFolderNameVisible && folderName.isNotEmpty() private fun CardviewThreadItemBinding.displayThread(thread: Thread, position: Int) { - val folderName = getFolderName(thread) - if (shouldDisplayFolderName(folderName)) { + if (shouldDisplayFolderName(thread.folderName)) { folderNameView.isVisible = true - folderNameView.text = folderName + folderNameView.text = context.postfixWithTag( + tag = thread.folderName, + tagColor = TagColor(R.color.folderNameBackground, R.color.folderNameTextColor), + ellipsizeConfiguration = SubjectFormatter.EllipsizeConfiguration( + maxWidth = context.resources.getDimension(R.dimen.subjectTagMaxSize).toInt(), + truncateAt = TextUtils.TruncateAt.END + ), + ) + } else { + folderNameView.isVisible = false } refreshCachedSelectedPosition(thread.uid, position) // If item changed position, update cached position. diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/RoundedBackgroundSpan.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/RoundedBackgroundSpan.kt index c6573782ada..eb2f601ed2b 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/RoundedBackgroundSpan.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/RoundedBackgroundSpan.kt @@ -17,13 +17,17 @@ */ package com.infomaniak.mail.ui.main.thread +import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.graphics.Paint.FontMetricsInt import android.graphics.RectF import android.graphics.Typeface +import android.text.TextPaint import android.text.style.LineHeightSpan import android.text.style.ReplacementSpan +import androidx.core.content.res.ResourcesCompat +import com.infomaniak.mail.R /** * A span to create a rounded background on a text. @@ -36,6 +40,7 @@ class RoundedBackgroundSpan( private val textColor: Int, private val textTypeface: Typeface, private val fontSize: Float, + private val cornerRadius: Float = CORNER_RADIUS ) : ReplacementSpan(), LineHeightSpan { override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: FontMetricsInt?): Int { @@ -65,7 +70,7 @@ class RoundedBackgroundSpan( ) paint.color = backgroundColor - canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint) + canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint) paint.setGivenTextStyle() canvas.drawText( @@ -98,5 +103,11 @@ class RoundedBackgroundSpan( private const val PADDING = 16 private const val VERTICAL_OFFSET = 4 private const val CORNER_RADIUS = 10f + + fun getTagsPaint(context: Context) = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = ResourcesCompat.getColor(context.resources, R.color.folderNameTextColor, null) + textSize = context.resources.getDimension(R.dimen.externalTagTextSize) + typeface = ResourcesCompat.getFont(context, R.font.tag_font) + } } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/SubjectFormatter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/SubjectFormatter.kt index 57d07e3fb39..7417642f35e 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/SubjectFormatter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/SubjectFormatter.kt @@ -19,164 +19,114 @@ package com.infomaniak.mail.ui.main.thread import android.content.Context import android.content.res.Resources -import android.graphics.Paint -import android.text.Spannable import android.text.StaticLayout -import android.text.TextPaint import android.text.TextUtils -import androidx.core.content.res.ResourcesCompat -import androidx.core.text.toSpannable import com.infomaniak.mail.MatomoMail.trackExternalEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.utils.ExternalUtils.findExternalRecipients import com.infomaniak.mail.utils.extensions.MergedContactDictionary +import com.infomaniak.mail.utils.extensions.TagColor import com.infomaniak.mail.utils.extensions.formatSubject import com.infomaniak.mail.utils.extensions.postfixWithTag +import javax.inject.Inject +import javax.inject.Singleton import com.infomaniak.lib.core.R as CoreR -object SubjectFormatter { +@Singleton +class SubjectFormatter @Inject constructor(private val context: Context) { fun generateSubjectContent( - context: Context, subjectData: SubjectData, - onTagClicked: (String) -> Unit + onExternalClicked: (String) -> Unit ): Pair = with(subjectData) { val subject = context.formatSubject(thread.subject) - var spannedSubject: Pair? = null - if (!externalMailFlagEnabled) spannedSubject = subject to subject + val spannedSubjectWithExternal = handleExternals(subject, onExternalClicked) - val (externalRecipientEmail, externalRecipientQuantity) = thread.findExternalRecipients(emailDictionary, aliases) - if (externalRecipientQuantity == 0) spannedSubject = subject to subject - - val spannedSubjectWithExternal = createSpannedSubjectWithExternal( - context, - subject, - externalRecipientEmail, - externalRecipientQuantity, - onTagClicked + val spannedSubjectWithFolder = handleFolders( + spannedSubjectWithExternal, + getEllipsizeConfiguration(spannedSubjectWithExternal, getFolderName(thread)) ) - val folderName = getFolderName( - context, - subject, - getFolderName(thread), - externalRecipientQuantity != 0 - ) - getSpannedFolderName(context, folderName, spannedSubjectWithExternal)?.let { spannedFolderName -> - spannedSubject = subject to spannedFolderName - } - - return spannedSubject!! + return subject to spannedSubjectWithFolder } - fun getFolderName(thread: Thread) = if (thread.messages.size > 1) "" else thread.folderName + private fun SubjectData.handleExternals( + previousContent: String, + onExternalClicked: (String) -> Unit + ): CharSequence { + if (!externalMailFlagEnabled) return previousContent - private fun createSpannedSubjectWithExternal( - context: Context, - subject: String, - externalRecipientEmail: String?, - externalRecipientQuantity: Int, - onTagClicked: (String) -> Unit - ): Spannable { - return context.postfixWithTag( - subject.toSpannable(), - R.string.externalTag, - R.color.externalTagBackground, - R.color.externalTagOnBackground, - ) { - context.trackExternalEvent("threadTag") - - val description = context.resources.getQuantityString( - R.plurals.externalDialogDescriptionExpeditor, - externalRecipientQuantity, - externalRecipientEmail, - ) - onTagClicked(description) - } - } + val (externalRecipientEmail, externalRecipientQuantity) = thread.findExternalRecipients(emailDictionary, aliases) + if (externalRecipientQuantity == 0) return previousContent - private fun getSpannedFolderName( - context: Context, - folderName: CharSequence?, - spannedSubjectWithExternal: Spannable - ): Spannable? { - return if (!folderName.isNullOrEmpty()) { - context.postfixWithTag( - spannedSubjectWithExternal, - folderName, - R.color.backgroundFolderName, - R.color.textColorFolderName, - ) { - context.trackExternalEvent("threadTag") - } - } else { - null - } + return postFixWithExternal(previousContent, externalRecipientQuantity, externalRecipientEmail, onExternalClicked) } - private fun getFolderName( - context: Context, - subject: String, - fullFolderName: String, - hasExternalTag: Boolean - ): CharSequence { - - val (subjectAndExternalLayout, fullSubjectLayout, folderNameLayout) = getStaticLayouts( - context, - subject, - fullFolderName, - hasExternalTag + private fun postFixWithExternal( + previousContent: CharSequence, + externalRecipientQuantity: Int, + externalRecipientEmail: String?, + onExternalClicked: (String) -> Unit + ) = context.postfixWithTag( + previousContent, + R.string.externalTag, + TagColor(R.color.externalTagBackground, R.color.externalTagOnBackground) + ) { + context.trackExternalEvent("threadTag") + + val description = context.resources.getQuantityString( + R.plurals.externalDialogDescriptionExpeditor, + externalRecipientQuantity, + externalRecipientEmail, ) - - // In case we know that the folder name take more than one line, we insert a break line to have more space - // If in any case, the folder name take more than the width of the screen, the string will be ellipsized - // the middle. - return if (fullSubjectLayout.lineCount - subjectAndExternalLayout.lineCount > 0) { - "\n ${folderNameLayout.text}" - } else { - fullFolderName - } + onExternalClicked(description) } - private fun getTagsTextPaint(context: Context) : TextPaint { - val tagsTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) - tagsTextPaint.textSize = context.resources.getDimension(R.dimen.externalTagTextSize) - tagsTextPaint.typeface = ResourcesCompat.getFont(context, R.font.tag_font) - tagsTextPaint.density + private fun SubjectData.handleFolders( + previousContent: CharSequence, + ellipsizeTag: EllipsizeConfiguration + ): CharSequence { + val folderName = getFolderName(thread) + if (folderName.isEmpty()) return previousContent - return tagsTextPaint + return postFixWithFolder(previousContent, folderName, ellipsizeTag) } - private fun getStaticLayouts( - context: Context, - subjectString: String, - fullFolderName: String, - hasExternalTag: Boolean - ) : StaticLayouts { - val tagsTextPaint = getTagsTextPaint(context) + private fun postFixWithFolder( + previousContent: CharSequence, + folderName: String, + ellipsizeTag: EllipsizeConfiguration + ) = context.postfixWithTag( + previousContent, + folderName, + TagColor(R.color.folderNameBackground, R.color.folderNameTextColor), + ellipsizeTag + ) + + private fun getFolderName(thread: Thread) = if (thread.messages.size > 1) "" else thread.folderName + private fun getEllipsizeConfiguration(previousContent: CharSequence, tag: String): EllipsizeConfiguration { val paddingsInPixels = (context.resources.getDimension(CoreR.dimen.marginStandard) * 2).toInt() val width = Resources.getSystem().displayMetrics.widthPixels - paddingsInPixels - - val externalString = if (hasExternalTag) context.getString(R.string.externalTag) else "" - val subjectAndExternalString = subjectString + externalString - val fullString = subjectString + externalString + fullFolderName - - val folderNameLayout = - StaticLayout.Builder.obtain(fullFolderName, 0, fullFolderName.length, tagsTextPaint, width) - .setEllipsizedWidth(width) - .setEllipsize(TextUtils.TruncateAt.MIDDLE) - .setMaxLines(1) - .build() - - val subjectAndExternalLayout = - StaticLayout.Builder.obtain(subjectAndExternalString, 0, subjectAndExternalString.length, tagsTextPaint, width) - .build() - val fullSubjectLayout = StaticLayout.Builder.obtain(fullString, 0, fullString.length, tagsTextPaint, width).build() - - return StaticLayouts(subjectAndExternalLayout, fullSubjectLayout, folderNameLayout) + val tagsTextPaint = RoundedBackgroundSpan.getTagsPaint(context) + + val layoutBeforeAddingTag = StaticLayout.Builder.obtain( + previousContent, + 0, + previousContent.length, + tagsTextPaint, + width + ).build() + + val fullString = "$previousContent $tag" + val layoutAfterAddingTag = StaticLayout.Builder.obtain(fullString, 0, fullString.length, tagsTextPaint, width).build() + + val positionLastChar = layoutBeforeAddingTag.getPrimaryHorizontal(previousContent.length).toInt() + val linesCountDifferent = layoutAfterAddingTag.lineCount != layoutBeforeAddingTag.lineCount + val maxWidth = if (layoutAfterAddingTag.lineCount != layoutBeforeAddingTag.lineCount) width else width - positionLastChar + return EllipsizeConfiguration(maxWidth, TextUtils.TruncateAt.MIDDLE, linesCountDifferent) } data class SubjectData( @@ -186,9 +136,9 @@ object SubjectFormatter { val externalMailFlagEnabled: Boolean ) - private data class StaticLayouts( - val subjectAndExternalLayout: StaticLayout, - val fullSubjectLayout: StaticLayout, - val folderNameLayout: StaticLayout + data class EllipsizeConfiguration( + val maxWidth: Int, + val truncateAt: TextUtils.TruncateAt = TextUtils.TruncateAt.MIDDLE, + val withNewLine: Boolean = false ) } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt index 5a5d16bd812..a3c8fbb631c 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt @@ -108,6 +108,9 @@ class ThreadFragment : Fragment() { @Inject lateinit var permissionUtils: PermissionUtils + @Inject + lateinit var subjectFormatter: SubjectFormatter + @Inject lateinit var snackbarManager: SnackbarManager @@ -425,8 +428,7 @@ class ThreadFragment : Fragment() { private fun observeSubjectUpdateTriggers() = with(binding) { threadViewModel.assembleSubjectData(mainViewModel.mergedContactsLive).observe(viewLifecycleOwner) { result -> - val (subject, spannedSubject) = SubjectFormatter.generateSubjectContent( - context = requireContext(), + val (subjectWithoutTags, subjectWithTags) = subjectFormatter.generateSubjectContent( subjectData = SubjectFormatter.SubjectData( thread = result.thread ?: return@observe, emailDictionary = result.mergedContacts ?: emptyMap(), @@ -441,15 +443,15 @@ class ThreadFragment : Fragment() { ) } - threadSubject.text = spannedSubject - toolbarSubject.text = subject + threadSubject.text = subjectWithTags + toolbarSubject.text = subjectWithoutTags threadSubject.setOnLongClickListener { - context.copyStringToClipboard(subject, R.string.snackbarSubjectCopiedToClipboard, snackbarManager) + context.copyStringToClipboard(subjectWithoutTags, R.string.snackbarSubjectCopiedToClipboard, snackbarManager) true } toolbarSubject.setOnLongClickListener { - context.copyStringToClipboard(subject, R.string.snackbarSubjectCopiedToClipboard, snackbarManager) + context.copyStringToClipboard(subjectWithoutTags, R.string.snackbarSubjectCopiedToClipboard, snackbarManager) true } } diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPromptFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPromptFragment.kt index 6922792d075..6ba0c96160b 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPromptFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPromptFragment.kt @@ -35,6 +35,7 @@ import com.infomaniak.mail.MatomoMail.trackEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.databinding.FragmentAiPromptBinding +import com.infomaniak.mail.utils.extensions.TagColor import com.infomaniak.mail.utils.extensions.postfixWithTag import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers @@ -84,10 +85,9 @@ class AiPromptFragment : Fragment() { setCorrectSheetMargins() aiPromptTitle.text = requireContext().postfixWithTag( - getString(R.string.aiPromptTitle).toSpannable(), + getString(R.string.aiPromptTitle), R.string.aiPromptTag, - R.color.aiBetaTagBackground, - R.color.aiBetaTagTextColor, + TagColor(R.color.aiBetaTagBackground, R.color.aiBetaTagTextColor) ) prompt.showKeyboard() diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt index 25b21ba2443..a650688b266 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/AiPropositionFragment.kt @@ -44,6 +44,7 @@ import com.infomaniak.mail.ui.alertDialogs.AiDescriptionAlertDialog import com.infomaniak.mail.ui.newMessage.AiViewModel.PropositionStatus import com.infomaniak.mail.ui.newMessage.AiViewModel.Shortcut import com.infomaniak.mail.utils.SimpleIconPopupMenu +import com.infomaniak.mail.utils.extensions.TagColor import com.infomaniak.mail.utils.extensions.changeToolbarColorOnScroll import com.infomaniak.mail.utils.extensions.postfixWithTag import com.infomaniak.mail.utils.extensions.setSystemBarsColors @@ -155,10 +156,9 @@ class AiPropositionFragment : Fragment() { toolbar.apply { setNavigationOnClickListener { trackDismissalAndPopBack() } title = requireContext().postfixWithTag( - getString(R.string.aiPromptTitle).toSpannable(), + getString(R.string.aiPromptTitle), R.string.aiPromptTag, - R.color.aiBetaTagBackground, - R.color.aiBetaTagTextColor + TagColor(R.color.aiBetaTagBackground, R.color.aiBetaTagTextColor) ) } } diff --git a/app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt b/app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt index e74bfa454be..688b67a5f24 100644 --- a/app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt +++ b/app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt @@ -25,15 +25,10 @@ import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.res.Configuration -import android.content.res.Resources import android.content.res.TypedArray -import android.graphics.Paint import android.net.Uri import android.os.Build -import android.text.Html -import android.text.Spannable -import android.text.Spanned -import android.text.TextUtils +import android.text.* import android.text.style.ClickableSpan import android.util.Patterns import android.view.View @@ -87,9 +82,11 @@ import com.infomaniak.mail.ui.main.folder.HeaderItemDecoration import com.infomaniak.mail.ui.main.folder.ThreadListAdapter import com.infomaniak.mail.ui.main.thread.MessageWebViewClient import com.infomaniak.mail.ui.main.thread.RoundedBackgroundSpan +import com.infomaniak.mail.ui.main.thread.SubjectFormatter import com.infomaniak.mail.ui.main.thread.ThreadFragment.HeaderState import com.infomaniak.mail.utils.* import com.infomaniak.mail.utils.Utils +import com.infomaniak.mail.utils.Utils.TAG_SEPARATOR import com.infomaniak.mail.utils.Utils.isPermanentDeleteFolder import com.infomaniak.mail.utils.Utils.kSyncAccountUri import io.realm.kotlin.MutableRealm @@ -493,38 +490,57 @@ inline val AndroidViewModel.context: Context get() = getApplication() val TextInputEditText.trimmedText inline get() = text?.trim().toString() fun Context.postfixWithTag( - original: Spannable, + original: CharSequence, @StringRes tagRes: Int, - @ColorRes backgroundColorRes: Int, - @ColorRes textColorRes: Int, + tagColor: TagColor, + ellipsizeConfiguration: SubjectFormatter.EllipsizeConfiguration? = null, onClicked: (() -> Unit)? = null, -) = postfixWithTag(original, getString(tagRes), backgroundColorRes, textColorRes, onClicked) - -fun Context.postfixWithTag( - original: Spannable, - tag: String, - @ColorRes backgroundColorRes: Int, - @ColorRes textColorRes: Int, - onClicked: (() -> Unit)? = null, -) = postfixWithTag(original, tag as CharSequence, backgroundColorRes, textColorRes, onClicked) +) = postfixWithTag( + original = original, + tag = getString(tagRes), + tagColor = tagColor, + ellipsizeConfiguration = ellipsizeConfiguration, + onClicked = onClicked +) /** * Do not forget to set `movementMethod = LinkMovementMethod.getInstance()` on a TextView to make the tag clickable */ fun Context.postfixWithTag( - original: Spannable, + original: CharSequence = "", tag: CharSequence, - @ColorRes backgroundColorRes: Int, - @ColorRes textColorRes: Int, + tagColor: TagColor, + ellipsizeConfiguration: SubjectFormatter.EllipsizeConfiguration? = null, onClicked: (() -> Unit)? = null, ): Spannable { - val postfixed = TextUtils.concat(original, Utils.TAG_SEPARATOR + tag) - return postfixed.toSpannable().apply { - val startIndex = original.length + Utils.TAG_SEPARATOR.length - val endIndex = startIndex + tag.length + fun getConfiguredTag(): CharSequence { + return if (ellipsizeConfiguration != null) { + val textPaint = RoundedBackgroundSpan.getTagsPaint(this) + with(ellipsizeConfiguration) { + val tagNameLayout = + StaticLayout.Builder.obtain(tag, 0, tag.length, textPaint, maxWidth) + .setEllipsizedWidth(maxWidth) + .setEllipsize(truncateAt) + .setMaxLines(1) + .build() + if (withNewLine) "$TAG_SEPARATOR\n${tagNameLayout.text}" else "$TAG_SEPARATOR${tagNameLayout.text}" + } + } else { + "$TAG_SEPARATOR$tag" + } + } - setTagSpan(this@postfixWithTag, startIndex, endIndex, backgroundColorRes, textColorRes) + val modifiedTag = getConfiguredTag() + val postFixed = TextUtils.concat(original, modifiedTag) + + return postFixed.toSpannable().apply { + val startIndex = original.length + TAG_SEPARATOR.length + val endIndex = startIndex + modifiedTag.length - TAG_SEPARATOR.length + + with(tagColor) { + setTagSpan(this@postfixWithTag, startIndex, endIndex, backgroundColorRes, textColorRes) + } onClicked?.let { setClickableSpan(startIndex, endIndex, it) } } } @@ -546,6 +562,7 @@ private fun Spannable.setTagSpan( textColor = textColor, textTypeface = textTypeface, fontSize = textSize, + cornerRadius = context.resources.getDimension(R.dimen.subjectTagRadius) ), startIndex, endIndex, @@ -590,3 +607,5 @@ fun ViewPager2.removeOverScrollForApiBelow31() { (getChildAt(0) as? RecyclerView)?.overScrollMode = View.OVER_SCROLL_NEVER } } + +data class TagColor(@ColorRes val backgroundColorRes: Int, @ColorRes val textColorRes: Int) diff --git a/app/src/main/res/drawable/folder_background.xml b/app/src/main/res/font-v26/tag_font.xml similarity index 61% rename from app/src/main/res/drawable/folder_background.xml rename to app/src/main/res/font-v26/tag_font.xml index f579f8d09c4..99a9aaa1417 100644 --- a/app/src/main/res/drawable/folder_background.xml +++ b/app/src/main/res/font-v26/tag_font.xml @@ -1,6 +1,6 @@ - - - - - - + + + diff --git a/app/src/main/res/font/tag_font.xml b/app/src/main/res/font/tag_font.xml index 28a74e68826..f02d5cac0aa 100644 --- a/app/src/main/res/font/tag_font.xml +++ b/app/src/main/res/font/tag_font.xml @@ -15,6 +15,6 @@ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see . --> - - + + diff --git a/app/src/main/res/layout/cardview_thread_item.xml b/app/src/main/res/layout/cardview_thread_item.xml index 4d4a24bbba0..0fa62461d45 100644 --- a/app/src/main/res/layout/cardview_thread_item.xml +++ b/app/src/main/res/layout/cardview_thread_item.xml @@ -230,20 +230,17 @@ @@ -251,13 +248,14 @@ android:id="@+id/iconAttachment" android:layout_width="@dimen/mediumIconSize" android:layout_height="@dimen/mediumIconSize" - android:layout_marginEnd="@dimen/marginStandardSmall" + android:layout_marginStart="@dimen/marginStandardSmall" android:contentDescription="@string/contentDescriptionIconAttachments" android:src="@drawable/ic_attachment" android:visibility="gone" - app:layout_constraintBottom_toBottomOf="@id/iconFavorite" + app:layout_constraintBottom_toBottomOf="@id/mailSubject" app:layout_constraintEnd_toStartOf="@id/iconCalendar" - app:layout_constraintTop_toTopOf="@id/iconFavorite" + app:layout_constraintStart_toEndOf="@id/folderNameView" + app:layout_constraintTop_toTopOf="@id/mailSubject" app:tint="@color/iconColorPrimaryText" tools:visibility="visible" /> @@ -265,13 +263,14 @@ android:id="@+id/iconCalendar" android:layout_width="@dimen/mediumIconSize" android:layout_height="@dimen/mediumIconSize" - android:layout_marginEnd="@dimen/marginStandardSmall" + android:layout_marginStart="@dimen/marginStandardSmall" android:contentDescription="@string/contentDescriptionIconCalendar" android:src="@drawable/ic_calendar" android:visibility="gone" - app:layout_constraintBottom_toBottomOf="@id/iconFavorite" + app:layout_constraintBottom_toBottomOf="@id/mailSubject" app:layout_constraintEnd_toStartOf="@id/iconFavorite" - app:layout_constraintTop_toTopOf="@id/iconFavorite" + app:layout_constraintStart_toEndOf="@id/iconAttachment" + app:layout_constraintTop_toTopOf="@id/mailSubject" app:tint="@color/iconColorPrimaryText" tools:visibility="visible" /> @@ -279,11 +278,13 @@ android:id="@+id/iconFavorite" android:layout_width="@dimen/mediumIconSize" android:layout_height="@dimen/mediumIconSize" + android:layout_marginStart="@dimen/marginStandardSmall" android:contentDescription="@string/contentDescriptionIconFavorite" android:src="@drawable/ic_star_filled" android:visibility="gone" app:layout_constraintBottom_toBottomOf="@id/mailSubject" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/iconCalendar" app:layout_constraintTop_toTopOf="@id/mailSubject" tools:visibility="visible" /> diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 617e7dfe15e..b5c3ce146d9 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -88,8 +88,8 @@ @color/orca - @color/shark - @color/orca + @color/shark + @color/orca @color/redDestructiveAction diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index eb36e1e9421..0263841ae35 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -165,8 +165,8 @@ @color/iconColorSecondaryText - @color/elephant - @color/polar_bear + @color/elephant + @color/polar_bear @color/redDestructiveAction diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index ebd13b957c1..febb42cc632 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -38,6 +38,8 @@ 300dp 38dp 6dp + 4dp + 150dp 40dp 6dp 12dp