Skip to content

Commit cfacaf5

Browse files
Correctly create SuperCollapsedBlock only when needed + Fix race condition when marking Thread as seen
1 parent b012cdc commit cfacaf5

File tree

2 files changed

+48
-14
lines changed

2 files changed

+48
-14
lines changed

app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadViewModel.kt

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ import com.infomaniak.mail.utils.MessageBodyUtils.SplitBody
4040
import com.infomaniak.mail.utils.extensions.MergedContactDictionary
4141
import com.infomaniak.mail.utils.extensions.context
4242
import com.infomaniak.mail.utils.extensions.getUids
43+
import com.infomaniak.mail.utils.extensions.indexOfFirstOrNull
4344
import dagger.hilt.android.lifecycle.HiltViewModel
4445
import io.realm.kotlin.MutableRealm
4546
import io.realm.kotlin.notifications.ResultsChange
47+
import io.realm.kotlin.query.RealmResults
4648
import io.sentry.Sentry
4749
import io.sentry.SentryLevel
4850
import kotlinx.coroutines.*
@@ -83,6 +85,7 @@ class ThreadViewModel @Inject constructor(
8385
val threadLive = MutableLiveData<Thread?>()
8486
val messagesLive = MutableLiveData<Pair<ThreadAdapterItems, MessagesWithoutHeavyData>>()
8587

88+
private var indexOfFirstUnreadMessage: Int? = null
8689
private var cachedSplitBodies = mutableMapOf<String, SplitBody>()
8790
private var superCollapsedBlock: MutableSet<String>? = null
8891
var hasUserClickedTheSuperCollapsedBlock = false
@@ -95,6 +98,7 @@ class ThreadViewModel @Inject constructor(
9598
).map { it.obj }.asLiveData(ioCoroutineContext)
9699

97100
fun resetMessagesCache() {
101+
indexOfFirstUnreadMessage = null
98102
cachedSplitBodies = mutableMapOf()
99103
superCollapsedBlock = null
100104
hasUserClickedTheSuperCollapsedBlock = false
@@ -118,15 +122,11 @@ class ThreadViewModel @Inject constructor(
118122

119123
private suspend fun mapRealmMessagesResult(results: ResultsChange<Message>): Pair<ThreadAdapterItems, MessagesWithoutHeavyData> {
120124

121-
val messagesCount = results.list.count()
122125
val items = mutableListOf<Any>()
123126
val messagesToFetch = mutableListOf<Message>()
124-
125-
val shouldDisplaySuperCollapsedBlock =
126-
messagesCount >= SUPER_COLLAPSE_BLOCK_MESSAGES_LIMIT && !hasUserClickedTheSuperCollapsedBlock
127-
val shouldCreateSuperCollapsedBlock = (shouldDisplaySuperCollapsedBlock && superCollapsedBlock == null).also {
128-
if (it) superCollapsedBlock = mutableSetOf()
129-
}
127+
val firstIndexAfterBlock = computeFirstIndexAfterBlock(results.list)
128+
val shouldDisplaySuperCollapsedBlock = shouldSuperCollapsedBlockBeDisplayed(results.list.count(), firstIndexAfterBlock)
129+
val shouldCreateSuperCollapsedBlock = shouldSuperCollapsedBlockBeCreated(shouldDisplaySuperCollapsedBlock)
130130

131131
suspend fun addMessage(message: Message) {
132132
splitBody(message).let {
@@ -137,18 +137,18 @@ class ThreadViewModel @Inject constructor(
137137

138138
suspend fun mapListWithNewSuperCollapsedBlock() {
139139
results.list.forEachIndexed { index, message ->
140-
when {
141-
index == 0 -> { // First Message
140+
when (index) {
141+
0 -> { // First Message
142142
addMessage(message)
143143
}
144-
index > 0 && index < messagesCount - 2 -> { // All Messages that should go in block
144+
in 1..<firstIndexAfterBlock -> { // All Messages that should go in block
145145
superCollapsedBlock!!.add(message.uid)
146146
}
147-
index == messagesCount - 2 -> { // First Message not in block
147+
firstIndexAfterBlock -> { // First Message not in block
148148
items += SuperCollapsedBlock(superCollapsedBlock!!)
149149
addMessage(message.apply { shouldHideDivider = true })
150150
}
151-
else -> { // All following Messages (theoretically, only 1 Message)
151+
else -> { // All following Messages
152152
addMessage(message)
153153
}
154154
}
@@ -191,6 +191,31 @@ class ThreadViewModel @Inject constructor(
191191
return items to messagesToFetch
192192
}
193193

194+
private fun computeFirstIndexAfterBlock(list: RealmResults<Message>): Int {
195+
val fallbackIndex = list.count() - 2
196+
val firstUnreadIndex = indexOfFirstUnreadMessage ?: fallbackIndex
197+
return minOf(firstUnreadIndex, fallbackIndex)
198+
}
199+
200+
/**
201+
* Before trying to create the SuperCollapsedBlock, we need these required Messages that will be displayed:
202+
* - The 1st Message will always be displayed.
203+
* - The last 2 Messages will always be displayed.
204+
* - If there's any unread Message in between, it will be displayed (hence, all following Messages will be displayed too).
205+
* After all these Messages are displayed, if there's at least 2 remaining Messages, they're gonna be collapsed in the SuperCollapsedBlock.
206+
*/
207+
private fun shouldSuperCollapsedBlockBeDisplayed(messagesCount: Int, firstIndexAfterBlock: Int): Boolean {
208+
return messagesCount >= SUPER_COLLAPSED_BLOCK_MINIMUM_MESSAGES_LIMIT && // At least 5 Messages in the Thread
209+
firstIndexAfterBlock >= SUPER_COLLAPSED_BLOCK_FIRST_INDEX_LIMIT && // At least 2 Messages in the SuperCollapsedBlock
210+
!hasUserClickedTheSuperCollapsedBlock // SuperCollapsedBlock hasn't been expanded by the user
211+
}
212+
213+
private fun shouldSuperCollapsedBlockBeCreated(shouldDisplaySuperCollapsedBlock: Boolean): Boolean {
214+
return (shouldDisplaySuperCollapsedBlock && superCollapsedBlock == null).also {
215+
if (it) superCollapsedBlock = mutableSetOf()
216+
}
217+
}
218+
194219
private suspend fun splitBody(message: Message): Message = withContext(ioDispatcher) {
195220
if (message.body == null) return@withContext message
196221

@@ -226,7 +251,10 @@ class ThreadViewModel @Inject constructor(
226251

227252
emit(OpenThreadResult(thread, isExpandedMap, initialSetOfExpandedMessagesUids, isThemeTheSameMap))
228253

229-
if (thread.unseenMessagesCount > 0) sharedUtils.markAsSeen(mailbox, listOf(thread))
254+
if (thread.unseenMessagesCount > 0) {
255+
indexOfFirstUnreadMessage = thread.messages.indexOfFirstOrNull { !it.isSeen }
256+
sharedUtils.markAsSeen(mailbox, listOf(thread))
257+
}
230258
}
231259

232260
private fun sendMatomoAndSentryAboutThreadMessagesCount(thread: Thread) {
@@ -393,6 +421,7 @@ class ThreadViewModel @Inject constructor(
393421
)
394422

395423
companion object {
396-
private const val SUPER_COLLAPSE_BLOCK_MESSAGES_LIMIT = 5
424+
private const val SUPER_COLLAPSED_BLOCK_MINIMUM_MESSAGES_LIMIT = 5
425+
private const val SUPER_COLLAPSED_BLOCK_FIRST_INDEX_LIMIT = 3
397426
}
398427
}

app/src/main/java/com/infomaniak/mail/utils/extensions/Extensions.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,8 @@ fun ViewPager2.removeOverScrollForApiBelow31() {
581581
(getChildAt(0) as? RecyclerView)?.overScrollMode = View.OVER_SCROLL_NEVER
582582
}
583583
}
584+
585+
fun <T> List<T>.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? {
586+
val index = indexOfFirst(predicate)
587+
return if (index == -1) null else index
588+
}

0 commit comments

Comments
 (0)