mirror of https://github.com/readrops/Readrops.git
Improve state management in FeedTab
This commit is contained in:
parent
69788de077
commit
215399d3ac
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue