import { WebView } from "react-native-webview"; import { useRef, useState } from "react"; import { Alert, StyleSheet, View } from "react-native"; export default function HomeScreen() { const webviewRef = useRef(null); const [postData, setPostData] = useState([]); const [isApiCallInProgress, setIsApiCallInProgress] = useState(false); // Function to call the fact-check API const checkFacts = async (caption, imageUrl) => { try { const response = await fetch('https://factcheck.planpostai.com/check-facts', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ query: caption, url:imageUrl }), }); const responseText = await response.text(); console.log('Raw API Response:', responseText); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status} - ${responseText}`); } const result = JSON.parse(responseText); let color; if (result?.verdict === 'True') { color = "#2E7D32"; } else if (result?.verdict === 'False') { color = "#D32F2F"; } else { color = "#F9A825"; } // Send the result back to WebView to show toast webviewRef.current?.injectJavaScript(` (function() { showToast('${result.evidence.replace(/'/g, "\\'")}', 10000, '${color}'); return true; })(); `); return result; } catch (error) { console.error('API Error Details:', { message: error.message, stack: error.stack, name: error.name }); // Show error in WebView webviewRef.current?.injectJavaScript(` (function() { showToast('Error: ${error.message.replace(/'/g, "\\'")}', 5000, true); return true; })(); `); throw error; } }; const handleMessage = async (event) => { try { const data = JSON.parse(event.nativeEvent.data); switch (data.type) { case 'postData': setPostData(prevData => [...prevData, { caption: data.caption, imageUrls: data.imageUrls, postId: data.postId }]); if (isApiCallInProgress) { return; } setIsApiCallInProgress(true); try { const result = await checkFacts(data.caption, data.imageUrls[0]); setIsApiCallInProgress(false); webviewRef.current?.injectJavaScript(` (function() { enableAllButtons(); const button = document.querySelector('button[data-post-id="${data.postId}"]'); if (button) { button.textContent = 'Processed'; button.style.background="green"; button.style.color="white"; button.style.padding="5px 5px"; button.style.opacity = '0.7'; button.disabled = true; } return true; })(); `); } catch (error) { setIsApiCallInProgress(false); webviewRef.current?.injectJavaScript(` (function() { const svgCode=\` \`; enableAllButtons(); const button = document.querySelector('button[data-post-id="${data.postId}"]'); if (button) { button.disabled = false; button.style.opacity = '1'; button.innerHTML = svgCode; } return true; })(); `); } finally { setIsApiCallInProgress(false); } break; case 'error': console.error(`WebView Error: ${data.message}`); break; } } catch (error) { console.error('Error handling message:', error); } }; const injectedJavaScript = ` (function() { // Add toast CSS const style = document.createElement('style'); style.innerHTML = \` #toast-container { position: fixed; top: 50px; right: 20px; left: 20px; z-index: 999999; display: flex; flex-direction: column; gap: 10px; pointer-events: auto; } .toast { background-color: #1db146; color: #fff; font-weight: 800; padding: 12px 16px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); font-size: 16px; opacity: 1; position: relative; width: 90%; z-index: 99999999999; transition: opacity 0.5s ease; } .toast.fade-out { opacity: 0; } .error-toast { background-color: #ff4444; } \`; document.head.appendChild(style); // Function to disable all buttons window.disableAllButtons = function() { document.querySelectorAll("button").forEach((btn) => { btn.disabled = true; btn.style.opacity = "0.5"; btn.style.cursor = "not-allowed"; }); }; // Function to enable all buttons window.enableAllButtons = function() { document.querySelectorAll("button").forEach((btn) => { if (btn.textContent !== "Processed") { btn.disabled = false; btn.style.opacity = "1"; btn.style.cursor = "pointer"; } }); }; // Toast notification function window.showToast = function showToast(message, duration, color = '#333') { // Create the toast element const toast = document.createElement('div'); toast.textContent = message; // Apply basic styles toast.style.position = 'fixed'; toast.style.top = '5px'; toast.style.left = '50%'; toast.style.fontSize="16px"; toast.style.fontWeight="800"; toast.style.transform = 'translateX(-50%)'; toast.style.padding = '10px 20px'; toast.style.color = '#fff'; toast.style.backgroundColor = color; // Dynamic background color toast.style.borderRadius = '8px'; toast.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.3)'; toast.style.zIndex = '9999'; toast.style.opacity = '1'; toast.style.transition = 'opacity 0.5s ease-in-out'; toast.style.width = '85%'; // Add toast to the DOM document.body.appendChild(toast); // Auto-remove the toast after the specified duration setTimeout(() => { toast.style.opacity = '0'; // Fade out setTimeout(() => { document.body.removeChild(toast); // Remove from DOM }, 500); // Match fade-out duration }, duration); }; // Function to handle "See More" click async function handleSeeMoreClick(seeMoreElement, post, button, initialCaption, initialImageURLs, postId) { return new Promise((resolve) => { setTimeout(() => { seeMoreElement.click(); setTimeout(() => { const captionElement = post.querySelector( "div.m div.m div[data-type='text'] div.native-text" ); fullCaption = captionElement ? captionElement.textContent.trim() : initialCaption; // Send data to React Native window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'postData', caption: fullCaption, imageUrls: initialImageURLs, postId })); resolve(); }, 2000); }, 100); }); } // Function to collect post data immediately function collectDataImmediately(post, button, initialCaption, initialImageURLs, postId) { const captionElement = post.querySelector( "div.m div.m div[data-type='text'] div.native-text" ); const caption = captionElement ? captionElement.textContent.trim() : initialCaption; // Send data to React Native window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'postData', caption, imageUrls: initialImageURLs, postId })); } const svgCode=\` \` // Function to collect post data and send to React Native function collectPostData(post) { if (post.dataset.processed === "true") return; // Get the caption const captionElement = post.querySelector( "div.m div.m div[data-type='text'] div.native-text" ); const caption = captionElement ? captionElement.textContent.trim() : null; // Get the first image (if exists) const singleImageElement = post.querySelector("div.m.bg-s13 img"); const singleImageURL = singleImageElement && singleImageElement.src ? singleImageElement.src : null; // Collect all image elements const imageElements = post.querySelectorAll("div.m.bg-s13 img"); const imageURLs = Array.from(imageElements) .filter(img => img.src) .map(img => img.src); post.dataset.processed = "true"; // Prioritize using a single image if only one exists, otherwise use all images let imagesToUse = []; if (singleImageURL) { imagesToUse = [singleImageURL]; } else if (imageURLs.length > 0) { imagesToUse = imageURLs; } // Create button if there's a caption or at least one image if ((caption && caption.length > 50) || imagesToUse.length > 0) { const postId = 'post_' + Math.random().toString(36).substr(2, 9); createButton(post, caption, imagesToUse, postId); } } // Create check button function createButton(post, caption, imageURLs, postId) { post.style.position = "relative"; const button = document.createElement("button"); button.textContent = "Check"; button.setAttribute('data-post-id', postId); button.innerHTML=svgCode Object.assign(button.style, { position: "absolute", right: "5px", top: "-30px", border: "none", borderRadius: "8px", cursor: "pointer", zIndex: 9999, pointerEvents: "auto", transition: "all 0.3s", }); button.addEventListener("click", async () => { disableAllButtons(); button.textContent = "Processing"; button.style.opacity = "0.5"; button.style.color="white"; button.style.padding="5px 5px"; button.style.background="blue"; const seeMoreElement = post.querySelector("span[style*='color:#65676b']"); if (seeMoreElement) { await handleSeeMoreClick(seeMoreElement, post, button, caption, imageURLs, postId); } else { collectDataImmediately(post, button, caption, imageURLs, postId); } }); post.appendChild(button); } // Mutation observer to handle new posts function handleMutations(mutationsList) { mutationsList.forEach((mutation) => { if (mutation.type === "childList" || mutation.type === "subtree") { document.querySelectorAll("div.m.bg-s3").forEach((post) => { if (!post.querySelector("button")) { collectPostData(post); } }); } }); } const observer = new MutationObserver(handleMutations); observer.observe(document.querySelector(".m"), { childList: true, subtree: true, }); document.addEventListener("DOMContentLoaded", () => { document.querySelectorAll("div.m.bg-s3").forEach((post) => { if (!post.dataset.processed) { collectPostData(post); } }); }); return true; })(); `; return ( ); } const styles = StyleSheet.create({ container: { flex: 1, marginTop: 35, }, });