- 
                Notifications
    You must be signed in to change notification settings 
- Fork 56
Description
When using InstagramStories together with the BottomSheet, tapping the Share button causes either:
The sheet to render underneath the InstagramStories modal.
This blocks users from sharing from the stories UI.
`import { useCallback, useEffect, useMemo, useRef } from 'react';
import InstagramStories, {
InstagramStoriesPublicMethods,
} from '@birdwingo/react-native-instagram-stories';
import { useQueryClient } from '@tanstack/react-query';
import { useShallow } from 'zustand/react/shallow';
import { createStyleSheet, useStyles } from '@delta/native';
import { CACHE_KEYS } from '@delta/service/constants';
import { useStoriesMutation } from '@delta/service/src/hooks/queries/general/useStoriesQuery';
import { useStoryStore } from 'src/store/story/story';
import { StoryBackground } from './storyBackground/StoryBackground';
import { StoryCTA } from './StoryCTA';
import { StoryHeader } from './storyHeader/StoryHeader';
import { StoryShareBottomSheet } from './storyHeader/StoryShareBottomSheet';
const themedStyles = createStyleSheet((theme) => ({
container: {
marginTop: -70,
},
mediaContainerStyle: {
flex: 1,
marginTop: theme.spacing['3xl'],
},
}));
const StoryScreen = () => {
const { styles, theme } = useStyles(themedStyles);
const storyRef = useRef(null);
const {
stories,
viewedStories,
viewedSlides,
currentStoryIndex,
currentSlideIndex,
markStoryAsViewed,
setViewedSlides,
setCurrentSlideIndex,
setCurrentStoryIndex,
showStory,
setShowStory,
} = useStoryStore(
useShallow((state) => ({
currentSlideIndex: state.currentSlideIndex,
currentStoryIndex: state.currentStoryIndex,
markStoryAsViewed: state.markStoryAsViewed,
setCurrentSlideIndex: state.setCurrentSlideIndex,
setCurrentStoryIndex: state.setCurrentStoryIndex,
setViewedSlides: state.setViewedSlides,
showStory: state.showStory,
setShowStory: state.setShowStory,
stories: state.stories,
viewedSlides: state.viewedSlides,
viewedStories: state.viewedStories,
}))
);
const { mutate: updateViewedStory } = useStoriesMutation();
const queryClient = useQueryClient();
// API call to update the story and slide as viewed
const saveViewedStoryAndSlide = useCallback(async () => {
const storiesToUpdate: {
completed: boolean;
last_viewed_slide_id: number;
story_id: number;
}[] = [];
setShowStory(false);
Array.from(viewedStories).forEach((viewedStoryId) => {
  const story = stories.find((s) => s.story_id.toString() === viewedStoryId && !s.completed);
  if (story) {
    const isCompleted = story.slides.every((slide) =>
      viewedSlides.has(slide.slide_id.toString())
    );
    let lastViewedSlideIdForStory = 0;
    for (let i = story.slides.length - 1; i >= 0; i -= 1) {
      if (viewedSlides.has(story.slides[i].slide_id.toString())) {
        lastViewedSlideIdForStory = story.slides[i].slide_id;
        break;
      }
    }
    if (lastViewedSlideIdForStory) {
      storiesToUpdate.push({
        completed: isCompleted,
        last_viewed_slide_id: lastViewedSlideIdForStory,
        story_id: Number(story.story_id),
      });
    }
  }
});
await Promise.all(storiesToUpdate.map((storyData) => updateViewedStory(storyData))).then(() => {
  queryClient.invalidateQueries({ queryKey: [CACHE_KEYS.PERSONALISED_STORIES] });
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [viewedStories, viewedSlides, stories, updateViewedStory, queryClient]);
// Transform stories data to match Instagram stories package format
const transformedStories = useMemo(() => {
return stories.map((story) => ({
avatarSource: { uri: '' },
id: story.story_id.toString(),
name: story.caption,
renderStoryHeader: () => {
return ;
},
  stories: story.slides.map((slide) => ({
    animationDuration: (slide.duration || 30) * 1000,
    id: slide.slide_id.toString(),
    mediaType: 'image' as const,
    renderFooter: () => {
      return (
        <StoryCTA
          ctaLabel={story.cta_label}
          ctaLogoUrl={story.cta_logo_url}
          ctaUrl={story.cta_url}
        />
      );
    },
    source: { uri: slide.image_url },
  })),
}));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stories]);
const handleStoryProgress = (storyId?: string, slideId?: string) => {
if (storyId && slideId) {
if (!viewedStories.has(storyId)) {
markStoryAsViewed(storyId);
}
if (!viewedSlides.has(slideId)) {
setViewedSlides(slideId);
}
  const storyIndex = stories.findIndex((story) => story.story_id.toString() === storyId);
  const slideIndex = stories[storyIndex]?.slides.findIndex(
    (slide) => slide.slide_id.toString() === slideId
  );
  setCurrentStoryIndex(storyIndex);
  setCurrentSlideIndex(slideIndex);
}
};
useEffect(() => {
if (storyRef.current) {
storyRef.current.goToSpecificStory(
stories[currentStoryIndex]?.story_id?.toString() ?? '',
currentSlideIndex
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stories, storyRef]);
if (!stories.length) {
return null;
}
return (
);
};
export { StoryScreen };
`
`import { forwardRef, useCallback } from 'react';
import { InstagramStoriesPublicMethods } from '@birdwingo/react-native-instagram-stories';
import { SCREEN_HEIGHT } from '@gorhom/bottom-sheet';
import { Social } from 'react-native-share';
import { useShallow } from 'zustand/react/shallow';
import {
Box,
BottomSheet,
Flex,
SmR,
useStyles,
createStyleSheet,
commonStyles,
gutters,
H1SB,
Button,
} from '@delta/native';
import {
WhatsAppIcon,
FacebookIcon,
TelegramIcon,
TwitterIcon,
InstagramIcon,
} from '@delta/native/icons';
import { useStoryStore } from 'src/store/story/story';
import { launchShareSingle } from 'src/utils/socialShare';
const themedStyles = createStyleSheet((theme) => ({
bottomSheetContainer: {
backgroundColor: theme.colors.main.bg.surface,
},
socialAppButton: {
...commonStyles.center,
...gutters.sm3VPadding,
},
socialAppIcon: {
...gutters.sm2BMargin,
},
socialAppsContainer: {
...commonStyles.flexRow,
...commonStyles.jcSpaceBetween,
},
}));
// Social app configurations
const SOCIAL_APPS = [
{
icon: ,
id: Social.Whatsapp,
name: 'WhatsApp',
},
{
icon: ,
id: Social.Twitter,
name: 'Twitter',
},
{
icon: ,
id: Social.Instagram,
name: 'Instagram',
},
{
icon: ,
id: Social.Telegram,
name: 'Telegram',
},
{
icon: ,
id: Social.Facebook,
name: 'Facebook',
},
] as const;
const StoryShareBottomSheet = forwardRef((_, ref) => {
const { styles } = useStyles(themedStyles);
const { isOpen, setIsShareBottomSheetOpen, currentSlideIndex, currentStoryIndex, stories } =
useStoryStore(
useShallow((state) => ({
currentSlideIndex: state.currentSlideIndex,
currentStoryIndex: state.currentStoryIndex,
isOpen: state.isShareBottomSheetOpen,
setIsShareBottomSheetOpen: state.setIsShareBottomSheetOpen,
stories: state.stories,
}))
);
const currentStory = stories?.[currentStoryIndex];
const currentSlide = currentStory?.slides?.[currentSlideIndex];
// Function to convert image URL to base64 with proper error handling
const convertImageToBase64 = async (imageUrl: string): Promise => {
try {
const response = await fetch(imageUrl);
  const blob = await response.blob();
  return await new Promise<string>((resolve) => {
    const reader = new FileReader();
    reader.onload = () => {
      const base64 = reader.result as string;
      const base64Data = base64.split(',')[1];
      const formattedBase64 = `data:image/jpeg;base64,${base64Data}`;
      resolve(formattedBase64);
    };
    reader.readAsDataURL(blob);
  });
} catch (error) {
  return '';
}
};
const handleSocialShare = useCallback(
async (socialApp: (typeof SOCIAL_APPS)[number]) => {
try {
const boldCaption = *${currentStory?.caption || ''}*;
const shareMessage = \n${boldCaption}\n\n${currentStory?.cta_url || ''};
    const base64Image = await convertImageToBase64(currentSlide.image_url);
    const fileNameWithTimestamp = `story_${currentSlide.slide_id}_${Date.now()}.jpg`;
    const shareOptions = {
      failOnCancel: false,
      filename: fileNameWithTimestamp,
      forceDialog: true,
      isNewTask: true,
      message: shareMessage,
      social: socialApp.id,
      title: currentStory?.title || 'Check out this story',
      type: 'image/jpeg',
      url: base64Image,
    };
    await launchShareSingle(shareOptions);
  } catch (error) {
    // empty
  }
},
[currentSlide, currentStory]
);
const onClose = useCallback(() => {
setIsShareBottomSheetOpen(false);
if (ref && 'current' in ref && ref.current) {
ref.current.resume();
}
}, [setIsShareBottomSheetOpen, ref]);
return (
<BottomSheet
isOpen={isOpen}
onClose={onClose}
sliderMaxHeight={SCREEN_HEIGHT * 0.2}
enableBackDropDismiss
headerTitle="Share via"
sheetContainerStyle={styles.bottomSheetContainer}
>
{SOCIAL_APPS.map((app) => (
<Button
key={app.id}
variant="text"
borderLess
style={styles.socialAppButton}
onPress={() => handleSocialShare(app)}
testID={share-${app.id}}
>
{app.icon}
{app.name}
))}
);
});
export { StoryShareBottomSheet };
`