react-native-ajora
Version:
The most complete AI agent UI for React Native
188 lines (186 loc) • 5.98 kB
JavaScript
import React from "react";
import { View, Image, TouchableOpacity, StyleSheet, Animated, ActivityIndicator, } from "react-native";
// TODO: support web
import Lightbox from "react-native-lightbox-v2";
import { MaterialIcons } from "@expo/vector-icons";
import { useChatContext } from "../AjoraContext";
import Color from "../Color";
export function AttachmentPreview({ renderAttachment, }) {
const { ajora } = useChatContext();
const { clearAttachement, attachement } = ajora;
const fadeAnim = React.useRef(new Animated.Value(0)).current;
React.useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
}, [fadeAnim]);
const formatFileSize = (bytes) => {
if (bytes === 0)
return "0 Bytes";
const k = 1024;
const sizes = ["Bytes", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
// Helper function to get file type from MIME type
const getFileType = (mimeType) => {
if (!mimeType)
return "file";
if (mimeType.startsWith("image/"))
return "image";
if (mimeType === "application/pdf")
return "pdf";
return "file";
};
// Helper function to get file icon based on type
const getFileIcon = (fileType) => {
const iconMap = {
image: "image",
pdf: "picture-as-pdf",
file: "insert-drive-file",
};
return iconMap[fileType] || "insert-drive-file";
};
const handleRemove = () => {
clearAttachement();
};
// Early return if no attachment
if (!attachement)
return null;
// If custom render function is provided, use it
if (renderAttachment)
return renderAttachment();
const fileType = getFileType(attachement.mimeType);
const fileIcon = getFileIcon(fileType);
const isImage = fileType === "image";
return (<Animated.View style={[
styles.container,
{
opacity: fadeAnim,
transform: [
{
translateY: fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: [10, 0],
}),
},
],
},
]}>
<View style={styles.previewCard}>
{/* File Preview Container */}
<View style={styles.previewContainer}>
{isImage ? (
// Image Preview with Lightbox
// @ts-expect-error: Lightbox types are not fully compatible
<Lightbox renderContent={() => (<Image source={{ uri: attachement?.fileUri }} style={styles.fullscreenImage}/>)} activeProps={{ style: styles.fullscreenImage }} underlayColor="transparent">
<Image source={{ uri: attachement?.fileUri }} style={styles.image}/>
</Lightbox>) : (
// Non-image file preview
<View style={styles.filePreview}>
<MaterialIcons name={fileIcon} size={32} color={Color.gray600}/>
</View>)}
{/* Upload Progress Overlay (if uploading) */}
{attachement?.progress !== undefined &&
attachement?.progress < 100 && (<View style={styles.progressOverlay}>
<ActivityIndicator size="small" color={Color.white}/>
</View>)}
{/* Remove Button */}
<TouchableOpacity style={styles.removeButton} onPress={handleRemove} activeOpacity={0.7}>
<MaterialIcons name="close" size={12} color={Color.white}/>
</TouchableOpacity>
</View>
</View>
</Animated.View>);
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: 10,
paddingVertical: 10,
alignItems: "flex-start",
},
previewCard: {
backgroundColor: Color.card,
borderRadius: 12,
borderWidth: 1,
borderColor: Color.border,
shadowColor: Color.shadow,
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.08,
shadowRadius: 6,
// elevation: 3,
overflow: "hidden",
maxWidth: 120,
width: "40%",
},
previewContainer: {
position: "relative",
height: 80,
backgroundColor: Color.gray50,
},
filePreview: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: Color.gray100,
},
image: {
width: "100%",
height: "100%",
resizeMode: "cover",
},
fullscreenImage: {
width: "100%",
height: "100%",
resizeMode: "contain",
backgroundColor: "black",
},
progressOverlay: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(255, 255, 255, 0.2)",
justifyContent: "center",
alignItems: "center",
},
progressTextContainer: {
backgroundColor: "rgba(0, 0, 0, 0.7)",
borderRadius: 16,
paddingHorizontal: 12,
paddingVertical: 6,
alignItems: "center",
justifyContent: "center",
},
progressText: {
color: Color.white,
fontSize: 14,
fontWeight: "600",
},
removeButton: {
position: "absolute",
top: 6,
right: 6,
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: "rgba(0, 0, 0, 0.7)",
alignItems: "center",
justifyContent: "center",
shadowColor: Color.black,
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.15,
shadowRadius: 2,
elevation: 2,
},
});
//# sourceMappingURL=AttachmentPreview.js.map