okwasniewski / react-native-bottom-tabs

Native Bottom Tabs for React Native
https://okwasniewski.github.io/react-native-bottom-tabs/
MIT License
628 stars 24 forks source link

The library doesn't support edge-to-edge #97

Closed zoontek closed 3 weeks ago

zoontek commented 3 weeks ago

Hello again! πŸ“

I have a bad news, react-native-edge-to-edge is not at fault at all 😬

Having extra themes will be a nice touch to avoid the initial system bars blink (which is only visible if there's no splash screen, as applyEdgeToEdge will probably be called before you app can switch from your BootTheme to AppTheme)

To reproduce, I used a blank app with react-native-bottom-tabs@0.2.0, as I was not able to quickly understand where are the styles.xml and MainActivity.kt on this repository example app πŸ˜… I didn't installed react-native-edge-to-edge as I want to reproduction to be as small as possible.

It breaks immediately when I set WindowCompat.setDecorFitsSystemWindows(window, false). That's the first step of how to enable edge-to-edge.

This is of course used by react-native-edge-to-edge, but also by:

And probably a lot of other libs.

The reproduction can be found here. And these changes are enough to recreate the issue.

Screenshot 2024-10-30 at 18 33 12
zoontek commented 3 weeks ago

Did a quick check, the issue exists because you have to handle insets

Here, it's super buggy and all and I harcoded the value, but the tab bar has (I think) the correct height:

Screenshot 2024-10-30 at 19 17 08

The react-native-safe-area-context codebase could probably help you.

okwasniewski commented 3 weeks ago

@zoontek Thanks for your investigation! I will handle it in the library πŸ™Œ

okwasniewski commented 3 weeks ago

I got this to work!

@zoontek As you are more familiar with the topic, what's a reliable way of checking if edge to edge is enabled? It's the last thing Im struggling with

CleanShot 2024-10-30 at 22 06 19@2x

zoontek commented 3 weeks ago

@okwasniewski That's the tricky part. As I mentionned in this message, there's no way to properly detect edge-to-edge (see this tweet too). That's why we went for a common library, because at least this is something that could be detected.

Are you performing different computation if it's enabled or not (maybe you can inset in all cases, as it's 0 when disabled)?

If yes, add react-native-is-edge-to-edge as a dependency (it's extra lightweight), add a prop / option (navigationBarTranslucent, for example). Then follow the method in the README:

import { controlEdgeToEdgeValues, isEdgeToEdge } from "react-native-is-edge-to-edge";

const EDGE_TO_EDGE = isEdgeToEdge();

function TabBar({ navigationBarTranslucent = false }) {
  if (__DEV__) {
    controlEdgeToEdgeValues({ navigationBarTranslucent });
  }

  return (
    <NativeTabBar
      navigationBarTranslucent={EDGE_TO_EDGE || navigationBarTranslucent}
      // …
    />
  );
}

This way, it will just work out-of-the-box for people using react-native-edge-to-edge, and the others will still be able to set navigationBarTranslucent={true} if they want.

okwasniewski commented 3 weeks ago

Thanks for the tip!

Looks like this check on the native side val isSystemBarTransparent = window?.navigationBarColor == Color.TRANSPARENT might be enough for this library as I need to "stretch" the navigation view only when the navigationBarColor is transparent.

I've tested it with your library (enabled and disabled) and this PR: #101 should address this issue.

zoontek commented 3 weeks ago

@okwasniewski This is really fragile. On Android < 10, window?.navigationBarColor != Color.TRANSPARENT, even in edge-to-edge (it's a semi opaque white or black - Color.argb(0xe6, 0xFF, 0xFF, 0xFF) or Color.argb(0x80, 0x1b, 0x1b, 0x1b), depending on the OS versions)

Even on newer versions, buttons based navigation isn't transparent:

Screenshot 2024-10-31 at 00 00 48
zoontek commented 3 weeks ago

Note that if you don't want an extra JS dependency, you can achieve package detection in Kotlin too (Class.forName is handled by Proguard):

val isSystemBarTransparent = try {
  Class.forName("com.zoontek.rnedgetoedge.EdgeToEdgePackage")
  true
} catch (exception: ClassNotFoundException) {
  window?.navigationBarColor == Color.TRANSPARENT // fallback to best-effort detection
}

EDIT: the class check could be a val in a companion object, it only needs to be checked once in app lifecycle:

companion object {
  val EDGE_TO_EDGE = try {
    Class.forName("com.zoontek.rnedgetoedge.EdgeToEdgePackage")
    true
  } catch (exception: ClassNotFoundException) {
    false
  }
}

fun yourFn() {
  // fallback to best-effort detection
  val isSystemBarTransparent = EDGE_TO_EDGE || window?.navigationBarColor == Color.TRANSPARENT

  // …
okwasniewski commented 3 weeks ago

Thanks @zoontek this check in Kotlin is perfect!

Going to add it πŸ‘πŸ»

okwasniewski commented 3 weeks ago

Thanks for your support @zoontek I also added page in docs describing to install your library in order to enable the support: https://okwasniewski.github.io/react-native-bottom-tabs/docs/guides/edge-to-edge-support.html