mirror of https://github.com/readrops/Readrops.git
Show feeds as sub items of folders in FeedTab
This commit is contained in:
parent
6b50d40800
commit
5cd7ead78f
|
@ -15,7 +15,7 @@ val composeAppModule = module {
|
|||
|
||||
viewModel { TimelineViewModel(get(), get()) }
|
||||
|
||||
viewModel { FeedViewModel(get()) }
|
||||
viewModel { FeedViewModel(get(), get()) }
|
||||
|
||||
viewModel { AccountSelectionViewModel(get()) }
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.readrops.app.compose.feeds
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
@ -25,10 +26,13 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.readrops.app.compose.R
|
||||
import com.readrops.app.compose.util.components.Placeholder
|
||||
import com.readrops.db.entities.Feed
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
|
@ -58,7 +62,9 @@ object FeedTab : Tab {
|
|||
FeedModalBottomSheet(
|
||||
feed = selectedFeed!!,
|
||||
onDismissRequest = { showBottomSheet = false },
|
||||
onOpen = { uriHandler.openUri(selectedFeed!!.siteUrl!!) },
|
||||
onOpen = {
|
||||
Log.d("TAG", "Content: ")
|
||||
uriHandler.openUri(selectedFeed!!.siteUrl!!) },
|
||||
onModify = { },
|
||||
onDelete = {},
|
||||
)
|
||||
|
@ -100,20 +106,22 @@ object FeedTab : Tab {
|
|||
) {
|
||||
when (state) {
|
||||
is FeedsState.LoadedState -> {
|
||||
val feeds = (state as FeedsState.LoadedState).feeds
|
||||
val foldersAndFeeds = (state as FeedsState.LoadedState).foldersAndFeeds
|
||||
|
||||
if (feeds.isNotEmpty()) {
|
||||
if (foldersAndFeeds.isNotEmpty()) {
|
||||
LazyColumn {
|
||||
items(
|
||||
items = feeds
|
||||
) { feed ->
|
||||
FeedItem(
|
||||
feed = feed,
|
||||
onClick = {
|
||||
items = foldersAndFeeds.toList()
|
||||
) { folderWithFeeds ->
|
||||
if (folderWithFeeds.first != null) {
|
||||
FolderExpandableItem(
|
||||
folder = folderWithFeeds.first!!,
|
||||
feeds = folderWithFeeds.second,
|
||||
onFeedClick = { feed ->
|
||||
selectedFeed = feed
|
||||
showBottomSheet = true
|
||||
},
|
||||
onLongClick = {
|
||||
onFeedLongClick = { feed ->
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
uriHandler.openUri(feed.siteUrl!!)
|
||||
}
|
||||
|
@ -121,6 +129,12 @@ object FeedTab : Tab {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Placeholder(
|
||||
text = "No feed",
|
||||
painter = painterResource(R.drawable.ic_rss_feed_grey)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is FeedsState.ErrorState -> {
|
||||
|
|
|
@ -2,16 +2,21 @@ package com.readrops.app.compose.feeds
|
|||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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 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.launch
|
||||
|
||||
class FeedViewModel(
|
||||
private val database: Database,
|
||||
database: Database,
|
||||
private val getFoldersWithFeeds: GetFoldersWithFeeds
|
||||
) : TabViewModel(database) {
|
||||
|
||||
private val _feedsState = MutableStateFlow<FeedsState>(FeedsState.InitialState)
|
||||
|
@ -19,7 +24,10 @@ class FeedViewModel(
|
|||
|
||||
init {
|
||||
viewModelScope.launch(context = Dispatchers.IO) {
|
||||
database.newFeedDao().selectFeeds()
|
||||
accountEvent.consumeAsFlow()
|
||||
.flatMapConcat { account ->
|
||||
getFoldersWithFeeds.get(account.id)
|
||||
}
|
||||
.catch { _feedsState.value = FeedsState.ErrorState(Exception(it)) }
|
||||
.collect { _feedsState.value = FeedsState.LoadedState(it) }
|
||||
}
|
||||
|
@ -35,5 +43,5 @@ class FeedViewModel(
|
|||
sealed class FeedsState {
|
||||
object InitialState : FeedsState()
|
||||
data class ErrorState(val exception: Exception) : FeedsState()
|
||||
data class LoadedState(val feeds: List<Feed>) : FeedsState()
|
||||
data class LoadedState(val foldersAndFeeds: Map<Folder?, List<Feed>>) : FeedsState()
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package com.readrops.app.compose.feeds
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.readrops.app.compose.R
|
||||
import com.readrops.app.compose.util.theme.MediumSpacer
|
||||
import com.readrops.app.compose.util.theme.spacing
|
||||
import com.readrops.db.entities.Feed
|
||||
import com.readrops.db.entities.Folder
|
||||
|
||||
@Composable
|
||||
fun FolderExpandableItem(
|
||||
folder: Folder,
|
||||
feeds: List<Feed>,
|
||||
onFeedClick: (Feed) -> Unit,
|
||||
onFeedLongClick: (Feed) -> Unit,
|
||||
) {
|
||||
var isExpanded by remember { mutableStateOf(false) }
|
||||
val rotationState by animateFloatAsState(
|
||||
targetValue = if (isExpanded) 180f else 0f,
|
||||
label = "folder item arrow rotation"
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateContentSize(
|
||||
animationSpec = tween(
|
||||
durationMillis = 300,
|
||||
easing = LinearOutSlowInEasing,
|
||||
)
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable { isExpanded = isExpanded.not() }
|
||||
.padding(
|
||||
horizontal = MaterialTheme.spacing.shortSpacing,
|
||||
vertical = MaterialTheme.spacing.veryShortSpacing,
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_folder_grey),
|
||||
contentDescription = folder.name
|
||||
)
|
||||
|
||||
MediumSpacer()
|
||||
|
||||
Text(
|
||||
text = folder.name!!,
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
}
|
||||
|
||||
Row {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowDropDown,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.rotate(rotationState),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
if (isExpanded) {
|
||||
for (feed in feeds) {
|
||||
FeedItem(
|
||||
feed = feed,
|
||||
onClick = { onFeedClick(feed) },
|
||||
onLongClick = { onFeedLongClick(feed) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ class GetFoldersWithFeeds(
|
|||
id = it.feedId,
|
||||
name = it.feedName,
|
||||
iconUrl = it.feedIcon,
|
||||
siteUrl = it.feedUrl,
|
||||
unreadCount = it.unreadCount
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ 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, count(*) As unreadCount From Folder Left Join Feed " +
|
||||
"Feed.icon_url As feedIcon, Feed.siteUrl as feedUrl, 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")
|
||||
|
|
|
@ -18,6 +18,7 @@ data class FolderWithFeed(
|
|||
val feedId: Int = 0,
|
||||
val feedName: String? = null,
|
||||
val feedIcon: String? = null,
|
||||
val feedUrl: String? = null,
|
||||
val unreadCount: Int = 0
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue