Bharath-CODER
Bharath-CODER

Reputation: 392

BottomSheet (@gorhom/bottom-sheet) doesn't drag the sheet up or down (iOS Only)

Sometimes BottomSheet doesn't showing, I think it happens because of conflict with. When I build release APK and install it on IOS, when you open application first time then bottom sheet doesn't show but if you close and reopen application then bottom sheet appears. Had anyone this problem? This issue not appear in android, it issue only in IOS

import React, {
  useCallback,
  useEffect,
  useLayoutEffect,
  useRef,
  useState,
} from 'react';
import {
  Text,
  StyleSheet,
  Image,
  Linking,
  TouchableOpacity,
  View,
  Platform,
} from 'react-native';
import { Box, AbstractButton } from '../../components';
import { COLORS, assets, FONTS, FONT_WEIGHT, SIZES } from '../../constants';
import { RootStackParamList } from '../../navigation/types';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { useSelector } from 'react-redux';
import { RootState } from '../../state/rootReducer';
import { useAppDispatch } from '../../state/index';
import { setProfilePhoto, setUserId } from '../../state/onboarding';
import { setIsBottomSheet, setIsEnableHeader } from '../../state/generalUtil';
import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet';
import ProfilePhotoBottomSheet from './ProfilePhotoBottomSheet';
import { useBackHandler, useDimensions } from '@react-native-community/hooks';
import {
  addDoitLater,
  createUserOnboarding,
  getDoitLater,
  getUserDataById,
  uploadProfileImage,
} from '../../service/OnboardingService';
import {
  setBarStyle,
  setStatusbarColor,
  setTranslucent,
} from '../../state/generalUtil';
import { CustomCamera } from '../../components/CameraScreen';
// import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
import ApiUtil from '../../util/ApiUtil';
import { CommonActions } from '@react-navigation/native';
import ImagePicker from 'react-native-image-crop-picker';
import PermissionService from '../../service/PermissionService';
import { InfoPopUp } from '../../components/PopUpModal';
import CameraInfoPopup from './CameraInfoPopup';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

