Implement FreshRSS update/delete folder

This commit is contained in:
Shinokuni 2024-05-19 23:31:36 +02:00
parent 064d588b28
commit 219d816483
8 changed files with 76 additions and 19 deletions

View File

@ -102,7 +102,12 @@ class FeedScreenModel(
_updateFeedDialogState.update { _updateFeedDialogState.update {
it.copy( it.copy(
folders = if (!it.isNoFolderCase) { // TODO implement no folder case properly folders = if (!it.isNoFolderCase) { // TODO implement no folder case properly
folders + listOf(Folder(id = 0, name = context.resources.getString(R.string.no_folder))) folders + listOf(
Folder(
id = 0,
name = context.resources.getString(R.string.no_folder)
)
)
} else { } else {
folders folders
} }
@ -128,6 +133,7 @@ class FeedScreenModel(
it.copy( it.copy(
folder = Folder(), folder = Folder(),
nameError = null, nameError = null,
exception = null
) )
} }
} }
@ -166,7 +172,11 @@ class FeedScreenModel(
fun deleteFolder(folder: Folder) { fun deleteFolder(folder: Folder) {
screenModelScope.launch(Dispatchers.IO) { screenModelScope.launch(Dispatchers.IO) {
repository?.deleteFolder(folder) try {
repository?.deleteFolder(folder)
} catch (e: Exception) {
_feedState.update { it.copy(exception = e) }
}
} }
} }
@ -340,17 +350,24 @@ class FeedScreenModel(
} }
screenModelScope.launch(Dispatchers.IO) { screenModelScope.launch(Dispatchers.IO) {
if (updateFolder) { try {
repository?.updateFolder(_folderState.value.folder) if (updateFolder) {
} else { repository?.updateFolder(_folderState.value.folder)
repository?.addFolder(_folderState.value.folder.apply { } else {
accountId = currentAccount!!.id repository?.addFolder(_folderState.value.folder.apply {
}) accountId = currentAccount!!.id
})
}
} catch (e: Exception) {
_folderState.update { it.copy(exception = e) }
return@launch
} }
closeDialog(DialogState.AddFolder) closeDialog(DialogState.AddFolder)
} }
} }
fun resetException() = _feedState.update { it.copy(exception = null) }
// add/update folder // add/update folder
} }

View File

