Replace FolderDialog by TextFieldDialog in FeedTab

This commit is contained in:
Shinokuni 2024-07-10 16:43:26 +02:00
parent feeed2771f
commit 8e8d1ce9c8
3 changed files with 153 additions and 208 deletions

View File

@ -9,11 +9,13 @@ import com.readrops.app.R
import com.readrops.app.base.TabScreenModel
import com.readrops.app.repositories.GetFoldersWithFeeds
import com.readrops.app.util.components.TextFieldError
import com.readrops.app.util.components.dialog.TextFieldDialogState
import com.readrops.db.Database
import com.readrops.db.entities.Feed
import com.readrops.db.entities.Folder
import com.readrops.db.entities.account.Account
import com.readrops.db.filters.MainFilter
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@ -31,7 +33,8 @@ class FeedScreenModel(
database: Database,
private val getFoldersWithFeeds: GetFoldersWithFeeds,
private val localRSSDataSource: LocalRSSDataSource,
private val context: Context
private val context: Context,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : TabScreenModel(database), KoinComponent {
private val _feedState = MutableStateFlow(FeedState())
@ -43,11 +46,11 @@ class FeedScreenModel(
private val _updateFeedDialogState = MutableStateFlow(UpdateFeedDialogState())
val updateFeedDialogState = _updateFeedDialogState.asStateFlow()
private val _folderState = MutableStateFlow(FolderState())
private val _folderState = MutableStateFlow(TextFieldDialogState())
val folderState = _folderState.asStateFlow()
init {
screenModelScope.launch(context = Dispatchers.IO) {
screenModelScope.launch(dispatcher) {
accountEvent
.flatMapLatest { account ->
_feedState.update { it.copy(displayFolderCreationButton = account.config.canCreateFolder) }
@ -57,7 +60,11 @@ class FeedScreenModel(
)
}
getFoldersWithFeeds.get(account.id, MainFilter.ALL, account.config.useSeparateState)
getFoldersWithFeeds.get(
account.id,
MainFilter.ALL,
account.config.useSeparateState
)
}
.catch { throwable ->
_feedState.update {
@ -72,7 +79,7 @@ class FeedScreenModel(
}
screenModelScope.launch(context = Dispatchers.IO) {
screenModelScope.launch(dispatcher) {
database.accountDao()
.selectAllAccounts()
.collect { accounts ->
@ -87,17 +94,16 @@ class FeedScreenModel(
}
}
screenModelScope.launch(context = Dispatchers.IO) {
screenModelScope.launch(dispatcher) {
accountEvent.flatMapLatest { account ->
_updateFeedDialogState.update {
it.copy(
isFeedUrlReadOnly = account.config.isFeedUrlReadOnly,
)
}
database.folderDao()
.selectFolders(account.id)
_updateFeedDialogState.update {
it.copy(
isFeedUrlReadOnly = account.config.isFeedUrlReadOnly,
)
}
database.folderDao().selectFolders(account.id)
}
.collect { folders ->
_updateFeedDialogState.update {
it.copy(
@ -131,18 +137,21 @@ class FeedScreenModel(
)
}
}
is DialogState.AddFolder, is DialogState.UpdateFolder -> {
_folderState.update {
it.copy(
folder = Folder(),
nameError = null,
value = "",
textFieldError = null,
exception = null
)
}
}
is DialogState.UpdateFeed -> {
_updateFeedDialogState.update { it.copy(exception = null) }
}
else -> {}
}
@ -165,7 +174,7 @@ class FeedScreenModel(
if (state is DialogState.UpdateFolder) {
_folderState.update {
it.copy(
folder = state.folder
value = state.folder.name.orEmpty()
)
}
}
@ -174,7 +183,7 @@ class FeedScreenModel(
}
fun deleteFeed(feed: Feed) {
screenModelScope.launch(Dispatchers.IO) {
screenModelScope.launch(dispatcher) {
try {
repository?.deleteFeed(feed)
} catch (e: Exception) {
@ -184,7 +193,7 @@ class FeedScreenModel(
}
fun deleteFolder(folder: Folder) {
screenModelScope.launch(Dispatchers.IO) {
screenModelScope.launch(dispatcher) {
try {
repository?.deleteFolder(folder)
} catch (e: Exception) {
@ -228,7 +237,7 @@ class FeedScreenModel(
return
}
else -> screenModelScope.launch(Dispatchers.IO) {
else -> screenModelScope.launch(dispatcher) {
try {
if (localRSSDataSource.isUrlRSSResource(url)) {
insertFeeds(listOf(Feed(url = url)))
@ -329,7 +338,7 @@ class FeedScreenModel(
else -> {
_updateFeedDialogState.update { it.copy(exception = null) }
screenModelScope.launch(Dispatchers.IO) {
screenModelScope.launch(dispatcher) {
with(_updateFeedDialogState.value) {
try {
repository?.updateFeed(
@ -362,28 +371,26 @@ class FeedScreenModel(
fun setFolderName(name: String) = _folderState.update {
it.copy(
folder = it.folder.copy(name = name),
nameError = null,
value = name,
textFieldError = null,
)
}
fun folderValidate(updateFolder: Boolean = false) {
val name = _folderState.value.name.orEmpty()
val name = _folderState.value.value
if (name.isEmpty()) {
_folderState.update { it.copy(nameError = TextFieldError.EmptyField) }
_folderState.update { it.copy(textFieldError = TextFieldError.EmptyField) }
return
}
screenModelScope.launch(Dispatchers.IO) {
screenModelScope.launch(dispatcher) {
try {
if (updateFolder) {
repository?.updateFolder(_folderState.value.folder)
val folder = (_feedState.value.dialog as DialogState.UpdateFolder).folder
repository?.updateFolder(folder.copy(name = name))
} else {
repository?.addFolder(_folderState.value.folder.apply {
accountId = currentAccount!!.id
})
repository?.addFolder(Folder(name = name, accountId = currentAccount!!.id))
}
} catch (e: Exception) {
_folderState.update { it.copy(exception = e) }

View File

@ -44,12 +44,12 @@ import cafe.adriel.voyager.navigator.tab.TabOptions
import com.readrops.app.R
import com.readrops.app.feeds.dialogs.AddFeedDialog
import com.readrops.app.feeds.dialogs.FeedModalBottomSheet
import com.readrops.app.feeds.dialogs.FolderDialog
import com.readrops.app.feeds.dialogs.UpdateFeedDialog
import com.readrops.app.util.ErrorMessage
import com.readrops.app.util.components.CenteredProgressIndicator
import com.readrops.app.util.components.ErrorMessage
import com.readrops.app.util.components.Placeholder
import com.readrops.app.util.components.dialog.TextFieldDialog
import com.readrops.app.util.components.dialog.TwoChoicesDialog
import com.readrops.app.util.theme.spacing
import com.readrops.db.entities.Feed
@ -70,109 +70,24 @@ object FeedTab : Tab {
val uriHandler = LocalUriHandler.current
val context = LocalContext.current
val viewModel = getScreenModel<FeedScreenModel>()
val state by viewModel.feedsState.collectAsStateWithLifecycle()
val screenModel = getScreenModel<FeedScreenModel>()
val state by screenModel.feedsState.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
val topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val topAppBarScrollBehavior =
TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
LaunchedEffect(state.exception) {
if (state.exception != null) {
snackbarHostState.showSnackbar(ErrorMessage.get(state.exception!!, context))
viewModel.resetException()
screenModel.resetException()
}
}
when (val dialog = state.dialog) {
is DialogState.AddFeed -> {
AddFeedDialog(
viewModel = viewModel,
onDismiss = {
viewModel.closeDialog(DialogState.AddFeed)
},
)
}
is DialogState.DeleteFeed -> {
TwoChoicesDialog(
title = stringResource(R.string.delete_feed),
text = "Do you want to delete feed ${dialog.feed.name}?",
icon = rememberVectorPainter(image = Icons.Default.Delete),
confirmText = stringResource(R.string.delete),
dismissText = stringResource(R.string.cancel),
onDismiss = { viewModel.closeDialog() },
onConfirm = {
viewModel.deleteFeed(dialog.feed)
viewModel.closeDialog()
}
)
}
is DialogState.FeedSheet -> {
FeedModalBottomSheet(
feed = dialog.feed,
onDismissRequest = { viewModel.closeDialog() },
onOpen = {
uriHandler.openUri(dialog.feed.siteUrl!!)
viewModel.closeDialog()
},
onUpdate = {
viewModel.openDialog(DialogState.UpdateFeed(dialog.feed, dialog.folder))
},
onUpdateColor = {},
onDelete = { viewModel.openDialog(DialogState.DeleteFeed(dialog.feed)) }
)
}
is DialogState.UpdateFeed -> {
UpdateFeedDialog(
viewModel = viewModel,
onDismissRequest = { viewModel.closeDialog(dialog) }
)
}
DialogState.AddFolder -> {
FolderDialog(
viewModel = viewModel,
onDismiss = {
viewModel.closeDialog(DialogState.AddFolder)
},
onValidate = {
viewModel.folderValidate()
}
)
}
is DialogState.DeleteFolder -> {
TwoChoicesDialog(
title = stringResource(R.string.delete_folder),
text = "Do you want to delete folder ${dialog.folder.name}?",
icon = rememberVectorPainter(image = Icons.Default.Delete),
confirmText = stringResource(R.string.delete),
dismissText = stringResource(R.string.cancel),
onDismiss = { viewModel.closeDialog() },
onConfirm = {
viewModel.deleteFolder(dialog.folder)
viewModel.closeDialog()
}
)
}
is DialogState.UpdateFolder -> {
FolderDialog(
updateFolder = true,
viewModel = viewModel,
onDismiss = {
viewModel.closeDialog(DialogState.UpdateFolder(dialog.folder))
},
onValidate = {
viewModel.folderValidate(updateFolder = true)
}
)
}
null -> {}
}
FeedDialogs(
state = state,
screenModel = screenModel
)
Scaffold(
topBar = {
@ -180,7 +95,7 @@ object FeedTab : Tab {
title = { Text(text = stringResource(R.string.feeds)) },
actions = {
IconButton(
onClick = { viewModel.setFolderExpandState(state.areFoldersExpanded.not()) }
onClick = { screenModel.setFolderExpandState(state.areFoldersExpanded.not()) }
) {
Icon(
painter = painterResource(
@ -205,7 +120,7 @@ object FeedTab : Tab {
start = MaterialTheme.spacing.veryShortSpacing,
bottom = MaterialTheme.spacing.shortSpacing
),
onClick = { viewModel.openDialog(DialogState.AddFolder) }
onClick = { screenModel.openDialog(DialogState.AddFolder) }
) {
Icon(
painter = painterResource(id = R.drawable.ic_new_folder),
@ -216,7 +131,7 @@ object FeedTab : Tab {
}
FloatingActionButton(
onClick = { viewModel.openDialog(DialogState.AddFeed) }
onClick = { screenModel.openDialog(DialogState.AddFeed) }
) {
Icon(
imageVector = Icons.Default.Add,
@ -256,18 +171,18 @@ object FeedTab : Tab {
feeds = folderWithFeeds.second,
isExpanded = state.areFoldersExpanded,
onFeedClick = { feed ->
viewModel.openDialog(
screenModel.openDialog(
DialogState.FeedSheet(feed, folder)
)
},
onFeedLongClick = { feed -> onFeedLongClick(feed) },
onUpdateFolder = {
viewModel.openDialog(
screenModel.openDialog(
DialogState.UpdateFolder(folder)
)
},
onDeleteFolder = {
viewModel.openDialog(
screenModel.openDialog(
DialogState.DeleteFolder(folder)
)
}
@ -279,7 +194,7 @@ object FeedTab : Tab {
FeedItem(
feed = feed,
onClick = {
viewModel.openDialog(
screenModel.openDialog(
DialogState.FeedSheet(feed, null)
)
},
@ -310,4 +225,102 @@ object FeedTab : Tab {
}
}
}
@Composable
private fun FeedDialogs(state: FeedState, screenModel: FeedScreenModel) {
val uriHandler = LocalUriHandler.current
val folderState by screenModel.folderState.collectAsStateWithLifecycle()
when (val dialog = state.dialog) {
is DialogState.AddFeed -> {
AddFeedDialog(
viewModel = screenModel,
onDismiss = {
screenModel.closeDialog(DialogState.AddFeed)
},
)
}
is DialogState.DeleteFeed -> {
TwoChoicesDialog(
title = stringResource(R.string.delete_feed),
text = "Do you want to delete feed ${dialog.feed.name}?",
icon = rememberVectorPainter(image = Icons.Default.Delete),
confirmText = stringResource(R.string.delete),
dismissText = stringResource(R.string.cancel),
onDismiss = { screenModel.closeDialog() },
onConfirm = {
screenModel.deleteFeed(dialog.feed)
screenModel.closeDialog()
}
)
}
is DialogState.FeedSheet -> {
FeedModalBottomSheet(
feed = dialog.feed,
onDismissRequest = { screenModel.closeDialog() },
onOpen = {
uriHandler.openUri(dialog.feed.siteUrl!!)
screenModel.closeDialog()
},
onUpdate = {
screenModel.openDialog(DialogState.UpdateFeed(dialog.feed, dialog.folder))
},
onUpdateColor = {},
onDelete = { screenModel.openDialog(DialogState.DeleteFeed(dialog.feed)) }
)
}
is DialogState.UpdateFeed -> {
UpdateFeedDialog(
viewModel = screenModel,
onDismissRequest = { screenModel.closeDialog(dialog) }
)
}
DialogState.AddFolder -> {
TextFieldDialog(
title = stringResource(id = R.string.add_folder),
icon = painterResource(id = R.drawable.ic_new_folder),
label = stringResource(id = R.string.name),
state = folderState,
onValueChange = { screenModel.setFolderName(it) },
onValidate = { screenModel.folderValidate() },
onDismiss = { screenModel.closeDialog(DialogState.AddFolder) }
)
}
is DialogState.DeleteFolder -> {
TwoChoicesDialog(
title = stringResource(R.string.delete_folder),
text = "Do you want to delete folder ${dialog.folder.name}?",
icon = rememberVectorPainter(image = Icons.Default.Delete),
confirmText = stringResource(R.string.delete),
dismissText = stringResource(R.string.cancel),
onDismiss = { screenModel.closeDialog() },
onConfirm = {
screenModel.deleteFolder(dialog.folder)
screenModel.closeDialog()
}
)
}
is DialogState.UpdateFolder -> {
TextFieldDialog(
title = stringResource(id = R.string.edit_folder),
icon = painterResource(id = R.drawable.ic_folder_grey),
label = stringResource(id = R.string.name),
state = folderState,
onValueChange = { screenModel.setFolderName(it) },
onValidate = { screenModel.folderValidate(updateFolder = true) },
onDismiss = { screenModel.closeDialog(DialogState.UpdateFolder(dialog.folder)) }
)
}
null -> {}
}
}
}

View File

@ -1,75 +0,0 @@
package com.readrops.app.feeds.dialogs
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.readrops.app.R
import com.readrops.app.feeds.FeedScreenModel
import com.readrops.app.util.ErrorMessage
import com.readrops.app.util.components.dialog.BaseDialog
import com.readrops.app.util.theme.LargeSpacer
@Composable
fun FolderDialog(
updateFolder: Boolean = false,
viewModel: FeedScreenModel,
onDismiss: () -> Unit,
onValidate: () -> Unit
) {
val state by viewModel.folderState.collectAsStateWithLifecycle()
BaseDialog(
title = stringResource(id = if (updateFolder) R.string.edit_folder else R.string.add_folder),
icon = painterResource(id = if (updateFolder) R.drawable.ic_folder_grey else R.drawable.ic_new_folder),
onDismiss = onDismiss
) {
OutlinedTextField(
value = state.name.orEmpty(),
label = {
Text(text = "URL")
},
onValueChange = { viewModel.setFolderName(it) },
singleLine = true,
trailingIcon = {
if (!state.name.isNullOrEmpty()) {
IconButton(
onClick = { viewModel.setFolderName("") }
) {
Icon(
imageVector = Icons.Default.Clear,
contentDescription = null
)
}
}
},
isError = state.isTextFieldError,
supportingText = { Text(text = state.nameError?.errorText().orEmpty()) }
)
if (state.exception != null) {
Text(
text = ErrorMessage.get(state.exception!!, LocalContext.current),
color = MaterialTheme.colorScheme.error
)
}
LargeSpacer()
TextButton(
onClick = { onValidate() },
) {
Text(text = stringResource(R.string.validate))
}
}
}