| /* |
| * Copyright 2019 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.ui.core.demos |
| |
| import androidx.compose.Composable |
| import androidx.compose.foundation.BaseTextField |
| import androidx.compose.foundation.Box |
| import androidx.compose.foundation.ContentGravity |
| import androidx.compose.foundation.ExperimentalFoundationApi |
| import androidx.compose.foundation.ScrollableColumn |
| import androidx.compose.foundation.Text |
| import androidx.compose.foundation.background |
| import androidx.compose.foundation.clickable |
| import androidx.compose.foundation.layout.Arrangement |
| import androidx.compose.foundation.layout.Column |
| import androidx.compose.foundation.layout.ColumnScope |
| import androidx.compose.foundation.layout.Row |
| import androidx.compose.foundation.layout.Spacer |
| import androidx.compose.foundation.layout.fillMaxHeight |
| import androidx.compose.foundation.layout.fillMaxWidth |
| import androidx.compose.foundation.layout.preferredHeight |
| import androidx.compose.foundation.layout.preferredSize |
| import androidx.compose.foundation.layout.preferredWidth |
| import androidx.compose.foundation.shape.CircleShape |
| import androidx.compose.state |
| import androidx.ui.core.Alignment |
| import androidx.ui.core.Modifier |
| import androidx.ui.core.Popup |
| import androidx.compose.ui.graphics.Color |
| import androidx.compose.ui.text.input.ImeAction |
| import androidx.compose.ui.text.input.KeyboardType |
| import androidx.compose.ui.text.input.TextFieldValue |
| import androidx.compose.ui.text.style.TextAlign |
| import androidx.compose.ui.unit.Dp |
| import androidx.compose.ui.unit.dp |
| |
| @Composable |
| fun PopupDemo() { |
| val exampleIndex = state { 0 } |
| val totalExamples = 8 |
| |
| Column { |
| Row( |
| Modifier.fillMaxWidth().gravity(Alignment.CenterHorizontally), |
| horizontalArrangement = Arrangement.SpaceBetween |
| ) { |
| this@Column.ClickableTextWithBackground( |
| text = "Prev", |
| color = Color.Cyan, |
| onClick = { |
| if (exampleIndex.value == 0) { |
| exampleIndex.value = totalExamples |
| } |
| |
| exampleIndex.value = (exampleIndex.value - 1) % totalExamples |
| }, |
| padding = 20.dp |
| ) |
| |
| Box( |
| modifier = Modifier.weight(1f), |
| gravity = ContentGravity.Center |
| ) { |
| val description: String = { |
| when (exampleIndex.value) { |
| 0 -> "Toggle a simple popup" |
| 1 -> "Different content for the popup" |
| 2 -> "Popup's behavior when the parent's size or position " + |
| "changes" |
| 3 -> "Aligning the popup below the parent" |
| 4 -> "Aligning the popup inside a parent" |
| 5 -> "Insert an email in the popup and then click outside to " + |
| "dismiss" |
| 6 -> "[bug] Undesired visual effect caused by" + |
| " having a new size content displayed at the old" + |
| " position, until the new one is calculated" |
| 7 -> "The popup is aligning to its parent when the parent is" + |
| " inside a Scroller" |
| 8 -> "[bug] The popup is not repositioned " + |
| "when the parent is moved by the keyboard" |
| else -> "Demo description here" |
| } |
| }.invoke() |
| |
| Text( |
| text = description, |
| textAlign = TextAlign.Center |
| ) |
| } |
| |
| this@Column.ClickableTextWithBackground( |
| text = "Next", |
| color = Color.Cyan, |
| onClick = { |
| exampleIndex.value = (exampleIndex.value + 1) % totalExamples |
| }, |
| padding = 20.dp |
| ) |
| } |
| |
| when (exampleIndex.value) { |
| 0 -> PopupToggle() |
| 1 -> PopupWithChangingContent() |
| 2 -> PopupWithChangingParent() |
| 3 -> PopupAlignmentDemo() |
| 4 -> PopupWithEditText() |
| 5 -> PopupWithChangingSize() |
| 6 -> PopupInsideScroller() |
| 7 -> PopupOnKeyboardUp() |
| } |
| } |
| } |
| |
| @Composable |
| private fun ColumnScope.PopupToggle() { |
| val showPopup = state { true } |
| |
| Column(Modifier.gravity(Alignment.CenterHorizontally)) { |
| Box(Modifier.preferredSize(100.dp)) { |
| if (showPopup.value) { |
| Popup(alignment = Alignment.Center) { |
| Box( |
| Modifier.preferredSize(70.dp), |
| backgroundColor = Color.Green, |
| shape = CircleShape, |
| gravity = ContentGravity.Center |
| ) { |
| Text( |
| text = "This is a popup!", |
| textAlign = TextAlign.Center |
| ) |
| } |
| } |
| } |
| } |
| |
| ClickableTextWithBackground( |
| text = "Toggle Popup", |
| color = Color.Cyan, |
| onClick = { |
| showPopup.value = !showPopup.value |
| } |
| ) |
| } |
| } |
| |
| @Composable |
| private fun ColumnScope.PopupWithChangingContent() { |
| Column(Modifier.gravity(Alignment.CenterHorizontally)) { |
| val heightSize = 120.dp |
| val widthSize = 160.dp |
| val popupContentState = state { 0 } |
| val totalContentExamples = 2 |
| val popupCounter = state { 0 } |
| |
| Box(Modifier.preferredSize(widthSize, heightSize), backgroundColor = Color.Gray) { |
| Popup(Alignment.Center) { |
| when (popupContentState.value % totalContentExamples) { |
| 0 -> ClickableTextWithBackground( |
| text = "Counter : ${popupCounter.value}", |
| color = Color.Green, |
| onClick = { |
| popupCounter.value += 1 |
| } |
| ) |
| 1 -> Box( |
| Modifier.preferredSize(60.dp, 40.dp), |
| backgroundColor = Color.Blue, |
| shape = CircleShape |
| ) |
| } |
| } |
| } |
| |
| Spacer(Modifier.preferredHeight(10.dp)) |
| ClickableTextWithBackground( |
| text = "Change content", |
| color = Color.Cyan, |
| onClick = { |
| popupContentState.value += 1 |
| } |
| ) |
| } |
| } |
| |
| @Composable |
| private fun ColumnScope.PopupWithChangingParent() { |
| val containerWidth = 400.dp |
| val containerHeight = 200.dp |
| val parentGravity = state { ContentGravity.TopStart } |
| val parentWidth = state { 80.dp } |
| val parentHeight = state { 60.dp } |
| val parentSizeChanged = state { false } |
| |
| Column(Modifier.gravity(Alignment.CenterHorizontally)) { |
| Box( |
| Modifier.preferredSize(containerWidth, containerHeight), |
| gravity = parentGravity.value |
| ) { |
| Box( |
| Modifier.preferredSize(parentWidth.value, parentHeight.value), |
| backgroundColor = Color.Blue |
| ) { |
| Popup(Alignment.BottomCenter) { |
| Text("Popup", modifier = Modifier.background(color = Color.Green)) |
| } |
| } |
| } |
| Spacer(Modifier.preferredHeight(10.dp)) |
| ClickableTextWithBackground( |
| text = "Change parent's position", |
| color = Color.Cyan, |
| onClick = { |
| parentGravity.value = |
| if (parentGravity.value == ContentGravity.TopStart) |
| ContentGravity.TopEnd |
| else |
| ContentGravity.TopStart |
| } |
| ) |
| Spacer(Modifier.preferredHeight(10.dp)) |
| ClickableTextWithBackground( |
| text = "Change parent's size", |
| color = Color.Cyan, |
| onClick = { |
| if (parentSizeChanged.value) { |
| parentWidth.value = 80.dp |
| parentHeight.value = 60.dp |
| } else { |
| parentWidth.value = 160.dp |
| parentHeight.value = 120.dp |
| } |
| parentSizeChanged.value = !parentSizeChanged.value |
| } |
| ) |
| } |
| } |
| |
| @Composable |
| private fun ColumnScope.PopupAlignmentDemo() { |
| Column(Modifier.gravity(Alignment.CenterHorizontally)) { |
| val heightSize = 200.dp |
| val widthSize = 400.dp |
| val counter = state { 0 } |
| val popupAlignment = state { Alignment.TopStart } |
| Box( |
| modifier = Modifier.preferredSize(widthSize, heightSize), |
| backgroundColor = Color.Red, |
| gravity = ContentGravity.BottomCenter |
| ) { |
| Popup(popupAlignment.value) { |
| ClickableTextWithBackground( |
| text = "Click to change alignment", |
| color = Color.White, |
| onClick = { |
| counter.value += 1 |
| when (counter.value % 9) { |
| 0 -> popupAlignment.value = Alignment.TopStart |
| 1 -> popupAlignment.value = Alignment.TopCenter |
| 2 -> popupAlignment.value = Alignment.TopEnd |
| 3 -> popupAlignment.value = Alignment.CenterEnd |
| 4 -> popupAlignment.value = Alignment.BottomEnd |
| 5 -> popupAlignment.value = Alignment.BottomCenter |
| 6 -> popupAlignment.value = Alignment.BottomStart |
| 7 -> popupAlignment.value = Alignment.CenterStart |
| 8 -> popupAlignment.value = Alignment.Center |
| } |
| } |
| ) |
| } |
| } |
| |
| Spacer(Modifier.preferredHeight(10.dp)) |
| Text( |
| modifier = Modifier.gravity(Alignment.CenterHorizontally) |
| .background(color = Color.White), |
| text = "Alignment : " + popupAlignment.value.toString() |
| ) |
| } |
| } |
| |
| @Composable |
| private fun ColumnScope.PopupWithEditText() { |
| Column(Modifier.gravity(Alignment.CenterHorizontally)) { |
| val widthSize = 190.dp |
| val heightSize = 120.dp |
| val editLineSize = 150.dp |
| val showEmail = state { |
| "Enter your email in the white rectangle and click outside" |
| } |
| val email = state { "email" } |
| val showPopup = state { true } |
| |
| Text(text = showEmail.value) |
| |
| Box( |
| modifier = Modifier.preferredSize(widthSize, heightSize) |
| .gravity(Alignment.CenterHorizontally), |
| backgroundColor = Color.Red |
| ) { |
| if (showPopup.value) { |
| Popup( |
| alignment = Alignment.Center, |
| isFocusable = true, |
| onDismissRequest = { |
| showEmail.value = "You entered: " + email.value |
| showPopup.value = false |
| } |
| ) { |
| EditLine( |
| modifier = Modifier.preferredWidth(editLineSize), |
| initialText = "", |
| color = Color.White, |
| onValueChange = { |
| email.value = it |
| } |
| ) |
| } |
| } |
| } |
| } |
| } |
| |
| @Composable |
| private fun ColumnScope.PopupWithChangingSize() { |
| Column(Modifier.gravity(Alignment.CenterHorizontally)) { |
| val showPopup = state { true } |
| val heightSize = 120.dp |
| val widthSize = 160.dp |
| val rectangleState = state { 0 } |
| |
| Spacer(Modifier.preferredHeight(15.dp)) |
| Box( |
| modifier = Modifier.preferredSize(widthSize, heightSize), |
| backgroundColor = Color.Magenta |
| ) { |
| if (showPopup.value) { |
| Popup(Alignment.Center) { |
| val size = when (rectangleState.value % 4) { |
| 0 -> Modifier.preferredSize(30.dp) |
| 1 -> Modifier.preferredSize(100.dp) |
| 2 -> Modifier.preferredSize(30.dp, 90.dp) |
| else -> Modifier.preferredSize(90.dp, 30.dp) |
| } |
| Box(modifier = size, backgroundColor = Color.Gray) |
| } |
| } |
| } |
| Spacer(Modifier.preferredHeight(25.dp)) |
| ClickableTextWithBackground( |
| text = "Change size", |
| color = Color.Cyan, |
| onClick = { |
| rectangleState.value += 1 |
| } |
| ) |
| } |
| } |
| |
| @Composable |
| private fun ColumnScope.PopupInsideScroller() { |
| ScrollableColumn( |
| modifier = Modifier.preferredSize(200.dp, 400.dp).gravity(Alignment.CenterHorizontally) |
| ) { |
| Column(Modifier.fillMaxHeight()) { |
| Box( |
| modifier = Modifier.preferredSize(80.dp, 160.dp), |
| backgroundColor = Color(0xFF00FF00) |
| ) { |
| Popup(alignment = Alignment.Center) { |
| ClickableTextWithBackground(text = "Centered", color = Color.Cyan) |
| } |
| } |
| |
| for (i in 0..30) { |
| Text(text = "Scroll #$i", modifier = Modifier.gravity(Alignment.CenterHorizontally)) |
| } |
| } |
| } |
| } |
| |
| @Composable |
| private fun PopupOnKeyboardUp() { |
| Column { |
| val widthSize = 190.dp |
| val heightSize = 120.dp |
| |
| Spacer(Modifier.preferredHeight(350.dp)) |
| Text("Start typing in the EditText below the parent(Red rectangle)") |
| Box( |
| modifier = Modifier.preferredSize(widthSize, heightSize) |
| .gravity(Alignment.CenterHorizontally), |
| backgroundColor = Color.Red |
| ) { |
| Popup(Alignment.Center) { |
| Box(backgroundColor = Color.Green) { |
| Text("Popup") |
| } |
| } |
| } |
| |
| EditLine(initialText = "Continue typing...", color = Color.Gray) |
| |
| Spacer(Modifier.preferredHeight(24.dp)) |
| } |
| } |
| |
| @Composable |
| private fun ColumnScope.ClickableTextWithBackground( |
| text: String, |
| color: Color, |
| onClick: (() -> Unit)? = null, |
| padding: Dp = 0.dp |
| ) { |
| Box( |
| Modifier |
| .gravity(Alignment.CenterHorizontally) |
| .clickable(onClick = onClick ?: {}, enabled = onClick != null), |
| backgroundColor = color, |
| padding = padding |
| ) { |
| Text(text) |
| } |
| } |
| |
| @Composable |
| @OptIn(ExperimentalFoundationApi::class) |
| private fun EditLine( |
| modifier: Modifier = Modifier, |
| keyboardType: KeyboardType = KeyboardType.Text, |
| imeAction: ImeAction = ImeAction.Unspecified, |
| onValueChange: (String) -> Unit = {}, |
| initialText: String = "", |
| color: Color = Color.White |
| ) { |
| val state = state { TextFieldValue(initialText) } |
| BaseTextField( |
| value = state.value, |
| modifier = modifier.background(color = color), |
| keyboardType = keyboardType, |
| imeAction = imeAction, |
| onValueChange = { |
| state.value = it |
| onValueChange(it.text) |
| } |
| ) |
| } |