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.

HomeScreen.js 15KB

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