import { useI18n } from '@/hooks/useI18n'; import type { MedicationDisplayItem } from '@/types/medication'; import React, { useEffect } from 'react'; import { StyleSheet, TouchableOpacity, View } from 'react-native'; import Animated, { Extrapolation, interpolate, type SharedValue, useAnimatedStyle, useSharedValue, withSpring, } from 'react-native-reanimated'; import { MedicationCard } from './MedicationCard'; type Props = { medications: MedicationDisplayItem[]; colors: (typeof import('@/constants/Colors').Colors)[keyof typeof import('@/constants/Colors').Colors]; selectedDate: any; onOpenDetails: (medication: MedicationDisplayItem) => void; onCelebrate?: () => void; }; const STACK_OFFSET = 12; const STACK_SCALE_STEP = 0.04; const MAX_STACK_VISIBLE = 3; export function TakenMedicationsStack({ medications, colors, selectedDate, onOpenDetails, onCelebrate, }: Props) { const { t } = useI18n(); const [isExpanded, setIsExpanded] = React.useState(false); const progress = useSharedValue(0); useEffect(() => { progress.value = withSpring(isExpanded ? 1 : 0, { damping: 20, stiffness: 200, // Faster spring mass: 0.8, }); }, [isExpanded, progress]); const handleToggle = () => { setIsExpanded(!isExpanded); }; // Header arrow rotation style const arrowStyle = useAnimatedStyle(() => { return { transform: [ { rotate: `${interpolate(progress.value, [0, 1], [0, 180])}deg`, }, ], }; }); if (medications.length === 0) { return null; } return ( {/* Stack/List Container */} {medications.map((item, index) => ( ))} ); } const CardItem = ({ item, index, total, progress, isExpanded, colors, selectedDate, onOpenDetails, onCelebrate, onToggle, }: { item: MedicationDisplayItem; index: number; total: number; progress: SharedValue; isExpanded: boolean; colors: (typeof import('@/constants/Colors').Colors)[keyof typeof import('@/constants/Colors').Colors]; selectedDate: any; onOpenDetails: (medication: MedicationDisplayItem) => void; onCelebrate?: () => void; onToggle: () => void; }) => { // Only render top 3 cards when collapsed to save performance/visuals // But we need to render all when expanding. // We'll hide index >= MAX_STACK_VISIBLE when collapsed via opacity/zIndex. const style = useAnimatedStyle(() => { // Stack state (progress = 0) const stackTranslateY = index * STACK_OFFSET; const stackScale = 1 - index * STACK_SCALE_STEP; const stackOpacity = index < MAX_STACK_VISIBLE ? 1 - index * 0.15 : 0; const stackZIndex = total - index; // List state (progress = 1) // In list state, we rely on layout (relative positioning). // However, to animate smoothly from absolute (stack) to relative (list), // we need a strategy. // Strategy: Always Absolute? No, height is dynamic. // Strategy: Use negative margins for stack? // Let's try: // Collapsed: marginTop = -(height - offset). // Expanded: marginTop = 16 (gap). // But we don't know height. // Alternative: // Use 'top' offset relative to the first card? // This is hard without measuring. // Let's go with the "Transform" approach assuming standard card height for the stack effect, // but switching to relative layout when expanded. // Wait, switching 'position' prop is not animatable by useAnimatedStyle directly (requires Layout Animation). // Let's keep it simple: // When collapsed (progress 0): // Items > 0 are absolutely positioned relative to the container (which wraps them all). // Item 0 is relative. // When expanded (progress 1): // All items are relative. // To smooth this, we can use interpolate for translateY. return { zIndex: stackZIndex, opacity: interpolate(progress.value, [0, 1], [stackOpacity, 1]), transform: [ { scale: interpolate(progress.value, [0, 1], [stackScale, 1]), }, { translateY: interpolate( progress.value, [0, 1], [stackTranslateY, 0] // In stack, they go down. In list, translation is 0 (relative flow handles pos). ), }, ], }; }); // Logic for positioning: // We'll use a container View for each card. // When collapsed, the container height for index > 0 should be 0? // That would pull them up. const containerStyle = useAnimatedStyle(() => { // We can animate the height of the wrapper view. // But we don't know the content height. // Assuming ~140px for card. const approxHeight = 140; if (index === 0) return {}; // First card always takes space // For others: // Collapsed: height is 0 (so they stack on top of first one, roughly) // Expanded: height is 'auto' (we can't animate to auto easily in RN without LayoutAnimation) return { marginTop: interpolate(progress.value, [0, 1], [-approxHeight + STACK_OFFSET, 16], Extrapolation.CLAMP), }; }); // Using Layout Animation for the actual position change support // requires the parent to handle it. // Simpler Visual Hack: // When collapsed, we just set marginTop to a negative value that overlaps them. // Since MedicationCard is roughly constant height, we can tune this. // MedicationCard height is roughly 130-150. // Let's guess -130 + 12. const cardContainerStyle = useAnimatedStyle(() => { // We assume a fixed height for the negative margin calculation logic. // A better way is needed if heights vary wildly. // But for now, let's use a safe estimated overlap. const cardHeight = 140; const collapsedMarginTop = index === 0 ? 0 : -(cardHeight - STACK_OFFSET); const expandedMarginTop = index === 0 ? 0 : 16; return { marginTop: interpolate(progress.value, [0, 1], [collapsedMarginTop, expandedMarginTop]), zIndex: total - index, }; }); return ( {/* When collapsed, clicking any card should expand. When expanded, open details. */} {/* We can intercept touches if !isExpanded */} {/* Overlay to intercept clicks when collapsed */} {!isExpanded && ( )} ); }; const styles = StyleSheet.create({ container: { marginTop: 8, gap: 12, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 8, paddingHorizontal: 4, }, headerContent: { flexDirection: 'row', alignItems: 'center', gap: 8, }, iconContainer: { width: 28, height: 28, borderRadius: 14, alignItems: 'center', justifyContent: 'center', }, headerTitle: { fontSize: 16, fontWeight: '600', }, stackContainer: { position: 'relative', // minHeight ensures space for the stack when collapsed }, });