From 4c533c6f01138dc2b7e1f376394d98cb522aa8a8 Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Mon, 10 Jul 2023 13:40:10 +0200 Subject: [PATCH 1/3] Expect Dialog and DialogProperties in common --- .../ui/window/AndroidDialog.android.kt | 18 +++-- .../androidx/compose/ui/window/Dialog.kt | 66 +++++++++++++++++++ .../compose/ui/window/Dialog.skiko.kt | 51 ++++++++++++++ 3 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/window/Dialog.kt create mode 100644 compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt index 346e6f3732de8..aec482564d768 100644 --- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt +++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt @@ -85,13 +85,23 @@ import kotlin.math.roundToInt * set to `false` for Android [R][Build.VERSION_CODES.R] and earlier. */ @Immutable -class DialogProperties constructor( - val dismissOnBackPress: Boolean = true, - val dismissOnClickOutside: Boolean = true, +actual class DialogProperties constructor( + actual val dismissOnBackPress: Boolean = true, + actual val dismissOnClickOutside: Boolean = true, val securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit, val usePlatformDefaultWidth: Boolean = true, val decorFitsSystemWindows: Boolean = true ) { + actual constructor( + dismissOnBackPress: Boolean, + dismissOnClickOutside: Boolean + ) : this( + dismissOnBackPress = dismissOnBackPress, + dismissOnClickOutside = dismissOnClickOutside, + securePolicy = SecureFlagPolicy.Inherit, + usePlatformDefaultWidth = true, + decorFitsSystemWindows = true + ) constructor( dismissOnBackPress: Boolean = true, @@ -148,7 +158,7 @@ class DialogProperties constructor( * @param content The content to be displayed inside the dialog. */ @Composable -fun Dialog( +actual fun Dialog( onDismissRequest: () -> Unit, properties: DialogProperties = DialogProperties(), content: @Composable () -> Unit diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/window/Dialog.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/window/Dialog.kt new file mode 100644 index 0000000000000..8a822a3066c2b --- /dev/null +++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/window/Dialog.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2023 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.compose.ui.window + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable + + +/** + * Properties used to customize the behavior of a [Dialog]. + * + * @property dismissOnBackPress whether the popup can be dismissed by pressing the back button + * * on Android or escape key on desktop. + * If true, pressing the back button will call onDismissRequest. + * @property dismissOnClickOutside whether the dialog can be dismissed by clicking outside the + * dialog's bounds. If true, clicking outside the dialog will call onDismissRequest. + */ +@Immutable +expect class DialogProperties( + dismissOnBackPress: Boolean = true, + dismissOnClickOutside: Boolean = true +) { + val dismissOnBackPress: Boolean + val dismissOnClickOutside: Boolean +} + + +/** + * Opens a dialog with the given content. + * + * A dialog is a small window that prompts the user to make a decision or enter + * additional information. A dialog does not fill the screen and is normally used + * for modal events that require users to take an action before they can proceed. + * + * The dialog is visible as long as it is part of the composition hierarchy. + * In order to let the user dismiss the Dialog, the implementation of [onDismissRequest] should + * contain a way to remove the dialog from the composition hierarchy. + * + * Example usage: + * + * @sample androidx.compose.ui.samples.DialogSample + * + * @param onDismissRequest Executes when the user tries to dismiss the dialog. + * @param properties [DialogProperties] for further customization of this dialog's behavior. + * @param content The content to be displayed inside the dialog. + */ +@Composable +expect fun Dialog( + onDismissRequest: () -> Unit, + properties: DialogProperties = DialogProperties(), + content: @Composable () -> Unit +) diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt new file mode 100644 index 0000000000000..947baa2fbf61e --- /dev/null +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2023 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.compose.ui.window + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable + +@Immutable +actual class DialogProperties actual constructor( + actual val dismissOnBackPress: Boolean, + actual val dismissOnClickOutside: Boolean +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is DialogProperties) return false + + if (dismissOnBackPress != other.dismissOnBackPress) return false + if (dismissOnClickOutside != other.dismissOnClickOutside) return false + + return true + } + + override fun hashCode(): Int { + var result = dismissOnBackPress.hashCode() + result = 31 * result + dismissOnClickOutside.hashCode() + return result + } +} + +@Composable +actual fun Dialog( + onDismissRequest: () -> Unit, + properties: DialogProperties, + content: @Composable () -> Unit +) { + // TODO +} From cced8f3666d788671e2fecb7323649770eb23746 Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Mon, 10 Jul 2023 14:08:27 +0200 Subject: [PATCH 2/3] Use PopupLayout as platform agnostic dialog --- .../compose/ui/window/Dialog.skiko.kt | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt index 947baa2fbf61e..767a210f320b7 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt @@ -18,6 +18,14 @@ package androidx.compose.ui.window import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.type +import androidx.compose.ui.unit.IntOffset @Immutable actual class DialogProperties actual constructor( @@ -41,11 +49,33 @@ actual class DialogProperties actual constructor( } } +@OptIn(ExperimentalComposeUiApi::class) @Composable actual fun Dialog( onDismissRequest: () -> Unit, properties: DialogProperties, content: @Composable () -> Unit ) { - // TODO + val popupPositioner = remember { + AlignmentOffsetPositionProvider( + alignment = Alignment.Center, + offset = IntOffset(0, 0) + ) + } + PopupLayout( + popupPositionProvider = popupPositioner, + focusable = true, + if (properties.dismissOnClickOutside) onDismissRequest else null, + onKeyEvent = { + if (properties.dismissOnBackPress && + it.type == KeyEventType.KeyDown && it.key == Key.Escape + ) { + onDismissRequest() + true + } else { + false + } + }, + content = content + ) } From f4860882da173c75010364c0f15bb24e40dffea8 Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Tue, 11 Jul 2023 11:33:38 +0200 Subject: [PATCH 3/3] Dim dialog background --- .../kotlin/androidx/compose/ui/window/Dialog.skiko.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt index 767a210f320b7..7b1e1c3ea4860 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/window/Dialog.skiko.kt @@ -21,6 +21,9 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key @@ -66,6 +69,9 @@ actual fun Dialog( popupPositionProvider = popupPositioner, focusable = true, if (properties.dismissOnClickOutside) onDismissRequest else null, + modifier = Modifier.drawBehind { + drawRect(Color.Black.copy(alpha = 0.4f)) + }, onKeyEvent = { if (properties.dismissOnBackPress && it.type == KeyEventType.KeyDown && it.key == Key.Escape