759 lines
31 KiB
Kotlin
759 lines
31 KiB
Kotlin
/*
|
|
* Copyright (c) 2022 Proton AG
|
|
*
|
|
* This file is part of Proton Mail.
|
|
*
|
|
* Proton Mail 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.
|
|
*
|
|
* Proton Mail 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 Proton Mail. If not, see https://www.gnu.org/licenses/.
|
|
*/
|
|
package ch.protonmail.android.mailbox.presentation.viewmodel
|
|
|
|
import androidx.lifecycle.LiveData
|
|
import androidx.lifecycle.MutableLiveData
|
|
import androidx.lifecycle.asLiveData
|
|
import androidx.lifecycle.switchMap
|
|
import androidx.lifecycle.viewModelScope
|
|
import arrow.core.Either
|
|
import arrow.core.Right
|
|
import arrow.core.left
|
|
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository
|
|
import ch.protonmail.android.adapters.swipe.SwipeAction
|
|
import ch.protonmail.android.api.NetworkConfigurator
|
|
import ch.protonmail.android.api.segments.event.FetchEventsAndReschedule
|
|
import ch.protonmail.android.core.Constants
|
|
import ch.protonmail.android.core.Constants.MessageLocationType.INBOX
|
|
import ch.protonmail.android.core.UserManager
|
|
import ch.protonmail.android.data.local.model.Message
|
|
import ch.protonmail.android.domain.LoadMoreFlow
|
|
import ch.protonmail.android.domain.loadMoreBuffer
|
|
import ch.protonmail.android.domain.loadMoreCombine
|
|
import ch.protonmail.android.domain.loadMoreMap
|
|
import ch.protonmail.android.drawer.presentation.mapper.DrawerFoldersAndLabelsSectionUiModelMapper
|
|
import ch.protonmail.android.drawer.presentation.model.DrawerFoldersAndLabelsSectionUiModel
|
|
import ch.protonmail.android.feature.NotLoggedIn
|
|
import ch.protonmail.android.feature.rating.usecase.StartRateAppFlowIfNeeded
|
|
import ch.protonmail.android.labels.domain.LabelRepository
|
|
import ch.protonmail.android.labels.domain.model.Label
|
|
import ch.protonmail.android.labels.domain.model.LabelId
|
|
import ch.protonmail.android.labels.domain.usecase.ObserveLabels
|
|
import ch.protonmail.android.labels.domain.usecase.ObserveLabelsAndFoldersWithChildren
|
|
import ch.protonmail.android.mailbox.domain.ChangeConversationsReadStatus
|
|
import ch.protonmail.android.mailbox.domain.ChangeConversationsStarredStatus
|
|
import ch.protonmail.android.mailbox.domain.DeleteConversations
|
|
import ch.protonmail.android.mailbox.domain.MoveConversationsToFolder
|
|
import ch.protonmail.android.mailbox.domain.model.AllUnreadCounters
|
|
import ch.protonmail.android.mailbox.domain.model.Conversation
|
|
import ch.protonmail.android.mailbox.domain.model.GetAllConversationsParameters
|
|
import ch.protonmail.android.mailbox.domain.model.GetAllMessagesParameters
|
|
import ch.protonmail.android.mailbox.domain.model.GetAllMessagesParameters.UnreadStatus
|
|
import ch.protonmail.android.mailbox.domain.model.GetConversationsResult
|
|
import ch.protonmail.android.mailbox.domain.model.GetMessagesResult
|
|
import ch.protonmail.android.mailbox.domain.model.UnreadCounter
|
|
import ch.protonmail.android.mailbox.domain.usecase.MoveMessagesToFolder
|
|
import ch.protonmail.android.mailbox.domain.usecase.ObserveAllUnreadCounters
|
|
import ch.protonmail.android.mailbox.domain.usecase.ObserveConversationsByLocation
|
|
import ch.protonmail.android.mailbox.domain.usecase.ObserveMessagesByLocation
|
|
import ch.protonmail.android.mailbox.presentation.mapper.MailboxItemUiModelMapper
|
|
import ch.protonmail.android.mailbox.presentation.model.MailboxItemUiModel
|
|
import ch.protonmail.android.mailbox.presentation.model.MailboxListState
|
|
import ch.protonmail.android.mailbox.presentation.model.MailboxState
|
|
import ch.protonmail.android.mailbox.presentation.model.UnreadChipState
|
|
import ch.protonmail.android.mailbox.presentation.model.UnreadChipUiModel
|
|
import ch.protonmail.android.mailbox.presentation.util.ConversationModeEnabled
|
|
import ch.protonmail.android.notifications.presentation.usecase.ClearNotificationsForUser
|
|
import ch.protonmail.android.settings.domain.usecase.GetMailSettings
|
|
import ch.protonmail.android.usecase.VerifyConnection
|
|
import ch.protonmail.android.usecase.delete.DeleteMessage
|
|
import ch.protonmail.android.usecase.delete.EmptyFolder
|
|
import ch.protonmail.android.usecase.message.ChangeMessagesReadStatus
|
|
import ch.protonmail.android.usecase.message.ChangeMessagesStarredStatus
|
|
import ch.protonmail.android.utils.Event
|
|
import ch.protonmail.android.viewmodel.ConnectivityBaseViewModel
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
import kotlinx.coroutines.channels.BufferOverflow
|
|
import kotlinx.coroutines.flow.Flow
|
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.SharedFlow
|
|
import kotlinx.coroutines.flow.asStateFlow
|
|
import kotlinx.coroutines.flow.catch
|
|
import kotlinx.coroutines.flow.combine
|
|
import kotlinx.coroutines.flow.filter
|
|
import kotlinx.coroutines.flow.filterIsInstance
|
|
import kotlinx.coroutines.flow.filterNotNull
|
|
import kotlinx.coroutines.flow.flatMapLatest
|
|
import kotlinx.coroutines.flow.flowOf
|
|
import kotlinx.coroutines.flow.launchIn
|
|
import kotlinx.coroutines.flow.map
|
|
import kotlinx.coroutines.flow.onEach
|
|
import kotlinx.coroutines.flow.onStart
|
|
import kotlinx.coroutines.flow.take
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.runBlocking
|
|
import me.proton.core.domain.arch.DataResult
|
|
import me.proton.core.domain.entity.UserId
|
|
import me.proton.core.util.kotlin.takeIfNotBlank
|
|
import timber.log.Timber
|
|
import javax.inject.Inject
|
|
|
|
const val FLOW_START_ACTIVITY = 1
|
|
const val FLOW_USED_SPACE_CHANGED = 2
|
|
const val FLOW_TRY_COMPOSE = 3
|
|
|
|
@Suppress("LongParameterList") // Every new parameter adds a new issue and breaks the build
|
|
@HiltViewModel
|
|
internal class MailboxViewModel @Inject constructor(
|
|
private val messageDetailsRepositoryFactory: MessageDetailsRepository.AssistedFactory,
|
|
private val userManager: UserManager,
|
|
private val deleteMessage: DeleteMessage,
|
|
private val labelRepository: LabelRepository,
|
|
verifyConnection: VerifyConnection,
|
|
networkConfigurator: NetworkConfigurator,
|
|
private val conversationModeEnabled: ConversationModeEnabled,
|
|
private val observeMessagesByLocation: ObserveMessagesByLocation,
|
|
private val observeConversationsByLocation: ObserveConversationsByLocation,
|
|
private val changeMessagesReadStatus: ChangeMessagesReadStatus,
|
|
private val changeConversationsReadStatus: ChangeConversationsReadStatus,
|
|
private val changeMessagesStarredStatus: ChangeMessagesStarredStatus,
|
|
private val changeConversationsStarredStatus: ChangeConversationsStarredStatus,
|
|
private val observeAllUnreadCounters: ObserveAllUnreadCounters,
|
|
private val moveConversationsToFolder: MoveConversationsToFolder,
|
|
private val moveMessagesToFolder: MoveMessagesToFolder,
|
|
private val deleteConversations: DeleteConversations,
|
|
private val emptyFolder: EmptyFolder,
|
|
private val observeLabels: ObserveLabels,
|
|
private val observeLabelsAndFoldersWithChildren: ObserveLabelsAndFoldersWithChildren,
|
|
private val drawerFoldersAndLabelsSectionUiModelMapper: DrawerFoldersAndLabelsSectionUiModelMapper,
|
|
private val getMailSettings: GetMailSettings,
|
|
private val mailboxItemUiModelMapper: MailboxItemUiModelMapper,
|
|
private val fetchEventsAndReschedule: FetchEventsAndReschedule,
|
|
private val clearNotificationsForUser: ClearNotificationsForUser,
|
|
private val startRateAppFlowIfNeeded: StartRateAppFlowIfNeeded
|
|
) : ConnectivityBaseViewModel(verifyConnection, networkConfigurator) {
|
|
|
|
private val _manageLimitReachedWarning = MutableLiveData<Event<Boolean>>()
|
|
private val _manageLimitApproachingWarning = MutableLiveData<Event<Boolean>>()
|
|
private val _manageLimitBelowCritical = MutableLiveData<Event<Boolean>>()
|
|
private val _manageLimitReachedWarningOnTryCompose = MutableLiveData<Event<Boolean>>()
|
|
private val _toastMessageMaxLabelsReached = MutableLiveData<Event<MaxLabelsReached>>()
|
|
private val _hasSuccessfullyDeletedMessages = MutableLiveData<Boolean>()
|
|
private val mutableMailboxState = MutableStateFlow<MailboxState>(MailboxState.Loading)
|
|
private val mutableMailboxLocation = MutableStateFlow(INBOX)
|
|
private val mutableMailboxLabelId = MutableStateFlow<String?>(null)
|
|
private val mutableUserId = userManager.primaryUserId
|
|
private val mutableRefreshFlow = MutableSharedFlow<Boolean>(
|
|
replay = 1,
|
|
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
|
)
|
|
private val _exitSelectionModeSharedFlow = MutableSharedFlow<Boolean>()
|
|
|
|
private val messageDetailsRepository: MessageDetailsRepository
|
|
get() = messageDetailsRepositoryFactory.create(userManager.requireCurrentUserId())
|
|
|
|
private val isUnreadFilterEnabled = MutableStateFlow(false)
|
|
|
|
var pendingSendsLiveData = mutableUserId.filterNotNull().asLiveData().switchMap {
|
|
messageDetailsRepository.findAllPendingSendsAsync()
|
|
}
|
|
var pendingUploadsLiveData = mutableUserId.filterNotNull().asLiveData().switchMap {
|
|
messageDetailsRepository.findAllPendingUploadsAsync()
|
|
}
|
|
|
|
val primaryUserId: Flow<UserId> = mutableUserId.filterNotNull()
|
|
|
|
val manageLimitReachedWarning: LiveData<Event<Boolean>>
|
|
get() = _manageLimitReachedWarning
|
|
val manageLimitApproachingWarning: LiveData<Event<Boolean>>
|
|
get() = _manageLimitApproachingWarning
|
|
val manageLimitBelowCritical: LiveData<Event<Boolean>>
|
|
get() = _manageLimitBelowCritical
|
|
val manageLimitReachedWarningOnTryCompose: LiveData<Event<Boolean>>
|
|
get() = _manageLimitReachedWarningOnTryCompose
|
|
val toastMessageMaxLabelsReached: LiveData<Event<MaxLabelsReached>>
|
|
get() = _toastMessageMaxLabelsReached
|
|
|
|
val hasSuccessfullyDeletedMessages: LiveData<Boolean>
|
|
get() = _hasSuccessfullyDeletedMessages
|
|
|
|
val exitSelectionModeSharedFlow: SharedFlow<Boolean>
|
|
get() = _exitSelectionModeSharedFlow
|
|
|
|
val mailboxState = mutableMailboxState.asStateFlow()
|
|
val mailboxLocation = mutableMailboxLocation.asStateFlow()
|
|
|
|
val drawerLabels: Flow<DrawerFoldersAndLabelsSectionUiModel> = combine(
|
|
mutableUserId.filterNotNull(),
|
|
mutableRefreshFlow.onStart { emit(true) }
|
|
) { userId, isRefresh -> userId to isRefresh }
|
|
.flatMapLatest { userIdPair ->
|
|
observeLabelsAndFoldersWithChildren(userId = userIdPair.first, shallRefresh = userIdPair.second)
|
|
}
|
|
.map { labelsAndFolders ->
|
|
drawerFoldersAndLabelsSectionUiModelMapper.toUiModels(labelsAndFolders)
|
|
}
|
|
|
|
private val allCounters = mutableUserId.filterNotNull()
|
|
.flatMapLatest { userId -> observeAllUnreadCounters(userId) }
|
|
.filterIsInstance<DataResult.Success<AllUnreadCounters>>()
|
|
.map { countersDataResult ->
|
|
if (conversationModeEnabled(mailboxLocation.value)) {
|
|
countersDataResult.value.conversationsCounters
|
|
} else {
|
|
countersDataResult.value.messagesCounters
|
|
}
|
|
}
|
|
|
|
val unreadCounters: Flow<List<UnreadCounter>> = combine(
|
|
mailboxLocation,
|
|
mutableMailboxLabelId,
|
|
mutableRefreshFlow.onStart { emit(false) },
|
|
allCounters
|
|
) { _, _, _, allCounters -> allCounters }
|
|
.onEach { allCounters ->
|
|
val currentLabelId = getLabelId(mailboxLocation.value, mutableMailboxLabelId.value).id
|
|
val currentLocationUnreadCounter = allCounters.find { it.labelId == currentLabelId }
|
|
?.unreadCount
|
|
?: 0
|
|
val newUnreadChipState = UnreadChipState.Data(
|
|
UnreadChipUiModel(
|
|
isFilterEnabled = isUnreadFilterEnabled.value,
|
|
unreadCount = currentLocationUnreadCounter
|
|
)
|
|
)
|
|
val newMailboxState = mailboxState.value.copy(unreadChip = newUnreadChipState)
|
|
mutableMailboxState.emit(newMailboxState)
|
|
}
|
|
|
|
private lateinit var mailboxStateFlow: LoadMoreFlow<MailboxListState>
|
|
|
|
init {
|
|
combine(
|
|
mutableMailboxLocation,
|
|
mutableMailboxLabelId,
|
|
mutableUserId.filterNotNull(),
|
|
isUnreadFilterEnabled,
|
|
mutableRefreshFlow.onStart { emit(false) }
|
|
) { location, label, userId, isUnreadFilterEnabled, isRefresh ->
|
|
Timber.v("New location: $location, label: $label, user: $userId, isRefresh: $isRefresh")
|
|
GetMailboxItemsParameters(
|
|
userId = userId,
|
|
labelId = getLabelId(location, label),
|
|
isUnreadFilterEnabled = isUnreadFilterEnabled
|
|
)
|
|
}
|
|
.onEach {
|
|
val newState = mailboxState.value.copy(list = MailboxListState.Loading)
|
|
mutableMailboxState.value = newState
|
|
}
|
|
.flatMapLatest { params ->
|
|
val userId = params.userId
|
|
val labelId = requireNotNull(params.labelId) { "labelId is null" }
|
|
|
|
mailboxStateFlow = if (conversationModeEnabled(userId, labelId)) {
|
|
Timber.v("Getting conversations for label: $labelId, user: $userId")
|
|
conversationsAsMailboxItems(params.toGetAllConversationsParameters())
|
|
} else {
|
|
Timber.v("Getting messages for label: $labelId, user: $userId")
|
|
messagesAsMailboxItems(params.toGetAllMessagesParameters())
|
|
}
|
|
mailboxStateFlow
|
|
}
|
|
.catch {
|
|
emit(
|
|
MailboxListState.Error(
|
|
"Failed getting messages, catch",
|
|
it
|
|
)
|
|
)
|
|
}
|
|
.onEach { mailboxListState ->
|
|
mutableMailboxState.value = mailboxState.value.copy(list = mailboxListState)
|
|
}
|
|
.launchIn(viewModelScope)
|
|
|
|
val observePullToRefreshEvents = mutableRefreshFlow.filter { it }
|
|
|
|
fun Flow<Boolean>.waitForRefreshedDataToArrive() = flatMapLatest {
|
|
mutableMailboxState.filter { it.list is MailboxListState.DataRefresh }.take(1)
|
|
}
|
|
|
|
observePullToRefreshEvents
|
|
.waitForRefreshedDataToArrive()
|
|
.onEach { fetchEventsAndReschedule() }
|
|
.launchIn(viewModelScope)
|
|
}
|
|
|
|
fun usedSpaceActionEvent(limitReachedFlow: Int) {
|
|
viewModelScope.launch {
|
|
userManager.setShowStorageLimitReached(true)
|
|
val user = userManager.currentUser
|
|
?: return@launch
|
|
val (usedSpace, totalSpace) = with(user.dedicatedSpace) { used.l.toLong() to total.l.toLong() }
|
|
val userMaxSpace = if (totalSpace == 0L) Long.MAX_VALUE else totalSpace
|
|
val percentageUsed = usedSpace * 100L / userMaxSpace
|
|
val limitReached = percentageUsed >= 100
|
|
val limitApproaching = percentageUsed >= Constants.STORAGE_LIMIT_WARNING_PERCENTAGE
|
|
|
|
when (limitReachedFlow) {
|
|
FLOW_START_ACTIVITY -> {
|
|
if (limitReached) {
|
|
_manageLimitReachedWarning.postValue(Event(limitReached))
|
|
} else if (limitApproaching) {
|
|
_manageLimitApproachingWarning.postValue(Event(limitApproaching))
|
|
}
|
|
}
|
|
FLOW_USED_SPACE_CHANGED -> {
|
|
if (limitReached) {
|
|
_manageLimitReachedWarning.postValue(Event(limitReached))
|
|
} else if (limitApproaching) {
|
|
_manageLimitApproachingWarning.postValue(Event(limitApproaching))
|
|
} else {
|
|
_manageLimitBelowCritical.postValue(Event(true))
|
|
}
|
|
}
|
|
FLOW_TRY_COMPOSE -> {
|
|
_manageLimitReachedWarningOnTryCompose.postValue(Event(limitReached))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Request to fetch more items from API
|
|
*/
|
|
fun loadMore() {
|
|
val location = mailboxLocation.value
|
|
Timber.v("loadMailboxItems location: $location")
|
|
mailboxStateFlow.loadMore()
|
|
}
|
|
|
|
fun messagesToMailboxItemsBlocking(messages: List<Message>): List<MailboxItemUiModel> = runBlocking {
|
|
val userId = userManager.currentUserId
|
|
?: return@runBlocking emptyList()
|
|
val currentLabelId = getLabelId(mailboxLocation.value, mutableMailboxLabelId.value)
|
|
return@runBlocking messagesToMailboxItems(userId, messages, currentLabelId, null)
|
|
}
|
|
|
|
private fun conversationsAsMailboxItems(params: GetAllConversationsParameters): LoadMoreFlow<MailboxListState> {
|
|
val userId = params.userId
|
|
val labelId = requireNotNull(params.labelId) { "labelId is null" }
|
|
|
|
Timber.v("conversationsAsMailboxItems labelId: $labelId")
|
|
var isFirstData = true
|
|
var hasReceivedFirstApiRefresh: Boolean? = null
|
|
return loadMoreCombine(
|
|
observeLabels(userId),
|
|
observeConversationsByLocation(params)
|
|
) { labels, conversations -> labels to conversations }
|
|
.loadMoreBuffer()
|
|
.loadMoreMap { (labels, result) ->
|
|
when (result) {
|
|
is GetConversationsResult.Success -> {
|
|
val shouldResetPosition = isFirstData || hasReceivedFirstApiRefresh == true
|
|
isFirstData = false
|
|
|
|
MailboxListState.Data(
|
|
conversationsToMailboxItems(userId, result.conversations, labelId, labels),
|
|
isFreshData = hasReceivedFirstApiRefresh != null,
|
|
shouldResetPosition = shouldResetPosition
|
|
)
|
|
}
|
|
is GetConversationsResult.DataRefresh -> {
|
|
if (hasReceivedFirstApiRefresh == null) hasReceivedFirstApiRefresh = true
|
|
else if (hasReceivedFirstApiRefresh == true) hasReceivedFirstApiRefresh = false
|
|
|
|
MailboxListState.DataRefresh(
|
|
lastFetchedItemsIds = result.lastFetchedConversations.map { it.id }
|
|
)
|
|
}
|
|
is GetConversationsResult.Error -> {
|
|
hasReceivedFirstApiRefresh = false
|
|
|
|
MailboxListState.Error(
|
|
error = "Failed getting conversations",
|
|
throwable = result.throwable,
|
|
isOffline = result.isOffline
|
|
)
|
|
}
|
|
is GetConversationsResult.Loading ->
|
|
MailboxListState.Loading
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun messagesAsMailboxItems(params: GetAllMessagesParameters): LoadMoreFlow<MailboxListState> {
|
|
val labelId = requireNotNull(params.labelId) { "labelId is null" }
|
|
|
|
Timber.v("messagesAsMailboxItems labelId: ${params.labelId}")
|
|
var isFirstData = true
|
|
var hasReceivedFirstApiRefresh: Boolean? = null
|
|
return loadMoreCombine(
|
|
observeLabels(params.userId),
|
|
observeMessagesByLocation(params)
|
|
) { labels, messages -> labels to messages }
|
|
.loadMoreBuffer()
|
|
.loadMoreMap { pair ->
|
|
val labels = pair.first
|
|
when (val result = pair.second) {
|
|
is GetMessagesResult.Success -> {
|
|
val shouldResetPosition = isFirstData || hasReceivedFirstApiRefresh == true
|
|
isFirstData = false
|
|
|
|
MailboxListState.Data(
|
|
items = messagesToMailboxItems(
|
|
userId = params.userId,
|
|
messages = result.messages,
|
|
currentLabelId = labelId,
|
|
labelsList = labels
|
|
),
|
|
isFreshData = hasReceivedFirstApiRefresh != null,
|
|
shouldResetPosition = shouldResetPosition
|
|
)
|
|
}
|
|
is GetMessagesResult.DataRefresh -> {
|
|
if (hasReceivedFirstApiRefresh == null) hasReceivedFirstApiRefresh = true
|
|
else if (hasReceivedFirstApiRefresh == true) hasReceivedFirstApiRefresh = false
|
|
|
|
MailboxListState.DataRefresh(
|
|
lastFetchedItemsIds = result.lastFetchedMessages.mapNotNull { it.messageId }
|
|
)
|
|
}
|
|
is GetMessagesResult.Error -> {
|
|
hasReceivedFirstApiRefresh = false
|
|
|
|
MailboxListState.Error(
|
|
error = "GetMessagesResult Error",
|
|
throwable = result.throwable,
|
|
isOffline = result.isOffline
|
|
)
|
|
}
|
|
is GetMessagesResult.Loading ->
|
|
MailboxListState.Loading
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun getLabelId(location: Constants.MessageLocationType, labelId: String?): LabelId =
|
|
labelId?.let(::LabelId) ?: location.asLabelId()
|
|
|
|
private suspend fun conversationsToMailboxItems(
|
|
userId: UserId,
|
|
conversations: List<Conversation>,
|
|
labelId: LabelId,
|
|
labels: List<Label>
|
|
): List<MailboxItemUiModel> =
|
|
mailboxItemUiModelMapper.toUiModels(
|
|
userId = userId,
|
|
conversations = conversations,
|
|
currentLabelId = labelId,
|
|
allLabels = labels
|
|
)
|
|
|
|
private suspend fun messagesToMailboxItems(
|
|
userId: UserId,
|
|
messages: List<Message>,
|
|
currentLabelId: LabelId,
|
|
labelsList: List<Label>?
|
|
): List<MailboxItemUiModel> {
|
|
Timber.v("messagesToMailboxItems size: ${messages.size}")
|
|
|
|
val labelIds = messages.flatMap { message -> message.allLabelIDs }.distinct().map { LabelId(it) }
|
|
|
|
val allLabels = labelsList ?: labelIds
|
|
.chunked(Constants.MAX_SQL_ARGUMENTS)
|
|
.flatMap { idsChunk -> labelRepository.findLabels(idsChunk) }
|
|
|
|
return mailboxItemUiModelMapper.toUiModels(messages, currentLabelId, allLabels)
|
|
}
|
|
|
|
fun enableUnreadFilter() {
|
|
viewModelScope.launch {
|
|
exitSelectionMode(areMailboxItemsMovedFromLocation = true)
|
|
toggleUnreadFilter(isFilterEnabled = true)
|
|
}
|
|
}
|
|
|
|
fun disableUnreadFilter() {
|
|
viewModelScope.launch {
|
|
exitSelectionMode(areMailboxItemsMovedFromLocation = true)
|
|
toggleUnreadFilter(isFilterEnabled = false)
|
|
}
|
|
}
|
|
|
|
private suspend fun toggleUnreadFilter(isFilterEnabled: Boolean) {
|
|
val prevMailboxState = mailboxState.value
|
|
val prevUnreadChipState = prevMailboxState.unreadChip
|
|
if (prevUnreadChipState is UnreadChipState.Data) {
|
|
val newChipUiModel = prevUnreadChipState.model.copy(isFilterEnabled = isFilterEnabled)
|
|
val newUnreadChipState = prevUnreadChipState.copy(model = newChipUiModel)
|
|
mutableMailboxState.emit(prevMailboxState.copy(unreadChip = newUnreadChipState))
|
|
}
|
|
|
|
isUnreadFilterEnabled.emit(isFilterEnabled)
|
|
}
|
|
|
|
fun deleteAction(
|
|
ids: List<String>,
|
|
userId: UserId,
|
|
currentLocation: Constants.MessageLocationType
|
|
) {
|
|
viewModelScope.launch {
|
|
if (conversationModeEnabled(currentLocation)) {
|
|
deleteConversations(ids, userId, currentLocation.messageLocationTypeValue.toString())
|
|
} else {
|
|
val deleteMessagesResult = deleteMessage(
|
|
ids,
|
|
currentLocation.messageLocationTypeValue.toString(),
|
|
userId
|
|
)
|
|
_hasSuccessfullyDeletedMessages.postValue(deleteMessagesResult.isSuccessfullyDeleted)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun markRead(
|
|
ids: List<String>,
|
|
userId: UserId,
|
|
location: Constants.MessageLocationType,
|
|
locationId: String
|
|
) {
|
|
viewModelScope.launch {
|
|
if (conversationModeEnabled(location)) {
|
|
changeConversationsReadStatus(
|
|
ids,
|
|
ChangeConversationsReadStatus.Action.ACTION_MARK_READ,
|
|
userId,
|
|
locationId
|
|
)
|
|
} else {
|
|
changeMessagesReadStatus(
|
|
ids,
|
|
ChangeMessagesReadStatus.Action.ACTION_MARK_READ,
|
|
userId
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun markUnRead(
|
|
ids: List<String>,
|
|
userId: UserId,
|
|
location: Constants.MessageLocationType,
|
|
locationId: String
|
|
) {
|
|
viewModelScope.launch {
|
|
if (conversationModeEnabled(location)) {
|
|
changeConversationsReadStatus(
|
|
ids,
|
|
ChangeConversationsReadStatus.Action.ACTION_MARK_UNREAD,
|
|
userId,
|
|
locationId
|
|
)
|
|
} else {
|
|
changeMessagesReadStatus(
|
|
ids,
|
|
ChangeMessagesReadStatus.Action.ACTION_MARK_UNREAD,
|
|
userId
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun star(ids: List<String>, userId: UserId, location: Constants.MessageLocationType) {
|
|
viewModelScope.launch {
|
|
if (conversationModeEnabled(location)) {
|
|
changeConversationsStarredStatus(
|
|
ids,
|
|
userId,
|
|
ChangeConversationsStarredStatus.Action.ACTION_STAR
|
|
)
|
|
} else {
|
|
changeMessagesStarredStatus(
|
|
userId,
|
|
ids,
|
|
ChangeMessagesStarredStatus.Action.ACTION_STAR
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun unstar(ids: List<String>, userId: UserId, location: Constants.MessageLocationType) {
|
|
viewModelScope.launch {
|
|
if (conversationModeEnabled(location)) {
|
|
changeConversationsStarredStatus(
|
|
ids,
|
|
userId,
|
|
ChangeConversationsStarredStatus.Action.ACTION_UNSTAR
|
|
)
|
|
} else {
|
|
changeMessagesStarredStatus(
|
|
userId,
|
|
ids,
|
|
ChangeMessagesStarredStatus.Action.ACTION_UNSTAR
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun moveToFolder(
|
|
ids: List<String>,
|
|
userId: UserId,
|
|
currentLocation: Constants.MessageLocationType,
|
|
destinationFolderId: String
|
|
) {
|
|
viewModelScope.launch {
|
|
if (conversationModeEnabled(currentLocation)) {
|
|
moveConversationsToFolder(
|
|
ids,
|
|
userId,
|
|
destinationFolderId
|
|
)
|
|
} else {
|
|
moveMessagesToFolder(
|
|
ids,
|
|
destinationFolderId,
|
|
currentLocation.messageLocationTypeValue.toString(),
|
|
userId
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun emptyFolderAction(userId: UserId, labelId: LabelId) {
|
|
viewModelScope.launch {
|
|
emptyFolder(userId, labelId)
|
|
}
|
|
}
|
|
|
|
fun setNewMailboxLocation(newLocation: Constants.MessageLocationType) {
|
|
if (mutableMailboxLocation.value != newLocation) {
|
|
mutableMailboxLocation.value = newLocation
|
|
}
|
|
}
|
|
|
|
fun setNewMailboxLabel(labelId: String) {
|
|
if (mutableMailboxLabelId.value != labelId) {
|
|
mutableMailboxLabelId.value = labelId.takeIfNotBlank()
|
|
}
|
|
}
|
|
|
|
fun refreshMessages() {
|
|
mutableRefreshFlow.tryEmit(true)
|
|
}
|
|
|
|
fun exitSelectionMode(areMailboxItemsMovedFromLocation: Boolean) {
|
|
viewModelScope.launch {
|
|
_exitSelectionModeSharedFlow.emit(areMailboxItemsMovedFromLocation)
|
|
}
|
|
}
|
|
|
|
fun handleConversationSwipe(
|
|
swipeAction: SwipeAction,
|
|
mailboxUiItem: MailboxItemUiModel,
|
|
mailboxLocation: Constants.MessageLocationType,
|
|
mailboxLocationId: String
|
|
) {
|
|
when (swipeAction) {
|
|
SwipeAction.TRASH ->
|
|
moveToFolder(
|
|
listOf(mailboxUiItem.itemId),
|
|
UserId(userManager.requireCurrentUserId().id),
|
|
mailboxLocation,
|
|
Constants.MessageLocationType.TRASH.messageLocationTypeValue.toString()
|
|
)
|
|
SwipeAction.SPAM ->
|
|
moveToFolder(
|
|
listOf(mailboxUiItem.itemId),
|
|
UserId(userManager.requireCurrentUserId().id),
|
|
mailboxLocation,
|
|
Constants.MessageLocationType.SPAM.messageLocationTypeValue.toString()
|
|
)
|
|
SwipeAction.UPDATE_STAR ->
|
|
if (mailboxUiItem.isStarred) {
|
|
unstar(
|
|
listOf(mailboxUiItem.itemId),
|
|
UserId(userManager.requireCurrentUserId().id),
|
|
mailboxLocation
|
|
)
|
|
} else {
|
|
star(
|
|
listOf(mailboxUiItem.itemId),
|
|
UserId(userManager.requireCurrentUserId().id),
|
|
mailboxLocation
|
|
)
|
|
}
|
|
SwipeAction.ARCHIVE ->
|
|
moveToFolder(
|
|
listOf(mailboxUiItem.itemId),
|
|
UserId(userManager.requireCurrentUserId().id),
|
|
mailboxLocation,
|
|
Constants.MessageLocationType.ARCHIVE.messageLocationTypeValue.toString()
|
|
)
|
|
SwipeAction.MARK_READ ->
|
|
markRead(
|
|
listOf(mailboxUiItem.itemId),
|
|
UserId(userManager.requireCurrentUserId().id),
|
|
mailboxLocation,
|
|
mailboxLocationId
|
|
)
|
|
}
|
|
}
|
|
|
|
fun clearNotifications(userId: UserId) {
|
|
viewModelScope.launch {
|
|
clearNotificationsForUser.invoke(userId)
|
|
}
|
|
}
|
|
|
|
fun getMailSettingsState(): Flow<Either<NotLoggedIn, GetMailSettings.Result>> =
|
|
userManager.primaryUserId.flatMapLatest { userId ->
|
|
if (userId == null) {
|
|
return@flatMapLatest flowOf(NotLoggedIn.left())
|
|
}
|
|
|
|
getMailSettings(userId).map(::Right)
|
|
}
|
|
|
|
fun startRateAppFlowIfNeeded() {
|
|
val userId = userManager.currentUserId ?: return
|
|
viewModelScope.launch {
|
|
startRateAppFlowIfNeeded.invoke(userId)
|
|
}
|
|
}
|
|
|
|
data class GetMailboxItemsParameters(
|
|
val userId: UserId,
|
|
val labelId: LabelId,
|
|
val isUnreadFilterEnabled: Boolean
|
|
) {
|
|
|
|
fun toGetAllConversationsParameters() = GetAllConversationsParameters(
|
|
userId = userId,
|
|
labelId = labelId
|
|
)
|
|
|
|
fun toGetAllMessagesParameters() = GetAllMessagesParameters(
|
|
userId = userId,
|
|
labelId = labelId,
|
|
unreadStatus = if (isUnreadFilterEnabled) UnreadStatus.UNREAD_ONLY else UnreadStatus.ALL,
|
|
sortDirection = if (labelId.id == Constants.MessageLocationType.ALL_SCHEDULED.asLabelIdString()) {
|
|
GetAllMessagesParameters.SortDirection.ASCENDANT
|
|
} else {
|
|
GetAllMessagesParameters.SortDirection.DESCENDANT
|
|
}
|
|
)
|
|
}
|
|
|
|
data class MaxLabelsReached(val subject: String?, val maxAllowedLabels: Int)
|
|
}
|