Implement FreshRSS add/update/delete feed actions

This commit is contained in:
Shinokuni 2024-05-20 22:07:30 +02:00
parent 940b6de97a
commit 133d8e6992
6 changed files with 118 additions and 42 deletions

View File

@ -121,14 +121,17 @@ class FeedScreenModel(
_feedState.update { it.copy(areFoldersExpanded = isExpanded) } _feedState.update { it.copy(areFoldersExpanded = isExpanded) }
fun closeDialog(dialog: DialogState? = null) { fun closeDialog(dialog: DialogState? = null) {
if (dialog is DialogState.AddFeed) { when (dialog) {
is DialogState.AddFeed -> {
_addFeedDialogState.update { _addFeedDialogState.update {
it.copy( it.copy(
url = "", url = "",
error = null, error = null,
exception = null
) )
} }
} else if (dialog is DialogState.AddFolder || dialog is DialogState.UpdateFolder) { }
is DialogState.AddFolder, is DialogState.UpdateFolder -> {
_folderState.update { _folderState.update {
it.copy( it.copy(
folder = Folder(), folder = Folder(),
@ -137,6 +140,11 @@ class FeedScreenModel(
) )
} }
} }
is DialogState.UpdateFeed -> {
_updateFeedDialogState.update { it.copy(exception = null) }
}
else -> {}
}
_feedState.update { it.copy(dialog = null) } _feedState.update { it.copy(dialog = null) }
} }
@ -166,7 +174,11 @@ class FeedScreenModel(
fun deleteFeed(feed: Feed) { fun deleteFeed(feed: Feed) {
screenModelScope.launch(Dispatchers.IO) { screenModelScope.launch(Dispatchers.IO) {
try {
repository?.deleteFeed(feed) repository?.deleteFeed(feed)
} catch (e: Exception) {
_feedState.update { it.copy(exception = e) }
}
} }
} }
@ -180,7 +192,7 @@ class FeedScreenModel(
} }
} }
// Add feed //region Add Feed
fun setAddFeedDialogURL(url: String) { fun setAddFeedDialogURL(url: String) {
_addFeedDialogState.update { _addFeedDialogState.update {
@ -218,12 +230,7 @@ class FeedScreenModel(
else -> screenModelScope.launch(Dispatchers.IO) { else -> screenModelScope.launch(Dispatchers.IO) {
try { try {
if (localRSSDataSource.isUrlRSSResource(url)) { if (localRSSDataSource.isUrlRSSResource(url)) {
// TODO add support for all account types insertFeeds(listOf(Feed(url = url)))
repository?.insertNewFeeds(
newFeeds = listOf(Feed(url = url))
) {}
closeDialog(DialogState.AddFeed)
} else { } else {
val rssUrls = HtmlParser.getFeedLink(url, get()) val rssUrls = HtmlParser.getFeedLink(url, get())
@ -232,12 +239,7 @@ class FeedScreenModel(
it.copy(error = TextFieldError.NoRSSFeed) it.copy(error = TextFieldError.NoRSSFeed)
} }
} else { } else {
// TODO add support for all account types insertFeeds(rssUrls.map { Feed(url = it.url) })
repository?.insertNewFeeds(
newFeeds = rssUrls.map { Feed(url = it.url) }
) {}
closeDialog(DialogState.AddFeed)
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -250,9 +252,22 @@ class FeedScreenModel(
} }
} }
// add feed private suspend fun insertFeeds(feeds: List<Feed>) {
val errors = repository?.insertNewFeeds(
newFeeds = feeds,
onUpdate = { /* no need of this here */ }
)!!
// update feed if (errors.isEmpty()) {
closeDialog(DialogState.AddFeed)
} else {
_addFeedDialogState.update { it.copy(exception = errors.values.first()) }
}
}
//endregion
//region Update feed
fun setAccountDropDownState(isExpanded: Boolean) { fun setAccountDropDownState(isExpanded: Boolean) {
_updateFeedDialogState.update { _updateFeedDialogState.update {
@ -311,16 +326,24 @@ class FeedScreenModel(
} }
else -> { else -> {
_updateFeedDialogState.update { it.copy(exception = null) }
screenModelScope.launch(Dispatchers.IO) { screenModelScope.launch(Dispatchers.IO) {
with(_updateFeedDialogState.value) { with(_updateFeedDialogState.value) {
try {
repository?.updateFeed( repository?.updateFeed(
Feed( Feed(
id = feedId, id = feedId,
name = feedName, name = feedName,
url = feedUrl, url = feedUrl,
folderId = selectedFolder?.id folderId = selectedFolder?.id,
remoteFolderId = selectedFolder?.remoteId
) )
) )
} catch (e: Exception) {
_updateFeedDialogState.update { it.copy(exception = e) }
return@launch
}
} }
closeDialog() closeDialog()
@ -329,9 +352,9 @@ class FeedScreenModel(
} }
} }
// update feed //endregion
// add/update folder //region Add/Update folder
fun setFolderName(name: String) = _folderState.update { fun setFolderName(name: String) = _folderState.update {
it.copy( it.copy(
@ -369,5 +392,5 @@ class FeedScreenModel(
fun resetException() = _feedState.update { it.copy(exception = null) } fun resetException() = _feedState.update { it.copy(exception = null) }
// add/update folder //endregion
} }

View File

@ -34,6 +34,7 @@ data class AddFeedDialogState(
val selectedAccount: Account = Account(accountName = ""), val selectedAccount: Account = Account(accountName = ""),
val accounts: List<Account> = listOf(), val accounts: List<Account> = listOf(),
val error: TextFieldError? = null, val error: TextFieldError? = null,
val exception: Exception? = null
) { ) {
val isError: Boolean get() = error != null val isError: Boolean get() = error != null
} }
@ -48,7 +49,8 @@ data class UpdateFeedDialogState(
val folders: List<Folder> = listOf(), val folders: List<Folder> = listOf(),
val isAccountDropDownExpanded: Boolean = false, val isAccountDropDownExpanded: Boolean = false,
val isFeedUrlReadOnly: Boolean = true, val isFeedUrlReadOnly: Boolean = true,
val isNoFolderCase: Boolean = false val isNoFolderCase: Boolean = false,
val exception: Exception? = null
) { ) {
val isFeedNameError val isFeedNameError
get() = feedNameError != null get() = feedNameError != null

View File

@ -123,7 +123,7 @@ object FeedTab : Tab {
is DialogState.UpdateFeed -> { is DialogState.UpdateFeed -> {
UpdateFeedDialog( UpdateFeedDialog(
viewModel = viewModel, viewModel = viewModel,
onDismissRequest = { viewModel.closeDialog() } onDismissRequest = { viewModel.closeDialog(dialog) }
) )
} }

View File

@ -10,6 +10,7 @@ import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ExposedDropdownMenuDefaults
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
@ -19,15 +20,19 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
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.account.selection.adaptiveIconPainterResource import com.readrops.app.compose.account.selection.adaptiveIconPainterResource
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
import com.readrops.app.compose.util.theme.MediumSpacer
import com.readrops.app.compose.util.theme.ShortSpacer import com.readrops.app.compose.util.theme.ShortSpacer
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -118,6 +123,16 @@ fun AddFeedDialog(
) )
} }
if (state.exception != null) {
MediumSpacer()
Text(
text = ErrorMessage.get(state.exception!!, LocalContext.current),
color = MaterialTheme.colorScheme.error,
textAlign = TextAlign.Center
)
}
LargeSpacer() LargeSpacer()
TextButton( TextButton(

View File

@ -5,17 +5,20 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
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.Modifier import androidx.compose.ui.Modifier
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
import com.readrops.app.compose.util.theme.MediumSpacer import com.readrops.app.compose.util.theme.MediumSpacer
@ -119,6 +122,15 @@ fun UpdateFeedDialog(
) )
} }
if (state.exception != null) {
MediumSpacer()
Text(
text = ErrorMessage.get(state.exception!!, LocalContext.current),
color = MaterialTheme.colorScheme.error
)
}
LargeSpacer() LargeSpacer()
TextButton( TextButton(

View File

@ -85,7 +85,31 @@ class FreshRSSRepository(
override suspend fun insertNewFeeds( override suspend fun insertNewFeeds(
newFeeds: List<Feed>, newFeeds: List<Feed>,
onUpdate: (Feed) -> Unit onUpdate: (Feed) -> Unit
): ErrorResult = TODO("Not yet implemented") ): ErrorResult {
val errors = mutableMapOf<Feed, Exception>()
for (newFeed in newFeeds) {
onUpdate(newFeed)
try {
dataSource.createFeed(account.writeToken!!, newFeed.url!!)
} catch (e: Exception) {
errors[newFeed] = e
}
}
return errors
}
override suspend fun updateFeed(feed: Feed) {
dataSource.updateFeed(account.writeToken!!, feed.url!!, feed.name!!, feed.remoteFolderId!!)
super.updateFeed(feed)
}
override suspend fun deleteFeed(feed: Feed) {
dataSource.deleteFeed(account.writeToken!!, feed.url!!)
super.deleteFeed(feed)
}
override suspend fun updateFolder(folder: Folder) { override suspend fun updateFolder(folder: Folder) {
dataSource.updateFolder(account.writeToken!!, folder.remoteId!!, folder.name!!) dataSource.updateFolder(account.writeToken!!, folder.remoteId!!, folder.name!!)