How to show the content of a dialog without shadow? - kotlin

I am displaying a Dialog with Jetpack Compose in my application. The Dialog contains a Google map. The problem is that this map appears dark, as if it has a shadow on it:
If you notice, it is displayed in exactly the same way as what is below the Dialog. How can I make this map appear clear without any shadow?
My code:
Dialog:
#Composable
fun DialogCustom(
data: Data
onDismiss: () -> Unit
) {
Dialog(onDismissRequest = onDismiss) {
Card(
backgroundColor = Color.Green,
elevation = 20.dp,
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 20.dp,
vertical = 8.dp
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
) {
CustomCardTitle(
data= data
color = Color.Green
)
CustomMap(
lat = data.lat,
lon = data.lon
)
}
}
}
}
Map:
#Composable
fun CustomMap(
lat: Double,
lon: Double
) {
val mapView = rememberMapViewWithLifeCycle()
Column(
modifier = Modifier
.background(Color.White)
) {
AndroidView(
{
mapView
}
) {
mapView.getMapAsync {
val map = it
map.uiSettings.isZoomControlsEnabled = false
map.addMarker(marker(title, lat, lon, 250f))
map.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(lat, lon), 16f))
map.mapType = GoogleMap.MAP_TYPE_HYBRID
}
}
}
}

Related

How can I position snackbar on top of each other with floating action button in kotlin compose?

I have been doing dictionary app for a while. when I delete dictionary snackBar shows and writes dictionary is deleted but there is a floating action button and when the snackBar appears on the screen ,the snackbar appears above the floating action button, I don't want it to appear on it. It just stays on the screen for 1-2 seconds. I want the floating action button and snackbar to appear on top of each other. I couldn't adapt this to my own code. How can I do it ? I will share my code and image
CreateYourOwnDictionaryScreen
#Composable
fun CreateYourOwnDictionaryScreen(
navController: NavController,
viewModel: CreateYourOwnDictionaryViewModel = hiltViewModel()
) {
val scaffoldState = rememberScaffoldState()
val state = viewModel.state.value
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
backgroundColor = bar,
title = {
androidx.compose.material3.Text(
text = "your dictionaries",
modifier = Modifier.fillMaxWidth(),
color = Color.White,
fontSize = 22.sp
)
},
navigationIcon = {
IconButton(onClick = {
navController.navigate(Screen.MainScreen.route)
}) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Go Back"
)
}
}
)
},
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
FloatingActionButton(
onClick = { navController.navigate(Screen.CreateDicScreen.route) },
backgroundColor = bar,
) {
Icon(Icons.Filled.Add, "fab")
}
}
) {
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(state.dictionaries) { dictionary ->
CreateYourOwnDictionaryItem(
dictionary = dictionary,
modifier = Modifier
.fillMaxWidth()
.clickable {
},
onDeleteClick = {
viewModel.onEvent(
CreateYourOwnDictionaryEvents.DeleteDictionary(dictionary)
)
scope.launch {
val result = scaffoldState.snackbarHostState.showSnackbar(
message = "dictionary is deleted",
actionLabel = "Undo",
duration = SnackbarDuration.Short
)
}
},
onEditClick = {
})
}
}
}
}
}
}
Image
I'm afraid this is difficult in Compose, you can dig the ScaffoldLayout and you'll find this code block.
val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight)
} else {
0
}
Scaffold will always offset the snackbar on top of a fab or a bottombar
And based on the answer in this post regarding material specs
This is specifically one of the "Don't" examples from the Material guidelines: https://material.io/components/snackbars#behavior
Making sure visual elements don't move out from underneath (say, when users are about to tap the FAB) is one of the key points to making a predictable UI
Also based on the Material Guidelines
Consecutive snackbars should appear above persistent bottom navigation

Push Front View up with Keyboard, don't push Background View up in Kotlin Compose

