import { useRef, useEffect, useState } from "react"; import { View, Text, StyleSheet, Modal, TouchableOpacity, Platform, Linking, Share, useWindowDimensions, Image, } from "react-native"; import { BlurView } from "expo-blur"; import { LinearGradient } from "expo-linear-gradient"; import Animated, { FadeIn, FadeOut, SlideInDown, SlideOutDown, } from "react-native-reanimated"; import { Ionicons } from "@expo/vector-icons"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import ViewShot from "react-native-view-shot"; import * as Sharing from "expo-sharing"; import * as FileSystem from "expo-file-system/legacy"; import { colors, gradients, radius, typography, spacing, blur } from "../../theme"; const logo = require("../../../assets/logo.png"); const MAX_CARD_WIDTH = 340; interface SharePreviewModalProps { visible: boolean; onClose: () => void; question: { text: string; partner_text?: string | null; is_two_part?: boolean; }; packName?: string; cardColor?: string; } export function SharePreviewModal({ visible, onClose, question, packName, cardColor, }: SharePreviewModalProps) { const insets = useSafeAreaInsets(); const { width: screenWidth } = useWindowDimensions(); const cardWidth = Math.min(screenWidth - 64, MAX_CARD_WIDTH); const shareViewRef = useRef(null); const [capturedUri, setCapturedUri] = useState(null); const [isCapturing, setIsCapturing] = useState(false); // Capture the card when modal becomes visible useEffect(() => { if (visible && shareViewRef.current) { setIsCapturing(true); // Small delay to ensure the view is rendered const timer = setTimeout(async () => { try { const uri = await shareViewRef.current?.capture?.(); if (uri) { setCapturedUri(uri); } } catch (error) { console.error('Error capturing share image:', error); } finally { setIsCapturing(false); } }, 100); return () => clearTimeout(timer); } }, [visible]); // Reset state when modal closes useEffect(() => { if (!visible) { setCapturedUri(null); } }, [visible]); const handleInstagramShare = async () => { if (!capturedUri) return; try { // Check if Instagram is installed const instagramUrl = 'instagram://app'; const canOpenInstagram = await Linking.canOpenURL(instagramUrl); if (canOpenInstagram && Platform.OS === 'ios') { // For iOS, we can use the Instagram Stories URL scheme // First, we need to copy the image to a location Instagram can access const base64 = await FileSystem.readAsStringAsync(capturedUri, { encoding: FileSystem.EncodingType.Base64, }); // Use Instagram Stories URL scheme const instagramStoriesUrl = `instagram-stories://share?source_application=com.sauci.app`; // Try to open Instagram Stories with the image // Note: This requires the image to be in the pasteboard on iOS await Linking.openURL(instagramStoriesUrl); } else { // Fallback to regular sharing await handleMoreShare(); } } catch (error) { console.error('Instagram share error:', error); // Fallback to regular sharing await handleMoreShare(); } }; const handleMessagesShare = async () => { if (!capturedUri) { // Fallback to text await Share.share({ message: `${question.text}\n\nDiscover more on sauci.app`, }); return; } try { // On iOS, we can share directly to Messages if (Platform.OS === 'ios') { await Sharing.shareAsync(capturedUri, { mimeType: 'image/png', UTI: 'public.png', }); } else { await handleMoreShare(); } } catch (error) { console.error('Messages share error:', error); await Share.share({ message: `${question.text}\n\nDiscover more on sauci.app`, }); } }; const handleMoreShare = async () => { if (!capturedUri) { await Share.share({ message: `${question.text}\n\nDiscover more on sauci.app`, }); return; } try { const isAvailable = await Sharing.isAvailableAsync(); if (isAvailable) { await Sharing.shareAsync(capturedUri, { mimeType: 'image/png', dialogTitle: 'Share this question', }); } else { await Share.share({ message: `${question.text}\n\nDiscover more on sauci.app`, }); } } catch (error) { console.error('Share error:', error); } }; const useBlur = Platform.OS === 'ios'; return ( {/* Backdrop */} {useBlur ? ( ) : ( )} {/* Content */} {/* Header */} Share {/* Card Preview */} {/* Share Actions */} ); } function ShareActionButton({ icon, label, colors: buttonColors, onPress, disabled, isOutline, }: { icon: string; label: string; colors: string[]; onPress: () => void; disabled?: boolean; isOutline?: boolean; }) { return ( {!isOutline && ( )} {label} ); } function ShareableCard({ question, packName, cardWidth, cardColor, }: { question: { text: string; partner_text?: string | null; is_two_part?: boolean; }; packName?: string; cardWidth: number; cardColor?: string; }) { // Use the card color if provided, otherwise fall back to primary gradient const useGradient = !cardColor; return ( {/* Background - solid color matching what the user saw, or gradient fallback */} {useGradient ? ( ) : ( )} {/* Subtle overlay for depth */} {/* Pack name badge */} {packName && ( {packName} )} {/* Content */} {question.text} {/* Branding with logo */} sauci.app ); } const styles = StyleSheet.create({ overlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', }, backdropFallback: { flex: 1, backgroundColor: 'rgba(13, 13, 26, 0.95)', }, content: { flex: 1, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: spacing.lg, paddingVertical: spacing.md, }, closeButton: { width: 44, height: 44, justifyContent: 'center', alignItems: 'center', }, title: { ...typography.headline, color: colors.text, fontWeight: '600', }, headerSpacer: { width: 44, }, previewContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: spacing.lg, }, actionsContainer: { flexDirection: 'row', justifyContent: 'center', gap: spacing.xl, paddingTop: spacing.xl, paddingHorizontal: spacing.lg, }, actionButton: { alignItems: 'center', gap: spacing.sm, }, actionIconContainer: { width: 64, height: 64, borderRadius: 32, justifyContent: 'center', alignItems: 'center', overflow: 'hidden', }, actionIconOutline: { backgroundColor: colors.glass.backgroundLight, borderWidth: 1, borderColor: colors.glass.border, }, actionLabel: { ...typography.caption1, color: colors.text, fontWeight: '500', }, // Shareable card styles shareableCard: { aspectRatio: 0.7, borderRadius: radius.xl, overflow: 'hidden', padding: spacing.lg, }, cardOverlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0, 0, 0, 0.1)', }, packBadge: { alignSelf: 'flex-start', backgroundColor: 'rgba(255, 255, 255, 0.2)', paddingHorizontal: spacing.md, paddingVertical: spacing.xs, borderRadius: radius.full, }, packBadgeText: { ...typography.caption1, color: colors.text, fontWeight: '600', }, shareableContent: { flex: 1, justifyContent: 'center', paddingVertical: spacing.xl, }, questionText: { ...typography.title1, color: colors.text, fontWeight: '700', lineHeight: 36, }, questionTextSmaller: { ...typography.title2, lineHeight: 30, }, partnerSection: { marginTop: spacing.lg, }, partnerDivider: { flexDirection: 'row', alignItems: 'center', marginBottom: spacing.sm, gap: spacing.sm, }, partnerDividerLine: { flex: 1, height: 1, backgroundColor: 'rgba(255, 255, 255, 0.3)', }, partnerLabel: { ...typography.caption1, color: 'rgba(255, 255, 255, 0.7)', fontWeight: '500', }, partnerText: { ...typography.subhead, color: 'rgba(255, 255, 255, 0.8)', fontStyle: 'italic', }, branding: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.sm, }, brandingLogo: { width: 24, height: 24, resizeMode: 'contain', }, brandingText: { ...typography.subhead, color: colors.text, fontWeight: '600', }, });