I want to show Indeterminate progress dialog when user performs some action like signing up. The old way it was possible using ProgressDialog.
For compose, I found this How can I render plain android ProgressBar with Compose? But this is not exactly what I'm looking for as it's just a view. It does not cover the screen like dialog does.
With CircularProgressIndicator I'm able to achieve this:
As you can see it's shown below the views.
I want to create something like this:
It should have:
Dim background
draw over other views
Non cancellable
How can I achieve this in Jetpack compose?
You can use the Dialog composable:
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
Dialog(
onDismissRequest = { showDialog = false },
DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
) {
Box(
contentAlignment= Center,
modifier = Modifier
.size(100.dp)
.background(White, shape = RoundedCornerShape(8.dp))
) {
CircularProgressIndicator()
}
}
}
Dialog with loading text using Dialog component of compose.material.
#Composable
fun DummyProgress() {
var dialogState by remember { mutableStateOf(false) }
Button(onClick = { dialogState = !dialogState }) {
Text(text = "Show Progress")
}
if (dialogState) {
Dialog(onDismissRequest = { dialogState = false },
DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)) {
Box(
contentAlignment= Alignment.Center,
modifier = Modifier
.size(100.dp)
.background(White, shape = RoundedCornerShape(12.dp))
) {
Column {
CircularProgressIndicator(modifier = Modifier.padding(6.dp, 0.dp, 0.dp, 0.dp))
Text(text = "Loading...", Modifier.padding(0.dp, 8.dp, 0.dp, 0.dp))
}
}
}
}
}
Related
I'm facing this weird issue with fillMaxWidth(fraction = ...) with an AlertDialog and Button, where the Button shows up initially at one size, and on click it shrinks to wrapping its content. Here is the most basic example I can create. I've tried with multiple versions of Compose but they all do the same thing. Any ideas?
AlertDialog(
modifier = modifier,
onDismissRequest = {},
text = { },
buttons = {
Button(
onClick = { },
modifier = Modifier
.fillMaxWidth(0.75f)
.padding(start = 12.dp, end = 12.dp, bottom = 8.dp)
) {
Text(text = "Done")
}
}
)
Before click:
After click:
I think you have to define a specific width for the AlertDialog, any child it has may not be able to calculate a 75% of unknown width .
either you fill the max width of the dialog
AlertDialog(
modifier = Modifier.fillMaxWidth(),
...
or specify an actual dp width
AlertDialog(
modifier = Modifier.width(150.dp),
...
I don't wanna throw jargons I can't explain, but I suspect when AlertDialog doesn't have any specific width, it cannot provide any incoming measurements for its children, which is in this case the Button cannot compute a 75% of unknown width on initial display, it looks like all size computation is happening only after an action has been made to the button, maybe a recomposition is happening under-the-hood having correct measurements.
I've used the sample composable from here and added your modifier to it for each button so it looks like
#Composable
fun AlertDialogSample() {
MaterialTheme {
Column {
val openDialog = remember { mutableStateOf(false) }
Button(onClick = {
openDialog.value = true
}) {
Text("Click me")
}
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
// Dismiss the dialog when the user clicks outside the dialog or on the back
// button. If you want to disable that functionality, simply use an empty
// onCloseRequest.
openDialog.value = false
},
title = {
Text(text = "Dialog Title")
},
text = {
Text("Here is a text ")
},
confirmButton = {
Button(
modifier = Modifier
.fillMaxWidth(0.75f)
.padding(start = 12.dp, end = 12.dp, bottom = 8.dp),
onClick = {
openDialog.value = false
}) {
Text("This is the Confirm Button")
}
},
dismissButton = {
Button(
modifier = Modifier
.fillMaxWidth(0.75f)
.padding(start = 12.dp, end = 12.dp, bottom = 8.dp),
onClick = {
openDialog.value = false
}) {
Text("This is the dismiss Button")
}
}
)
}
}
}
}
and I see the buttons at 75% width.
Doesn't explain why you see the described issue, but does give you a solution.
I have AppBarNavGraph and RootNavGraph.
I'd like to know two methods, Full Screen Dialog and also Navigating to a new Screen.
What I am trying to is..
Column(
modifier = Modifier
.width(97.dp)
.clickable {
Dialog(onDismissResult = { /*TODO*/ }) {
}
}
,
horizontalAlignment = Alignment.CenterHorizontally
But in clickable, composable function is not callable.
So, What I tried is using this,
val isClicked = remember { mutableStateOf(false) }
Column(
modifier = Modifier
.width(97.dp)
.clickable {
isClicked.value = !isClicked.value
},
horizontalAlignment = Alignment.CenterHorizontally
)
if(isClicked.value){
// show full screen dialog or navigate to some screen.
}
Here are two problems.
I tried with Dialog function.
Dialog(
onDismissResult: () -> Unit,
properties: DialogProperties = DialogProperties(),
content: #Composable () -> Unit
)
but, this doesn't show full screen but just next item. (it's in GridLayout and when I click an item, the dialog needs to shows up to add the item.)
For navigation, I tried, varies ways, but I haven't found any clear solutions...
Could you please help me out? or any advices?
This should do the trick for a simple full screen popup
var showPopup by remember { mutableStateOf(false) }
Box(Modifier
.background(Color.Blue)
.fillMaxSize()) {
Button(onClick = { showPopup = true }) {
Text("Show popup")
}
}
if (showPopup) {
Popup(
onDismissRequest = { showPopup = false }
) {
Box(Modifier
.background(Color.Red)
.fillMaxSize()) {
Button(onClick = { showPopup = false },
modifier = Modifier.align(Alignment.Center)) {
Text("Hide popup")
}
}
}
}
Though if you want proper navigation you should probably use this
I implemented a simple dialog with Jetpack Compose on Android.
I am trying to show a caution message when isRehearsal is true.
The variable isRehearsal is toggled when the user clicks buttons, and changing button works fine when the user clicks the buttons and toggles isRehearal.
The problem is, that the caution text does not appear when the initial value of isRehearsal is false and later the variable becomes true. When I change the initial value of isRehearsal to true, then the text disappears/appears fine when isRehearsal becomes false or true.
var isRehearsal by remember { mutableStateOf(false) }
Dialog(
onDismissRequest = { dismiss() },
DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(White, shape = RoundedCornerShape(8.dp))
.fillMaxWidth()
.padding(12.dp)
) { // the box does not resize when the caution text appears dynamically.
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(text = "Set speed")
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth()
) {
if (isRehearsal) {
Button(
onClick = { isRehearsal = false },
colors = ButtonDefaults.buttonColors(
backgroundColor = Colors.Red400
)
) {
Text(text = "Rehearsal ON")
}
} else {
Button(
onClick = { isRehearsal = true },
colors = ButtonDefaults.buttonColors(
backgroundColor = Colors.Green400
)
) {
Text(text = "Rehearsal OFF")
}
}
Button(onClick = { onClickStart(pickerValue) }) {
Text(text = "Start")
}
}
if (isRehearsal) { // BUG!! when the intial value of isRehearsal is false, then the text never appears even when the value becomes true.
Text(text = "Rehearsal does not count as high score") // <- The caution text
}
}
}
}
How should I change the Box block to make the box's height extend properly to be able to wrap caution text when a view appears dynamically?
Edit
If I change the catuion message part to like below, the text appears fine even when the initial value of isRehearsal is false. Therefore, I think that the issue is with the height of the Box composable.
if (isRehearsal) {
Text(text = "Rehearsal does not count as high score")
} else {
Spacer(modifier = Modifier.height(100.dp))
}
You can use DialogProperties() with
usePlatformDefaultWidth = false
as a workaround. Example:
Dialog(
onDismissRequest = {},
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Card(modifier = Modifier.padding(8.dp).wrapContentSize().animateContentSize()) {
// content
}
}
It looks like the topmost Dialog view size is not getting updated after the first recomposition. It's a known bug.
As a workaround until it's fixed, you can make a transparent topmost view take all the size available, and handle clicks same as dismissOnClickOutside option does:
Dialog(
onDismissRequest = { },
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxSize()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) {
// same action as in onDismissRequest
}
) {
// content
}
}
So I have this composable in my project ...
#Composable
private fun ShowDialog() {
var showText by remember { mutableStateOf(false) }
val text = if (showText) {
"Hide Text"
} else {
"Show Text"
}
Dialog(onDismissRequest = { }) {
Card(modifier = Modifier.padding(15.dp)) {
Column(modifier = Modifier.padding(15.dp)) {
AnimatedVisibility(visible = showText) {
Text(
text = "Here is the show text sample",
modifier = Modifier.padding(5.dp),
style = MaterialTheme.typography.body1,
color= Color.Black
)
}
Button(onClick = { showText = !showText }) {
Text(text = text)
}
}
}
}
}
If you have gone through the code, you might get what it is supposed to do. i.e it is basically a dialog with one text and a button below it. When the user clicks on a button the text above the button will toggle its visibility.
But the problem with the code is, When I click on the button, the text appears but the button gets invisible in other words the text takes the space and pushes a button to below. But yet the container in this case card or the column doesn't expand its height.
Is it supposed to work like that ? Or is this a bug?
I tried animateContentSize() on Column and Card but it didn't work. And checked similar questions on StackOverflow but didn't found any useful information.
Luckily, I found a temporary working answer for this problem,
What we need to use is just pass DialogProperties(usePlatformDefaultWidth = false) as properties parameter for dialog. This will make the dialog to resizable like this
#Composable
private fun ShowDialog() {
var showText by remember { mutableStateOf(false) }
val text = if (showText) {
"Hide Text"
} else {
"Show Text"
}
Dialog(
onDismissRequest = { },
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Card(
modifier = Modifier
.padding(15.dp)
.wrapContentWidth()
.animateContentSize()
) {
Column(modifier = Modifier.padding(15.dp).fillMaxWidth(1f)) {
AnimatedVisibility(visible = showText) {
Text(
text = "Sample",
modifier = Modifier
.padding(5.dp)
.fillMaxWidth(1f),
style = MaterialTheme.typography.body1,
color = Color.Black
)
}
Button(onClick = { showText = !showText }) {
Text(text = text)
}
}
}
}
}
Caution: It uses #ExperimentalComposeUiApi
This API is experimental and is likely to change in the future.
Building a simple keyboard is fairly simple and straightforward in Jetpack Compose.
I built a really simple KeyRow by using this:
Key.kt
#Composable
fun Key(modifier: Modifier = Modifier, label: String, onClick: () -> Unit) {
val shape = RoundedCornerShape(4.dp)
//TODO: make clickable outside but don't show ripple
Box(modifier = modifier
.padding(2.dp)
.clip(shape)
.clickable(onClick = onClick)
.background(Color.White)
.padding(vertical = 12.dp, horizontal = 4.dp), contentAlignment = Alignment.Center) {
Text(text = label, fontSize = 20.sp)
}
}
KeyRow.kt
#Composable
fun KeyRow(keys: List<String>) {
Row(modifier = Modifier.fillMaxWidth().background(color = grey200)) {
keys.forEach {
Key(modifier = Modifier.weight(1f), label = it, onClick = { })
}
}
}
That's what it looks like:
I want to achieve this animation:
However, I'm currently stuck with this
![4]
Hierachy
-Keyboard
--KeyRow
---KeyLayout
----Key
----KeyPressedOverlay (only visible when pressed)
My main problem is that I don't know how to show the KeyPressedOverlay Composale (which is larger than the Key Composable) without making the parent Layout larger. As a result, I need to overflow the parent layout in some way.
Not sure if it's the best way (probably not), but I found a solution using ConstraintLayout...
val keys = listOf("A", "B", "C", "D")
ConstraintLayout(
modifier = Modifier.graphicsLayer(clip = false)
) {
val refs = keys.map { createRef() }
refs.forEachIndexed { index, ref ->
val modifier = when (index) {
0 -> Modifier.constrainAs(ref) {
start.linkTo(parent.start)
}
refs.lastIndex -> Modifier.constrainAs(ref) {
start.linkTo(refs[index - 1].end)
end.linkTo(parent.end)
}
else -> Modifier.constrainAs(ref) {
start.linkTo(refs[index - 1].end)
end.linkTo(refs[index + 1].start)
}
}
val modifierPressed = Modifier.constrainAs(createRef()) {
start.linkTo(ref.start)
end.linkTo(ref.end)
bottom.linkTo(ref.bottom)
}
KeyboardKey(
keyboardKey = keys[index],
modifier = modifier,
modifierPressed = modifierPressed,
pressed = { s -> /* Do something with the key */}
)
}
}
One important detail here is graphicLayer(clip = false) (which is similar to the clipChildren in View Toolkit). Then, I'm creating a modifier to each key and to the pressed key. Noticed that the modifierPressed is aligned to the center/bottom of the other modifier.
Finally the KeyboardKey is described below.
#Composable
fun KeyboardKey(
keyboardKey: String,
modifier: Modifier,
modifierPressed: Modifier,
pressed: (String) -> Unit
) {
var isKeyPressed by remember { mutableStateOf(false) }
Text(keyboardKey, Modifier
.then(modifier)
.pointerInput(Unit) {
detectTapGestures(onPress = {
isKeyPressed = true
val success = tryAwaitRelease()
if (success) {
isKeyPressed = false
pressed(keyboardKey)
} else {
isKeyPressed = false
}
})
}
.background(Color.White)
.padding(
start = 12.dp,
end = 12.dp,
top = 16.dp,
bottom = 16.dp
),
color = Color.Black
)
if (isKeyPressed) {
Text(
keyboardKey, Modifier
.then(modifierPressed)
.background(Color.White)
.padding(
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 48.dp
),
color = Color.Black
)
}
}
This is the result I got:
Edit:
Adding some more logic, I was able to get this...
I hope it helps this time ;)
Here's the gist just in case...
https://gist.github.com/nglauber/4cb1573efba9024c008ea71f3320b4d8
I guess you're looking for the pressIndicatorGestureFilter modifier...
I tried this and worked for me...
var pressed by remember { mutableStateOf(false) }
val padding = if (pressed) 32.dp else 16.dp
Text("A", Modifier
.pressIndicatorGestureFilter(
onStart = {
pressed = true
},
onStop = {
pressed = false
},
onCancel = {
pressed = false
}
)
.background(Color.White)
.padding(start = 16.dp, end = 16.dp, top = padding, bottom = padding)
)