import React, { useEffect, useState } from 'react'; import { View, TextInput, Image, ScrollView } from "react-native"; import { Text, Button, Divider, Chip } from "react-native-paper"; import API from './../API.js'; import { useNavigation } from '@react-navigation/native'; import * as ImagePicker from 'expo-image-picker'; import i18n from "../i18nMessages"; import Media from '../components/Media'; import { Platform } from 'react-native'; import { useSnapshot } from 'valtio'; import GlobalState from '../contexts/GlobalState.js'; import { createBibleToken, extractBibleReferences } from '../utils/bibleReferences.js'; const BATCH_SIZE = 1; // Constant for batch size let NewPostView = (props) => { const gState = useSnapshot(GlobalState); let [postContent, setPostContent] = useState(''); let initialContent = props.route?.params?.intialContent; let [extraContent, setExtraContent] = useState(initialContent ? [initialContent] : []); let [toProfile, setToProfile] = useState([]); const [photo, setPhoto] = useState(null); const [uploadProgress, setUploadProgress] = useState(0); const [isUploading, setIsUploading] = useState(false); // Variable to prevent posting during upload const [cancelUpload, setCancelUpload] = useState(false); // Variable to handle upload cancellation const [bibleReferences, setBibleReferences] = useState([]); const navigation = useNavigation(); const biblePickerSelectionTs = gState?.biblePickerSelection?.ts; useEffect(() => { let subscribed = true; const getProfileData = async () => { if (!props.route?.params?.toProfile) return; try { // Fetch profile data and update state if component is still subscribed const profileObj = await API.getUserProfile(props.route.params.toProfile); if (subscribed) setToProfile(profileObj); // If sendNow flag is present, trigger post creation if (props.route.params?.sendNow) { console.log("sending from tab bar button"); await handleNewPostButton(); } } catch (error) { console.error("Error fetching profile data", error); } }; getProfileData(); return () => { // Cleanup subscription flag subscribed = false; } }, [props.route?.params?.sendNow]); useEffect(() => { const selection = gState?.biblePickerSelection; if (!selection || selection.target !== "post" || !selection.token) return; if (selection.reference) { setBibleReferences((prev) => { if (prev.includes(selection.reference)) return prev; return prev.concat(selection.reference); }); } GlobalState.biblePickerSelection = null; }, [biblePickerSelectionTs]); const removeBibleTokensFromDraft = (value = "") => { return String(value || "").replace(/@bible:[^\s]+/gi, ""); }; const handlePostContentChange = (nextValue = "") => { const incomingValue = String(nextValue || ""); const detectedReferences = extractBibleReferences(incomingValue); if (detectedReferences.length) { setBibleReferences((prev) => { const seen = new Set(prev); detectedReferences.forEach((reference) => seen.add(reference)); return Array.from(seen); }); } setPostContent(removeBibleTokensFromDraft(incomingValue)); }; const pickImage = async () => { try { // Launch image picker to select images from gallery const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 0.85, // Avoid aggressive client-side compression before upload allowsMultipleSelection: true, // Allow multiple images to be selected }); if (!result.canceled) { setIsUploading(true); // Set uploading state to true setCancelUpload(false); // Reset cancel flag await handleUploadPhoto(result); // Upload selected photos setPhoto(null); // Clear selected photo after upload setIsUploading(false); // Set uploading state to false } } catch (error) { console.error("Error picking image", error); setIsUploading(false); // Set uploading state to false in case of error } }; const handleUploadPhoto = async (results) => { if (!results) return; try { setIsUploading(true); // Set uploading state to true let uploadedFiles = []; let totalPhotos = results.assets.length; let currentIndex = 0; // Upload photos in batches of BATCH_SIZE while (currentIndex < totalPhotos) { if (cancelUpload) { setIsUploading(false); setUploadProgress(0); alert("Upload cancelled."); return; } const batch = results.assets.slice(currentIndex, currentIndex + BATCH_SIZE); setPhoto(batch[0]); // Set the first photo in the batch to preview it const batchUploads = batch.map((photo) => { // Prepare photo for upload const uri = Platform.OS === "android" ? photo.uri // Use photo URI directly on Android : photo.uri.replace("file://", ""); // Remove file:// prefix on other platforms const filename = photo.uri.split("/").pop(); // Extract filename from URI const match = /\.(\w+)$/.exec(filename); // Extract file extension const ext = match?.[1]; const type = match ? `image/${match[1]}` : `image`; // Set MIME type based on file extension const formData = new FormData(); formData.append("banner", { uri, name: `image.${ext}`, // Set the file name for upload type, }); // Upload photo to server using .then statements return fetch("https://social.emmint.com/upload.php", { method: "POST", body: formData, headers: { "Content-Type": "multipart/form-data" } }) .then((response) => { if (!response.ok) { throw new Error('Failed to upload image'); } return response.json(); }) .then((data) => { return data.fileName; // Return the filename from the server response }); }); const batchResults = await Promise.all(batchUploads); // Wait for all uploads in the batch to complete uploadedFiles = uploadedFiles.concat(batchResults); // Add uploaded files to the list currentIndex += BATCH_SIZE; // Move to the next batch setUploadProgress((currentIndex / totalPhotos) * 100); // Update upload progress // Update extra content after each batch completes setExtraContent(prevExtraContent => { return prevExtraContent.concat(batchResults.map(url => "@image:" + url)); }); } setIsUploading(false); // Set uploading state to false after completion } catch (error) { console.error("Error uploading photo", error); alert("Something went wrong uploading the photo."); setIsUploading(false); // Set uploading state to false in case of error } }; const handleNewPostButton = async () => { if (isUploading) { alert("Please wait for the upload to finish before posting."); return; } try { const bibleTokens = bibleReferences.map((reference) => createBibleToken(reference)).filter(Boolean); // Create a new post with the combined content await API.newPost( [postContent, extraContent.join(" "), bibleTokens.join(" ")].join(" ").trim(), props.route?.params?.toProfile ); setPostContent(''); // Clear post content after submission setExtraContent([]); // Clear extra content after submission setBibleReferences([]); navigation.navigate('Feed', { reRender: Math.random() }); // Navigate back to the Feed and trigger re-render } catch (error) { console.error("Error creating new post", error); } } const handleCancelUpload = () => { setCancelUpload(true); // Set cancel flag to true }; return ( {/* Header section with status update text and post button */} {i18n.t("message.statusUpdate")}: {/* Display the profile being posted on if available */} { toProfile._id ? {i18n.t("message.postingOn")}: {toProfile.profile?.firstName} {toProfile.profile?.lastName} : null } {/* Text input for post content */} {bibleReferences.length ? ( {bibleReferences.map((reference) => ( navigation.navigate("BibleChapter", { reference })} onClose={() => { setBibleReferences((prev) => prev.filter((item) => item !== reference)); }} > {reference} ))} ) : null} {/* Button to pick images from the gallery */} {isUploading && ( )} {/* Display uploading state and selected image preview */} {photo && ( {i18n.t("message.uploading")} )} {/* Display upload progress if in progress */} { uploadProgress > 0 && uploadProgress < 100 && ( {i18n.t("message.uploadProgress")}: {uploadProgress.toFixed(2)}% ) } {/* Display media content if extra content is available */} { extraContent.length > 0 && ( ) } ) } export default NewPostView;