diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/CompositionLocals.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/CompositionLocals.skiko.kt index 770d56dd94907..c8f948a0e217d 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/CompositionLocals.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/CompositionLocals.skiko.kt @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:OptIn(ExperimentalMediaQueryApi::class) package androidx.compose.ui.platform @@ -25,7 +26,9 @@ import androidx.compose.runtime.ProvidedValue import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.LocalSaveableStateRegistry import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.InternalComposeUiApi +import androidx.compose.ui.LocalUiMediaScope import androidx.lifecycle.LifecycleOwner import androidx.savedstate.compose.LocalSavedStateRegistryOwner @@ -94,6 +97,7 @@ internal fun ProvidePlatformCompositionLocals( LocalPlatformScreenReader provides platformContext.screenReader, LocalPlatformWindowInsets provides platformContext.windowInsets, LocalPlatformPrefetchScheduler provides platformContext.prefetchScheduler, + LocalUiMediaScope provides platformContext.mediaEnvironment, androidx.lifecycle.compose.LocalLifecycleOwner provides platformContext.architectureComponentsOwner.lifecycleOwner, LocalSavedStateRegistryOwner provides platformContext.architectureComponentsOwner.savedStateRegistryOwner, LocalSaveableStateRegistry provides saveableStateRegistry, diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/DefaultHapticFeedback.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/DefaultHapticFeedback.skiko.kt deleted file mode 100644 index a0c91764e5190..0000000000000 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/DefaultHapticFeedback.skiko.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2020 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.platform - -import androidx.compose.ui.hapticfeedback.HapticFeedback -import androidx.compose.ui.hapticfeedback.HapticFeedbackType - -// TODO(demin): implement HapticFeedback -internal object DefaultHapticFeedback : HapticFeedback { - override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) { - } -} \ No newline at end of file diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/PlatformContext.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/PlatformContext.skiko.kt index c39614b2b162e..de983f435efe5 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/PlatformContext.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/PlatformContext.skiko.kt @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:OptIn(ExperimentalMediaQueryApi::class) + package androidx.compose.ui.platform import androidx.compose.runtime.getValue @@ -20,13 +22,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.ComposeUiFlags import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.ExperimentalMediaQueryApi import androidx.compose.ui.FrameRateCategory import androidx.compose.ui.InternalComposeUiApi +import androidx.compose.ui.UiMediaScope import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.hapticfeedback.HapticFeedback +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.InputMode import androidx.compose.ui.input.InputModeManager import androidx.compose.ui.input.pointer.PointerIcon @@ -45,6 +50,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeOptions import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.intl.LocaleList +import androidx.compose.ui.unit.Dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelStoreOwner @@ -156,7 +162,7 @@ interface PlatformContext { } val textToolbar: TextToolbar get() = EmptyTextToolbar - val hapticFeedback: HapticFeedback get() = DefaultHapticFeedback + val hapticFeedback: HapticFeedback get() = NoOpHapticFeedback fun setPointerIcon(pointerIcon: PointerIcon) = Unit val parentFocusManager: FocusManager get() = EmptyFocusManager @@ -217,6 +223,17 @@ interface PlatformContext { */ val prefetchScheduler: PlatformPrefetchScheduler get() = NoOpPlatformPrefetchScheduler + /** + * Media-related information exposed to the composition. + * + * This provides platform-specific environment details such as window posture, + * pointer precision, keyboard type, and device capabilities. The default + * implementation is a no-op environment that reports neutral values so code + * using media state remains safe on platforms that do not provide a richer + * implementation. + */ + val mediaEnvironment: UiMediaScope get() = NoOpMediaEnvironment + interface RootForTestListener { fun onRootForTestCreated(root: PlatformRootForTest) fun onRootForTestDisposed(root: PlatformRootForTest) @@ -389,3 +406,27 @@ internal class DelegateRootForTestListener : PlatformContext.RootForTestListener } } } + +private object NoOpHapticFeedback : HapticFeedback { + override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) { + } +} + +private object NoOpMediaEnvironment : UiMediaScope { + override val windowPosture: UiMediaScope.Posture + get() = UiMediaScope.Posture.Flat + override val windowWidth: Dp + get() = Dp.Unspecified + override val windowHeight: Dp + get() = Dp.Unspecified + override val pointerPrecision: UiMediaScope.PointerPrecision + get() = UiMediaScope.PointerPrecision.None + override val keyboardKind: UiMediaScope.KeyboardKind + get() = UiMediaScope.KeyboardKind.None + override val hasMicrophone: Boolean + get() = false + override val hasCamera: Boolean + get() = false + override val viewingDistance: UiMediaScope.ViewingDistance + get() = UiMediaScope.ViewingDistance.Near +} \ No newline at end of file diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DefaultHapticFeedback.web.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DefaultHapticFeedback.web.kt index 5dae6a1178b53..de75a82314b6f 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DefaultHapticFeedback.web.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/platform/DefaultHapticFeedback.web.kt @@ -39,8 +39,6 @@ internal class WebHapticFeedback : HapticFeedback { private val SoftTickVibrationPattern = vibrationPatternOf(6) private val LongPressVibrationPattern = vibrationPatternOf(0, 30) private val VirtualKeyVibrationPattern = vibrationPatternOf(0, 20) - - fun webHapticFeedbackOrDefault(): HapticFeedback = if (isVibrationSupported()) WebHapticFeedback() else DefaultHapticFeedback } override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) { @@ -81,7 +79,7 @@ private fun vibrate(pattern: JsArray) { js("window.navigator.vibrate(pattern)") } -private fun isVibrationSupported(): Boolean = js( +internal fun isVibrationSupported(): Boolean = js( //language=javascript """ typeof window !== 'undefined' && diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindowInternal.web.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindowInternal.web.kt index 1b62a170f55c5..0c6a39dcb6d2c 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindowInternal.web.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindowInternal.web.kt @@ -79,6 +79,7 @@ import androidx.compose.ui.platform.PlatformOutOfFrameExecutor import androidx.compose.ui.platform.PlatformPrefetchScheduler import androidx.compose.ui.platform.WebPrefetchScheduler import androidx.compose.ui.platform.isIdleCallbackSupported +import androidx.compose.ui.platform.isVibrationSupported import androidx.compose.ui.scene.ComposeSceneDragAndDropNode import androidx.compose.ui.scene.ComposeScenePointer import androidx.compose.ui.scene.PointerEventResult @@ -309,7 +310,7 @@ internal class ComposeWindow( } override val hapticFeedback by lazy(LazyThreadSafetyMode.NONE) { - WebHapticFeedback.webHapticFeedbackOrDefault() + if (isVibrationSupported()) WebHapticFeedback() else super.hapticFeedback } override val prefetchScheduler: PlatformPrefetchScheduler =