Show feeds as sub items of folders in FeedTab

This commit is contained in:
Shinokuni 2024-01-12 19:28:25 +01:00
parent 6b50d40800
commit 5cd7ead78f
7 changed files with 154 additions and 23 deletions

View File

@ -15,7 +15,7 @@ val composeAppModule = module {
viewModel { TimelineViewModel(get(), get()) }
viewModel { FeedViewModel(get()) }
viewModel { FeedViewModel(get(), get()) }
viewModel { AccountSelectionViewModel(get()) }

View File

@ -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 -> {

View File

@ -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()
}

View File

@ -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) },
)
}
}
}
}
}

View File

@ -24,6 +24,7 @@ class GetFoldersWithFeeds(
id = it.feedId,
name = it.feedName,
iconUrl = it.feedIcon,
siteUrl = it.feedUrl,
unreadCount = it.unreadCount
)
}

View File

@ -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")

View File

@ -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
)