| .. | .. |
|---|
| 1 | 1 | import React, { useEffect, useRef, useState } from "react"; |
|---|
| 2 | 2 | import { |
|---|
| 3 | 3 | Dimensions, |
|---|
| 4 | + FlatList, |
|---|
| 4 | 5 | Image, |
|---|
| 5 | 6 | KeyboardAvoidingView, |
|---|
| 6 | 7 | Modal, |
|---|
| .. | .. |
|---|
| 12 | 13 | } from "react-native"; |
|---|
| 13 | 14 | import { useTheme } from "../../contexts/ThemeContext"; |
|---|
| 14 | 15 | |
|---|
| 16 | +interface ImageItem { |
|---|
| 17 | + uri: string; |
|---|
| 18 | +} |
|---|
| 19 | + |
|---|
| 15 | 20 | interface ImageCaptionModalProps { |
|---|
| 16 | 21 | visible: boolean; |
|---|
| 17 | | - imageUri: string; |
|---|
| 22 | + images: ImageItem[]; |
|---|
| 18 | 23 | onSend: (caption: string) => void; |
|---|
| 19 | 24 | onCancel: () => void; |
|---|
| 20 | 25 | } |
|---|
| 21 | 26 | |
|---|
| 22 | | -export function ImageCaptionModal({ visible, imageUri, onSend, onCancel }: ImageCaptionModalProps) { |
|---|
| 27 | +export function ImageCaptionModal({ visible, images, onSend, onCancel }: ImageCaptionModalProps) { |
|---|
| 23 | 28 | const { colors } = useTheme(); |
|---|
| 24 | 29 | const [caption, setCaption] = useState(""); |
|---|
| 30 | + const [selectedIndex, setSelectedIndex] = useState(0); |
|---|
| 25 | 31 | const inputRef = useRef<TextInput>(null); |
|---|
| 26 | 32 | const { width, height } = Dimensions.get("window"); |
|---|
| 27 | 33 | |
|---|
| 28 | 34 | useEffect(() => { |
|---|
| 29 | 35 | if (visible) { |
|---|
| 30 | 36 | setCaption(""); |
|---|
| 37 | + setSelectedIndex(0); |
|---|
| 31 | 38 | setTimeout(() => inputRef.current?.focus(), 300); |
|---|
| 32 | 39 | } |
|---|
| 33 | 40 | }, [visible]); |
|---|
| .. | .. |
|---|
| 37 | 44 | setCaption(""); |
|---|
| 38 | 45 | }; |
|---|
| 39 | 46 | |
|---|
| 47 | + const currentImage = images[selectedIndex]?.uri ?? ""; |
|---|
| 48 | + const isMultiple = images.length > 1; |
|---|
| 49 | + |
|---|
| 40 | 50 | return ( |
|---|
| 41 | 51 | <Modal visible={visible} animationType="slide" transparent={false} onRequestClose={onCancel}> |
|---|
| 42 | 52 | <View style={{ flex: 1, backgroundColor: "#000" }}> |
|---|
| .. | .. |
|---|
| 45 | 55 | behavior={Platform.OS === "ios" ? "padding" : undefined} |
|---|
| 46 | 56 | keyboardVerticalOffset={0} |
|---|
| 47 | 57 | > |
|---|
| 48 | | - {/* Top bar with cancel */} |
|---|
| 58 | + {/* Top bar with cancel + count */} |
|---|
| 49 | 59 | <View |
|---|
| 50 | 60 | style={{ |
|---|
| 51 | 61 | paddingTop: 54, |
|---|
| .. | .. |
|---|
| 68 | 78 | > |
|---|
| 69 | 79 | <Text style={{ color: "#fff", fontSize: 16, fontWeight: "600" }}>Cancel</Text> |
|---|
| 70 | 80 | </Pressable> |
|---|
| 81 | + {isMultiple && ( |
|---|
| 82 | + <Text style={{ color: "rgba(255,255,255,0.6)", fontSize: 14 }}> |
|---|
| 83 | + {selectedIndex + 1} / {images.length} |
|---|
| 84 | + </Text> |
|---|
| 85 | + )} |
|---|
| 71 | 86 | </View> |
|---|
| 72 | 87 | |
|---|
| 73 | 88 | {/* Image preview */} |
|---|
| 74 | 89 | <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> |
|---|
| 75 | 90 | <Image |
|---|
| 76 | | - source={{ uri: imageUri }} |
|---|
| 77 | | - style={{ width, height: height * 0.55 }} |
|---|
| 91 | + source={{ uri: currentImage }} |
|---|
| 92 | + style={{ width, height: isMultiple ? height * 0.45 : height * 0.55 }} |
|---|
| 78 | 93 | resizeMode="contain" |
|---|
| 79 | 94 | /> |
|---|
| 80 | 95 | </View> |
|---|
| 81 | 96 | |
|---|
| 97 | + {/* Thumbnail strip — only for multiple images */} |
|---|
| 98 | + {isMultiple && ( |
|---|
| 99 | + <FlatList |
|---|
| 100 | + data={images} |
|---|
| 101 | + horizontal |
|---|
| 102 | + showsHorizontalScrollIndicator={false} |
|---|
| 103 | + keyExtractor={(_, i) => String(i)} |
|---|
| 104 | + contentContainerStyle={{ paddingHorizontal: 12, paddingVertical: 8, gap: 8 }} |
|---|
| 105 | + renderItem={({ item, index }) => ( |
|---|
| 106 | + <Pressable onPress={() => setSelectedIndex(index)}> |
|---|
| 107 | + <Image |
|---|
| 108 | + source={{ uri: item.uri }} |
|---|
| 109 | + style={{ |
|---|
| 110 | + width: 56, |
|---|
| 111 | + height: 56, |
|---|
| 112 | + borderRadius: 8, |
|---|
| 113 | + borderWidth: index === selectedIndex ? 2 : 0, |
|---|
| 114 | + borderColor: colors.accent, |
|---|
| 115 | + }} |
|---|
| 116 | + resizeMode="cover" |
|---|
| 117 | + /> |
|---|
| 118 | + </Pressable> |
|---|
| 119 | + )} |
|---|
| 120 | + /> |
|---|
| 121 | + )} |
|---|
| 122 | + |
|---|
| 82 | 123 | {/* Caption input + send */} |
|---|
| 83 | 124 | <View |
|---|
| 84 | 125 | style={{ |
|---|