/** * RepotModal — light-themed repotting guidance + action modal. * * ALWAYS light (overrides the phone's dark theme — native Alert renders dark * on Xiaomi, so we use a parchment PixieAlert-style card). Shows the AI's * repotting guidance (needs-repot signs, tips, soil suggestion, drainage note) * and offers: take a memory photo, update the setup photo, and confirm repot. */ import React, { useEffect, useState } from 'react'; import { ActivityIndicator, Modal, ScrollView, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { green, dark, line, haptic, pressWithHaptic } from '../constants/theme'; import type { RepotGuidance } from '../services/care'; const EdgeModal = Modal as any; interface RepotModalProps { visible: boolean; plantName: string; /** AI guidance (already fetched by the caller). */ guidance?: RepotGuidance | null; loading: boolean; /** Whether guidance has been requested yet (on-demand fetch). */ guidanceRequested: boolean; /** Fetch the AI repotting guidance on demand (Bekky, 2026-08-15). */ onRequestGuidance: () => void; onCancel: () => void; /** Confirm the repot (records the repotting event). */ onConfirm: () => void; /** Take a memory photo (opens the plant memory form). */ onTakeMemoryPhoto: () => void; /** Update the setup photo (opens the repot setup modal). */ onUpdateSetupPhoto: () => void; } export function RepotModal({ visible, plantName, guidance, loading, guidanceRequested, onRequestGuidance, onCancel, onConfirm, onTakeMemoryPhoto, onUpdateSetupPhoto, }: RepotModalProps) { const [confirmed, setConfirmed] = useState(false); // Pixel-based card height (Bekky rule #2 — no percentage maxHeight on modal // cards). Derive from the actual window + safe-area insets. const insets = useSafeAreaInsets(); const { height: windowHeight } = useWindowDimensions(); const topPad = insets.top + 24; const bottomPad = insets.bottom + 24; const cardMaxHeight = Math.max(320, windowHeight - topPad - bottomPad); useEffect(() => { if (visible) setConfirmed(false); }, [visible]); if (!visible) return null; const needsLabel = guidance?.needsRepot === 'likely' ? 'Likely needs repotting' : guidance?.needsRepot === 'maybe' ? 'May benefit from repotting' : 'Probably fine for now'; return ( 🪴 Repot {plantName} 🪴 {!guidanceRequested ? ( Do you need Pixie's help with repotting this? Get repotting guidance ) : loading ? ( Pixie is thinking about {plantName}'s repotting needs… ) : guidance ? ( {needsLabel} {guidance.summary} {guidance.signs.length > 0 ? ( Signs it needs repotting {guidance.signs.map((sign, i) => ( • {sign} ))} ) : null} {guidance.tips.length > 0 ? ( Repotting tips {guidance.tips.map((tip, i) => ( • {tip} ))} ) : null} {guidance.soilSuggestion ? ( Soil suggestion {guidance.soilSuggestion} ) : null} {guidance.drainageNote ? ( 💧 Drainage note {guidance.drainageNote} ) : null} ) : ( Pixie couldn't fetch repotting guidance right now. You can still repot. )} {/* Always-available setup/memory actions (Bekky, 2026-08-15). Update setup opens the dedicated repot setup modal — no need to wait for guidance. */} Update setup Take a memory photo {/* Actions */} Cancel { if (confirmed) { onConfirm(); } else { setConfirmed(true); haptic.success(); } })} > {confirmed ? 'Confirm Repot' : 'Repot'} ); } const s = StyleSheet.create({ overlay: { flex: 1, backgroundColor: 'rgba(16, 42, 16, 0.6)', justifyContent: 'center', alignItems: 'center', padding: 20, }, card: { width: '100%', maxWidth: 380, maxHeight: '88%', backgroundColor: '#FFFCF5', borderRadius: 24, padding: 20, shadowColor: '#000', shadowOpacity: 0.25, shadowRadius: 20, elevation: 12, }, headerRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginBottom: 8, gap: 10 }, title: { color: dark, fontSize: 19, fontWeight: '900', textAlign: 'center' }, titleEmoji: { fontSize: 19 }, loadingBox: { alignItems: 'center', justifyContent: 'center', paddingVertical: 40, gap: 12 }, loadingText: { color: '#5D5A4E', fontSize: 14, textAlign: 'center', lineHeight: 20 }, guidancePrompt: { alignItems: 'center', justifyContent: 'center', paddingVertical: 32, gap: 16 }, guidancePromptText: { color: '#3A382E', fontSize: 15, textAlign: 'center', lineHeight: 21, fontWeight: '700' }, guidanceButton: { minHeight: 48, borderRadius: 999, backgroundColor: green, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 24 }, guidanceButtonText: { color: '#fff', fontSize: 15, fontWeight: '900' }, scroll: { flexGrow: 0 }, scrollContent: { gap: 12, paddingBottom: 4 }, needsBadge: { backgroundColor: '#E8F5D3', borderRadius: 999, paddingHorizontal: 14, paddingVertical: 8, alignSelf: 'flex-start' }, needsText: { color: green, fontSize: 13, fontWeight: '800' }, summary: { color: '#3A382E', fontSize: 14, lineHeight: 20 }, section: { gap: 4 }, sectionTitle: { color: dark, fontSize: 14, fontWeight: '900', marginBottom: 2 }, bullet: { color: '#3A382E', fontSize: 13, lineHeight: 19, paddingLeft: 2 }, body: { color: '#3A382E', fontSize: 13, lineHeight: 19 }, drainageBox: { backgroundColor: '#EAF3F7', borderRadius: 12, padding: 12 }, drainageTitle: { color: dark, fontSize: 13, fontWeight: '900', marginBottom: 4 }, photoSection: { gap: 8, marginTop: 4 }, photoTitle: { color: dark, fontSize: 14, fontWeight: '900' }, photoButton: { minHeight: 44, borderRadius: 999, borderWidth: 1.5, borderColor: line, backgroundColor: '#FFF9EE', alignItems: 'center', justifyContent: 'center', paddingHorizontal: 16 }, photoButtonText: { color: dark, fontSize: 14, fontWeight: '800' }, actionRow: { flexDirection: 'row', gap: 12, marginTop: 16 }, cancelButton: { flex: 1, minHeight: 50, borderRadius: 999, borderWidth: 1.5, borderColor: '#B85C4A', backgroundColor: '#FCE9E6', alignItems: 'center', justifyContent: 'center' }, cancelText: { color: '#8E2F1E', fontSize: 15, fontWeight: '900' }, confirmButton: { flex: 1, minHeight: 50, borderRadius: 999, backgroundColor: green, alignItems: 'center', justifyContent: 'center' }, confirmDone: { backgroundColor: '#2E6B3F' }, confirmText: { color: '#fff', fontSize: 15, fontWeight: '900' }, });