const ProfilePhoto = ({
  route,
  navigation,
}: NativeStackScreenProps<RootStackParamList>) => {
  const { profilePhoto, userId } = useSelector(
    (state: RootState) => state.onboarding,
  );
  const { PERMISSIONS_TYPE, checkPermissions, requestPermisssions } =
    PermissionService;
  const [isSelected, setIsSelected] = useState(false);
  const [openCamera, setOpenCamera] = useState(false);
  const [visible, setVisible] = useState(false);
  const dispatch = useAppDispatch();
  const { screen, window } = useDimensions();
  // var ImagePicker = NativeModules.ImageCropPicker;

  useEffect(() => {
    if (openCamera) {
      dispatch(setStatusbarColor('#000000'));
      dispatch(setBarStyle('light-content'));
      dispatch(setIsBottomSheet(true));
      dispatch(setTranslucent(false));
      dispatch(setIsEnableHeader(false));
    } else {
      dispatch(setStatusbarColor('#F2F2EF'));
      dispatch(setBarStyle('dark-content'));
      dispatch(setIsBottomSheet(false));
      dispatch(setTranslucent(false));
    }
  }, [openCamera, dispatch]);

  // BottomSheet Hooks
  const sheetRef = useRef<BottomSheet>(null);

  // Keep Tracking bootom sheet is open or not.
  const [isOpen, setIsOpen] = useState(false);

  // BottomSheet snap variables
  const snapPoints = ['38%'];

  // BottomSheet Callbacks
  const handleClosePress = useCallback(() => {
    sheetRef.current?.close();
  }, []);

  const handleCloseModal = useCallback(() => {
    setVisible(false);
  }, []);

  const UpdateProfilePhoto = async () => {
    const userData: any = await getUserDataById();
    if (userData?.basicInfo?.userId) {
      await uploadProfileImage(profilePhoto.url, userData?.basicInfo?.userId);
      dispatch(setUserId(userData.basicInfo?.userId));
    }
  };

  // BottomSheet Callbacks
  const handleSnapPress = useCallback(index => {
    sheetRef.current?.snapToIndex(index);
    setIsOpen(true);
  }, []);

  const handleTakePhoto = async () => {
    const PermissionStatus = await checkPermissions(PERMISSIONS_TYPE.camera);
    if (PermissionStatus === 'blocked') {
      setVisible(true);
    }
    const permission = await requestPermisssions(PERMISSIONS_TYPE.camera);
    if (permission === 'granted') {
      setOpenCamera(true);
    }
  };

  const handleCloseCam = (image?: any) => {
    if (image) {
      dispatch(setProfilePhoto(image.uri));
    }
    setOpenCamera(false);
    setIsOpen(false);
  };

  const handleSelectPhoto = () => {
    ImagePicker.openPicker({
      width: 300,
      height: 400,
      mediaType: 'photo',
    }).then(async image => {
      setIsSelected(true);
      dispatch(setProfilePhoto(image.path));
    });
  };

  const handleContinue = () => {
    if (userId) {
      UpdateProfilePhoto();
      createUserOnboarding(userId, 10);
    }
    if (route?.params?.doItLaterFlag === 10) {
      navigation.dispatch(
        CommonActions.reset({
          index: 0,
          routes: [{ name: 'ThingsToDoScreen' }],
        }),
      );
    } else {
      navigation.navigate('ConfirmDetails');
    }
  };

  const doItLater = async () => {
    dispatch(setProfilePhoto(''));
    if (route?.params?.doItLaterFlag === 10) {
      navigation.dispatch(
        CommonActions.reset({
          index: 0,
          routes: [{ name: 'ThingsToDoScreen' }],
        }),
      );
    } else {
      navigation.navigate('ConfirmDetails');
    }
  };

  useBackHandler(
    useCallback(() => {
      if (isOpen) {
        sheetRef.current?.close();
      } else {
        navigation.goBack();
      }
      return true;
    }, [isOpen]),
  );

  return (
    <>
      {openCamera ? (
        <Box width={window.width} height={window.height - 40}>
          <CustomCamera type={'front'} close={handleCloseCam} />
        </Box>
      ) : (
        <>
          <Box style={isOpen ? styles.containerOpacity : styles.container}>
            <InfoPopUp visible={visible} handleCloseModal={handleCloseModal}>
              {<CameraInfoPopup handleCloseModal={handleCloseModal} />}
            </InfoPopUp>
            <Box>
              <Box
                pt={145}
                flexDirection={'column'}
                style={{ alignItems: 'center' }}>
                <TouchableOpacity
                  disabled={isOpen}
                  onPress={() => {
                    handleSnapPress(1);
                  }}>
                  <View style={styles.photoView}>
                    {profilePhoto.url ? (
                      <>
                        <Box>
                          <Image
                            source={{ uri: profilePhoto.url }}
                            style={styles.image}
                          />
                        </Box>
                        <Box style={styles.insideImg3}>
                          <Image
                            source={assets.EditPen}
                            style={styles.PlusIconImage}
                          />
                        </Box>
                      </>
                    ) : (
                      <>
                        <Box style={styles.insideImg1}>
                          <Image
                            source={assets.Smile}
                            style={styles.SmileImage}
                          />
                        </Box>
                        <Box style={styles.insideImg2}>
                          <Image
                            source={assets.PlusIcon}
                            style={styles.PlusIconImage}
                          />
                        </Box>
                      </>
                    )}
                  </View>
                </TouchableOpacity>

                <Text style={styles.text}>Add profile photo</Text>
                <Text style={styles.text1}>
                  To promote a safe and transparent community, we recommend a
                  clear photo of yourself
                </Text>
              </Box>
            </Box>
          </Box>
          <Box style={styles.continueBtn}>
            <AbstractButton
              disabled={!profilePhoto.url}
              onPress={handleContinue}>
              Continue
            </AbstractButton>
            <TouchableOpacity onPress={doItLater}>
              <Text style={styles.text2}>Do it later</Text>
            </TouchableOpacity>
          </Box>
          {/* Bottom Sheet */}
          {isOpen && (
            <BottomSheet
              ref={sheetRef}
              snapPoints={snapPoints}
              enablePanDownToClose={true}
              onClose={() => setIsOpen(false)}
              backgroundStyle={{
                backgroundColor: COLORS.background.primary,
                borderColor: COLORS.gray,
                borderWidth: 2,
              }}>
              <GestureHandlerRootView>
                <BottomSheetView>
                  <ProfilePhotoBottomSheet
                    route={route}
                    navigation={navigation}
                    handleTakePhoto={handleTakePhoto}
                    handleSelectPhoto={handleSelectPhoto}
                    handleClosePress={handleClosePress}
                  />
                </BottomSheetView>
              </GestureHandlerRootView>
            </BottomSheet>
          )}
        </>
      )}
    </>
  );
};

