BlocksLiveBrian SJennieJamesRoes
Social Discover
A warm-toned social feed with stories, full-bleed post cards, action counters and a floating compose button.
Me
Discover
Add story
B
J
J
R
12.3k
250
100
NR
Nathan Rusl
@nathanrsl
Nature's beauty is the best remedy for a restless soul. #FindYourPeace
Home
Search
Message
Profile
Installation
caveui components are copy-paste Jetpack Compose built entirely on Material 3 — there's no caveui dependency to add. Make sure Material 3 is on your classpath (it ships with the Compose BOM), then copy the Usage snippet below into your project.
kotlin
// build.gradle.kts (module)
dependencies {
implementation(platform("androidx.compose:compose-bom:2025.06.00"))
implementation("androidx.compose.material3:material3")
}Usage
kotlin
data class Story(
val name: String,
val isLive: Boolean,
)
data class Post(
val authorName: String,
val authorHandle: String,
val caption: String,
val likes: String,
val shares: String,
val comments: String,
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SocialDiscoverScreen(
stories: List<Story>,
posts: List<Post>,
onAddStory: () -> Unit,
onPost: () -> Unit,
) {
val terracotta = Color(0xFFE8612C)
val warmBg = MaterialTheme.colorScheme.background
Scaffold(
containerColor = warmBg,
bottomBar = {
Box(contentAlignment = Alignment.TopCenter) {
NavigationBar(containerColor = warmBg) {
NavigationBarItem(
selected = true,
onClick = {},
icon = {
Icon(
Icons.Filled.Home,
contentDescription = "Home",
tint = terracotta,
)
},
label = {
Text(
"Home",
color = terracotta,
style = MaterialTheme.typography.labelSmall,
)
},
)
NavigationBarItem(
selected = false,
onClick = {},
icon = {
Icon(Icons.Filled.Search, contentDescription = "Search")
},
label = { Text("Search") },
)
// FAB spacer
NavigationBarItem(
selected = false,
onClick = {},
icon = { Spacer(Modifier.size(48.dp)) },
label = {},
enabled = false,
)
NavigationBarItem(
selected = false,
onClick = {},
icon = {
Icon(
Icons.Outlined.ChatBubbleOutline,
contentDescription = "Message",
)
},
label = { Text("Message") },
)
NavigationBarItem(
selected = false,
onClick = {},
icon = {
Icon(Icons.Outlined.Person, contentDescription = "Profile")
},
label = { Text("Profile") },
)
}
FloatingActionButton(
onClick = onPost,
modifier = Modifier.offset(y = (-24).dp),
containerColor = terracotta,
shape = CircleShape,
) {
Icon(Icons.Filled.Add, contentDescription = "New post", tint = Color.White)
}
}
},
) { padding ->
LazyColumn(
modifier = Modifier.fillMaxSize().padding(padding),
contentPadding = PaddingValues(bottom = 16.dp),
) {
// Top bar
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(top = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Surface(shape = CircleShape, color = terracotta.copy(alpha = 0.2f)) {
Box(
Modifier.size(40.dp),
contentAlignment = Alignment.Center,
) {
Text(
"Me",
color = terracotta,
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Bold,
)
}
}
IconButton(onClick = {}) {
Icon(Icons.Outlined.Notifications, contentDescription = "Notifications")
}
}
}
// Title
item {
Text(
"Discover",
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.ExtraBold,
)
}
// Stories row
item {
LazyRow(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
item {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Surface(
onClick = onAddStory,
shape = CircleShape,
color = terracotta.copy(alpha = 0.08f),
border = BorderStroke(
1.5.dp,
MaterialTheme.colorScheme.outlineVariant,
),
modifier = Modifier.size(56.dp),
) {
Box(contentAlignment = Alignment.Center) {
Icon(
Icons.Filled.Add,
contentDescription = "Add story",
tint = terracotta,
)
}
}
Text(
"Add story",
modifier = Modifier.padding(top = 4.dp),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
items(stories) { s ->
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box {
Surface(
shape = CircleShape,
border = BorderStroke(2.dp, terracotta),
modifier = Modifier
.size(56.dp)
.padding(2.dp),
) {
Box(
Modifier
.fillMaxSize()
.background(
MaterialTheme.colorScheme.surfaceVariant,
CircleShape,
),
contentAlignment = Alignment.Center,
) {
Text(
s.name.first().toString(),
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Bold,
)
}
}
if (s.isLive) {
Surface(
modifier = Modifier
.align(Alignment.BottomCenter)
.offset(y = 4.dp),
color = terracotta,
shape = CircleShape,
) {
Text(
"LIVE",
modifier = Modifier.padding(
horizontal = 5.dp, vertical = 1.dp
),
color = Color.White,
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.ExtraBold,
)
}
}
}
Text(
s.name,
modifier = Modifier.padding(top = 6.dp),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
}
}
// Post cards
items(posts) { post ->
PostCard(post = post, accent = terracotta)
}
}
}
}
@Composable
private fun PostCard(post: Post, accent: Color) {
Box(
modifier = Modifier
.padding(horizontal = 12.dp)
.padding(top = 12.dp)
.fillMaxWidth()
.height(240.dp)
.clip(RoundedCornerShape(20.dp))
.background(Color(0xFF4A3728)),
) {
// Bottom gradient overlay
Box(
Modifier
.fillMaxSize()
.background(
Brush.verticalGradient(
listOf(Color.Transparent, Color(0xCC1a1208)),
startY = 80f,
),
),
)
// Action column — right side
Column(
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
listOf(
Triple(Icons.Filled.Favorite, post.likes, true),
Triple(Icons.Outlined.Share, post.shares, false),
Triple(Icons.Outlined.ChatBubbleOutline, post.comments, false),
).forEach { (icon, count, filled) ->
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
imageVector = icon,
contentDescription = null,
tint = if (filled) accent else Color.White,
modifier = Modifier.size(22.dp),
)
Text(
count,
color = Color.White,
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.SemiBold,
)
}
}
}
// Author + caption — bottom left
Column(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(12.dp)
.padding(end = 56.dp),
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Surface(
shape = CircleShape,
border = BorderStroke(2.dp, accent),
modifier = Modifier.size(32.dp),
) {
Box(
Modifier
.fillMaxSize()
.background(accent.copy(alpha = 0.3f)),
contentAlignment = Alignment.Center,
) {
Text(
post.authorName.take(2).uppercase(),
color = Color.White,
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Bold,
)
}
}
Spacer(Modifier.width(8.dp))
Column {
Text(
post.authorName,
color = Color.White,
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Bold,
)
Text(
post.authorHandle,
color = Color.White.copy(alpha = 0.7f),
style = MaterialTheme.typography.labelSmall,
)
}
}
Text(
post.caption,
modifier = Modifier.padding(top = 6.dp),
color = Color.White.copy(alpha = 0.9f),
style = MaterialTheme.typography.bodySmall,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
}
}