@ -9,7 +9,8 @@ data class FeedState(
val foldersAndFeeds: FolderAndFeedsState = FolderAndFeedsState.InitialState, val foldersAndFeeds: FolderAndFeedsState = FolderAndFeedsState.InitialState,
val dialog: DialogState? = null, val dialog: DialogState? = null,
val areFoldersExpanded: Boolean = false, val areFoldersExpanded: Boolean = false,
val displayFolderCreationButton: Boolean = false val displayFolderCreationButton: Boolean = false,
val exception: Exception? = null
) )
sealed interface DialogState { sealed interface DialogState {
@ -61,8 +62,9 @@ data class UpdateFeedDialogState(
data class FolderState( data class FolderState(
val folder: Folder = Folder(), val folder: Folder = Folder(),
val nameError: TextFieldError? = null, val nameError: TextFieldError? = null,
val exception: Exception? = null
) { ) {
val name = folder.name val name = folder.name
val isError = nameError != null val isTextFieldError = nameError != null
} }

View File

@ -17,13 +17,18 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallFloatingActionButton import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@ -38,6 +43,7 @@ import com.readrops.app.compose.feeds.dialogs.AddFeedDialog
import com.readrops.app.compose.feeds.dialogs.FeedModalBottomSheet import com.readrops.app.compose.feeds.dialogs.FeedModalBottomSheet
import com.readrops.app.compose.feeds.dialogs.FolderDialog import com.readrops.app.compose.feeds.dialogs.FolderDialog
import com.readrops.app.compose.feeds.dialogs.UpdateFeedDialog import com.readrops.app.compose.feeds.dialogs.UpdateFeedDialog
import com.readrops.app.compose.util.ErrorMessage
import com.readrops.app.compose.util.components.CenteredProgressIndicator import com.readrops.app.compose.util.components.CenteredProgressIndicator
import com.readrops.app.compose.util.components.ErrorMessage import com.readrops.app.compose.util.components.ErrorMessage
import com.readrops.app.compose.util.components.Placeholder import com.readrops.app.compose.util.components.Placeholder
@ -59,11 +65,20 @@ object FeedTab : Tab {
override fun Content() { override fun Content() {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val context = LocalContext.current
val viewModel = getScreenModel<FeedScreenModel>() val viewModel = getScreenModel<FeedScreenModel>()
val state by viewModel.feedsState.collectAsStateWithLifecycle() val state by viewModel.feedsState.collectAsStateWithLifecycle()
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(state.exception) {
if (state.exception != null) {
snackbarHostState.showSnackbar(ErrorMessage.get(state.exception!!, context))
viewModel.resetException()
}
}
when (val dialog = state.dialog) { when (val dialog = state.dialog) {
is DialogState.AddFeed -> { is DialogState.AddFeed -> {
AddFeedDialog( AddFeedDialog(
@ -204,7 +219,8 @@ object FeedTab : Tab {
) )
} }
} }
} },
snackbarHost = { SnackbarHost(snackbarHostState) }
) { paddingValues -> ) { paddingValues ->
Box( Box(
modifier = Modifier modifier = Modifier

View File

@ -4,16 +4,19 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Clear
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.readrops.app.compose.R import com.readrops.app.compose.R
import com.readrops.app.compose.feeds.FeedScreenModel import com.readrops.app.compose.feeds.FeedScreenModel
import com.readrops.app.compose.util.ErrorMessage
import com.readrops.app.compose.util.components.BaseDialog import com.readrops.app.compose.util.components.BaseDialog
import com.readrops.app.compose.util.theme.LargeSpacer import com.readrops.app.compose.util.theme.LargeSpacer
@ -50,10 +53,17 @@ fun FolderDialog(
} }
} }
}, },
isError = state.isError, isError = state.isTextFieldError,
supportingText = { Text(text = state.nameError?.errorText().orEmpty()) } supportingText = { Text(text = state.nameError?.errorText().orEmpty()) }
) )
if (state.exception != null) {
Text(
text = ErrorMessage.get(state.exception!!, LocalContext.current),
color = MaterialTheme.colorScheme.error
)
}
LargeSpacer() LargeSpacer()
TextButton( TextButton(

View File

@ -85,8 +85,18 @@ class FreshRSSRepository(
override suspend fun insertNewFeeds( override suspend fun insertNewFeeds(
newFeeds: List<Feed>, newFeeds: List<Feed>,
onUpdate: (Feed) -> Unit onUpdate: (Feed) -> Unit
): ErrorResult { ): ErrorResult = TODO("Not yet implemented")
TODO("Not yet implemented")
override suspend fun updateFolder(folder: Folder) {
dataSource.updateFolder(account.writeToken!!, folder.remoteId!!, folder.name!!)
folder.remoteId = NewFreshRSSDataSource.FOLDER_PREFIX + folder.name
super.updateFolder(folder)
}
override suspend fun deleteFolder(folder: Folder) {
dataSource.deleteFolder(account.writeToken!!, folder.remoteId!!)
super.deleteFolder(folder)
} }
private suspend fun insertFeeds(feeds: List<Feed>) { private suspend fun insertFeeds(feeds: List<Feed>) {

View File

@ -25,6 +25,7 @@ class GetFoldersWithFeeds(
Folder( Folder(
id = it.folderId!!, id = it.folderId!!,
name = it.folderName, name = it.folderName,
remoteId = it.folderRemoteId,
accountId = it.accountId accountId = it.accountId
) )
} else { } else {

View File

@ -13,14 +13,14 @@ interface NewFolderDao : NewBaseDao<Folder> {
@Query(""" @Query("""
Select Feed.id As feedId, Feed.name As feedName, Feed.icon_url As feedIcon, Feed.url As feedUrl, Select Feed.id As feedId, Feed.name As feedName, Feed.icon_url As feedIcon, Feed.url As feedUrl,
Feed.siteUrl As feedSiteUrl, Feed.description as feedDescription, Feed.siteUrl As feedSiteUrl, Feed.description as feedDescription, Folder.id As folderId,
Folder.id As folderId, Folder.name As folderName, Feed.account_id as accountId Folder.name As folderName, Feed.account_id as accountId, Folder.remoteId as folderRemoteId
From Feed Left Join Folder On Folder.id = Feed.folder_id From Feed Left Join Folder On Folder.id = Feed.folder_id
Where Feed.folder_id is NULL OR Feed.folder_id is NOT NULL And Feed.id is NULL Or Feed.id is NOT NULL And Feed.account_id = :accountId Group By Feed.id Where Feed.folder_id is NULL OR Feed.folder_id is NOT NULL And Feed.id is NULL Or Feed.id is NOT NULL And Feed.account_id = :accountId Group By Feed.id
UNION ALL UNION ALL
Select Feed.id As feedId, Feed.name As feedName, Feed.icon_url As feedIcon, Feed.url As feedUrl, Select Feed.id As feedId, Feed.name As feedName, Feed.icon_url As feedIcon, Feed.url As feedUrl,
Feed.siteUrl As feedSiteUrl, Feed.description as feedDescription, Feed.siteUrl As feedSiteUrl, Feed.description as feedDescription,Folder.id As folderId,
Folder.id As folderId, Folder.name As folderName, Folder.account_id as accountId Folder.name As folderName, Folder.account_id as accountId, Folder.remoteId as folderRemoteId
From Folder Left Join Feed On Folder.id = Feed.folder_id From Folder Left Join Feed On Folder.id = Feed.folder_id
Where Feed.id is NULL And Folder.account_id = :accountId Where Feed.id is NULL And Folder.account_id = :accountId
""") """)

View File

@ -15,6 +15,7 @@ data class FeedWithFolder(
data class FolderWithFeed( data class FolderWithFeed(
val folderId: Int?, val folderId: Int?,
val folderName: String?, val folderName: String?,
val folderRemoteId: String?,
val feedId: Int = 0, val feedId: Int = 0,
val feedName: String? = null, val feedName: String? = null,
val feedIcon: String? = null, val feedIcon: String? = null,