import { View, Text, Image, StyleSheet, Dimensions, Platform, StatusBar, FlatList, TouchableOpacity, SafeAreaView, Modal, ScrollView, ActivityIndicator, Linking, } from "react-native"; import { useState, useEffect } from "react"; import { BlurView } from "expo-blur"; import { useRouter } from "expo-router"; import { IconSymbol } from "@/components/ui/IconSymbol"; import { useColorScheme } from "@/hooks/useColorScheme"; import { useSafeAreaInsets } from "react-native-safe-area-context"; const { width, height } = Dimensions.get("window"); // API endpoint - use your own IP address here const API_URL = "https://dev.dashboard.planpostai.com/news/api/news"; // TypeScript interfaces interface Paragraph { content: string; } interface NewsItem { id: number; created_at: string; title: string; description: string; image_url: string; image_path?: string; paragraphs: string[]; } interface AdItem { id: string; isAd: boolean; adIndex: number; } type FeedItem = NewsItem | AdItem; // Type guard to check if an item is an ad const isAd = (item: FeedItem): item is AdItem => { return (item as AdItem).isAd === true; }; // Single ad data const singleAd = { id: 1, title: "Internet Packages", description: "Get the latest data offers...", image: "https://cdn01da.grameenphone.com/sites/default/files/2023-09/GP_013_New_Number_Series-1060-x-764.jpg", url: "https://www.premium-headphones.com", }; export default function NewsScreen(): JSX.Element { const colorScheme = useColorScheme(); const router = useRouter(); const insets = useSafeAreaInsets(); const [selectedNews, setSelectedNews] = useState(null); const [modalVisible, setModalVisible] = useState(false); const [newsData, setNewsData] = useState([]); const [processedData, setProcessedData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Fetch news data from API useEffect(() => { fetchNews(); }, []); // Process news data to add ads after every 4 news items useEffect(() => { const processed: FeedItem[] = []; newsData.forEach((item, index) => { processed.push(item); if ((index + 1) % 4 === 0 && index !== newsData.length - 1) { processed.push({ id: `ad-${Math.floor(index / 4)}`, isAd: true, adIndex: Math.floor(index / 4), }); } }); setProcessedData(processed); }, [newsData]); const fetchNews = async (): Promise => { setLoading(true); setError(null); try { // Set a timeout to prevent hanging requests const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); const response = await fetch(API_URL, { signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const responseJson = await response.json(); // Extract data from the nested structure const data = responseJson.data || responseJson; if (!data || !Array.isArray(data)) { throw new Error("Invalid data format received from API"); } // Process the data to ensure it has the required format const processedData: NewsItem[] = data.map((item: any) => ({ ...item, // If paragraphs don't exist, create them from description paragraphs: item.paragraphs || [ item.description.substring(0, item.description.length / 3), item.description.substring( item.description.length / 3, (2 * item.description.length) / 3 ), item.description.substring((2 * item.description.length) / 3), ], })); setNewsData(processedData); } catch (err) { console.error("Failed to fetch news:", err); setError( err instanceof Error ? err.message : "An unknown error occurred" ); setNewsData([]); } finally { setLoading(false); } }; const formatDate = (dateString: string): string => { const date = new Date(dateString); return date.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }); }; // Function to truncate text to 99% and add ellipsis const truncateDescription = (text: string): string => { const truncateLength = Math.floor(text.length * 0.99); return text.substring(0, truncateLength) + "..."; }; const openNewsModal = (news: NewsItem): void => { setSelectedNews(news); setModalVisible(true); }; const closeNewsModal = (): void => { setModalVisible(false); }; // Navigate to ad website const navigateToAdPage = (url: string): void => { // Open the URL in the device's browser Linking.openURL(url).catch((err) => console.error("Couldn't open URL: ", err) ); }; // Advertisement component with full screen layout const AdvertisementCard = ({ index }: { index: number }): JSX.Element => { return ( SPONSORED {/* Full screen ad with vertical layout */} navigateToAdPage(singleAd.url)} activeOpacity={0.9} > {/* Image first */} {/* Content below image */} {singleAd.title} {singleAd.description} ); }; const renderNewsItem = ({ item }: { item: FeedItem }): JSX.Element => { if (isAd(item)) { return ; } // Truncate description to 99% and add ellipsis const truncatedDescription = truncateDescription(item.paragraphs[0]); return ( {/* News image - 30% of screen height */} {/* Content area - 70% of screen height */} {Platform.OS === "ios" ? ( {formatDate(item.created_at)} {/* Title first as requested */} {item.title} {/* Show 99% of description with ellipsis */} {truncatedDescription} {/* Read more button positioned on the right */} openNewsModal(item)} > Read more ) : ( {formatDate(item.created_at)} {/* Title first as requested */} {item.title} {/* Show 99% of description with ellipsis */} {truncatedDescription} {/* Read more button positioned on the right */} openNewsModal(item)} > Read more )} ); }; const renderErrorView = (): JSX.Element => ( Connection Error {error} Retry ); return ( {/* Custom Navbar */} router.back()} > {/* Loading Indicator */} {loading ? ( Loading news... ) : error ? ( renderErrorView() ) : newsData.length === 0 ? ( No News Found We couldn't find any news articles. Please try again later. Refresh ) : ( /* Show all news items with ads after every 4 items */ item.id.toString()} pagingEnabled showsVerticalScrollIndicator={false} snapToAlignment="start" decelerationRate="fast" snapToInterval={height} /> )} {/* News Detail Modal with text X close button */} {/* Text X close button instead of icon */} X {/* Modal Content - Title and Description */} {selectedNews && ( {/* Title */} {selectedNews.title} {/* Date */} {formatDate(selectedNews.created_at)} {/* All paragraphs */} {selectedNews.paragraphs.map((paragraph, index) => ( {paragraph} ))} {/* Extra padding at the bottom for better scrolling */} )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, navbar: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingHorizontal: 16, }, backButton: { padding: 8, borderRadius: 20, }, brandContainer: { flexDirection: "row", alignItems: "center", }, brandText: { fontSize: 20, fontWeight: "700", }, brandAccent: { fontSize: 20, fontWeight: "700", color: "#007AFF", marginLeft: 4, }, refreshButton: { padding: 8, borderRadius: 20, }, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center", }, loadingText: { marginTop: 10, fontSize: 16, color: "#007AFF", }, errorContainer: { flex: 1, justifyContent: "center", alignItems: "center", padding: 20, }, errorTitle: { fontSize: 20, fontWeight: "bold", marginTop: 16, marginBottom: 8, }, errorMessage: { fontSize: 16, textAlign: "center", marginBottom: 20, color: "#666", }, retryButton: { backgroundColor: "#007AFF", paddingHorizontal: 20, paddingVertical: 10, borderRadius: 8, }, retryButtonText: { color: "#fff", fontWeight: "600", fontSize: 16, }, newsItemContainer: { height: height, width: width, position: "relative", }, imageContainer: { height: height * 0.3, // 30% of screen height for image width: width, overflow: "hidden", }, newsImage: { width: "100%", height: "100%", }, contentSection: { height: height * 0.7, // 70% of screen height for content width: width, }, blurContainer: { flex: 1, borderTopLeftRadius: 0, borderTopRightRadius: 0, overflow: "hidden", }, androidBlur: { backgroundColor: "rgba(167, 61, 228, 0.75)", // Purple background from the provided code }, scrollableContent: { flex: 1, }, textContainer: { padding: 20, paddingTop: 20, paddingBottom: 30, }, date: { color: "#ccc", fontSize: 12, marginBottom: 10, }, title: { color: "#fff", fontSize: 18, fontWeight: "bold", marginBottom: 15, }, paragraph: { color: "#fff", fontSize: 15, lineHeight: 22, marginBottom: 15, }, // Read more button styles readMoreContainer: { flexDirection: "row", justifyContent: "flex-end", marginTop: 20, }, readMoreButton: { flexDirection: "row", alignItems: "center", backgroundColor: "rgba(255, 255, 255, 0.2)", paddingVertical: 10, paddingHorizontal: 16, borderRadius: 8, }, readMoreText: { color: "#ffffff", fontWeight: "600", fontSize: 16, marginRight: 5, }, modalContainer: { flex: 1, backgroundColor: "rgba(0,0,0,0.5)", }, modalContent: { flex: 1, marginTop: 60, // Space from top marginBottom: 20, // Space from bottom marginHorizontal: 15, // Space from sides backgroundColor: "rgba(167, 61, 228, 0.95)", // Purple background borderRadius: 20, overflow: "hidden", }, modalScrollView: { flex: 1, }, modalScrollViewContent: { padding: 20, paddingTop: 25, }, modalNewsTitle: { fontSize: 24, fontWeight: "bold", marginBottom: 10, color: "#ffffff", marginTop: 15, // Add space at the top for the close button }, modalDate: { fontSize: 14, color: "rgba(255, 255, 255, 0.7)", marginBottom: 20, }, modalParagraph: { fontSize: 17, lineHeight: 24, marginBottom: 16, color: "#ffffff", }, closeIconButton: { position: "absolute", top: 15, right: 15, zIndex: 10, width: 36, height: 36, borderRadius: 18, // Half of width/height backgroundColor: "rgba(255, 255, 255, 0.3)", justifyContent: "center", alignItems: "center", }, closeButtonText: { color: "#ffffff", fontSize: 20, fontWeight: "bold", }, modalBottomPadding: { height: 40, // Extra padding at the bottom for better scrolling }, fullScreenItem: { height: height, width: width, backgroundColor: "#ffffff", }, adHeader: { backgroundColor: "#007AFF", paddingVertical: 6, paddingHorizontal: 12, alignItems: "center", }, adLabel: { fontSize: 12, fontWeight: "bold", color: "#fff", }, // Full screen ad styles fullScreenAdContainer: { height: height, width: width, }, adImageContainer: { height: height * 0.5, // 70% for image width: width, }, singleAdImage: { width: "100%", height: "100%", }, singleAdContent: { height: height * 0.5, // 30% for content width: width, padding: 20, backgroundColor: "#ffffff", }, singleAdTitle: { fontSize: 24, fontWeight: "bold", marginBottom: 15, color: "#000", }, singleAdDescription: { fontSize: 16, lineHeight: 24, color: "#333", }, });