SearchListPage.js 13 KB

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