mirror of https://github.com/readrops/Readrops.git
Add UpdateFeedDialog in FeedTab
This commit is contained in:
parent
a6d753ef8a
commit
caf55451d3
|
@ -5,7 +5,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.readrops.app.compose.repositories.BaseRepository
|
||||
import com.readrops.db.Database
|
||||
import com.readrops.db.entities.account.Account
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
@ -26,7 +26,7 @@ abstract class TabViewModel(
|
|||
|
||||
protected var currentAccount: Account? = null
|
||||
|
||||
protected val accountEvent = Channel<Account>()
|
||||
protected val accountEvent = MutableSharedFlow<Account>()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
|
@ -38,7 +38,7 @@ abstract class TabViewModel(
|
|||
currentAccount = account
|
||||
repository = get(parameters = { parametersOf(account) })
|
||||
|
||||
accountEvent.send(account)
|
||||
accountEvent.emit(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ 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
|
||||
import com.readrops.db.entities.account.AccountType
|
||||
|
||||
data class FeedState(
|
||||
val foldersAndFeeds: FolderAndFeedsState = FolderAndFeedsState.InitialState,
|
||||
|
@ -14,7 +15,7 @@ sealed interface DialogState {
|
|||
object AddFolder : DialogState
|
||||
class DeleteFeed(val feed: Feed) : DialogState
|
||||
class DeleteFolder(val folder: Folder) : DialogState
|
||||
class UpdateFeed(val feed: Feed) : DialogState
|
||||
class UpdateFeed(val feed: Feed, val folder: Folder?) : DialogState
|
||||
class UpdateFolder(val folder: Folder) : DialogState
|
||||
class FeedSheet(val feed: Feed, val folder: Folder?) : DialogState
|
||||
}
|
||||
|
@ -30,9 +31,8 @@ data class AddFeedDialogState(
|
|||
val selectedAccount: Account = Account(accountName = ""),
|
||||
val accounts: List<Account> = listOf(),
|
||||
val error: AddFeedError? = null,
|
||||
val closeDialog: Boolean = false,
|
||||
) {
|
||||
fun isError() = error != null
|
||||
val isError: Boolean get() = error != null
|
||||
|
||||
val errorText: String
|
||||
get() = when (error) {
|
||||
|
@ -51,4 +51,39 @@ data class AddFeedDialogState(
|
|||
object NoRSSFeed : AddFeedError()
|
||||
object NoConnection : AddFeedError()
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateFeedDialogState(
|
||||
val feedName: String = "",
|
||||
val feedNameError: Error? = null,
|
||||
val feedUrl: String = "",
|
||||
val feedUrlError: Error? = null,
|
||||
val accountType: AccountType? = null,
|
||||
val selectedFolder: Folder? = null,
|
||||
val folders: List<Folder> = listOf(),
|
||||
val isAccountDropDownExpanded: Boolean = false,
|
||||
) {
|
||||
|
||||
sealed class Error {
|
||||
object EmptyField : Error()
|
||||
object BadUrl : Error()
|
||||
object NoRSSUrl : Error()
|
||||
}
|
||||
|
||||
val isFeedNameError
|
||||
get() = feedNameError != null
|
||||
|
||||
val isFeedUrlError
|
||||
get() = feedUrlError != null
|
||||
|
||||
fun errorText(error: Error?): String = when (error) {
|
||||
Error.BadUrl -> "Input is not a valid URL"
|
||||
Error.EmptyField -> "Field can't be empty"
|
||||
Error.NoRSSUrl -> "The provided URL is not a valid RSS feed"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
val isFeedUrlReadOnly: Boolean
|
||||
get() = accountType != null && !accountType.accountConfig!!.isFeedUrlEditable
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ import com.readrops.app.compose.R
|
|||
import com.readrops.app.compose.feeds.dialogs.AddFeedDialog
|
||||
import com.readrops.app.compose.feeds.dialogs.DeleteFeedDialog
|
||||
import com.readrops.app.compose.feeds.dialogs.FeedModalBottomSheet
|
||||
import com.readrops.app.compose.feeds.dialogs.UpdateFeedDialog
|
||||
import com.readrops.app.compose.util.components.Placeholder
|
||||
import com.readrops.db.entities.Feed
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
@ -84,13 +85,19 @@ object FeedTab : Tab {
|
|||
uriHandler.openUri(dialog.feed.siteUrl!!)
|
||||
viewModel.closeDialog()
|
||||
},
|
||||
onModify = { },
|
||||
onUpdate = { viewModel.openDialog(DialogState.UpdateFeed(dialog.feed, dialog.folder)) },
|
||||
onUpdateColor = {},
|
||||
onDelete = { viewModel.openDialog(DialogState.DeleteFeed(dialog.feed)) },
|
||||
)
|
||||
}
|
||||
|
||||
is DialogState.UpdateFeed -> {}
|
||||
is DialogState.UpdateFeed -> {
|
||||
UpdateFeedDialog(
|
||||
viewModel = viewModel,
|
||||
onDismissRequest = { viewModel.closeDialog() }
|
||||
)
|
||||
}
|
||||
|
||||
DialogState.AddFolder -> {}
|
||||
is DialogState.DeleteFolder -> {}
|
||||
is DialogState.UpdateFolder -> {}
|
||||
|
|
|
@ -8,12 +8,12 @@ import com.readrops.app.compose.base.TabViewModel
|
|||
import com.readrops.app.compose.repositories.GetFoldersWithFeeds
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -32,9 +32,12 @@ class FeedViewModel(
|
|||
private val _addFeedDialogState = MutableStateFlow(AddFeedDialogState())
|
||||
val addFeedDialogState = _addFeedDialogState.asStateFlow()
|
||||
|
||||
private val _updateFeedDialogState = MutableStateFlow(UpdateFeedDialogState())
|
||||
val updateFeedDialogState = _updateFeedDialogState.asStateFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch(context = Dispatchers.IO) {
|
||||
accountEvent.consumeAsFlow()
|
||||
accountEvent
|
||||
.flatMapConcat { account ->
|
||||
getFoldersWithFeeds.get(account.id)
|
||||
}
|
||||
|
@ -62,11 +65,39 @@ class FeedViewModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch(context = Dispatchers.IO) {
|
||||
accountEvent
|
||||
.flatMapConcat { account ->
|
||||
database.newFolderDao()
|
||||
.selectFolders(account.id)
|
||||
}
|
||||
.collect { folders ->
|
||||
_updateFeedDialogState.update {
|
||||
it.copy(
|
||||
folders = folders,
|
||||
accountType = currentAccount!!.accountType
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun closeDialog() = _feedState.update { it.copy(dialog = null) }
|
||||
|
||||
fun openDialog(state: DialogState) = _feedState.update { it.copy(dialog = state) }
|
||||
fun openDialog(state: DialogState) {
|
||||
if (state is DialogState.UpdateFeed) {
|
||||
_updateFeedDialogState.update {
|
||||
it.copy(
|
||||
feedName = state.feed.name!!,
|
||||
feedUrl = state.feed.url!!,
|
||||
selectedFolder = state.folder
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_feedState.update { it.copy(dialog = state) }
|
||||
}
|
||||
|
||||
fun deleteFeed(feed: Feed) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
@ -74,6 +105,8 @@ class FeedViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
// Add feed
|
||||
|
||||
fun setAddFeedDialogURL(url: String) {
|
||||
_addFeedDialogState.update {
|
||||
it.copy(
|
||||
|
@ -94,47 +127,41 @@ class FeedViewModel(
|
|||
fun addFeedDialogValidate() {
|
||||
val url = _addFeedDialogState.value.url
|
||||
|
||||
if (url.isEmpty()) {
|
||||
_addFeedDialogState.update {
|
||||
it.copy(
|
||||
error = AddFeedDialogState.AddFeedError.EmptyUrl
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
} else if (!Patterns.WEB_URL.matcher(url).matches()) {
|
||||
_addFeedDialogState.update {
|
||||
it.copy(
|
||||
error = AddFeedDialogState.AddFeedError.BadUrl
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (localRSSDataSource.isUrlRSSResource(url)) {
|
||||
// TODO add support for all account types
|
||||
repository?.insertNewFeeds(listOf(url))
|
||||
|
||||
when {
|
||||
url.isEmpty() -> {
|
||||
_addFeedDialogState.update {
|
||||
it.copy(closeDialog = true)
|
||||
it.copy(error = AddFeedDialogState.AddFeedError.EmptyUrl)
|
||||
}
|
||||
} else {
|
||||
val rssUrls = HtmlParser.getFeedLink(url, get())
|
||||
|
||||
if (rssUrls.isEmpty()) {
|
||||
_addFeedDialogState.update {
|
||||
it.copy(
|
||||
error = AddFeedDialogState.AddFeedError.NoRSSFeed
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
!Patterns.WEB_URL.matcher(url).matches() -> {
|
||||
_addFeedDialogState.update {
|
||||
it.copy(error = AddFeedDialogState.AddFeedError.BadUrl)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
else -> viewModelScope.launch(Dispatchers.IO) {
|
||||
if (localRSSDataSource.isUrlRSSResource(url)) {
|
||||
// TODO add support for all account types
|
||||
repository?.insertNewFeeds(rssUrls.map { it.url })
|
||||
repository?.insertNewFeeds(listOf(url))
|
||||
|
||||
_addFeedDialogState.update {
|
||||
it.copy(closeDialog = true)
|
||||
closeDialog()
|
||||
} else {
|
||||
val rssUrls = HtmlParser.getFeedLink(url, get())
|
||||
|
||||
if (rssUrls.isEmpty()) {
|
||||
_addFeedDialogState.update {
|
||||
it.copy(error = AddFeedDialogState.AddFeedError.NoRSSFeed)
|
||||
}
|
||||
} else {
|
||||
// TODO add support for all account types
|
||||
repository?.insertNewFeeds(rssUrls.map { it.url })
|
||||
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,9 +173,82 @@ class FeedViewModel(
|
|||
it.copy(
|
||||
url = "",
|
||||
error = null,
|
||||
closeDialog = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// add feed
|
||||
|
||||
// update feed
|
||||
|
||||
fun setAccountDropDownState(isExpanded: Boolean) {
|
||||
_updateFeedDialogState.update {
|
||||
it.copy(isAccountDropDownExpanded = isExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelectedFolder(folder: Folder) {
|
||||
_updateFeedDialogState.update {
|
||||
it.copy(selectedFolder = folder)
|
||||
}
|
||||
}
|
||||
|
||||
fun setUpdateFeedDialogStateFeedName(feedName: String) {
|
||||
_updateFeedDialogState.update {
|
||||
it.copy(
|
||||
feedName = feedName,
|
||||
feedNameError = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setUpdateFeedDialogFeedUrl(feedUrl: String) {
|
||||
_updateFeedDialogState.update {
|
||||
it.copy(
|
||||
feedUrl = feedUrl,
|
||||
feedUrlError = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateFeedDialogValidate() {
|
||||
val feedName = _updateFeedDialogState.value.feedName
|
||||
val feedUrl = _updateFeedDialogState.value.feedUrl
|
||||
|
||||
when {
|
||||
feedName.isEmpty() -> {
|
||||
_updateFeedDialogState.update {
|
||||
it.copy(feedNameError = UpdateFeedDialogState.Error.EmptyField)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
feedUrl.isEmpty() -> {
|
||||
_updateFeedDialogState.update {
|
||||
it.copy(feedUrlError = UpdateFeedDialogState.Error.EmptyField)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
!Patterns.WEB_URL.matcher(feedUrl).matches() -> {
|
||||
_updateFeedDialogState.update {
|
||||
it.copy(feedUrlError = UpdateFeedDialogState.Error.BadUrl)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
else -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// TODO add logig to update feed
|
||||
//repository?.updateFeed()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// update feed
|
||||
}
|
||||
|
||||
|
|
|
@ -48,10 +48,6 @@ fun AddFeedDialog(
|
|||
|
||||
var isExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
if (state.closeDialog) {
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss
|
||||
) {
|
||||
|
@ -100,7 +96,7 @@ fun AddFeedDialog(
|
|||
}
|
||||
}
|
||||
},
|
||||
isError = state.isError(),
|
||||
isError = state.isError,
|
||||
supportingText = { Text(state.errorText) }
|
||||
)
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ fun FeedModalBottomSheet(
|
|||
folder: Folder?,
|
||||
onDismissRequest: () -> Unit,
|
||||
onOpen: () -> Unit,
|
||||
onModify: () -> Unit,
|
||||
onUpdate: () -> Unit,
|
||||
onUpdateColor: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
) {
|
||||
|
@ -98,9 +98,9 @@ fun FeedModalBottomSheet(
|
|||
)
|
||||
|
||||
BottomSheetOption(
|
||||
text = "Modify",
|
||||
text = "Update",
|
||||
icon = Icons.Default.Create,
|
||||
onClick = onModify
|
||||
onClick = onUpdate
|
||||
)
|
||||
|
||||
BottomSheetOption(
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package com.readrops.app.compose.feeds.dialogs
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.readrops.app.compose.R
|
||||
import com.readrops.app.compose.feeds.FeedViewModel
|
||||
import com.readrops.app.compose.util.theme.LargeSpacer
|
||||
import com.readrops.app.compose.util.theme.MediumSpacer
|
||||
import com.readrops.app.compose.util.theme.spacing
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun UpdateFeedDialog(
|
||||
viewModel: FeedViewModel,
|
||||
onDismissRequest: () -> Unit
|
||||
) {
|
||||
val state by viewModel.updateFeedDialogState.collectAsStateWithLifecycle()
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest
|
||||
) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(MaterialTheme.spacing.largeSpacing)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_rss_feed_grey),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(MaterialTheme.spacing.largeSpacing)
|
||||
)
|
||||
|
||||
MediumSpacer()
|
||||
|
||||
Text(
|
||||
text = "Update feed",
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
|
||||
MediumSpacer()
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.feedName,
|
||||
onValueChange = { viewModel.setUpdateFeedDialogStateFeedName(it) },
|
||||
label = { Text(text = "Feed name") },
|
||||
singleLine = true,
|
||||
isError = state.isFeedNameError,
|
||||
supportingText = {
|
||||
if (state.isFeedNameError) {
|
||||
Text(
|
||||
text = state.errorText(state.feedNameError)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
MediumSpacer()
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.feedUrl,
|
||||
onValueChange = { viewModel.setUpdateFeedDialogFeedUrl(it) },
|
||||
label = { Text(text = "Feed URL") },
|
||||
singleLine = true,
|
||||
readOnly = state.isFeedUrlReadOnly,
|
||||
isError = state.isFeedUrlError,
|
||||
supportingText = {
|
||||
if (state.isFeedUrlError) {
|
||||
Text(
|
||||
text = state.errorText(state.feedUrlError)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
MediumSpacer()
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = state.isAccountDropDownExpanded,
|
||||
onExpandedChange = { viewModel.setAccountDropDownState(state.isAccountDropDownExpanded.not()) }
|
||||
) {
|
||||
ExposedDropdownMenu(
|
||||
expanded = state.isAccountDropDownExpanded,
|
||||
onDismissRequest = { viewModel.setAccountDropDownState(false) }
|
||||
) {
|
||||
for (folder in state.folders) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = folder.name!!) },
|
||||
onClick = {
|
||||
viewModel.setSelectedFolder(folder)
|
||||
viewModel.setAccountDropDownState(false)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_folder_grey),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = state.selectedFolder?.name.orEmpty(),
|
||||
readOnly = true,
|
||||
onValueChange = {},
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(expanded = state.isAccountDropDownExpanded)
|
||||
},
|
||||
leadingIcon = {
|
||||
if (state.selectedFolder != null) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_folder_grey),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.menuAnchor()
|
||||
)
|
||||
}
|
||||
|
||||
LargeSpacer()
|
||||
|
||||
TextButton(
|
||||
onClick = { viewModel.updateFeedDialogValidate() },
|
||||
) {
|
||||
Text(text = "Validate")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,8 @@ class GetFoldersWithFeeds(
|
|||
id = it.feedId,
|
||||
name = it.feedName,
|
||||
iconUrl = it.feedIcon,
|
||||
siteUrl = it.feedUrl,
|
||||
url = it.feedUrl,
|
||||
siteUrl = it.feedSiteUrl,
|
||||
unreadCount = it.unreadCount
|
||||
)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -44,7 +43,7 @@ class TimelineViewModel(
|
|||
init {
|
||||
viewModelScope.launch(dispatcher) {
|
||||
combine(
|
||||
accountEvent.consumeAsFlow(),
|
||||
accountEvent,
|
||||
filters
|
||||
) { account, filters ->
|
||||
filters.accountId = account.id
|
||||
|
|
|
@ -10,9 +10,12 @@ import kotlinx.coroutines.flow.Flow
|
|||
abstract class NewFolderDao : NewBaseDao<Folder> {
|
||||
|
||||
@Query("Select Folder.id As folderId, Folder.name as folderName, Feed.id As feedId, Feed.name AS feedName, " +
|
||||
"Feed.icon_url As feedIcon, Feed.siteUrl as feedUrl, count(*) As unreadCount From Folder Left Join Feed " +
|
||||
"Feed.icon_url As feedIcon, Feed.url as feedUrl, Feed.siteUrl as feedSiteUrl, count(*) As unreadCount From Folder Left Join Feed " +
|
||||
"On Folder.id = Feed.folder_id Left Join Item On Item.feed_id = Feed.id " +
|
||||
"Where Feed.folder_id is NULL OR Feed.folder_id is NOT NULL And Item.read = 0 " +
|
||||
"And Feed.account_id = :accountId Group By Feed.id, Folder.id Order By Folder.id")
|
||||
abstract fun selectFoldersAndFeeds(accountId: Int): Flow<List<FolderWithFeed>>
|
||||
|
||||
@Query("Select * From Folder Where account_id = :accountId")
|
||||
abstract fun selectFolders(accountId: Int): Flow<List<Folder>>
|
||||
}
|
|
@ -19,6 +19,7 @@ data class FolderWithFeed(
|
|||
val feedName: String? = null,
|
||||
val feedIcon: String? = null,
|
||||
val feedUrl: String? = null,
|
||||
val feedSiteUrl: String? = null,
|
||||
val unreadCount: Int = 0
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue