Improve state management in FeedTab

This commit is contained in:
Shinokuni 2024-01-17 15:48:07 +01:00
parent 69788de077
commit 215399d3ac
3 changed files with 101 additions and 80 deletions

View File

@ -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<Folder?, List<Feed>>) : FolderAndFeedsState()
}
data class AddFeedDialogState(
val url: String = "",
val selectedAccount: Account = Account(accountName = ""),
val accounts: List<Account> = 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()
}
}

View File

@ -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<FeedViewModel>()
val state by viewModel.feedsState.collectAsStateWithLifecycle()
var showDialog by remember { mutableStateOf(false) }
var selectedFeed by remember { mutableStateOf<Feed?>(null) }
var selectedFolder by remember { mutableStateOf<Folder?>(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,

View File

@ -27,8 +27,8 @@ class FeedViewModel(
private val localRSSDataSource: LocalRSSDataSource,
) : TabViewModel(database), KoinComponent {
private val _feedsState = MutableStateFlow<FeedsState>(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<Folder?, List<Feed>>) : FeedsState()
}
data class AddFeedDialogState(
val url: String = "",
val selectedAccount: Account = Account(accountName = ""),
val accounts: List<Account> = 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()
}
}