Skip to content

Instantly share code, notes, and snippets.

@parishsu
Created March 3, 2025 21:53
Show Gist options
  • Save parishsu/0348bfc8b2e931feba1d0d0ee20beb25 to your computer and use it in GitHub Desktop.
Save parishsu/0348bfc8b2e931feba1d0d0ee20beb25 to your computer and use it in GitHub Desktop.
package com.example.multimodal
import androidx.compose.runtime.mutableStateOf
import android.annotation.SuppressLint
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BookmarkBorder
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalConfiguration
/**
* Data class to represent an article.
*/
data class Article(
val id: Int,
val title: String,
val date: Date,
val tags: List<String>,
val content: String,
val imageResId: Int,
val isBookmarked: Boolean = false
)
/**
* Static composition local to manage the current Article selected.
*/
val LocalSelectedArticle = staticCompositionLocalOf<Article?> { null }
/**
* Main composable function for the "Now in Android" screen.
*
* This function structures the layout of the app's main screen,
* including the top app bar, a list of articles, and a bottom navigation bar.
*/
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class,
ExperimentalMaterial3WindowSizeClassApi::class
)
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun ArticleScreen(windowSize: WindowWidthSizeClass) {
// Sample articles data
val articles = remember {
listOf(
Article(
id = 1,
title = "The new Google Pixel Watch is here — start building for Wear OS!",
date = SimpleDateFormat("MMM dd, yyyy", Locale.US).parse("Oct 6, 2022")
?: Date(),
tags = listOf("HEADLINES", "WEAR OS", "COMPOSE"),
content = "We launched the Google Pixel Watch, powered by Wear OS 3.5, at the Made by Google event — the perfect device to showcase apps built with Compose for Wear OS. With Compose for Wear OS, the Tiles Material library, and the tools in Android Studio Dolphin, it's now simpler and more efficient than ever to make apps for WearOS.",
imageResId = R.drawable.placeholder_koala
),
Article(
id = 2,
title = "Android Studio Electric Eel (2022.1.1) is now stable",
date = SimpleDateFormat("MMM dd, yyyy", Locale.US).parse("Dec 15, 2022")
?: Date(),
tags = listOf("ANDROID STUDIO", "HEADLINES"),
content = "Android Studio Electric Eel (2022.1.1) is now stable",
imageResId = R.drawable.placeholder_iguana
),
Article(
id = 3,
title = "Now in Android: Episode 80",
date = SimpleDateFormat("MMM dd, yyyy", Locale.US).parse("Feb 2, 2023")
?: Date(),
tags = listOf("NOW IN ANDROID"),
content = "Episode 80 of Now in Android is out now!",
imageResId = R.drawable.placeholder_dolphin
)
)
}
var selectedItem by remember { mutableIntStateOf(0) }
val items = listOf("For you", "Saved", "Interests")
var selectedArticle by remember { mutableStateOf<Article?>(null) }
// To manage the different window sizes
CompositionLocalProvider(LocalSelectedArticle provides selectedArticle) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = "Now in Android",
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
},
navigationIcon = {
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = "Search"
)
}
},
actions = {
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Outlined.Settings,
contentDescription = "Settings"
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface
)
)
},
bottomBar = {
NavigationBar(
containerColor = MaterialTheme.colorScheme.surface
) {
items.forEachIndexed { index, item ->
NavigationBarItem(
icon = {
when (item) {
"For you" -> Icon(
imageVector = Icons.Filled.Home,
contentDescription = item
)
"Saved" -> Icon(
imageVector = Icons.Filled.BookmarkBorder,
contentDescription = item
)
"Interests" -> Icon(
imageVector = Icons.Filled.Favorite,
contentDescription = item
)
}
},
label = { Text(item) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
},
modifier = Modifier.fillMaxSize()
) { innerPadding ->
Surface(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.background(MaterialTheme.colorScheme.surface)
) {
// List of articles
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(articles) { article ->
ArticleItem(article = article, onArticleClick = {
selectedArticle = article
})
}
}
if (windowSize != WindowWidthSizeClass.Compact) {
DetailArticle()
}
}
}
}
}
/**
* Composable function for displaying a single article item.
*
* @param article The article data to display.
*/
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ArticleItem(article: Article, onArticleClick: (Article) -> Unit) {
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant, RoundedCornerShape(16.dp))
.clickable { onArticleClick(article) }
.padding(16.dp)
) {
Image(
painter = painterResource(id = article.imageResId),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(screenWidth / 2)
.clip(RoundedCornerShape(16.dp)),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = article.title,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(8.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = SimpleDateFormat("MMM dd, yyyy", Locale.US).format(article.date),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
modifier = Modifier.padding(start = 4.dp),
text = "• Article",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.weight(1f))
Icon(
imageVector = Icons.Filled.BookmarkBorder,
contentDescription = "Bookmark",
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = article.content,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 3
)
Spacer(modifier = Modifier.height(16.dp))
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
article.tags.forEach { tag ->
TagButton(tag = tag)
}
}
}
}
/**
* Composable function for displaying a tag button.
*
* @param tag The tag text to display.
*/
@Composable
fun TagButton(tag: String) {
Button(
onClick = { /*TODO*/ },
modifier = Modifier,
shape = RoundedCornerShape(16.dp),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
contentColor = MaterialTheme.colorScheme.onTertiaryContainer
)
) {
Text(text = tag, fontSize = 12.sp)
}
}
@Composable
fun DetailArticle(){
val selectedArticle = LocalSelectedArticle.current
selectedArticle?.let {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Gray) // Example color
) {
Text(
text = it.title,
style = MaterialTheme.typography.headlineLarge,
color = Color.White, // Example color
textAlign = TextAlign.Center,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
/**
* Preview function to display NowInAndroidScreen in the IDE.
*/
@Preview(showBackground = true)
@Composable
fun ArticleScreenPreview() {
MaterialTheme {
ArticleScreen(WindowWidthSizeClass.Compact)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment