Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: workaround for mChrome empty space when navigating after address bar collapses #11366

Merged

Conversation

BeeMargarida
Copy link
Member

@BeeMargarida BeeMargarida commented May 10, 2023

Motivation

Related to #11274 and Expensify/App#15749.

In mobile Chrome, the address bar collapses, which causes some problems: in an SPA and using a flex: 1; height: 100% style for the document, if the navigation is made with the address bar collapsed, it will show an empty space at the bottom of the screen (only visible if there are differences in background color).
A possible fix is updating the body height using viewport height units based on the window.innerHeight, which should have the correct height taking into consideration the collapsed address bar.

Resources:

Test plan

Run the example app in mobile chrome

AuthFlow example code modified to test this exact scenario
import { ParamListBase, useTheme } from '@react-navigation/native';
import {
  createStackNavigator,
  StackScreenProps,
} from '@react-navigation/stack';
import * as React from 'react';
import {
  ActivityIndicator,
  ScrollView,
  StyleSheet,
  TextInput,
  View,
} from 'react-native';
import { Button, Title } from 'react-native-paper';
import { SafeAreaProvider } from 'react-native-safe-area-context';

type AuthStackParams = {
  Home: undefined;
  SignIn: undefined;
  Chat: undefined;
};

const AUTH_CONTEXT_ERROR =
  'Authentication context not found. Have your wrapped your components with AuthContext.Consumer?';

const AuthContext = React.createContext<{
  isSignedIn: boolean;
  signIn: () => void;
  signOut: () => void;
}>({
  isSignedIn: false,
  signIn: () => {
    throw new Error(AUTH_CONTEXT_ERROR);
  },
  signOut: () => {
    throw new Error(AUTH_CONTEXT_ERROR);
  },
});

const SplashScreen = () => {
  const { colors } = useTheme();

  return (
    <View style={styles.content}>
      <ActivityIndicator color={colors.primary} />
    </View>
  );
};

const SignInScreen = ({
  navigation,
}: StackScreenProps<AuthStackParams, 'SignIn'>) => {
  const { signIn } = React.useContext(AuthContext);
  const { colors } = useTheme();

  return (
    <ScrollView>
      <View style={styles.content}>
        <TextInput
          placeholder="Username"
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <TextInput
          placeholder="Password"
          secureTextEntry
          style={[
            styles.input,
            { backgroundColor: colors.card, color: colors.text },
          ]}
        />
        <Button
          mode="contained"
          onPress={() => setTimeout(() => signIn(), 1000)}
          style={styles.button}
        >
          Sign in
        </Button>
        <Button
          onPress={() => navigation.navigate('Chat')}
          style={styles.button}
        >
          Go to Chat
        </Button>
      </View>
    </ScrollView>
  );
};

const HomeScreen = ({
  navigation,
}: StackScreenProps<AuthStackParams, 'Home'>) => {
  const { signOut } = React.useContext(AuthContext);

  return (
    <View style={styles.content}>
      <Title style={styles.text}>Signed in successfully 🎉</Title>
      <Button onPress={signOut} style={styles.button}>
        Sign out
      </Button>
      <Button onPress={() => navigation.navigate('Chat')} style={styles.button}>
        Go to Chat
      </Button>
    </View>
  );
};

const ChatScreen = ({ navigation }) => {
  const { isSignedIn, signIn, signOut } = React.useContext(AuthContext);

  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      if (typeof document === 'undefined' || !document.body) {
        // Only run when DOM is available
        return;
      }

      console.log('hererere', window.scrollY);

      document.getElementById('root')?.scrollIntoView();

      // window.scrollTo(0, 0);
    });

    // Return the function to unsubscribe from the event so it gets removed on unmount
    return unsubscribe;
  }, [navigation]);

  return (
    <View style={styles.content}>
      <Title style={styles.text}>What&apos;s up?</Title>
      <Title style={styles.text}>What&apos;s up?</Title>
      <Title style={styles.text}>What&apos;s up?</Title>
      <Title style={styles.text}>What&apos;s up?</Title>
      <Title style={{ position: 'absolute', bottom: 0 }}>Bottom</Title>
      {isSignedIn ? (
        <Button onPress={signOut} style={styles.button}>
          Sign out
        </Button>
      ) : (
        <Button onPress={signIn} style={styles.button}>
          Sign in
        </Button>
      )}
    </View>
  );
};

const SimpleStack = createStackNavigator<AuthStackParams>();

type State = {
  isLoading: boolean;
  isSignout: boolean;
  userToken: undefined | string;
};

type Action =
  | { type: 'RESTORE_TOKEN'; token: undefined | string }
  | { type: 'SIGN_IN'; token: string }
  | { type: 'SIGN_OUT' };

