@@ -33,7 +33,8 @@ import androidx.core.view.isVisible
33
33
import androidx.recyclerview.widget.DiffUtil
34
34
import androidx.recyclerview.widget.ListAdapter
35
35
import androidx.recyclerview.widget.RecyclerView
36
- import androidx.recyclerview.widget.RecyclerView.ViewHolder
36
+ import androidx.recyclerview.widget.RecyclerView.*
37
+ import androidx.viewbinding.ViewBinding
37
38
import androidx.webkit.WebSettingsCompat
38
39
import androidx.webkit.WebViewFeature
39
40
import com.infomaniak.lib.core.utils.context
@@ -45,8 +46,9 @@ import com.infomaniak.mail.data.models.calendar.Attendee
45
46
import com.infomaniak.mail.data.models.calendar.Attendee.AttendanceState
46
47
import com.infomaniak.mail.data.models.correspondent.Recipient
47
48
import com.infomaniak.mail.data.models.message.Message
48
- import com.infomaniak.mail.databinding.ItemMessageBinding
49
- import com.infomaniak.mail.ui.main.thread.ThreadAdapter.MessageViewHolder
49
+ import com.infomaniak.mail.data.models.message.Message.*
50
+ import com.infomaniak.mail.databinding.*
51
+ import com.infomaniak.mail.ui.main.thread.ThreadAdapter.*
50
52
import com.infomaniak.mail.utils.*
51
53
import com.infomaniak.mail.utils.MailDateFormatUtils.mailFormattedDate
52
54
import com.infomaniak.mail.utils.MailDateFormatUtils.mostDetailedDate
@@ -70,9 +72,9 @@ class ThreadAdapter(
70
72
private val isForPrinting : Boolean = false ,
71
73
private val isCalendarEventExpandedMap : MutableMap <String , Boolean > = mutableMapOf(),
72
74
private var threadAdapterCallbacks : ThreadAdapterCallbacks ? = null ,
73
- ) : ListAdapter<Message, MessageViewHolder >(MessageDiffCallback ()) {
75
+ ) : ListAdapter<Any, ThreadAdapterViewHolder >(MessageDiffCallback ()) {
74
76
75
- inline val messages : MutableList <Message > get() = currentList
77
+ inline val items : MutableList <Any > get() = currentList
76
78
77
79
var isExpandedMap = mutableMapOf<String , Boolean >()
78
80
@@ -105,50 +107,87 @@ class ThreadAdapter(
105
107
super .onAttachedToRecyclerView(recyclerView)
106
108
}
107
109
108
- override fun getItemCount (): Int = runCatchingRealm { messages .count() }.getOrDefault(0 )
110
+ override fun getItemCount (): Int = runCatchingRealm { items .count() }.getOrDefault(0 )
109
111
110
- override fun onCreateViewHolder (parent : ViewGroup , viewType : Int ): MessageViewHolder {
111
- return MessageViewHolder (
112
- ItemMessageBinding .inflate(LayoutInflater .from(parent.context), parent, false ),
113
- shouldLoadDistantResources,
114
- threadAdapterCallbacks?.onContactClicked,
115
- threadAdapterCallbacks?.onAttachmentClicked,
116
- threadAdapterCallbacks?.onAttachmentOptionsClicked,
117
- )
112
+ override fun getItemViewType (position : Int ): Int = runCatchingRealm {
113
+ return when (items[position]) {
114
+ is Message -> DisplayType .MAIL .layout
115
+ else -> DisplayType .SUPER_COLLAPSED_BLOCK .layout
116
+ }
117
+ }.getOrDefault(super .getItemViewType(position))
118
+
119
+ override fun onCreateViewHolder (parent : ViewGroup , viewType : Int ): ThreadAdapterViewHolder {
120
+ val layoutInflater = LayoutInflater .from(parent.context)
121
+ return if (viewType == DisplayType .MAIL .layout) {
122
+ MessageViewHolder (
123
+ ItemMessageBinding .inflate(layoutInflater, parent, false ),
124
+ shouldLoadDistantResources,
125
+ threadAdapterCallbacks?.onContactClicked,
126
+ threadAdapterCallbacks?.onAttachmentClicked,
127
+ threadAdapterCallbacks?.onAttachmentOptionsClicked,
128
+ )
129
+ } else {
130
+ SuperCollapsedBlockViewHolder (ItemSuperCollapsedBlockBinding .inflate(layoutInflater, parent, false ))
131
+ }
118
132
}
119
133
120
- override fun onBindViewHolder (holder : MessageViewHolder , position : Int , payloads : MutableList <Any >) = runCatchingRealm {
121
- with (holder.binding) {
122
- val payload = payloads.firstOrNull()
123
- if (payload !is NotifyType ) {
124
- super .onBindViewHolder(holder, position, payloads)
125
- return @runCatchingRealm
126
- }
134
+ override fun onBindViewHolder (holder : ThreadAdapterViewHolder , position : Int , payloads : MutableList <Any >) = runCatchingRealm {
127
135
128
- val message = messages[position]
136
+ val payload = payloads.firstOrNull()
137
+ if (payload !is NotifyType ) {
138
+ super .onBindViewHolder(holder, position, payloads)
139
+ return @runCatchingRealm
140
+ }
129
141
142
+ val item = items[position]
143
+ if (item is Message && holder is MessageViewHolder ) with (holder.binding) {
130
144
when (payload) {
131
145
NotifyType .TOGGLE_LIGHT_MODE -> {
132
- isThemeTheSameMap[message .uid] = ! isThemeTheSameMap[message .uid]!!
133
- holder.toggleContentAndQuoteTheme(message .uid)
146
+ isThemeTheSameMap[item .uid] = ! isThemeTheSameMap[item .uid]!!
147
+ holder.toggleContentAndQuoteTheme(item .uid)
134
148
}
135
149
NotifyType .RE_RENDER -> reloadVisibleWebView()
136
150
NotifyType .FAILED_MESSAGE -> {
137
151
messageLoader.isGone = true
138
152
failedLoadingErrorMessage.isVisible = true
139
- if (isExpandedMap[message .uid] == true ) onExpandedMessageLoaded(message .uid)
153
+ if (isExpandedMap[item .uid] == true ) onExpandedMessageLoaded(item .uid)
140
154
}
141
155
NotifyType .ONLY_REBIND_CALENDAR_ATTENDANCE -> {
142
- val attendees = message .latestCalendarEventResponse?.calendarEvent?.attendees ? : emptyList()
156
+ val attendees = item .latestCalendarEventResponse?.calendarEvent?.attendees ? : emptyList()
143
157
holder.binding.calendarEvent.onlyUpdateAttendance(attendees)
144
158
}
145
159
}
146
160
}
147
161
}.getOrDefault(Unit )
148
162
149
- override fun onBindViewHolder (holder : MessageViewHolder , position : Int ) = with (holder) {
150
- val message = messages[position]
163
+ override fun onBindViewHolder (holder : ThreadAdapterViewHolder , position : Int ) {
164
+
165
+ val item = items[position]
166
+
167
+ holder.binding.root.tag = if (item is SuperCollapsedBlock || (item is Message && item.shouldHideDivider)) {
168
+ IGNORE_DIVIDER_TAG
169
+ } else {
170
+ null
171
+ }
172
+
173
+ if (item is Message ) {
174
+ (holder as MessageViewHolder ).bindMail(item, position)
175
+ } else {
176
+ (holder as SuperCollapsedBlockViewHolder ).bindSuperCollapsedBlock(item as SuperCollapsedBlock )
177
+ }
178
+ }
179
+
180
+ private fun SuperCollapsedBlockViewHolder.bindSuperCollapsedBlock (
181
+ item : SuperCollapsedBlock ,
182
+ ) = with (binding.superCollapsedBlock) {
183
+ text = context.getString(R .string.superCollapsedBlock, item.messagesUids.count())
184
+ setOnClickListener {
185
+ text = context.getString(R .string.loadingText)
186
+ threadAdapterCallbacks?.onSuperCollapsedBlockClicked?.invoke()
187
+ }
188
+ }
151
189
190
+ private fun MessageViewHolder.bindMail (message : Message , position : Int ) {
152
191
initMapForNewMessage(message, position)
153
192
154
193
bindHeader(message)
@@ -197,7 +236,7 @@ class ThreadAdapter(
197
236
198
237
private fun initMapForNewMessage (message : Message , position : Int ) {
199
238
if (isExpandedMap[message.uid] == null ) {
200
- isExpandedMap[message.uid] = message.shouldBeExpanded(position, messages .lastIndex)
239
+ isExpandedMap[message.uid] = message.shouldBeExpanded(position, items .lastIndex)
201
240
}
202
241
203
242
if (isThemeTheSameMap[message.uid] == null ) isThemeTheSameMap[message.uid] = true
@@ -255,7 +294,7 @@ class ThreadAdapter(
255
294
private fun WebView.processMailDisplay (styledBody : String , uid : String , isForPrinting : Boolean ): String {
256
295
val isDisplayedInDark = context.isNightModeEnabled() && isThemeTheSameMap[uid] == true && ! isForPrinting
257
296
return if (isForPrinting) {
258
- webViewUtils.processHtmlForPrint(styledBody, HtmlFormatter .PrintData (context, messages .first()))
297
+ webViewUtils.processHtmlForPrint(styledBody, HtmlFormatter .PrintData (context, items .first() as Message ))
259
298
} else {
260
299
webViewUtils.processHtmlForDisplay(styledBody, isDisplayedInDark)
261
300
}
@@ -534,7 +573,7 @@ class ThreadAdapter(
534
573
fun isMessageUidManuallyAllowed (messageUid : String ) = manuallyAllowedMessageUids.contains(messageUid)
535
574
536
575
fun toggleLightMode (message : Message ) {
537
- val index = messages .indexOf(message)
576
+ val index = items .indexOf(message)
538
577
notifyItemChanged(index, NotifyType .TOGGLE_LIGHT_MODE )
539
578
}
540
579
@@ -544,7 +583,7 @@ class ThreadAdapter(
544
583
545
584
fun updateFailedMessages (uids : List <String >) {
546
585
uids.forEach { uid ->
547
- val index = messages .indexOfFirst { it.uid == uid }
586
+ val index = items .indexOfFirst { it is Message && it.uid == uid }
548
587
notifyItemChanged(index, NotifyType .FAILED_MESSAGE )
549
588
}
550
589
}
@@ -554,7 +593,7 @@ class ThreadAdapter(
554
593
}
555
594
556
595
fun undoUserAttendanceClick (message : Message ) {
557
- val indexOfMessage = messages .indexOfFirst { it.uid == message.uid }.takeIf { it >= 0 }
596
+ val indexOfMessage = items .indexOfFirst { it is Message && it.uid == message.uid }.takeIf { it >= 0 }
558
597
indexOfMessage?.let { notifyItemChanged(it, NotifyType .ONLY_REBIND_CALENDAR_ATTENDANCE ) }
559
598
}
560
599
@@ -571,34 +610,53 @@ class ThreadAdapter(
571
610
PHONE ,
572
611
}
573
612
574
- class MessageDiffCallback : DiffUtil .ItemCallback <Message >() {
575
- override fun areItemsTheSame (oldMessage : Message , newMessage : Message ): Boolean {
576
- return oldMessage.uid == newMessage.uid
613
+ class MessageDiffCallback : DiffUtil .ItemCallback <Any >() {
614
+
615
+ override fun areItemsTheSame (oldItem : Any , newItem : Any ): Boolean {
616
+ return when (oldItem) {
617
+ is Message -> newItem is Message && newItem.uid == oldItem.uid
618
+ is SuperCollapsedBlock -> newItem is SuperCollapsedBlock
619
+ else -> false
620
+ }
577
621
}
578
622
579
- override fun areContentsTheSame (oldMessage : Message , newMessage : Message ): Boolean {
580
- return areMessageContentsTheSameExceptCalendar(oldMessage, newMessage) &&
581
- newMessage.latestCalendarEventResponse == oldMessage.latestCalendarEventResponse
623
+ override fun areContentsTheSame (oldItem : Any , newItem : Any ): Boolean {
624
+ return when (oldItem) {
625
+ is Message -> {
626
+ newItem is Message &&
627
+ areMessageContentsTheSameExceptCalendar(oldItem, newItem) &&
628
+ newItem.latestCalendarEventResponse == oldItem.latestCalendarEventResponse
629
+ }
630
+ is SuperCollapsedBlock -> {
631
+ newItem is SuperCollapsedBlock &&
632
+ newItem.messagesUids.count() == oldItem.messagesUids.count()
633
+ }
634
+ else -> false
635
+ }
582
636
}
583
637
584
- override fun getChangePayload (oldItem : Message , newItem : Message ): Any? {
638
+ override fun getChangePayload (oldItem : Any , newItem : Any ): Any? {
639
+
640
+ if (oldItem !is Message || newItem !is Message ) return null
641
+
585
642
// If everything but Attendees is the same, then we know the only thing that could've changed is Attendees.
586
643
return if (everythingButAttendeesIsTheSame(oldItem, newItem)) NotifyType .ONLY_REBIND_CALENDAR_ATTENDANCE else null
587
644
}
588
645
589
646
companion object {
590
- fun everythingButAttendeesIsTheSame (oldItem : Message , newItem : Message ): Boolean {
591
- val newCalendarEventResponse = newItem .latestCalendarEventResponse
592
- val oldCalendarEventResponse = oldItem .latestCalendarEventResponse
647
+ fun everythingButAttendeesIsTheSame (oldMessage : Message , newMessage : Message ): Boolean {
648
+ val newCalendarEventResponse = newMessage .latestCalendarEventResponse
649
+ val oldCalendarEventResponse = oldMessage .latestCalendarEventResponse
593
650
594
- return (areMessageContentsTheSameExceptCalendar(oldItem, newItem ) &&
651
+ return (areMessageContentsTheSameExceptCalendar(oldMessage, newMessage ) &&
595
652
! (newCalendarEventResponse == null && oldCalendarEventResponse == null )
596
653
&& newCalendarEventResponse?.everythingButAttendeesIsTheSame(oldCalendarEventResponse) == true )
597
654
}
598
655
599
656
private fun areMessageContentsTheSameExceptCalendar (oldMessage : Message , newMessage : Message ): Boolean {
600
657
return newMessage.body?.value == oldMessage.body?.value &&
601
- newMessage.splitBody == oldMessage.splitBody
658
+ newMessage.splitBody == oldMessage.splitBody &&
659
+ newMessage.shouldHideDivider == oldMessage.shouldHideDivider
602
660
}
603
661
}
604
662
}
@@ -614,20 +672,40 @@ class ThreadAdapter(
614
672
var onReplyClicked : ((Message ) -> Unit )? = null ,
615
673
var onMenuClicked : ((Message ) -> Unit )? = null ,
616
674
var onAllExpandedMessagesLoaded : (() -> Unit )? = null ,
675
+ var onSuperCollapsedBlockClicked : (() -> Unit )? = null ,
617
676
var navigateToNewMessageActivity : ((Uri ) -> Unit )? = null ,
618
677
var navigateToAttendeeBottomSheet : ((List <Attendee >) -> Unit )? = null ,
619
678
var navigateToDownloadProgressDialog : ((Attachment , AttachmentIntentType ) -> Unit )? = null ,
620
679
var replyToCalendarEvent : ((AttendanceState , Message ) -> Unit )? = null ,
621
680
var promptLink : ((String , ContextMenuType ) -> Unit )? = null ,
622
681
)
623
682
624
- class MessageViewHolder (
625
- val binding : ItemMessageBinding ,
683
+ private enum class DisplayType (val layout : Int ) {
684
+ MAIL (R .layout.item_message),
685
+ SUPER_COLLAPSED_BLOCK (R .layout.item_super_collapsed_block),
686
+ }
687
+
688
+ data class SuperCollapsedBlock (
689
+ var shouldBeDisplayed : Boolean = true ,
690
+ var hasBeenClicked : Boolean = false ,
691
+ val messagesUids : MutableSet <String > = mutableSetOf(),
692
+ ) {
693
+ fun isFirstTime () = shouldBeDisplayed && messagesUids.isEmpty()
694
+ }
695
+
696
+ abstract class ThreadAdapterViewHolder (open val binding : ViewBinding ) : ViewHolder(binding.root)
697
+
698
+ private class SuperCollapsedBlockViewHolder (
699
+ override val binding : ItemSuperCollapsedBlockBinding ,
700
+ ) : ThreadAdapterViewHolder(binding)
701
+
702
+ private class MessageViewHolder (
703
+ override val binding : ItemMessageBinding ,
626
704
private val shouldLoadDistantResources : Boolean ,
627
705
onContactClicked : ((contact: Recipient ) -> Unit )? ,
628
706
onAttachmentClicked : ((attachment: Attachment ) -> Unit )? ,
629
707
onAttachmentOptionsClicked : ((attachment: Attachment ) -> Unit )? ,
630
- ) : ViewHolder (binding.root ) {
708
+ ) : ThreadAdapterViewHolder (binding) {
631
709
632
710
val fromAdapter = DetailedRecipientAdapter (onContactClicked)
633
711
val toAdapter = DetailedRecipientAdapter (onContactClicked)
@@ -697,6 +775,8 @@ class ThreadAdapter(
697
775
698
776
companion object {
699
777
778
+ const val IGNORE_DIVIDER_TAG = " ignoreDividerTag"
779
+
700
780
private val contextMenuTypeForHitTestResultType = mapOf (
701
781
HitTestResult .PHONE_TYPE to ContextMenuType .PHONE ,
702
782
HitTestResult .EMAIL_TYPE to ContextMenuType .EMAIL ,
0 commit comments