Skip to content

Commit b1da033

Browse files
Merge pull request #1729 from Infomaniak/optimize-threadState
Use a class to store Thread state
2 parents cc80d26 + 6256568 commit b1da033

File tree

6 files changed

+96
-98
lines changed

6 files changed

+96
-98
lines changed

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,16 @@ class PrintMailFragment : Fragment() {
6565
threadViewModel.reassignMessagesLiveWithoutSuperCollapsedBlock(navigationArgs.messageUid)
6666
}
6767

68-
private fun setupAdapter() {
68+
private fun setupAdapter() = with(threadViewModel) {
6969
binding.messagesList.adapter = ThreadAdapter(
7070
shouldLoadDistantResources = true,
7171
isForPrinting = true,
7272
threadAdapterState = object : ThreadAdapterState {
73-
override var isExpandedMap by threadViewModel::isExpandedMap
74-
override var isThemeTheSameMap by threadViewModel::isThemeTheSameMap
75-
override var hasSuperCollapsedBlockBeenClicked by threadViewModel::hasSuperCollapsedBlockBeenClicked
76-
override var verticalScroll by threadViewModel::verticalScroll
73+
override val isExpandedMap by threadState::isExpandedMap
74+
override val isThemeTheSameMap by threadState::isThemeTheSameMap
75+
override val hasSuperCollapsedBlockBeenClicked by threadState::hasSuperCollapsedBlockBeenClicked
76+
override val verticalScroll by threadState::verticalScroll
77+
override val isCalendarEventExpandedMap by threadState::isCalendarEventExpandedMap
7778
},
7879
threadAdapterCallbacks = ThreadAdapterCallbacks(
7980
onBodyWebViewFinishedLoading = { startPrintingView() },

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ import com.google.android.material.R as RMaterial
7070
class ThreadAdapter(
7171
private val shouldLoadDistantResources: Boolean,
7272
private val isForPrinting: Boolean = false,
73-
private val isCalendarEventExpandedMap: MutableMap<String, Boolean> = mutableMapOf(),
7473
private val threadAdapterState: ThreadAdapterState,
7574
private var threadAdapterCallbacks: ThreadAdapterCallbacks? = null,
7675
) : ListAdapter<Any, ThreadAdapterViewHolder>(MessageDiffCallback()) {
@@ -220,7 +219,7 @@ class ThreadAdapter(
220219
shouldDisplayReplyOptions = calendarEventResponse.isReplyAuthorized(),
221220
attachment = calendarAttachment,
222221
hasAssociatedInfomaniakCalendarEvent = calendarEventResponse.hasAssociatedInfomaniakCalendarEvent(),
223-
shouldStartExpanded = isCalendarEventExpandedMap[message.uid] ?: false,
222+
shouldStartExpanded = threadAdapterState.isCalendarEventExpandedMap[message.uid] ?: false,
224223
)
225224
}
226225

@@ -234,7 +233,9 @@ class ThreadAdapter(
234233
replyToCalendarEvent = { attendanceState ->
235234
threadAdapterCallbacks?.replyToCalendarEvent?.invoke(attendanceState, message)
236235
},
237-
onAttendeesButtonClicked = { isExpanded -> isCalendarEventExpandedMap[message.uid] = isExpanded },
236+
onAttendeesButtonClicked = { isExpanded ->
237+
threadAdapterState.isCalendarEventExpandedMap[message.uid] = isExpanded
238+
},
238239
)
239240
}
240241
}

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

Lines changed: 0 additions & 25 deletions
This file was deleted.

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

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ class ThreadFragment : Fragment() {
153153
}
154154

155155
override fun onStop() {
156-
threadViewModel.verticalScroll = binding.messagesListNestedScrollView.scrollY
156+
threadViewModel.threadState.verticalScroll = binding.messagesListNestedScrollView.scrollY
157157
super.onStop()
158158
}
159159

@@ -163,11 +163,8 @@ class ThreadFragment : Fragment() {
163163
_binding = null
164164
}
165165

166-
fun resetThreadState() = with(threadViewModel) {
167-
isExpandedMap = mutableMapOf()
168-
isThemeTheSameMap = mutableMapOf()
169-
hasSuperCollapsedBlockBeenClicked = false
170-
verticalScroll = null
166+
fun resetThreadState() {
167+
threadViewModel.threadState.reset()
171168
}
172169

173170
private fun setupUi() = with(binding) {
@@ -212,12 +209,12 @@ class ThreadFragment : Fragment() {
212209

213210
binding.messagesList.adapter = ThreadAdapter(
214211
shouldLoadDistantResources = shouldLoadDistantResources(),
215-
isCalendarEventExpandedMap = isCalendarEventExpandedMap,
216212
threadAdapterState = object : ThreadAdapterState {
217-
override var isExpandedMap by threadViewModel::isExpandedMap
218-
override var isThemeTheSameMap by threadViewModel::isThemeTheSameMap
219-
override var hasSuperCollapsedBlockBeenClicked by threadViewModel::hasSuperCollapsedBlockBeenClicked
220-
override var verticalScroll by threadViewModel::verticalScroll
213+
override val isExpandedMap by threadState::isExpandedMap
214+
override val isThemeTheSameMap by threadState::isThemeTheSameMap
215+
override val hasSuperCollapsedBlockBeenClicked by threadState::hasSuperCollapsedBlockBeenClicked
216+
override val verticalScroll by threadState::verticalScroll
217+
override val isCalendarEventExpandedMap by threadState::isCalendarEventExpandedMap
221218
},
222219
threadAdapterCallbacks = ThreadAdapterCallbacks(
223220
onContactClicked = {
@@ -332,7 +329,6 @@ class ThreadFragment : Fragment() {
332329

333330
twoPaneViewModel.currentThreadUid.distinctUntilChanged().observeNotNull(viewLifecycleOwner) { threadUid ->
334331

335-
resetMessagesRelatedCache()
336332
displayThreadView()
337333

338334
openThread(threadUid).observe(viewLifecycleOwner) { thread ->
@@ -382,8 +378,8 @@ class ThreadFragment : Fragment() {
382378
messagesLive.observe(viewLifecycleOwner) { (items, messagesToFetch) ->
383379
SentryLog.i("UI", "Received ${items.count()} messages")
384380

385-
if (shouldMarkThreadAsSeen) {
386-
shouldMarkThreadAsSeen = false
381+
if (threadState.shouldMarkThreadAsSeen) {
382+
threadState.shouldMarkThreadAsSeen = false
387383
markThreadAsSeen()
388384
}
389385

@@ -565,7 +561,7 @@ class ThreadFragment : Fragment() {
565561
args = MessageActionsBottomSheetDialogArgs(
566562
messageUid = uid,
567563
threadUid = twoPaneViewModel.currentThreadUid.value ?: return,
568-
isThemeTheSame = threadViewModel.isThemeTheSameMap[uid] ?: return,
564+
isThemeTheSame = threadViewModel.threadState.isThemeTheSameMap[uid] ?: return,
569565
shouldLoadDistantResources = shouldLoadDistantResources(uid),
570566
).toBundle(),
571567
)
@@ -575,9 +571,9 @@ class ThreadFragment : Fragment() {
575571

576572
fun getBottomY(): Int = binding.messagesListNestedScrollView.maxScrollAmount
577573

578-
val scrollY = verticalScroll ?: run {
574+
val scrollY = threadState.verticalScroll ?: run {
579575

580-
val indexToScroll = threadAdapter.items.indexOfFirst { it is Message && isExpandedMap[it.uid] == true }
576+
val indexToScroll = threadAdapter.items.indexOfFirst { it is Message && threadState.isExpandedMap[it.uid] == true }
581577

582578
// If no Message is expanded (e.g. the last Message of the Thread is a Draft),
583579
// we want to automatically scroll to the very bottom.
@@ -590,7 +586,7 @@ class ThreadFragment : Fragment() {
590586
scope.level = SentryLevel.ERROR
591587
scope.setExtra("indexToScroll", indexToScroll.toString())
592588
scope.setExtra("messageCount", threadAdapter.items.count().toString())
593-
scope.setExtra("isExpandedMap", isExpandedMap.toString())
589+
scope.setExtra("isExpandedMap", threadState.isExpandedMap.toString())
594590
scope.setExtra("isLastMessageDraft", (threadAdapter.items.lastOrNull() as Message?)?.isDraft.toString())
595591
Sentry.captureMessage("Target child for scroll in ThreadFragment is null. Fallback to scrolling to bottom")
596592
}
@@ -605,7 +601,7 @@ class ThreadFragment : Fragment() {
605601
}
606602

607603
private fun expandSuperCollapsedBlock() = with(threadViewModel) {
608-
hasSuperCollapsedBlockBeenClicked = true
604+
threadState.hasSuperCollapsedBlockBeenClicked = true
609605
reassignMessagesLive(twoPaneViewModel.currentThreadUid.value!!)
610606
}
611607

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Infomaniak Mail - Android
3+
* Copyright (C) 2024 Infomaniak Network SA
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.infomaniak.mail.ui.main.thread
19+
20+
import com.infomaniak.mail.ui.main.thread.ThreadAdapter.SuperCollapsedBlock
21+
import com.infomaniak.mail.utils.MessageBodyUtils.SplitBody
22+
23+
interface ThreadAdapterState {
24+
val isExpandedMap: MutableMap<String, Boolean>
25+
val isThemeTheSameMap: MutableMap<String, Boolean>
26+
val hasSuperCollapsedBlockBeenClicked: Boolean
27+
val verticalScroll: Int?
28+
val isCalendarEventExpandedMap: MutableMap<String, Boolean>
29+
}
30+
31+
class ThreadState {
32+
33+
val isExpandedMap: MutableMap<String, Boolean> = mutableMapOf()
34+
val isThemeTheSameMap: MutableMap<String, Boolean> = mutableMapOf()
35+
var hasSuperCollapsedBlockBeenClicked: Boolean = false
36+
var verticalScroll: Int? = null
37+
val isCalendarEventExpandedMap: MutableMap<String, Boolean> = mutableMapOf()
38+
val treatedMessagesForCalendarEvent: MutableSet<String> = mutableSetOf()
39+
val cachedSplitBodies: MutableMap<String, SplitBody> = mutableMapOf()
40+
var shouldMarkThreadAsSeen: Boolean = false
41+
var superCollapsedBlock: SuperCollapsedBlock? = null
42+
43+
fun reset() {
44+
isExpandedMap.clear()
45+
isThemeTheSameMap.clear()
46+
hasSuperCollapsedBlockBeenClicked = false
47+
verticalScroll = null
48+
isCalendarEventExpandedMap.clear()
49+
treatedMessagesForCalendarEvent.clear()
50+
cachedSplitBodies.clear()
51+
shouldMarkThreadAsSeen = false
52+
superCollapsedBlock = null
53+
}
54+
}

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

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import com.infomaniak.mail.data.models.thread.Thread
3636
import com.infomaniak.mail.di.IoDispatcher
3737
import com.infomaniak.mail.ui.main.thread.ThreadAdapter.SuperCollapsedBlock
3838
import com.infomaniak.mail.utils.*
39-
import com.infomaniak.mail.utils.MessageBodyUtils.SplitBody
4039
import com.infomaniak.mail.utils.extensions.MergedContactDictionary
4140
import com.infomaniak.mail.utils.extensions.context
4241
import com.infomaniak.mail.utils.extensions.getUids
@@ -74,31 +73,15 @@ class ThreadViewModel @Inject constructor(
7473
private var fetchMessagesJob: Job? = null
7574
private var fetchCalendarEventJob: Job? = null
7675

77-
val quickActionBarClicks = SingleLiveEvent<QuickActionBarResult>()
78-
79-
//region Calendar Events
80-
private val treatedMessagesForCalendarEvent = mutableSetOf<String>()
81-
val isCalendarEventExpandedMap = mutableMapOf<String, Boolean>()
82-
//endregion
83-
84-
var deletedMessagesUids = mutableSetOf<String>()
85-
val failedMessagesUids = SingleLiveEvent<List<String>>()
86-
8776
val threadLive = MutableLiveData<Thread?>()
8877
val messagesLive = MutableLiveData<Pair<ThreadAdapterItems, MessagesWithoutHeavyData>>()
8978

90-
private val cachedSplitBodies = mutableMapOf<String, SplitBody>()
91-
92-
var shouldMarkThreadAsSeen: Boolean = false
79+
val quickActionBarClicks = SingleLiveEvent<QuickActionBarResult>()
9380

94-
private var superCollapsedBlock: SuperCollapsedBlock? = null
81+
val failedMessagesUids = SingleLiveEvent<List<String>>()
82+
var deletedMessagesUids = mutableSetOf<String>()
9583

96-
//region Restore Thread state after going to MoveFragment or somewhere else, and then coming back to ThreadFragment.
97-
var isExpandedMap: MutableMap<String, Boolean> = mutableMapOf()
98-
var isThemeTheSameMap: MutableMap<String, Boolean> = mutableMapOf()
99-
var hasSuperCollapsedBlockBeenClicked: Boolean = false
100-
var verticalScroll: Int? = null
101-
//endregion
84+
val threadState = ThreadState()
10285

10386
private val mailbox by lazy { mailboxController.getMailbox(AccountUtils.currentUserId, AccountUtils.currentMailboxId)!! }
10487

@@ -107,14 +90,6 @@ class ThreadViewModel @Inject constructor(
10790
AccountUtils.currentMailboxId,
10891
).map { it.obj }.asLiveData(ioCoroutineContext)
10992

110-
fun resetMessagesRelatedCache() {
111-
treatedMessagesForCalendarEvent.clear()
112-
isCalendarEventExpandedMap.clear()
113-
cachedSplitBodies.clear()
114-
shouldMarkThreadAsSeen = false
115-
superCollapsedBlock = null
116-
}
117-
11893
fun reassignThreadLive(threadUid: String) {
11994
threadLiveJob?.cancel()
12095
threadLiveJob = viewModelScope.launch(ioCoroutineContext) {
@@ -144,7 +119,7 @@ class ThreadViewModel @Inject constructor(
144119
private suspend fun mapRealmMessagesResult(
145120
messages: RealmResults<Message>,
146121
threadUid: String,
147-
): Pair<ThreadAdapterItems, MessagesWithoutHeavyData> {
122+
): Pair<ThreadAdapterItems, MessagesWithoutHeavyData> = with(threadState) {
148123

149124
superCollapsedBlock = superCollapsedBlock ?: SuperCollapsedBlock()
150125

@@ -212,7 +187,7 @@ class ThreadViewModel @Inject constructor(
212187
* - If there's any unread Message in between, it will be displayed (hence, all following Messages will be displayed too).
213188
* After all these Messages are displayed, if there's at least 2 remaining Messages, they're gonna be collapsed in the Block.
214189
*/
215-
private fun shouldBlockBeDisplayed(messagesCount: Int, firstIndexAfterBlock: Int): Boolean {
190+
private fun shouldBlockBeDisplayed(messagesCount: Int, firstIndexAfterBlock: Int): Boolean = with(threadState) {
216191
return superCollapsedBlock?.shouldBeDisplayed == true && // If the Block was hidden for any reason, we mustn't ever display it again
217192
!hasSuperCollapsedBlockBeenClicked && // Block hasn't been expanded by the user
218193
messagesCount >= SUPER_COLLAPSED_BLOCK_MINIMUM_MESSAGES_LIMIT && // At least 5 Messages in the Thread
@@ -223,7 +198,7 @@ class ThreadViewModel @Inject constructor(
223198
private suspend fun formatLists(
224199
messages: List<Message>,
225200
computeBehavior: (Int, String) -> MessageBehavior,
226-
): Pair<MutableList<Any>, MutableList<Message>> {
201+
): Pair<MutableList<Any>, MutableList<Message>> = with(threadState) {
227202

228203
val items = mutableListOf<Any>()
229204
val messagesToFetch = mutableListOf<Message>()
@@ -237,12 +212,8 @@ class ThreadViewModel @Inject constructor(
237212

238213
messages.forEachIndexed { index, message ->
239214
when (computeBehavior(index, message.uid)) {
240-
MessageBehavior.DISPLAYED -> {
241-
addMessage(message)
242-
}
243-
MessageBehavior.COLLAPSED -> {
244-
superCollapsedBlock!!.messagesUids.add(message.uid)
245-
}
215+
MessageBehavior.DISPLAYED -> addMessage(message)
216+
MessageBehavior.COLLAPSED -> superCollapsedBlock!!.messagesUids.add(message.uid)
246217
MessageBehavior.FIRST_AFTER_BLOCK -> {
247218
items += superCollapsedBlock!!
248219
addMessage(message.apply { shouldHideDivider = true })
@@ -258,9 +229,9 @@ class ThreadViewModel @Inject constructor(
258229

259230
message.apply {
260231
body?.let {
261-
val isNotAlreadySplit = !cachedSplitBodies.contains(message.uid)
262-
if (isNotAlreadySplit) cachedSplitBodies[message.uid] = MessageBodyUtils.splitContentAndQuote(it)
263-
splitBody = cachedSplitBodies[message.uid]
232+
val isNotAlreadySplit = !threadState.cachedSplitBodies.contains(message.uid)
233+
if (isNotAlreadySplit) threadState.cachedSplitBodies[message.uid] = MessageBodyUtils.splitContentAndQuote(it)
234+
splitBody = threadState.cachedSplitBodies[message.uid]
264235
}
265236
}
266237

@@ -277,14 +248,14 @@ class ThreadViewModel @Inject constructor(
277248
sendMatomoAndSentryAboutThreadMessagesCount(thread)
278249

279250
// These 2 will always be empty or not all together at the same time.
280-
if (isExpandedMap.isEmpty() || isThemeTheSameMap.isEmpty()) {
251+
if (threadState.isExpandedMap.isEmpty() || threadState.isThemeTheSameMap.isEmpty()) {
281252
thread.messages.forEachIndexed { index, message ->
282-
isExpandedMap[message.uid] = message.shouldBeExpanded(index, thread.messages.lastIndex)
283-
isThemeTheSameMap[message.uid] = true
253+
threadState.isExpandedMap[message.uid] = message.shouldBeExpanded(index, thread.messages.lastIndex)
254+
threadState.isThemeTheSameMap[message.uid] = true
284255
}
285256
}
286257

287-
shouldMarkThreadAsSeen = thread.unseenMessagesCount > 0
258+
threadState.shouldMarkThreadAsSeen = thread.unseenMessagesCount > 0
288259

289260
emit(thread)
290261
}
@@ -382,7 +353,7 @@ class ThreadViewModel @Inject constructor(
382353
if (!message.isFullyDownloaded()) return // Only treat message that have their Attachments downloaded
383354

384355
if (!forceFetch) {
385-
val alreadyTreated = !treatedMessagesForCalendarEvent.add(message.uid)
356+
val alreadyTreated = !threadState.treatedMessagesForCalendarEvent.add(message.uid)
386357
if (alreadyTreated) return
387358
}
388359

0 commit comments

Comments
 (0)