update
This commit is contained in:
parent
32402275ac
commit
3fcbffd986
5 changed files with 797 additions and 104 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { Tabs } from 'expo-router';
|
||||
import React from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { Image } from 'react-native';
|
||||
|
||||
import { HapticTab } from '@/components/HapticTab';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
|
|
@ -29,15 +30,35 @@ export default function TabLayout() {
|
|||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: 'Home',
|
||||
tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
|
||||
title: 'Guard',
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<Image
|
||||
source={
|
||||
focused
|
||||
? require('@/assets/images/guard.jpg')
|
||||
: require('@/assets/images/guard.jpg')
|
||||
}
|
||||
style={{ width: 90, height: 28 }}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="explore"
|
||||
options={{
|
||||
title: 'Explore',
|
||||
tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
|
||||
title: 'News',
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<Image
|
||||
source={
|
||||
focused
|
||||
? require('@/assets/images/news.jpeg')
|
||||
: require('@/assets/images/news.jpeg')
|
||||
}
|
||||
style={{ width: 40, height: 28 }}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
|
|
|
|||
|
|
@ -1,109 +1,781 @@
|
|||
import { StyleSheet, Image, Platform } from 'react-native';
|
||||
|
||||
import { Collapsible } from '@/components/Collapsible';
|
||||
import { ExternalLink } from '@/components/ExternalLink';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
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";
|
||||
|
||||
export default function TabTwoScreen() {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
|
||||
headerImage={
|
||||
<IconSymbol
|
||||
size={310}
|
||||
color="#808080"
|
||||
name="chevron.left.forwardslash.chevron.right"
|
||||
style={styles.headerImage}
|
||||
/>
|
||||
}>
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText type="title">Explore</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedText>This app includes example code to help you get started.</ThemedText>
|
||||
<Collapsible title="File-based routing">
|
||||
<ThemedText>
|
||||
This app has two screens:{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
|
||||
</ThemedText>
|
||||
<ThemedText>
|
||||
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
|
||||
sets up the tab navigator.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/router/introduction">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Android, iOS, and web support">
|
||||
<ThemedText>
|
||||
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
|
||||
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
|
||||
</ThemedText>
|
||||
</Collapsible>
|
||||
<Collapsible title="Images">
|
||||
<ThemedText>
|
||||
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
|
||||
different screen densities
|
||||
</ThemedText>
|
||||
<Image source={require('@/assets/images/react-logo.png')} style={{ alignSelf: 'center' }} />
|
||||
<ExternalLink href="https://reactnative.dev/docs/images">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Custom fonts">
|
||||
<ThemedText>
|
||||
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{' '}
|
||||
<ThemedText style={{ fontFamily: 'SpaceMono' }}>
|
||||
custom fonts such as this one.
|
||||
</ThemedText>
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Light and dark mode components">
|
||||
<ThemedText>
|
||||
This template has light and dark mode support. The{' '}
|
||||
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
|
||||
what the user's current color scheme is, and so you can adjust UI colors accordingly.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
|
||||
<ThemedText type="link">Learn more</ThemedText>
|
||||
</ExternalLink>
|
||||
</Collapsible>
|
||||
<Collapsible title="Animations">
|
||||
<ThemedText>
|
||||
This template includes an example of an animated component. The{' '}
|
||||
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
|
||||
the powerful <ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText>{' '}
|
||||
library to create a waving hand animation.
|
||||
</ThemedText>
|
||||
{Platform.select({
|
||||
ios: (
|
||||
<ThemedText>
|
||||
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
|
||||
component provides a parallax effect for the header image.
|
||||
</ThemedText>
|
||||
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<NewsItem | null>(null);
|
||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||
const [newsData, setNewsData] = useState<NewsItem[]>([]);
|
||||
const [processedData, setProcessedData] = useState<FeedItem[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(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<void> => {
|
||||
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
|
||||
),
|
||||
})}
|
||||
</Collapsible>
|
||||
</ParallaxScrollView>
|
||||
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 (
|
||||
<View style={styles.fullScreenItem}>
|
||||
<View style={styles.adHeader}>
|
||||
<Text style={[styles.adLabel, { color: "#fff" }]}>SPONSORED</Text>
|
||||
</View>
|
||||
|
||||
{/* Full screen ad with vertical layout */}
|
||||
<TouchableOpacity
|
||||
style={styles.fullScreenAdContainer}
|
||||
onPress={() => navigateToAdPage(singleAd.url)}
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
{/* Image first */}
|
||||
<View style={styles.adImageContainer}>
|
||||
<Image
|
||||
source={{ uri: singleAd.image }}
|
||||
style={styles.singleAdImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Content below image */}
|
||||
<View style={styles.singleAdContent}>
|
||||
<Text style={styles.singleAdTitle}>{singleAd.title}</Text>
|
||||
<Text style={styles.singleAdDescription}>
|
||||
{singleAd.description}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const renderNewsItem = ({ item }: { item: FeedItem }): JSX.Element => {
|
||||
if (isAd(item)) {
|
||||
return <AdvertisementCard index={item.adIndex} />;
|
||||
}
|
||||
|
||||
// Truncate description to 99% and add ellipsis
|
||||
const truncatedDescription = truncateDescription(item.paragraphs[0]);
|
||||
|
||||
return (
|
||||
<View style={styles.newsItemContainer}>
|
||||
{/* News image - 30% of screen height */}
|
||||
<View style={styles.imageContainer}>
|
||||
<Image
|
||||
source={{ uri: item.image_url }}
|
||||
style={styles.newsImage}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Content area - 70% of screen height */}
|
||||
<View style={styles.contentSection}>
|
||||
{Platform.OS === "ios" ? (
|
||||
<BlurView
|
||||
intensity={80}
|
||||
tint="dark"
|
||||
style={[
|
||||
styles.blurContainer,
|
||||
{ backgroundColor: "rgba(167, 61, 228, 0.75)" },
|
||||
]}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.scrollableContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={styles.date}>{formatDate(item.created_at)}</Text>
|
||||
|
||||
{/* Title first as requested */}
|
||||
<Text style={styles.title}>{item.title}</Text>
|
||||
|
||||
{/* Show 99% of description with ellipsis */}
|
||||
<Text style={styles.paragraph}>{truncatedDescription}</Text>
|
||||
|
||||
{/* Read more button positioned on the right */}
|
||||
<View style={styles.readMoreContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.readMoreButton}
|
||||
onPress={() => openNewsModal(item)}
|
||||
>
|
||||
<Text style={styles.readMoreText}>Read more</Text>
|
||||
<IconSymbol
|
||||
name="chevron.right"
|
||||
size={16}
|
||||
color="#ffffff"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</BlurView>
|
||||
) : (
|
||||
<View style={[styles.blurContainer, styles.androidBlur]}>
|
||||
<ScrollView
|
||||
style={styles.scrollableContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={styles.date}>{formatDate(item.created_at)}</Text>
|
||||
|
||||
{/* Title first as requested */}
|
||||
<Text style={styles.title}>{item.title}</Text>
|
||||
|
||||
{/* Show 99% of description with ellipsis */}
|
||||
<Text style={styles.paragraph}>{truncatedDescription}</Text>
|
||||
|
||||
{/* Read more button positioned on the right */}
|
||||
<View style={styles.readMoreContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.readMoreButton}
|
||||
onPress={() => openNewsModal(item)}
|
||||
>
|
||||
<Text style={styles.readMoreText}>Read more</Text>
|
||||
<IconSymbol
|
||||
name="chevron.right"
|
||||
size={16}
|
||||
color="#ffffff"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const renderErrorView = (): JSX.Element => (
|
||||
<View
|
||||
style={[
|
||||
styles.errorContainer,
|
||||
{ backgroundColor: colorScheme === "dark" ? "#1e1e1e" : "#ffffff" },
|
||||
]}
|
||||
>
|
||||
<IconSymbol name="exclamationmark.triangle" size={50} color="#FF3B30" />
|
||||
<Text
|
||||
style={[
|
||||
styles.errorTitle,
|
||||
{ color: colorScheme === "dark" ? "#fff" : "#000" },
|
||||
]}
|
||||
>
|
||||
Connection Error
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.errorMessage,
|
||||
{ color: colorScheme === "dark" ? "#ddd" : "#666" },
|
||||
]}
|
||||
>
|
||||
{error}
|
||||
</Text>
|
||||
<TouchableOpacity style={styles.retryButton} onPress={fetchNews}>
|
||||
<Text style={styles.retryButtonText}>Retry</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: colorScheme === "dark" ? "#121212" : "#f0f2f5" },
|
||||
]}
|
||||
>
|
||||
<StatusBar
|
||||
translucent
|
||||
backgroundColor="transparent"
|
||||
barStyle={colorScheme === "dark" ? "light-content" : "dark-content"}
|
||||
/>
|
||||
|
||||
{/* Custom Navbar */}
|
||||
<SafeAreaView
|
||||
style={{
|
||||
backgroundColor: colorScheme === "dark" ? "#121212" : "#ffffff",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.navbar,
|
||||
{
|
||||
backgroundColor: colorScheme === "dark" ? "#121212" : "#ffffff",
|
||||
paddingTop: Platform.OS === "android" ? insets.top + 8 : 8,
|
||||
paddingBottom: 12,
|
||||
borderBottomColor:
|
||||
colorScheme === "dark" ? "#2c2c2c" : "rgba(150, 150, 150, 0.2)",
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.backButton}
|
||||
onPress={() => router.back()}
|
||||
>
|
||||
<IconSymbol
|
||||
name="chevron.left"
|
||||
size={24}
|
||||
color={colorScheme === "dark" ? "#ffffff" : "#000000"}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={styles.brandContainer}>
|
||||
<Text
|
||||
style={[
|
||||
styles.brandText,
|
||||
{ color: colorScheme === "dark" ? "#ffffff" : "#000000" },
|
||||
]}
|
||||
>
|
||||
30 Second
|
||||
</Text>
|
||||
<Text style={styles.brandAccent}>News</Text>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity style={styles.refreshButton} onPress={fetchNews}>
|
||||
<IconSymbol
|
||||
name="arrow.clockwise"
|
||||
size={20}
|
||||
color={colorScheme === "dark" ? "#ffffff" : "#000000"}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
|
||||
{/* Loading Indicator */}
|
||||
{loading ? (
|
||||
<View
|
||||
style={[
|
||||
styles.loadingContainer,
|
||||
{ backgroundColor: colorScheme === "dark" ? "#121212" : "#f0f2f5" },
|
||||
]}
|
||||
>
|
||||
<ActivityIndicator size="large" color="#007AFF" />
|
||||
<Text
|
||||
style={[
|
||||
styles.loadingText,
|
||||
{ color: colorScheme === "dark" ? "#fff" : "#007AFF" },
|
||||
]}
|
||||
>
|
||||
Loading news...
|
||||
</Text>
|
||||
</View>
|
||||
) : error ? (
|
||||
renderErrorView()
|
||||
) : newsData.length === 0 ? (
|
||||
<View
|
||||
style={[
|
||||
styles.errorContainer,
|
||||
{ backgroundColor: colorScheme === "dark" ? "#1e1e1e" : "#ffffff" },
|
||||
]}
|
||||
>
|
||||
<IconSymbol name="newspaper" size={50} color="#007AFF" />
|
||||
<Text
|
||||
style={[
|
||||
styles.errorTitle,
|
||||
{ color: colorScheme === "dark" ? "#fff" : "#000" },
|
||||
]}
|
||||
>
|
||||
No News Found
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.errorMessage,
|
||||
{ color: colorScheme === "dark" ? "#ddd" : "#666" },
|
||||
]}
|
||||
>
|
||||
We couldn't find any news articles. Please try again later.
|
||||
</Text>
|
||||
<TouchableOpacity style={styles.retryButton} onPress={fetchNews}>
|
||||
<Text style={styles.retryButtonText}>Refresh</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
/* Show all news items with ads after every 4 items */
|
||||
<FlatList
|
||||
data={processedData}
|
||||
renderItem={renderNewsItem}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
pagingEnabled
|
||||
showsVerticalScrollIndicator={false}
|
||||
snapToAlignment="start"
|
||||
decelerationRate="fast"
|
||||
snapToInterval={height}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* News Detail Modal with text X close button */}
|
||||
<Modal
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
visible={modalVisible}
|
||||
onRequestClose={closeNewsModal}
|
||||
>
|
||||
<View style={styles.modalContainer}>
|
||||
<View
|
||||
style={[
|
||||
styles.modalContent,
|
||||
{ backgroundColor: "rgba(167, 61, 228, 0.95)" },
|
||||
]}
|
||||
>
|
||||
{/* Text X close button instead of icon */}
|
||||
<TouchableOpacity
|
||||
style={styles.closeIconButton}
|
||||
onPress={closeNewsModal}
|
||||
>
|
||||
<Text style={styles.closeButtonText}>X</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Modal Content - Title and Description */}
|
||||
{selectedNews && (
|
||||
<ScrollView
|
||||
style={styles.modalScrollView}
|
||||
showsVerticalScrollIndicator={true}
|
||||
contentContainerStyle={styles.modalScrollViewContent}
|
||||
>
|
||||
{/* Title */}
|
||||
<Text style={styles.modalNewsTitle}>{selectedNews.title}</Text>
|
||||
|
||||
{/* Date */}
|
||||
<Text style={styles.modalDate}>
|
||||
{formatDate(selectedNews.created_at)}
|
||||
</Text>
|
||||
|
||||
{/* All paragraphs */}
|
||||
{selectedNews.paragraphs.map((paragraph, index) => (
|
||||
<Text key={index} style={styles.modalParagraph}>
|
||||
{paragraph}
|
||||
</Text>
|
||||
))}
|
||||
|
||||
{/* Extra padding at the bottom for better scrolling */}
|
||||
<View style={styles.modalBottomPadding} />
|
||||
</ScrollView>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
headerImage: {
|
||||
color: '#808080',
|
||||
bottom: -90,
|
||||
left: -35,
|
||||
position: 'absolute',
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
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",
|
||||
},
|
||||
});
|
||||
BIN
assets/images/gp.jpeg
Normal file
BIN
assets/images/gp.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
assets/images/guard.jpg
Normal file
BIN
assets/images/guard.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/images/news.jpeg
Normal file
BIN
assets/images/news.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
Loading…
Add table
Reference in a new issue