From 937bb3da225c1f0e9670a67a924b80ae9a06d264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montw=C3=A9?= Date: Fri, 13 Sep 2024 13:44:08 +0200 Subject: [PATCH] Add OnFolderClick event and add OpenFolder and CloseDrawer effect --- .../drawer/ui/DrawerContentPreview.kt | 1 + .../feature/navigation/drawer/FolderDrawer.kt | 8 +++- .../navigation/drawer/ui/DrawerContent.kt | 6 ++- .../navigation/drawer/ui/DrawerContract.kt | 11 ++++-- .../navigation/drawer/ui/DrawerView.kt | 10 ++++- .../navigation/drawer/ui/DrawerViewModel.kt | 23 +++++++++-- .../navigation/drawer/ui/DrawerStateTest.kt | 26 +++++++++++++ .../navigation/drawer/ui/DrawerViewKtTest.kt | 34 +++++++++++++++++ .../drawer/ui/DrawerViewModelTest.kt | 38 +++++++++++++++++++ .../java/com/fsck/k9/activity/MessageList.kt | 2 + 10 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerStateTest.kt diff --git a/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContentPreview.kt b/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContentPreview.kt index f9cb6d5376..6b7de75020 100644 --- a/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContentPreview.kt +++ b/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContentPreview.kt @@ -29,6 +29,7 @@ fun DrawerContentWithAccountPreview() { state = DrawerContract.State( accounts = persistentListOf(DISPLAY_ACCOUNT), currentAccount = DISPLAY_ACCOUNT, + folders = persistentListOf(), ), onEvent = {}, ) diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/FolderDrawer.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/FolderDrawer.kt index b2ff0e540f..0eefbe58b6 100644 --- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/FolderDrawer.kt +++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/FolderDrawer.kt @@ -15,6 +15,8 @@ import org.koin.core.component.inject class FolderDrawer( override val parent: AppCompatActivity, + private val openFolder: (folderId: Long) -> Unit, + createDrawerListener: () -> DrawerLayout.DrawerListener, ) : NavigationDrawer, KoinComponent { private val themeProvider: FeatureThemeProvider by inject() @@ -28,10 +30,14 @@ class FolderDrawer( sliderView.visibility = View.GONE drawerView.visibility = View.VISIBLE swipeRefreshLayout.isEnabled = false + drawer.addDrawerListener(createDrawerListener()) drawerView.setContent { themeProvider.WithTheme { - DrawerView() + DrawerView( + openFolder = openFolder, + closeDrawer = { close() }, + ) } } } diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContent.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContent.kt index 202bf9c7b0..3ad4ae63ab 100644 --- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContent.kt +++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContent.kt @@ -46,8 +46,10 @@ fun DrawerContent( } FolderList( folders = state.folders, - selectedFolder = state.folders.firstOrNull(), // TODO Use selected folder from state - onFolderClick = { }, + selectedFolder = state.selectedFolder, + onFolderClick = { folder -> + onEvent(Event.OnFolderClick(folder)) + }, showStarredCount = state.showStarredCount, ) } diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContract.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContract.kt index d2652ddc2f..aabeb8cfb0 100644 --- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContract.kt +++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContract.kt @@ -13,18 +13,23 @@ interface DrawerContract { @Stable data class State( - val currentAccount: DisplayAccount? = null, val accounts: ImmutableList = persistentListOf(), + val currentAccount: DisplayAccount? = null, val folders: ImmutableList = persistentListOf(), + val selectedFolder: DisplayFolder? = null, val showStarredCount: Boolean = false, val isLoading: Boolean = false, ) sealed interface Event { - data object OnRefresh : Event data class OnAccountClick(val account: DisplayAccount) : Event data class OnAccountViewClick(val account: DisplayAccount) : Event + data class OnFolderClick(val folder: DisplayFolder) : Event + data object OnRefresh : Event } - sealed interface Effect + sealed interface Effect { + data class OpenFolder(val folderId: Long) : Effect + data object CloseDrawer : Effect + } } diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerView.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerView.kt index ca14902c94..d174cc103d 100644 --- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerView.kt +++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerView.kt @@ -3,15 +3,23 @@ package app.k9mail.feature.navigation.drawer.ui import androidx.compose.runtime.Composable import app.k9mail.core.ui.compose.common.mvi.observe import app.k9mail.core.ui.compose.designsystem.molecule.PullToRefreshBox +import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Effect import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Event import app.k9mail.feature.navigation.drawer.ui.DrawerContract.ViewModel import org.koin.androidx.compose.koinViewModel @Composable fun DrawerView( + openFolder: (folderId: Long) -> Unit, + closeDrawer: () -> Unit, viewModel: ViewModel = koinViewModel(), ) { - val (state, dispatch) = viewModel.observe { } + val (state, dispatch) = viewModel.observe { effect -> + when (effect) { + is Effect.OpenFolder -> openFolder(effect.folderId) + Effect.CloseDrawer -> closeDrawer() + } + } PullToRefreshBox( isRefreshing = state.value.isLoading, diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModel.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModel.kt index 87a7f664bc..1ba208edd5 100644 --- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModel.kt +++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModel.kt @@ -8,6 +8,7 @@ import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Effect import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Event import app.k9mail.feature.navigation.drawer.ui.DrawerContract.State import app.k9mail.feature.navigation.drawer.ui.DrawerContract.ViewModel +import app.k9mail.legacy.ui.folder.DisplayFolder import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -77,6 +78,7 @@ class DrawerViewModel( when (event) { Event.OnRefresh -> refresh() is Event.OnAccountClick -> selectAccount(event.account) + is Event.OnFolderClick -> selectFolder(event.folder) is Event.OnAccountViewClick -> { selectAccount( state.value.accounts.nextOrFirst(event.account)!!, @@ -106,10 +108,19 @@ class DrawerViewModel( } } - private fun refresh() { - if (state.value.isLoading) { - return + private fun selectFolder(folder: DisplayFolder) { + updateState { + it.copy(selectedFolder = folder) } + emitEffect(Effect.OpenFolder(folder.folder.id)) + + viewModelScope.launch { + delay(DRAWER_CLOSE_DELAY) + emitEffect(Effect.CloseDrawer) + } + } + + private fun refresh() { viewModelScope.launch { updateState { it.copy(isLoading = true) @@ -124,3 +135,9 @@ class DrawerViewModel( } } } + +/** + * Delay before closing the drawer to avoid the drawer being closed immediately and give time + * for the ripple effect to finish. + */ +private const val DRAWER_CLOSE_DELAY = 250L diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerStateTest.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerStateTest.kt new file mode 100644 index 0000000000..6f36a5801d --- /dev/null +++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerStateTest.kt @@ -0,0 +1,26 @@ +package app.k9mail.feature.navigation.drawer.ui + +import app.k9mail.feature.navigation.drawer.ui.DrawerContract.State +import assertk.assertThat +import assertk.assertions.isEqualTo +import kotlinx.collections.immutable.persistentListOf +import org.junit.Test + +class DrawerStateTest { + + @Test + fun `should set default values`() { + val state = State() + + assertThat(state).isEqualTo( + State( + accounts = persistentListOf(), + currentAccount = null, + folders = persistentListOf(), + selectedFolder = null, + showStarredCount = false, + isLoading = false, + ), + ) + } +} diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewKtTest.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewKtTest.kt index 82c0c76c8a..bf14cbfa5c 100644 --- a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewKtTest.kt +++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewKtTest.kt @@ -5,12 +5,44 @@ import androidx.compose.ui.test.printToString import app.k9mail.core.ui.compose.testing.ComposeTest import app.k9mail.core.ui.compose.testing.onNodeWithTag import app.k9mail.core.ui.compose.testing.setContentWithTheme +import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Effect import app.k9mail.feature.navigation.drawer.ui.DrawerContract.State +import assertk.assertThat +import assertk.assertions.isEqualTo import kotlin.test.Test import kotlinx.coroutines.test.runTest class DrawerViewKtTest : ComposeTest() { + @Test + fun `should delegate effects`() = runTest { + val initialState = State() + val viewModel = FakeDrawerViewModel(initialState) + var openFolderCounter = 0 + var closeDrawerCounter = 0 + + setContentWithTheme { + DrawerView( + openFolder = { openFolderCounter++ }, + closeDrawer = { closeDrawerCounter++ }, + viewModel = viewModel, + ) + } + + assertThat(openFolderCounter).isEqualTo(0) + assertThat(closeDrawerCounter).isEqualTo(0) + + viewModel.effect(Effect.OpenFolder(1)) + + assertThat(openFolderCounter).isEqualTo(1) + assertThat(closeDrawerCounter).isEqualTo(0) + + viewModel.effect(Effect.CloseDrawer) + + assertThat(openFolderCounter).isEqualTo(1) + assertThat(closeDrawerCounter).isEqualTo(1) + } + @Test fun `pull refresh should listen to view model state`() = runTest { val initialState = State( @@ -20,6 +52,8 @@ class DrawerViewKtTest : ComposeTest() { setContentWithTheme { DrawerView( + openFolder = {}, + closeDrawer = {}, viewModel = viewModel, ) } diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModelTest.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModelTest.kt index e4fdece880..7ea0e24a50 100644 --- a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModelTest.kt +++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModelTest.kt @@ -3,8 +3,12 @@ package app.k9mail.feature.navigation.drawer.ui import app.k9mail.core.mail.folder.api.Folder import app.k9mail.core.mail.folder.api.FolderType import app.k9mail.core.ui.compose.testing.MainDispatcherRule +import app.k9mail.core.ui.compose.testing.mvi.assertThatAndEffectTurbineConsumed import app.k9mail.core.ui.compose.testing.mvi.eventStateTest +import app.k9mail.core.ui.compose.testing.mvi.turbines +import app.k9mail.core.ui.compose.testing.mvi.turbinesWithInitialStateCheck import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount +import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Effect import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Event import app.k9mail.feature.navigation.drawer.ui.DrawerContract.State import app.k9mail.legacy.account.Account @@ -13,6 +17,8 @@ import app.k9mail.legacy.ui.folder.DisplayFolder import assertk.assertThat import assertk.assertions.isEqualTo import kotlin.test.Test +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -153,6 +159,38 @@ class DrawerViewModelTest { assertThat(testSubject.state.value.folders).isEqualTo(displayFolders) } + @Test + fun `should set selected folder when OnFolderClick event is received`() = runTest { + val displayAccounts = createDisplayAccountList(3) + val getDisplayAccountsFlow = MutableStateFlow(displayAccounts) + val displayFoldersMap = mapOf( + displayAccounts[0].account.uuid to createDisplayFolderList(3), + ) + val initialState = State( + accounts = displayAccounts.toImmutableList(), + currentAccount = displayAccounts[0], + folders = displayFoldersMap[displayAccounts[0].account.uuid]?.toImmutableList() ?: persistentListOf(), + ) + val testSubject = createTestSubject( + displayAccountsFlow = getDisplayAccountsFlow, + displayFoldersMap = displayFoldersMap, + ) + val turbines = turbinesWithInitialStateCheck(testSubject, initialState) + + advanceUntilIdle() + + val displayFolders = displayFoldersMap[displayAccounts[0].account.uuid] ?: emptyList() + testSubject.event(Event.OnFolderClick(displayFolders[1])) + + assertThat(turbines.awaitStateItem().selectedFolder).isEqualTo(displayFolders[1]) + + assertThat(turbines.awaitEffectItem()).isEqualTo(Effect.OpenFolder(displayFolders[1].folder.id)) + + turbines.assertThatAndEffectTurbineConsumed { + isEqualTo(Effect.CloseDrawer) + } + } + private fun createTestSubject( displayAccountsFlow: Flow> = flow { emit(emptyList()) }, displayFoldersMap: Map> = emptyMap(), diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt index 1a269b058c..9645d93a64 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt @@ -608,6 +608,8 @@ open class MessageList : private fun initializeFolderDrawer() { navigationDrawer = FolderDrawer( parent = this, + openFolder = { folderId -> openFolder(folderId) }, + createDrawerListener = { createDrawerListener() }, ) }