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

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