Skip to content

Other modal appears behind InstagramStories modal when opening Share from Story Header #171

@vaibhav-delta

Description

@vaibhav-delta

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 };
`

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions