peachsim 0.1.669 → 0.1.671
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/control.ts +8 -5
- package/cli/commands/inspect/core.ts +7 -5
- package/cli/commands/inspect/wait-selector.ts +2 -11
- package/cli/commands/inspect.ts +6 -59
- package/cli/commands/kotlin.ts +6 -4
- package/cli/commands/skills.ts +28 -7
- package/cli/commands/upgrade.ts +18 -3
- package/cli/main.ts +21 -0
- package/cli/output-redaction.ts +182 -0
- package/cli/parse-args.ts +1 -0
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +30 -22
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +260 -17
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +2 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +259 -16
- package/dist-lib/native-app-identity.cjs +1 -1
- package/dist-lib/native-app-identity.mjs +1 -1
- package/dist-lib/native-firebase-metadata.cjs +115 -0
- package/dist-lib/native-firebase-metadata.mjs +86 -0
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +2 -2
- package/dist-lib/sdk.mjs +2 -2
- package/dist-lib/skills.cjs +480 -206
- package/dist-lib/swift-compile.cjs +1 -1
- package/dist-lib/swift-compile.mjs +1 -1
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +21 -13
- package/dist-lib/vite.cjs +1 -1
- package/kotlin/AppSources/androidx/activity/ActivityFlags.kt +9 -0
- package/kotlin/AppSources/androidx/activity/BackEventCompat.kt +25 -4
- package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +15 -4
- package/kotlin/AppSources/androidx/activity/EdgeToEdge.kt +12 -8
- package/kotlin/AppSources/androidx/activity/ExperimentalActivityApi.kt +6 -0
- package/kotlin/AppSources/androidx/activity/OnBackPressedCallback.kt +50 -9
- package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +67 -89
- package/kotlin/AppSources/androidx/activity/compose/BackHandler.kt +55 -24
- package/kotlin/AppSources/androidx/activity/compose/internal/BackHandlerCompat.kt +38 -0
- package/kotlin/AppSources/androidx/activity/compose/internal/BackHandlerDispatcherCompat.kt +27 -0
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +5 -5
- package/kotlin/ComposeSources/PeachComposeHost.kt +31 -7
- package/kotlin/fixtures/kotlin-app/app/build.gradle.kts +1 -1
- package/kotlin/fixtures/kotlin-app-variants/Browse.kt +73 -0
- package/kotlin/fixtures/kotlin-app-variants/Focus.kt +62 -0
- package/kotlin/fixtures/kotlin-app-variants/Lists.kt +57 -0
- package/kotlin/fixtures/kotlin-app-variants/Nav.kt +47 -0
- package/kotlin/fixtures/kotlin-app-variants/Nested.kt +64 -0
- package/kotlin/fixtures/kotlin-app-variants/Pan.kt +38 -0
- package/kotlin/fixtures/kotlin-app-variants/Pickers.kt +44 -0
- package/kotlin/fixtures/kotlin-app-variants/Press.kt +43 -0
- package/kotlin/fixtures/kotlin-app-variants/Transition.kt +69 -0
- package/kotlin/fixtures/kotlin-app-variants/ViewConfig.kt +22 -0
- package/kotlin/toolchain.json +30 -2
- package/package.json +8 -1
- package/skills/contrast/SKILL.md +2 -1
- package/skills/peach-debug/SKILL.md +2 -2
- package/skills/peach-test/SKILL.md +3 -3
- package/src/expo-swift-host.ts +13 -4
- package/src/host/bridge-host.ts +3 -3
- package/src/kotlin-app.ts +35 -16
- package/src/kotlin-host.ts +1 -0
- package/src/metro-plugin.ts +24 -0
- package/src/native-firebase-config.ts +202 -21
- package/src/native-firebase-metadata.ts +114 -0
- package/src/swift-host.ts +30 -15
- package/src/vite-plugin.ts +46 -2
- package/kotlin/AppSources/androidx/activity/Cancellable.kt +0 -5
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package androidx.activity.compose.internal
|
|
2
|
+
|
|
3
|
+
import androidx.activity.BackEventCompat
|
|
4
|
+
import androidx.activity.OnBackPressedCallback
|
|
5
|
+
import androidx.navigationevent.NavigationEvent
|
|
6
|
+
import androidx.navigationevent.NavigationEventHandler
|
|
7
|
+
import androidx.navigationevent.NavigationEventInfo
|
|
8
|
+
|
|
9
|
+
// activity-compose 1.13.0's BackHandlerCompat: one back handler as both a
|
|
10
|
+
// navigation event handler and a back callback, for whichever dispatcher the
|
|
11
|
+
// composition provides
|
|
12
|
+
internal abstract class BackHandlerCompat(val info: NavigationEventInfo) {
|
|
13
|
+
open fun onBackStarted(event: BackEventCompat) {}
|
|
14
|
+
open fun onBackProgressed(event: BackEventCompat) {}
|
|
15
|
+
abstract fun onBackCompleted()
|
|
16
|
+
open fun onBackCancelled() {}
|
|
17
|
+
|
|
18
|
+
open var isBackEnabled: Boolean
|
|
19
|
+
get() = onBackPressedCallback.isEnabled && navigationEventHandler.isBackEnabled
|
|
20
|
+
set(value) {
|
|
21
|
+
onBackPressedCallback.isEnabled = value
|
|
22
|
+
navigationEventHandler.isBackEnabled = value
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
val onBackPressedCallback = object : OnBackPressedCallback(enabled = false) {
|
|
26
|
+
override fun handleOnBackStarted(backEvent: BackEventCompat) = this@BackHandlerCompat.onBackStarted(backEvent)
|
|
27
|
+
override fun handleOnBackProgressed(backEvent: BackEventCompat) = this@BackHandlerCompat.onBackProgressed(backEvent)
|
|
28
|
+
override fun handleOnBackPressed() = this@BackHandlerCompat.onBackCompleted()
|
|
29
|
+
override fun handleOnBackCancelled() = this@BackHandlerCompat.onBackCancelled()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
val navigationEventHandler = object : NavigationEventHandler<NavigationEventInfo>(info, isBackEnabled = false) {
|
|
33
|
+
override fun onBackStarted(event: NavigationEvent) = this@BackHandlerCompat.onBackStarted(BackEventCompat(event))
|
|
34
|
+
override fun onBackProgressed(event: NavigationEvent) = this@BackHandlerCompat.onBackProgressed(BackEventCompat(event))
|
|
35
|
+
override fun onBackCompleted() = this@BackHandlerCompat.onBackCompleted()
|
|
36
|
+
override fun onBackCancelled() = this@BackHandlerCompat.onBackCancelled()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package androidx.activity.compose.internal
|
|
2
|
+
|
|
3
|
+
import androidx.activity.OnBackPressedDispatcher
|
|
4
|
+
import androidx.navigationevent.NavigationEventDispatcher
|
|
5
|
+
|
|
6
|
+
// activity-compose 1.13.0's BackHandlerDispatcherCompat: the navigation event
|
|
7
|
+
// dispatcher when there is one, else the back dispatcher
|
|
8
|
+
internal class BackHandlerDispatcherCompat(
|
|
9
|
+
private val navigationEventDispatcher: NavigationEventDispatcher?,
|
|
10
|
+
private val onBackPressedDispatcher: OnBackPressedDispatcher?,
|
|
11
|
+
) {
|
|
12
|
+
init {
|
|
13
|
+
requireNotNull(navigationEventDispatcher ?: onBackPressedDispatcher) {
|
|
14
|
+
"At least one dispatcher (NavigationEventDispatcher or OnBackPressedDispatcher) must be non-null."
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fun addHandler(handler: BackHandlerCompat) {
|
|
19
|
+
if (navigationEventDispatcher != null) navigationEventDispatcher.addHandler(handler.navigationEventHandler)
|
|
20
|
+
else checkNotNull(onBackPressedDispatcher).addCallback(handler.onBackPressedCallback)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fun removeHandler(handler: BackHandlerCompat) {
|
|
24
|
+
if (navigationEventDispatcher != null) handler.navigationEventHandler.remove()
|
|
25
|
+
else handler.onBackPressedCallback.remove()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -30,8 +30,6 @@ import androidx.compose.runtime.mutableStateOf
|
|
|
30
30
|
import androidx.compose.runtime.setValue
|
|
31
31
|
import androidx.compose.ui.text.font.FontFamily
|
|
32
32
|
import androidx.lifecycle.Lifecycle
|
|
33
|
-
import androidx.navigationevent.NavigationEventDispatcher
|
|
34
|
-
import androidx.navigationevent.NavigationEventDispatcherOwner
|
|
35
33
|
import PeachComposeMount
|
|
36
34
|
import addMount
|
|
37
35
|
import mounts
|
|
@@ -55,6 +53,9 @@ internal class PeachAppSpec(
|
|
|
55
53
|
// the activity's configChanges includes uiMode: it takes a night mode
|
|
56
54
|
// change in onConfigurationChanged rather than relaunching
|
|
57
55
|
val handlesUiMode: Boolean,
|
|
56
|
+
// how the activity's window meets the keyboard, pan, resize or nothing:
|
|
57
|
+
// adjustPan and an unspecified adjust on a compose window pan
|
|
58
|
+
val softInputAdjust: String,
|
|
58
59
|
// the app's view models, each built with the constructor the device's
|
|
59
60
|
// default factory finds for it
|
|
60
61
|
val viewModels: ViewModelProvider.Factory,
|
|
@@ -98,9 +99,7 @@ private class ActivityOwners(activity: ComponentActivity) : PlatformArchitecture
|
|
|
98
99
|
override val lifecycleOwner = activity
|
|
99
100
|
override val viewModelStoreOwner = activity
|
|
100
101
|
override val savedStateRegistryOwner = activity
|
|
101
|
-
override val navigationEventDispatcherOwner =
|
|
102
|
-
override val navigationEventDispatcher = NavigationEventDispatcher()
|
|
103
|
-
}
|
|
102
|
+
override val navigationEventDispatcherOwner = activity
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
// the device and the application one started app process keeps, and what
|
|
@@ -167,6 +166,7 @@ internal fun peachStartApp(
|
|
|
167
166
|
return app.activity.window.describe().put("ok", true).put("id", app.mountId)
|
|
168
167
|
.put("wrapWidth", params?.width == ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
169
168
|
.put("wrapHeight", params?.height == ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
169
|
+
.put("softInputAdjust", spec.softInputAdjust)
|
|
170
170
|
.toString()
|
|
171
171
|
} catch (error: Throwable) {
|
|
172
172
|
return JSONObject().put("ok", false).put("code", "E_KOTLIN_APP")
|
|
@@ -24,6 +24,7 @@ import androidx.compose.ui.platform.PlatformArchitectureComponentsOwner
|
|
|
24
24
|
import androidx.compose.ui.platform.PlatformWindowInsets
|
|
25
25
|
import androidx.compose.ui.platform.PlatformContext
|
|
26
26
|
import androidx.compose.ui.platform.PlatformTextInputMethodRequest
|
|
27
|
+
import androidx.compose.ui.platform.ViewConfiguration
|
|
27
28
|
import androidx.compose.ui.platform.WindowInfo
|
|
28
29
|
import androidx.compose.ui.platform.registerSkikoComposeImplementation
|
|
29
30
|
import androidx.compose.ui.scene.CanvasLayersComposeScene
|
|
@@ -63,7 +64,10 @@ import androidx.compose.ui.unit.LayoutDirection
|
|
|
63
64
|
import dev.onejs.one.OneNativeSourceViewDispatch
|
|
64
65
|
import kotlin.io.encoding.Base64
|
|
65
66
|
import kotlinx.coroutines.Dispatchers
|
|
67
|
+
import kotlinx.coroutines.Job
|
|
66
68
|
import kotlinx.coroutines.awaitCancellation
|
|
69
|
+
import kotlinx.coroutines.currentCoroutineContext
|
|
70
|
+
import kotlinx.coroutines.job
|
|
67
71
|
import org.jetbrains.skiko.SystemTheme
|
|
68
72
|
import org.jetbrains.skia.BackendRenderTarget
|
|
69
73
|
import org.jetbrains.skia.Bitmap
|
|
@@ -85,6 +89,20 @@ import org.json.JSONObject
|
|
|
85
89
|
// owners and window insets come from the hosting window: an activity's
|
|
86
90
|
// ComposeView passes the activity and the insets its window delivers; an
|
|
87
91
|
// imported composable keeps the empty defaults.
|
|
92
|
+
// the values android.view.ViewConfiguration gives a window: config.xml's dp
|
|
93
|
+
// dimensions resolved as getDimensionPixelSize does, rounding half up
|
|
94
|
+
internal class AndroidViewConfiguration(private val density: Float) : ViewConfiguration {
|
|
95
|
+
private fun pixels(dp: Float) = (dp * density + 0.5f).toInt().toFloat()
|
|
96
|
+
override val longPressTimeoutMillis: Long = 400
|
|
97
|
+
override val doubleTapTimeoutMillis: Long = 300
|
|
98
|
+
override val doubleTapMinTimeMillis: Long = 40
|
|
99
|
+
override val touchSlop: Float = pixels(8f)
|
|
100
|
+
override val handwritingSlop: Float = pixels(2f)
|
|
101
|
+
override val maximumFlingVelocity: Float = pixels(8000f)
|
|
102
|
+
override val minimumFlingVelocity: Float = pixels(50f)
|
|
103
|
+
override val handwritingGestureLineMargin: Float = pixels(16f)
|
|
104
|
+
}
|
|
105
|
+
|
|
88
106
|
internal class PeachComposePlatform(
|
|
89
107
|
val density: Density,
|
|
90
108
|
override val localeList: LocaleList,
|
|
@@ -101,6 +119,9 @@ internal class PeachComposePlatform(
|
|
|
101
119
|
override val containerSize: IntSize get() = size
|
|
102
120
|
override val containerDpSize: DpSize get() = with(density) { DpSize(size.width.toDp(), size.height.toDp()) }
|
|
103
121
|
}
|
|
122
|
+
// android's ViewConfiguration: AOSP's dp defaults as the device's pixels,
|
|
123
|
+
// where the skiko default is a desktop's
|
|
124
|
+
override val viewConfiguration: ViewConfiguration = AndroidViewConfiguration(density.density)
|
|
104
125
|
override val inputModeManager: InputModeManager = object : InputModeManager {
|
|
105
126
|
override var inputMode: InputMode by mutableStateOf(InputMode.Touch)
|
|
106
127
|
override fun requestInputMode(inputMode: InputMode): Boolean {
|
|
@@ -116,19 +137,22 @@ internal class PeachComposePlatform(
|
|
|
116
137
|
|
|
117
138
|
// the input method session the focused text field holds, android's
|
|
118
139
|
// InputConnection. each session gets a new token, so the host can tell a
|
|
119
|
-
// late close from the session that replaced it
|
|
120
|
-
|
|
121
|
-
|
|
140
|
+
// late close from the session that replaced it. a field that detaches
|
|
141
|
+
// cancels its session at once, though the cancelled coroutine finishes
|
|
142
|
+
// later; from the cancel on the session is closed, as compose web only
|
|
143
|
+
// reads a request inside the session's own coroutines
|
|
144
|
+
private var session: PeachInputSession? = null
|
|
145
|
+
val input get() = session?.takeIf { it.job.isActive }
|
|
122
146
|
var onInputChange: () -> Unit = {}
|
|
123
147
|
private var nextInput = 1
|
|
124
148
|
|
|
125
149
|
override suspend fun startInputMethod(request: PlatformTextInputMethodRequest): Nothing {
|
|
126
|
-
val
|
|
127
|
-
|
|
150
|
+
val opened = PeachInputSession(nextInput++, request, currentCoroutineContext().job)
|
|
151
|
+
session = opened
|
|
128
152
|
onInputChange()
|
|
129
153
|
try { awaitCancellation() }
|
|
130
154
|
finally {
|
|
131
|
-
if (
|
|
155
|
+
if (session === opened) session = null
|
|
132
156
|
onInputChange()
|
|
133
157
|
}
|
|
134
158
|
}
|
|
@@ -147,7 +171,7 @@ internal class PeachComposePlatform(
|
|
|
147
171
|
}
|
|
148
172
|
}
|
|
149
173
|
|
|
150
|
-
internal class PeachInputSession(val token: Int, val request: PlatformTextInputMethodRequest) {
|
|
174
|
+
internal class PeachInputSession(val token: Int, val request: PlatformTextInputMethodRequest, val job: Job) {
|
|
151
175
|
var keyboard = true
|
|
152
176
|
private set
|
|
153
177
|
// each show or hide compose asks for, so a repeat still reaches the keyboard
|
|
@@ -23,7 +23,7 @@ android {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
dependencies {
|
|
26
|
-
implementation("androidx.activity:activity-compose:1.
|
|
26
|
+
implementation("androidx.activity:activity-compose:1.13.0")
|
|
27
27
|
implementation("androidx.compose.ui:ui:1.13.0-alpha02")
|
|
28
28
|
implementation("androidx.compose.foundation:foundation:1.13.0-alpha02")
|
|
29
29
|
implementation("androidx.compose.material3:material3:1.5.0-alpha27")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
5
|
+
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.foundation.layout.Column
|
|
7
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
8
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
9
|
+
import androidx.compose.foundation.layout.height
|
|
10
|
+
import androidx.compose.foundation.layout.padding
|
|
11
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
12
|
+
import androidx.compose.foundation.text.input.rememberTextFieldState
|
|
13
|
+
import androidx.compose.material3.ExpandedFullScreenSearchBar
|
|
14
|
+
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
15
|
+
import androidx.compose.material3.SearchBar
|
|
16
|
+
import androidx.compose.material3.SearchBarDefaults
|
|
17
|
+
import androidx.compose.material3.Text
|
|
18
|
+
import androidx.compose.material3.carousel.HorizontalMultiBrowseCarousel
|
|
19
|
+
import androidx.compose.material3.carousel.rememberCarouselState
|
|
20
|
+
import androidx.compose.material3.rememberSearchBarState
|
|
21
|
+
import androidx.compose.runtime.Composable
|
|
22
|
+
import androidx.compose.runtime.getValue
|
|
23
|
+
import androidx.compose.runtime.mutableStateOf
|
|
24
|
+
import androidx.compose.runtime.remember
|
|
25
|
+
import androidx.compose.runtime.rememberCoroutineScope
|
|
26
|
+
import androidx.compose.runtime.setValue
|
|
27
|
+
import androidx.compose.ui.Alignment
|
|
28
|
+
import androidx.compose.ui.Modifier
|
|
29
|
+
import androidx.compose.ui.graphics.Color
|
|
30
|
+
import androidx.compose.ui.unit.dp
|
|
31
|
+
import kotlinx.coroutines.launch
|
|
32
|
+
|
|
33
|
+
// a search bar that expands over the screen and a multi browse carousel a
|
|
34
|
+
// swipe advances, reported in one line
|
|
35
|
+
@OptIn(ExperimentalMaterial3Api::class)
|
|
36
|
+
@Composable
|
|
37
|
+
fun Browse() {
|
|
38
|
+
val search = rememberSearchBarState()
|
|
39
|
+
val field = rememberTextFieldState()
|
|
40
|
+
val scope = rememberCoroutineScope()
|
|
41
|
+
var searched by remember { mutableStateOf("") }
|
|
42
|
+
val carousel = rememberCarouselState { 8 }
|
|
43
|
+
val colors = listOf(0xFF6750A4, 0xFF7D5260, 0xFF625B71, 0xFF386A20, 0xFF00639B, 0xFF8C4A00, 0xFF984061, 0xFF006A6A)
|
|
44
|
+
Column(
|
|
45
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
46
|
+
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
47
|
+
) {
|
|
48
|
+
Text("Browse: ${carousel.currentItem} searched $searched ${search.currentValue}")
|
|
49
|
+
val input = @Composable {
|
|
50
|
+
SearchBarDefaults.InputField(
|
|
51
|
+
searchBarState = search,
|
|
52
|
+
textFieldState = field,
|
|
53
|
+
onSearch = { searched = it; scope.launch { search.animateToCollapsed() } },
|
|
54
|
+
placeholder = { Text("Find") },
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
SearchBar(state = search, inputField = input)
|
|
58
|
+
ExpandedFullScreenSearchBar(state = search, inputField = input) {
|
|
59
|
+
Text("Suggestion", Modifier.padding(16.dp))
|
|
60
|
+
}
|
|
61
|
+
HorizontalMultiBrowseCarousel(
|
|
62
|
+
state = carousel,
|
|
63
|
+
preferredItemWidth = 186.dp,
|
|
64
|
+
modifier = Modifier.fillMaxWidth().height(200.dp),
|
|
65
|
+
itemSpacing = 8.dp,
|
|
66
|
+
) { index ->
|
|
67
|
+
Box(
|
|
68
|
+
Modifier.fillMaxSize().maskClip(SearchBarDefaults.inputFieldShape).background(Color(colors[index])),
|
|
69
|
+
contentAlignment = Alignment.Center,
|
|
70
|
+
) { Text("Item $index", color = Color.White) }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
4
|
+
import androidx.compose.foundation.background
|
|
5
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
6
|
+
import androidx.compose.foundation.layout.Column
|
|
7
|
+
import androidx.compose.foundation.layout.Row
|
|
8
|
+
import androidx.compose.foundation.layout.WindowInsets
|
|
9
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
10
|
+
import androidx.compose.foundation.layout.ime
|
|
11
|
+
import androidx.compose.foundation.layout.padding
|
|
12
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
13
|
+
import androidx.compose.foundation.text.KeyboardOptions
|
|
14
|
+
import androidx.compose.foundation.text.input.rememberTextFieldState
|
|
15
|
+
import androidx.compose.material3.Button
|
|
16
|
+
import androidx.compose.material3.Text
|
|
17
|
+
import androidx.compose.material3.TextField
|
|
18
|
+
import androidx.compose.runtime.Composable
|
|
19
|
+
import androidx.compose.runtime.getValue
|
|
20
|
+
import androidx.compose.runtime.mutableStateOf
|
|
21
|
+
import androidx.compose.runtime.remember
|
|
22
|
+
import androidx.compose.runtime.setValue
|
|
23
|
+
import androidx.compose.ui.Modifier
|
|
24
|
+
import androidx.compose.ui.focus.FocusRequester
|
|
25
|
+
import androidx.compose.ui.focus.focusRequester
|
|
26
|
+
import androidx.compose.ui.focus.onFocusChanged
|
|
27
|
+
import androidx.compose.ui.graphics.Color
|
|
28
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
29
|
+
import androidx.compose.ui.unit.dp
|
|
30
|
+
|
|
31
|
+
// two fields focused from code, one with showKeyboardOnFocus off, and whether
|
|
32
|
+
// the keyboard came up for each
|
|
33
|
+
@OptIn(ExperimentalFoundationApi::class)
|
|
34
|
+
@Composable
|
|
35
|
+
fun Focus() {
|
|
36
|
+
val alpha = remember { FocusRequester() }
|
|
37
|
+
val beta = remember { FocusRequester() }
|
|
38
|
+
var alphaFocused by remember { mutableStateOf(false) }
|
|
39
|
+
var betaFocused by remember { mutableStateOf(false) }
|
|
40
|
+
Column(
|
|
41
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
42
|
+
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
43
|
+
) {
|
|
44
|
+
val ime = WindowInsets.ime.getBottom(LocalDensity.current) > 0
|
|
45
|
+
Text("Focus: alpha $alphaFocused beta $betaFocused ime $ime")
|
|
46
|
+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
47
|
+
Button(onClick = { alpha.requestFocus() }) { Text("Focus alpha") }
|
|
48
|
+
Button(onClick = { beta.requestFocus() }) { Text("Focus beta") }
|
|
49
|
+
}
|
|
50
|
+
TextField(
|
|
51
|
+
rememberTextFieldState(),
|
|
52
|
+
Modifier.focusRequester(alpha).onFocusChanged { alphaFocused = it.isFocused },
|
|
53
|
+
label = { Text("Alpha") },
|
|
54
|
+
)
|
|
55
|
+
TextField(
|
|
56
|
+
rememberTextFieldState(),
|
|
57
|
+
Modifier.focusRequester(beta).onFocusChanged { betaFocused = it.isFocused },
|
|
58
|
+
label = { Text("Beta") },
|
|
59
|
+
keyboardOptions = KeyboardOptions(showKeyboardOnFocus = false),
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
5
|
+
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.foundation.layout.Column
|
|
7
|
+
import androidx.compose.foundation.layout.PaddingValues
|
|
8
|
+
import androidx.compose.foundation.layout.aspectRatio
|
|
9
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
10
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
11
|
+
import androidx.compose.foundation.layout.height
|
|
12
|
+
import androidx.compose.foundation.layout.padding
|
|
13
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
14
|
+
import androidx.compose.foundation.layout.size
|
|
15
|
+
import androidx.compose.foundation.lazy.LazyRow
|
|
16
|
+
import androidx.compose.foundation.lazy.grid.GridCells
|
|
17
|
+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
18
|
+
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
|
19
|
+
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
20
|
+
import androidx.compose.material3.Text
|
|
21
|
+
import androidx.compose.runtime.Composable
|
|
22
|
+
import androidx.compose.ui.Alignment
|
|
23
|
+
import androidx.compose.ui.Modifier
|
|
24
|
+
import androidx.compose.ui.graphics.Color
|
|
25
|
+
import androidx.compose.ui.unit.dp
|
|
26
|
+
|
|
27
|
+
// a lazy row a horizontal drag scrolls and a lazy grid a vertical drag
|
|
28
|
+
// scrolls, each reporting its first visible item and offset in dp
|
|
29
|
+
@Composable
|
|
30
|
+
fun Lists() {
|
|
31
|
+
val row = rememberLazyListState()
|
|
32
|
+
val grid = rememberLazyGridState()
|
|
33
|
+
val colors = listOf(Color(0xFF3F51B5), Color(0xFFE91E63), Color(0xFF009688), Color(0xFFFF9800))
|
|
34
|
+
Column(
|
|
35
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
36
|
+
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
37
|
+
) {
|
|
38
|
+
Text("Row: ${row.firstVisibleItemIndex} ${row.firstVisibleItemScrollOffset} grid ${grid.firstVisibleItemIndex} ${grid.firstVisibleItemScrollOffset}")
|
|
39
|
+
LazyRow(Modifier.fillMaxWidth().height(96.dp), state = row, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
40
|
+
items(30) { i ->
|
|
41
|
+
Box(Modifier.size(96.dp).background(colors[i % 4]), contentAlignment = Alignment.Center) { Text("R$i", color = Color.White) }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
LazyVerticalGrid(
|
|
45
|
+
GridCells.Fixed(3),
|
|
46
|
+
Modifier.fillMaxWidth().weight(1f),
|
|
47
|
+
state = grid,
|
|
48
|
+
contentPadding = PaddingValues(4.dp),
|
|
49
|
+
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
50
|
+
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
51
|
+
) {
|
|
52
|
+
items(90) { i ->
|
|
53
|
+
Box(Modifier.aspectRatio(1f).background(colors[(i + 1) % 4]), contentAlignment = Alignment.Center) { Text("G$i", color = Color.White) }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
5
|
+
import androidx.compose.foundation.layout.Column
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
7
|
+
import androidx.compose.foundation.layout.padding
|
|
8
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
9
|
+
import androidx.compose.material3.Button
|
|
10
|
+
import androidx.compose.material3.Text
|
|
11
|
+
import androidx.compose.runtime.Composable
|
|
12
|
+
import androidx.compose.runtime.getValue
|
|
13
|
+
import androidx.compose.runtime.mutableIntStateOf
|
|
14
|
+
import androidx.compose.runtime.saveable.rememberSaveable
|
|
15
|
+
import androidx.compose.runtime.setValue
|
|
16
|
+
import androidx.compose.ui.Modifier
|
|
17
|
+
import androidx.compose.ui.graphics.Color
|
|
18
|
+
import androidx.compose.ui.unit.dp
|
|
19
|
+
import androidx.navigation.compose.NavHost
|
|
20
|
+
import androidx.navigation.compose.composable
|
|
21
|
+
import androidx.navigation.compose.currentBackStackEntryAsState
|
|
22
|
+
import androidx.navigation.compose.rememberNavController
|
|
23
|
+
|
|
24
|
+
// a NavHost with a home destination holding saveable state and a detail
|
|
25
|
+
// destination the system back pops
|
|
26
|
+
@Composable
|
|
27
|
+
fun Nav() {
|
|
28
|
+
val nav = rememberNavController()
|
|
29
|
+
val entry by nav.currentBackStackEntryAsState()
|
|
30
|
+
Column(
|
|
31
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
32
|
+
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
33
|
+
) {
|
|
34
|
+
Text("Route: ${entry?.destination?.route}")
|
|
35
|
+
NavHost(nav, startDestination = "home") {
|
|
36
|
+
composable("home") {
|
|
37
|
+
var taps by rememberSaveable { mutableIntStateOf(0) }
|
|
38
|
+
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
39
|
+
Text("Home taps $taps")
|
|
40
|
+
Button(onClick = { taps++ }) { Text("Tap") }
|
|
41
|
+
Button(onClick = { nav.navigate("detail") }) { Text("Open detail") }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
composable("detail") { Text("Detail screen") }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.horizontalScroll
|
|
5
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
6
|
+
import androidx.compose.foundation.layout.Box
|
|
7
|
+
import androidx.compose.foundation.layout.Column
|
|
8
|
+
import androidx.compose.foundation.layout.Row
|
|
9
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
10
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
11
|
+
import androidx.compose.foundation.layout.height
|
|
12
|
+
import androidx.compose.foundation.layout.padding
|
|
13
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
14
|
+
import androidx.compose.foundation.layout.size
|
|
15
|
+
import androidx.compose.foundation.rememberScrollState
|
|
16
|
+
import androidx.compose.foundation.verticalScroll
|
|
17
|
+
import androidx.compose.material3.Text
|
|
18
|
+
import androidx.compose.runtime.Composable
|
|
19
|
+
import androidx.compose.runtime.getValue
|
|
20
|
+
import androidx.compose.runtime.mutableFloatStateOf
|
|
21
|
+
import androidx.compose.runtime.remember
|
|
22
|
+
import androidx.compose.runtime.setValue
|
|
23
|
+
import androidx.compose.ui.Alignment
|
|
24
|
+
import androidx.compose.ui.Modifier
|
|
25
|
+
import androidx.compose.ui.geometry.Offset
|
|
26
|
+
import androidx.compose.ui.graphics.Color
|
|
27
|
+
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
28
|
+
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
|
29
|
+
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
30
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
31
|
+
import androidx.compose.ui.unit.dp
|
|
32
|
+
import kotlin.math.roundToInt
|
|
33
|
+
|
|
34
|
+
// a header a nested scroll connection collapses before a verticalScroll
|
|
35
|
+
// column moves, and a horizontalScroll row inside that column
|
|
36
|
+
@Composable
|
|
37
|
+
fun Nested() {
|
|
38
|
+
val maxHeader = with(LocalDensity.current) { 160.dp.toPx() }
|
|
39
|
+
var header by remember { mutableFloatStateOf(maxHeader) }
|
|
40
|
+
val column = rememberScrollState()
|
|
41
|
+
val row = rememberScrollState()
|
|
42
|
+
val connection = remember {
|
|
43
|
+
object : NestedScrollConnection {
|
|
44
|
+
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
|
45
|
+
if (available.y >= 0) return Offset.Zero
|
|
46
|
+
val next = (header + available.y).coerceAtLeast(0f)
|
|
47
|
+
val used = next - header
|
|
48
|
+
header = next
|
|
49
|
+
return Offset(0f, used)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
val colors = listOf(Color(0xFF3F51B5), Color(0xFFE91E63), Color(0xFF009688), Color(0xFFFF9800))
|
|
54
|
+
Column(Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().nestedScroll(connection)) {
|
|
55
|
+
Text("Nested: header ${header.roundToInt()} column ${column.value} row ${row.value}", Modifier.padding(16.dp))
|
|
56
|
+
Box(Modifier.fillMaxWidth().height(with(LocalDensity.current) { header.toDp() }).background(Color(0xFF673AB7)))
|
|
57
|
+
Column(Modifier.fillMaxWidth().weight(1f).verticalScroll(column).padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
58
|
+
Row(Modifier.horizontalScroll(row), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
59
|
+
repeat(12) { i -> Box(Modifier.size(96.dp).background(colors[i % 4]), contentAlignment = Alignment.Center) { Text("H$i", color = Color.White) } }
|
|
60
|
+
}
|
|
61
|
+
repeat(20) { i -> Box(Modifier.fillMaxWidth().height(64.dp).background(colors[(i + 2) % 4]), contentAlignment = Alignment.Center) { Text("V$i", color = Color.White) } }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Column
|
|
5
|
+
import androidx.compose.foundation.layout.Spacer
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
7
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
8
|
+
import androidx.compose.foundation.layout.padding
|
|
9
|
+
import androidx.compose.material3.OutlinedTextField
|
|
10
|
+
import androidx.compose.material3.Text
|
|
11
|
+
import androidx.compose.runtime.Composable
|
|
12
|
+
import androidx.compose.runtime.getValue
|
|
13
|
+
import androidx.compose.runtime.mutableStateOf
|
|
14
|
+
import androidx.compose.runtime.remember
|
|
15
|
+
import androidx.compose.runtime.setValue
|
|
16
|
+
import androidx.compose.ui.Modifier
|
|
17
|
+
import androidx.compose.ui.focus.onFocusChanged
|
|
18
|
+
import androidx.compose.ui.graphics.Color
|
|
19
|
+
import androidx.compose.ui.unit.dp
|
|
20
|
+
|
|
21
|
+
// a field at the bottom of a window that fits system windows, which the
|
|
22
|
+
// keyboard would cover: android pans the window to keep it in view
|
|
23
|
+
@Composable
|
|
24
|
+
fun Pan() {
|
|
25
|
+
var text by remember { mutableStateOf("") }
|
|
26
|
+
var focused by remember { mutableStateOf(false) }
|
|
27
|
+
Column(Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).padding(16.dp)) {
|
|
28
|
+
Text("Top line")
|
|
29
|
+
Spacer(Modifier.weight(1f))
|
|
30
|
+
Text("Pan: focused $focused")
|
|
31
|
+
OutlinedTextField(
|
|
32
|
+
value = text,
|
|
33
|
+
onValueChange = { text = it },
|
|
34
|
+
label = { Text("Reply") },
|
|
35
|
+
modifier = Modifier.fillMaxWidth().padding(top = 12.dp).onFocusChanged { focused = it.isFocused },
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
5
|
+
import androidx.compose.foundation.layout.Column
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
7
|
+
import androidx.compose.foundation.layout.padding
|
|
8
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
9
|
+
import androidx.compose.material3.Button
|
|
10
|
+
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
11
|
+
import androidx.compose.material3.PlainTooltip
|
|
12
|
+
import androidx.compose.material3.Text
|
|
13
|
+
import androidx.compose.material3.TimePicker
|
|
14
|
+
import androidx.compose.material3.TooltipBox
|
|
15
|
+
import androidx.compose.material3.TooltipDefaults
|
|
16
|
+
import androidx.compose.material3.rememberTimePickerState
|
|
17
|
+
import androidx.compose.material3.rememberTooltipState
|
|
18
|
+
import androidx.compose.runtime.Composable
|
|
19
|
+
import androidx.compose.ui.Modifier
|
|
20
|
+
import androidx.compose.ui.graphics.Color
|
|
21
|
+
import androidx.compose.ui.unit.dp
|
|
22
|
+
|
|
23
|
+
// a persistent tooltip a long press shows and a 12 hour time picker whose dial
|
|
24
|
+
// a tap sets, both reported in one line
|
|
25
|
+
@OptIn(ExperimentalMaterial3Api::class)
|
|
26
|
+
@Composable
|
|
27
|
+
fun Pickers() {
|
|
28
|
+
val time = rememberTimePickerState(initialHour = 9, initialMinute = 30, is24Hour = false)
|
|
29
|
+
val tip = rememberTooltipState(isPersistent = true)
|
|
30
|
+
Column(
|
|
31
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
32
|
+
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
33
|
+
) {
|
|
34
|
+
Text("Time: ${time.hour}:${time.minute} tip ${tip.isVisible}")
|
|
35
|
+
TooltipBox(
|
|
36
|
+
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
|
37
|
+
tooltip = { PlainTooltip { Text("Saves the draft") } },
|
|
38
|
+
state = tip,
|
|
39
|
+
) {
|
|
40
|
+
Button(onClick = {}) { Text("Save") }
|
|
41
|
+
}
|
|
42
|
+
TimePicker(time)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
4
|
+
import androidx.compose.foundation.background
|
|
5
|
+
import androidx.compose.foundation.combinedClickable
|
|
6
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
7
|
+
import androidx.compose.foundation.layout.Box
|
|
8
|
+
import androidx.compose.foundation.layout.Column
|
|
9
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
10
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
11
|
+
import androidx.compose.foundation.layout.height
|
|
12
|
+
import androidx.compose.foundation.layout.padding
|
|
13
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
14
|
+
import androidx.compose.material3.Text
|
|
15
|
+
import androidx.compose.runtime.Composable
|
|
16
|
+
import androidx.compose.runtime.getValue
|
|
17
|
+
import androidx.compose.runtime.mutableStateOf
|
|
18
|
+
import androidx.compose.runtime.remember
|
|
19
|
+
import androidx.compose.runtime.setValue
|
|
20
|
+
import androidx.compose.ui.Alignment
|
|
21
|
+
import androidx.compose.ui.Modifier
|
|
22
|
+
import androidx.compose.ui.graphics.Color
|
|
23
|
+
import androidx.compose.ui.unit.dp
|
|
24
|
+
|
|
25
|
+
// a target combinedClickable counts taps and long presses on, so a hold just
|
|
26
|
+
// past the long press timeout tells which timeout the window uses
|
|
27
|
+
@OptIn(ExperimentalFoundationApi::class)
|
|
28
|
+
@Composable
|
|
29
|
+
fun Press() {
|
|
30
|
+
var clicks by remember { mutableStateOf(0) }
|
|
31
|
+
var longs by remember { mutableStateOf(0) }
|
|
32
|
+
Column(
|
|
33
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
34
|
+
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
35
|
+
) {
|
|
36
|
+
Text("Press: clicks $clicks long $longs")
|
|
37
|
+
Box(
|
|
38
|
+
Modifier.fillMaxWidth().height(200.dp).background(Color(0xFFCFD8DC))
|
|
39
|
+
.combinedClickable(onLongClick = { longs += 1 }) { clicks += 1 },
|
|
40
|
+
contentAlignment = Alignment.Center,
|
|
41
|
+
) { Text("Target") }
|
|
42
|
+
}
|
|
43
|
+
}
|