export function AuthFlow({ navigation }: StackScreenProps<ParamListBase>) {
  const [state, dispatch] = React.useReducer<React.Reducer<State, Action>>(
    (prevState, action) => {
      switch (action.type) {
        case 'RESTORE_TOKEN':
          return {
            ...prevState,
            userToken: action.token,
            isLoading: false,
          };
        case 'SIGN_IN':
          return {
            ...prevState,
            isSignout: false,
            userToken: action.token,
          };
        case 'SIGN_OUT':
          return {
            ...prevState,
            isSignout: true,
            userToken: undefined,
          };
      }
    },
    {
      isLoading: true,
      isSignout: false,
      userToken: undefined,
    }
  );

  React.useEffect(() => {
    const timer = setTimeout(() => {
      dispatch({ type: 'RESTORE_TOKEN', token: undefined });
    }, 1000);

    return () => clearTimeout(timer);
  }, []);

  React.useLayoutEffect(() => {
    navigation.setOptions({
      headerShown: false,
    });
  }, [navigation]);

  const isSignedIn = state.userToken !== undefined;

  const authContext = React.useMemo(
    () => ({
      isSignedIn,
      signIn: () => dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' }),
      signOut: () => dispatch({ type: 'SIGN_OUT' }),
    }),
    [isSignedIn]
  );

  if (state.isLoading) {
    return <SplashScreen />;
  }

  return (
    <SafeAreaProvider>
      <AuthContext.Provider value={authContext}>
        <SimpleStack.Navigator>
          {!isSignedIn ? (
            <SimpleStack.Screen
              name="SignIn"
              options={{
                title: 'Sign in',
                animationTypeForReplace: state.isSignout ? 'pop' : 'push',
                // cardStyle: { flex: 1 },
              }}
              component={SignInScreen}
            />
          ) : (
            <SimpleStack.Screen name="Home" component={HomeScreen} />
          )}
          <SimpleStack.Screen
            navigationKey={String(isSignedIn)}
            name="Chat"
            component={ChatScreen}
          />
        </SimpleStack.Navigator>
      </AuthContext.Provider>
    </SafeAreaProvider>
  );
}

const styles = StyleSheet.create({
  content: {
    flex: 1,
    padding: 16,
    justifyContent: 'center',
  },
  input: {
    margin: 8,
    padding: 10,
    borderRadius: 3,
    borderWidth: StyleSheet.hairlineWidth,
    borderColor: 'rgba(0, 0, 0, 0.08)',
  },
  button: {
    margin: 8,
  },
  text: {
    textAlign: 'center',
    margin: 8,
  },
});
Before With the fix
Screen.Recording.2023-05-10.at.15.45.11.mov
Screen.Recording.2023-05-10.at.15.44.50.mov

This next video contains an example of the modified AuthFlow with the fix applied. As it can be seen, if there is bottom content, it will cut when the address bar comes back.

hum.mov

@netlify
Copy link

netlify bot commented May 10, 2023

Deploy Preview for react-navigation-example ready!

Name Link
🔨 Latest commit a61a5ef
🔍 Latest deploy log https://app.netlify.com/sites/react-navigation-example/deploys/652e6ce2ad9dfd0008de19fc
😎 Deploy Preview https://deploy-preview-11366--react-navigation-example.netlify.app/
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@codecov-commenter
Copy link

codecov-commenter commented May 10, 2023

Codecov Report

Attention: 9 lines in your changes are missing coverage. Please review.

Comparison is base (27bfc12) 75.69% compared to head (a61a5ef) 75.57%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11366      +/-   ##
==========================================
- Coverage   75.69%   75.57%   -0.12%     
==========================================
  Files         202      202              
  Lines        5854     5864      +10     
  Branches     2304     2307       +3     
==========================================
+ Hits         4431     4432       +1     
- Misses       1372     1381       +9     
  Partials       51       51              
Files Coverage Δ
packages/stack/src/views/Stack/CardSheet.tsx 52.00% <10.00%> (-28.00%) ⬇️

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@BeeMargarida
Copy link
Member Author

Pinging about this, so that it does not get forgotten

@BeeMargarida
Copy link
Member Author

@satya164 Pinging so that it does not get forgotten

@BeeMargarida BeeMargarida force-pushed the fix/11274-blank-space-mChrome branch from 85d6afc to ab93902 Compare July 5, 2023 08:33
@BeeMargarida BeeMargarida force-pushed the fix/11274-blank-space-mChrome branch 2 times, most recently from 34ca2e9 to 03b177b Compare August 21, 2023 07:57
@BeeMargarida BeeMargarida force-pushed the fix/11274-blank-space-mChrome branch 2 times, most recently from 0aa248b to e4ad88b Compare October 4, 2023 09:06
@satya164 satya164 requested a review from osdnk October 11, 2023 21:26
Copy link
Member

@osdnk osdnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirm it works on my S22 Ultra and treat the issue was visible earlier.

The issue doesn't seem tobe visible on any other browser (including chrome on iOS)

@osdnk
Copy link
Member

osdnk commented Oct 12, 2023

If you want, I feel confident to go forward with the PR

@BeeMargarida BeeMargarida marked this pull request as ready for review October 13, 2023 07:25
@osdnk osdnk merged commit d638454 into react-navigation:main Oct 17, 2023
8 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants