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 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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 * as React from 'react';
  20. import { Platform } from 'react-native';
  21. import i18n from 'i18n-js';
  22. import { Searchbar } from 'react-native-paper';
  23. import { StackNavigationProp } from '@react-navigation/stack';
  24. import { stringMatchQuery } from '../../utils/Search';
  25. import WebSectionList from '../../components/Screens/WebSectionList';
  26. import GroupListAccordion from '../../components/Lists/PlanexGroups/GroupListAccordion';
  27. import AsyncStorageManager from '../../managers/AsyncStorageManager';
  28. import Urls from '../../constants/Urls';
  29. import { readData } from '../../utils/WebData';
  30. export type PlanexGroupType = {
  31. name: string;
  32. id: number;
  33. };
  34. export type PlanexGroupCategoryType = {
  35. name: string;
  36. id: number;
  37. content: Array<PlanexGroupType>;
  38. };
  39. type PropsType = {
  40. navigation: StackNavigationProp<any>;
  41. };
  42. type StateType = {
  43. currentSearchString: string;
  44. favoriteGroups: Array<PlanexGroupType>;
  45. };
  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. /**
  59. * Class defining planex group selection screen.
  60. */
  61. class GroupSelectionScreen extends React.Component<PropsType, StateType> {
  62. constructor(props: PropsType) {
  63. super(props);
  64. this.state = {
  65. currentSearchString: '',
  66. favoriteGroups: AsyncStorageManager.getObject(
  67. AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key
  68. ),
  69. };
  70. }
  71. /**
  72. * Creates the header content
  73. */
  74. componentDidMount() {
  75. const { navigation } = this.props;
  76. navigation.setOptions({
  77. headerTitle: this.getSearchBar,
  78. headerBackTitleVisible: false,
  79. headerTitleContainerStyle:
  80. Platform.OS === 'ios'
  81. ? { marginHorizontal: 0, width: '70%' }
  82. : { marginHorizontal: 0, right: 50, left: 50 },
  83. });
  84. }
  85. /**
  86. * Gets the header search bar
  87. *
  88. * @return {*}
  89. */
  90. getSearchBar = () => {
  91. return (
  92. // @ts-ignore
  93. <Searchbar
  94. placeholder={i18n.t('screens.proximo.search')}
  95. onChangeText={this.onSearchStringChange}
  96. />
  97. );
  98. };
  99. /**
  100. * Gets a render item for the given article
  101. *
  102. * @param item The article to render
  103. * @return {*}
  104. */
  105. getRenderItem = ({ item }: { item: PlanexGroupCategoryType }) => {
  106. const { currentSearchString, favoriteGroups } = this.state;
  107. if (
  108. this.shouldDisplayAccordion(item) ||
  109. (item.id === 0 && item.content.length === 0)
  110. ) {
  111. return (
  112. <GroupListAccordion
  113. item={item}
  114. favorites={[...favoriteGroups]}
  115. onGroupPress={this.onListItemPress}
  116. onFavoritePress={this.onListFavoritePress}
  117. currentSearchString={currentSearchString}
  118. />
  119. );
  120. }
  121. return null;
  122. };
  123. /**
  124. * Creates the dataset to be used in the FlatList
  125. *
  126. * @param fetchedData
  127. * @return {*}
  128. * */
  129. createDataset = (
  130. fetchedData:
  131. | {
  132. [key: string]: PlanexGroupCategoryType;
  133. }
  134. | undefined
  135. ): Array<{ title: string; data: Array<PlanexGroupCategoryType> }> => {
  136. return [
  137. {
  138. title: '',
  139. data: this.generateData(fetchedData),
  140. },
  141. ];
  142. };
  143. /**
  144. * Callback used when the search changes
  145. *
  146. * @param str The new search string
  147. */
  148. onSearchStringChange = (str: string) => {
  149. this.setState({ currentSearchString: str });
  150. };
  151. /**
  152. * Callback used when clicking an article in the list.
  153. * It opens the modal to show detailed information about the article
  154. *
  155. * @param item The article pressed
  156. */
  157. onListItemPress = (item: PlanexGroupType) => {
  158. const { navigation } = this.props;
  159. navigation.navigate('planex', {
  160. screen: 'index',
  161. params: { group: item },
  162. });
  163. };
  164. /**
  165. * Callback used when the user clicks on the favorite button
  166. *
  167. * @param item The item to add/remove from favorites
  168. */
  169. onListFavoritePress = (item: PlanexGroupType) => {
  170. this.updateGroupFavorites(item);
  171. };
  172. /**
  173. * Checks if the given group is in the favorites list
  174. *
  175. * @param group The group to check
  176. * @returns {boolean}
  177. */
  178. isGroupInFavorites(group: PlanexGroupType): boolean {
  179. let isFav = false;
  180. const { favoriteGroups } = this.state;
  181. favoriteGroups.forEach((favGroup: PlanexGroupType) => {
  182. if (group.id === favGroup.id) {
  183. isFav = true;
  184. }
  185. });
  186. return isFav;
  187. }
  188. /**
  189. * Adds or removes the given group to the favorites list, depending on whether it is already in it or not.
  190. * Favorites are then saved in user preferences
  191. *
  192. * @param group The group to add/remove to favorites
  193. */
  194. updateGroupFavorites(group: PlanexGroupType) {
  195. if (this.isGroupInFavorites(group)) {
  196. this.removeGroupFromFavorites(group);
  197. } else {
  198. this.addGroupToFavorites(group);
  199. }
  200. }
  201. /**
  202. * Checks whether to display the given group category, depending on user search query
  203. *
  204. * @param item The group category
  205. * @returns {boolean}
  206. */
  207. shouldDisplayAccordion(item: PlanexGroupCategoryType): boolean {
  208. const { currentSearchString } = this.state;
  209. let shouldDisplay = false;
  210. for (let i = 0; i < item.content.length; i += 1) {
  211. if (stringMatchQuery(item.content[i].name, currentSearchString)) {
  212. shouldDisplay = true;
  213. break;
  214. }
  215. }
  216. return shouldDisplay;
  217. }
  218. /**
  219. * Generates the dataset to be used in the FlatList.
  220. * This improves formatting of group names, sorts alphabetically the categories, and adds favorites at the top.
  221. *
  222. * @param fetchedData The raw data fetched from the server
  223. * @returns {[]}
  224. */
  225. generateData(
  226. fetchedData:
  227. | {
  228. [key: string]: PlanexGroupCategoryType;
  229. }
  230. | undefined
  231. ): Array<PlanexGroupCategoryType> {
  232. const { favoriteGroups } = this.state;
  233. const data: Array<PlanexGroupCategoryType> = [];
  234. if (fetchedData) {
  235. Object.values(fetchedData).forEach(
  236. (category: PlanexGroupCategoryType) => {
  237. data.push(category);
  238. }
  239. );
  240. data.sort(sortName);
  241. data.unshift({
  242. name: i18n.t('screens.planex.favorites'),
  243. id: 0,
  244. content: favoriteGroups,
  245. });
  246. }
  247. return data;
  248. }
  249. /**
  250. * Removes the given group from the favorites
  251. *
  252. * @param group The group to remove from the array
  253. */
  254. removeGroupFromFavorites(group: PlanexGroupType) {
  255. this.setState((prevState: StateType): {
  256. favoriteGroups: Array<PlanexGroupType>;
  257. } => {
  258. const { favoriteGroups } = prevState;
  259. for (let i = 0; i < favoriteGroups.length; i += 1) {
  260. if (group.id === favoriteGroups[i].id) {
  261. favoriteGroups.splice(i, 1);
  262. break;
  263. }
  264. }
  265. AsyncStorageManager.set(
  266. AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
  267. favoriteGroups
  268. );
  269. return { favoriteGroups };
  270. });
  271. }
  272. /**
  273. * Adds the given group to favorites
  274. *
  275. * @param group The group to add to the array
  276. */
  277. addGroupToFavorites(group: PlanexGroupType) {
  278. this.setState((prevState: StateType): {
  279. favoriteGroups: Array<PlanexGroupType>;
  280. } => {
  281. const { favoriteGroups } = prevState;
  282. favoriteGroups.push(group);
  283. favoriteGroups.sort(sortName);
  284. AsyncStorageManager.set(
  285. AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
  286. favoriteGroups
  287. );
  288. return { favoriteGroups };
  289. });
  290. }
  291. render() {
  292. const { state } = this;
  293. return (
  294. <WebSectionList
  295. request={() =>
  296. readData<{ [key: string]: PlanexGroupCategoryType }>(
  297. Urls.planex.groups
  298. )
  299. }
  300. createDataset={this.createDataset}
  301. refreshOnFocus={true}
  302. renderItem={this.getRenderItem}
  303. updateData={state.currentSearchString + state.favoriteGroups.length}
  304. />
  305. );
  306. }
  307. }
  308. export default GroupSelectionScreen;