blob: a9091f9cced99f50e0aecb104d43fc25de865f7f [file] [log] [blame]
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.foundation.demos
import android.content.Context
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.verticalScroll
import androidx.compose.integration.demos.common.ComposableDemo
import androidx.compose.material.BottomAppBar
import androidx.compose.material.Button
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Scaffold
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TopAppBar
import androidx.compose.material.primarySurface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.scrollcapture.ComposeFeatureFlag_LongScreenshotsEnabled
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
val LongScreenshotsDemos = listOf(
ComposableDemo("Single, small, eager list") { SingleEagerListDemo() },
ComposableDemo("Single, small, lazy list") { SingleLazyListDemo() },
ComposableDemo("Single, full-screen list") { SingleFullScreenListDemo() },
ComposableDemo("Lazy list with content padding") { LazyListContentPaddingDemo() },
ComposableDemo("Big viewport nested in smaller outer viewport") { BigInLittleDemo() },
ComposableDemo("Scrollable in dialog") { InDialogDemo() },
ComposableDemo("Nested AndroidView") { AndroidViewDemo() },
ComposableDemo("TextField in scrollable (legacy)") { LegacyTextFieldInScrollableDemo() },
ComposableDemo("Single giant text field (legacy)") { LegacySingleGiantTextFieldDemo() },
ComposableDemo("TextField in scrollable") { TextFieldInScrollableDemo() },
ComposableDemo("Single giant text field") { SingleGiantTextFieldDemo() },
)
@Composable
private fun SingleEagerListDemo() {
var fullWidth by remember { mutableStateOf(false) }
var fullHeight by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.wrapContentHeight(),
horizontalAlignment = Alignment.CenterHorizontally
) {
FeatureFlagToggle()
Text(
"This is some scrollable content. When a screenshot is taken, it should let you " +
"capture the entire content, not just the part currently visible.",
style = MaterialTheme.typography.caption
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Full-width")
Switch(fullWidth, onCheckedChange = { fullWidth = it })
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Full-height")
Switch(fullHeight, onCheckedChange = { fullHeight = it })
}
Divider()
Column(
Modifier
.border(1.dp, Color.Black)
.fillMaxWidth(fraction = if (fullWidth) 1f else 0.75f)
.fillMaxHeight(fraction = if (fullHeight) 1f else 0.75f)
.verticalScroll(rememberScrollState())
) {
repeat(50) { index ->
Button(
onClick = {},
Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text("Button $index")
}
}
}
}
}
@Composable
private fun SingleLazyListDemo() {
Column(
modifier = Modifier
.fillMaxSize()
.wrapContentHeight(),
horizontalAlignment = Alignment.CenterHorizontally
) {
FeatureFlagToggle()
Text(
"This is some scrollable content. When a screenshot is taken, it should let you " +
"capture the entire content, not just the part currently visible.",
style = MaterialTheme.typography.caption
)
LazyColumn(
Modifier
.border(1.dp, Color.Black)
.fillMaxWidth(fraction = 0.75f)
.height(200.dp)
) {
items(50) { index ->
Button(
onClick = {},
Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text("Button $index")
}
}
}
}
}
@Composable
private fun SingleFullScreenListDemo() {
Column {
FeatureFlagToggle()
LazyColumn(Modifier.fillMaxSize()) {
items(50) { index ->
Button(
onClick = {},
Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text("Button $index")
}
}
}
}
}
@Composable
private fun LazyListContentPaddingDemo() {
Column {
FeatureFlagToggle()
Scaffold(
modifier = Modifier
.padding(8.dp)
.border(1.dp, Color.Black),
topBar = {
TopAppBar(
title = { Text("Top bar") },
backgroundColor = MaterialTheme.colors.primarySurface.copy(alpha = 0.5f)
)
},
bottomBar = {
BottomAppBar(
backgroundColor = MaterialTheme.colors.primarySurface.copy(alpha = 0.5f)
) { Text("Bottom bar") }
}
) { contentPadding ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(Color.Red),
contentPadding = contentPadding
) {
items(15) { index ->
Button(
onClick = {},
Modifier
.background(Color.LightGray)
.padding(8.dp)
.fillMaxWidth()
) {
Text("Button $index")
}
}
}
}
}
}
@Composable
private fun BigInLittleDemo() {
Column(
modifier = Modifier
.fillMaxSize()
.wrapContentHeight(),
horizontalAlignment = Alignment.CenterHorizontally
) {
FeatureFlagToggle()
Text(
"This is a small scroll container that has a much larger scroll container inside it. " +
"The inner scroll container should be captured.",
style = MaterialTheme.typography.caption
)
LazyColumn(
Modifier
.border(1.dp, Color.Black)
.weight(1f)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
items(4) {
Text(
"Header $it",
Modifier
.fillMaxWidth()
.background(Color.LightGray)
.padding(16.dp)
)
Box(
Modifier
.background(Color.Magenta)
.fillParentMaxHeight(0.5f)
// .height(400.dp)
.padding(horizontal = 16.dp)
) {
SingleFullScreenListDemo()
}
Text(
"Footer $it",
Modifier
.fillMaxWidth()
.background(Color.LightGray)
.padding(16.dp)
)
}
}
}
}
@Composable
private fun InDialogDemo() {
Column {
FeatureFlagToggle()
// Need a scrolling list in the below screen to check that the scrollable in the dialog is
// selected instead.
LazyColumn(Modifier.fillMaxSize()) {
items(50) { index ->
var showDialog by remember { mutableStateOf(false) }
Button(
onClick = { showDialog = true },
Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Text("Open dialog ($index)")
}
if (showDialog) {
Dialog(onDismissRequest = { showDialog = false }) {
Box(
Modifier
.fillMaxSize(fraction = 0.5f)
.background(Color.LightGray)
) {
SingleFullScreenListDemo()
}
}
}
}
}
}
}
@Composable
private fun AndroidViewDemo() {
class DemoAndroidView(context: Context) : LinearLayout(context) {
init {
orientation = VERTICAL
addView(TextView(context).also { it.text = "AndroidView Header" })
addView(ScrollView(context).apply {
setBackgroundColor(android.graphics.Color.CYAN)
addView(LinearLayout(context).apply {
orientation = VERTICAL
repeat(20) {
addView(TextView(context).apply {
setPadding(20, 20, 20, 20)
text = "Item $it"
})
}
})
}, LayoutParams(MATCH_PARENT, 0, 1f))
addView(TextView(context).also { it.text = "AndroidView Footer" })
}
}
Column {
FeatureFlagToggle()
LazyColumn(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
items(10) {
Text("Compose item", Modifier.padding(16.dp))
}
item {
AndroidView(
factory = ::DemoAndroidView,
modifier = Modifier
.background(Color.Magenta)
.fillParentMaxHeight(0.5f)
.padding(horizontal = 16.dp)
)
}
items(5) {
Text("Compose item", Modifier.padding(16.dp))
}
}
}
}
@Composable
private fun LegacyTextFieldInScrollableDemo() {
LazyColumn(
Modifier
.fillMaxSize()
.imePadding()
) {
item { FeatureFlagToggle() }
repeat(10) {
item {
var text by remember { mutableStateOf("") }
OutlinedTextField(
label = { Text("Single line") },
value = text,
onValueChange = { text = it },
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
)
}
item {
var text by remember { mutableStateOf("one\ntwo\nthree\nfour\nfive\nsix") }
OutlinedTextField(
label = { Text("Multiline, scrollable field") },
value = text,
onValueChange = { text = it },
singleLine = false,
maxLines = 3,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
)
}
}
}
}
@Composable
fun LegacySingleGiantTextFieldDemo() {
var text by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
) {
FeatureFlagToggle()
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.fillMaxSize()
)
}
}
@Composable
private fun TextFieldInScrollableDemo() {
LazyColumn(
Modifier
.fillMaxSize()
.imePadding()
) {
item { FeatureFlagToggle() }
repeat(10) {
item {
val text = rememberTextFieldState()
Text("Single line")
BasicTextField(
state = text,
lineLimits = TextFieldLineLimits.SingleLine,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
)
}
item {
val text = rememberTextFieldState("one\ntwo\nthree\nfour\nfive\nsix")
Text("Multiline, scrollable field")
BasicTextField(
state = text,
lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 3),
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
)
}
}
}
}
@Composable
fun SingleGiantTextFieldDemo() {
val text = rememberTextFieldState()
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
) {
FeatureFlagToggle()
BasicTextField(
state = text,
modifier = Modifier.fillMaxSize()
)
}
}
@Suppress("DEPRECATION")
@Composable
private fun FeatureFlagToggle() {
Column(Modifier.background(Color.Yellow.copy(alpha = 0.5f))) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(Modifier.weight(1f)) {
Text("Enable long screenshots")
Text(
"The long screenshots feature is behind a feature flag while under " +
"development. ",
style = MaterialTheme.typography.caption
)
}
Switch(
checked = ComposeFeatureFlag_LongScreenshotsEnabled,
onCheckedChange = { ComposeFeatureFlag_LongScreenshotsEnabled = it },
)
}
Divider()
}
}