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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. // @flow
  2. import * as React from 'react';
  3. import {View} from 'react-native';
  4. import i18n from "i18n-js";
  5. import DashboardItem from "../components/EventDashboardItem";
  6. import * as WebBrowser from 'expo-web-browser';
  7. import WebSectionList from "../components/WebSectionList";
  8. import {Text, withTheme} from 'react-native-paper';
  9. import FeedItem from "../components/FeedItem";
  10. import SquareDashboardItem from "../components/SquareDashboardItem";
  11. import PreviewEventDashboardItem from "../components/PreviewEventDashboardItem";
  12. import {stringToDate} from "../utils/Planning";
  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. type Props = {
  22. navigation: Object,
  23. theme: Object,
  24. }
  25. /**
  26. * Class defining the app's home screen
  27. */
  28. 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. colors: Object;
  36. constructor(props) {
  37. super(props);
  38. this.onProxiwashClick = this.onProxiwashClick.bind(this);
  39. this.onTutorInsaClick = this.onTutorInsaClick.bind(this);
  40. this.onMenuClick = this.onMenuClick.bind(this);
  41. this.onProximoClick = this.onProximoClick.bind(this);
  42. this.getRenderItem = this.getRenderItem.bind(this);
  43. this.createDataset = this.createDataset.bind(this);
  44. this.colors = props.theme.colors;
  45. }
  46. /**
  47. * Converts a dateString using Unix Timestamp to a formatted date
  48. *
  49. * @param dateString {string} The Unix Timestamp representation of a date
  50. * @return {string} The formatted output date
  51. */
  52. static getFormattedDate(dateString: string) {
  53. let date = new Date(Number.parseInt(dateString) * 1000);
  54. return date.toLocaleString();
  55. }
  56. onProxiwashClick() {
  57. this.props.navigation.navigate('Proxiwash');
  58. }
  59. onTutorInsaClick() {
  60. WebBrowser.openBrowserAsync("https://www.etud.insa-toulouse.fr/~tutorinsa/");
  61. }
  62. onProximoClick() {
  63. this.props.navigation.navigate('Proximo');
  64. }
  65. onMenuClick() {
  66. this.props.navigation.navigate('SelfMenuScreen');
  67. }
  68. /**
  69. * Extract a key for the given item
  70. *
  71. * @param item The item to extract the key from
  72. * @return {*} The extracted key
  73. */
  74. getKeyExtractor(item: Object) {
  75. return item !== undefined ? item.id : undefined;
  76. }
  77. /**
  78. * Creates the dataset to be used in the FlatList
  79. *
  80. * @param fetchedData
  81. * @return {*}
  82. */
  83. createDataset(fetchedData: Object) {
  84. // fetchedData = DATA;
  85. let newsData = [];
  86. let dashboardData = [];
  87. if (fetchedData['news_feed'] !== undefined)
  88. newsData = fetchedData['news_feed']['data'];
  89. if (fetchedData['dashboard'] !== undefined)
  90. dashboardData = this.generateDashboardDataset(fetchedData['dashboard']);
  91. return [
  92. {
  93. title: '',
  94. data: dashboardData,
  95. extraData: super.state,
  96. keyExtractor: this.getKeyExtractor,
  97. id: SECTIONS_ID[0]
  98. },
  99. {
  100. title: i18n.t('homeScreen.newsFeed'),
  101. data: newsData,
  102. extraData: super.state,
  103. keyExtractor: this.getKeyExtractor,
  104. id: SECTIONS_ID[1]
  105. }
  106. ];
  107. }
  108. /**
  109. * Generates the dataset associated to the dashboard to be displayed in the FlatList as a section
  110. *
  111. * @param dashboardData
  112. * @return {*}
  113. */
  114. generateDashboardDataset(dashboardData: Object) {
  115. let dataset = [
  116. {
  117. id: 'middle',
  118. content: []
  119. },
  120. {
  121. id: 'event',
  122. content: undefined
  123. },
  124. ];
  125. for (let [key, value] of Object.entries(dashboardData)) {
  126. switch (key) {
  127. case 'today_events':
  128. dataset[1]['content'] = value;
  129. break;
  130. case 'available_machines':
  131. dataset[0]['content'][0] = {id: key, data: value};
  132. break;
  133. case 'available_tutorials':
  134. dataset[0]['content'][1] = {id: key, data: value};
  135. break;
  136. case 'proximo_articles':
  137. dataset[0]['content'][2] = {id: key, data: value};
  138. break;
  139. case 'today_menu':
  140. dataset[0]['content'][3] = {id: key, data: value};
  141. break;
  142. }
  143. }
  144. return dataset
  145. }
  146. /**
  147. * Gets a dashboard item
  148. *
  149. * @param item The item to display
  150. * @return {*}
  151. */
  152. getDashboardItem(item: Object) {
  153. let content = item['content'];
  154. if (item['id'] === 'event')
  155. return this.getDashboardEventItem(content);
  156. else if (item['id'] === 'middle')
  157. return this.getDashboardMiddleItem(content);
  158. }
  159. /**
  160. * Gets the time limit depending on the current day:
  161. * 17:30 for every day of the week except for thursday 11:30
  162. * 00:00 on weekends
  163. */
  164. getTodayEventTimeLimit() {
  165. let now = new Date();
  166. if (now.getDay() === 4) // Thursday
  167. now.setHours(11, 30, 0);
  168. else if (now.getDay() === 6 || now.getDay() === 0) // Weekend
  169. now.setHours(0, 0, 0);
  170. else
  171. now.setHours(17, 30, 0);
  172. return now;
  173. }
  174. /**
  175. * Gets the duration (in milliseconds) of an event
  176. *
  177. * @param event {Object}
  178. * @return {number} The number of milliseconds
  179. */
  180. getEventDuration(event: Object): number {
  181. let start = stringToDate(event['date_begin']);
  182. let end = stringToDate(event['date_end']);
  183. let duration = 0;
  184. if (start !== undefined && start !== null && end !== undefined && end !== null)
  185. duration = end - start;
  186. return duration;
  187. }
  188. /**
  189. * Gets events starting after the limit
  190. *
  191. * @param events
  192. * @param limit
  193. * @return {Array<Object>}
  194. */
  195. getEventsAfterLimit(events: Object, limit: Date): Array<Object> {
  196. let validEvents = [];
  197. for (let event of events) {
  198. let startDate = stringToDate(event['date_begin']);
  199. if (startDate !== undefined && startDate !== null && startDate >= limit) {
  200. validEvents.push(event);
  201. }
  202. }
  203. return validEvents;
  204. }
  205. /**
  206. * Gets the event with the longest duration in the given array.
  207. * If all events have the same duration, return the first in the array.
  208. *
  209. * @param events
  210. */
  211. getLongestEvent(events: Array<Object>): Object {
  212. let longestEvent = events[0];
  213. let longestTime = 0;
  214. for (let event of events) {
  215. let time = this.getEventDuration(event);
  216. if (time > longestTime) {
  217. longestTime = time;
  218. longestEvent = event;
  219. }
  220. }
  221. return longestEvent;
  222. }
  223. /**
  224. * Gets events that have not yet ended/started
  225. *
  226. * @param events
  227. */
  228. getFutureEvents(events: Array<Object>): Array<Object> {
  229. let validEvents = [];
  230. let now = new Date();
  231. for (let event of events) {
  232. let startDate = stringToDate(event['date_begin']);
  233. let endDate = stringToDate(event['date_end']);
  234. if (startDate !== undefined && startDate !== null) {
  235. if (startDate > now)
  236. validEvents.push(event);
  237. else if (endDate !== undefined && endDate !== null) {
  238. if (endDate > now || endDate < startDate) // Display event if it ends the following day
  239. validEvents.push(event);
  240. }
  241. }
  242. }
  243. return validEvents;
  244. }
  245. /**
  246. * Gets the event to display in the preview
  247. *
  248. * @param events
  249. * @return {Object}
  250. */
  251. getDisplayEvent(events: Array<Object>): Object {
  252. let displayEvent = undefined;
  253. if (events.length > 1) {
  254. let eventsAfterLimit = this.getEventsAfterLimit(events, this.getTodayEventTimeLimit());
  255. if (eventsAfterLimit.length > 0) {
  256. if (eventsAfterLimit.length === 1)
  257. displayEvent = eventsAfterLimit[0];
  258. else
  259. displayEvent = this.getLongestEvent(events);
  260. } else {
  261. displayEvent = this.getLongestEvent(events);
  262. }
  263. } else if (events.length === 1) {
  264. displayEvent = events[0];
  265. }
  266. return displayEvent;
  267. }
  268. /**
  269. * Gets the event render item.
  270. * If a preview is available, it will be rendered inside
  271. *
  272. * @param content
  273. * @return {*}
  274. */
  275. getDashboardEventItem(content: Array<Object>) {
  276. let icon = 'calendar-range';
  277. let title = i18n.t('homeScreen.dashboard.todayEventsTitle');
  278. let subtitle;
  279. let futureEvents = this.getFutureEvents(content);
  280. let isAvailable = futureEvents.length > 0;
  281. if (isAvailable) {
  282. subtitle =
  283. <Text>
  284. <Text style={{fontWeight: "bold"}}>{futureEvents.length}</Text>
  285. <Text>
  286. {
  287. futureEvents.length > 1 ?
  288. i18n.t('homeScreen.dashboard.todayEventsSubtitlePlural') :
  289. i18n.t('homeScreen.dashboard.todayEventsSubtitle')
  290. }
  291. </Text>
  292. </Text>;
  293. } else
  294. subtitle = i18n.t('homeScreen.dashboard.todayEventsSubtitleNA');
  295. let displayEvent = this.getDisplayEvent(futureEvents);
  296. const clickContainerAction = () => this.props.navigation.navigate('Planning');
  297. const clickPreviewAction = () => this.props.navigation.navigate('PlanningDisplayScreen', {data: displayEvent});
  298. return (
  299. <DashboardItem
  300. {...this.props}
  301. subtitle={subtitle}
  302. icon={icon}
  303. clickAction={clickContainerAction}
  304. title={title}
  305. isAvailable={isAvailable}
  306. >
  307. <PreviewEventDashboardItem
  308. {...this.props}
  309. event={displayEvent}
  310. clickAction={clickPreviewAction}
  311. />
  312. </DashboardItem>
  313. );
  314. }
  315. /**
  316. * Gets a classic dashboard item.
  317. *
  318. * @param content
  319. * @return {*}
  320. */
  321. getDashboardMiddleItem(content: Array<Object>) {
  322. let proxiwashData = content[0]['data'];
  323. let tutorinsaData = content[1]['data'];
  324. let proximoData = content[2]['data'];
  325. let menuData = content[3]['data'];
  326. return (
  327. <View style={{
  328. flex: 1,
  329. flexDirection: 'row',
  330. justifyContent: 'center',
  331. flexWrap: 'wrap',
  332. margin: 10,
  333. }}>
  334. <SquareDashboardItem
  335. color={this.colors.proxiwashColor}
  336. icon={'washing-machine'}
  337. clickAction={this.onProxiwashClick}
  338. isAvailable={parseInt(proxiwashData['washers']) > 0}
  339. badgeNumber={proxiwashData['washers']}
  340. />
  341. <SquareDashboardItem
  342. color={this.colors.proxiwashColor}
  343. icon={'tumble-dryer'}
  344. clickAction={this.onProxiwashClick}
  345. isAvailable={parseInt(proxiwashData['dryers']) > 0}
  346. badgeNumber={proxiwashData['dryers']}
  347. />
  348. <SquareDashboardItem
  349. color={this.colors.tutorinsaColor}
  350. icon={'school'}
  351. clickAction={this.onTutorInsaClick}
  352. isAvailable={tutorinsaData > 0}
  353. badgeNumber={tutorinsaData}
  354. />
  355. <SquareDashboardItem
  356. color={this.colors.proximoColor}
  357. icon={'shopping'}
  358. clickAction={this.onProximoClick}
  359. isAvailable={parseInt(proximoData) > 0}
  360. badgeNumber={parseInt(proximoData)}
  361. />
  362. <SquareDashboardItem
  363. color={this.colors.menuColor}
  364. icon={'silverware-fork-knife'}
  365. clickAction={this.onMenuClick}
  366. isAvailable={menuData.length > 0}
  367. badgeNumber={0}
  368. />
  369. </View>
  370. );
  371. }
  372. openLink(link: string) {
  373. WebBrowser.openBrowserAsync(link);
  374. }
  375. /**
  376. * Gets a render item for the given feed object
  377. *
  378. * @param item The feed item to display
  379. * @return {*}
  380. */
  381. getFeedItem(item: Object) {
  382. const onImagePress = this.openLink.bind(this, item.full_picture);
  383. const onOutLinkPress = this.openLink.bind(this, item.permalink_url);
  384. return (
  385. <FeedItem
  386. title={NAME_AMICALE}
  387. subtitle={HomeScreen.getFormattedDate(item.created_time)}
  388. full_picture={item.full_picture}
  389. message={item.message}
  390. onImagePress={onImagePress}
  391. onOutLinkPress={onOutLinkPress}
  392. />
  393. );
  394. }
  395. /**
  396. * Gets a FlatList render item
  397. *
  398. * @param item The item to display
  399. * @param section The current section
  400. * @return {*}
  401. */
  402. getRenderItem({item, section}: Object) {
  403. return (section['id'] === SECTIONS_ID[0] ?
  404. this.getDashboardItem(item) : this.getFeedItem(item));
  405. }
  406. render() {
  407. const nav = this.props.navigation;
  408. return (
  409. <WebSectionList
  410. createDataset={this.createDataset}
  411. navigation={nav}
  412. autoRefreshTime={REFRESH_TIME}
  413. refreshOnFocus={true}
  414. fetchUrl={DATA_URL}
  415. renderItem={this.getRenderItem}/>
  416. );
  417. }
  418. }
  419. export default withTheme(HomeScreen);