From e25bdba29f49b1b55a8a8cccdc4583aea3c101ed Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Sun, 15 Mar 2026 13:41:09 +0100
Subject: [PATCH] feat: multi-image upload and catch_up message delivery
---
components/chat/ImageCaptionModal.tsx | 51 ++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/components/chat/ImageCaptionModal.tsx b/components/chat/ImageCaptionModal.tsx
index f408157..6c753e0 100644
--- a/components/chat/ImageCaptionModal.tsx
+++ b/components/chat/ImageCaptionModal.tsx
@@ -1,6 +1,7 @@
import React, { useEffect, useRef, useState } from "react";
import {
Dimensions,
+ FlatList,
Image,
KeyboardAvoidingView,
Modal,
@@ -12,22 +13,28 @@
} from "react-native";
import { useTheme } from "../../contexts/ThemeContext";
+interface ImageItem {
+ uri: string;
+}
+
interface ImageCaptionModalProps {
visible: boolean;
- imageUri: string;
+ images: ImageItem[];
onSend: (caption: string) => void;
onCancel: () => void;
}
-export function ImageCaptionModal({ visible, imageUri, onSend, onCancel }: ImageCaptionModalProps) {
+export function ImageCaptionModal({ visible, images, onSend, onCancel }: ImageCaptionModalProps) {
const { colors } = useTheme();
const [caption, setCaption] = useState("");
+ const [selectedIndex, setSelectedIndex] = useState(0);
const inputRef = useRef<TextInput>(null);
const { width, height } = Dimensions.get("window");
useEffect(() => {
if (visible) {
setCaption("");
+ setSelectedIndex(0);
setTimeout(() => inputRef.current?.focus(), 300);
}
}, [visible]);
@@ -37,6 +44,9 @@
setCaption("");
};
+ const currentImage = images[selectedIndex]?.uri ?? "";
+ const isMultiple = images.length > 1;
+
return (
<Modal visible={visible} animationType="slide" transparent={false} onRequestClose={onCancel}>
<View style={{ flex: 1, backgroundColor: "#000" }}>
@@ -45,7 +55,7 @@
behavior={Platform.OS === "ios" ? "padding" : undefined}
keyboardVerticalOffset={0}
>
- {/* Top bar with cancel */}
+ {/* Top bar with cancel + count */}
<View
style={{
paddingTop: 54,
@@ -68,17 +78,48 @@
>
<Text style={{ color: "#fff", fontSize: 16, fontWeight: "600" }}>Cancel</Text>
</Pressable>
+ {isMultiple && (
+ <Text style={{ color: "rgba(255,255,255,0.6)", fontSize: 14 }}>
+ {selectedIndex + 1} / {images.length}
+ </Text>
+ )}
</View>
{/* Image preview */}
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Image
- source={{ uri: imageUri }}
- style={{ width, height: height * 0.55 }}
+ source={{ uri: currentImage }}
+ style={{ width, height: isMultiple ? height * 0.45 : height * 0.55 }}
resizeMode="contain"
/>
</View>
+ {/* Thumbnail strip — only for multiple images */}
+ {isMultiple && (
+ <FlatList
+ data={images}
+ horizontal
+ showsHorizontalScrollIndicator={false}
+ keyExtractor={(_, i) => String(i)}
+ contentContainerStyle={{ paddingHorizontal: 12, paddingVertical: 8, gap: 8 }}
+ renderItem={({ item, index }) => (
+ <Pressable onPress={() => setSelectedIndex(index)}>
+ <Image
+ source={{ uri: item.uri }}
+ style={{
+ width: 56,
+ height: 56,
+ borderRadius: 8,
+ borderWidth: index === selectedIndex ? 2 : 0,
+ borderColor: colors.accent,
+ }}
+ resizeMode="cover"
+ />
+ </Pressable>
+ )}
+ />
+ )}
+
{/* Caption input + send */}
<View
style={{
--
Gitblit v1.3.1