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.

PlanningScreen.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. // @flow
  2. import * as React from 'react';
  3. import {BackHandler} from 'react-native';
  4. import {Content, H1, H3, Text, Button} from 'native-base';
  5. import i18n from "i18n-js";
  6. import {View, Image} from "react-native";
  7. import ThemeManager from "../utils/ThemeManager";
  8. import {Linking} from "expo";
  9. import BaseContainer from "../components/BaseContainer";
  10. import {Agenda, LocaleConfig} from 'react-native-calendars';
  11. import HTML from 'react-native-render-html';
  12. import Touchable from 'react-native-platform-touchable';
  13. import Modalize from 'react-native-modalize';
  14. import WebDataManager from "../utils/WebDataManager";
  15. import CustomMaterialIcon from "../components/CustomMaterialIcon";
  16. LocaleConfig.locales['fr'] = {
  17. monthNames: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
  18. monthNamesShort: ['Janv.', 'Févr.', 'Mars', 'Avril', 'Mai', 'Juin', 'Juil.', 'Août', 'Sept.', 'Oct.', 'Nov.', 'Déc.'],
  19. dayNames: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
  20. dayNamesShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
  21. today: 'Aujourd\'hui'
  22. };
  23. type Props = {
  24. navigation: Object,
  25. }
  26. type State = {
  27. modalCurrentDisplayItem: Object,
  28. refreshing: boolean,
  29. agendaItems: Object,
  30. calendarShowing: boolean,
  31. };
  32. const FETCH_URL = "https://amicale-insat.fr/event/json/list";
  33. const AGENDA_MONTH_SPAN = 6;
  34. /**
  35. * Opens a link in the device's browser
  36. * @param link The link to open
  37. */
  38. function openWebLink(link) {
  39. Linking.openURL(link).catch((err) => console.error('Error opening link', err));
  40. }
  41. /**
  42. * Class defining the app's planning screen
  43. */
  44. export default class PlanningScreen extends React.Component<Props, State> {
  45. modalRef: Modalize;
  46. agendaRef: Agenda;
  47. webDataManager: WebDataManager;
  48. lastRefresh: Date;
  49. minTimeBetweenRefresh = 60;
  50. didFocusSubscription: Function;
  51. willBlurSubscription: Function;
  52. constructor(props: any) {
  53. super(props);
  54. this.modalRef = React.createRef();
  55. this.webDataManager = new WebDataManager(FETCH_URL);
  56. this.didFocusSubscription = props.navigation.addListener(
  57. 'didFocus',
  58. payload =>
  59. BackHandler.addEventListener(
  60. 'hardwareBackPress',
  61. this.onBackButtonPressAndroid
  62. )
  63. );
  64. if (i18n.currentLocale().startsWith("fr")) {
  65. LocaleConfig.defaultLocale = 'fr';
  66. }
  67. }
  68. componentDidMount() {
  69. this._onRefresh();
  70. this.willBlurSubscription = this.props.navigation.addListener(
  71. 'willBlur',
  72. payload =>
  73. BackHandler.removeEventListener(
  74. 'hardwareBackPress',
  75. this.onBackButtonPressAndroid
  76. )
  77. );
  78. }
  79. onBackButtonPressAndroid = () => {
  80. if (this.state.calendarShowing) {
  81. this.agendaRef.chooseDay(this.agendaRef.state.selectedDay);
  82. return true;
  83. } else {
  84. return false;
  85. }
  86. };
  87. componentWillUnmount() {
  88. this.didFocusSubscription && this.didFocusSubscription.remove();
  89. this.willBlurSubscription && this.willBlurSubscription.remove();
  90. }
  91. state = {
  92. modalCurrentDisplayItem: {},
  93. refreshing: false,
  94. agendaItems: {},
  95. calendarShowing: false,
  96. };
  97. getCurrentDate() {
  98. let today = new Date();
  99. return this.getFormattedDate(today);
  100. }
  101. getFormattedDate(date: Date) {
  102. let dd = String(date.getDate()).padStart(2, '0');
  103. let mm = String(date.getMonth() + 1).padStart(2, '0'); //January is 0!
  104. let yyyy = date.getFullYear();
  105. return yyyy + '-' + mm + '-' + dd;
  106. }
  107. generateEmptyCalendar() {
  108. let end = new Date(new Date().setMonth(new Date().getMonth() + AGENDA_MONTH_SPAN + 1));
  109. let daysOfYear = {};
  110. for (let d = new Date(2019, 8, 1); d <= end; d.setDate(d.getDate() + 1)) {
  111. daysOfYear[this.getFormattedDate(new Date(d))] = []
  112. }
  113. return daysOfYear;
  114. }
  115. getModalHeader() {
  116. return (
  117. <View style={{marginBottom: 0}}>
  118. <Button
  119. onPress={() => this.modalRef.current.close()}
  120. style={{
  121. marginTop: 50,
  122. marginLeft: 'auto',
  123. }}
  124. transparent>
  125. <CustomMaterialIcon icon={'close'}/>
  126. </Button>
  127. </View>
  128. );
  129. }
  130. getModalContent() {
  131. return (
  132. <View style={{
  133. flex: 1,
  134. padding: 20
  135. }}>
  136. <H1>
  137. {this.state.modalCurrentDisplayItem.title}
  138. </H1>
  139. <H3 style={{
  140. marginTop: 10,
  141. color: ThemeManager.getCurrentThemeVariables().listNoteColor
  142. }}>
  143. {this.getFormattedTime(this.state.modalCurrentDisplayItem)}
  144. </H3>
  145. <Content>
  146. {this.state.modalCurrentDisplayItem.logo !== null ?
  147. <View style={{width: '100%', height: 200, marginTop: 20, marginBottom: 20}}>
  148. <Image style={{flex: 1, resizeMode: "contain"}}
  149. source={{uri: this.state.modalCurrentDisplayItem.logo}}/>
  150. </View>
  151. : <View/>}
  152. {this.state.modalCurrentDisplayItem.description !== null ?
  153. // Surround description with div to allow text styling if the description is not html
  154. <HTML html={"<div>" + this.state.modalCurrentDisplayItem.description + "</div>"}
  155. tagsStyles={{
  156. p: {
  157. color: ThemeManager.getCurrentThemeVariables().textColor,
  158. fontSize: ThemeManager.getCurrentThemeVariables().fontSizeBase
  159. },
  160. div: {color: ThemeManager.getCurrentThemeVariables().textColor}
  161. }}
  162. onLinkPress={(event, link) => openWebLink(link)}/>
  163. : <View/>}
  164. </Content>
  165. </View>
  166. );
  167. }
  168. showItemDetails(item: Object) {
  169. this.setState({
  170. modalCurrentDisplayItem: item,
  171. });
  172. if (this.modalRef.current) {
  173. this.modalRef.current.open();
  174. }
  175. }
  176. getRenderItem(item: Object) {
  177. return (
  178. <Touchable
  179. style={{
  180. backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor,
  181. borderRadius: 10,
  182. marginRight: 10,
  183. marginTop: 17,
  184. }}
  185. onPress={() => this.showItemDetails(item)}>
  186. <View style={{
  187. padding: 10,
  188. flex: 1,
  189. flexDirection: 'row'
  190. }}>
  191. <View style={{
  192. width: item.logo !== null ? '70%' : '100%',
  193. }}>
  194. <Text style={{
  195. color: ThemeManager.getCurrentThemeVariables().listNoteColor,
  196. marginTop: 5,
  197. marginBottom: 10
  198. }}>
  199. {this.getFormattedTime(item)}
  200. </Text>
  201. <H3 style={{marginBottom: 10}}>{item.title}</H3>
  202. </View>
  203. <View style={{
  204. width: item.logo !== null ? '30%' : 0,
  205. height: 80
  206. }}>
  207. {item.logo !== null ?
  208. <Image source={{uri: item.logo}}
  209. style={{
  210. flex: 1,
  211. resizeMode: "contain"
  212. }}/>
  213. : <View/>}
  214. </View>
  215. </View>
  216. </Touchable>
  217. );
  218. }
  219. getRenderEmptyDate() {
  220. return (
  221. <View style={{
  222. padding: 10,
  223. flex: 1,
  224. }}>
  225. <View style={{
  226. width: '100%',
  227. height: 1,
  228. backgroundColor: ThemeManager.getCurrentThemeVariables().agendaEmptyLine,
  229. marginTop: 'auto',
  230. marginBottom: 'auto',
  231. }}/>
  232. </View>
  233. );
  234. }
  235. rowHasChanged(r1: Object, r2: Object) {
  236. if (r1 !== undefined && r2 !== undefined)
  237. return r1.title !== r2.title;
  238. else return !(r1 === undefined && r2 === undefined);
  239. }
  240. /**
  241. * Refresh data and show a toast if any error occurred
  242. * @private
  243. */
  244. _onRefresh = () => {
  245. let canRefresh;
  246. if (this.lastRefresh !== undefined)
  247. canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) / 1000 > this.minTimeBetweenRefresh;
  248. else
  249. canRefresh = true;
  250. if (canRefresh) {
  251. this.setState({refreshing: true});
  252. this.webDataManager.readData()
  253. .then((fetchedData) => {
  254. this.setState({
  255. refreshing: false,
  256. });
  257. this.generateEventAgenda(fetchedData);
  258. this.lastRefresh = new Date();
  259. })
  260. .catch((err) => {
  261. this.setState({
  262. refreshing: false,
  263. });
  264. console.log(err);
  265. });
  266. }
  267. };
  268. generateEventAgenda(eventList: Array<Object>) {
  269. let agendaItems = this.generateEmptyCalendar();
  270. for (let i = 0; i < eventList.length; i++) {
  271. if (agendaItems[this.getEventStartDate(eventList[i])] !== undefined) {
  272. this.pushEventInOrder(agendaItems, eventList[i], this.getEventStartDate(eventList[i]));
  273. }
  274. }
  275. this.setState({agendaItems: agendaItems})
  276. }
  277. pushEventInOrder(agendaItems: Object, event: Object, startDate: string) {
  278. if (agendaItems[startDate].length === 0)
  279. agendaItems[startDate].push(event);
  280. else {
  281. for (let i = 0; i < agendaItems[startDate].length; i++) {
  282. if (this.isEventBefore(event, agendaItems[startDate][i])) {
  283. agendaItems[startDate].splice(i, 0, event);
  284. break;
  285. } else if (i === agendaItems[startDate].length - 1) {
  286. agendaItems[startDate].push(event);
  287. break;
  288. }
  289. }
  290. }
  291. }
  292. isEventBefore(event1: Object, event2: Object) {
  293. let date1 = new Date();
  294. let date2 = new Date();
  295. let timeArray = this.getEventStartTime(event1).split(":");
  296. date1.setHours(parseInt(timeArray[0]), parseInt(timeArray[1]));
  297. timeArray = this.getEventStartTime(event2).split(":");
  298. date2.setHours(parseInt(timeArray[0]), parseInt(timeArray[1]));
  299. return date1 < date2;
  300. }
  301. getEventStartDate(event: Object) {
  302. return event.date_begin.split(" ")[0];
  303. }
  304. getEventStartTime(event: Object) {
  305. if (event !== undefined && Object.keys(event).length > 0 && event.date_begin !== null)
  306. return this.formatTime(event.date_begin.split(" ")[1]);
  307. else
  308. return "";
  309. }
  310. getEventEndTime(event: Object) {
  311. if (event !== undefined && Object.keys(event).length > 0 && event.date_end !== null)
  312. return this.formatTime(event.date_end.split(" ")[1]);
  313. else
  314. return "";
  315. }
  316. getFormattedTime(event: Object) {
  317. if (this.getEventEndTime(event) !== "")
  318. return this.getEventStartTime(event) + " - " + this.getEventEndTime(event)
  319. else
  320. return this.getEventStartTime(event);
  321. }
  322. formatTime(time: string) {
  323. let array = time.split(':');
  324. return array[0] + ':' + array[1];
  325. }
  326. onModalClosed() {
  327. this.setState({
  328. modalCurrentDisplayItem: {},
  329. });
  330. }
  331. render() {
  332. const nav = this.props.navigation;
  333. return (
  334. <BaseContainer navigation={nav} headerTitle={i18n.t('screens.planning')}>
  335. <Modalize ref={this.modalRef}
  336. modalStyle={{
  337. backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor,
  338. }}
  339. // adjustToContentHeight // Breaks when displaying full screen, half, then full again
  340. HeaderComponent={() => this.getModalHeader()}
  341. onClosed={() => this.onModalClosed()}>
  342. {this.getModalContent()}
  343. </Modalize>
  344. <Agenda
  345. // the list of items that have to be displayed in agenda. If you want to render item as empty date
  346. // the value of date key kas to be an empty array []. If there exists no value for date key it is
  347. // considered that the date in question is not yet loaded
  348. items={this.state.agendaItems}
  349. // initially selected day
  350. selected={this.getCurrentDate()}
  351. // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
  352. minDate={this.getCurrentDate()}
  353. // Max amount of months allowed to scroll to the past. Default = 50
  354. pastScrollRange={1}
  355. // Max amount of months allowed to scroll to the future. Default = 50
  356. futureScrollRange={AGENDA_MONTH_SPAN}
  357. // If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly.
  358. onRefresh={() => this._onRefresh()}
  359. // callback that fires when the calendar is opened or closed
  360. onCalendarToggled={(calendarOpened) => {
  361. this.setState({calendarShowing: calendarOpened})
  362. }}
  363. // Set this true while waiting for new data from a refresh
  364. refreshing={this.state.refreshing}
  365. renderItem={(item) => this.getRenderItem(item)}
  366. renderEmptyDate={() => this.getRenderEmptyDate()}
  367. rowHasChanged={() => this.rowHasChanged()}
  368. // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
  369. firstDay={1}
  370. // ref to this agenda in order to handle back button event
  371. ref={(ref) => this.agendaRef = ref}
  372. // agenda theme
  373. theme={{
  374. backgroundColor: ThemeManager.getCurrentThemeVariables().agendaBackgroundColor,
  375. calendarBackground: ThemeManager.getCurrentThemeVariables().containerBgColor,
  376. textSectionTitleColor: ThemeManager.getCurrentThemeVariables().listNoteColor,
  377. selectedDayBackgroundColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
  378. selectedDayTextColor: '#ffffff',
  379. todayTextColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
  380. dayTextColor: ThemeManager.getCurrentThemeVariables().textColor,
  381. textDisabledColor: ThemeManager.getCurrentThemeVariables().textDisabledColor,
  382. dotColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
  383. selectedDotColor: '#ffffff',
  384. arrowColor: 'orange',
  385. monthTextColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
  386. indicatorColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
  387. textDayFontWeight: '300',
  388. textMonthFontWeight: 'bold',
  389. textDayHeaderFontWeight: '300',
  390. textDayFontSize: 16,
  391. textMonthFontSize: 16,
  392. textDayHeaderFontSize: 16,
  393. agendaDayTextColor: ThemeManager.getCurrentThemeVariables().listNoteColor,
  394. agendaDayNumColor: ThemeManager.getCurrentThemeVariables().listNoteColor,
  395. agendaTodayColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
  396. agendaKnobColor: ThemeManager.getCurrentThemeVariables().brandPrimary,
  397. // Fix for days hiding behind knob
  398. 'stylesheet.calendar.header': {
  399. week: {
  400. marginTop: 0,
  401. flexDirection: 'row',
  402. justifyContent: 'space-between'
  403. }
  404. }
  405. }}
  406. />
  407. </BaseContainer>
  408. );
  409. }
  410. }