const styles = StyleSheet.create({
  continueBtn: {
    position: 'absolute',
    justifyContent: 'center',
    alignItems: 'center',
    alignSelf: 'center',
    width: '100%',
    paddingHorizontal: 10,
    top:
      Platform.OS === 'ios'
        ? SIZES.screen_height / 1.4
        : SIZES.screen_height / 1.35,
  },
  container: {
    flex: 1,
    paddingHorizontal: 20,
    backgroundColor: COLORS.background.primary,
  },
  containerOpacity: {
    flex: 1,
    opacity: 0.5,
    paddingHorizontal: 20,
    // backgroundColor: COLORS.lightGray,
  },
  container1: {
    backgroundColor: COLORS.background.primary,
    alignItems: 'center',
    flexDirection: 'row',
    justifyContent: 'space-between',
    paddingHorizontal: 20,
  },
  SmileImage: {
    width: 65,
    height: 64,
  },
  PlusIconImage: {
    width: 40,
    height: 40,
  },
  text: {
    paddingTop: '10%',
    color: COLORS.black,
    fontSize: SIZES.extraLarge,
    fontFamily: FONTS.PlayfairDisplayBold,
    lineHeight: 30,
    alignItems: 'center',
  },
  text1: {
    fontSize: SIZES.font,
    fontFamily: FONTS.MerriweatherRegular,
    lineHeight: 24,
    color: '#2B2928',
    paddingHorizontal: '15%',
    paddingVertical: '4%',
    textAlign: 'center',
  },
  text2: {
    paddingTop: '8%',
    color: COLORS.black,
    fontFamily: FONTS.MerriweatherBold,
    fontSize: 13,
    lineHeight: 21,
    textAlign: 'center',
  },
  photoView: {
    width: 150,
    height: 150,
    backgroundColor: COLORS.white,
    borderRadius: 100,
  },
  insideImg1: {
    top: '30%',
    left: '28%',
  },
  insideImg2: {
    top: '30%',
    left: '70%',
  },
  insideImg3: {
    top: '-30%',
    left: '70%',
  },
  image: {
    width: 150,
    height: 150,
    borderRadius: 100,
  },
});

export default ProfilePhoto;

Upvotes: 0

Views: 6639

Answers (3)

Swetha Selvam
Swetha Selvam

Reputation: 242

Update: Different similar issue but on Android

I was also having this exact problem, my BottomSheetModals and BottomSheetScrollViews were not responding to touch events on Android.

Root Cause

react-native-gesture-handler requires a wrapper around the root view to function. In v1 of the library, this was done on the native side, and expo did this for you. In v2 of the library, you must use the GestureHandlerRootView in your app manually. Upgrading to SDK 44 of expo removes the native RNGH setup.

FIX

The GestureHandlerRootView must be applied as high as possible in your app's component tree.

In my case, I had my BottomSheetModalProvider outside the GestureHandlerRootView, and swapping these two components fixed the issue for me!

Before:

<BottomSheetModalProvider>
  <GestureHandlerRootView style={{ flex: 1 }}>
    <MainNavigation />
  </GestureHandlerRootView>
</BottomSheetModalProvider>

After:

<GestureHandlerRootView style={{ flex: 1 }}>
  <BottomSheetModalProvider>
    <MainNavigation />
  </BottomSheetModalProvider>
</GestureHandlerRootView>

@gorhom does it make sense to update the docs to say that BottomSheetModalProvider must be inside GestureHandlerRootView?

Upvotes: 11

Bharath-CODER
Bharath-CODER

Reputation: 392

Finally, I got a solution for this issue. don't wrap with Ternary operator the bottomsheet. pls reffer below code.

Before :

{isOpen && (
            <BottomSheet
              ref={sheetRef}
              snapPoints={snapPoints}
              enablePanDownToClose={true}
              onClose={() => setIsOpen(false)}
              backgroundStyle={{
                backgroundColor: COLORS.background.primary,
                borderColor: COLORS.gray,
                borderWidth: 2,
              }}>
              <GestureHandlerRootView>
                <BottomSheetView>
                  <ProfilePhotoBottomSheet
                    route={route}
                    navigation={navigation}
                    handleTakePhoto={handleTakePhoto}
                    handleSelectPhoto={handleSelectPhoto}
                    handleClosePress={handleClosePress}
                  />
                </BottomSheetView>
              </GestureHandlerRootView>
            </BottomSheet>
          )}
        </>
      )}

After fix :

<BottomSheet
index={-1}
              ref={sheetRef}
              snapPoints={snapPoints}
              enablePanDownToClose={true}
              onClose={() => setIsOpen(false)}
              backgroundStyle={{
                backgroundColor: COLORS.background.primary,
                borderColor: COLORS.gray,
                borderWidth: 2,
              }}>
                <BottomSheetView>
                  <ProfilePhotoBottomSheet
                    route={route}
                    navigation={navigation}
                    handleTakePhoto={handleTakePhoto}
                    handleSelectPhoto={handleSelectPhoto}
                    handleClosePress={handleClosePress}
                  />
                </BottomSheetView>
            </BottomSheet>
    ```

Upvotes: 0

RodSarhan
RodSarhan

Reputation: 1457

make sure to wrap your whole app with

<GestureHandlerRootView style={{flex:1}}>
{your other components}
</GestureHandlerRootView>

and read the gesture handler docs for installation and setup tips if this doesn't solve your issue

you might also need to add import 'react-native-gesture-handler'; at the beginning of your index.js

Upvotes: 2

Related Questions