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.

HomeScreen.js 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. // @flow
  2. import * as React from 'react';
  3. import {TouchableOpacity, View} from 'react-native';
  4. import i18n from "i18n-js";
  5. import Autolink from 'react-native-autolink';
  6. import ThemeManager from "../utils/ThemeManager";
  7. import DashboardItem from "../components/DashboardItem";
  8. import * as WebBrowser from 'expo-web-browser';
  9. import WebSectionList from "../components/WebSectionList";
  10. import {Avatar, Button, Card, Text} from 'react-native-paper';
  11. import FeedItem from "../components/FeedItem";
  12. import SquareDashboardItem from "../components/SquareDashboardItem";
  13. // import DATA from "../dashboard_data.json";
  14. const NAME_AMICALE = 'Amicale INSA Toulouse';
  15. const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/dashboard/dashboard_data.json";
  16. const SECTIONS_ID = [
  17. 'dashboard',
  18. 'news_feed'
  19. ];
  20. const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds
  21. const CARD_BORDER_RADIUS = 10;
  22. type Props = {
  23. navigation: Object,
  24. }
  25. /**
  26. * Class defining the app's home screen
  27. */
  28. export default class HomeScreen extends React.Component<Props> {
  29. onProxiwashClick: Function;
  30. onTutorInsaClick: Function;
  31. onMenuClick: Function;
  32. onProximoClick: Function;
  33. getRenderItem: Function;
  34. createDataset: Function;
  35. constructor() {
  36. super();
  37. this.onProxiwashClick = this.onProxiwashClick.bind(this);
  38. this.onTutorInsaClick = this.onTutorInsaClick.bind(this);
  39. this.onMenuClick = this.onMenuClick.bind(this);
  40. this.onProximoClick = this.onProximoClick.bind(this);
  41. this.getRenderItem = this.getRenderItem.bind(this);
  42. this.createDataset = this.createDataset.bind(this);
  43. }
  44. /**
  45. * Converts a dateString using Unix Timestamp to a formatted date
  46. * @param dateString {string} The Unix Timestamp representation of a date
  47. * @return {string} The formatted output date
  48. */
  49. static getFormattedDate(dateString: string) {
  50. let date = new Date(Number.parseInt(dateString) * 1000);
  51. return date.toLocaleString();
  52. }
  53. onProxiwashClick() {
  54. this.props.navigation.navigate('Proxiwash');
  55. }
  56. onTutorInsaClick() {
  57. WebBrowser.openBrowserAsync("https://www.etud.insa-toulouse.fr/~tutorinsa/");
  58. }
  59. onProximoClick() {
  60. this.props.navigation.navigate('Proximo');
  61. }
  62. onMenuClick() {
  63. this.props.navigation.navigate('SelfMenuScreen');
  64. }
  65. getKeyExtractor(item: Object) {
  66. return item !== undefined ? item.id : undefined;
  67. }
  68. createDataset(fetchedData: Object) {
  69. // fetchedData = DATA;
  70. let newsData = [];
  71. let dashboardData = [];
  72. if (fetchedData['news_feed'] !== undefined)
  73. newsData = fetchedData['news_feed']['data'];
  74. if (fetchedData['dashboard'] !== undefined)
  75. dashboardData = this.generateDashboardDataset(fetchedData['dashboard']);
  76. return [
  77. {
  78. title: '',
  79. data: dashboardData,
  80. extraData: super.state,
  81. keyExtractor: this.getKeyExtractor,
  82. id: SECTIONS_ID[0]
  83. },
  84. {
  85. title: i18n.t('homeScreen.newsFeed'),
  86. data: newsData,
  87. extraData: super.state,
  88. keyExtractor: this.getKeyExtractor,
  89. id: SECTIONS_ID[1]
  90. }
  91. ];
  92. }
  93. generateDashboardDataset(dashboardData: Object) {
  94. let dataset = [
  95. {
  96. id: 'event',
  97. content: undefined
  98. },
  99. {
  100. id: 'middle',
  101. content: [{}, {}]
  102. },
  103. {
  104. id: 'bottom',
  105. content: [{}, {}]
  106. },
  107. ];
  108. for (let [key, value] of Object.entries(dashboardData)) {
  109. switch (key) {
  110. case 'today_events':
  111. dataset[0]['content'] = value;
  112. break;
  113. case 'available_machines':
  114. dataset[1]['content'][0] = {id: key, data: value};
  115. break;
  116. case 'available_tutorials':
  117. dataset[1]['content'][1] = {id: key, data: value};
  118. break;
  119. case 'proximo_articles':
  120. dataset[2]['content'][0] = {id: key, data: value};
  121. break;
  122. case 'today_menu':
  123. dataset[2]['content'][1] = {id: key, data: value};
  124. break;
  125. }
  126. }
  127. return dataset
  128. }
  129. getDashboardItem(item: Object) {
  130. let content = item['content'];
  131. if (item['id'] === 'event')
  132. return this.getDashboardEventItem(content);
  133. else if (item['id'] === 'middle')
  134. return this.getDashboardMiddleItem(content);
  135. else
  136. return this.getDashboardBottomItem(content);
  137. }
  138. /**
  139. * Convert the date string given by in the event list json to a date object
  140. * @param dateString
  141. * @return {Date}
  142. */
  143. stringToDate(dateString: ?string): ?Date {
  144. let date = new Date();
  145. if (dateString === undefined || dateString === null)
  146. date = undefined;
  147. else if (dateString.split(' ').length > 1) {
  148. let timeStr = dateString.split(' ')[1];
  149. date.setHours(parseInt(timeStr.split(':')[0]), parseInt(timeStr.split(':')[1]), 0);
  150. } else
  151. date = undefined;
  152. return date;
  153. }
  154. /**
  155. * Get the time limit depending on the current day:
  156. * 17:30 for every day of the week except for thursday 11:30
  157. * 00:00 on weekends
  158. */
  159. getTodayEventTimeLimit() {
  160. let now = new Date();
  161. if (now.getDay() === 4) // Thursday
  162. now.setHours(11, 30, 0);
  163. else if (now.getDay() === 6 || now.getDay() === 0) // Weekend
  164. now.setHours(0, 0, 0);
  165. else
  166. now.setHours(17, 30, 0);
  167. return now;
  168. }
  169. /**
  170. * Get the duration (in milliseconds) of an event
  171. * @param event {Object}
  172. * @return {number} The number of milliseconds
  173. */
  174. getEventDuration(event: Object): number {
  175. let start = this.stringToDate(event['date_begin']);
  176. let end = this.stringToDate(event['date_end']);
  177. let duration = 0;
  178. if (start !== undefined && start !== null && end !== undefined && end !== null)
  179. duration = end - start;
  180. return duration;
  181. }
  182. /**
  183. * Get events starting after the limit
  184. *
  185. * @param events
  186. * @param limit
  187. * @return {Array<Object>}
  188. */
  189. getEventsAfterLimit(events: Object, limit: Date): Array<Object> {
  190. let validEvents = [];
  191. for (let event of events) {
  192. let startDate = this.stringToDate(event['date_begin']);
  193. if (startDate !== undefined && startDate !== null && startDate >= limit) {
  194. validEvents.push(event);
  195. }
  196. }
  197. return validEvents;
  198. }
  199. /**
  200. * Get the event with the longest duration in the given array.
  201. * If all events have the same duration, return the first in the array.
  202. * @param events
  203. */
  204. getLongestEvent(events: Array<Object>): Object {
  205. let longestEvent = events[0];
  206. let longestTime = 0;
  207. for (let event of events) {
  208. let time = this.getEventDuration(event);
  209. if (time > longestTime) {
  210. longestTime = time;
  211. longestEvent = event;
  212. }
  213. }
  214. return longestEvent;
  215. }
  216. /**
  217. * Get events that have not yet ended/started
  218. *
  219. * @param events
  220. */
  221. getFutureEvents(events: Array<Object>): Array<Object> {
  222. let validEvents = [];
  223. let now = new Date();
  224. for (let event of events) {
  225. let startDate = this.stringToDate(event['date_begin']);
  226. let endDate = this.stringToDate(event['date_end']);
  227. if (startDate !== undefined && startDate !== null) {
  228. if (startDate > now)
  229. validEvents.push(event);
  230. else if (endDate !== undefined && endDate !== null) {
  231. if (endDate > now || endDate < startDate) // Display event if it ends the following day
  232. validEvents.push(event);
  233. }
  234. }
  235. }
  236. return validEvents;
  237. }
  238. /**
  239. *
  240. *
  241. * @param events
  242. * @return {Object}
  243. */
  244. getDisplayEvent(events: Array<Object>): Object {
  245. let displayEvent = undefined;
  246. if (events.length > 1) {
  247. let eventsAfterLimit = this.getEventsAfterLimit(events, this.getTodayEventTimeLimit());
  248. if (eventsAfterLimit.length > 0) {
  249. if (eventsAfterLimit.length === 1)
  250. displayEvent = eventsAfterLimit[0];
  251. else
  252. displayEvent = this.getLongestEvent(events);
  253. } else {
  254. displayEvent = this.getLongestEvent(events);
  255. }
  256. } else if (events.length === 1) {
  257. displayEvent = events[0];
  258. }
  259. return displayEvent;
  260. }
  261. clickAction(isAvailable: boolean, displayEvent: Object) {
  262. if (isAvailable)
  263. this.props.navigation.navigate('PlanningDisplayScreen', {data: displayEvent});
  264. else
  265. this.props.navigation.navigate('Planning');
  266. };
  267. getDashboardEventItem(content: Array<Object>) {
  268. let icon = 'calendar-range';
  269. let color = ThemeManager.getCurrentThemeVariables().planningColor;
  270. let title = i18n.t('homeScreen.dashboard.todayEventsTitle');
  271. let subtitle;
  272. let futureEvents = this.getFutureEvents(content);
  273. let isAvailable = futureEvents.length > 0;
  274. if (isAvailable) {
  275. subtitle =
  276. <Text>
  277. <Text style={{fontWeight: "bold"}}>{futureEvents.length}</Text>
  278. <Text>
  279. {
  280. futureEvents.length > 1 ?
  281. i18n.t('homeScreen.dashboard.todayEventsSubtitlePlural') :
  282. i18n.t('homeScreen.dashboard.todayEventsSubtitle')
  283. }
  284. </Text>
  285. </Text>;
  286. } else
  287. subtitle = i18n.t('homeScreen.dashboard.todayEventsSubtitleNA');
  288. let displayEvent = this.getDisplayEvent(futureEvents);
  289. const clickAction = this.clickAction.bind(this, isAvailable, displayEvent);
  290. return (
  291. <DashboardItem
  292. subtitle={subtitle}
  293. color={color}
  294. icon={icon}
  295. clickAction={clickAction}
  296. title={title}
  297. isAvailable={isAvailable}
  298. displayEvent={displayEvent}
  299. />
  300. );
  301. }
  302. getDashboardBottomItem(content: Array<Object>) {
  303. let proximoData = content[0]['data'];
  304. let menuData = content[1]['data'];
  305. let proximoIcon = 'shopping';
  306. let proximoColor = ThemeManager.getCurrentThemeVariables().proximoColor;
  307. let proximoTitle = i18n.t('homeScreen.dashboard.proximoTitle');
  308. let isProximoAvailable = parseInt(proximoData) > 0;
  309. let proximoSubtitle;
  310. if (isProximoAvailable) {
  311. proximoSubtitle =
  312. <Text>
  313. <Text style={{fontWeight: "bold"}}>{proximoData}</Text>
  314. <Text>
  315. {
  316. proximoData > 1 ?
  317. i18n.t('homeScreen.dashboard.proximoSubtitlePlural') :
  318. i18n.t('homeScreen.dashboard.proximoSubtitle')
  319. }
  320. </Text>
  321. </Text>;
  322. } else
  323. proximoSubtitle = i18n.t('homeScreen.dashboard.proximoSubtitleNA');
  324. let menuIcon = 'silverware-fork-knife';
  325. let menuColor = ThemeManager.getCurrentThemeVariables().menuColor;
  326. let menuTitle = i18n.t('homeScreen.dashboard.menuTitle');
  327. let isMenuAvailable = menuData.length > 0;
  328. let menuSubtitle;
  329. if (isMenuAvailable) {
  330. menuSubtitle = i18n.t('homeScreen.dashboard.menuSubtitle');
  331. } else
  332. menuSubtitle = i18n.t('homeScreen.dashboard.menuSubtitleNA');
  333. return (
  334. <View style={{
  335. flexDirection: 'row',
  336. marginLeft: 10,
  337. marginRight: 10,
  338. marginBottom: 10,
  339. }}>
  340. <SquareDashboardItem
  341. title={menuTitle}
  342. subtitle={menuSubtitle}
  343. color={menuColor}
  344. icon={menuIcon}
  345. clickAction={this.onMenuClick}
  346. isAvailable={isMenuAvailable}
  347. isLeft={true}/>
  348. <SquareDashboardItem
  349. title={proximoTitle}
  350. subtitle={proximoSubtitle}
  351. color={proximoColor}
  352. icon={proximoIcon}
  353. clickAction={this.onProximoClick}
  354. isAvailable={isProximoAvailable}
  355. isLeft={false}/>
  356. </View>
  357. );
  358. }
  359. getDashboardMiddleItem(content: Array<Object>) {
  360. let proxiwashData = content[0]['data'];
  361. let tutorinsaData = content[1]['data'];
  362. let proxiwashIcon = 'washing-machine';
  363. let proxiwashColor = ThemeManager.getCurrentThemeVariables().proxiwashColor;
  364. let proxiwashTitle = i18n.t('homeScreen.dashboard.proxiwashTitle');
  365. let proxiwashIsAvailable = parseInt(proxiwashData['dryers']) > 0 || parseInt(proxiwashData['washers']) > 0;
  366. let proxiwashSubtitle;
  367. let dryerColor = parseInt(proxiwashData['dryers']) > 0 ?
  368. ThemeManager.getCurrentThemeVariables().text :
  369. ThemeManager.getCurrentThemeVariables().textDisabled;
  370. let washerColor = parseInt(proxiwashData['washers']) > 0 ?
  371. ThemeManager.getCurrentThemeVariables().text :
  372. ThemeManager.getCurrentThemeVariables().textDisabled;
  373. let availableDryers = proxiwashData['dryers'];
  374. let availableWashers = proxiwashData['washers'];
  375. if (proxiwashIsAvailable) {
  376. proxiwashSubtitle =
  377. <Text>
  378. <Text style={{
  379. fontWeight: parseInt(proxiwashData['dryers']) > 0 ?
  380. 'bold' :
  381. 'normal',
  382. color: dryerColor
  383. }}>
  384. {availableDryers}
  385. </Text>
  386. <Text>
  387. {
  388. availableDryers > 1 ?
  389. i18n.t('homeScreen.dashboard.proxiwashSubtitle1Plural') :
  390. i18n.t('homeScreen.dashboard.proxiwashSubtitle1')
  391. }
  392. </Text>
  393. {"\n"}
  394. <Text style={{
  395. fontWeight: parseInt(proxiwashData['washers']) > 0 ?
  396. 'bold' :
  397. 'normal',
  398. color: washerColor
  399. }}>
  400. {availableWashers}
  401. </Text>
  402. <Text>
  403. {
  404. availableWashers > 1 ?
  405. i18n.t('homeScreen.dashboard.proxiwashSubtitle2Plural') :
  406. i18n.t('homeScreen.dashboard.proxiwashSubtitle2')
  407. }
  408. </Text>
  409. </Text>;
  410. } else
  411. proxiwashSubtitle = i18n.t('homeScreen.dashboard.proxiwashSubtitleNA');
  412. let tutorinsaIcon = 'school';
  413. let tutorinsaColor = ThemeManager.getCurrentThemeVariables().tutorinsaColor;
  414. let tutorinsaTitle = 'Tutor\'INSA';
  415. let tutorinsaIsAvailable = tutorinsaData > 0;
  416. let tutorinsaSubtitle;
  417. if (tutorinsaIsAvailable) {
  418. tutorinsaSubtitle =
  419. <Text>
  420. <Text style={{fontWeight: "bold"}}>{tutorinsaData}</Text>
  421. <Text>
  422. {
  423. tutorinsaData > 1 ?
  424. i18n.t('homeScreen.dashboard.tutorinsaSubtitlePlural') :
  425. i18n.t('homeScreen.dashboard.tutorinsaSubtitle')
  426. }
  427. </Text>
  428. </Text>;
  429. } else
  430. tutorinsaSubtitle = i18n.t('homeScreen.dashboard.tutorinsaSubtitleNA');
  431. return (
  432. <View style={{
  433. flexDirection: 'row',
  434. marginLeft: 10,
  435. marginRight: 10,
  436. }}>
  437. <SquareDashboardItem
  438. title={proxiwashTitle}
  439. subtitle={proxiwashSubtitle}
  440. color={proxiwashColor}
  441. icon={proxiwashIcon}
  442. clickAction={this.onProxiwashClick}
  443. isAvailable={proxiwashIsAvailable}
  444. isLeft={true}/>
  445. <SquareDashboardItem
  446. title={tutorinsaTitle}
  447. subtitle={tutorinsaSubtitle}
  448. color={tutorinsaColor}
  449. icon={tutorinsaIcon}
  450. clickAction={this.onTutorInsaClick}
  451. isAvailable={tutorinsaIsAvailable}
  452. isLeft={false}/>
  453. </View>
  454. );
  455. }
  456. openLink(link: string) {
  457. WebBrowser.openBrowserAsync(link);
  458. }
  459. getFeedItem(item: Object) {
  460. const onImagePress = this.openLink.bind(this, item.full_picture);
  461. const onOutLinkPress = this.openLink.bind(this, item.permalink_url);
  462. return (
  463. <FeedItem
  464. title={NAME_AMICALE}
  465. subtitle={HomeScreen.getFormattedDate(item.created_time)}
  466. full_picture={item.full_picture}
  467. message={item.message}
  468. onImagePress={onImagePress}
  469. onOutLinkPress={onOutLinkPress}
  470. />
  471. );
  472. }
  473. getRenderItem({item, section}: Object) {
  474. return (section['id'] === SECTIONS_ID[0] ?
  475. this.getDashboardItem(item) : this.getFeedItem(item));
  476. }
  477. render() {
  478. const nav = this.props.navigation;
  479. return (
  480. <WebSectionList
  481. createDataset={this.createDataset}
  482. navigation={nav}
  483. refreshTime={REFRESH_TIME}
  484. fetchUrl={DATA_URL}
  485. renderItem={this.getRenderItem}/>
  486. );
  487. }
  488. }