mirror of
https://github.com/thunderbird/thunderbird-android.git
synced 2024-09-19 19:52:14 +02:00
Merge pull request #8143 from wmontwe/add-drawer-folder-list-part4
Add drawer folder list - part 4
This commit is contained in:
commit
41c06cebbb
@ -13,7 +13,7 @@ internal fun DrawerContentPreview() {
|
|||||||
DrawerContent(
|
DrawerContent(
|
||||||
state = DrawerContract.State(
|
state = DrawerContract.State(
|
||||||
accounts = persistentListOf(),
|
accounts = persistentListOf(),
|
||||||
currentAccount = null,
|
selectedAccount = null,
|
||||||
folders = persistentListOf(),
|
folders = persistentListOf(),
|
||||||
),
|
),
|
||||||
onEvent = {},
|
onEvent = {},
|
||||||
@ -28,7 +28,8 @@ fun DrawerContentWithAccountPreview() {
|
|||||||
DrawerContent(
|
DrawerContent(
|
||||||
state = DrawerContract.State(
|
state = DrawerContract.State(
|
||||||
accounts = persistentListOf(DISPLAY_ACCOUNT),
|
accounts = persistentListOf(DISPLAY_ACCOUNT),
|
||||||
currentAccount = DISPLAY_ACCOUNT,
|
selectedAccount = DISPLAY_ACCOUNT,
|
||||||
|
folders = persistentListOf(),
|
||||||
),
|
),
|
||||||
onEvent = {},
|
onEvent = {},
|
||||||
)
|
)
|
||||||
|
@ -3,9 +3,9 @@ package app.k9mail.feature.navigation.drawer.ui
|
|||||||
import app.k9mail.core.mail.folder.api.Folder
|
import app.k9mail.core.mail.folder.api.Folder
|
||||||
import app.k9mail.core.mail.folder.api.FolderType
|
import app.k9mail.core.mail.folder.api.FolderType
|
||||||
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
|
||||||
import app.k9mail.legacy.account.Account
|
import app.k9mail.legacy.account.Account
|
||||||
import app.k9mail.legacy.account.Identity
|
import app.k9mail.legacy.account.Identity
|
||||||
import app.k9mail.legacy.ui.folder.DisplayFolder
|
|
||||||
|
|
||||||
internal object FakeData {
|
internal object FakeData {
|
||||||
|
|
||||||
@ -45,7 +45,8 @@ internal object FakeData {
|
|||||||
isLocalOnly = false,
|
isLocalOnly = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
val DISPLAY_FOLDER = DisplayFolder(
|
val DISPLAY_FOLDER = DisplayAccountFolder(
|
||||||
|
accountUuid = ACCOUNT_UUID,
|
||||||
folder = FOLDER,
|
folder = FOLDER,
|
||||||
isInTopGroup = false,
|
isInTopGroup = false,
|
||||||
unreadMessageCount = 14,
|
unreadMessageCount = 14,
|
||||||
|
@ -15,6 +15,9 @@ import org.koin.core.component.inject
|
|||||||
|
|
||||||
class FolderDrawer(
|
class FolderDrawer(
|
||||||
override val parent: AppCompatActivity,
|
override val parent: AppCompatActivity,
|
||||||
|
private val openAccount: (account: Account) -> Unit,
|
||||||
|
private val openFolder: (folderId: Long) -> Unit,
|
||||||
|
createDrawerListener: () -> DrawerLayout.DrawerListener,
|
||||||
) : NavigationDrawer, KoinComponent {
|
) : NavigationDrawer, KoinComponent {
|
||||||
|
|
||||||
private val themeProvider: FeatureThemeProvider by inject()
|
private val themeProvider: FeatureThemeProvider by inject()
|
||||||
@ -28,10 +31,15 @@ class FolderDrawer(
|
|||||||
sliderView.visibility = View.GONE
|
sliderView.visibility = View.GONE
|
||||||
drawerView.visibility = View.VISIBLE
|
drawerView.visibility = View.VISIBLE
|
||||||
swipeRefreshLayout.isEnabled = false
|
swipeRefreshLayout.isEnabled = false
|
||||||
|
drawer.addDrawerListener(createDrawerListener())
|
||||||
|
|
||||||
drawerView.setContent {
|
drawerView.setContent {
|
||||||
themeProvider.WithTheme {
|
themeProvider.WithTheme {
|
||||||
DrawerView()
|
DrawerView(
|
||||||
|
openAccount = openAccount,
|
||||||
|
openFolder = openFolder,
|
||||||
|
closeDrawer = { close() },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package app.k9mail.feature.navigation.drawer
|
||||||
|
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DrawerConfig
|
||||||
|
|
||||||
|
interface NavigationDrawerExternalContract {
|
||||||
|
|
||||||
|
fun interface DrawerConfigLoader {
|
||||||
|
fun loadDrawerConfig(): DrawerConfig
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ package app.k9mail.feature.navigation.drawer
|
|||||||
import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
|
import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
|
||||||
import app.k9mail.feature.navigation.drawer.domain.usecase.GetDisplayAccounts
|
import app.k9mail.feature.navigation.drawer.domain.usecase.GetDisplayAccounts
|
||||||
import app.k9mail.feature.navigation.drawer.domain.usecase.GetDisplayFoldersForAccount
|
import app.k9mail.feature.navigation.drawer.domain.usecase.GetDisplayFoldersForAccount
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.usecase.GetDrawerConfig
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.usecase.SyncMail
|
||||||
import app.k9mail.feature.navigation.drawer.legacy.AccountsViewModel
|
import app.k9mail.feature.navigation.drawer.legacy.AccountsViewModel
|
||||||
import app.k9mail.feature.navigation.drawer.legacy.FoldersViewModel
|
import app.k9mail.feature.navigation.drawer.legacy.FoldersViewModel
|
||||||
import app.k9mail.feature.navigation.drawer.ui.DrawerViewModel
|
import app.k9mail.feature.navigation.drawer.ui.DrawerViewModel
|
||||||
@ -14,6 +16,12 @@ import org.koin.dsl.module
|
|||||||
|
|
||||||
val navigationDrawerModule: Module = module {
|
val navigationDrawerModule: Module = module {
|
||||||
|
|
||||||
|
single<UseCase.GetDrawerConfig> {
|
||||||
|
GetDrawerConfig(
|
||||||
|
configProver = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
single<UseCase.GetDisplayAccounts> {
|
single<UseCase.GetDisplayAccounts> {
|
||||||
GetDisplayAccounts(
|
GetDisplayAccounts(
|
||||||
accountManager = get(),
|
accountManager = get(),
|
||||||
@ -28,6 +36,12 @@ val navigationDrawerModule: Module = module {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
single<UseCase.SyncMail> {
|
||||||
|
SyncMail(
|
||||||
|
messagingController = get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
viewModel {
|
viewModel {
|
||||||
AccountsViewModel(
|
AccountsViewModel(
|
||||||
getDisplayAccounts = get(),
|
getDisplayAccounts = get(),
|
||||||
@ -48,8 +62,10 @@ val navigationDrawerModule: Module = module {
|
|||||||
|
|
||||||
viewModel {
|
viewModel {
|
||||||
DrawerViewModel(
|
DrawerViewModel(
|
||||||
|
getDrawerConfig = get(),
|
||||||
getDisplayAccounts = get(),
|
getDisplayAccounts = get(),
|
||||||
getDisplayFoldersForAccount = get(),
|
getDisplayFoldersForAccount = get(),
|
||||||
|
syncMail = get(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,33 @@
|
|||||||
package app.k9mail.feature.navigation.drawer.domain
|
package app.k9mail.feature.navigation.drawer.domain
|
||||||
|
|
||||||
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
||||||
import app.k9mail.legacy.ui.folder.DisplayFolder
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DrawerConfig
|
||||||
|
import app.k9mail.legacy.account.Account
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface DomainContract {
|
interface DomainContract {
|
||||||
|
|
||||||
interface UseCase {
|
interface UseCase {
|
||||||
|
fun interface GetDrawerConfig {
|
||||||
|
operator fun invoke(): Flow<DrawerConfig>
|
||||||
|
}
|
||||||
|
|
||||||
fun interface GetDisplayAccounts {
|
fun interface GetDisplayAccounts {
|
||||||
operator fun invoke(): Flow<List<DisplayAccount>>
|
operator fun invoke(): Flow<List<DisplayAccount>>
|
||||||
}
|
}
|
||||||
|
|
||||||
fun interface GetDisplayFoldersForAccount {
|
fun interface GetDisplayFoldersForAccount {
|
||||||
operator fun invoke(accountUuid: String): Flow<List<DisplayFolder>>
|
operator fun invoke(accountUuid: String): Flow<List<DisplayAccountFolder>>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize mail for the given account.
|
||||||
|
*
|
||||||
|
* Account can be null to synchronize unified inbox or account list.
|
||||||
|
*/
|
||||||
|
fun interface SyncMail {
|
||||||
|
operator fun invoke(account: Account?): Flow<Result<Unit>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
package app.k9mail.feature.navigation.drawer.domain.entity
|
||||||
|
|
||||||
|
import app.k9mail.core.mail.folder.api.Folder
|
||||||
|
|
||||||
|
data class DisplayAccountFolder(
|
||||||
|
val accountUuid: String,
|
||||||
|
val folder: Folder,
|
||||||
|
val isInTopGroup: Boolean,
|
||||||
|
val unreadMessageCount: Int,
|
||||||
|
val starredMessageCount: Int,
|
||||||
|
)
|
@ -0,0 +1,6 @@
|
|||||||
|
package app.k9mail.feature.navigation.drawer.domain.entity
|
||||||
|
|
||||||
|
data class DrawerConfig(
|
||||||
|
val showUnifiedInbox: Boolean,
|
||||||
|
val showStarredCount: Boolean,
|
||||||
|
)
|
@ -1,14 +1,25 @@
|
|||||||
package app.k9mail.feature.navigation.drawer.domain.usecase
|
package app.k9mail.feature.navigation.drawer.domain.usecase
|
||||||
|
|
||||||
import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
|
import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
|
||||||
import app.k9mail.legacy.ui.folder.DisplayFolder
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
|
||||||
import app.k9mail.legacy.ui.folder.DisplayFolderRepository
|
import app.k9mail.legacy.ui.folder.DisplayFolderRepository
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
class GetDisplayFoldersForAccount(
|
class GetDisplayFoldersForAccount(
|
||||||
private val repository: DisplayFolderRepository,
|
private val repository: DisplayFolderRepository,
|
||||||
) : UseCase.GetDisplayFoldersForAccount {
|
) : UseCase.GetDisplayFoldersForAccount {
|
||||||
override fun invoke(accountUuid: String): Flow<List<DisplayFolder>> {
|
override fun invoke(accountUuid: String): Flow<List<DisplayAccountFolder>> {
|
||||||
return repository.getDisplayFoldersFlow(accountUuid)
|
return repository.getDisplayFoldersFlow(accountUuid).map { displayFolders ->
|
||||||
|
displayFolders.map { displayFolder ->
|
||||||
|
DisplayAccountFolder(
|
||||||
|
accountUuid = accountUuid,
|
||||||
|
folder = displayFolder.folder,
|
||||||
|
isInTopGroup = displayFolder.isInTopGroup,
|
||||||
|
unreadMessageCount = displayFolder.unreadMessageCount,
|
||||||
|
starredMessageCount = displayFolder.starredMessageCount,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package app.k9mail.feature.navigation.drawer.domain.usecase
|
||||||
|
|
||||||
|
import app.k9mail.feature.navigation.drawer.NavigationDrawerExternalContract.DrawerConfigLoader
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DrawerConfig
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
|
class GetDrawerConfig(
|
||||||
|
private val configProver: DrawerConfigLoader,
|
||||||
|
) : UseCase.GetDrawerConfig {
|
||||||
|
override operator fun invoke(): Flow<DrawerConfig> {
|
||||||
|
// TODO This needs to be updated when the config changes
|
||||||
|
return flow {
|
||||||
|
emit(configProver.loadDrawerConfig())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package app.k9mail.feature.navigation.drawer.domain.usecase
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
|
||||||
|
import app.k9mail.legacy.account.Account
|
||||||
|
import app.k9mail.legacy.message.controller.MessagingControllerMailChecker
|
||||||
|
import app.k9mail.legacy.message.controller.SimpleMessagingListener
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
|
class SyncMail(
|
||||||
|
private val messagingController: MessagingControllerMailChecker,
|
||||||
|
private val coroutineContext: CoroutineContext = Dispatchers.IO,
|
||||||
|
) : UseCase.SyncMail {
|
||||||
|
override fun invoke(account: Account?): Flow<Result<Unit>> = callbackFlow {
|
||||||
|
val listener = object : SimpleMessagingListener() {
|
||||||
|
override fun checkMailFinished(context: Context?, account: Account?) {
|
||||||
|
trySend(Result.success(Unit))
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messagingController.checkMail(
|
||||||
|
account = account,
|
||||||
|
ignoreLastCheckedTime = true,
|
||||||
|
useManualWakeLock = true,
|
||||||
|
notify = true,
|
||||||
|
listener = listener,
|
||||||
|
)
|
||||||
|
|
||||||
|
awaitClose()
|
||||||
|
}.flowOn(coroutineContext)
|
||||||
|
}
|
@ -34,7 +34,7 @@ fun DrawerContent(
|
|||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.default),
|
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.default),
|
||||||
) {
|
) {
|
||||||
state.currentAccount?.let {
|
state.selectedAccount?.let {
|
||||||
AccountView(
|
AccountView(
|
||||||
displayName = it.account.displayName,
|
displayName = it.account.displayName,
|
||||||
emailAddress = it.account.email,
|
emailAddress = it.account.email,
|
||||||
@ -46,9 +46,11 @@ fun DrawerContent(
|
|||||||
}
|
}
|
||||||
FolderList(
|
FolderList(
|
||||||
folders = state.folders,
|
folders = state.folders,
|
||||||
selectedFolder = state.folders.firstOrNull(), // TODO Use selected folder from state
|
selectedFolder = state.selectedFolder,
|
||||||
onFolderClick = { },
|
onFolderClick = { folder ->
|
||||||
showStarredCount = state.showStarredCount,
|
onEvent(Event.OnFolderClick(folder))
|
||||||
|
},
|
||||||
|
showStarredCount = state.config.showStarredCount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@ package app.k9mail.feature.navigation.drawer.ui
|
|||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
|
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
|
||||||
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
||||||
import app.k9mail.legacy.ui.folder.DisplayFolder
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DrawerConfig
|
||||||
|
import app.k9mail.legacy.account.Account
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
@ -13,18 +15,27 @@ interface DrawerContract {
|
|||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class State(
|
data class State(
|
||||||
val currentAccount: DisplayAccount? = null,
|
val config: DrawerConfig = DrawerConfig(
|
||||||
|
showUnifiedInbox = false,
|
||||||
|
showStarredCount = false,
|
||||||
|
),
|
||||||
val accounts: ImmutableList<DisplayAccount> = persistentListOf(),
|
val accounts: ImmutableList<DisplayAccount> = persistentListOf(),
|
||||||
val folders: ImmutableList<DisplayFolder> = persistentListOf(),
|
val selectedAccount: DisplayAccount? = null,
|
||||||
val showStarredCount: Boolean = false,
|
val folders: ImmutableList<DisplayAccountFolder> = persistentListOf(),
|
||||||
|
val selectedFolder: DisplayAccountFolder? = null,
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed interface Event {
|
sealed interface Event {
|
||||||
data object OnRefresh : Event
|
|
||||||
data class OnAccountClick(val account: DisplayAccount) : Event
|
data class OnAccountClick(val account: DisplayAccount) : Event
|
||||||
data class OnAccountViewClick(val account: DisplayAccount) : Event
|
data class OnAccountViewClick(val account: DisplayAccount) : Event
|
||||||
|
data class OnFolderClick(val folder: DisplayAccountFolder) : Event
|
||||||
|
data object OnRefresh : Event
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface Effect
|
sealed interface Effect {
|
||||||
|
data class OpenAccount(val account: Account) : Effect
|
||||||
|
data class OpenFolder(val folderId: Long) : Effect
|
||||||
|
data object CloseDrawer : Effect
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,26 @@ package app.k9mail.feature.navigation.drawer.ui
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import app.k9mail.core.ui.compose.common.mvi.observe
|
import app.k9mail.core.ui.compose.common.mvi.observe
|
||||||
import app.k9mail.core.ui.compose.designsystem.molecule.PullToRefreshBox
|
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.Event
|
||||||
import app.k9mail.feature.navigation.drawer.ui.DrawerContract.ViewModel
|
import app.k9mail.feature.navigation.drawer.ui.DrawerContract.ViewModel
|
||||||
|
import app.k9mail.legacy.account.Account
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DrawerView(
|
fun DrawerView(
|
||||||
|
openAccount: (account: Account) -> Unit,
|
||||||
|
openFolder: (folderId: Long) -> Unit,
|
||||||
|
closeDrawer: () -> Unit,
|
||||||
viewModel: ViewModel = koinViewModel<DrawerViewModel>(),
|
viewModel: ViewModel = koinViewModel<DrawerViewModel>(),
|
||||||
) {
|
) {
|
||||||
val (state, dispatch) = viewModel.observe { }
|
val (state, dispatch) = viewModel.observe { effect ->
|
||||||
|
when (effect) {
|
||||||
|
is Effect.OpenAccount -> openAccount(effect.account)
|
||||||
|
is Effect.OpenFolder -> openFolder(effect.folderId)
|
||||||
|
Effect.CloseDrawer -> closeDrawer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PullToRefreshBox(
|
PullToRefreshBox(
|
||||||
isRefreshing = state.value.isLoading,
|
isRefreshing = state.value.isLoading,
|
||||||
|
@ -4,6 +4,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||||
import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
|
import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
|
||||||
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
|
||||||
import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Effect
|
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.Event
|
||||||
import app.k9mail.feature.navigation.drawer.ui.DrawerContract.State
|
import app.k9mail.feature.navigation.drawer.ui.DrawerContract.State
|
||||||
@ -12,6 +13,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
@ -20,8 +22,10 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
class DrawerViewModel(
|
class DrawerViewModel(
|
||||||
|
private val getDrawerConfig: UseCase.GetDrawerConfig,
|
||||||
private val getDisplayAccounts: UseCase.GetDisplayAccounts,
|
private val getDisplayAccounts: UseCase.GetDisplayAccounts,
|
||||||
private val getDisplayFoldersForAccount: UseCase.GetDisplayFoldersForAccount,
|
private val getDisplayFoldersForAccount: UseCase.GetDisplayFoldersForAccount,
|
||||||
|
private val syncMail: UseCase.SyncMail,
|
||||||
initialState: State = State(),
|
initialState: State = State(),
|
||||||
) : BaseViewModel<State, Event, Effect>(
|
) : BaseViewModel<State, Event, Effect>(
|
||||||
initialState = initialState,
|
initialState = initialState,
|
||||||
@ -29,6 +33,14 @@ class DrawerViewModel(
|
|||||||
ViewModel {
|
ViewModel {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
getDrawerConfig().collectLatest { config ->
|
||||||
|
updateState {
|
||||||
|
it.copy(config = config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
loadAccounts()
|
loadAccounts()
|
||||||
}
|
}
|
||||||
@ -45,38 +57,45 @@ class DrawerViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateAccounts(accounts: List<DisplayAccount>) {
|
private fun updateAccounts(accounts: List<DisplayAccount>) {
|
||||||
val currentAccountUuid = state.value.currentAccount?.account?.uuid
|
val selectedAccount = accounts.find { it.account.uuid == state.value.selectedAccount?.account?.uuid }
|
||||||
val isCurrentAccountAvailable = accounts.any { currentAccountUuid == it.account.uuid }
|
?: accounts.firstOrNull()
|
||||||
|
|
||||||
updateState {
|
updateState {
|
||||||
if (isCurrentAccountAvailable) {
|
it.copy(
|
||||||
it.copy(accounts = accounts.toImmutableList())
|
accounts = accounts.toImmutableList(),
|
||||||
} else {
|
selectedAccount = selectedAccount,
|
||||||
it.copy(
|
)
|
||||||
accounts = accounts.toImmutableList(),
|
|
||||||
currentAccount = accounts.firstOrNull(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private suspend fun loadFolders() {
|
private suspend fun loadFolders() {
|
||||||
state.mapNotNull { it.currentAccount?.account?.uuid }
|
state.mapNotNull { it.selectedAccount?.account?.uuid }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.flatMapLatest { accountUuid ->
|
.flatMapLatest { accountUuid ->
|
||||||
getDisplayFoldersForAccount(accountUuid)
|
getDisplayFoldersForAccount(accountUuid)
|
||||||
}.collectLatest { folders ->
|
}.collectLatest { folders ->
|
||||||
updateState {
|
updateFolders(folders)
|
||||||
it.copy(folders = folders.toImmutableList())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateFolders(displayFolders: List<DisplayAccountFolder>) {
|
||||||
|
val selectedFolder = displayFolders.find { it == state.value.selectedFolder }
|
||||||
|
?: displayFolders.firstOrNull()
|
||||||
|
|
||||||
|
updateState {
|
||||||
|
it.copy(
|
||||||
|
folders = displayFolders.toImmutableList(),
|
||||||
|
selectedFolder = selectedFolder,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun event(event: Event) {
|
override fun event(event: Event) {
|
||||||
when (event) {
|
when (event) {
|
||||||
Event.OnRefresh -> refresh()
|
Event.OnRefresh -> refresh()
|
||||||
is Event.OnAccountClick -> selectAccount(event.account)
|
is Event.OnAccountClick -> selectAccount(event.account)
|
||||||
|
is Event.OnFolderClick -> selectFolder(event.folder)
|
||||||
is Event.OnAccountViewClick -> {
|
is Event.OnAccountViewClick -> {
|
||||||
selectAccount(
|
selectAccount(
|
||||||
state.value.accounts.nextOrFirst(event.account)!!,
|
state.value.accounts.nextOrFirst(event.account)!!,
|
||||||
@ -89,10 +108,12 @@ class DrawerViewModel(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
currentAccount = account,
|
selectedAccount = account,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitEffect(Effect.OpenAccount(account.account))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ImmutableList<DisplayAccount>.nextOrFirst(account: DisplayAccount): DisplayAccount? {
|
private fun ImmutableList<DisplayAccount>.nextOrFirst(account: DisplayAccount): DisplayAccount? {
|
||||||
@ -106,17 +127,25 @@ class DrawerViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refresh() {
|
private fun selectFolder(folder: DisplayAccountFolder) {
|
||||||
if (state.value.isLoading) {
|
updateState {
|
||||||
return
|
it.copy(selectedFolder = folder)
|
||||||
}
|
}
|
||||||
|
emitEffect(Effect.OpenFolder(folder.folder.id))
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
delay(DRAWER_CLOSE_DELAY)
|
||||||
|
emitEffect(Effect.CloseDrawer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refresh() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(isLoading = true)
|
it.copy(isLoading = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with actual data loading
|
syncMail(state.value.selectedAccount?.account).collect()
|
||||||
delay(500)
|
|
||||||
|
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(isLoading = false)
|
it.copy(isLoading = false)
|
||||||
@ -124,3 +153,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
|
||||||
|
@ -5,14 +5,14 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import app.k9mail.legacy.ui.folder.DisplayFolder
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FolderList(
|
fun FolderList(
|
||||||
folders: ImmutableList<DisplayFolder>,
|
folders: ImmutableList<DisplayAccountFolder>,
|
||||||
selectedFolder: DisplayFolder?,
|
selectedFolder: DisplayAccountFolder?,
|
||||||
onFolderClick: (DisplayFolder) -> Unit,
|
onFolderClick: (DisplayAccountFolder) -> Unit,
|
||||||
showStarredCount: Boolean,
|
showStarredCount: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
@ -7,14 +7,14 @@ import app.k9mail.core.mail.folder.api.FolderType
|
|||||||
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
|
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
|
||||||
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
|
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
|
||||||
import app.k9mail.core.ui.compose.designsystem.organism.drawer.NavigationDrawerItem
|
import app.k9mail.core.ui.compose.designsystem.organism.drawer.NavigationDrawerItem
|
||||||
import app.k9mail.legacy.ui.folder.DisplayFolder
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FolderListItem(
|
fun FolderListItem(
|
||||||
displayFolder: DisplayFolder,
|
displayFolder: DisplayAccountFolder,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
showStarredCount: Boolean,
|
showStarredCount: Boolean,
|
||||||
onClick: (DisplayFolder) -> Unit,
|
onClick: (DisplayAccountFolder) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
NavigationDrawerItem(
|
NavigationDrawerItem(
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package app.k9mail.feature.navigation.drawer.domain.usecase
|
||||||
|
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DrawerConfig
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
|
class GetDrawerConfigTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should get drawer config`() = runTest {
|
||||||
|
val drawerConfig = DrawerConfig(
|
||||||
|
showUnifiedInbox = true,
|
||||||
|
showStarredCount = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
val testSubject = GetDrawerConfig(
|
||||||
|
configProver = { drawerConfig },
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = testSubject().first()
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(
|
||||||
|
DrawerConfig(
|
||||||
|
showUnifiedInbox = true,
|
||||||
|
showStarredCount = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package app.k9mail.feature.navigation.drawer.domain.usecase
|
||||||
|
|
||||||
|
import app.k9mail.legacy.account.Account
|
||||||
|
import app.k9mail.legacy.message.controller.MessagingControllerMailChecker
|
||||||
|
import app.k9mail.legacy.message.controller.MessagingListener
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class SyncMailTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should sync mail`() = runTest {
|
||||||
|
val listenerExecutor: (MessagingListener?) -> Unit = { listener ->
|
||||||
|
listener?.checkMailFinished(null, null)
|
||||||
|
}
|
||||||
|
val testSubject = SyncMail(
|
||||||
|
messagingController = FakeMessagingControllerMailChecker(
|
||||||
|
listenerExecutor = listenerExecutor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = testSubject(null).first()
|
||||||
|
|
||||||
|
assertThat(result.isSuccess).isEqualTo(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeMessagingControllerMailChecker(
|
||||||
|
private val listenerExecutor: (MessagingListener?) -> Unit = {},
|
||||||
|
) : MessagingControllerMailChecker {
|
||||||
|
override fun checkMail(
|
||||||
|
account: Account?,
|
||||||
|
ignoreLastCheckedTime: Boolean,
|
||||||
|
useManualWakeLock: Boolean,
|
||||||
|
notify: Boolean,
|
||||||
|
listener: MessagingListener?,
|
||||||
|
) {
|
||||||
|
listenerExecutor(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package app.k9mail.feature.navigation.drawer.ui
|
||||||
|
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DrawerConfig
|
||||||
|
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(
|
||||||
|
config = DrawerConfig(
|
||||||
|
showUnifiedInbox = false,
|
||||||
|
showStarredCount = false,
|
||||||
|
),
|
||||||
|
accounts = persistentListOf(),
|
||||||
|
selectedAccount = null,
|
||||||
|
folders = persistentListOf(),
|
||||||
|
selectedFolder = null,
|
||||||
|
isLoading = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -5,12 +5,53 @@ import androidx.compose.ui.test.printToString
|
|||||||
import app.k9mail.core.ui.compose.testing.ComposeTest
|
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||||
import app.k9mail.core.ui.compose.testing.onNodeWithTag
|
import app.k9mail.core.ui.compose.testing.onNodeWithTag
|
||||||
import app.k9mail.core.ui.compose.testing.setContentWithTheme
|
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 app.k9mail.feature.navigation.drawer.ui.DrawerContract.State
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
||||||
class DrawerViewKtTest : ComposeTest() {
|
class DrawerViewKtTest : ComposeTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should delegate effects`() = runTest {
|
||||||
|
val initialState = State()
|
||||||
|
val viewModel = FakeDrawerViewModel(initialState)
|
||||||
|
var openAccountCounter = 0
|
||||||
|
var openFolderCounter = 0
|
||||||
|
var closeDrawerCounter = 0
|
||||||
|
|
||||||
|
setContentWithTheme {
|
||||||
|
DrawerView(
|
||||||
|
openAccount = { openAccountCounter++ },
|
||||||
|
openFolder = { openFolderCounter++ },
|
||||||
|
closeDrawer = { closeDrawerCounter++ },
|
||||||
|
viewModel = viewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(openAccountCounter).isEqualTo(0)
|
||||||
|
assertThat(openFolderCounter).isEqualTo(0)
|
||||||
|
assertThat(closeDrawerCounter).isEqualTo(0)
|
||||||
|
|
||||||
|
viewModel.effect(Effect.OpenAccount(FakeData.ACCOUNT))
|
||||||
|
|
||||||
|
assertThat(openAccountCounter).isEqualTo(1)
|
||||||
|
|
||||||
|
viewModel.effect(Effect.OpenFolder(1))
|
||||||
|
|
||||||
|
assertThat(openAccountCounter).isEqualTo(1)
|
||||||
|
assertThat(openFolderCounter).isEqualTo(1)
|
||||||
|
assertThat(closeDrawerCounter).isEqualTo(0)
|
||||||
|
|
||||||
|
viewModel.effect(Effect.CloseDrawer)
|
||||||
|
|
||||||
|
assertThat(openAccountCounter).isEqualTo(1)
|
||||||
|
assertThat(openFolderCounter).isEqualTo(1)
|
||||||
|
assertThat(closeDrawerCounter).isEqualTo(1)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `pull refresh should listen to view model state`() = runTest {
|
fun `pull refresh should listen to view model state`() = runTest {
|
||||||
val initialState = State(
|
val initialState = State(
|
||||||
@ -20,6 +61,9 @@ class DrawerViewKtTest : ComposeTest() {
|
|||||||
|
|
||||||
setContentWithTheme {
|
setContentWithTheme {
|
||||||
DrawerView(
|
DrawerView(
|
||||||
|
openAccount = {},
|
||||||
|
openFolder = {},
|
||||||
|
closeDrawer = {},
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,23 @@ package app.k9mail.feature.navigation.drawer.ui
|
|||||||
import app.k9mail.core.mail.folder.api.Folder
|
import app.k9mail.core.mail.folder.api.Folder
|
||||||
import app.k9mail.core.mail.folder.api.FolderType
|
import app.k9mail.core.mail.folder.api.FolderType
|
||||||
import app.k9mail.core.ui.compose.testing.MainDispatcherRule
|
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.eventStateTest
|
||||||
|
import app.k9mail.core.ui.compose.testing.mvi.turbinesWithInitialStateCheck
|
||||||
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DrawerConfig
|
||||||
|
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.Event
|
||||||
import app.k9mail.feature.navigation.drawer.ui.DrawerContract.State
|
import app.k9mail.feature.navigation.drawer.ui.DrawerContract.State
|
||||||
import app.k9mail.legacy.account.Account
|
import app.k9mail.legacy.account.Account
|
||||||
import app.k9mail.legacy.account.Identity
|
import app.k9mail.legacy.account.Identity
|
||||||
import app.k9mail.legacy.ui.folder.DisplayFolder
|
|
||||||
import assertk.assertThat
|
import assertk.assertThat
|
||||||
import assertk.assertions.isEqualTo
|
import assertk.assertions.isEqualTo
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
@ -27,9 +33,35 @@ class DrawerViewModelTest {
|
|||||||
@get:Rule
|
@get:Rule
|
||||||
val mainDispatcherRule = MainDispatcherRule()
|
val mainDispatcherRule = MainDispatcherRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should collect drawer config`() = runTest {
|
||||||
|
val drawerConfig = createDrawerConfig()
|
||||||
|
val getDrawerConfigFlow = MutableStateFlow(drawerConfig)
|
||||||
|
val testSubject = createTestSubject(
|
||||||
|
drawerConfigFlow = getDrawerConfigFlow,
|
||||||
|
)
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assertThat(testSubject.state.value.config).isEqualTo(drawerConfig)
|
||||||
|
|
||||||
|
val newDrawerConfig = createDrawerConfig(showUnifiedInbox = true)
|
||||||
|
|
||||||
|
getDrawerConfigFlow.emit(newDrawerConfig)
|
||||||
|
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assertThat(testSubject.state.value.config).isEqualTo(newDrawerConfig)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should change loading state when OnRefresh event is received`() = runTest {
|
fun `should change loading state when OnRefresh event is received`() = runTest {
|
||||||
val testSubject = createTestSubject()
|
val testSubject = createTestSubject(
|
||||||
|
syncMailFlow = flow {
|
||||||
|
delay(25)
|
||||||
|
emit(Result.success(Unit))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
eventStateTest(
|
eventStateTest(
|
||||||
viewModel = testSubject,
|
viewModel = testSubject,
|
||||||
@ -45,8 +77,8 @@ class DrawerViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should collect display accounts when created and select first as current`() = runTest {
|
fun `should collect display accounts when created and select first as selected`() = runTest {
|
||||||
val displayAccounts = createDisplayAccountList(3)
|
val displayAccounts = createDisplayAccountList(2)
|
||||||
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
||||||
val testSubject = createTestSubject(
|
val testSubject = createTestSubject(
|
||||||
displayAccountsFlow = getDisplayAccountsFlow,
|
displayAccountsFlow = getDisplayAccountsFlow,
|
||||||
@ -56,11 +88,11 @@ class DrawerViewModelTest {
|
|||||||
|
|
||||||
assertThat(testSubject.state.value.accounts.size).isEqualTo(displayAccounts.size)
|
assertThat(testSubject.state.value.accounts.size).isEqualTo(displayAccounts.size)
|
||||||
assertThat(testSubject.state.value.accounts).isEqualTo(displayAccounts)
|
assertThat(testSubject.state.value.accounts).isEqualTo(displayAccounts)
|
||||||
assertThat(testSubject.state.value.currentAccount).isEqualTo(displayAccounts.first())
|
assertThat(testSubject.state.value.selectedAccount).isEqualTo(displayAccounts.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should reselect current account when old not present anymore`() = runTest {
|
fun `should reselect selected account when old not present anymore`() = runTest {
|
||||||
val displayAccounts = createDisplayAccountList(3)
|
val displayAccounts = createDisplayAccountList(3)
|
||||||
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
||||||
val testSubject = createTestSubject(
|
val testSubject = createTestSubject(
|
||||||
@ -76,11 +108,11 @@ class DrawerViewModelTest {
|
|||||||
|
|
||||||
assertThat(testSubject.state.value.accounts.size).isEqualTo(newDisplayAccounts.size)
|
assertThat(testSubject.state.value.accounts.size).isEqualTo(newDisplayAccounts.size)
|
||||||
assertThat(testSubject.state.value.accounts).isEqualTo(newDisplayAccounts)
|
assertThat(testSubject.state.value.accounts).isEqualTo(newDisplayAccounts)
|
||||||
assertThat(testSubject.state.value.currentAccount).isEqualTo(newDisplayAccounts.first())
|
assertThat(testSubject.state.value.selectedAccount).isEqualTo(newDisplayAccounts.first())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should set current account to null when no accounts are present`() = runTest {
|
fun `should set selected account to null when no accounts are present`() = runTest {
|
||||||
val getDisplayAccountsFlow = MutableStateFlow(emptyList<DisplayAccount>())
|
val getDisplayAccountsFlow = MutableStateFlow(emptyList<DisplayAccount>())
|
||||||
val testSubject = createTestSubject(
|
val testSubject = createTestSubject(
|
||||||
displayAccountsFlow = getDisplayAccountsFlow,
|
displayAccountsFlow = getDisplayAccountsFlow,
|
||||||
@ -89,16 +121,23 @@ class DrawerViewModelTest {
|
|||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
assertThat(testSubject.state.value.accounts.size).isEqualTo(0)
|
assertThat(testSubject.state.value.accounts.size).isEqualTo(0)
|
||||||
assertThat(testSubject.state.value.currentAccount).isEqualTo(null)
|
assertThat(testSubject.state.value.selectedAccount).isEqualTo(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should set current account when OnAccountClick event is received`() = runTest {
|
fun `should set selected account when OnAccountClick event is received`() = runTest {
|
||||||
val displayAccounts = createDisplayAccountList(3)
|
val displayAccounts = createDisplayAccountList(3)
|
||||||
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
||||||
val testSubject = createTestSubject(
|
val testSubject = createTestSubject(
|
||||||
displayAccountsFlow = getDisplayAccountsFlow,
|
displayAccountsFlow = getDisplayAccountsFlow,
|
||||||
)
|
)
|
||||||
|
val turbines = turbinesWithInitialStateCheck(
|
||||||
|
testSubject,
|
||||||
|
State(
|
||||||
|
accounts = displayAccounts.toImmutableList(),
|
||||||
|
selectedAccount = displayAccounts.first(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
@ -106,11 +145,15 @@ class DrawerViewModelTest {
|
|||||||
|
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
assertThat(testSubject.state.value.currentAccount).isEqualTo(displayAccounts[1])
|
assertThat(turbines.awaitStateItem().selectedAccount).isEqualTo(displayAccounts[1])
|
||||||
|
|
||||||
|
turbines.assertThatAndEffectTurbineConsumed {
|
||||||
|
isEqualTo(Effect.OpenAccount(displayAccounts[1].account))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should collect display folders for current account`() = runTest {
|
fun `should collect display folders for selected account`() = runTest {
|
||||||
val displayAccounts = createDisplayAccountList(3)
|
val displayAccounts = createDisplayAccountList(3)
|
||||||
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
||||||
val displayFoldersMap = mapOf(
|
val displayFoldersMap = mapOf(
|
||||||
@ -129,7 +172,7 @@ class DrawerViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should collect display folders when current account is changed`() = runTest {
|
fun `should collect display folders when selected account is changed`() = runTest {
|
||||||
val displayAccounts = createDisplayAccountList(3)
|
val displayAccounts = createDisplayAccountList(3)
|
||||||
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
val getDisplayAccountsFlow = MutableStateFlow(displayAccounts)
|
||||||
val displayFoldersMap = mapOf(
|
val displayFoldersMap = mapOf(
|
||||||
@ -153,15 +196,62 @@ class DrawerViewModelTest {
|
|||||||
assertThat(testSubject.state.value.folders).isEqualTo(displayFolders)
|
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(),
|
||||||
|
selectedAccount = displayAccounts[0],
|
||||||
|
folders = displayFoldersMap[displayAccounts[0].account.uuid]!!.toImmutableList(),
|
||||||
|
selectedFolder = displayFoldersMap[displayAccounts[0].account.uuid]!![0],
|
||||||
|
)
|
||||||
|
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(
|
private fun createTestSubject(
|
||||||
|
drawerConfigFlow: Flow<DrawerConfig> = flow { emit(createDrawerConfig()) },
|
||||||
displayAccountsFlow: Flow<List<DisplayAccount>> = flow { emit(emptyList()) },
|
displayAccountsFlow: Flow<List<DisplayAccount>> = flow { emit(emptyList()) },
|
||||||
displayFoldersMap: Map<String, List<DisplayFolder>> = emptyMap(),
|
displayFoldersMap: Map<String, List<DisplayAccountFolder>> = emptyMap(),
|
||||||
|
syncMailFlow: Flow<Result<Unit>> = flow { emit(Result.success(Unit)) },
|
||||||
): DrawerViewModel {
|
): DrawerViewModel {
|
||||||
return DrawerViewModel(
|
return DrawerViewModel(
|
||||||
|
getDrawerConfig = { drawerConfigFlow },
|
||||||
getDisplayAccounts = { displayAccountsFlow },
|
getDisplayAccounts = { displayAccountsFlow },
|
||||||
getDisplayFoldersForAccount = { accountUuid ->
|
getDisplayFoldersForAccount = { accountUuid ->
|
||||||
flow { emit(displayFoldersMap[accountUuid] ?: emptyList()) }
|
flow { emit(displayFoldersMap[accountUuid] ?: emptyList()) }
|
||||||
},
|
},
|
||||||
|
syncMail = { syncMailFlow },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDrawerConfig(
|
||||||
|
showUnifiedInbox: Boolean = false,
|
||||||
|
showStarredCount: Boolean = false,
|
||||||
|
): DrawerConfig {
|
||||||
|
return DrawerConfig(
|
||||||
|
showUnifiedInbox = showUnifiedInbox,
|
||||||
|
showStarredCount = showStarredCount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,12 +294,13 @@ class DrawerViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createDisplayFolder(
|
private fun createDisplayFolder(
|
||||||
|
accountUuid: String = "uuid",
|
||||||
id: Long = 1234,
|
id: Long = 1234,
|
||||||
name: String = "name",
|
name: String = "name",
|
||||||
type: FolderType = FolderType.REGULAR,
|
type: FolderType = FolderType.REGULAR,
|
||||||
unreadCount: Int = 0,
|
unreadCount: Int = 0,
|
||||||
starredCount: Int = 0,
|
starredCount: Int = 0,
|
||||||
): DisplayFolder {
|
): DisplayAccountFolder {
|
||||||
val folder = Folder(
|
val folder = Folder(
|
||||||
id = id,
|
id = id,
|
||||||
name = name,
|
name = name,
|
||||||
@ -217,7 +308,8 @@ class DrawerViewModelTest {
|
|||||||
isLocalOnly = false,
|
isLocalOnly = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
return DisplayFolder(
|
return DisplayAccountFolder(
|
||||||
|
accountUuid = accountUuid,
|
||||||
folder = folder,
|
folder = folder,
|
||||||
isInTopGroup = false,
|
isInTopGroup = false,
|
||||||
unreadMessageCount = unreadCount,
|
unreadMessageCount = unreadCount,
|
||||||
@ -225,7 +317,7 @@ class DrawerViewModelTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createDisplayFolderList(count: Int): List<DisplayFolder> {
|
private fun createDisplayFolderList(count: Int): List<DisplayAccountFolder> {
|
||||||
return List(count) { index ->
|
return List(count) { index ->
|
||||||
createDisplayFolder(
|
createDisplayFolder(
|
||||||
id = index.toLong() + 100,
|
id = index.toLong() + 100,
|
||||||
|
@ -15,6 +15,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(projects.feature.account.setup)
|
implementation(projects.feature.account.setup)
|
||||||
implementation(projects.feature.account.edit)
|
implementation(projects.feature.account.edit)
|
||||||
|
implementation(projects.feature.navigation.drawer)
|
||||||
implementation(projects.feature.settings.import)
|
implementation(projects.feature.settings.import)
|
||||||
|
|
||||||
implementation(projects.feature.widget.unread)
|
implementation(projects.feature.widget.unread)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.fsck.k9.feature
|
package com.fsck.k9.feature
|
||||||
|
|
||||||
import app.k9mail.feature.launcher.FeatureLauncherExternalContract
|
import app.k9mail.feature.launcher.FeatureLauncherExternalContract
|
||||||
|
import app.k9mail.feature.navigation.drawer.NavigationDrawerExternalContract
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
@ -10,4 +11,8 @@ val featureModule = module {
|
|||||||
context = androidContext(),
|
context = androidContext(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
single<NavigationDrawerExternalContract.DrawerConfigLoader> {
|
||||||
|
NavigationDrawerConfigLoader()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.fsck.k9.feature
|
||||||
|
|
||||||
|
import app.k9mail.feature.navigation.drawer.NavigationDrawerExternalContract.DrawerConfigLoader
|
||||||
|
import app.k9mail.feature.navigation.drawer.domain.entity.DrawerConfig
|
||||||
|
import com.fsck.k9.K9
|
||||||
|
|
||||||
|
class NavigationDrawerConfigLoader : DrawerConfigLoader {
|
||||||
|
override fun loadDrawerConfig(): DrawerConfig {
|
||||||
|
return DrawerConfig(
|
||||||
|
showUnifiedInbox = K9.isShowUnifiedInbox,
|
||||||
|
showStarredCount = K9.isShowStarredCount,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -608,6 +608,9 @@ open class MessageList :
|
|||||||
private fun initializeFolderDrawer() {
|
private fun initializeFolderDrawer() {
|
||||||
navigationDrawer = FolderDrawer(
|
navigationDrawer = FolderDrawer(
|
||||||
parent = this,
|
parent = this,
|
||||||
|
openAccount = { account -> openRealAccount(account) },
|
||||||
|
openFolder = { folderId -> openFolder(folderId) },
|
||||||
|
createDrawerListener = { createDrawerListener() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -630,7 +633,7 @@ open class MessageList :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openFolder(folderId: Long) {
|
private fun openFolder(folderId: Long) {
|
||||||
if (displayMode == DisplayMode.SPLIT_VIEW) {
|
if (displayMode == DisplayMode.SPLIT_VIEW) {
|
||||||
removeMessageViewContainerFragment()
|
removeMessageViewContainerFragment()
|
||||||
showMessageViewPlaceHolder()
|
showMessageViewPlaceHolder()
|
||||||
|
Loading…
Reference in New Issue
Block a user