From 215399d3acb0b9e17135bc38c9e123247f96f0d3 Mon Sep 17 00:00:00 2001 From: Shinokuni Date: Wed, 17 Jan 2024 15:48:07 +0100 Subject: [PATCH] Improve state management in FeedTab --- .../readrops/app/compose/feeds/FeedState.kt | 51 +++++++++++++ .../com/readrops/app/compose/feeds/FeedTab.kt | 73 ++++++++----------- .../app/compose/feeds/FeedViewModel.kt | 57 +++++---------- 3 files changed, 101 insertions(+), 80 deletions(-) create mode 100644 appcompose/src/main/java/com/readrops/app/compose/feeds/FeedState.kt diff --git a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedState.kt b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedState.kt new file mode 100644 index 00000000..b686f69e --- /dev/null +++ b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedState.kt @@ -0,0 +1,51 @@ +package com.readrops.app.compose.feeds + +import com.readrops.db.entities.Feed +import com.readrops.db.entities.Folder +import com.readrops.db.entities.account.Account + +data class FeedState( + val foldersAndFeeds: FolderAndFeedsState = FolderAndFeedsState.InitialState, + val dialog: DialogState? = null, +) + +sealed interface DialogState { + object AddFeed : DialogState + class DeleteFeed(val feed: Feed) : DialogState + class UpdateFeed(val feed: Feed) : DialogState + class FeedSheet(val feed: Feed, val folder: Folder?) : DialogState +} + +sealed class FolderAndFeedsState { + object InitialState : FolderAndFeedsState() + data class ErrorState(val exception: Exception) : FolderAndFeedsState() + data class LoadedState(val values: Map>) : FolderAndFeedsState() +} + +data class AddFeedDialogState( + val url: String = "", + val selectedAccount: Account = Account(accountName = ""), + val accounts: List = listOf(), + val error: AddFeedError? = null, + val closeDialog: Boolean = false, +) { + fun isError() = error != null + + val errorText: String + get() = when (error) { + is AddFeedError.EmptyUrl -> "Field can't be empty" + AddFeedError.BadUrl -> "Input is not a valid URL" + AddFeedError.NoConnection -> "" + AddFeedError.NoRSSFeed -> "No RSS feed found" + AddFeedError.UnreachableUrl -> "" + else -> "" + } + + sealed class AddFeedError { + object EmptyUrl : AddFeedError() + object BadUrl : AddFeedError() + object UnreachableUrl : AddFeedError() + object NoRSSFeed : AddFeedError() + object NoConnection : AddFeedError() + } +} \ No newline at end of file diff --git a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedTab.kt b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedTab.kt index e8fdd996..d1fb95c7 100644 --- a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedTab.kt +++ b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedTab.kt @@ -17,9 +17,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType @@ -33,7 +30,6 @@ import cafe.adriel.voyager.navigator.tab.TabOptions import com.readrops.app.compose.R import com.readrops.app.compose.util.components.Placeholder import com.readrops.db.entities.Feed -import com.readrops.db.entities.Folder import org.koin.androidx.compose.getViewModel object FeedTab : Tab { @@ -53,32 +49,31 @@ object FeedTab : Tab { val viewModel = getViewModel() val state by viewModel.feedsState.collectAsStateWithLifecycle() - var showDialog by remember { mutableStateOf(false) } - var selectedFeed by remember { mutableStateOf(null) } - var selectedFolder by remember { mutableStateOf(null) } - var showBottomSheet by remember { mutableStateOf(false) } - - if (showBottomSheet) { - FeedModalBottomSheet( - feed = selectedFeed!!, - folder = selectedFolder, - onDismissRequest = { showBottomSheet = false }, - onOpen = { uriHandler.openUri(selectedFeed!!.siteUrl!!) }, - onModify = { }, - onUpdateColor = {}, - onDelete = {}, - ) - } - - if (showDialog) { - AddFeedDialog( - viewModel = viewModel, - onDismiss = { - showDialog = false - viewModel.resetAddFeedDialogState() - }, - ) + when (val dialog = state.dialog) { + is DialogState.AddFeed -> { + AddFeedDialog( + viewModel = viewModel, + onDismiss = { + viewModel.closeDialog() + viewModel.resetAddFeedDialogState() + }, + ) + } + is DialogState.DeleteFeed -> {} + is DialogState.FeedSheet -> { + FeedModalBottomSheet( + feed = dialog.feed, + folder = dialog.folder, + onDismissRequest = { viewModel.closeDialog() }, + onOpen = { uriHandler.openUri(dialog.feed.siteUrl!!) }, + onModify = { }, + onUpdateColor = {}, + onDelete = {}, + ) + } + is DialogState.UpdateFeed -> {} + null -> {} } Scaffold( @@ -105,9 +100,9 @@ object FeedTab : Tab { .fillMaxSize() .padding(paddingValues) ) { - when (state) { - is FeedsState.LoadedState -> { - val foldersAndFeeds = (state as FeedsState.LoadedState).foldersAndFeeds + when (state.foldersAndFeeds) { + is FolderAndFeedsState.LoadedState -> { + val foldersAndFeeds = (state.foldersAndFeeds as FolderAndFeedsState.LoadedState).values if (foldersAndFeeds.isNotEmpty()) { LazyColumn { @@ -115,12 +110,6 @@ object FeedTab : Tab { items = foldersAndFeeds.toList() ) { folderWithFeeds -> - fun onFeedClick(feed: Feed) { - selectedFeed = feed - selectedFolder = folderWithFeeds.first - showBottomSheet = true - } - fun onFeedLongClick(feed: Feed) { haptic.performHapticFeedback(HapticFeedbackType.LongPress) uriHandler.openUri(feed.siteUrl!!) @@ -130,7 +119,7 @@ object FeedTab : Tab { FolderExpandableItem( folder = folderWithFeeds.first!!, feeds = folderWithFeeds.second, - onFeedClick = { feed -> onFeedClick(feed) }, + onFeedClick = { feed -> viewModel.openFeedSheet(feed, folderWithFeeds.first) }, onFeedLongClick = { feed -> onFeedLongClick(feed) } ) } else { @@ -139,7 +128,7 @@ object FeedTab : Tab { for (feed in feeds) { FeedItem( feed = feed, - onClick = { onFeedClick(feed) }, + onClick = { viewModel.openFeedSheet(feed, null) }, onLongClick = { onFeedLongClick(feed) }, ) } @@ -154,7 +143,7 @@ object FeedTab : Tab { } } - is FeedsState.ErrorState -> { + is FolderAndFeedsState.ErrorState -> { } @@ -167,7 +156,7 @@ object FeedTab : Tab { modifier = Modifier .align(Alignment.BottomEnd) .padding(16.dp), - onClick = { showDialog = true } + onClick = { viewModel.openAddFeedDialog() } ) { Icon( imageVector = Icons.Default.Add, diff --git a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedViewModel.kt b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedViewModel.kt index e42a4877..c2e20f58 100644 --- a/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedViewModel.kt +++ b/appcompose/src/main/java/com/readrops/app/compose/feeds/FeedViewModel.kt @@ -27,8 +27,8 @@ class FeedViewModel( private val localRSSDataSource: LocalRSSDataSource, ) : TabViewModel(database), KoinComponent { - private val _feedsState = MutableStateFlow(FeedsState.InitialState) - val feedsState = _feedsState.asStateFlow() + private val _feedState = MutableStateFlow(FeedState()) + val feedsState = _feedState.asStateFlow() private val _addFeedDialogState = MutableStateFlow(AddFeedDialogState()) val addFeedDialogState = _addFeedDialogState.asStateFlow() @@ -39,8 +39,16 @@ class FeedViewModel( .flatMapConcat { account -> getFoldersWithFeeds.get(account.id) } - .catch { _feedsState.value = FeedsState.ErrorState(Exception(it)) } - .collect { _feedsState.value = FeedsState.LoadedState(it) } + .catch { throwable -> + _feedState.update { + it.copy(foldersAndFeeds = FolderAndFeedsState.ErrorState(Exception(throwable))) + } + } + .collect { foldersAndFeeds -> + _feedState.update { + it.copy(foldersAndFeeds = FolderAndFeedsState.LoadedState(foldersAndFeeds)) + } + } } viewModelScope.launch(context = Dispatchers.IO) { @@ -57,6 +65,13 @@ class FeedViewModel( } } + fun closeDialog() = _feedState.update { it.copy(dialog = null) } + + fun openAddFeedDialog() = _feedState.update { it.copy(dialog = DialogState.AddFeed) } + + fun openFeedSheet(feed: Feed, folder: Folder?) = + _feedState.update { it.copy(dialog = DialogState.FeedSheet(feed, folder)) } + fun setAddFeedDialogURL(url: String) { _addFeedDialogState.update { it.copy( @@ -135,37 +150,3 @@ class FeedViewModel( } } -sealed class FeedsState { - object InitialState : FeedsState() - data class ErrorState(val exception: Exception) : FeedsState() - data class LoadedState(val foldersAndFeeds: Map>) : FeedsState() -} - - -data class AddFeedDialogState( - val url: String = "", - val selectedAccount: Account = Account(accountName = ""), - val accounts: List = listOf(), - val error: AddFeedError? = null, - val closeDialog: Boolean = false, -) { - fun isError() = error != null - - val errorText: String - get() = when (error) { - is AddFeedError.EmptyUrl -> "Field can't be empty" - AddFeedError.BadUrl -> "Input is not a valid URL" - AddFeedError.NoConnection -> "" - AddFeedError.NoRSSFeed -> "No RSS feed found" - AddFeedError.UnreachableUrl -> "" - else -> "" - } - - sealed class AddFeedError { - object EmptyUrl : AddFeedError() - object BadUrl : AddFeedError() - object UnreachableUrl : AddFeedError() - object NoRSSFeed : AddFeedError() - object NoConnection : AddFeedError() - } -} \ No newline at end of file