diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index 49f42603..48f2a110 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -49,6 +49,7 @@ import com.example.compose.snippets.components.ScaffoldExample import com.example.compose.snippets.components.SearchBarExamples import com.example.compose.snippets.components.SegmentedButtonExamples import com.example.compose.snippets.components.SliderExamples +import com.example.compose.snippets.components.SwipeToDismissBoxExamples import com.example.compose.snippets.components.SwitchExamples import com.example.compose.snippets.components.TimePickerExamples import com.example.compose.snippets.components.TooltipExamples @@ -122,6 +123,7 @@ class SnippetsActivity : ComponentActivity() { TopComponentsDestination.TooltipExamples -> TooltipExamples() TopComponentsDestination.NavigationDrawerExamples -> NavigationDrawerExamples() TopComponentsDestination.SegmentedButtonExamples -> SegmentedButtonExamples() + TopComponentsDestination.SwipeToDismissBoxExamples -> SwipeToDismissBoxExamples() TopComponentsDestination.SearchBarExamples -> SearchBarExamples() } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/SwipeToDismissBox.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/SwipeToDismissBox.kt new file mode 100644 index 00000000..34a6aaeb --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/SwipeToDismissBox.kt @@ -0,0 +1,339 @@ +/* + * 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 + * + * https://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 com.example.compose.snippets.components + +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckBox +import androidx.compose.material.icons.filled.CheckBoxOutlineBlank +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.SwipeToDismissBox +import androidx.compose.material3.SwipeToDismissBoxValue +import androidx.compose.material3.Text +import androidx.compose.material3.rememberSwipeToDismissBoxState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +fun SwipeToDismissBoxExamples() { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text("Swipe to dismiss with change of background", fontWeight = FontWeight.Bold) + SwipeItemExample() + Text("Swipe to dismiss with a cross-fade animation", fontWeight = FontWeight.Bold) + SwipeCardItemExample() + } +} + +// [START android_compose_components_todoitem] +data class TodoItem( + var isItemDone: Boolean, + var itemDescription: String +) +// [END android_compose_components_todoitem] + +// [START android_compose_components_swipeitem] +@Composable +fun SwipeItem( + value: TodoItem, + startToEndAction: (TodoItem) -> Unit, + endToStartAction: (TodoItem) -> Unit, + modifier: Modifier = Modifier, + content: @Composable (TodoItem) -> Unit +) { + val swipeToDismissBoxState = rememberSwipeToDismissBoxState( + confirmValueChange = { + when (it) { + SwipeToDismissBoxValue.StartToEnd -> { + startToEndAction(value) + // Do not dismiss this item. + false + } + SwipeToDismissBoxValue.EndToStart -> { + endToStartAction(value) + true + } + SwipeToDismissBoxValue.Settled -> { + false + } + } + } + ) + + SwipeToDismissBox( + state = swipeToDismissBoxState, + modifier = modifier + .fillMaxSize(), + backgroundContent = { + Row( + modifier = Modifier + .background( + when (swipeToDismissBoxState.dismissDirection) { + SwipeToDismissBoxValue.StartToEnd -> { + Color.Blue + } + SwipeToDismissBoxValue.EndToStart -> { + Color.Red + } + SwipeToDismissBoxValue.Settled -> { + Color.LightGray + } + } + ) + .fillMaxSize(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + when (swipeToDismissBoxState.dismissDirection) { + SwipeToDismissBoxValue.StartToEnd -> { + if (value.isItemDone) { + Icon( + imageVector = Icons.Default.CheckBox, + contentDescription = "Item done", + tint = Color.White, + modifier = Modifier + .padding(12.dp) + ) + } else { + Icon( + imageVector = Icons.Default.CheckBoxOutlineBlank, + contentDescription = "Item not done", + tint = Color.White, + modifier = Modifier + .padding(12.dp) + ) + } + } + + SwipeToDismissBoxValue.EndToStart -> { + Spacer(modifier = Modifier) + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Remove item", + tint = Color.White, + modifier = Modifier + .padding(12.dp) + ) + } + + SwipeToDismissBoxValue.Settled -> {} + } + } + } + ) { + content(value) + } +} +// [END android_compose_components_swipeitem] + +@Preview(showBackground = true) +// [START android_compose_components_swipeitemexample] +@Composable +private fun SwipeItemExample() { + val todoItems = remember { + mutableStateListOf( + TodoItem(isItemDone = false, itemDescription = "Pay bills"), + TodoItem(isItemDone = false, itemDescription = "Buy groceries"), + TodoItem(isItemDone = false, itemDescription = "Go to gym"), + TodoItem(isItemDone = false, itemDescription = "Get dinner") + ) + } + + LazyColumn { + items( + items = todoItems, + key = { it.itemDescription } + ) { todoItem -> + SwipeItem( + value = todoItem, + startToEndAction = { + todoItem.isItemDone = !todoItem.isItemDone + }, + endToStartAction = { + todoItems -= todoItem + } + ) { + ListItem( + headlineContent = { Text(text = todoItem.itemDescription) }, + supportingContent = { Text(text = "swipe me to update or remove.") } + ) + } + } + } +} +// [END android_compose_components_swipeitemexample] + +// [START android_compose_components_swipecarditem] +@Composable +fun SwipeCardItem( + value: TodoItem, + startToEndAction: (TodoItem) -> Unit, + endToStartAction: (TodoItem) -> Unit, + modifier: Modifier = Modifier, + content: @Composable (TodoItem) -> Unit +) { + val swipeToDismissState = rememberSwipeToDismissBoxState( + positionalThreshold = { totalDistance -> totalDistance * 0.25f }, + confirmValueChange = { + when (it) { + SwipeToDismissBoxValue.StartToEnd -> { + startToEndAction(value) + // Do not dismiss this item. + false + } + SwipeToDismissBoxValue.EndToStart -> { + endToStartAction(value) + true + } + SwipeToDismissBoxValue.Settled -> { + false + } + } + } + ) + + SwipeToDismissBox( + modifier = Modifier, + state = swipeToDismissState, + backgroundContent = { + // Cross-fade the background color as the drag gesture progresses. + val color by animateColorAsState( + when (swipeToDismissState.targetValue) { + SwipeToDismissBoxValue.Settled -> Color.LightGray + SwipeToDismissBoxValue.StartToEnd -> + lerp(Color.LightGray, Color.Blue, swipeToDismissState.progress) + + SwipeToDismissBoxValue.EndToStart -> + lerp(Color.LightGray, Color.Red, swipeToDismissState.progress) + }, + label = "swipeable card item background color" + ) + Row( + modifier = Modifier + .background(color) + .fillMaxSize(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + when (swipeToDismissState.dismissDirection) { + SwipeToDismissBoxValue.StartToEnd -> { + if (value.isItemDone) { + Icon( + imageVector = Icons.Default.CheckBox, + contentDescription = "Item done", + tint = Color.White, + modifier = Modifier + .padding(12.dp) + ) + } else { + Icon( + imageVector = Icons.Default.CheckBoxOutlineBlank, + contentDescription = "Item not done", + tint = Color.White, + modifier = Modifier + .padding(12.dp) + ) + } + } + + SwipeToDismissBoxValue.EndToStart -> { + Spacer(modifier = Modifier) + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Remove item", + tint = Color.White, + modifier = Modifier + .padding(12.dp) + ) + } + + SwipeToDismissBoxValue.Settled -> {} + } + } + } + ) { + content(value) + } +} +// [END android_compose_components_swipecarditem] + +// [START android_compose_components_swipecarditemexample] +@Preview +@Composable +private fun SwipeCardItemExample() { + val todoItems = remember { + mutableStateListOf( + TodoItem(isItemDone = false, itemDescription = "Pay bills"), + TodoItem(isItemDone = false, itemDescription = "Buy groceries"), + TodoItem(isItemDone = false, itemDescription = "Go to gym"), + TodoItem(isItemDone = false, itemDescription = "Get dinner") + ) + } + + LazyColumn { + items( + items = todoItems, + key = { it.itemDescription } + ) { todoItem -> + SwipeCardItem( + value = todoItem, + startToEndAction = { + todoItem.isItemDone = !todoItem.isItemDone + }, + endToStartAction = { + todoItems -= todoItem + } + ) { + OutlinedCard(shape = RectangleShape) { + ListItem( + headlineContent = { Text(todoItem.itemDescription) }, + supportingContent = { Text("swipe me to update or remove.") } + ) + } + } + } + } +} +// [END android_compose_components_swipecarditemexample] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt index dbfdece1..8d86b28b 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt @@ -50,5 +50,6 @@ enum class TopComponentsDestination(val route: String, val title: String) { TooltipExamples("tooltipExamples", "Tooltips"), NavigationDrawerExamples("navigationDrawerExamples", "Navigation drawer"), SegmentedButtonExamples("segmentedButtonExamples", "Segmented button"), + SwipeToDismissBoxExamples("swipeToDismissBoxExamples", "Swipe to dismiss box examples"), SearchBarExamples("searchBarExamples", "Search bar") }