Browse Source

First Phase API Integration

fsavio-lab 1 year ago
parent
commit
3a7fcfdfe3

+ 7 - 0
src/api/APIHandler.js

@@ -0,0 +1,7 @@
+import axios from "axios";
+import { API_URL_v1} from "./urls";
+
+export default APIHandler = axios.create({
+    baseURL: API_URL_v1,
+    timeout: 1000,
+})

+ 10 - 0
src/api/data.js

@@ -0,0 +1,10 @@
+import APIHandler from "./APIHandler";
+import { ARTICLE_COMMENTS_URL, ARTICLE_DETAIL_URL, ARTICLE_RECOMMENDATIONS_URL, ARTICLE_SEARCH_URL, MENUS_URL, TRENDING_URL } from "./urls";
+
+export const getTrendingNews = () =>  APIHandler.get(TRENDING_URL)
+export const getMenus = () => APIHandler.get(MENUS_URL)
+export const getArticlesByCategory = (category) => APIHandler.get(ARTICLE_SEARCH_URL(category))
+export const getArticleBySlug = (slug) => APIHandler.get(ARTICLE_DETAIL_URL(slug))
+export const getCommentByArticleID = (id) => APIHandler.get(ARTICLE_COMMENTS_URL(id))
+export const getRecommendationByArticleID = (id) => APIHandler.get(ARTICLE_RECOMMENDATIONS_URL(id))
+export const getCategories = () => APIHandler.get(MENUS_URL)

+ 16 - 0
src/api/urls.js

@@ -0,0 +1,16 @@
+export const BASE_URL = "http://www.newscout.in";
+export const DEFAULT_DOMAIN = "newscout";
+export const PAGINATE_BY = 5;
+export const API_URL_v1 = BASE_URL + "/api/v1/"
+export const API_URL_v2 = BASE_URL + "/api/v2/"
+
+
+
+// * V1 API URLS
+export const ARTICLE_URL = `article/search/?domain=${DEFAULT_DOMAIN}`
+export const TRENDING_URL = `trending/?domain=${DEFAULT_DOMAIN}`
+export const MENUS_URL =  `menus/?domain=${DEFAULT_DOMAIN}`
+export const ARTICLE_SEARCH_URL = (category_type) =>  `article/search/?domain=${DEFAULT_DOMAIN}&category=${category_type}`
+export const ARTICLE_DETAIL_URL = (slug) => `articles/${slug}/?domain=${DEFAULT_DOMAIN}`
+export const ARTICLE_COMMENTS_URL = (id) => `comment/?article_id=${id}`
+export const ARTICLE_RECOMMENDATIONS_URL = (id) => `articles/${id}/recommendations`

+ 4 - 4
src/components/molecules/Cards/VerticalNewsCard.js