I want to do something like this:
Where there is a view that pops on top of the open keyboard.
I've tried to do this, and I have this so far:
However, when I put this view in a Box, as the second view, the first view is also pushed up, here's the code:
#OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
#ExperimentalComposeUiApi
fun Modifier.bringIntoViewAfterImeAnimation(): Modifier = composed {
var focusState by remember { mutableStateOf<FocusState?>(null) }
val relocationRequester = remember { BringIntoViewRequester() }
val isImeVisible = WindowInsets.isImeVisible
LaunchedEffect(
isImeVisible,
focusState,
relocationRequester
) {
if (isImeVisible && focusState?.isFocused == false) {
relocationRequester.bringIntoView()
}
relocationRequester.bringIntoView()
}
bringIntoViewRequester(relocationRequester).onFocusChanged { focusState = it }
}
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun SpaceCreator(navController: NavController) {
Column(
modifier = Modifier.fillMaxSize().clip(RoundedCornerShape(10.dp)),
verticalArrangement = Arrangement.Bottom
) {
SpaceCreatorContainer()
}
}
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun SpaceCreatorContainer() {
Card(
modifier = Modifier
.bringIntoViewAfterImeAnimation()
.shadow(elevation = 10.dp, shape = RoundedCornerShape(10.dp), clip = true)
.background(color = colors.background)
) {
SpaceCreatorWrapper()
}
}
#Composable
fun SpaceCreatorWrapper() {
val localFocusManager = LocalFocusManager.current
Column(
modifier = Modifier.padding(15.dp).clip(RoundedCornerShape(10.dp))
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Top Text"
)
Text(text = "Content")
OutlinedTextField(
value = "ss",
onValueChange = { },
label = { Text("Email Address") },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Done,
),
keyboardActions = KeyboardActions(
onDone = { localFocusManager.clearFocus() }
)
)
OutlinedTextField(
value = "ss",
onValueChange = { },
label = { Text("Email Address") },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { localFocusManager.clearFocus() }
)
)
Text(text = "Content")
}
}
That's the code for SpaceCreator(), and then I add it to a Box where it's supposed to float over another view, like so:
BoxWithConstraints(
propagateMinConstraints = true
) {
Box(
modifier = Modifier
.fillMaxSize()
) {
MainView()
SpaceCreator(navController = navController)
}
}
How do I only push up the second view in the Box when the keyboard is opened, and not the entire box?
Also,
Currently, I have a AnimationNavigation screen which is a Box(fillMaxSize), and the content of the keyboard modal stuck to the bottom. I also have a auto focus on the input, which is making the navigation a bit slow, unlike other apps I've seen.
Is there a smooth way to do this on low-mid-range devices like Samsung A23?
Thank you.

Draw a circle behind BadgeBox to make it more visible, circle getting too small

My code:
IconButton(onClick = { /*TODO*/ }) {
BadgedBox(
modifier = Modifier.drawBehind
{
drawCircle(
color = Color.LightGray,
center = Offset(
this.size.maxDimension / 2,
this.size.maxDimension / 2
),
radius = 10f
)
},
badge = { Badge(modifier = Modifier.size(12.dp)) {} },
) {
Icon(imageVector = Icons.Outlined.Notifications, contentDescription = "")
}
Current results:
The light-gray circle, shall cover in background behind and look like a circular button. But, currently, it doesn't look like that. I tried with maxDimension. I am doing this in Kotlin with Jetpack Compose.
Desired:
EDIT: Added this line,
modifier = Modifier.clip(CircleShape).background(Color.LightGray)
Now it looks like this:
However, this looks very strange and does not provide the desired results.
You can do it using one box that covers both content and notification circle
#Composable
private fun MyBadgeBox(
badge: #Composable () -> Unit,
notificationRadius: Dp = 8.dp,
notification: Boolean
) {
Box {
Box(modifier = Modifier.padding(notificationRadius)) {
badge()
}
Box(
modifier = if (notification) Modifier
.padding(notificationRadius / 2)
.align(Alignment.TopEnd)
.size(notificationRadius * 2)
.drawBehind {
drawCircle(Color.Red)
} else Modifier)
}
}
Usage
val modifier = Modifier
.size(40.dp)
.background(
color = Color.LightGray.copy(.5f),
shape = CircleShape
)
.clip(CircleShape)
.clickable { }
.padding(6.dp)
MyBadgeBox(
badge = {
Icon(
modifier = modifier,
imageVector = Icons.Outlined.Notifications,
contentDescription = ""
)
}, notification = false
)
MyBadgeBox(
badge = {
Icon(
modifier = modifier,
imageVector = Icons.Outlined.Notifications,
contentDescription = ""
)
}, notification = true
)
}
Result
You can use something like:
BadgedBox(
badge = { Badge(modifier = Modifier.size(12.dp)) {} },
) {
val radius = 16.dp
val shape = RoundedCornerShape(radius)
Box(
contentAlignment= Alignment.Center,
modifier = Modifier
.defaultMinSize(minWidth = radius * 2, minHeight = radius * 2)
.background(
color = Color.LightGray,
shape = shape
)
.clip(shape)
,
) {
Icon(imageVector = Icons.Outlined.Notifications, contentDescription = "")
}
}

