Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display Folder name when we search/read a Thread #1699

Merged
merged 36 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b7231de
Base of folder tag in SearchFragment and ThreadFragment
tevincent Feb 6, 2024
5e2a553
Create SubjectFormatter class to put all subject formatter related me…
tevincent Feb 8, 2024
630c671
Fix padding issues when some of the icon are gone
tevincent Feb 8, 2024
9f0d07e
Display folder name only if we have one message in the thread
tevincent Feb 8, 2024
62d5a3b
Use dimension to get text size
tevincent Feb 8, 2024
f938a78
Remove unnecessary else
tevincent Feb 8, 2024
faa2714
Remove comment in xml drawable
tevincent Feb 8, 2024
ffff60a
Group private methods together
tevincent Feb 8, 2024
e4f8417
Use with for threadListAdapter
tevincent Feb 8, 2024
6365a2c
Get folder name from id
tevincent Feb 9, 2024
6f95591
Add folder name to thread only if we have one message
tevincent Feb 9, 2024
b260bd9
Add let parameter name
tevincent Feb 9, 2024
a8abd45
Get localized folder name
tevincent Feb 9, 2024
16ca746
Pass folder name visibility to adapter directly in the constructor
tevincent Feb 9, 2024
8461d7e
Rename method
tevincent Feb 9, 2024
20baae0
Clean layout of thread
tevincent Feb 9, 2024
5fa8416
Create a container for icons
tevincent Feb 9, 2024
b20c10e
Add other folder name view for compact mode and expand mode
tevincent Feb 14, 2024
96a087e
create a method to reset folder name visibility
tevincent Feb 15, 2024
e98a6d9
Code review
tevincent Feb 15, 2024
43fc7c3
Update app/src/main/java/com/infomaniak/mail/ui/main/thread/SubjectFo…
tevincent Feb 15, 2024
dfd6fe5
Code review
tevincent Feb 15, 2024
032b262
Remove ThreadDensity imports
KevinBoulongne Feb 16, 2024
a951fd5
Revamp `displayFolderName()` visibility handling
KevinBoulongne Feb 16, 2024
b1c86b7
Extract Folder name computation to its own function
KevinBoulongne Feb 16, 2024
d39a255
Code review of Kevin B.
tevincent Feb 16, 2024
93f0005
Reformat code
KevinBoulongne Feb 16, 2024
522e53d
Move `TagColor` data class to SubjectFormatter
KevinBoulongne Feb 16, 2024
e14f854
Intuitively set the visibility of folder names
LunarX Feb 19, 2024
ce84f9e
Rename color for foldername
tevincent Feb 19, 2024
d6edcfd
Remove unused method
tevincent Feb 19, 2024
a0e0fb7
Code review
tevincent Feb 19, 2024
42ce25c
Simplify code to ellipsize or not tags in subject
tevincent Feb 19, 2024
d964857
Rename variable
tevincent Feb 20, 2024
59625b2
Cache folder ids when reading folder names from realm
LunarX Feb 20, 2024
91794f4
Measure tag's actual width to know when to ellipsize or not
LunarX Feb 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ object RealmDatabase {
//region Configurations versions
const val USER_INFO_SCHEMA_VERSION = 1L
const val MAILBOX_INFO_SCHEMA_VERSION = 4L
const val MAILBOX_CONTENT_SCHEMA_VERSION = 9L
const val MAILBOX_CONTENT_SCHEMA_VERSION = 10L
//endregion

//region Configurations names
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package com.infomaniak.mail.data.cache.mailboxContent

import android.content.Context
import com.infomaniak.mail.data.api.ApiRepository
import com.infomaniak.mail.data.cache.RealmDatabase
import com.infomaniak.mail.data.models.Folder
Expand Down Expand Up @@ -44,6 +45,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,
) {
Expand Down Expand Up @@ -110,12 +112,18 @@ class ThreadController @Inject constructor(

return@withContext mailboxContentRealm().writeBlocking {
val searchFolder = FolderController.getOrCreateSearchFolder(realm = this)
val cachedFolderIds = mutableMapOf<String, String>()

remoteThreads.map { remoteThread ->
ensureActive()

remoteThread.isFromSearch = true

// If we only have 1 Message, we want to display its Folder name.
val folderId = if (remoteThread.messages.count() == 1) {
remoteThread.messages.single().folderId
val firstMessageFolderId = remoteThread.messages.single().folderId
setFolderName(firstMessageFolderId, remoteThread, cachedFolderIds)
firstMessageFolderId
} else {
filterFolder!!.id
}
Expand All @@ -127,6 +135,19 @@ class ThreadController @Inject constructor(
}.also(searchFolder.threads::addAll)
}
}

private fun MutableRealm.setFolderName(
firstMessageFolderId: String,
remoteThread: Thread,
cachedFolderIds: MutableMap<String, String>,
) {
val folderName = cachedFolderIds[firstMessageFolderId]
?: FolderController.getFolder(firstMessageFolderId, this)
?.getLocalizedName(context)
?.also { cachedFolderIds[firstMessageFolderId] = it }

folderName?.let { remoteThread.folderName = it }
}
//endregion

//region Edit data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class Thread : RealmObject {
@Transient
var folderId: String = ""
@Transient
var folderName: String = ""
@Transient
var duplicates: RealmList<Message> = realmListOf()
@Transient
var messagesIds: RealmSet<String> = realmSetOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Canvas
import android.os.Build
import android.text.Spannable
import android.text.TextUtils.TruncateAt
import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.View
Expand All @@ -46,13 +48,13 @@ import com.infomaniak.mail.R
import com.infomaniak.mail.data.LocalSettings
import com.infomaniak.mail.data.LocalSettings.SwipeAction
import com.infomaniak.mail.data.LocalSettings.ThreadDensity
import com.infomaniak.mail.data.LocalSettings.ThreadDensity.COMPACT
import com.infomaniak.mail.data.LocalSettings.ThreadDensity.LARGE
import com.infomaniak.mail.data.models.Folder.FolderRole
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
import com.infomaniak.mail.ui.main.thread.SubjectFormatter.TagColor
import com.infomaniak.mail.utils.RealmChangesBinding
import com.infomaniak.mail.utils.Utils.runCatchingRealm
import com.infomaniak.mail.utils.extensions.*
Expand Down Expand Up @@ -94,6 +96,7 @@ class ThreadListAdapter @Inject constructor(
private var folderRole: FolderRole? = null
private var onSwipeFinished: (() -> Unit)? = null
private var multiSelection: MultiSelectionListener<Thread>? = null
private var isFolderNameVisible: Boolean = false

//region Tablet mode
private var openedThreadPosition: Int? = null
Expand All @@ -108,10 +111,12 @@ class ThreadListAdapter @Inject constructor(
folderRole: FolderRole?,
onSwipeFinished: (() -> Unit)? = null,
multiSelection: MultiSelectionListener<Thread>? = null,
isFolderNameVisible: Boolean = false,
) {
this.folderRole = folderRole
this.onSwipeFinished = onSwipeFinished
this.multiSelection = multiSelection
this.isFolderNameVisible = isFolderNameVisible
}

override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
Expand Down Expand Up @@ -188,6 +193,8 @@ class ThreadListAdapter @Inject constructor(
setupThreadDensityDependentUi()
displayAvatar(thread)

displayFolderName(thread)

with(thread) {
expeditor.text = formatRecipientNames(computeDisplayedRecipients())
mailSubject.text = context.formatSubject(subject)
Expand Down Expand Up @@ -226,6 +233,37 @@ class ThreadListAdapter @Inject constructor(
updateSelectedUi(thread)
}

private fun CardviewThreadItemBinding.displayFolderName(thread: Thread) {
val isCompactMode = localSettings.threadDensity == ThreadDensity.COMPACT

fun setFolderNameVisibility(isVisible: Boolean) {
folderNameExpandMode.isVisible = !isCompactMode && isVisible
folderNameCompactMode.isVisible = isCompactMode && isVisible
}

val folderNameView = if (isCompactMode) folderNameCompactMode else folderNameExpandMode

if (shouldDisplayFolderName(thread.folderName)) {
folderNameView.text = computeFolderName(thread)
setFolderNameVisibility(isVisible = true)
} else {
setFolderNameVisibility(isVisible = false)
}
}

private fun shouldDisplayFolderName(folderName: String) = isFolderNameVisible && folderName.isNotEmpty()

private fun CardviewThreadItemBinding.computeFolderName(thread: Thread): Spannable {
return context.postfixWithTag(
tag = thread.folderName,
tagColor = TagColor(R.color.folderTagBackground, R.color.folderTagTextColor),
ellipsizeConfiguration = SubjectFormatter.EllipsizeConfiguration(
maxWidth = context.resources.getDimension(R.dimen.folderNameTagMaxSize),
truncateAt = TruncateAt.END,
),
)
}

private fun CardviewThreadItemBinding.onThreadClickWithAbilityToOpenMultiSelection(
thread: Thread,
listener: MultiSelectionListener<Thread>,
Expand Down Expand Up @@ -302,27 +340,27 @@ class ThreadListAdapter @Inject constructor(

multiSelection?.let {
with(localSettings) {
expeditorAvatar.isVisible = !isMultiSelected && threadDensity == LARGE
expeditorAvatar.isVisible = !isMultiSelected && threadDensity == ThreadDensity.LARGE
checkMarkLayout.isVisible = it.isEnabled
checkedState.isVisible = isMultiSelected
uncheckedState.isVisible = !isMultiSelected && threadDensity != LARGE
uncheckedState.isVisible = !isMultiSelected && threadDensity != ThreadDensity.LARGE
}
}
}

private fun CardviewThreadItemBinding.setupThreadDensityDependentUi() = with(localSettings) {
val margin = if (threadDensity == COMPACT) threadMarginCompact else threadMarginOther
val margin = if (threadDensity == ThreadDensity.COMPACT) threadMarginCompact else threadMarginOther
threadCard.setMarginsRelative(top = margin, bottom = margin)

expeditorAvatar.isVisible = threadDensity == LARGE
mailBodyPreview.isGone = threadDensity == COMPACT
expeditorAvatar.isVisible = threadDensity == ThreadDensity.LARGE
mailBodyPreview.isGone = threadDensity == ThreadDensity.COMPACT

checkMarkBackground.reshapeToDensity()
uncheckedState.reshapeToDensity()
}

private fun ImageView.reshapeToDensity() {
val checkMarkSize = if (localSettings.threadDensity == LARGE) checkMarkSizeLarge else checkMarkSizeOther
val checkMarkSize = if (localSettings.threadDensity == ThreadDensity.LARGE) checkMarkSizeLarge else checkMarkSizeOther
layoutParams.apply {
width = checkMarkSize
height = checkMarkSize
Expand Down Expand Up @@ -542,7 +580,7 @@ class ThreadListAdapter @Inject constructor(
add(folderRole)
}

if (threadDensity == COMPACT) {
if (threadDensity == ThreadDensity.COMPACT) {
if (multiSelection?.selectedItems?.let(threads::containsAll) == false) {
multiSelection?.selectedItems?.removeAll { !threads.contains(it) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class SearchFragment : TwoPaneFragment() {
}

private fun setupAdapter() {
threadListAdapter(folderRole = null)
threadListAdapter(folderRole = null, isFolderNameVisible = true)
}

private fun setupListeners() = with(binding) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2023 Infomaniak Network SA
* Copyright (C) 2023-2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand All @@ -26,17 +26,16 @@ import android.text.style.LineHeightSpan
import android.text.style.ReplacementSpan

/**
* A span to create a rounded background on a text.
*
* If radius is set, it generates a rounded background.
* If radius is null, it generates a circle background.
* A span to create a rounded background with the specified radius on a text.
*/
class RoundedBackgroundSpan(
private val backgroundColor: Int,
private val textColor: Int,
private val textTypeface: Typeface,
private val fontSize: Float,
private val cornerRadius: Float,
) : ReplacementSpan(), LineHeightSpan {

override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: FontMetricsInt?): Int {
paint.setGivenTextStyle()
return (LEFT_MARGIN + PADDING + paint.measureText(text, start, end) + PADDING).toInt()
Expand Down Expand Up @@ -64,7 +63,7 @@ class RoundedBackgroundSpan(
)

paint.color = backgroundColor
canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint)
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)

paint.setGivenTextStyle()
canvas.drawText(
Expand Down Expand Up @@ -96,6 +95,7 @@ class RoundedBackgroundSpan(
private const val LEFT_MARGIN = 4
private const val PADDING = 16
private const val VERTICAL_OFFSET = 4
private const val CORNER_RADIUS = 6.0f

fun getTotalHorizontalSpace(): Int = PADDING * 2 + LEFT_MARGIN
}
}
Loading
Loading