Skip to content

Commit

Permalink
Show unread posts count in groups (#463)
Browse files Browse the repository at this point in the history
* Update source queries to include number of unread posts count for groups

* Show unread posts badge for feed group items
  • Loading branch information
msasikanth authored Apr 18, 2024
1 parent ddaf1ba commit 8659691
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ data class FeedGroup(
val name: String,
val feedIds: List<String>,
val feedIcons: List<String>,
val numberOfUnreadPosts: Long = 0,
val createdAt: Instant,
val updatedAt: Instant,
override val pinnedAt: Instant?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ internal fun BottomSheetCollapsedContent(
is FeedGroup -> {
FeedGroupBottomBarItem(
feedGroup = source,
canShowUnreadPostsCount = canShowUnreadPostsCount,
selected = activeSource?.id == source.id,
onClick = { onSourceClick(source) }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,18 +498,19 @@ private fun LazyGridScope.allSources(
when (source) {
is FeedGroup -> {
FeedGroupItem(
feedGroup = source,
canShowUnreadPostsCount = canShowUnreadPostsCount,
isInMultiSelectMode = isInMultiSelectMode,
selected = selectedSources.contains(source),
onFeedGroupSelected = onToggleSourceSelection,
onFeedGroupClick = onSourceClick,
modifier =
Modifier.padding(
start = startPadding,
top = topPadding,
end = endPadding,
bottom = bottomPadding
),
feedGroup = source,
isInMultiSelectMode = isInMultiSelectMode,
selected = selectedSources.contains(source),
onFeedGroupSelected = onToggleSourceSelection,
onFeedGroupClick = onSourceClick
)
}
is Feed -> {
Expand Down Expand Up @@ -582,18 +583,19 @@ private fun LazyGridScope.pinnedSources(
when (source) {
is FeedGroup -> {
FeedGroupItem(
feedGroup = source,
canShowUnreadPostsCount = canShowUnreadPostsCount,
isInMultiSelectMode = isInMultiSelectMode,
selected = selectedSources.contains(source),
onFeedGroupSelected = onToggleSourceSelection,
onFeedGroupClick = onSourceClick,
modifier =
Modifier.padding(
start = startPadding,
top = topPadding,
end = endPadding,
bottom = bottomPadding
),
feedGroup = source,
isInMultiSelectMode = isInMultiSelectMode,
selected = selectedSources.contains(source),
onFeedGroupSelected = onToggleSourceSelection,
onFeedGroupClick = onSourceClick
)
}
is Feed -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import dev.sasikanth.rss.reader.components.image.AsyncImage
import dev.sasikanth.rss.reader.ui.AppTheme

private const val BADGE_COUNT_TRIM_LIMIT = 99
import dev.sasikanth.rss.reader.utils.Constants.BADGE_COUNT_TRIM_LIMIT

@Composable
internal fun FeedBottomBarItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,55 +21,89 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Badge
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import dev.sasikanth.rss.reader.core.model.local.FeedGroup
import dev.sasikanth.rss.reader.ui.AppTheme
import dev.sasikanth.rss.reader.utils.Constants.BADGE_COUNT_TRIM_LIMIT

@Composable
internal fun FeedGroupBottomBarItem(
feedGroup: FeedGroup,
canShowUnreadPostsCount: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
selected: Boolean = false,
) {
Box(modifier = modifier, contentAlignment = Alignment.Center) {
SelectionIndicator(selected = selected, animationProgress = 1f)
Box(
modifier =
Modifier.requiredSize(56.dp)
.clip(RoundedCornerShape(16.dp))
.clickable { onClick() }
.background(AppTheme.colorScheme.tintedSurface)
.padding(8.dp),
contentAlignment = Alignment.Center
) {
val iconSize =
if (feedGroup.feedIcons.size > 2) {
18.dp
} else {
20.dp
}
Box(modifier = modifier) {
Box(contentAlignment = Alignment.Center) {
SelectionIndicator(selected = selected, animationProgress = 1f)
Box(
modifier =
Modifier.requiredSize(56.dp)
.clip(RoundedCornerShape(16.dp))
.clickable { onClick() }
.background(AppTheme.colorScheme.tintedSurface)
.padding(8.dp),
contentAlignment = Alignment.Center
) {
val iconSize =
if (feedGroup.feedIcons.size > 2) {
18.dp
} else {
20.dp
}

val iconSpacing =
if (feedGroup.feedIcons.size > 2) {
4.dp
} else {
0.dp
}
val iconSpacing =
if (feedGroup.feedIcons.size > 2) {
4.dp
} else {
0.dp
}

FeedGroupIconGrid(
icons = feedGroup.feedIcons,
iconSize = iconSize,
iconShape = CircleShape,
verticalArrangement = Arrangement.spacedBy(iconSpacing),
horizontalArrangement = Arrangement.spacedBy(iconSpacing),
)
FeedGroupIconGrid(
icons = feedGroup.feedIcons,
iconSize = iconSize,
iconShape = CircleShape,
verticalArrangement = Arrangement.spacedBy(iconSpacing),
horizontalArrangement = Arrangement.spacedBy(iconSpacing),
)
}
}

val badgeCount = feedGroup.numberOfUnreadPosts
if (badgeCount > 0 && canShowUnreadPostsCount) {
Badge(
containerColor = AppTheme.colorScheme.tintedForeground,
contentColor = AppTheme.colorScheme.tintedBackground,
modifier = Modifier.sizeIn(minWidth = 24.dp, minHeight = 16.dp).align(Alignment.TopEnd),
) {
val badgeText =
if (badgeCount > BADGE_COUNT_TRIM_LIMIT) {
"+$BADGE_COUNT_TRIM_LIMIT"
} else {
badgeCount.toString()
}

Text(
text = badgeText,
style = MaterialTheme.typography.labelSmall,
modifier =
Modifier.align(Alignment.CenterVertically).graphicsLayer {
translationY = -2.toDp().toPx()
}
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Badge
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
Expand All @@ -51,6 +53,7 @@ import dev.sasikanth.rss.reader.ui.AppTheme
@Composable
internal fun FeedGroupItem(
feedGroup: FeedGroup,
canShowUnreadPostsCount: Boolean,
isInMultiSelectMode: Boolean,
selected: Boolean,
onFeedGroupSelected: (FeedGroup) -> Unit,
Expand Down Expand Up @@ -140,6 +143,21 @@ internal fun FeedGroupItem(

Spacer(Modifier.requiredWidth(4.dp))

val numberOfUnreadPosts = feedGroup.numberOfUnreadPosts
if (canShowUnreadPostsCount && numberOfUnreadPosts > 0 && !isInMultiSelectMode) {
Badge(
containerColor = AppTheme.colorScheme.tintedForeground,
contentColor = AppTheme.colorScheme.tintedBackground,
modifier = Modifier.sizeIn(minWidth = 24.dp, minHeight = 16.dp)
) {
Text(
text = feedGroup.numberOfUnreadPosts.toString(),
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.align(Alignment.CenterVertically)
)
}
}

if (isInMultiSelectMode) {
val icon =
if (selected) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ fun GroupSelectionSheet(presenter: GroupSelectionPresenter, modifier: Modifier =
if (group != null) {
FeedGroupItem(
feedGroup = group,
canShowUnreadPostsCount = false,
isInMultiSelectMode = true,
selected = state.selectedGroups.contains(group.id),
onFeedGroupSelected = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ class RssRepository(
createdAt = createdAt!!,
updatedAt = updatedAt!!,
pinnedAt = pinnedAt,
numberOfUnreadPosts = numberOfUnreadPosts,
)
} else {
Feed(
Expand Down Expand Up @@ -641,6 +642,7 @@ class RssRepository(
createdAt = createdAt,
updatedAt = updatedAt!!,
pinnedAt = pinnedAt,
numberOfUnreadPosts = numberOfUnreadPosts,
)
} else {
Feed(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ internal object Constants {
const val OPEN_SOURCE_LINK = "https://github.com/sponsors/msasikanth"

const val MINIMUM_REQUIRED_SEARCH_CHARACTERS = 3

const val BADGE_COUNT_TRIM_LIMIT = 99
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ FROM (
NULL AS link,
NULL AS homepageLink,
NULL AS lastCleanUpAt,
0 AS numberOfUnreadPosts,
COUNT(CASE WHEN p.read == 0 THEN 1 ELSE NULL END) AS numberOfUnreadPosts,
fg.feedIds,
COALESCE((SELECT GROUP_CONCAT(feed.icon)
FROM feed
Expand All @@ -75,6 +75,8 @@ FROM (
fg.pinnedAt,
fg.updatedAt
FROM feedGroup fg
LEFT JOIN post p ON INSTR(fg.feedIds, p.sourceId) AND p.date > :postsAfter
GROUP BY fg.id
)
WHERE pinnedAt IS NOT NULL
ORDER BY pinnedAt DESC
Expand Down Expand Up @@ -127,7 +129,7 @@ FROM (
NULL AS link,
NULL AS homepageLink,
NULL AS lastCleanUpAt,
0 AS numberOfUnreadPosts,
COUNT(CASE WHEN p.read == 0 THEN 1 ELSE NULL END) AS numberOfUnreadPosts,
fg.feedIds,
COALESCE((SELECT GROUP_CONCAT(feed.icon)
FROM feed
Expand All @@ -137,6 +139,8 @@ FROM (
fg.pinnedAt,
fg.updatedAt
FROM feedGroup fg
LEFT JOIN post p ON INSTR(fg.feedIds, p.sourceId) AND p.date > :postsAfter
GROUP BY fg.id
)
ORDER BY type DESC,
CASE WHEN :orderBy = 'latest' THEN createdAt END DESC,
Expand Down

0 comments on commit 8659691

Please sign in to comment.