Skip to content

Commit

Permalink
Merge pull request #1718 from Infomaniak/threadFragment-state
Browse files Browse the repository at this point in the history
Restore Thread state when coming back
  • Loading branch information
KevinBoulongne authored Mar 1, 2024
2 parents 2878a35 + 26140ef commit 0460ad1
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class ThreadListFragment : TwoPaneFragment(), SwipeRefreshLayout.OnRefreshListen
}

// If we are coming from a Notification, we need to navigate to ThreadFragment.
twoPaneViewModel.openThread(threadUid)
openThreadAndResetBackup(threadUid)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import javax.inject.Inject
abstract class TwoPaneFragment : Fragment() {

val mainViewModel: MainViewModel by activityViewModels()
protected val twoPaneViewModel: TwoPaneViewModel by activityViewModels()
private val twoPaneViewModel: TwoPaneViewModel by activityViewModels()

// TODO: When we'll update DragDropSwipeRecyclerViewLib, we'll need to make the adapter nullable.
// For now it causes a memory leak, because we can't remove the strong reference
Expand Down Expand Up @@ -133,15 +133,20 @@ abstract class TwoPaneFragment : Fragment() {
}
}

fun navigateToThread(thread: Thread) = with(twoPaneViewModel) {
fun navigateToThread(thread: Thread) {
if (thread.isOnlyOneDraft) {
trackNewMessageEvent(OPEN_FROM_DRAFT_NAME)
openDraft(thread)
twoPaneViewModel.openDraft(thread)
} else {
openThread(thread.uid)
openThreadAndResetBackup(thread.uid)
}
}

fun openThreadAndResetBackup(threadUid: String) {
getRightPane()?.getFragment<ThreadFragment?>()?.resetThreadBackupCache()
twoPaneViewModel.openThread(threadUid)
}

private fun resetPanes() {

if (isOnlyLeftShown()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,30 @@ package com.infomaniak.mail.ui.main.folder
import android.net.Uri
import android.os.Bundle
import androidx.annotation.IdRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.*
import com.infomaniak.lib.core.utils.SingleLiveEvent
import com.infomaniak.mail.data.cache.mailboxContent.DraftController
import com.infomaniak.mail.data.models.draft.Draft.DraftMode
import com.infomaniak.mail.data.models.message.Message
import com.infomaniak.mail.data.models.thread.Thread
import com.infomaniak.mail.di.IoDispatcher
import com.infomaniak.mail.ui.newMessage.NewMessageActivityArgs
import com.infomaniak.mail.utils.Utils.runCatchingRealm
import com.infomaniak.mail.utils.coroutineContext
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class TwoPaneViewModel @Inject constructor(
private val state: SavedStateHandle,
private val draftController: DraftController,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {

private val ioCoroutineContext = viewModelScope.coroutineContext(ioDispatcher)

val currentThreadUid: LiveData<String?> = state.getLiveData(CURRENT_THREAD_UID_KEY)

inline val isThreadOpen get() = currentThreadUid.value != null
Expand All @@ -57,16 +61,18 @@ class TwoPaneViewModel @Inject constructor(
state[CURRENT_THREAD_UID_KEY] = null
}

fun openDraft(thread: Thread) {
fun openDraft(thread: Thread) = viewModelScope.launch(ioCoroutineContext) {
navigateToSelectedDraft(thread.messages.single())
}

private fun navigateToSelectedDraft(message: Message) = runCatchingRealm {
newMessageArgs.value = NewMessageActivityArgs(
arrivedFromExistingDraft = true,
draftLocalUuid = draftController.getDraftByMessageUid(message.uid)?.localUuid,
draftResource = message.draftResource,
messageUid = message.uid,
newMessageArgs.postValue(
NewMessageActivityArgs(
arrivedFromExistingDraft = true,
draftLocalUuid = draftController.getDraftByMessageUid(message.uid)?.localUuid,
draftResource = message.draftResource,
messageUid = message.uid,
),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,16 @@ class PrintMailFragment : Fragment() {
}
}

private fun setupAdapter() = with(binding.messagesList) {
adapter = ThreadAdapter(
private fun setupAdapter() {
binding.messagesList.adapter = ThreadAdapter(
shouldLoadDistantResources = true,
isForPrinting = true,
threadAdapterState = object : ThreadAdapterState {
override var isExpandedMap by threadViewModel::isExpandedMap
override var isThemeTheSameMap by threadViewModel::isThemeTheSameMap
override var hasSuperCollapsedBlockBeenClicked by threadViewModel::hasSuperCollapsedBlockBeenClicked
override var verticalScroll by threadViewModel::verticalScroll
},
threadAdapterCallbacks = ThreadAdapterCallbacks(
onBodyWebViewFinishedLoading = { startPrintingView() },
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,18 @@ class ThreadAdapter(
private val shouldLoadDistantResources: Boolean,
private val isForPrinting: Boolean = false,
private val isCalendarEventExpandedMap: MutableMap<String, Boolean> = mutableMapOf(),
private val threadAdapterState: ThreadAdapterState,
private var threadAdapterCallbacks: ThreadAdapterCallbacks? = null,
) : ListAdapter<Any, ThreadAdapterViewHolder>(MessageDiffCallback()) {

inline val items: MutableList<Any> get() = currentList

var isExpandedMap = mutableMapOf<String, Boolean>()

//region Auto-scroll at Thread opening
var initialSetOfExpandedMessagesUids = setOf<String>()
private val currentSetOfLoadedExpandedMessagesUids = mutableSetOf<String>()
private var hasNotScrolledYet = true
//endregion

private val manuallyAllowedMessageUids = mutableSetOf<String>()
var isThemeTheSameMap = mutableMapOf<String, Boolean>()

private lateinit var recyclerView: RecyclerView
private val webViewUtils by lazy { WebViewUtils(recyclerView.context) }
Expand Down Expand Up @@ -150,15 +147,15 @@ class ThreadAdapter(
}
}.getOrDefault(Unit)

private fun MessageViewHolder.handleToggleLightModePayload(messageUid: String) {
private fun MessageViewHolder.handleToggleLightModePayload(messageUid: String) = with(threadAdapterState) {
isThemeTheSameMap[messageUid] = !isThemeTheSameMap[messageUid]!!
toggleContentAndQuoteTheme(messageUid)
}

private fun ItemMessageBinding.handleFailedMessagePayload(messageUid: String) {
messageLoader.isGone = true
failedLoadingErrorMessage.isVisible = true
if (isExpandedMap[messageUid] == true) onExpandedMessageLoaded(messageUid)
if (threadAdapterState.isExpandedMap[messageUid] == true) onExpandedMessageLoaded(messageUid)
}

private fun ItemMessageBinding.handleCalendarAttendancePayload(message: Message) {
Expand Down Expand Up @@ -194,6 +191,7 @@ class ThreadAdapter(
}

private fun MessageViewHolder.bindMail(message: Message, position: Int) {

initMapForNewMessage(message, position)

bindHeader(message)
Expand Down Expand Up @@ -240,7 +238,7 @@ class ThreadAdapter(
}
}

private fun initMapForNewMessage(message: Message, position: Int) {
private fun initMapForNewMessage(message: Message, position: Int) = with(threadAdapterState) {
if (isExpandedMap[message.uid] == null) {
isExpandedMap[message.uid] = message.shouldBeExpanded(position, items.lastIndex)
}
Expand All @@ -249,7 +247,7 @@ class ThreadAdapter(
}

private fun MessageViewHolder.toggleContentAndQuoteTheme(messageUid: String) = with(binding) {
val isThemeTheSame = isThemeTheSameMap[messageUid]!!
val isThemeTheSame = threadAdapterState.isThemeTheSameMap[messageUid]!!
bodyWebView.toggleWebViewTheme(isThemeTheSame)
fullMessageWebView.toggleWebViewTheme(isThemeTheSame)
toggleFrameLayoutsTheme(isThemeTheSame)
Expand Down Expand Up @@ -285,7 +283,7 @@ class ThreadAdapter(

private fun WebView.applyWebViewContent(uid: String, bodyWebView: String, type: String) {
if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {
WebSettingsCompat.setAlgorithmicDarkeningAllowed(settings, isThemeTheSameMap[uid]!!)
WebSettingsCompat.setAlgorithmicDarkeningAllowed(settings, threadAdapterState.isThemeTheSameMap[uid]!!)
}

var styledBody = if (type == TEXT_PLAIN) createHtmlForPlainText(bodyWebView) else bodyWebView
Expand All @@ -298,7 +296,8 @@ class ThreadAdapter(
}

private fun WebView.processMailDisplay(styledBody: String, uid: String, isForPrinting: Boolean): String {
val isDisplayedInDark = context.isNightModeEnabled() && isThemeTheSameMap[uid] == true && !isForPrinting
val isDisplayedInDark =
context.isNightModeEnabled() && threadAdapterState.isThemeTheSameMap[uid] == true && !isForPrinting
return if (isForPrinting) {
webViewUtils.processHtmlForPrint(styledBody, HtmlFormatter.PrintData(context, items.first() as Message))
} else {
Expand Down Expand Up @@ -369,8 +368,8 @@ class ThreadAdapter(
bccGroup.isVisible = message.bcc.isNotEmpty()
}

private fun MessageViewHolder.handleHeaderClick(message: Message) = with(binding) {
messageHeader.setOnClickListener {
private fun MessageViewHolder.handleHeaderClick(message: Message) = with(threadAdapterState) {
binding.messageHeader.setOnClickListener {
if (isExpandedMap[message.uid] == true) {
isExpandedMap[message.uid] = false
onExpandOrCollapseMessage(message)
Expand Down Expand Up @@ -467,6 +466,7 @@ class ThreadAdapter(
}

private fun MessageViewHolder.bindBody(message: Message, hasQuote: Boolean) = with(binding) {

bodyWebView.setupLinkContextualMenu { data, type ->
threadAdapterCallbacks?.promptLink?.invoke(data, type)
}
Expand Down Expand Up @@ -536,15 +536,15 @@ class ThreadAdapter(
private fun onExpandedMessageLoaded(messageUid: String) {
if (hasNotScrolledYet) {
currentSetOfLoadedExpandedMessagesUids.add(messageUid)
if (currentSetOfLoadedExpandedMessagesUids.containsAll(initialSetOfExpandedMessagesUids)) {
if (currentSetOfLoadedExpandedMessagesUids.containsAll(threadAdapterState.isExpandedMap.keys)) {
hasNotScrolledYet = false
threadAdapterCallbacks?.onAllExpandedMessagesLoaded?.invoke()
}
}
}

private fun MessageViewHolder.onExpandOrCollapseMessage(message: Message, shouldTrack: Boolean = true) = with(binding) {
val isExpanded = isExpandedMap[message.uid]!!
val isExpanded = threadAdapterState.isExpandedMap[message.uid]!!

if (shouldTrack) context.trackMessageEvent("openMessage", isExpanded)

Expand Down Expand Up @@ -693,7 +693,6 @@ class ThreadAdapter(

data class SuperCollapsedBlock(
var shouldBeDisplayed: Boolean = true,
var hasBeenClicked: Boolean = false,
val messagesUids: MutableSet<String> = mutableSetOf(),
) {
fun isFirstTime() = shouldBeDisplayed && messagesUids.isEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.mail.ui.main.thread

interface ThreadAdapterState {
var isExpandedMap: MutableMap<String, Boolean>
var isThemeTheSameMap: MutableMap<String, Boolean>
var hasSuperCollapsedBlockBeenClicked: Boolean
var verticalScroll: Int?
}
Loading

0 comments on commit 0460ad1

Please sign in to comment.