@@ -18,7 +18,7 @@ const VerticalNewsCard = ({ image, headline, tagline, style, onPress }) => {
             maxWidth: horizontalScale(210),
             backgroundColor: currentTheme === 'light' ? colors().white : colors().black_variant ,
             borderRadius: moderateScale(4),
-            height: verticalScale(300),
+            height: verticalScale(275),
             maxHeight: verticalScale(300),
             marginRight: horizontalScale(16),
             paddingBottom: verticalScale(24),
@@ -33,7 +33,7 @@ const VerticalNewsCard = ({ image, headline, tagline, style, onPress }) => {
         },
         imagesContainer: {
             width: '100%',
-            height: '50%'
+            height: '60%'
 
         },
         image: {
@@ -64,8 +64,8 @@ const VerticalNewsCard = ({ image, headline, tagline, style, onPress }) => {
                     <Image source={image ?? images.verticalCard} style={styles.image} />
                 </View>
                 <View style={styles.textContainer}>
-                    <Text style={styles.headline}> {headline ?? "Aston Villa avoid relegation on final day"}</Text>
-                    <Text style={styles.tagline}>{tagline ?? "The reason behind their disappointment is that iPir disappointment is that iPhonsers have been."}</Text>
+                    <Text style={styles.headline}> {headline}</Text>
+                    <Text style={styles.tagline}>{tagline }</Text>
                 </View>
             </View>
         </TouchableWithoutFeedback>

+ 1 - 1
src/components/molecules/Header/NewscoutTitleHeader.js

@@ -28,7 +28,7 @@ const NewscoutTitleHeader = ({ title, titleStyle, children, backButtonShown, onB
             backButtonShown={backButtonShown} 
             onBackClick={onBackClick}
             backIconTextComponent={
-                <Text style={[styles.title, titleStyle]}>{title ?? "Title"}</Text>
+                <Text style={[styles.title, titleStyle]}></Text>
             }   
         
         >

+ 110 - 130
src/components/organisms/Sections/CategorySection.js

@@ -1,23 +1,32 @@
-import { StyleSheet, Text, View, ScrollView } from 'react-native';
-import React, { useContext, useEffect } from 'react';
-import { ToggleButton } from 'react-native-paper';
+import {StyleSheet, Text, View, ScrollView} from 'react-native';
+import React, {useContext, useEffect} from 'react';
+import {ToggleButton} from 'react-native-paper';
 import fonts from '../../../constants/fonts';
 import colors from '../../../constants/colors';
 import HorizontalNewsCardVariant from '../../molecules/Cards/HorizontalNewsCardVariant';
-import { PAGINATE_BY, getScreenType, useConstructor } from '../../../constants/functions';
+import {
+  PAGINATE_BY,
+  getScreenType,
+  useConstructor,
+  navigateToArticle,
+} from '../../../constants/functions';
 import SectionHeader from '../../molecules/Header/SectionHeader';
-import { horizontalScale, moderateScale, verticalScale } from '../../../constants/metrics';
-import { ThemeContext } from '../../../context/theme.context';
-
-
-// * API Handling
-
-const CategorySection = ({ navigation }) => {
-
-  const theme = useContext(ThemeContext)
-  const currentTheme = theme.state.theme
-
-  const [news, setNews] = React.useState({ all: [] });
+import {
+  horizontalScale,
+  moderateScale,
+  verticalScale,
+} from '../../../constants/metrics';
+import {ThemeContext} from '../../../context/theme.context';
+import {getArticlesByCategory, getCategories} from '../../../api/data';
+import LoadingScreen from './LoadingScreen';
+
+const CategorySection = ({navigation}) => {
+  const theme = useContext(ThemeContext);
+  const currentTheme = theme.state.theme;
+
+  const [isLoading, setLoading] = React.useState(true);
+
+  const [news, setNews] = React.useState({all: []});
   const updateNewsByCategories = (key, value) => {
     setNews(prevObject => ({
       ...prevObject,
@@ -27,82 +36,47 @@ const CategorySection = ({ navigation }) => {
 
   const [categoryValue, setCategoryValue] = React.useState('');
   const [categories, setCategories] = React.useState([]);
-  const fetchCategories = () => {
-    fetch(`http://www.newscout.in/api/v1/menus/?domain=newscout`)
-      .then(res => res.json())
-      .then(json => {
-        let categoriesHeading = json.body.results.map(
-          item => item.heading.name,
-        );
-        setCategories(categoriesHeading);
-        setNews({});
+
+  const fetchData = () => {
+    getCategories()
+      .then(res => {
+        let categoryData = res.data.body.results
+        setCategories(categoryData);
         setNews(
-          categoriesHeading.reduce(
-            (result, key) => ({ ...result, [key]: [] }),
+          categoryData.reduce(
+            (result, key) => ({...result, [key.heading.name]: []}),
             {},
           ),
-        ),
-          setCategoryValue(categoriesHeading[0]);
-        return categoriesHeading;
-      })
-      .then(cats => {
-        cats.forEach(cat => {
-          fetchNews(cat);
-        });
-        return cats;
+        )
+       
+        return categoryData
+      }).then(categoryData => {
+        categoryData.forEach((category) => fetchNews(category.heading.name))
+        return categoryData
+      }).then(categoryData => {
+        setCategoryValue(categoryData[0].heading.name);
+        setLoading(false);
+        console.warn(categoryData)
+        console.warn(news)
       })
-      .catch(err => console.log(err))
+      .catch(err => console.error(`Fetch Categories : ${err}`))
       .finally(() => console.log('Fetch Categories Executed'));
   };
 
   const fetchNews = category => {
-    fetch(
-      `http://www.newscout.in/api/v1/article/search/?domain=newscout&category=${category}`,
-    )
-      .then(res => res.json())
-      .then(data => {
-        const newsDataFetch = data.body.results;
-        const finalNewsData = newsDataFetch
-          .slice(0, PAGINATE_BY)
-          .map(article => ({
-            headline: article.title,
-            image: { uri: article.cover_image },
-            category: article.category,
-            root_category: article.root_category,
-            timestamp: article.published_on,
-            tagline: 'Bruh Momento Oi Lorem Ipsum di rubi rabbi',
-            id: article.id,
-            slug: article.slug
-          }));
-        updateNewsByCategories(category, finalNewsData);
-        // categories.forEach((category) => {setNews((prev) => ({...prev,[category]:json.body.results}))})
-        return finalNewsData;
+    getArticlesByCategory(category)
+      .then(res => {
+        console.log(`Fetch News Trial : ${res.data.body.results}`)
+        updateNewsByCategories(category, res.data.body.results.slice(0,PAGINATE_BY))
       })
-      .catch(err => console.log(err))
-      .finally(() => console.log('Fetch Category News Executed'));
+      .catch(err => console.error(`Fetch News : ${err}`))
+      .finally(() => console.log('Fetch News Executed'));
   };
 
-  const detailPageNavigation = (slug, id) => {
-    navigation.push('NewsDetailPage', { articleId: id, articleSlug: slug })
-  }
-
-
   useConstructor(() => {
-    const cat_data = fetchCategories();
-    //console.log(`construct ${categories}`)
-    // for (let category in cat_data) {
-    //     updateNewsByCategories(category,fetchNews(category))
-
-    // }
+    fetchData();
   });
 
-  // useEffect(() => {
-  //     for (let category in cat_data) {
-  //         fetchNews(category)
-
-  //     }
-  // })
-
   const styles = StyleSheet.create({
     categoriesTitle: {
       flexDirection: 'row',
@@ -120,7 +94,7 @@ const CategorySection = ({ navigation }) => {
       flexDirection: 'row',
       alignItems: 'space-between',
       paddingHorizontal: horizontalScale(16),
-      backgroundColor: colors().dominant
+      backgroundColor: colors().dominant,
     },
     container: {
       borderRadius: moderateScale(18),
@@ -152,67 +126,73 @@ const CategorySection = ({ navigation }) => {
       backgroundColor: colors().dominant,
       gap: moderateScale(16),
       flexWrap: 'wrap',
-      flexDirection: 'row'
-
+      flexDirection: 'row',
     },
   });
 
-
   return (
     <View style={styles.categoriesContainer}>
       <SectionHeader label={'Categories'} />
-      <View style={styles.categoriesPillContainer}>
-        <ScrollView horizontal showsHorizontalScrollIndicator={false}>
-          <ToggleButton.Group
-            onValueChange={value => {
-              setCategoryValue(value);
-            }}>
-            {categories.map(category => (
-              <ToggleButton
-                key={category}
-                icon={() => (
-                  <Text
+      {isLoading === true ? (
+        <LoadingScreen containerHeight={240} />
+      ) : (
+        <>
+          <View style={styles.categoriesPillContainer}>
+            <ScrollView horizontal showsHorizontalScrollIndicator={false}>
+              <ToggleButton.Group
+                onValueChange={value => {
+                  setCategoryValue(value)
+                }}>
+                {categories.map(category => (
+                  <ToggleButton
+                    key={category.heading.name}
+                    icon={() => (
+                      <Text
+                        style={[
+                          category.heading.name === categoryValue
+                            ? styles.selectedPillText
+                            : styles.pillText,
+                        ]}>
+                        {category.heading.name}
+                      </Text>
+                    )}
                     style={[
-                      category === categoryValue
-                        ? styles.selectedPillText
-                        : styles.pillText,
-                    ]}>
-                    {category}
-                  </Text>
+                      category.heading.name === categoryValue
+                        ? styles.selectedContainer
+                        : styles.container,
+                      {marginHorizontal: horizontalScale(4)},
+                    ]}
+                    value={category.heading.name}
+                  />
+                ))}
+              </ToggleButton.Group>
+            </ScrollView>
+          </View>
+          <View>
+            <ScrollView showsVerticalScrollIndicator={false}>
+              <View style={styles.categoriesNewsContainer}>
+                {news[categoryValue] !== undefined ? (
+                  news[categoryValue].map(item => (
+                    <HorizontalNewsCardVariant
+                      headline={item.title}
+                      image={{uri: item.cover_image}}
+                      timestamp={item.published_on}
+                      tagline={""}
+                      onPress={() =>
+                        navigateToArticle(navigation, item.id, item.slug)
+                      }
+                    />
+                  ))
+                ) : (
+                  <LoadingScreen containerHeight={240}/>
                 )}
-                style={[
-                  category === categoryValue
-                    ? styles.selectedContainer
-                    : styles.container,
-                  { marginHorizontal: horizontalScale(4) },
-                ]}
-                value={category}
-              />
-            ))}
-          </ToggleButton.Group>
-        </ScrollView>
-      </View>
-      <View><ScrollView showsVerticalScrollIndicator={false}>
-        <View style={styles.categoriesNewsContainer}>
-          {news[categoryValue] !== undefined ? (
-            news[categoryValue].map(item => (
-              <HorizontalNewsCardVariant
-                headline={item.headline}
-                image={item.image}
-                timestamp={item.timestamp}
-                tagline={item.tagline}
-                onPress={() => detailPageNavigation(item.slug, item.id)}
-              />
-            ))
-          ) : (
-            <Text> News Not Present</Text>
-          )}
-        </View>
-      </ScrollView></View>
-
+              </View>
+            </ScrollView>
+          </View>
+        </>
+      )}
     </View>
   );
 };
 
 export default CategorySection;
-

+ 91 - 101
src/components/organisms/Sections/TrendingSection.js

@@ -1,22 +1,26 @@
-import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
-import React, { useContext } from 'react'
-import images from '../../../assets/images/images'
+import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
+import React, {useContext} from 'react';
 import Carousel from 'react-native-reanimated-carousel';
-import MaterialComIcon from 'react-native-vector-icons/MaterialCommunityIcons'
-import { PAGINATE_BY, useConstructor } from '../../../constants/functions'
-import fonts from '../../../constants/fonts'
-import colors from '../../../constants/colors'
-import ImageBGCard from '../../molecules/Cards/ImageBGCard'
-import { moderateScale, screenHeight, screenWidth } from '../../../constants/metrics';
+import MaterialComIcon from 'react-native-vector-icons/MaterialCommunityIcons';
+import {PAGINATE_BY, navigateToArticle, useConstructor} from '../../../constants/functions';
+import fonts from '../../../constants/fonts';
+import colors from '../../../constants/colors';
+import ImageBGCard from '../../molecules/Cards/ImageBGCard';
+import {
+  moderateScale,
+  screenHeight,
+  screenWidth,
+} from '../../../constants/metrics';
 import SectionHeader from '../../molecules/Header/SectionHeader';
-import { ThemeContext } from '../../../context/theme.context';
+import {ThemeContext} from '../../../context/theme.context';
+import {getTrendingNews} from '../../../api/data';
+import LoadingScreen from './LoadingScreen';
 
 
 
 // import {Q} from '@nozbe/watermelondb';
 // import withObservables from '@nozbe/with-observables';
 
-
 const ITEM_WIDTH = Math.round(screenWidth * 1);
 const ITEM_HEIGHT = Math.round(screenHeight * (1 / 3));
 
@@ -28,57 +32,36 @@ const ITEM_HEIGHT = Math.round(screenHeight * (1 / 3));
 //   news: news.observe(),
 // }))(ImageBGCard);
 
+const TrendingSection = props => {
+  const {
+    navigation
+  } = props
 
-const TrendingSection = () => {
-
-  const theme = useContext(ThemeContext)
-  const currentTheme = theme.state.theme
+  const theme = useContext(ThemeContext);
+  const currentTheme = theme.state.theme;
 
-  const [currentCardIndex, setCurrentCardIndex] = React.useState(0)
-  const updateCurrentIndex = (index) => {
-    console.log('current index:', index)
-    setCurrentCardIndex(index)
-  }
+  const [currentCardIndex, setCurrentCardIndex] = React.useState(0);
+  const updateCurrentIndex = index => {
+    console.log('current index:', index);
+    setCurrentCardIndex(index);
+  };
 
-  const [posts, setPosts] = React.useState([{}, {}, {}, {}, {}])
+  const [posts, setPosts] = React.useState([]);
+  const [isLoading, setLoading] = React.useState(true);
 
   const fetchPosts = () => {
-    fetch("http://www.newscout.in/api/v1/trending?domain=newscout")
-      .then((res) => res.json())
-      .then((json) => {
-        let postData = json.body.results
-        // const finalPostData = postData.slice(0,5).map((item)=> ({
-        //   image:item.cover_image,
-        //   author: item.author,
-        //   headline: item.title,
-        //   id: item.id,
-        // }))
-        let buffer = []
-        postData.slice(0, PAGINATE_BY).forEach((item) => buffer.push(...item.articles))
-        // let buffer = [] 
-        // for (const item of postData.slice(0,PAGINATE_BY)) {
-        //   buffer.push(...item.articles)
-        // }
-        // console.log(`Bruh ` + buffer)
-        const finalPostData = buffer.map((item) => ({
-          image: item.cover_image,
-          author: item.author,
-          headline: item.title,
-          id: item.id,
-        }))
-        setPosts(finalPostData)
-        return finalPostData
+    getTrendingNews()
+      .then(res => {
+        console.log(res.data)
+        setPosts(res.data.body.results.slice(0,PAGINATE_BY));
+        setLoading(false)
       })
-      .catch((err) => console.log(err))
-      .finally(() => console.log("Fetch Posts Executed"))
-  }
+      .catch(error => console.log(error));
+  };
 
   useConstructor(function () {
-    fetchPosts()
-
-  })
-
-
+    fetchPosts();
+  });
 
   const styles = StyleSheet.create({
     recentHeader: {
@@ -87,23 +70,20 @@ const TrendingSection = () => {
     recentHeaderText: {
       fontFamily: fonts.type.semibold,
       fontSize: moderateScale(16),
-      color: colors().recessive
+      color: colors().recessive,
     },
     seeAllText: {
       fontFamily: fonts.type.semibold,
       color: colors().primaryColor,
       paddingRight: 16,
-      alignSelf: 'stretch'
-
-    },
-    recentHeaderIcon: {
-
+      alignSelf: 'stretch',
     },
+    recentHeaderIcon: {},
     recentCardContainer: {
       alignItems: 'center',
       paddingHorizontal: 8,
       width: screenWidth,
-      backgroundColor: colors().dominant
+      backgroundColor: colors().dominant,
     },
     paginationContainer: {
       justifyContent: 'center',
@@ -113,54 +93,64 @@ const TrendingSection = () => {
       paddingBottom: 8,
       flexDirection: 'row',
       gap: 2,
-      backgroundColor: colors().dominant 
-    }
-  })
+      backgroundColor: colors().dominant,
+    },
+  });
+
 
   return (
     <View>
       {/* <SectionHeader label={"Recent News"} /> */}
-      <SectionHeader label={"Trending"} />
+      <SectionHeader label={'Trending'} />
       <View style={styles.recentCardContainer}>
-        <Carousel
-          loop
-          width={ITEM_WIDTH}
-          height={ITEM_HEIGHT}
-          style={styles.recentCardContainer}
-          autoPlay={true}
-          data={posts}
-          scrollAnimationDuration={2000}
-          mode="parallax"
-          modeConfig={{
-            parallaxScrollingScale: 1,
-            parallaxScrollingOffset: 1,
-          }}
-          onSnapToItem={(index) => updateCurrentIndex(index)}
-          renderItem={({ index, item }) => (
-            <View style={{ paddingHorizontal: 16 }}>
-              <ImageBGCard
-                image={{ uri: item.image } ?? images.imageCard}
-                author={item.author ?? "Anonymous Author"}
-                headline={item.headline ?? `Bruh Moment ${index}`}
-              />
-            </View>
-          )}
-        />
-
+        {isLoading === true ? (
+          <LoadingScreen containerHeight={ITEM_HEIGHT} containerWidth={ITEM_WIDTH} />
+        ) : (
+          <Carousel
+            loop
+            width={ITEM_WIDTH}
+            height={ITEM_HEIGHT}
+            style={styles.recentCardContainer}
+            autoPlay={true}
+            data={posts}
+            scrollAnimationDuration={2000}
+            mode="parallax"
+            modeConfig={{
+              parallaxScrollingScale: 1,
+              parallaxScrollingOffset: 1,
+            }}
+            onSnapToItem={index => updateCurrentIndex(index)}
+            renderItem={({index, item}) => (
+              <View style={{paddingHorizontal: 16}}>
+                <ImageBGCard
+                  image={{uri: item.articles[0].cover_image}}
+                  author={item.articles[0].author}
+                  headline={item.articles[0].title}
+                  onPress={() => navigateToArticle(navigation,item.articles[0].id, item.articles[0].slug)}
+                />
+              </View>
+            )}
+          />
+        )}
       </View>
       <View style={styles.paginationContainer}>
-        {posts.map((item, index) => {
-          return <MaterialComIcon 
-          key={`dot_${index}`} 
-          name={"checkbox-blank-circle"} 
-          color={currentCardIndex === index ?
-               colors().secondaryColor :
-               colors().white_variant} size={12} />
+        {isLoading === true ? <></> : posts.map((item, index) => {
+          return (
+            <MaterialComIcon
+              key={`dot_${index}`}
+              name={'checkbox-blank-circle'}
+              color={
+                currentCardIndex === index
+                  ? colors().secondaryColor
+                  : colors().white_variant
+              }
+              size={12}
+            />
+          );
         })}
       </View>
     </View>
-  )
-}
-
-export default TrendingSection
+  );
+};
 
+export default TrendingSection;

+ 5 - 2
src/constants/functions.js

@@ -98,9 +98,12 @@ export const getScreenType = () => {
     return screenType
 }
 
-
-const validateEmail = (email) => {
+export const validateEmail = (email) => {
     const emailRegex = "^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$"
     return String(email).match(emailRegex)
 }
 
+
+export const navigateToArticle = (navigation,article_id,article_slug) => {
+    navigation.push("NewsDetailPage",{slug: article_slug, id: article_id})
+  }

+ 12 - 0
src/navigation/MainPageNavigator.jsx

@@ -4,6 +4,8 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
 import SearchPage from '../screens/Search/SearchPage';
 import HomePage from '../screens/HomePage/HomePage';
 import NotificationPage from '../screens/Notifications/NotificationPage';
+import NewsDetailPage from '../screens/News/NewsDetailPage';
+import NewsListPage from '../screens/News/NewsListPage';
 
 
 const Stack = createNativeStackNavigator();
@@ -27,6 +29,16 @@ const MainPageNavigator = () => {
             component={NotificationPage}
             options={{...defaultScreenOptions}}
         />
+        <Stack.Screen
+            name="NewsDetailPage"
+            component={NewsDetailPage}
+            options={{...defaultScreenOptions}}
+        />
+        <Stack.Screen
+            name="NewsListPage"
+            component={NewsListPage}
+            options={{...defaultScreenOptions}}
+        />
     </Stack.Navigator>
   )
 }

+ 1 - 1
src/screens/HomePage/HomePage.js

@@ -26,7 +26,7 @@ const HomePage = props => {
             </TouchableWithoutFeedback>
           </View>
         </NewscoutHomeHeader>
-        <TrendingSection />
+        <TrendingSection navigation={navigation}/>
         <CategorySection navigation={navigation} />
         <RecentPostsSection />
       </ScrollView>

+ 334 - 0
src/screens/News/NewsDetailPage.js

@@ -0,0 +1,334 @@
+import {
+  StyleSheet,
+  Text,
+  View,
+  TouchableWithoutFeedback,
+  ScrollView,
+  Image,
+} from 'react-native';
+import React, {useCallback} from 'react';
+import NewscoutTitleHeader from '../../components/molecules/Header/NewscoutTitleHeader';
+import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
+import colors from '../../constants/colors';
+import {
+  horizontalScale,
+  moderateScale,
+  verticalScale,
+} from '../../constants/metrics';
+import {
+  getArticleBySlug,
+  getCommentByArticleID,
+  getRecommendationByArticleID,
+} from '../../api/data';
+import LoadingScreen from '../../components/organisms/Sections/LoadingScreen';
+import {getTimestamp, navigateToArticle, useConstructor} from '../../constants/functions';
+import fonts from '../../constants/fonts';
+import BookmarkButton from '../../components/atoms/Buttons/BookmarkButton';
+import ShareButton from '../../components/atoms/Buttons/ShareButton';
+import IonIcon from 'react-native-vector-icons/Ionicons';
+import {
+  BottomSheetModal,
+  BottomSheetModalProvider,
+  BottomSheetView,
+  BottomSheetScrollView,
+} from '@gorhom/bottom-sheet';
+import ButtonWrapper from '../../components/atoms/Buttons/ButtonWrapper';
+import images from '../../assets/images/images';
+import SectionHeader from '../../components/molecules/Header/SectionHeader';
+import VerticalNewsCard from '../../components/molecules/Cards/VerticalNewsCard';
+
+const NewsDetailPage = props => {
+  const {navigation, route} = props;
+  const {id, slug} = route.params;
+
+  const [isLoading, setLoading] = React.useState(true);
+  const [article, setArticle] = React.useState({});
+  const [comments, setComments] = React.useState({
+    total_article_likes: 0,
+    results: [],
+  });
+  const [recommendations, setRecommendations] = React.useState([]);
+
+  const mainPageRef = React.useRef(null);
+  const bottomSheetModalRef = React.useRef('');
+
+  // Comments Modal
+  const snapPoints = React.useMemo(() => ['70%', '100%'], []);
+  const handlePresentModalPress = useCallback(() => {
+    bottomSheetModalRef.current?.present();
+  }, []);
+  const handleCloseModalPress = () => bottomSheetModalRef.current.close();
+  const handleSheetChanges = useCallback(index => {
+    console.log('handleSheetChanges', index);
+  }, []);
+
+  const fetchArticle = slug => {
+    getArticleBySlug(slug)
+      .then(res => {
+        console.log(res.data);
+        setArticle(res.data.body.article);
+        setLoading(false);
+      })
+      .catch(error => console.log(error));
+  };
+
+  const fetchComments = id => {
+    getCommentByArticleID(id)
+      .then(res => {
+        console.log(res.data.body);
+        setComments(res.data.body);
+      })
+      .catch(err => console.log(err));
+  };
+
+  const fetchRecommendations = id => {
+    getRecommendationByArticleID(id)
+      .then(res => setRecommendations(res.data.body.results))
+      .catch(err => console.log(err));
+  };
+
+  useConstructor(function () {
+    fetchArticle(slug);
+    fetchComments(id);
+    fetchRecommendations(id);
+  });
+
+  const styles = StyleSheet.create({
+    container: {backgroundColor: colors().dominant, minHeight: '100%'},
+    newsContainer: {paddingHorizontal: horizontalScale(16)},
+    newsTitle: {
+      fontFamily: fonts.type.semibold,
+      color: colors().recessive,
+      fontSize: moderateScale(16),
+    },
+    newsTagline: {
+      color: colors().recessive_variant,
+      fontFamily: fonts.type.regular,
+      paddingVertical: verticalScale(12),
+    },
+    newsDescriptorContainer: {
+      flexDirection: 'row',
+      gap: horizontalScale(8),
+    },
+    newsDescriptor: {
+      color: colors().recessive,
+      fontFamily: fonts.type.semibold,
+      fontSize: moderateScale(12),
+    },
+    imagesContainer: {
+      width: 'auto',
+      height: verticalScale(200),
+      marginTop: verticalScale(16),
+    },
+    image: {
+      borderRadius: moderateScale(4),
+      width: '100%',
+      height: '100%',
+    },
+    buttonStyle: {
+      padding: moderateScale(8),
+    },
+    commentSection: {
+      alignItems: 'center',
+      justifyContent: 'center',
+      flexDirection: 'row',
+      gap: moderateScale(8),
+      paddingLeft: horizontalScale(4),
+    },
+    commentText: {
+      fontFamily: fonts.type.regular,
+      color: colors().grayShade_200,
+    },
+    commentInputContainer: {
+      flexDirection: 'row',
+      justifyContent: 'space-between',
+      alignItems: 'center',
+      paddingVertical: verticalScale(4),
+    },
+    profileImage: {
+      height: 42,
+      width: 42,
+      borderRadius: 32,
+      marginRight: horizontalScale(16),
+    },
+    commentInput: {
+      paddingVertical: verticalScale(16),
+      alignItems: 'center',
+      justifyContent: 'flex-start',
+      flexDirection: 'row',
+    },
+    utilButtons: {
+      flexDirection: 'row',
+      justifyContent: 'space-between',
+      paddingVertical: verticalScale(4),
+    },
+    newsText: {
+      color: colors().grayShade_200,
+      fontFamily: fonts.type.regular,
+      lineHeight: verticalScale(24),
+      paddingVertical: verticalScale(4),
+    },
+    backToTop: {
+      alignItems: 'center',
+      justifyContent: 'center',
+      paddingTop: verticalScale(16),
+      paddingBottom: verticalScale(8),
+    },
+    backToTopText: {
+      color: colors().primaryColor,
+      fontFamily: fonts.type.medium,
+      textDecorationLine: 'underline',
+      fontSize: moderateScale(16),
+    },
+    recommendationContainer: {
+      paddingHorizontal: horizontalScale(16),
+      gap: moderateScale(4),
+      paddingBottom: verticalScale(16),
+      backgroundColor: colors().dominant,
+      flexDirection: 'row',
+    },
+  });
+
+  return (
+    <BottomSheetModalProvider>
+      <ScrollView ref={mainPageRef} contentContainerStyle={styles.container}>
+        <NewscoutTitleHeader
+          backButtonShown
+          onBackClick={() => navigation.goBack()}>
+          <TouchableWithoutFeedback onPress={() => navigation.toggleDrawer()}>
+            <MaterialIcon name="list" color={colors().primaryColor} size={30} />
+          </TouchableWithoutFeedback>
+        </NewscoutTitleHeader>
+        {isLoading === true ? (
+          <LoadingScreen />
+        ) : (
+          <View style={styles.newsContainer}>
+            <Text style={styles.newsTitle}>{article.title}</Text>
+            <Text style={styles.newsTagline}>{'No Tagline'}</Text>
+            <View style={styles.newsDescriptorContainer}>
+              <Text style={styles.newsDescriptor}>
+                {getTimestamp(article.published_on)}
+              </Text>
+              {(article.author !== undefined || article.author.length <= 0) ?? (
+                <Text style={styles.newsDescriptor}>By {article.author}</Text>
+              )}
+              <Text style={styles.newsDescriptor}>{article.source}</Text>
+            </View>
+            <View style={styles.imagesContainer}>
+              <Image source={{uri: article.cover_image}} style={styles.image} />
+            </View>
+            <View style={styles.utilButtons}>
+              <TouchableWithoutFeedback onPress={handlePresentModalPress}>
+                <View style={styles.commentSection}>
+                  <IonIcon
+                    name="chatbubble-outline"
+                    size={moderateScale(20)}
+                    color={colors().primaryColor}
+                  />
+                  <Text style={styles.commentText}>
+                    {comments.results.length ?? 123} COMMENTS
+                  </Text>
+                </View>
+              </TouchableWithoutFeedback>
+              <View style={{flexDirection: 'row'}}>
+                <BookmarkButton
+                  buttonStyle={styles.buttonStyle}
+                  iconSize={20}
+                  onPress={true}
+                />
+                <ShareButton
+                  buttonStyle={styles.buttonStyle}
+                  iconSize={20}
+                  onPress={true}
+                />
+              </View>
+            </View>
+            <Text style={styles.newsText}>{article.blurb}</Text>
+            <View style={styles.backToTop}>
+              <TouchableWithoutFeedback
+                onPress={() =>
+                  mainPageRef.current.scrollTo({
+                    y: 0,
+                  })
+                }>
+                <Text style={styles.backToTopText}>Back To Top</Text>
+              </TouchableWithoutFeedback>
+            </View>
+
+            <>
+              <BottomSheetModal
+                ref={bottomSheetModalRef}
+                index={1}
+                snapPoints={snapPoints}
+                onChange={handleSheetChanges}>
+                <BottomSheetScrollView>
+                  <BottomSheetView
+                    style={{paddingHorizontal: horizontalScale(24)}}>
+                    <BottomSheetView style={styles.commentInputContainer}>
+                      <Text
+                        style={{
+                          fontFamily: fonts.type.semibold,
+                          color: colors.black,
+                          fontSize: moderateScale(16),
+                        }}>
+                        Comments
+                      </Text>
+                      <ButtonWrapper onPress={handleCloseModalPress}>
+                        <IonIcon
+                          name="close-sharp"
+                          size={moderateScale(20)}
+                          color={colors().recessive}
+                        />
+                      </ButtonWrapper>
+                    </BottomSheetView>
+                    <View style={styles.commentInput}>
+                      <Image
+                        source={images.imageCard}
+                        style={[styles.profileImage]}
+                      />
+                      <Text>Comment Text Input</Text>
+                    </View>
+                    <>
+                      <Text
+                        style={{
+                          fontFamily: fonts.type.medium,
+                          color: colors().recessive,
+                          paddingBottom: verticalScale(8),
+                        }}>
+                        View all Comments({comments.results.length})
+                      </Text>
+                    </>
+                    <BottomSheetView style={{gap: moderateScale(16)}}>
+                      {comments.results.length <= 0 ? (
+                        <LoadingScreen />
+                      ) : (
+                        comments.results.map(() => <CommentCard />)
+                      )}
+                    </BottomSheetView>
+                  </BottomSheetView>
+                </BottomSheetScrollView>
+              </BottomSheetModal>
+            </>
+          </View>
+        )}
+        <SectionHeader label={'Recommendations'} />
+        <ScrollView
+          horizontal
+          contentContainerStyle={styles.recommendationContainer}
+          style={{flexDirection: 'row'}}
+          showsHorizontalScrollIndicator={false}>
+          {recommendations.map(item => (
+            <VerticalNewsCard
+              image={{uri: item.cover_image}}
+              headline={item.title}
+              onPress={() => navigateToArticle(navigation, item.id, item.slug)}
+              
+            />
+          ))}
+        </ScrollView>
+      </ScrollView>
+    </BottomSheetModalProvider>
+  );
+};
+
+export default NewsDetailPage;

+ 19 - 0
src/screens/News/NewsListPage.js

@@ -0,0 +1,19 @@
+import { StyleSheet, Text, View } from 'react-native'
+import React from 'react'
+
+const NewsListPage = props => {
+  
+    const {
+        navigation,
+        route
+    } = props
+    return (
+    <View>
+      <Text>NewsListPage</Text>
+    </View>
+  )
+}
+
+export default NewsListPage
+
+const styles = StyleSheet.create({})