mirror of
https://github.com/ProtonMail/android-mail.git
synced 2026-05-15 09:50:40 +00:00
Enable switching active category and updating mailbox content accordingly.
- Allow selecting a new active category from mailbox - Propagate category selection through UI, vm and domain layers - Update Rust scroller to reflect active category change - Handle errors and provide user feedback on failure ET-6046
This commit is contained in:
+2
@@ -51,6 +51,8 @@ fun LocalCategoryLabel.toCategoryLabel(): CategoryLabel {
|
||||
|
||||
fun LocalCategoryLabelId.toCategoryLabelId(): CategoryLabelId = CategoryLabelId(this.value.toString())
|
||||
|
||||
fun CategoryLabelId.toLocalCategoryLabelId(): LocalCategoryLabelId = LocalCategoryLabelId(this.id.toULong())
|
||||
|
||||
fun ConversationScrollerCategoryViewResult.toCategoryViewStatus(): CategoryViewStatus = when (this) {
|
||||
is ConversationScrollerCategoryViewResult.Ok -> {
|
||||
v1.toCategoryViewStatus()
|
||||
|
||||
+2
@@ -49,3 +49,5 @@ fun CategoryLabel.toUiModel(): CategoryItemUiModel {
|
||||
}
|
||||
|
||||
fun CategoryLabelId.toUiModel(): CategoryLabelIdUiModel = CategoryLabelIdUiModel(id = id)
|
||||
|
||||
fun CategoryLabelIdUiModel.toDomainModel(): CategoryLabelId = CategoryLabelId(id = id)
|
||||
|
||||
+3
@@ -20,6 +20,7 @@ package ch.protonmail.android.mailconversation.data.local
|
||||
|
||||
import arrow.core.Either
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversation
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversationId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalLabelId
|
||||
@@ -132,4 +133,6 @@ interface RustConversationDataSource {
|
||||
fun observeScrollerFetchNewStatus(): Flow<ConversationScrollerFetchNewStatus>
|
||||
|
||||
fun observeCategoryViewStatus(): Flow<CategoryViewStatus>
|
||||
|
||||
fun setActiveCategoryLabel(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit>
|
||||
}
|
||||
|
||||
+3
@@ -21,6 +21,7 @@ package ch.protonmail.android.mailconversation.data.local
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversation
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversationId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalLabelId
|
||||
@@ -345,4 +346,6 @@ class RustConversationDataSourceImpl @Inject constructor(
|
||||
override fun observeCategoryViewStatus(): Flow<CategoryViewStatus> =
|
||||
rustConversationsQuery.observeCategoryViewStatus()
|
||||
|
||||
override fun setActiveCategoryLabel(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit> =
|
||||
rustConversationsQuery.setActiveCategoryLabel(categoryLabelId)
|
||||
}
|
||||
|
||||
+2
@@ -20,6 +20,7 @@ package ch.protonmail.android.mailconversation.data.local
|
||||
|
||||
import arrow.core.Either
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversation
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversationId
|
||||
import ch.protonmail.android.mailconversation.data.wrapper.ConversationCursorWrapper
|
||||
@@ -53,4 +54,5 @@ interface RustConversationsQuery {
|
||||
|
||||
fun observeScrollerFetchNewStatus(): Flow<ConversationScrollerStatusUpdate>
|
||||
fun observeCategoryViewStatus(): Flow<CategoryViewStatus>
|
||||
fun setActiveCategoryLabel(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit>
|
||||
}
|
||||
|
||||
+5
@@ -22,6 +22,7 @@ import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import ch.protonmail.android.mailcategory.data.mapper.toCategoryViewStatus
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversation
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversationId
|
||||
import ch.protonmail.android.mailcommon.domain.model.DataError
|
||||
@@ -298,6 +299,10 @@ class RustConversationsQueryImpl @Inject constructor(
|
||||
|
||||
override fun observeCategoryViewStatus(): Flow<CategoryViewStatus> = categoryViewStatusFlow.filterNotNull()
|
||||
|
||||
override fun setActiveCategoryLabel(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit> =
|
||||
paginatorState?.paginatorWrapper?.changeCategoryView(categoryLabelId)
|
||||
?: PaginationError.Other(DataError.Local.IllegalStateError).left()
|
||||
|
||||
private fun destroy() {
|
||||
if (paginatorState == null) {
|
||||
Timber.d("rust-conversation-query: no paginator to destroy")
|
||||
|
||||
+5
@@ -21,6 +21,8 @@ package ch.protonmail.android.mailconversation.data.repository
|
||||
import arrow.core.Either
|
||||
import arrow.core.flatMap
|
||||
import arrow.core.right
|
||||
import ch.protonmail.android.mailcategory.data.mapper.toLocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryLabelId
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.domain.model.ConversationCursorError
|
||||
import ch.protonmail.android.mailcommon.domain.model.ConversationId
|
||||
@@ -245,4 +247,7 @@ class RustConversationRepositoryImpl @Inject constructor(
|
||||
|
||||
override fun observeCategoryViewStatus(): Flow<CategoryViewStatus> =
|
||||
rustConversationDataSource.observeCategoryViewStatus()
|
||||
|
||||
override fun setActiveCategoryLabel(categoryLabelId: CategoryLabelId): Either<PaginationError, Unit> =
|
||||
rustConversationDataSource.setActiveCategoryLabel(categoryLabelId.toLocalCategoryLabelId())
|
||||
}
|
||||
|
||||
+8
@@ -23,11 +23,13 @@ import arrow.core.left
|
||||
import arrow.core.right
|
||||
import ch.protonmail.android.mailcategory.data.mapper.toCategoryViewStatus
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversationId
|
||||
import ch.protonmail.android.mailpagination.data.mapper.toPaginationError
|
||||
import ch.protonmail.android.mailpagination.domain.model.PaginationError
|
||||
import timber.log.Timber
|
||||
import uniffi.mail_uniffi.ConversationScroller
|
||||
import uniffi.mail_uniffi.ConversationScrollerChangeCategoryViewResult
|
||||
import uniffi.mail_uniffi.ConversationScrollerCursorResult
|
||||
import uniffi.mail_uniffi.ConversationScrollerFetchMoreResult
|
||||
import uniffi.mail_uniffi.ConversationScrollerGetItemsResult
|
||||
@@ -83,4 +85,10 @@ class ConversationPaginatorWrapper(private val rustPaginator: ConversationScroll
|
||||
fun getScrollerId(): String = rustPaginator.id()
|
||||
|
||||
suspend fun getCategoryViewStatus(): CategoryViewStatus = rustPaginator.categoryView().toCategoryViewStatus()
|
||||
|
||||
fun changeCategoryView(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit> =
|
||||
when (val result = rustPaginator.changeCategoryView(categoryLabelId)) {
|
||||
is ConversationScrollerChangeCategoryViewResult.Error -> result.v1.toPaginationError().left()
|
||||
is ConversationScrollerChangeCategoryViewResult.Ok -> Unit.right()
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -19,6 +19,7 @@
|
||||
package ch.protonmail.android.mailconversation.domain.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryLabelId
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.domain.model.ConversationCursorError
|
||||
import ch.protonmail.android.mailcommon.domain.model.ConversationId
|
||||
@@ -124,4 +125,6 @@ interface ConversationRepository {
|
||||
fun observeScrollerFetchNewStatus(): Flow<ConversationScrollerFetchNewStatus>
|
||||
|
||||
fun observeCategoryViewStatus(): Flow<CategoryViewStatus>
|
||||
|
||||
fun setActiveCategoryLabel(categoryLabelId: CategoryLabelId): Either<PaginationError, Unit>
|
||||
}
|
||||
|
||||
+8
-4
@@ -107,6 +107,7 @@ import ch.protonmail.android.mailattachments.presentation.model.FileContent
|
||||
import ch.protonmail.android.mailattachments.presentation.ui.OpenAttachmentInput
|
||||
import ch.protonmail.android.mailattachments.presentation.ui.fileOpener
|
||||
import ch.protonmail.android.mailattachments.presentation.ui.fileSaver
|
||||
import ch.protonmail.android.mailcategory.presentation.model.CategoryItemUiModel
|
||||
import ch.protonmail.android.mailcommon.presentation.AdaptivePreviews
|
||||
import ch.protonmail.android.mailcommon.presentation.ConsumableLaunchedEffect
|
||||
import ch.protonmail.android.mailcommon.presentation.ConsumableTextEffect
|
||||
@@ -288,7 +289,8 @@ fun MailboxScreen(
|
||||
onClearAllConfirmed = { viewModel.submit(MailboxViewAction.ClearAllConfirmed) },
|
||||
onClearAllDismissed = { viewModel.submit(MailboxViewAction.ClearAllDismissed) },
|
||||
onSnooze = { viewModel.submit(MailboxViewAction.RequestSnoozeBottomSheet) },
|
||||
validateUserSession = { viewModel.submit(MailboxViewAction.ValidateUserSession) }
|
||||
validateUserSession = { viewModel.submit(MailboxViewAction.ValidateUserSession) },
|
||||
onCategoryItemClicked = { viewModel.submit(MailboxViewAction.OnCategoryItemClicked(it)) }
|
||||
)
|
||||
|
||||
val lifecycle = LocalLifecycleOwner.current
|
||||
@@ -458,7 +460,7 @@ fun MailboxScreen(
|
||||
onSpamTrashFilterDisabled = actions.onDisableSpamTrashFilter,
|
||||
onSelectAllClicked = actions.onSelectAllClicked,
|
||||
onDeselectAllClicked = actions.onDeselectAllClicked,
|
||||
onCategoryItemClicked = {}
|
||||
onCategoryItemClicked = actions.onCategoryItemClicked
|
||||
)
|
||||
|
||||
val fileSaver = fileSaver(
|
||||
@@ -1211,7 +1213,8 @@ object MailboxScreen {
|
||||
val onSnooze: () -> Unit,
|
||||
val onCustomizeToolbar: () -> Unit,
|
||||
val validateUserSession: () -> Unit,
|
||||
val onShowRatingBooster: () -> Unit
|
||||
val onShowRatingBooster: () -> Unit,
|
||||
val onCategoryItemClicked: (CategoryItemUiModel) -> Unit
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -1275,7 +1278,8 @@ object MailboxScreen {
|
||||
onSnooze = {},
|
||||
onCustomizeToolbar = {},
|
||||
validateUserSession = {},
|
||||
onShowRatingBooster = {}
|
||||
onShowRatingBooster = {},
|
||||
onCategoryItemClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -36,6 +36,7 @@ import ch.protonmail.android.mailattachments.domain.model.AttachmentId
|
||||
import ch.protonmail.android.mailattachments.domain.model.AttachmentOpenMode
|
||||
import ch.protonmail.android.mailattachments.domain.usecase.GetAttachmentIntentValues
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcategory.presentation.mapper.toDomainModel
|
||||
import ch.protonmail.android.mailcategory.presentation.model.CategoryViewState
|
||||
import ch.protonmail.android.mailcommon.domain.coroutines.AppScope
|
||||
import ch.protonmail.android.mailcommon.domain.model.Action
|
||||
@@ -104,6 +105,7 @@ import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.ObserveCat
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.ObserveValidSenderAddress
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.ObserveViewModeChanged
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.RecordRatingBoosterTriggered
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.SetActiveCategoryLabel
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.ShouldShowRatingBooster
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.UpdateShowSpamTrashFilter
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.UpdateUnreadFilter
|
||||
@@ -228,6 +230,7 @@ class MailboxViewModel @Inject constructor(
|
||||
private val shouldShowRatingBooster: ShouldShowRatingBooster,
|
||||
private val recordRatingBoosterTriggered: RecordRatingBoosterTriggered,
|
||||
private val observeCategoryViewStatus: ObserveCategoryViewStatus,
|
||||
private val setActiveCategoryLabel: SetActiveCategoryLabel,
|
||||
@IsCategoryViewEnabled private val categoryViewEnabled: FeatureFlag<Boolean>
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -469,10 +472,30 @@ class MailboxViewModel @Inject constructor(
|
||||
is MailboxViewAction.SignalLabelAsCompleted -> handleLabelAsCompleted(viewAction)
|
||||
is MailboxViewAction.ValidateUserSession -> handleValidateUserSession()
|
||||
is MailboxViewAction.NavigateToComposer -> handleNavigateToComposer()
|
||||
is MailboxViewAction.OnCategoryItemClicked -> handleCategoryItemClicked(viewAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleCategoryItemClicked(viewAction: MailboxViewAction.OnCategoryItemClicked) {
|
||||
val categoryItem = viewAction.categoryItem
|
||||
|
||||
if (categoryItem.isActive) {
|
||||
Timber.d("Category ${categoryItem.id} is already active, ignoring click")
|
||||
return
|
||||
}
|
||||
|
||||
val viewMode = getViewModeForCurrentLocation(getSelectedMailLabelId())
|
||||
|
||||
setActiveCategoryLabel(
|
||||
categoryLabelId = categoryItem.id.toDomainModel(),
|
||||
viewMode = viewMode
|
||||
).onLeft { error ->
|
||||
Timber.e("Failed to set active category label: $error")
|
||||
emitNewStateFrom(MailboxEvent.ErrorChangingCategory)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNavigateToComposer() {
|
||||
if (state.value.composerNavigationState is MailboxComposerNavigationState.Disabled) {
|
||||
emitNewStateFrom(MailboxEvent.ErrorComposing)
|
||||
|
||||
+4
@@ -21,6 +21,7 @@ package ch.protonmail.android.mailmailbox.presentation.mailbox.model
|
||||
import ch.protonmail.android.mailattachments.domain.model.OpenAttachmentIntentValues
|
||||
import ch.protonmail.android.mailattachments.presentation.model.AttachmentIdUiModel
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcategory.presentation.model.CategoryItemUiModel
|
||||
import ch.protonmail.android.mailcommon.presentation.model.BottomBarEvent
|
||||
import ch.protonmail.android.mailcommon.presentation.model.BottomSheetOperation
|
||||
import ch.protonmail.android.maillabel.domain.model.LabelId
|
||||
@@ -182,6 +183,8 @@ internal sealed interface MailboxViewAction : MailboxOperation {
|
||||
AffectingBottomAppBar
|
||||
|
||||
object ValidateUserSession : MailboxViewAction
|
||||
|
||||
data class OnCategoryItemClicked(val categoryItem: CategoryItemUiModel) : MailboxViewAction
|
||||
}
|
||||
|
||||
internal sealed interface MailboxEvent : MailboxOperation {
|
||||
@@ -351,6 +354,7 @@ internal sealed interface MailboxEvent : MailboxOperation {
|
||||
object ErrorRetrievingFolderColorSettings : MailboxEvent, AffectingErrorBar, AffectingBottomSheet
|
||||
object ErrorMoving : MailboxEvent, AffectingErrorBar
|
||||
object ErrorRetrievingDestinationMailFolders : MailboxEvent, AffectingErrorBar, AffectingBottomSheet
|
||||
object ErrorChangingCategory : MailboxEvent, AffectingErrorBar
|
||||
|
||||
data class AttachmentDownloadStartedEvent(
|
||||
val attachmentId: AttachmentIdUiModel
|
||||
|
||||
+1
@@ -210,6 +210,7 @@ class MailboxReducer @Inject constructor(
|
||||
|
||||
is MailboxEvent.ErrorDeleting -> R.string.mailbox_action_delete_failed
|
||||
is MailboxEvent.ErrorComposing -> R.string.mailbox_action_no_sender_addresses
|
||||
is MailboxEvent.ErrorChangingCategory -> R.string.mailbox_action_change_category_failed
|
||||
}
|
||||
Effect.of(TextUiModel(textResource))
|
||||
} else {
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Proton Technologies AG
|
||||
* This file is part of Proton Technologies AG and 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.mailmailbox.presentation.mailbox.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryLabelId
|
||||
import ch.protonmail.android.mailconversation.domain.repository.ConversationRepository
|
||||
import ch.protonmail.android.maillabel.domain.model.ViewMode
|
||||
import ch.protonmail.android.mailmessage.domain.repository.MessageRepository
|
||||
import ch.protonmail.android.mailpagination.domain.model.PaginationError
|
||||
import javax.inject.Inject
|
||||
|
||||
class SetActiveCategoryLabel @Inject constructor(
|
||||
private val conversationRepository: ConversationRepository,
|
||||
private val messageRepository: MessageRepository
|
||||
) {
|
||||
|
||||
operator fun invoke(categoryLabelId: CategoryLabelId, viewMode: ViewMode): Either<PaginationError, Unit> =
|
||||
when (viewMode) {
|
||||
ViewMode.ConversationGrouping ->
|
||||
conversationRepository.setActiveCategoryLabel(categoryLabelId)
|
||||
|
||||
ViewMode.NoConversationGrouping ->
|
||||
messageRepository.setActiveCategoryLabel(categoryLabelId)
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,8 @@
|
||||
|
||||
<!-- Storage Quota Over Warning-->
|
||||
<string name="mailbox_action_delete_failed">Failed to delete message</string>
|
||||
<string name="mailbox_action_change_category_failed">Failed to change the active category</string>
|
||||
|
||||
|
||||
<string name="select_all">Select All</string>
|
||||
<string name="deselect_all">Deselect All</string>
|
||||
|
||||
+100
-1
@@ -30,7 +30,9 @@ import ch.protonmail.android.mailattachments.domain.model.OpenAttachmentIntentVa
|
||||
import ch.protonmail.android.mailattachments.domain.usecase.GetAttachmentIntentValues
|
||||
import ch.protonmail.android.mailattachments.presentation.model.AttachmentIdUiModel
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcategory.presentation.mapper.toDomainModel
|
||||
import ch.protonmail.android.mailcategory.presentation.model.CategoryViewState
|
||||
import ch.protonmail.android.mailcategory.presentation.sample.CategoryItemUiModelSample
|
||||
import ch.protonmail.android.mailcommon.domain.model.Action
|
||||
import ch.protonmail.android.mailcommon.domain.model.AllBottomBarActions
|
||||
import ch.protonmail.android.mailcommon.domain.model.ConversationId
|
||||
@@ -44,6 +46,7 @@ import ch.protonmail.android.mailcommon.presentation.model.BottomBarState
|
||||
import ch.protonmail.android.mailcommon.presentation.model.BottomBarTarget
|
||||
import ch.protonmail.android.mailcommon.presentation.model.BottomSheetState
|
||||
import ch.protonmail.android.mailcommon.presentation.model.CappedNumberUiModel
|
||||
import ch.protonmail.android.mailcommon.presentation.model.TextUiModel
|
||||
import ch.protonmail.android.mailcommon.presentation.model.toCappedNumberUiModel
|
||||
import ch.protonmail.android.mailcommon.presentation.sample.ActionUiModelSample
|
||||
import ch.protonmail.android.mailcommon.presentation.ui.delete.DeleteDialogState
|
||||
@@ -111,6 +114,7 @@ import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.ObserveCat
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.ObserveValidSenderAddress
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.ObserveViewModeChanged
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.RecordRatingBoosterTriggered
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.SetActiveCategoryLabel
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.ShouldShowRatingBooster
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.UpdateShowSpamTrashFilter
|
||||
import ch.protonmail.android.mailmailbox.presentation.mailbox.usecase.UpdateUnreadFilter
|
||||
@@ -130,6 +134,7 @@ import ch.protonmail.android.mailmessage.domain.usecase.UnStarMessages
|
||||
import ch.protonmail.android.mailmessage.presentation.model.bottomsheet.MailboxMoreActionsBottomSheetState
|
||||
import ch.protonmail.android.mailmessage.presentation.model.bottomsheet.SnoozeSheetState
|
||||
import ch.protonmail.android.mailpagination.domain.model.PageInvalidationEvent
|
||||
import ch.protonmail.android.mailpagination.domain.model.PaginationError
|
||||
import ch.protonmail.android.mailpagination.domain.usecase.ObservePageInvalidationEvents
|
||||
import ch.protonmail.android.mailsession.domain.repository.EventLoopRepository
|
||||
import ch.protonmail.android.mailsession.domain.usecase.HasValidUserSession
|
||||
@@ -364,6 +369,8 @@ internal class MailboxViewModelTest {
|
||||
} returns categoryViewStatusFlow
|
||||
}
|
||||
|
||||
private val setActiveCategoryLabel = mockk<SetActiveCategoryLabel>()
|
||||
|
||||
private val scope = TestScope(UnconfinedTestDispatcher())
|
||||
|
||||
private val mailboxViewModel by lazy {
|
||||
@@ -421,7 +428,8 @@ internal class MailboxViewModelTest {
|
||||
shouldShowRatingBooster = shouldShowRatingBooster,
|
||||
recordRatingBoosterTriggered = recordRatingBoosterTriggered,
|
||||
categoryViewEnabled = isCategoryViewEnabled,
|
||||
observeCategoryViewStatus = observeCategoryViewStatus
|
||||
observeCategoryViewStatus = observeCategoryViewStatus,
|
||||
setActiveCategoryLabel = setActiveCategoryLabel
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3881,6 +3889,97 @@ internal class MailboxViewModelTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when inactive category item is clicked, active category is changed`() = runTest {
|
||||
// Given
|
||||
val categoryItem = CategoryItemUiModelSample.social
|
||||
expectViewModeForCurrentLocation(NoConversationGrouping)
|
||||
|
||||
every {
|
||||
setActiveCategoryLabel(
|
||||
categoryLabelId = categoryItem.id.toDomainModel(),
|
||||
viewMode = NoConversationGrouping
|
||||
)
|
||||
} returns Unit.right()
|
||||
|
||||
// When
|
||||
mailboxViewModel.submit(MailboxViewAction.OnCategoryItemClicked(categoryItem))
|
||||
advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
verify(exactly = 1) {
|
||||
setActiveCategoryLabel(
|
||||
categoryLabelId = categoryItem.id.toDomainModel(),
|
||||
viewMode = NoConversationGrouping
|
||||
)
|
||||
}
|
||||
|
||||
verify(exactly = 0) {
|
||||
mailboxReducer.newStateFrom(any(), MailboxEvent.ErrorChangingCategory)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when active category item is clicked, the request is ignored`() = runTest {
|
||||
// Given
|
||||
val categoryItem = CategoryItemUiModelSample.primary.copy(isActive = true)
|
||||
|
||||
// When
|
||||
mailboxViewModel.submit(MailboxViewAction.OnCategoryItemClicked(categoryItem))
|
||||
advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
verify(exactly = 0) {
|
||||
setActiveCategoryLabel(any(), any())
|
||||
}
|
||||
|
||||
verify(exactly = 0) {
|
||||
mailboxReducer.newStateFrom(any(), MailboxEvent.ErrorChangingCategory)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when changing active category fails, error event is emitted`() = runTest {
|
||||
// Given
|
||||
val categoryItem = CategoryItemUiModelSample.social
|
||||
val expectedState = MailboxStateSampleData.Loading.copy(
|
||||
error = Effect.of(TextUiModel(R.string.mailbox_action_change_category_failed))
|
||||
)
|
||||
|
||||
expectViewModeForCurrentLocation(NoConversationGrouping)
|
||||
|
||||
every {
|
||||
setActiveCategoryLabel(
|
||||
categoryLabelId = categoryItem.id.toDomainModel(),
|
||||
viewMode = NoConversationGrouping
|
||||
)
|
||||
} returns PaginationError.Other(DataError.Local.IllegalStateError).left()
|
||||
|
||||
every {
|
||||
mailboxReducer.newStateFrom(
|
||||
any(),
|
||||
MailboxEvent.ErrorChangingCategory
|
||||
)
|
||||
} returns expectedState
|
||||
|
||||
mailboxViewModel.state.test {
|
||||
awaitItem()
|
||||
|
||||
// When
|
||||
mailboxViewModel.submit(MailboxViewAction.OnCategoryItemClicked(categoryItem))
|
||||
advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
assertEquals(expectedState, awaitItem())
|
||||
|
||||
verify(exactly = 1) {
|
||||
mailboxReducer.newStateFrom(any(), MailboxEvent.ErrorChangingCategory)
|
||||
}
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private fun returnExpectedStateForBottomBarEvent(
|
||||
intermediateState: MailboxState? = null,
|
||||
expectedState: MailboxState
|
||||
|
||||
+4
@@ -20,6 +20,7 @@ package ch.protonmail.android.mailmessage.data.local
|
||||
|
||||
import arrow.core.Either
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalLabelAsAction
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalMessageId
|
||||
@@ -142,4 +143,7 @@ interface RustMessageDataSource {
|
||||
fun observeScrollerFetchNewStatus(): Flow<MessageScrollerFetchNewStatus>
|
||||
|
||||
fun observeCategoryViewStatus(): Flow<CategoryViewStatus>
|
||||
|
||||
fun setActiveCategoryLabel(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit>
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -21,6 +21,7 @@ package ch.protonmail.android.mailmessage.data.local
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalLabelAsAction
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalMessageId
|
||||
@@ -456,4 +457,7 @@ class RustMessageDataSourceImpl @Inject constructor(
|
||||
override fun observeCategoryViewStatus(): Flow<CategoryViewStatus> =
|
||||
rustMessageListQuery.observeCategoryViewStatus()
|
||||
|
||||
override fun setActiveCategoryLabel(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit> =
|
||||
rustMessageListQuery.setActiveCategoryLabel(categoryLabelId)
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -20,6 +20,7 @@ package ch.protonmail.android.mailmessage.data.local
|
||||
|
||||
import arrow.core.Either
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalItemId
|
||||
import ch.protonmail.android.maillabel.domain.model.LabelId
|
||||
import ch.protonmail.android.mailmessage.data.wrapper.MailMessageCursorWrapper
|
||||
@@ -49,4 +50,6 @@ interface RustMessageListQuery {
|
||||
fun observeScrollerFetchNewStatus(): Flow<MessageScrollerStatusUpdate>
|
||||
|
||||
fun observeCategoryViewStatus(): Flow<CategoryViewStatus>
|
||||
|
||||
fun setActiveCategoryLabel(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit>
|
||||
}
|
||||
|
||||
+7
@@ -22,6 +22,7 @@ import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import ch.protonmail.android.mailcategory.data.mapper.toCategoryViewStatus
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalConversationId
|
||||
import ch.protonmail.android.mailcommon.domain.model.DataError
|
||||
import ch.protonmail.android.maillabel.data.local.RustMailboxFactory
|
||||
@@ -188,6 +189,12 @@ class RustMessageListQueryImpl @Inject constructor(
|
||||
|
||||
override suspend fun supportsIncludeFilter() = paginatorState?.paginatorWrapper?.supportsIncludeFilter() == true
|
||||
|
||||
override fun setActiveCategoryLabel(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit> =
|
||||
paginatorState?.paginatorWrapper?.changeCategoryView(categoryLabelId) ?: run {
|
||||
Timber.w("rust-message-query: No paginator to change category view")
|
||||
PaginationError.Other(DataError.Local.IllegalStateError).left()
|
||||
}
|
||||
|
||||
override suspend fun updateUnreadFilter(filterUnread: Boolean) {
|
||||
paginatorState?.paginatorWrapper?.filterUnread(filterUnread)
|
||||
?: Timber.w("rust-message-query: No paginator to update unread filter")
|
||||
|
||||
+6
@@ -20,6 +20,8 @@ package ch.protonmail.android.mailmessage.data.repository
|
||||
|
||||
import java.io.File
|
||||
import arrow.core.Either
|
||||
import ch.protonmail.android.mailcategory.data.mapper.toLocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryLabelId
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.domain.model.ConversationCursorError
|
||||
import ch.protonmail.android.mailcommon.domain.model.CursorId
|
||||
@@ -187,4 +189,8 @@ class RustMessageRepositoryImpl @Inject constructor(
|
||||
|
||||
override suspend fun isMessageSenderBlocked(userId: UserId, messageId: MessageId): Either<DataError, Boolean> =
|
||||
rustMessageDataSource.isMessageSenderBlocked(userId, messageId.toLocalMessageId())
|
||||
|
||||
override fun setActiveCategoryLabel(categoryLabelId: CategoryLabelId): Either<PaginationError, Unit> =
|
||||
rustMessageDataSource.setActiveCategoryLabel(categoryLabelId.toLocalCategoryLabelId())
|
||||
|
||||
}
|
||||
|
||||
+8
@@ -23,12 +23,14 @@ import arrow.core.left
|
||||
import arrow.core.right
|
||||
import ch.protonmail.android.mailcategory.data.mapper.toCategoryViewStatus
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalItemId
|
||||
import ch.protonmail.android.mailpagination.data.mapper.toPaginationError
|
||||
import ch.protonmail.android.mailpagination.domain.model.PaginationError
|
||||
import timber.log.Timber
|
||||
import uniffi.mail_uniffi.IncludeSwitch
|
||||
import uniffi.mail_uniffi.MessageScroller
|
||||
import uniffi.mail_uniffi.MessageScrollerChangeCategoryViewResult
|
||||
import uniffi.mail_uniffi.MessageScrollerCursorResult
|
||||
import uniffi.mail_uniffi.MessageScrollerFetchMoreResult
|
||||
import uniffi.mail_uniffi.MessageScrollerGetItemsResult
|
||||
@@ -94,4 +96,10 @@ class MailboxMessagePaginatorWrapper(
|
||||
override suspend fun getCategoryViewStatus(): CategoryViewStatus =
|
||||
rustPaginator.categoryView().toCategoryViewStatus()
|
||||
|
||||
override fun changeCategoryView(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit> =
|
||||
when (val result = rustPaginator.changeCategoryView(categoryLabelId)) {
|
||||
is MessageScrollerChangeCategoryViewResult.Error -> result.v1.toPaginationError().left()
|
||||
is MessageScrollerChangeCategoryViewResult.Ok -> Unit.right()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -20,6 +20,7 @@ package ch.protonmail.android.mailmessage.data.wrapper
|
||||
|
||||
import arrow.core.Either
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalItemId
|
||||
import ch.protonmail.android.mailpagination.domain.model.PaginationError
|
||||
|
||||
@@ -44,4 +45,6 @@ interface MessagePaginatorWrapper {
|
||||
fun getScrollerId(): String
|
||||
|
||||
suspend fun getCategoryViewStatus(): CategoryViewStatus
|
||||
|
||||
fun changeCategoryView(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit>
|
||||
}
|
||||
|
||||
+6
@@ -22,6 +22,7 @@ import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalCategoryLabelId
|
||||
import ch.protonmail.android.mailcommon.data.mapper.LocalItemId
|
||||
import ch.protonmail.android.mailpagination.data.mapper.toPaginationError
|
||||
import ch.protonmail.android.mailpagination.domain.model.PaginationError
|
||||
@@ -89,4 +90,9 @@ class SearchMessagePaginatorWrapper(
|
||||
|
||||
override suspend fun getCategoryViewStatus(): CategoryViewStatus = CategoryViewStatus.NotAvailable
|
||||
|
||||
override fun changeCategoryView(categoryLabelId: LocalCategoryLabelId): Either<PaginationError, Unit> {
|
||||
Timber.w("search-paginator: Called change category on a search paginator, which is illegal. No-op.")
|
||||
return Unit.right()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -19,6 +19,7 @@
|
||||
package ch.protonmail.android.mailmessage.domain.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryLabelId
|
||||
import ch.protonmail.android.mailcategory.domain.model.CategoryViewStatus
|
||||
import ch.protonmail.android.mailcommon.domain.model.ConversationCursorError
|
||||
import ch.protonmail.android.mailcommon.domain.model.CursorId
|
||||
@@ -168,4 +169,6 @@ interface MessageRepository {
|
||||
fun observeScrollerFetchNewStatus(): Flow<MessageScrollerFetchNewStatus>
|
||||
|
||||
fun observeCategoryViewStatus(): Flow<CategoryViewStatus>
|
||||
|
||||
fun setActiveCategoryLabel(categoryLabelId: CategoryLabelId): Either<PaginationError, Unit>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user