AnimatedVisibility not working with dialog - android-animation

I am trying to animate this dialog coming into the screen but animated visibility does not seem to work here. I've tried many things but none seem to work. Any ideas why this would not work and/or a possible work around?
val openDialog = remember { mutableStateOf(false) }
AnimatedVisibility(
visible = openDialog.value,
enter = fadeIn(animationSpec = tween(durationMillis = 1250)),
exit = scaleOut(),
) {
Dialog(onDismissRequest = { /*TODO*/ }) {
Text("Test")
}
}

Related

Problem with custom dialog show in jetpack compose. Not correct background

I'm created custom dialog from common class in ini
init {
activity.setContent {
CustomDialog(viewModel)
}
}
#Composable
fun CustomDialog(viewModel: ViewModel){
Dialog(
onDismissRequest = { },
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true)
) {
}
}
But under dialog background is an empty activity, but must be a preference activity.
Not correct composable:
correct dialog via XML:
I tried, but didn't help
Surface(modifier = Modifier.background(Color.Transparent)) {
CustomDialog(viewModel)
}
```
Exampe:
Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) {
Column(
modifier = Modifier
.wrapContentSize()
.background(
color = MaterialTheme.colors.surface,
shape = RoundedCornerShape(size = 16.dp)
).clip(shape = RoundedCornerShape(size = 16.dp))
) {
//.....
}
}
Found solution:
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog?.window?.setDimAmount(0.0f)

Multiple different pointerInput

I'm currently trying to implement the option of switching between a composable being either zoomable, pannable (dragging the surface) or neither of those. What works so far is toggling the respective buttons, with the expected result. What does not work is toggling one button from the other - this gives the unexpected result of keeping the functionality of the first button.
For example, let's say zoom is active. When I then press the pan button, the background highlighting changes accordingly, all test-logs show the expected state - but the surface is still zoomable, NOT draggable. I first have to manually disable zoom. Any ideas as to why this might happen?
Modifier.run {
if (zoomEnabled) {
this.pointerInput(Unit) {
detectTransformGestures { _, _, zoom, _ ->
passScale(zoom)
}
}
} else if (panEnabled) {
this.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
passOffsetX(dragAmount.x / 3)
passOffsetY(dragAmount.y / 3)
}
}
} else
this
}
Buttons:
#Composable
fun TopBarAction(
zoomEnabled: Boolean,
passZoomEnabled: (Boolean) -> Unit,
panEnabled: Boolean,
passPanEnabled: (Boolean) -> Unit
) {
IconToggleButton(
checked = zoomEnabled,
onCheckedChange = {
passPanEnabled(false)
passZoomEnabled(it)
},
modifier = Modifier
.background(
if (zoomEnabled) Color.LightGray else Color.Transparent,
shape = CircleShape
),
enabled = true
) {
Icon(...)
}
IconToggleButton(
checked = panEnabled,
onCheckedChange = {
passZoomEnabled(false)
passPanEnabled(it)
},
modifier = Modifier
.background(
if (panEnabled) Color.LightGray else Color.Transparent,
shape = CircleShape
),
enabled = true
) {
Icon(...)
}
}
Using the normal .pointerInput modifier with the condition inside instead of run does not recognize any input at all and detectTransformGesture's pan did not behave the way I need it to (though this is probably what I will use if all else fails)
First issue is PointerInput creates a closure with key or keys and uses old values unless the keys you set change.
You need to set keys accordingly.
Second issue is even if you set keys, detectDragGestures or detectTransformGestures will consume events so PointerInputChange above won't get it if first one has already consumed event.
What consume() or consumeAllChanges() does is it prevents pointerInput above it or on parent to receive events by returning PointeInputChange.positionChange() Offset.Zero, PointerInputChange.isConsumed true. Since drag, scroll or transform gestures check if PointeInputChange.isConsumed is true they will never get any event if you consume them in previous pointerInput.
Drag source code for instance
suspend fun PointerInputScope.detectDragGestures(
onDragStart: (Offset) -> Unit = { },
onDragEnd: () -> Unit = { },
onDragCancel: () -> Unit = { },
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
) {
forEachGesture {
awaitPointerEventScope {
val down = awaitFirstDown(requireUnconsumed = false)
var drag: PointerInputChange?
var overSlop = Offset.Zero
do {
drag = awaitPointerSlopOrCancellation(
down.id,
down.type
) { change, over ->
change.consume()
overSlop = over
}
// ! EVERY Default GESTURE HAS THIS CHECK
} while (drag != null && !drag.isConsumed)
if (drag != null) {
onDragStart.invoke(drag.position)
onDrag(drag, overSlop)
if (
!drag(drag.id) {
onDrag(it, it.positionChange())
it.consume()
}
) {
onDragCancel()
} else {
onDragEnd()
}
}
}
}
}
Instead of using Modifier.run you can chain Modifier.pointerInput()
Modifier
.pointerInput(keys){
// Gesture scope1
if(zoomEnabled){...}
}
.pointerInput(keys){
// Gesture scope2
if(panEnabled){
....
}
}
Events first go to gesture scope 2 then gesture scope 1
Created a small sample that you can observer how gestures change and propagate and how they reset with keys
#Composable
private fun MyComposable() {
var zoomEnabled by remember { mutableStateOf(false) }
var dragEnabled by remember { mutableStateOf(false) }
var text by remember { mutableStateOf("") }
Column() {
val modifier = Modifier
.size(400.dp)
.background(Color.Red)
.pointerInput(zoomEnabled) {
if (zoomEnabled) {
detectTransformGestures { centroid, pan, zoom, rotation ->
println("ZOOOMING")
text = "ZOOMING centroid: $centroid"
}
}
}
.pointerInput(key1 = dragEnabled, key2= zoomEnabled) {
if (dragEnabled && !zoomEnabled) {
detectDragGestures { change, dragAmount ->
println("DRAGGING")
text = "DRAGGING $dragAmount"
}
}
}
Box(modifier = modifier)
Text(text = text)
OutlinedButton(onClick = { zoomEnabled = !zoomEnabled }) {
Text("zoomEnabled: $zoomEnabled")
}
OutlinedButton(onClick = { dragEnabled = !dragEnabled }) {
Text("dragEnabled: $dragEnabled")
}
}
}
You can create your own behavior using the answer and snippet above.

Row drawn over columns in compose for desktop

I am developing a little application in compose for desktop on my windows machine. I put a OutlinedTextField into a column, which is in row. The problem is, that I cant see any text within it. Neither the default, nor am I able to edit the text. The box is focusable, but not editable.
I discovered, that the row is drawn above of my column. I assume that the row is swallow the event, or overpaint the text. I tried to set the z-index to high, but it didn't have any effect.
#Composable
fun menuInLeftSplitContent() = Row(Modifier.padding(all 5.dp).zIndex(-1F).background(Color
.Black)) {
Column {
IconButton(modifier = Modifier.padding(end = 5.dp), onClick = {}) {
Icon(Icons.Rounded.Add, "Add")
}
}
Column(modifier = Modifier.zIndex(20F)) {
val mutableText = mutableStateOf("Hello World")
OutlinedTextField(
value = mutableText.value,
onValueChange = { },
modifier = Modifier.fillMaxSize(),
singleLine = true,
leadingIcon = { Icons.Filled.Search },
enabled = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Characters,
autoCorrect = false,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.None
),
maxLines = 1,
readOnly = false
)
}}
If the column wasn't overpainted by the row, you would see the picture beneath.
This is how it should look like.

How to test onDismissRequest attribute of AlertDialog?

In its simplest form I have this dialog:
#Composable
fun MyDialog(
showDialogState: MutableState<Boolean>
) {
if (showDialogState.value) {
AlertDialog(onDismissRequest = { showDialogState.value = false },
// Other irrelevant attributes have been omitted
)
}
}
How can I trigger "onDismissRequest" on this composable in Robolectric?
This is usually how I build my composable tests by the way:
#Config(sdk = [Build.VERSION_CODES.O_MR1])
#RunWith(AndroidJUnit4::class)
#LooperMode(LooperMode.Mode.PAUSED)
class MyDialogTest {
#get:Rule
val composeTestRule = createComposeRule()
#Test
fun `MyDialog - when showing state and dismissed - changes showing state`() {
val state = mutableStateOf(true)
composeTestRule.setContent {
MyDialog(
showDialogState = state
)
}
// TODO: How do I trigger dismiss!?
assertFalse(state.value)
}
}
Compose version: 1.1.0-rc01
Android Gradle Plugin version: 7.0.4
Robolectric version: 4.7.3
I don't think this is possible at the moment. I have written this test to confirm:
val onButtonPressed = mock<() -> Unit>()
composeTestRule.setContent {
Scaffold(topBar = {
TopAppBar {
Text(text = "This test does not work")
}
}) {
AlertDialog(
onDismissRequest = {},
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
),
title = { Text(text = "This is a dialog")},
confirmButton = { Button(onClick = {}) {
Text(text = "Confirm")
}}
)
Column(modifier = Modifier.fillMaxSize()) {
Spacer(modifier = Modifier.weight(1f))
Button(onClick = onButtonPressed) {
Text(text = "test")
}
}
}
}
composeTestRule.onNode(isDialog()).assertExists()
composeTestRule.onNodeWithText("test", ignoreCase = true).performClick()
verify(onButtonPressed).invoke()
composeTestRule.onNode(isDialog()).assertDoesNotExist()
Even though the button is "behind" the dialog, it receives click events without dismissing the dialog.
Manual testing has confirmed that the implementation works, so perhaps a UIAutomator test could automate this, but that seems like an overly complicated way of solving this issue.
I quote the official documentation:
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.
https://foso.github.io/Jetpack-Compose-Playground/material/alertdialog/

Wheelnav.js, not clickable NavItems

I've started using Wheelnav.js. Currently my Wheel is toogled on/off with pressing and releasing X. I also got some hover effects, my Problem is that my NavItems are Clickable.. if i click a item it is "selected" and i cant hover over it anymore.
https://gyazo.com/29f73fd2fb0f8bbf1f634931686c3ec6
In this example i clicked on Item1
You have to set the selected property to false and refresh the wheel.
window.onload = function () {
wheel = new wheelnav("wheelDiv");
wheel.createWheel();
wheel.animateFinishFunction = disableSelected;
};
var disableSelected = function () {
for (var i = 0; i < this.navItemCount; i++) {
if (this.navItems[i].selected) {
this.navItems[i].selected = false;
this.navItems[i].refreshNavItem(true);
}
}
}