0
0
mirror of https://github.com/thunderbird/thunderbird-android.git synced 2024-09-19 19:52:14 +02:00

Add OnFolderClick event and add OpenFolder and CloseDrawer effect

This commit is contained in:
Wolf-Martell Montwé 2024-09-13 13:44:08 +02:00
parent 008a5e281c
commit 937bb3da22
No known key found for this signature in database
GPG Key ID: 6D45B21512ACBF72
10 changed files with 149 additions and 10 deletions

View File

@ -29,6 +29,7 @@ fun DrawerContentWithAccountPreview() {
state = DrawerContract.State(
accounts = persistentListOf(DISPLAY_ACCOUNT),
currentAccount = DISPLAY_ACCOUNT,
folders = persistentListOf(),
),
onEvent = {},
)

View File

@ -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() },
)
}
}
}

View File

@ -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,
)
}

View File

@ -13,18 +13,23 @@ interface DrawerContract {
@Stable
data class State(
val currentAccount: DisplayAccount? = null,
val accounts: ImmutableList<DisplayAccount> = persistentListOf(),
val currentAccount: DisplayAccount? = null,
val folders: ImmutableList<DisplayFolder> = 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
}
}

View File

@ -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<DrawerViewModel>(),
) {
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,

View File

@ -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

View File

@ -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,
),
)
}
}

View File

@ -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,
)
}

View File

@ -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<List<DisplayAccount>> = flow { emit(emptyList()) },
displayFoldersMap: Map<String, List<DisplayFolder>> = emptyMap(),

View File

@ -608,6 +608,8 @@ open class MessageList :
private fun initializeFolderDrawer() {
navigationDrawer = FolderDrawer(
parent = this,
openFolder = { folderId -> openFolder(folderId) },
createDrawerListener = { createDrawerListener() },
)
}