Compose Desktop AlertDialog rounded corners

In a compose desktop application I'm displaying an AlertDialog with rounded corners shape but there still appears a white rectangle at the corners.
Here is my code:
AlertDialog(
modifier = Modifier
.size(280.dp, 260.dp)
.shadow(elevation = 20.dp),
onDismissRequest = {},
buttons = {
Button(
modifier = Modifier.padding(start = 100.dp, top = 0.dp),
onClick = { onClose() }
) {
Text(
text = "OK",
textAlign = TextAlign.Center
)
}
},
title = {
Text(
"A Title"
)
},
text = {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Some Text")
}
},
shape = RoundedCornerShape(24.dp),
backgroundColor = Color.Red
)
How can I get rid of the background white corners that are visible behind the dialog.
#Composable
fun CustomAlertDialog(showingDialog: Boolean = false, onClickButton: () -> Unit) {
val color = if (isSystemInDarkTheme()) WhiteColor else Black90
val openDialog = remember { mutableStateOf(true) }
if (openDialog.value) {
AlertDialog(
modifier = Modifier.clip(RoundedCornerShape(12.sdp)), // corner rounded
onDismissRequest = {
openDialog.value = false
},
title = {
Text(text = "Title")
},
text = {
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center
) {
Text(stringResource(R.string.send_password_email), color = color)
}
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.sdp),
horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.fillMaxWidth()
.clickable { openDialog.value = false }, text = "Ok", color = ColorMain, textAlign = TextAlign.Center
)
}
}
)
}
}
I was having an issue with .clip() not working because of the fact that Order of modifiers matters, the .clip() effect was being overwritten by my .background() modifier.
This doesn't work:
modifier = Modifier
.background(MyTheme.colors.background) // overwrites .clip()
.clip(RoundedCornerShape(28.dp)), // .clip() evaluated first
This does work:
modifier = Modifier
.clip(RoundedCornerShape(28.dp)) // then we can safely .clip()
.background(MyTheme.colors.background), // .background() evaluated first
Here is what the AlertDialog code looks like:
AlertDialog(
modifier = Modifier
.clip(RoundedCornerShape(28.dp))
.background(MyTheme.colors.background),
onDismissRequest = { /* On Dismiss */ },
text = { /* Text */ },
buttons = { /* Buttons */ },
title = { /* Title * / },
)
Just remember that the order of the modifiers is of significant importance.

Kotlin Jetpack Compose DragGesture property cancel the scrolling of my View

I'am new to jetpack compose and i really liked it. But ran into a problem : I want know if my view is swiped up or down so i created a LazyColumn with some item in it to be able to scroll something. It work fine but i would like to access the Gesture property to know if the view is scrolled down or up, here is my code :
LazyColumn{
items (100){
Text(
text = "Item $it",
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxSize()
.padding(vertical = 24.dp)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
//change.consumeAllChanges()// i don't know if this does something, i tried to remove it
println("detectDragGestures")
val (x, y) = dragAmount
if(abs(x) < abs(y)){
if (y > 0)
println("drag down")
else
println("drag Up")
}
}
})
}
}
This work, i can detect if the view is scrolled down or up, the problem is when i tap on the item and scroll, i get the right print but the view isn't scrolled, i have to click between item to be able to scroll.
I don't really know how gesture work in jetpack compose but i would like to get the direction of the swipe without preventing my view to be scrolled.
I managed to detect scroll direction with using Column instead of LazyColumn.
I hope it can lead you.
#Composable
fun ScrollDetect() {
val scrollState = rememberScrollState()
var dragPosition by remember {
mutableStateOf(0)
}
LaunchedEffect(key1 = scrollState.value, block = {
if (scrollState.value > dragPosition)
Log.e("dragging", "up")
else
Log.e("dragging", "down")
dragPosition = scrollState.value
})
Column(
modifier = Modifier
.verticalScroll(scrollState)
)
{
repeat(100) {
Text(
text = "Item $it",
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxSize()
.padding(vertical = 24.dp)
)
}
}
}
Explanation of how gesture system in Jetpack Compose works in detail here and here
change.consumeAllChanges() is deprecated now, partial consumes are deprecated. change.consume() is the only consume function to be used. What consume does is it returns change.isConsumed true and because of that any drag, scroll, transform gesture stops progressing or receiving events. detectDragGestures and detectTransformGestures consume events by default so next
Modifier.pointerInput() doesn't get these events. What you commented doesn't mean anything since drag already consumes events.
Here it's source code.
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 movable 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()
}
}
}
}
}
What you can do is using nestedScroll on parent of LazyColumn as here
#Composable
private fun NestedScrollExample() {
var text by remember { mutableStateOf("") }
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
text = "onPreScroll()\n" +
"available: $available\n" +
"source: $source\n\n"
return super.onPreScroll(available, source)
}
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
): Offset {
text += "onPostScroll()\n" +
"consumed: $consumed\n" +
"available: $available\n" +
"source: $source\n\n"
return super.onPostScroll(consumed, available, source)
}
override suspend fun onPreFling(available: Velocity): Velocity {
text += "onPreFling()\n" +
" available: $available\n\n"
return super.onPreFling(available)
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
text += "onPostFling()\n" +
"consumed: $consumed\n" +
"available: $available\n\n"
return super.onPostFling(consumed, available)
}
}
}
Column() {
Box(
Modifier
.weight(1f)
.nestedScroll(nestedScrollConnection)
) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(100) {
Text(
text = "I'm item $it",
modifier = Modifier
.shadow(1.dp, RoundedCornerShape(5.dp))
.fillMaxWidth()
.background(Color.LightGray)
.padding(12.dp),
fontSize = 16.sp,
color = Color.White
)
}
}
}
Spacer(modifier = Modifier.height(10.dp))
Text(
text = text,
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.height(250.dp)
.padding(10.dp)
.background(BlueGrey400),
fontSize = 16.sp,
color = Color.White
)
}
}
It will return if you scroll up or down on your LazyColumn, if you want to do this only when your items are being scrolled you can do it as
Column(modifier = Modifier.fillMaxSize()) {
var text by remember { mutableStateOf("Drag to see effects") }
Text(text)
LazyColumn {
items(100) {
Text(
text = "Item $it",
fontSize = 24.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.border(3.dp, Color.Green)
.fillMaxSize()
.padding(vertical = 24.dp)
.pointerMotionEvents(Unit,
onDown = {
it.consume()
},
onMove = { change ->
val position = change.positionChange()
val dragText = if (position.y < 0) {
"drag up"
} else if (position.y > 0) {
"drag down"
} else {
"idle"
}
text = "position: ${change.position}\n" +
"positionChange: ${change.positionChange()}\n" +
"dragText: $dragText"
}
)
)
}
}
}
Modifier.pointerMotionEvents() is a gesture Modifier i wrote, it's available here.