Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions app/src/main/java/com/capyreader/app/ui/articles/ArticleScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material3.DrawerValue
Expand Down Expand Up @@ -465,7 +469,6 @@ fun ArticleScreen(
},
onFilterSelect = selectFilter,
onSelectToday = { selectToday() },
onSelectStatus = { selectStatus(it) },
refreshState = refreshAllState,
onRefresh = {
refreshAll()
Expand Down Expand Up @@ -541,13 +544,24 @@ fun ArticleScreen(
}
},
bottomBar = {
audioEnclosure?.let { audio ->
FloatingAudioPlayer(
audio = audio,
controller = audioController,
onDismiss = {
audioController.dismiss()
},
Column {
audioEnclosure?.let { audio ->
FloatingAudioPlayer(
audio = audio,
controller = audioController,
onDismiss = {
audioController.dismiss()
},
// The status bar below is bottom-most, so it owns the
// navigation bar inset the player would otherwise pad for
modifier = Modifier
.consumeWindowInsets(WindowInsets.navigationBars),
)
}

ArticleStatusBottomBar(
status = filter.status,
onSelectStatus = { selectStatus(it) },
)
}
}
Expand Down
137 changes: 109 additions & 28 deletions app/src/main/java/com/capyreader/app/ui/articles/ArticleStatusBar.kt
Original file line number Diff line number Diff line change
@@ -1,56 +1,137 @@
package com.capyreader.app.ui.articles

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.ButtonGroupDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.ToggleButton
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.capyreader.app.ui.navigationTitle
import com.capyreader.app.ui.theme.CapyTheme
import com.jocmp.capy.ArticleStatus

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ArticleStatusBar(
onSelectStatus: (status: ArticleStatus) -> Unit,
status: ArticleStatus,
modifier: Modifier = Modifier,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
options.forEachIndexed { index, buttonStatus ->
ToggleButton(
checked = buttonStatus == status,
onCheckedChange = {
if (buttonStatus != status) {
onSelectStatus(buttonStatus)
}
},
modifier = Modifier.weight(1f),
shapes = when (index) {
0 -> ButtonGroupDefaults.connectedLeadingButtonShapes()
options.lastIndex -> ButtonGroupDefaults.connectedTrailingButtonShapes()
else -> ButtonGroupDefaults.connectedMiddleButtonShapes()
options.forEach { buttonStatus ->
StatusButton(
status = buttonStatus,
selected = buttonStatus == status,
onSelect = { onSelectStatus(buttonStatus) },
)
}
}
}

@Composable
private fun StatusButton(
status: ArticleStatus,
selected: Boolean,
onSelect: () -> Unit,
) {
val label = stringResource(status.navigationTitle)
val colors = MaterialTheme.colorScheme

val containerColor by animateColorAsState(
targetValue = when {
selected -> colors.surfaceContainerHighest
else -> Color.Transparent
},
label = "statusContainer",
)
val contentColor by animateColorAsState(
targetValue = when {
selected -> colors.onSurface
else -> colors.onSurfaceVariant
},
label = "statusContent",
)

Surface(
onClick = {
if (!selected) {
onSelect()
}
},
selected = selected,
shape = CircleShape,
color = containerColor,
contentColor = contentColor,
modifier = Modifier.semantics { contentDescription = label },
) {
Row(
modifier = Modifier
.defaultMinSize(minWidth = 48.dp, minHeight = 40.dp)
.padding(horizontal = 14.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
ArticleStatusIcon(
status = status,
modifier = Modifier.size(iconSize(status)),
)

AnimatedVisibility(visible = selected) {
Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.width(8.dp))
Text(
text = label.uppercase(),
style = MaterialTheme.typography.labelMedium,
letterSpacing = 0.8.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
) {
ArticleStatusIcon(status = buttonStatus)
}
}
}
}

private fun iconSize(status: ArticleStatus) = when (status) {
ArticleStatus.UNREAD -> 12.dp
else -> 20.dp
}

val options = listOf(
ArticleStatus.ALL,
ArticleStatus.UNREAD,
ArticleStatus.STARRED,
ArticleStatus.UNREAD,
ArticleStatus.ALL,
)

@Composable
@Preview
fun ArticleStatusBarPreview() {
ArticleStatusBar(
onSelectStatus = {},
status = ArticleStatus.UNREAD
)
@Composable
private fun ArticleStatusBarPreview() {
CapyTheme {
ArticleStatusBar(
onSelectStatus = {},
status = ArticleStatus.UNREAD,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.capyreader.app.ui.articles

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.capyreader.app.ui.theme.CapyTheme
import com.jocmp.capy.ArticleStatus

@Composable
fun ArticleStatusBottomBar(
status: ArticleStatus,
onSelectStatus: (status: ArticleStatus) -> Unit,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier.fillMaxWidth(),
) {
Row(
modifier = Modifier
.windowInsetsPadding(WindowInsets.navigationBars)
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
ArticleStatusBar(
status = status,
onSelectStatus = onSelectStatus,
)
}
}
}

@Preview
@Composable
private fun ArticleStatusBottomBarPreview() {
CapyTheme {
ArticleStatusBottomBar(
status = ArticleStatus.UNREAD,
onSelectStatus = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ import androidx.compose.material.icons.rounded.FiberManualRecord
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import com.jocmp.capy.ArticleStatus

@Composable
fun ArticleStatusIcon(status: ArticleStatus) {
return when (status) {
ArticleStatus.ALL -> Icon(
Icons.AutoMirrored.Rounded.Notes,
contentDescription = null
)

ArticleStatus.UNREAD -> Icon(
Icons.Rounded.FiberManualRecord,
contentDescription = null,
)
fun ArticleStatusIcon(
status: ArticleStatus,
modifier: Modifier = Modifier,
) {
Icon(
imageVector = status.icon,
contentDescription = null,
modifier = modifier,
)
}

ArticleStatus.STARRED -> Icon(
Icons.Rounded.Star,
contentDescription = null,
)
val ArticleStatus.icon: ImageVector
get() = when (this) {
ArticleStatus.ALL -> Icons.AutoMirrored.Rounded.Notes
ArticleStatus.UNREAD -> Icons.Rounded.FiberManualRecord
ArticleStatus.STARRED -> Icons.Rounded.Star
}
}
Loading
Loading