Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GroupSelectionScreen.tsx 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /*
  2. * Copyright (c) 2019 - 2020 Arnaud Vergnet.
  3. *
  4. * This file is part of Campus INSAT.
  5. *
  6. * Campus INSAT is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Campus INSAT is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import React, {
  20. useCallback,
  21. useEffect,
  22. useLayoutEffect,
  23. useState,
  24. } from 'react';
  25. import { Platform } from 'react-native';
  26. import i18n from 'i18n-js';
  27. import { Searchbar } from 'react-native-paper';
  28. import { stringMatchQuery } from '../../utils/Search';
  29. import WebSectionList from '../../components/Screens/WebSectionList';
  30. import GroupListAccordion from '../../components/Lists/PlanexGroups/GroupListAccordion';
  31. import AsyncStorageManager from '../../managers/AsyncStorageManager';
  32. import Urls from '../../constants/Urls';
  33. import { readData } from '../../utils/WebData';
  34. import { useNavigation } from '@react-navigation/core';
  35. import { useCachedPlanexGroups } from '../../context/cacheContext';
  36. export type PlanexGroupType = {
  37. name: string;
  38. id: number;
  39. };
  40. export type PlanexGroupCategoryType = {
  41. name: string;
  42. id: number;
  43. content: Array<PlanexGroupType>;
  44. };
  45. export type PlanexGroupsType = { [key: string]: PlanexGroupCategoryType };
  46. function sortName(
  47. a: PlanexGroupType | PlanexGroupCategoryType,
  48. b: PlanexGroupType | PlanexGroupCategoryType
  49. ): number {
  50. if (a.name.toLowerCase() < b.name.toLowerCase()) {
  51. return -1;
  52. }
  53. if (a.name.toLowerCase() > b.name.toLowerCase()) {
  54. return 1;
  55. }
  56. return 0;
  57. }
  58. function GroupSelectionScreen() {
  59. const navigation = useNavigation();
  60. const { groups, setGroups } = useCachedPlanexGroups();
  61. const [currentSearchString, setCurrentSearchString] = useState('');
  62. const [favoriteGroups, setFavoriteGroups] = useState<Array<PlanexGroupType>>(
  63. AsyncStorageManager.getObject(
  64. AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key
  65. )
  66. );
  67. useLayoutEffect(() => {
  68. navigation.setOptions({
  69. headerTitle: getSearchBar,
  70. headerBackTitleVisible: false,
  71. headerTitleContainerStyle:
  72. Platform.OS === 'ios'
  73. ? { marginHorizontal: 0, width: '70%' }
  74. : { marginHorizontal: 0, right: 50, left: 50 },
  75. });
  76. }, [navigation]);
  77. const getSearchBar = () => {
  78. return (
  79. // @ts-ignore
  80. <Searchbar
  81. placeholder={i18n.t('screens.proximo.search')}
  82. onChangeText={setCurrentSearchString}
  83. />
  84. );
  85. };
  86. /**
  87. * Gets a render item for the given article
  88. *
  89. * @param item The article to render
  90. * @return {*}
  91. */
  92. const getRenderItem = ({ item }: { item: PlanexGroupCategoryType }) => (
  93. <GroupListAccordion
  94. item={item}
  95. favorites={[...favoriteGroups]}
  96. onGroupPress={onListItemPress}
  97. onFavoritePress={onListFavoritePress}
  98. currentSearchString={currentSearchString}
  99. />
  100. );
  101. /**
  102. * Creates the dataset to be used in the FlatList
  103. *
  104. * @param fetchedData
  105. * @return {*}
  106. * */
  107. const createDataset = (
  108. fetchedData:
  109. | {
  110. [key: string]: PlanexGroupCategoryType;
  111. }
  112. | undefined
  113. ): Array<{ title: string; data: Array<PlanexGroupCategoryType> }> => {
  114. if (fetchedData) {
  115. return [
  116. {
  117. title: '',
  118. data: generateData(fetchedData),
  119. },
  120. ];
  121. } else {
  122. return [];
  123. }
  124. };
  125. /**
  126. * Callback used when clicking an article in the list.
  127. * It opens the modal to show detailed information about the article
  128. *
  129. * @param item The article pressed
  130. */
  131. const onListItemPress = (item: PlanexGroupType) => {
  132. navigation.navigate('planex', {
  133. screen: 'index',
  134. params: { group: item },
  135. });
  136. };
  137. /**
  138. * Callback used when the user clicks on the favorite button
  139. *
  140. * @param item The item to add/remove from favorites
  141. */
  142. const onListFavoritePress = useCallback(
  143. (group: PlanexGroupType) => {
  144. const removeGroupFromFavorites = (g: PlanexGroupType) => {
  145. setFavoriteGroups(favoriteGroups.filter((f) => f.id !== g.id));
  146. };
  147. const addGroupToFavorites = (g: PlanexGroupType) => {
  148. setFavoriteGroups([...favoriteGroups, g].sort(sortName));
  149. };
  150. if (favoriteGroups.some((f) => f.id === group.id)) {
  151. removeGroupFromFavorites(group);
  152. } else {
  153. addGroupToFavorites(group);
  154. }
  155. },
  156. [favoriteGroups]
  157. );
  158. useEffect(() => {
  159. AsyncStorageManager.set(
  160. AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
  161. favoriteGroups
  162. );
  163. }, [favoriteGroups]);
  164. /**
  165. * Generates the dataset to be used in the FlatList.
  166. * This improves formatting of group names, sorts alphabetically the categories, and adds favorites at the top.
  167. *
  168. * @param fetchedData The raw data fetched from the server
  169. * @returns {[]}
  170. */
  171. const generateData = (
  172. fetchedData: PlanexGroupsType
  173. ): Array<PlanexGroupCategoryType> => {
  174. const data: Array<PlanexGroupCategoryType> = [];
  175. // Convert the object into an array
  176. Object.values(fetchedData).forEach((category: PlanexGroupCategoryType) => {
  177. const content: Array<PlanexGroupType> = [];
  178. // Filter groups matching the search query
  179. category.content.forEach((g: PlanexGroupType) => {
  180. if (stringMatchQuery(g.name, currentSearchString)) {
  181. content.push(g);
  182. }
  183. });
  184. // Only add categories with groups matching the query
  185. if (content.length > 0) {
  186. data.push({
  187. id: category.id,
  188. name: category.name,
  189. content: content,
  190. });
  191. }
  192. });
  193. data.sort(sortName);
  194. // Add the favorites at the top
  195. data.unshift({
  196. name: i18n.t('screens.planex.favorites.title'),
  197. id: 0,
  198. content: favoriteGroups,
  199. });
  200. return data;
  201. };
  202. return (
  203. <WebSectionList
  204. request={() => readData<PlanexGroupsType>(Urls.planex.groups)}
  205. createDataset={createDataset}
  206. refreshOnFocus={true}
  207. renderItem={getRenderItem}
  208. extraData={currentSearchString + favoriteGroups.length}
  209. cache={groups}
  210. onCacheUpdate={setGroups}
  211. />
  212. );
  213. }
  214. export default GroupSelectionScreen;