diff --git a/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/IosSpecificFeaturesExample.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/IosSpecificFeaturesExample.kt new file mode 100644 index 0000000000000..1b2c12aadda2b --- /dev/null +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/IosSpecificFeaturesExample.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2024 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.mpp.demo + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.unit.dp + +val HapticFeedbackExample = Screen.Example("Haptic feedback") { + val feedback = LocalHapticFeedback.current + + Column(modifier = Modifier.padding(16.dp)) { + Button(onClick = { + feedback.performHapticFeedback(HapticFeedbackType.TextHandleMove) + }) { + Text("TextHandleMove") + } + + Spacer(Modifier.height(16.dp)) + + Button(onClick = { + feedback.performHapticFeedback(HapticFeedbackType.LongPress) + }) { + Text("LongPress") + } + } +} + +val IosSpecificFeatures = Screen.Selection( + "iOS-specific features", + NativeModalWithNaviationExample, + HapticFeedbackExample, +) \ No newline at end of file diff --git a/compose/mpp/demo/src/uikitMain/kotlin/NativePopupExample.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/NativePopupExample.kt similarity index 89% rename from compose/mpp/demo/src/uikitMain/kotlin/NativePopupExample.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/NativePopupExample.kt index 30a95c2391589..ea863464c5942 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/NativePopupExample.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/NativePopupExample.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2024 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.mpp.demo + import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -11,10 +29,8 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material.Button import androidx.compose.material.Text -import androidx.compose.mpp.demo.Screen import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -31,8 +47,6 @@ import androidx.compose.ui.window.ComposeUIViewController import androidx.lifecycle.LifecycleEventObserver import platform.UIKit.* import platform.Foundation.* -import platform.darwin.dispatch_async -import platform.darwin.dispatch_get_main_queue /* * Copyright 2023 The Android Open Source Project diff --git a/compose/mpp/demo/src/uikitMain/kotlin/SwiftUIInteropExample.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/SwiftUIInteropExample.kt similarity index 72% rename from compose/mpp/demo/src/uikitMain/kotlin/SwiftUIInteropExample.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/SwiftUIInteropExample.kt index 8989a79555ee0..dce89332594b4 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/SwiftUIInteropExample.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/SwiftUIInteropExample.kt @@ -1,3 +1,21 @@ +/* + * Copyright 2024 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.mpp.demo + import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/ComposeAndNativeScroll.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/ComposeAndNativeScroll.kt similarity index 98% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/ComposeAndNativeScroll.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/ComposeAndNativeScroll.kt index 10673e9c05858..042247e20c82d 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/ComposeAndNativeScroll.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/ComposeAndNativeScroll.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/DispatchersMainDelayCheck.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/DispatchersMainDelayCheck.kt similarity index 97% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/DispatchersMainDelayCheck.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/DispatchersMainDelayCheck.kt index ca95ac1e33951..69c7de3a5b6bb 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/DispatchersMainDelayCheck.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/DispatchersMainDelayCheck.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/IosBugs.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/IosBugs.kt similarity index 92% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/IosBugs.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/IosBugs.kt index 956da25135633..85d08e7f1a631 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/IosBugs.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/IosBugs.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * Copyright 2024 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.mpp.demo.Screen import androidx.compose.mpp.demo.bug.BackspaceIssue diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/KeyboardEmptyWhiteSpace.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/KeyboardEmptyWhiteSpace.kt similarity index 98% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/KeyboardEmptyWhiteSpace.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/KeyboardEmptyWhiteSpace.kt index 7ea05dd7cf96a..15f0aeb203530 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/KeyboardEmptyWhiteSpace.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/KeyboardEmptyWhiteSpace.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.background import androidx.compose.foundation.border diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/KeyboardPasswordType.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/KeyboardPasswordType.kt similarity index 97% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/KeyboardPasswordType.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/KeyboardPasswordType.kt index e25eced95fd94..100a06276b9d1 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/KeyboardPasswordType.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/KeyboardPasswordType.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardOptions diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/MeasureAndLayoutCrash.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/MeasureAndLayoutCrash.kt similarity index 98% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/MeasureAndLayoutCrash.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/MeasureAndLayoutCrash.kt index e8ea4046a2d45..fd65e4d7cf2c4 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/MeasureAndLayoutCrash.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/MeasureAndLayoutCrash.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/ProperContainmentDisposal.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/ProperContainmentDisposal.kt similarity index 97% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/ProperContainmentDisposal.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/ProperContainmentDisposal.kt index 51bed90344c46..f3183e580d379 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/ProperContainmentDisposal.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/ProperContainmentDisposal.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/StartRecompositionCheck.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/StartRecompositionCheck.kt similarity index 95% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/StartRecompositionCheck.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/StartRecompositionCheck.kt index 051a05fe208f4..676c72094b218 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/StartRecompositionCheck.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/StartRecompositionCheck.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * Copyright 2024 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/UIKitRenderSync.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/UIKitRenderSync.kt similarity index 97% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/UIKitRenderSync.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/UIKitRenderSync.kt index 575cebb62184a..5cfa667077abb 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/UIKitRenderSync.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/UIKitRenderSync.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * Copyright 2024 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/UIKitViewAndDropDownMenu.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/UIKitViewAndDropDownMenu.kt similarity index 96% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/UIKitViewAndDropDownMenu.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/UIKitViewAndDropDownMenu.kt index 377840c70949f..c688e4493cc21 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/UIKitViewAndDropDownMenu.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/UIKitViewAndDropDownMenu.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * Copyright 2024 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column diff --git a/compose/mpp/demo/src/uikitMain/kotlin/bugs/UIKitViewMatryoshka.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/UIKitViewMatryoshka.kt similarity index 98% rename from compose/mpp/demo/src/uikitMain/kotlin/bugs/UIKitViewMatryoshka.kt rename to compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/UIKitViewMatryoshka.kt index 084a5c0c793a3..d2035cdc106f8 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/bugs/UIKitViewMatryoshka.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/bugs/UIKitViewMatryoshka.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package bugs +package androidx.compose.mpp.demo.bugs import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box diff --git a/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/main.uikit.kt b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/main.uikit.kt index 88b1de7dc56dd..4b259c0db4144 100644 --- a/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/main.uikit.kt +++ b/compose/mpp/demo/src/uikitMain/kotlin/androidx/compose/mpp/demo/main.uikit.kt @@ -1,8 +1,6 @@ // Use `xcodegen` first, then `open ./SkikoSample.xcodeproj` and then Run button in XCode. package androidx.compose.mpp.demo -import NativeModalWithNaviationExample -import SwiftUIInteropExample import androidx.compose.runtime.Composable import androidx.compose.runtime.ExperimentalComposeApi import androidx.compose.runtime.remember @@ -11,8 +9,8 @@ import androidx.compose.ui.main.defaultUIKitMain import androidx.compose.ui.platform.AccessibilityDebugLogger import androidx.compose.ui.platform.AccessibilitySyncOptions import androidx.compose.ui.window.ComposeUIViewController -import bugs.IosBugs -import bugs.StartRecompositionCheck +import androidx.compose.mpp.demo.bugs.IosBugs +import androidx.compose.mpp.demo.bugs.StartRecompositionCheck import platform.UIKit.UIViewController @OptIn(ExperimentalComposeApi::class, ExperimentalComposeUiApi::class) @@ -41,7 +39,7 @@ fun IosDemo(arg: String, makeHostingController: ((Int) -> UIViewController)? = n App( extraScreens = listOf( IosBugs, - NativeModalWithNaviationExample, + IosSpecificFeatures, ) + listOf(makeHostingController).mapNotNull { it?.let { SwiftUIInteropExample(it) diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/hapticfeedback/HapticFeedback.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/hapticfeedback/HapticFeedback.uikit.kt new file mode 100644 index 0000000000000..b999af37e0033 --- /dev/null +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/hapticfeedback/HapticFeedback.uikit.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 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.hapticfeedback + +import platform.UIKit.UIImpactFeedbackGenerator +import platform.UIKit.UISelectionFeedbackGenerator + +// TODO: minor UX improvement, add `prepare()` calls when internal APIs are likely to use HapticFeedback +// (e.g. pan started during the text selection) to reduce haptic feedback latency +// see https://developer.apple.com/documentation/uikit/uifeedbackgenerator +internal class CupertinoHapticFeedback : HapticFeedback { + private val impactGenerator = UIImpactFeedbackGenerator() + private val selectionGenerator = UISelectionFeedbackGenerator() + + override fun performHapticFeedback(hapticFeedbackType: HapticFeedbackType) { + when (hapticFeedbackType) { + HapticFeedbackType.LongPress -> impactGenerator.impactOccurred() + HapticFeedbackType.TextHandleMove -> selectionGenerator.selectionChanged() + } + } +} \ No newline at end of file diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeContainer.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeContainer.uikit.kt index 8de77d6ee67b8..8abf6b2781628 100644 --- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeContainer.uikit.kt +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/ComposeContainer.uikit.kt @@ -25,8 +25,10 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.LocalSystemTheme import androidx.compose.ui.SystemTheme +import androidx.compose.ui.hapticfeedback.CupertinoHapticFeedback import androidx.compose.ui.interop.LocalUIViewController import androidx.compose.ui.interop.UIKitInteropContext +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.PlatformContext import androidx.compose.ui.platform.PlatformWindowContext @@ -96,6 +98,7 @@ internal class ComposeContainer( private val content: @Composable () -> Unit, ) : CMPViewController(nibName = null, bundle = null) { val lifecycleOwner = ViewControllerBasedLifecycleOwner() + val hapticFeedback = CupertinoHapticFeedback() private var isInsideSwiftUI = false private var mediator: ComposeSceneMediator? = null @@ -426,6 +429,7 @@ internal fun ProvideContainerCompositionLocals( content: @Composable () -> Unit, ) = with(composeContainer) { CompositionLocalProvider( + LocalHapticFeedback provides hapticFeedback, LocalUIViewController provides this, LocalInterfaceOrientation provides interfaceOrientationState.value, LocalSystemTheme provides systemThemeState.value,