Application Android et IOS pour l'amicale des élèves
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.4KB

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