mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-14 21:07:04 +05:30
Update LobstersCard
UI (#296)
This commit is contained in:
parent
93b013515c
commit
0db4e48613
5 changed files with 81 additions and 87 deletions
|
@ -5,18 +5,20 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
@ -57,6 +59,7 @@ fun LobstersApp(
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val postActions = rememberPostActions(urlLauncher, navController, viewModel)
|
val postActions = rememberPostActions(urlLauncher, navController, viewModel)
|
||||||
val currentDestination by currentNavigationDestination(navController)
|
val currentDestination by currentNavigationDestination(navController)
|
||||||
|
val scrollBehavior = remember { TopAppBarDefaults.enterAlwaysScrollBehavior() }
|
||||||
|
|
||||||
val networkPosts = viewModel.pagerFlow.collectAsLazyPagingItems()
|
val networkPosts = viewModel.pagerFlow.collectAsLazyPagingItems()
|
||||||
val savedPosts by viewModel.savedPosts.collectAsState(emptyList())
|
val savedPosts by viewModel.savedPosts.collectAsState(emptyList())
|
||||||
|
@ -98,10 +101,12 @@ fun LobstersApp(
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
topBar = {
|
topBar = {
|
||||||
ClawAppBar(
|
ClawAppBar(
|
||||||
backgroundColor = systemBarsColor,
|
|
||||||
modifier = Modifier.statusBarsPadding(),
|
modifier = Modifier.statusBarsPadding(),
|
||||||
|
backgroundColor = systemBarsColor,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
|
@ -115,7 +120,6 @@ fun LobstersApp(
|
||||||
NavHost(
|
NavHost(
|
||||||
navController,
|
navController,
|
||||||
startDestination = Destinations.startDestination.getRoute(),
|
startDestination = Destinations.startDestination.getRoute(),
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
|
||||||
) {
|
) {
|
||||||
composable(Destinations.Hottest.getRoute()) {
|
composable(Destinations.Hottest.getRoute()) {
|
||||||
setWebUri("https://lobste.rs/")
|
setWebUri("https://lobste.rs/")
|
||||||
|
|
|
@ -3,6 +3,7 @@ package dev.msfjarvis.claw.android.ui.decorations
|
||||||
import androidx.compose.material3.SmallTopAppBar
|
import androidx.compose.material3.SmallTopAppBar
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
@ -13,6 +14,7 @@ import dev.msfjarvis.claw.android.R
|
||||||
@Composable
|
@Composable
|
||||||
fun ClawAppBar(
|
fun ClawAppBar(
|
||||||
backgroundColor: Color,
|
backgroundColor: Color,
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
SmallTopAppBar(
|
SmallTopAppBar(
|
||||||
|
@ -24,5 +26,6 @@ fun ClawAppBar(
|
||||||
},
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = backgroundColor),
|
colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = backgroundColor),
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package dev.msfjarvis.claw.android.ui.lists
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
|
@ -31,6 +32,8 @@ fun NetworkPosts(
|
||||||
isSaved = isSaved,
|
isSaved = isSaved,
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Divider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import com.halilibo.richtext.markdown.Markdown
|
||||||
import com.halilibo.richtext.ui.RichTextScope
|
import com.halilibo.richtext.ui.RichTextScope
|
||||||
import com.halilibo.richtext.ui.material.MaterialRichText
|
import com.halilibo.richtext.ui.material.MaterialRichText
|
||||||
import dev.msfjarvis.claw.common.posts.PostDetails
|
import dev.msfjarvis.claw.common.posts.PostDetails
|
||||||
import dev.msfjarvis.claw.common.posts.SubmitterName
|
import dev.msfjarvis.claw.common.posts.Submitter
|
||||||
import dev.msfjarvis.claw.common.posts.toDbModel
|
import dev.msfjarvis.claw.common.posts.toDbModel
|
||||||
import dev.msfjarvis.claw.model.Comment
|
import dev.msfjarvis.claw.model.Comment
|
||||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
import dev.msfjarvis.claw.model.LobstersPostDetails
|
||||||
|
@ -70,7 +70,7 @@ fun CommentEntry(
|
||||||
bottom = 4.dp
|
bottom = 4.dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
SubmitterName(
|
Submitter(
|
||||||
text = comment.user.username,
|
text = comment.user.username,
|
||||||
avatarUrl = "https://lobste.rs/${comment.user.avatarUrl}",
|
avatarUrl = "https://lobste.rs/${comment.user.avatarUrl}",
|
||||||
contentDescription = "User avatar for ${comment.user.username}",
|
contentDescription = "User avatar for ${comment.user.username}",
|
||||||
|
|
|
@ -5,17 +5,21 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredSize
|
import androidx.compose.foundation.layout.requiredSize
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
@ -27,6 +31,7 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
|
@ -46,32 +51,39 @@ fun LobstersCard(
|
||||||
) {
|
) {
|
||||||
var localSavedState by remember(post, isSaved) { mutableStateOf(isSaved) }
|
var localSavedState by remember(post, isSaved) { mutableStateOf(isSaved) }
|
||||||
Box(modifier = modifier.clickable { postActions.viewPost(post.url, post.commentsUrl) }) {
|
Box(modifier = modifier.clickable { postActions.viewPost(post.url, post.commentsUrl) }) {
|
||||||
Column(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp).fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
PostDetails(
|
PostDetails(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
post = post,
|
post = post,
|
||||||
)
|
)
|
||||||
Row(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
modifier = Modifier.weight(0.15f).fillMaxHeight(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
SaveButton(
|
SaveButton(
|
||||||
isSaved = localSavedState,
|
isSaved = localSavedState,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.clickable {
|
Modifier.clickable(
|
||||||
|
role = Role.Button,
|
||||||
|
indication = rememberRipple(bounded = false, radius = 24.dp),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
) {
|
||||||
localSavedState = !localSavedState
|
localSavedState = !localSavedState
|
||||||
postActions.toggleSave(post)
|
postActions.toggleSave(post)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Spacer(
|
Divider()
|
||||||
modifier = Modifier.width(8.dp),
|
|
||||||
)
|
|
||||||
CommentsButton(
|
CommentsButton(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.combinedClickable(
|
Modifier.combinedClickable(
|
||||||
|
role = Role.Button,
|
||||||
|
indication = rememberRipple(bounded = false, radius = 24.dp),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
onClick = { postActions.viewComments(post.shortId) },
|
onClick = { postActions.viewComments(post.shortId) },
|
||||||
onLongClick = { postActions.viewCommentsPage(post.commentsUrl) },
|
onLongClick = { postActions.viewCommentsPage(post.commentsUrl) },
|
||||||
),
|
),
|
||||||
|
@ -82,22 +94,17 @@ fun LobstersCard(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PostDetails(
|
fun PostDetails(post: SavedPost, modifier: Modifier = Modifier) {
|
||||||
post: SavedPost,
|
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
) {
|
PostTitle(title = post.title)
|
||||||
PostTitle(
|
TagRow(tags = post.tags)
|
||||||
title = post.title,
|
Spacer(Modifier.height(4.dp))
|
||||||
modifier = Modifier.padding(bottom = 4.dp),
|
Submitter(
|
||||||
)
|
text = "Submitted by ${post.submitterName}",
|
||||||
TagRow(
|
avatarUrl = "https://lobste.rs/${post.submitterAvatarUrl}",
|
||||||
tags = post.tags,
|
contentDescription = "User avatar for ${post.submitterName}",
|
||||||
modifier = Modifier.padding(bottom = 4.dp),
|
)
|
||||||
)
|
}
|
||||||
SubmitterName(
|
|
||||||
text = "Submitted by ${post.submitterName}",
|
|
||||||
avatarUrl = "https://lobste.rs/${post.submitterAvatarUrl}",
|
|
||||||
contentDescription = "User avatar for ${post.submitterName}",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -107,13 +114,14 @@ fun PostTitle(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SubmitterName(
|
fun Submitter(
|
||||||
text: String,
|
text: String,
|
||||||
avatarUrl: String,
|
avatarUrl: String,
|
||||||
contentDescription: String,
|
contentDescription: String,
|
||||||
|
@ -122,42 +130,18 @@ fun SubmitterName(
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
) {
|
||||||
SubmitterAvatar(
|
NetworkImage(
|
||||||
avatarUrl = avatarUrl,
|
url = avatarUrl,
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
|
modifier = modifier.requiredSize(24.dp).clip(CircleShape),
|
||||||
)
|
)
|
||||||
SubmitterNameText(
|
|
||||||
text = text,
|
Text(text = text, modifier = modifier, style = MaterialTheme.typography.bodyMedium)
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SubmitterAvatar(
|
|
||||||
avatarUrl: String,
|
|
||||||
contentDescription: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
NetworkImage(
|
|
||||||
url = avatarUrl,
|
|
||||||
contentDescription = contentDescription,
|
|
||||||
modifier = modifier.requiredSize(24.dp).clip(CircleShape),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SubmitterNameText(
|
|
||||||
text: String,
|
|
||||||
modifier: Modifier,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SaveButton(
|
fun SaveButton(
|
||||||
isSaved: Boolean,
|
isSaved: Boolean,
|
||||||
|
@ -168,7 +152,7 @@ fun SaveButton(
|
||||||
painter = if (saved) heartIcon else heartBorderIcon,
|
painter = if (saved) heartIcon else heartBorderIcon,
|
||||||
tint = MaterialTheme.colorScheme.secondary,
|
tint = MaterialTheme.colorScheme.secondary,
|
||||||
contentDescription = if (saved) "Remove from saved posts" else "Add to saved posts",
|
contentDescription = if (saved) "Remove from saved posts" else "Add to saved posts",
|
||||||
modifier = modifier,
|
modifier = modifier.padding(12.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,7 +165,7 @@ fun CommentsButton(
|
||||||
painter = commentIcon,
|
painter = commentIcon,
|
||||||
tint = MaterialTheme.colorScheme.secondary,
|
tint = MaterialTheme.colorScheme.secondary,
|
||||||
contentDescription = "Open comments",
|
contentDescription = "Open comments",
|
||||||
modifier = modifier,
|
modifier = modifier.padding(12.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,25 +174,25 @@ fun TagRow(
|
||||||
tags: List<String>,
|
tags: List<String>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Box(
|
FlowRow(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
mainAxisSpacing = 8.dp,
|
||||||
FlowRow(
|
crossAxisSpacing = 8.dp,
|
||||||
mainAxisSpacing = 8.dp,
|
) { tags.forEach { tag -> TagText(tag) } }
|
||||||
crossAxisSpacing = 8.dp,
|
}
|
||||||
) {
|
|
||||||
tags.forEach { tag ->
|
@Composable
|
||||||
Text(
|
fun TagText(
|
||||||
text = tag,
|
tag: String,
|
||||||
modifier =
|
modifier: Modifier = Modifier,
|
||||||
Modifier.background(
|
) {
|
||||||
MaterialTheme.colorScheme.secondary.copy(alpha = 0.75f),
|
Text(
|
||||||
RoundedCornerShape(8.dp)
|
text = tag,
|
||||||
)
|
modifier =
|
||||||
.padding(vertical = 2.dp, horizontal = 6.dp),
|
Modifier.background(MaterialTheme.colorScheme.tertiaryContainer, RoundedCornerShape(50))
|
||||||
color = MaterialTheme.colorScheme.onSecondary,
|
.padding(vertical = 4.dp, horizontal = 12.dp)
|
||||||
)
|
.then(modifier),
|
||||||
}
|
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
}
|
style = MaterialTheme.typography.labelLarge
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue