SearchPage.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. import { ScrollView, StyleSheet, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native'
  2. import React, { useState } from 'react'
  3. import { SafeAreaView } from 'react-native-safe-area-context'
  4. import NewscoutTitleHeader from '../../components/molecules/Header/NewscoutTitleHeader'
  5. import colors from '../../constants/colors'
  6. import { Checkbox, IconButton, List, Modal, PaperProvider, Portal, TextInput, ToggleButton } from 'react-native-paper'
  7. import { horizontalScale, moderateScale, screenWidth, verticalScale } from '../../constants/metrics'
  8. import IonIcon from 'react-native-vector-icons/Ionicons'
  9. import fonts from '../../constants/fonts'
  10. import { navigateToArticle, navigateToListViewPage, useConstructor } from '../../constants/functions'
  11. import { getArticlesBySearch, getCategories, getMenus } from '../../api/data'
  12. import LoadingScreen from '../../components/organisms/Sections/LoadingScreen'
  13. import { PAGINATE_BY } from '../../api/urls'
  14. import HorizontalNewsCardVariant from '../../components/molecules/Cards/HorizontalNewsCardVariant'
  15. import ThemedTextButton from '../../components/molecules/Buttons/ThemeTextButton'
  16. const SearchPage = props => {
  17. const {
  18. navigation,
  19. route
  20. } = props
  21. const [isSearching, setSearching] = useState(false)
  22. const [categories, setCategories] = useState([])
  23. const [searchText, setSearchText] = useState("")
  24. const [suggestedNews, setSuggestedNews] = useState([])
  25. const [filtersData, setFiltersData] = useState({})
  26. const [currentFilter, setCurrentFilter] = useState("Category")
  27. // * Filters Modal
  28. const [isFiltersVisible, setFiltersVisible] = useState(false);
  29. const showFilters = () => setFiltersVisible(true);
  30. const hideFilters = () => setFiltersVisible(false);
  31. const styles = StyleSheet.create({
  32. container: {
  33. backgroundColor: colors().dominant,
  34. minHeight: '100%',
  35. maxHeight: 'auto'
  36. },
  37. inputTextContainer: {
  38. backgroundColor: colors().dominant,
  39. paddingHorizontal: horizontalScale(16),
  40. paddingBottom: verticalScale(16),
  41. flexDirection: 'row',
  42. alignItems: 'center',
  43. // gap: horizontalScale(16),
  44. maxWidth: '100%'
  45. },
  46. categoryContainer: {
  47. padding: verticalScale(16),
  48. flexDirection: 'row',
  49. flexWrap: 'wrap',
  50. gap: moderateScale(16),
  51. },
  52. category: {
  53. height: 170,
  54. width: 170,
  55. backgroundColor: colors().secondaryColor,
  56. borderRadius: moderateScale(8),
  57. padding: moderateScale(11),
  58. justifyContent: 'flex-end',
  59. },
  60. categoryText: {
  61. fontFamily: fonts.type.semibold,
  62. alignSelf: 'flex-end',
  63. color: colors().white,
  64. fontSize: moderateScale(14),
  65. maxWidth: "80%",
  66. textAlign: 'right'
  67. },
  68. filtersButton: {
  69. backgroundColor: colors().dominant,
  70. paddingRight: horizontalScale(8),
  71. paddingLeft: horizontalScale(16),
  72. alignItems: 'center',
  73. justifyContent: 'center',
  74. },
  75. filtersModalContainer: {
  76. padding: moderateScale(16),
  77. width: '100%',
  78. height: '100%',
  79. // marginHorizontal: horizontalScale(32),
  80. justifyContent: 'flex-start',
  81. backgroundColor: colors().dominant,
  82. },
  83. filtersHeader: {
  84. flexDirection: 'row',
  85. alignItems: 'center',
  86. justifyContent: 'space-between'
  87. },
  88. filterHeading: {
  89. fontFamily: fonts.type.semibold,
  90. color: colors().recessive,
  91. fontSize: moderateScale(20)
  92. },
  93. utilContainer: {
  94. flexDirection: 'row',
  95. gap: 8,
  96. alignItems: 'center'
  97. },
  98. filterPillContainer: {
  99. paddingVertical: verticalScale(8)
  100. },
  101. filterTitle: {
  102. fontFamily: fonts.type.semibold,
  103. color: colors().black,
  104. fontSize: moderateScale(16),
  105. paddingVertical: moderateScale(8)
  106. },
  107. filterPill: {
  108. borderRadius: moderateScale(18),
  109. paddingVertical: verticalScale(8),
  110. paddingHorizontal: horizontalScale(12),
  111. backgroundColor: colors().dominant,
  112. width: 'auto',
  113. },
  114. selectedFilterPill: {
  115. paddingVertical: verticalScale(8),
  116. paddingHorizontal: horizontalScale(16),
  117. backgroundColor: colors().primaryColor,
  118. borderRadius: moderateScale(18),
  119. width: 'auto',
  120. },
  121. selectedPillText: {
  122. fontFamily: fonts.type.semibold,
  123. fontSize: moderateScale(12),
  124. color: colors().white,
  125. },
  126. pillText: {
  127. fontFamily: fonts.type.semibold,
  128. fontSize: moderateScale(12),
  129. color: colors().recessive,
  130. },
  131. filterCheckboxes: {
  132. alignItems: 'flex-start'
  133. },
  134. listItemText:{
  135. fontFamily: fonts.type.medium,
  136. fontSize: moderateScale(14)
  137. },
  138. listItem:{
  139. paddingVertical: verticalScale(0)
  140. }
  141. })
  142. const fetchCategories = () => {
  143. getCategories().then(res => setCategories(res.data.body.results)).catch(err => console.log(err))
  144. }
  145. const fetchSuggestions = (search_text) => {
  146. getArticlesBySearch(search_text)
  147. .then(res => setSuggestedNews(res.data.body.results.slice(0, PAGINATE_BY)))
  148. .catch(err => console.log(err))
  149. }
  150. const fetchFilters = () => {
  151. getMenus()
  152. .then(res => {
  153. let filtersPayload = res.data.body.results
  154. //Reduce Submenus into a object
  155. setFiltersData(prev => ({ ...prev, "Category": filtersPayload.flatMap(item => item.heading.submenu.map(category => category.name)).reduce((current,value) => ({...current,[value]:false}),{})}))
  156. setFiltersData(prev => ({ ...prev, "Source": filtersPayload.flatMap(item => item.heading.submenu.map(category => category.name)).reduce((current,value) => ({...current,[value]:false}),{})}))
  157. setFiltersData(prev => ({ ...prev, "Hash Tags": filtersPayload.flatMap(item => item.heading.submenu.map(category => category.hash_tags.map(tag => tag.name))).reduce((current,value) => ({...current,[value]:false}),{})}))
  158. })
  159. }
  160. useConstructor(() => {
  161. fetchCategories()
  162. fetchFilters()
  163. })
  164. const onChangeText = (text) => {
  165. if (text.length > 0) {
  166. console.warn(text)
  167. setSearching(true)
  168. setSearchText(text)
  169. setSuggestedNews([])
  170. fetchSuggestions(text)
  171. } else {
  172. console.warn(text)
  173. setSearching(false)
  174. setSearchText(text)
  175. setSuggestedNews([])
  176. }
  177. }
  178. return (
  179. <PaperProvider>
  180. <SafeAreaView style={styles.container}>
  181. <Portal>
  182. <Modal visible={isFiltersVisible} onDismiss={hideFilters} contentContainerStyle={styles.filtersModalContainer}>
  183. <View style={styles.filtersHeader}>
  184. <Text style={styles.filterHeading}>Filters</Text>
  185. <View style={styles.utilContainer}>
  186. <ThemedTextButton theme={'primary-contained'} title={'Clear All'} buttonStyle={{ paddingVertical: verticalScale(6) }} />
  187. <IconButton
  188. icon="close"
  189. iconColor={colors().recessive}
  190. size={moderateScale(24)}
  191. onPress={hideFilters}
  192. style={{ alignSelf: 'flex-end' }}
  193. />
  194. </View>
  195. </View>
  196. <View>
  197. <ScrollView horizontal contentContainerStyle={styles.filterPillContainer}>
  198. <ToggleButton.Group
  199. onValueChange={value => {
  200. setCurrentFilter(value)
  201. }}
  202. >
  203. {
  204. Object.keys(filtersData).map(item =>
  205. <ToggleButton
  206. key={item}
  207. icon={() => (
  208. <Text
  209. style={[
  210. item === currentFilter
  211. ? styles.selectedPillText
  212. : styles.pillText,
  213. ]}>
  214. {item}
  215. </Text>
  216. )}
  217. style={[
  218. item === currentFilter
  219. ? styles.selectedFilterPill
  220. : styles.filterPill,
  221. { marginHorizontal: horizontalScale(4) },
  222. ]}
  223. value={item}
  224. />)
  225. }
  226. </ToggleButton.Group>
  227. </ScrollView>
  228. </View>
  229. <ScrollView contentContainerStyle={styles.filterCheckboxes} showsVerticalScrollIndicator={false}>
  230. {
  231. // filtersData[currentFilter]
  232. Object.keys(filtersData).length <= 0 ? <LoadingScreen />
  233. : Object.entries(filtersData[currentFilter]).map(item => {
  234. return (<List.Item
  235. style={styles.listItem}
  236. titleStyle={styles.listItemText}
  237. title={item[0]}
  238. left={props => <Checkbox
  239. status={filtersData[currentFilter][item[0]] === true ? 'checked' : 'unchecked'}
  240. onPress={() =>
  241. setFiltersData({ ...filtersData, [currentFilter]: { ...filtersData[currentFilter], [item[0]]: !filtersData[currentFilter][item[0]] } })}
  242. // setSelectedTopics({...filtersData,[item.heading.name]: !selectedTopics[item.heading.name]})}
  243. color={colors().secondaryColor}
  244. />}
  245. />)
  246. }
  247. )
  248. }
  249. </ScrollView>
  250. </Modal>
  251. </Portal>
  252. <ScrollView>
  253. <NewscoutTitleHeader title={"Search"} />
  254. <View style={styles.inputTextContainer}>
  255. <TextInput
  256. editable
  257. mode='outlined'
  258. placeholder='Search Text'
  259. placeholderTextColor={colors().grayShade_300}
  260. dense
  261. style={{
  262. backgroundColor: colors().grayShade_500,
  263. paddingVertical: moderateScale(4),
  264. // width: '100%',
  265. flex: 1
  266. }}
  267. contentStyle={{
  268. fontSize: moderateScale(16),
  269. fontFamily: fonts.type.medium,
  270. // height: moderateScale(16),
  271. }}
  272. outlineStyle={{
  273. borderColor: colors().dominant_variant,
  274. borderRadius: moderateScale(8),
  275. borderWidth: moderateScale(1),
  276. }}
  277. // underlineStyle={{backgroundColor: colors().dominant}}
  278. left={() => <Text>fdsfdss</Text>}
  279. right={() => <TouchableOpacity onPress={() => navigateToListViewPage(navigation, 'search', searchText)}><IonIcon name="search" color={colors().grayShade_200} size={moderateScale(8)} /></TouchableOpacity>}
  280. onChangeText={onChangeText}
  281. />
  282. <TouchableOpacity style={styles.filtersButton} onPress={showFilters}>
  283. <IonIcon name="filter" color={colors().recessive_variant} size={moderateScale(24)} />
  284. </TouchableOpacity>
  285. </View>
  286. {
  287. isSearching === false ?
  288. <View style={styles.categoryContainer}>
  289. {categories.map((item) =>
  290. <TouchableOpacity onPress={() => navigateToListViewPage(navigation, "category", item.heading.name)}>
  291. <View style={styles.category}>
  292. <Text style={styles.categoryText}>{item.heading.name}</Text>
  293. </View>
  294. </TouchableOpacity>
  295. )}
  296. </View>
  297. : suggestedNews.length <= 0 ?
  298. <View style={{ alignItems: 'center', justifyContent: 'center', }}><LoadingScreen containerHeight={600} /></View> : <View style={styles.categoryContainer}>
  299. {suggestedNews.map((item) =>
  300. <HorizontalNewsCardVariant
  301. onPress={() => navigateToArticle(navigation, item.id, item.slug)}
  302. timestamp={item.published_on}
  303. headline={item.title}
  304. image={{ uri: item.cover_image }}
  305. category={item.category}
  306. tagline={item.id}
  307. />
  308. )}
  309. </View>
  310. }
  311. </ScrollView>
  312. </SafeAreaView>
  313. </PaperProvider>
  314. )
  315. }
  316. export default SearchPage