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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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 '../../utils/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. return [
  115. {
  116. title: '',
  117. data: generateData(fetchedData),
  118. },
  119. ];
  120. };
  121. /**
  122. * Callback used when clicking an article in the list.
  123. * It opens the modal to show detailed information about the article
  124. *
  125. * @param item The article pressed
  126. */
  127. const onListItemPress = (item: PlanexGroupType) => {
  128. navigation.navigate('planex', {
  129. screen: 'index',
  130. params: { group: item },
  131. });
  132. };
  133. /**
  134. * Callback used when the user clicks on the favorite button
  135. *
  136. * @param item The item to add/remove from favorites
  137. */
  138. const onListFavoritePress = useCallback(
  139. (group: PlanexGroupType) => {
  140. const removeGroupFromFavorites = (g: PlanexGroupType) => {
  141. setFavoriteGroups(favoriteGroups.filter((f) => f.id !== g.id));
  142. };
  143. const addGroupToFavorites = (g: PlanexGroupType) => {
  144. setFavoriteGroups([...favoriteGroups, g].sort(sortName));
  145. };
  146. if (favoriteGroups.some((f) => f.id === group.id)) {
  147. removeGroupFromFavorites(group);
  148. } else {
  149. addGroupToFavorites(group);
  150. }
  151. },
  152. [favoriteGroups]
  153. );
  154. useEffect(() => {
  155. AsyncStorageManager.set(
  156. AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
  157. favoriteGroups
  158. );
  159. }, [favoriteGroups]);
  160. /**
  161. * Generates the dataset to be used in the FlatList.
  162. * This improves formatting of group names, sorts alphabetically the categories, and adds favorites at the top.
  163. *
  164. * @param fetchedData The raw data fetched from the server
  165. * @returns {[]}
  166. */
  167. const generateData = (
  168. fetchedData: PlanexGroupsType | undefined
  169. ): Array<PlanexGroupCategoryType> => {
  170. const data: Array<PlanexGroupCategoryType> = [];
  171. if (fetchedData) {
  172. // Convert the object into an array
  173. Object.values(fetchedData).forEach(
  174. (category: PlanexGroupCategoryType) => {
  175. const content: Array<PlanexGroupType> = [];
  176. // Filter groups matching the search query
  177. category.content.forEach((g: PlanexGroupType) => {
  178. if (stringMatchQuery(g.name, currentSearchString)) {
  179. content.push(g);
  180. }
  181. });
  182. // Only add categories with groups matching the query
  183. if (content.length > 0) {
  184. data.push({
  185. id: category.id,
  186. name: category.name,
  187. content: content,
  188. });
  189. }
  190. }
  191. );
  192. data.sort(sortName);
  193. // Add the favorites at the top
  194. data.unshift({
  195. name: i18n.t('screens.planex.favorites.title'),
  196. id: 0,
  197. content: favoriteGroups,
  198. });
  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;