Status Update
Comments
ys...@google.com <ys...@google.com> #2
More suggestions:
-
Allow to override containerElevation for RichTooltipBox - RichTooltipColors has container param, but doesn't allow overriding containerElevation (which mixes color with theme primary color, resulting in undesired color)
-
Allow to set or suggest the position of shown tooltip.
-
When isPersistent=true, allow dismissing by clicking outside and still passing through the click event
For example, when some persistent tooltip is shown for a button in screen's top area, user should be able to click something else in screen without having to click twice (once to dismiss the tooltip, second time to actually dismiss the tooltip)
-
Allow to customize PlainTooltipMaxWidth/RichTooltipMaxWidth
-
Accessibility - Hard to close isPersistent=true tooltip in TalkBack mode by clicking outside (this is probably an issue with PopupWindow itself)
-
Accessibility - TalkBack says "Popup window" when tooltip is shown (might be expected)
-
M3 spec doesn't seem to allow it, but tooltip arrows would be also useful. If not added to spec, maybe expose some currently internal stuff in M4 Tooltip implementation that will allow developers to implement arrows without having to rewrite it all?
ys...@google.com <ys...@google.com> #3
It sounds like the autohide behavior you are looking for is similar to the isPersistent parameter for tooltip state:
When this is set to true, the tooltip should be able to remain on the screen until the user clicks outside of it. The request for a modifiable timeout seems reasonable to me, wdyt Kevin? Something to defer to design perhaps?
ys...@google.com <ys...@google.com> #4
@isPersistent = false
to rememberTooltipState()
. These changes might not be available yet in the current release, but it should be included in the next one. Thanks!
Plain tooltip should be used for quick descriptions, if you have a lot of text that requires longer than 1.5 seconds to read please use rich tooltip and make it actionable.
@
- I think this is a reasonable ask, will file a bug to address.
- We recently merged a change to tooltips that exposes the
PopupPositionProvider
, you should be able to alter the actual position with this parameter. - You can use the
focusable
parameter to achieve this flow. - Dallas What do you think about exposing the maxWidth for Plain and Rich tooltips? From my understanding, this isn't something that we normally expose.
- Yeah this is part of the
Popup
implementation. If you'd like to escalate you can file an accessibility bug with the wider compose team. - Yup, this is expected.
- This is something that we're currently working on! No exact ETA, but it would be in M3.
st...@google.com <st...@google.com> #5
Branch: androidx-main
commit 6c66eb5e520994bf4f500731d2bc2e57081a89f0
Author: Kevin Truong <kevinctruong@google.com>
Date: Thu Oct 19 10:22:52 2023
[Tooltip] Adding tonalElevation and shadowElevation to APIs
Adding more flexibility to the component by allowing users to set the tonal and shadow elevation of the tooltip.
Also moving TooltipBox, PlainTooltip, and RichTooltip APIs back to experimental since they were accidentally released as stable.
Bug: 293939035
Test: N/A
Relnote: Adding tonalElevation and shadowElevation to tooltip APIs. Additionally, moving TooltipBox, PlainTooltip, and RichTooltip APIs back to experimental since they were accidentally released as stable.
Change-Id: If0f66a8ccc737a0c163fd2b2a890ce60ad3d5fe4
M compose/material3/material3/api/current.txt
M compose/material3/material3/api/restricted_current.txt
M compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/TooltipDemo.kt
M compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
ys...@google.com <ys...@google.com> #6
The main suggestion was to make the api flexible so a custom dismiss duration could be passed in when isPersistent = false and no extra logic is required. Any plans on providing that flexibility in the TooltipBox?
ha...@google.com <ha...@google.com>
ys...@google.com <ys...@google.com> #7
I'm still getting this with deeplinks from Complications. appIntent works, but sessionIntent crashes about 50% of the time.
private fun sessionIntent(conference: String, sessionDetails: SessionDetails): PendingIntent? {
val sessionDetailIntent = Intent(
Intent.ACTION_VIEW,
"confetti://confetti/session/${conference}/${sessionDetails.id}".toUri()
)
return getActivity(
this,
0,
sessionDetailIntent,
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
)
}
private fun appIntent(): PendingIntent? {
val appIntent = Intent(
this,
MainActivity::class.java
)
return getActivity(
this,
0,
appIntent,
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
)
}
st...@google.com <st...@google.com> #8
See also
ys...@google.com <ys...@google.com> #9
The current code has that deeplink disabled because it crashes.
Effective repro steps
Full Steps:
- install app on Mobile, select a conference, bookmark some sessions
- Install app on Wear, login, confirm bookmarks are synced automatically.
- Add a complication (if you undo my edit) then it should show the session details in the complication.
Short Steps.
- Change the
https://github.com/joreilly/Confetti/blob/main/wearApp/src/main/java/dev/johnoreilly/confetti/wear/complication/NextSessionComplicationService.kt to use a deeplink for the result returned in all cases (remove the if conditions) - Add a complication
If you want I can make a test PR tomorrow
ha...@google.com <ha...@google.com> #11
Thanks Yuri, Able to reproduce the issue for this application. Looking into the root cause now.
ha...@google.com <ha...@google.com> #12
The issue seems to be occuring while using deep links for navigation. The navigation graph doesn't seem to be built completely hence throwing this issue.
Though the issue is not occuring 100% of the times, pasting easier steps to reproduce this issue in androidx:
Code:
@Composable
fun Demo()
{
val navController = rememberSwipeDismissableNavController()
SwipeDismissableNavHost(navController = navController, startDestination = "hello"){
composable(route = "hello"){
Test()
}
composable(route = "hello2", deepLinks = listOf(
navDeepLink {uriPattern = "hk1://hk1"}
)){
Test2()
}
}
}
@Composable
fun Test2(){
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
Text("Hello Test 2")
}
}
@Composable
fun Test(){
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
Text("Hello world")
}
}
Add the following intent filter to AndroidManifest:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="hk1"
android:scheme="hk1" />
</intent-filter>
Next steps:
- Investigate on initialization flow of navigation graph.
- Link Intent deep link handling with navigation flow.
ha...@google.com <ha...@google.com> #13
Created internal tracking bug:
ha...@google.com <ha...@google.com> #14
The root cause of this issue is inconsistent backstack state when handling the pending intent.
The activity was being called twice once with backstack size 0 and next time with the expected size.
We need to add the following flags FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_CLEAR_TASK
to the session intent to ensure a consistent activity back stack.
See related bug:
@yschimke Can you please confirm if this works for you?
ys...@google.com <ys...@google.com> #15
So far, working perfectly.
ha...@google.com <ha...@google.com>
ys...@google.com <ys...@google.com> #16
I'd suggest fixing this anyway, does it fail like this on mobile? Or just silently ignore the empty stack?
The launch may not be getting the desired result, but it shouldn't cause the app to crash like this.
ys...@google.com <ys...@google.com> #18
Can we reopen then?
ha...@google.com <ha...@google.com>
st...@google.com <st...@google.com> #19
The reason for throwing the exception in the past was that the backstack could be empty due to using the wrong function to build the navigation graph - so there was a specific request to throw an error explaining why nothing was displayed on the screen.
This is something that the developer needs to address, so I think better to throw an error that describes the problem (the backstack is empty, which should not happen) and add more background in the comments as per
ys...@google.com <ys...@google.com> #20
It may not be optimal, but it's the default and not an error on mobile.
I don't think anything means this only applies to launching your own app, but I could be missing something. It seems like a DOS if I can launch an intent with deeplinks and nuke my competitors apps. To we have other examples of where you can cause crashes like this?
st...@google.com <st...@google.com> #21
Thanks for the extra insight Yuri - I agree that this can be seen as a vulnerability, so let's remove the exception and log a message instead, then continue (which will in some cases leave a blank screen, but the log message and extra comments in the source code should help developers in that case).
ha...@google.com <ha...@google.com>
ha...@google.com <ha...@google.com> #22
ap...@google.com <ap...@google.com> #23
Branch: androidx-main
commit 6d370cb96df84e695e3aa88db7cf4ad35a494bf4
Author: hardik <hardikkwl@google.com>
Date: Thu May 04 17:23:30 2023
Log warning inplace of throwing exception for empty backstack in SwipeDismissableNavHost
Relnote: SwipeDismissableNavHost now logs a warning with potential causes of empty backstack. This is done to prevent unexpected crashes caused because of IllegalArgumentException which was thrown when the backstack was empty.
Bug: 277700155
Test: Tested manually by triggering deep link intents
Change-Id: I04a812325b4a8b5d59d4ce1ba2f770ee75b0ba71
M wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
ju...@google.com <ju...@google.com> #24
The following release(s) address this bug.It is possible this bug has only been partially addressed:
androidx.wear.compose:compose-navigation:1.2.0-beta01
Description
Version used: 1.2.0-alpha07
Devices/Android versions reproduced on: Pixel Watch
Not 100% reproducible, and I haven't nailed down exactly what is going on. Feel free to close, but raising here as we saw it once previously in compose-wear slack.
Happens with
```
Fatal Exception: java.lang.IllegalArgumentException: The WearNavigator backstack is empty, there is no navigation destination to display.
at androidx.wear.compose.navigation.SwipeDismissableNavHostKt.SwipeDismissableNavHost(SwipeDismissableNavHost.kt:562)
at androidx.wear.compose.navigation.SwipeDismissableNavHostKt.SwipeDismissableNavHost(SwipeDismissableNavHost.kt:154)
at com.google.android.horologist.compose.navscaffold.WearNavScaffoldKt$WearNavScaffold$4.invoke(WearNavScaffold.kt:210)
at com.google.android.horologist.compose.navscaffold.WearNavScaffoldKt$WearNavScaffold$4.invoke(WearNavScaffold.kt:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at androidx.wear.compose.material.ScaffoldKt.Scaffold(Scaffold.kt:381)
at com.google.android.horologist.compose.navscaffold.WearNavScaffoldKt.WearNavScaffold(WearNavScaffold.kt:245)
at dev.johnoreilly.confetti.wear.ui.ConfettiAppKt$ConfettiApp$1.invoke(ConfettiApp.kt:57)
at dev.johnoreilly.confetti.wear.ui.ConfettiAppKt$ConfettiApp$1.invoke(ConfettiApp.kt:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at dev.johnoreilly.confetti.wear.ui.ThemeKt$ConfettiTheme$2.invoke(Theme.kt:43)
at dev.johnoreilly.confetti.wear.ui.ThemeKt$ConfettiTheme$2.invoke(Theme.kt:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:40)
at androidx.wear.compose.material.TextKt.ProvideTextStyle(Text.kt:104)
at androidx.wear.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:45)
at androidx.wear.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:40)
at androidx.wear.compose.material.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:424)
at dev.johnoreilly.confetti.wear.ui.ThemeKt.ConfettiTheme(Theme.kt:112)
at dev.johnoreilly.confetti.wear.ui.ThemeKt.ConfettiTheme(Theme.kt:173)
at dev.johnoreilly.confetti.wear.ui.ConfettiAppKt.ConfettiApp(ConfettiApp.kt:130)
at dev.johnoreilly.confetti.wear.MainActivity$onCreate$1$1.invoke(MainActivity.java:45)
at dev.johnoreilly.confetti.wear.MainActivity$onCreate$1$1.invoke(MainActivity.java:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:40)
at dev.johnoreilly.confetti.wear.MainActivity$onCreate$1.invoke(MainActivity.java:106)
at dev.johnoreilly.confetti.wear.MainActivity$onCreate$1.invoke(MainActivity.java:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at androidx.compose.ui.platform.ComposeView.Content(ComposeView.java:35)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(AbstractComposeView.java:35)
at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(AbstractComposeView.java:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:40)
at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:347)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals_android.kt:45)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals_android.kt:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:40)
at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals_android.kt:302)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(WrappedComposition.java:41)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(WrappedComposition.java:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:40)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(WrappedComposition.java:155)
at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(WrappedComposition.java:8)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambdaImpl.java:8)
at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm_jvm.kt:22)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(ComposerImpl.java:19)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(ComposerImpl.java)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(SnapshotStateKt__DerivedState.kt:45)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(SnapshotState.kt)
at androidx.compose.runtime.ComposerImpl.doCompose(ComposerImpl.java:138)
at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(ComposerImpl.java:18)
at androidx.compose.runtime.CompositionImpl.composeContent(CompositionImpl.java:17)
at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.java:34)
at androidx.compose.runtime.CompositionImpl.setContent(CompositionImpl.java:15)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(WrappedComposition.java:82)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(WrappedComposition.java:2)
at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.java:11)
at androidx.compose.ui.platform.WrappedComposition.setContent(WrappedComposition.java:12)
at androidx.compose.ui.platform.WrappedComposition.onStateChanged(WrappedComposition.java:28)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:24)
at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:105)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(WrappedComposition.java:43)
at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(WrappedComposition.java:2)
at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.java:115)
at android.view.View.dispatchAttachedToWindow(View.java:20516)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3493)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3500)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3500)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3500)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2462)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1997)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8250)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
at android.view.Choreographer.doCallbacks(Choreographer.java:796)
at android.view.Choreographer.doFrame(Choreographer.java:731)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7660)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
```