private fun Modifier.onShown( @FloatRange(from = 0.0, to = 1.0) threshold: Float, onVisible: () -> Unit, ): Modifier { val initialTime = System.currentTimeMillis() var lastVisibleCoordinates: LayoutCoordinates? = null return composed { if (inComposePreview) { Modifier } else { val context = LocalContext.current var wasEventReported by remember { mutableStateOf(false) } val toolbarHeight = context.resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) val isToolbarPlacedAtBottom = context.settings().shouldUseBottomToolbar // Get a Rect of the entire screen minus system insets minus the toolbar val screenBounds = Rect() .apply { LocalView.current.getWindowVisibleDisplayFrame(this) } .apply { when (isToolbarPlacedAtBottom) { true -> bottom -= toolbarHeight false -> top += toolbarHeight } } // In the event this composable starts as visible but then gets pushed offscreen // before MINIMUM_TIME_TO_SETTLE_MS we will not report is as being visible. // In the LaunchedEffect we add support for when the composable starts as visible and then // it's position isn't changed after MINIMUM_TIME_TO_SETTLE_MS so it must be reported as visible. LaunchedEffect(initialTime) { delay(MINIMUM_TIME_TO_SETTLE_MS.toLong()) if (!wasEventReported && lastVisibleCoordinates?.isVisible(screenBounds, threshold) == true) { wasEventReported = true onVisible() } } onGloballyPositioned { coordinates -> if (!wasEventReported && coordinates.isVisible(screenBounds, threshold)) { if (System.currentTimeMillis() - initialTime > MINIMUM_TIME_TO_SETTLE_MS) { wasEventReported = true onVisible() } else { lastVisibleCoordinates = coordinates } } } } } }
Add a callback for when this Composable is "shown" on the screen. This checks whether the composable has at least [threshold] ratio of it's total area drawn inside the screen bounds. Does not account for other Views / Windows covering it.