From 8e8d1ce9c85a84e94b901e5bf28220fec132b751 Mon Sep 17 00:00:00 2001 From: Shinokuni Date: Wed, 10 Jul 2024 16:43:26 +0200 Subject: [PATCH] Replace FolderDialog by TextFieldDialog in FeedTab --- .../com/readrops/app/feeds/FeedScreenModel.kt | 69 +++--- .../java/com/readrops/app/feeds/FeedTab.kt | 217 ++++++++++-------- .../app/feeds/dialogs/FolderDialog.kt | 75 ------ 3 files changed, 153 insertions(+), 208 deletions(-) delete mode 100644 app/src/main/java/com/readrops/app/feeds/dialogs/FolderDialog.kt diff --git a/app/src/main/java/com/readrops/app/feeds/FeedScreenModel.kt b/app/src/main/java/com/readrops/app/feeds/FeedScreenModel.kt index 1a98638d..6417a931 100644 --- a/app/src/main/java/com/readrops/app/feeds/FeedScreenModel.kt +++ b/app/src/main/java/com/readrops/app/feeds/FeedScreenModel.kt @@ -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) } diff --git a/app/src/main/java/com/readrops/app/feeds/FeedTab.kt b/app/src/main/java/com/readrops/app/feeds/FeedTab.kt index 7139db74..f2ff9811 100644 --- a/app/src/main/java/com/readrops/app/feeds/FeedTab.kt +++ b/app/src/main/java/com/readrops/app/feeds/FeedTab.kt @@ -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() - val state by viewModel.feedsState.collectAsStateWithLifecycle() + val screenModel = getScreenModel() + 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 -> {} + } + + } } \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/feeds/dialogs/FolderDialog.kt b/app/src/main/java/com/readrops/app/feeds/dialogs/FolderDialog.kt deleted file mode 100644 index 42c28258..00000000 --- a/app/src/main/java/com/readrops/app/feeds/dialogs/FolderDialog.kt +++ /dev/null @@ -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)) - } - } -} \ No newline at end of file