SearchListPage.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import { FlatList, SafeAreaView, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
  2. import React, { useState } from 'react'
  3. import colors from '../../constants/colors'
  4. import { horizontalScale, moderateScale, verticalScale } from '../../constants/metrics'
  5. import { navigateToArticle, useConstructor } from '../../utils/functions'
  6. import IonIcon from 'react-native-vector-icons/Ionicons'
  7. import { Checkbox, IconButton, List, Modal, PaperProvider, Portal, ToggleButton } from 'react-native-paper'
  8. import { getArticlesBySearch} from '../../api/data'
  9. import NewscoutCenteredTitleHeader from '../../components/molecules/Header/NewscoutCenteredTitleHeader'
  10. import LoadingScreen from '../../components/organisms/Sections/LoadingScreen'
  11. import ThemedTextButton from '../../components/molecules/Buttons/ThemeTextButton'
  12. import fonts from '../../constants/fonts'
  13. import HorizontalNewsCardVariant from '../../components/molecules/Cards/HorizontalNewsCardVariant'
  14. const filterMapping = {
  15. "hash_tags": "tag",
  16. "source":"source",
  17. "category":"category"
  18. }
  19. const SearchListPage = props => {
  20. const {
  21. navigation,
  22. route
  23. } = props
  24. const params = route.params
  25. const [filtersData, setFiltersData] = useState({})
  26. const [currentFilter, setCurrentFilter] = useState("Category")
  27. const [pagesLoaded, setPagesLoaded] = useState(0)
  28. const [news, setNews] = useState([])
  29. const [isFiltersVisible, setFiltersVisible] = useState(false);
  30. const showFilters = () => setFiltersVisible(true);
  31. const hideFilters = () => setFiltersVisible(false);
  32. const styles = StyleSheet.create({
  33. pageContainer: {
  34. backgroundColor: colors().dominant
  35. },
  36. newsContainer: {
  37. backgroundColor: colors().dominant,
  38. minHeight: "100%",
  39. height: 'auto',
  40. paddingHorizontal: horizontalScale(16),
  41. flexDirection: 'row',
  42. flexWrap: 'wrap',
  43. gap: moderateScale(8),
  44. paddingVertical: verticalScale(16),
  45. numColumns: 2
  46. },
  47. filtersModalContainer: {
  48. padding: moderateScale(16),
  49. width: '100%',
  50. height: '100%',
  51. // marginHorizontal: horizontalScale(32),
  52. justifyContent: 'flex-start',
  53. backgroundColor: colors().dominant,
  54. },
  55. filtersHeader: {
  56. flexDirection: 'row',
  57. alignItems: 'center',
  58. justifyContent: 'space-between'
  59. },
  60. filterHeading: {
  61. fontFamily: fonts.type.semibold,
  62. color: colors().recessive,
  63. fontSize: moderateScale(20)
  64. },
  65. utilContainer: {
  66. flexDirection: 'row',
  67. gap: 8,
  68. alignItems: 'center'
  69. },
  70. filterPillContainer: {
  71. paddingVertical: verticalScale(8)
  72. },
  73. filterTitle: {
  74. fontFamily: fonts.type.semibold,
  75. color: colors().black,
  76. fontSize: moderateScale(16),
  77. paddingVertical: moderateScale(8)
  78. },
  79. filterPill: {
  80. borderRadius: moderateScale(18),
  81. paddingVertical: verticalScale(8),
  82. paddingHorizontal: horizontalScale(12),
  83. backgroundColor: colors().dominant,
  84. width: 'auto',
  85. },
  86. selectedFilterPill: {
  87. paddingVertical: verticalScale(8),
  88. paddingHorizontal: horizontalScale(8),
  89. // backgroundColor: colors().primaryColor,
  90. borderRadius: 0,
  91. width: 'auto',
  92. borderColor: colors().primaryColor,
  93. borderBottomWidth: 3
  94. },
  95. selectedPillText: {
  96. fontFamily: fonts.type.bold,
  97. fontSize: moderateScale(14),
  98. color: colors().recessive,
  99. },
  100. pillText: {
  101. fontFamily: fonts.type.semibold,
  102. fontSize: moderateScale(12),
  103. color: colors().recessive,
  104. },
  105. filterCheckboxes: {
  106. alignItems: 'flex-start',
  107. flexDirection:'column',
  108. gap: moderateScale(8),
  109. },
  110. listItemText: {
  111. fontFamily: fonts.type.medium,
  112. fontSize: moderateScale(14),
  113. color: colors().recessive
  114. },
  115. listItem: {
  116. paddingVertical: verticalScale(0)
  117. }
  118. })
  119. const applyFilters = () => {
  120. setNews([])
  121. hideFilters()
  122. setPagesLoaded(1)
  123. fetchSearchResults(params.query, 1, createFilters(filtersData))
  124. }
  125. const _clearFilters = (data) => setFiltersData(Object.entries(data).reduce((current,value) => ({...current,[value[0]]:value[1].reduce((prev,item) => ({...prev,[item.key]: false}),{})}),{}))
  126. const clearAllFilters = () => {
  127. _clearFilters(filtersData)
  128. }
  129. const fetchSearchResults = (search_text, page = 1, filters = "") => {
  130. getArticlesBySearch(search_text, page, filters)
  131. .then(res => {
  132. setNews(prev => [...prev, ...res.data.body.results])
  133. if (pagesLoaded <= 0){
  134. setCurrentFilter(Object.entries(res.data.body.filters)[0][0])
  135. _clearFilters(res.data.body.filters)
  136. setPagesLoaded(1)
  137. console.log(Object.entries(res.data.body.filters)[2][0])
  138. }
  139. })
  140. .catch(err => console.log(err))
  141. }
  142. const humanizeFilters = (text) => text.split("_").map(item => item[0].toUpperCase() + item.substring(1)).join(" ")
  143. const createFilters = (filterObject) => {
  144. let filterText = ""
  145. for (const obj of Object.entries(filterObject)) {
  146. for (const filter of Object.entries(obj[1])) {
  147. if (filter[1] === true) {
  148. filterText += `&${filterMapping[obj[0]]}=${filter[0]}`
  149. }
  150. }
  151. }
  152. console.log(filterText)
  153. return filterText
  154. }
  155. useConstructor(() => {
  156. fetchSearchResults(params.query, 1, createFilters(filtersData))
  157. })
  158. return (
  159. <PaperProvider>
  160. <SafeAreaView style={styles.pageContainer}>
  161. <Portal>
  162. <Modal visible={isFiltersVisible} onDismiss={hideFilters} contentContainerStyle={styles.filtersModalContainer}>
  163. <View style={styles.filtersHeader}>
  164. <Text style={styles.filterHeading}>Filters</Text>
  165. <View style={styles.utilContainer}>
  166. <ThemedTextButton theme={'primary-contained'} title={'Clear All'} buttonStyle={{ paddingVertical: verticalScale(6) }} onPress={clearAllFilters} />
  167. <IconButton
  168. icon="close"
  169. iconColor={colors().recessive}
  170. size={moderateScale(24)}
  171. onPress={hideFilters}
  172. style={{ alignSelf: 'flex-end' }}
  173. />
  174. </View>
  175. </View>
  176. <View>
  177. <ScrollView horizontal contentContainerStyle={styles.filterPillContainer}>
  178. <ToggleButton.Group
  179. onValueChange={value => {
  180. setCurrentFilter(value)
  181. }}
  182. >
  183. {
  184. Object.keys(filtersData).map(item =>
  185. <ToggleButton
  186. key={item}
  187. icon={() => (
  188. <Text
  189. style={[
  190. item === currentFilter
  191. ? styles.selectedPillText
  192. : styles.pillText,
  193. ]}>
  194. {humanizeFilters(item)}
  195. </Text>
  196. )}
  197. style={[
  198. item === currentFilter
  199. ? styles.selectedFilterPill
  200. : styles.filterPill,
  201. { marginHorizontal: horizontalScale(4) },
  202. ]}
  203. value={item}
  204. />)
  205. }
  206. </ToggleButton.Group>
  207. </ScrollView>
  208. </View>
  209. {
  210. Object.keys(filtersData).length <= 0 ? <LoadingScreen />
  211. :
  212. <FlatList
  213. contentContainerStyle={styles.filterCheckboxes}
  214. showsVerticalScrollIndicator={false}
  215. data={Object.entries(filtersData[currentFilter])}
  216. renderItem={filter => <List.Item
  217. key={filter.item[0]}
  218. style={styles.listItem}
  219. titleStyle={styles.listItemText}
  220. title={filter.item[0]}
  221. left={props => <Checkbox
  222. status={filtersData[currentFilter][filter.item[0]] === true ? 'checked' : 'unchecked'}
  223. onPress={() => {
  224. setFiltersData({ ...filtersData, [currentFilter]: { ...filtersData[currentFilter], [filter.item[0]]: !filtersData[currentFilter][filter.item[0]] } })
  225. console.log(filtersData[currentFilter][filter.item[0]])
  226. }}
  227. color={colors().secondaryColor}
  228. />}
  229. />}
  230. />}
  231. <ThemedTextButton onPress={applyFilters} theme={'secondary-contained'} title={'Apply'} buttonStyle={{ paddingVertical: verticalScale(8), marginTop: verticalScale(8) }} />
  232. </Modal>
  233. </Portal>
  234. <NewscoutCenteredTitleHeader title={"Search Results"} backButtonShown={true} onBackClick={() => navigation.goBack()}>
  235. <TouchableOpacity style={styles.filtersButton} onPress={showFilters}>
  236. <IonIcon name="filter" color={colors().primaryColor} size={moderateScale(24)} />
  237. </TouchableOpacity>
  238. </NewscoutCenteredTitleHeader>
  239. {
  240. news.length <= 0 ?
  241. <LoadingScreen />
  242. :
  243. <FlatList
  244. contentContainerStyle={styles.newsContainer}
  245. showsVerticalScrollIndicator={false}
  246. data={news}
  247. renderItem={(news) =>
  248. <HorizontalNewsCardVariant
  249. onPress={() => navigateToArticle(navigation, news.item.id, news.item.slug)}
  250. timestamp={news.item.published_on}
  251. headline={news.item.title}
  252. image={{ uri: news.item.cover_image }}
  253. category={news.item.category}
  254. tagline={news.item.id}
  255. />}
  256. keyExtractor={item => item.index}
  257. onEndReached={() => {
  258. setPagesLoaded(pagesLoaded + 1)
  259. fetchSearchResults(params.query,pagesLoaded + 1,createFilters(filtersData))
  260. }}
  261. onEndReachedThreshold={200}
  262. />
  263. }
  264. </SafeAreaView>
  265. </PaperProvider>
  266. )
  267. }
  268. export default SearchListPage
  269. const styles = StyleSheet.create({})