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.

ProxiwashScreen.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. // @flow
  2. import * as React from 'react';
  3. import {Alert, Platform, View} from 'react-native';
  4. import i18n from "i18n-js";
  5. import WebSectionList from "../../components/Screens/WebSectionList";
  6. import * as Notifications from "../../utils/Notifications";
  7. import AsyncStorageManager from "../../managers/AsyncStorageManager";
  8. import * as Expo from "expo";
  9. import {Avatar, Banner, Button, Card, Text, withTheme} from 'react-native-paper';
  10. import ProxiwashListItem from "../../components/Lists/Proxiwash/ProxiwashListItem";
  11. import ProxiwashConstants from "../../constants/ProxiwashConstants";
  12. import CustomModal from "../../components/Overrides/CustomModal";
  13. import AprilFoolsManager from "../../managers/AprilFoolsManager";
  14. import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
  15. import ProxiwashSectionHeader from "../../components/Lists/Proxiwash/ProxiwashSectionHeader";
  16. import {withCollapsible} from "../../utils/withCollapsible";
  17. const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/washinsa/washinsa.json";
  18. let modalStateStrings = {};
  19. const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds
  20. const LIST_ITEM_HEIGHT = 64;
  21. type Props = {
  22. navigation: Object,
  23. route: Object,
  24. theme: Object,
  25. collapsibleStack: Object,
  26. }
  27. type State = {
  28. refreshing: boolean,
  29. modalCurrentDisplayItem: React.Node,
  30. machinesWatched: Array<string>,
  31. bannerVisible: boolean,
  32. };
  33. /**
  34. * Class defining the app's proxiwash screen. This screen shows information about washing machines and
  35. * dryers, taken from a scrapper reading proxiwash website
  36. */
  37. class ProxiwashScreen extends React.Component<Props, State> {
  38. modalRef: Object;
  39. fetchedData: Object;
  40. state = {
  41. refreshing: false,
  42. modalCurrentDisplayItem: null,
  43. machinesWatched: [],
  44. bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === '1',
  45. };
  46. /**
  47. * Creates machine state parameters using current theme and translations
  48. */
  49. constructor(props) {
  50. super(props);
  51. modalStateStrings[ProxiwashConstants.machineStates.TERMINE] = i18n.t('proxiwashScreen.modal.finished');
  52. modalStateStrings[ProxiwashConstants.machineStates.DISPONIBLE] = i18n.t('proxiwashScreen.modal.ready');
  53. modalStateStrings[ProxiwashConstants.machineStates["EN COURS"]] = i18n.t('proxiwashScreen.modal.running');
  54. modalStateStrings[ProxiwashConstants.machineStates.HS] = i18n.t('proxiwashScreen.modal.broken');
  55. modalStateStrings[ProxiwashConstants.machineStates.ERREUR] = i18n.t('proxiwashScreen.modal.error');
  56. }
  57. /**
  58. * Callback used when closing the banner.
  59. * This hides the banner and saves to preferences to prevent it from reopening
  60. */
  61. onHideBanner = () => {
  62. this.setState({bannerVisible: false});
  63. AsyncStorageManager.getInstance().savePref(
  64. AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.key,
  65. '0'
  66. );
  67. };
  68. /**
  69. * Setup notification channel for android and add listeners to detect notifications fired
  70. */
  71. componentDidMount() {
  72. this.props.navigation.setOptions({
  73. headerRight: this.getAboutButton,
  74. });
  75. if (AsyncStorageManager.getInstance().preferences.expoToken.current !== '') {
  76. // Get latest watchlist from server
  77. Notifications.getMachineNotificationWatchlist((fetchedList) => {
  78. this.setState({machinesWatched: fetchedList})
  79. });
  80. // Get updated watchlist after received notification
  81. Expo.Notifications.addListener(() => {
  82. Notifications.getMachineNotificationWatchlist((fetchedList) => {
  83. this.setState({machinesWatched: fetchedList})
  84. });
  85. });
  86. if (Platform.OS === 'android') {
  87. Expo.Notifications.createChannelAndroidAsync('reminders', {
  88. name: 'Reminders',
  89. priority: 'max',
  90. vibrate: [0, 250, 250, 250],
  91. });
  92. }
  93. }
  94. }
  95. /**
  96. * Callback used when pressing the about button.
  97. * This will open the ProxiwashAboutScreen.
  98. */
  99. onAboutPress = () => this.props.navigation.navigate('proxiwash-about');
  100. /**
  101. * Gets the about header button
  102. *
  103. * @return {*}
  104. */
  105. getAboutButton = () =>
  106. <MaterialHeaderButtons>
  107. <Item title="information" iconName="information" onPress={this.onAboutPress}/>
  108. </MaterialHeaderButtons>;
  109. /**
  110. * Extracts the key for the given item
  111. *
  112. * @param item The item to extract the key from
  113. * @return {*} The extracted key
  114. */
  115. getKeyExtractor(item: Object) {
  116. return item !== undefined ? item.number : undefined;
  117. }
  118. /**
  119. * Setups notifications for the machine with the given ID.
  120. * One notification will be sent at the end of the program.
  121. * Another will be send a few minutes before the end, based on the value of reminderNotifTime
  122. *
  123. * @param machineId The machine's ID
  124. * @returns {Promise<void>}
  125. */
  126. setupNotifications(machineId: string) {
  127. if (AsyncStorageManager.getInstance().preferences.expoToken.current !== '') {
  128. if (!this.isMachineWatched(machineId)) {
  129. Notifications.setupMachineNotification(machineId, true);
  130. this.saveNotificationToState(machineId);
  131. } else
  132. this.disableNotification(machineId);
  133. } else {
  134. this.showNotificationsDisabledWarning();
  135. }
  136. }
  137. /**
  138. * Shows a warning telling the user notifications are disabled for the app
  139. */
  140. showNotificationsDisabledWarning() {
  141. Alert.alert(
  142. i18n.t("proxiwashScreen.modal.notificationErrorTitle"),
  143. i18n.t("proxiwashScreen.modal.notificationErrorDescription"),
  144. );
  145. }
  146. /**
  147. * Stops scheduled notifications for the machine of the given ID.
  148. * This will also remove the notification if it was already shown.
  149. *
  150. * @param machineId The machine's ID
  151. */
  152. disableNotification(machineId: string) {
  153. let data = this.state.machinesWatched;
  154. if (data.length > 0) {
  155. let arrayIndex = data.indexOf(machineId);
  156. if (arrayIndex !== -1) {
  157. Notifications.setupMachineNotification(machineId, false);
  158. this.removeNotificationFroState(arrayIndex);
  159. }
  160. }
  161. }
  162. /**
  163. * Adds the given notifications associated to a machine ID to the watchlist, and saves the array to the preferences
  164. *
  165. * @param machineId
  166. */
  167. saveNotificationToState(machineId: string) {
  168. let data = this.state.machinesWatched;
  169. data.push(machineId);
  170. this.updateNotificationState(data);
  171. }
  172. /**
  173. * Removes the given index from the watchlist array and saves it to preferences
  174. *
  175. * @param index
  176. */
  177. removeNotificationFroState(index: number) {
  178. let data = this.state.machinesWatched;
  179. data.splice(index, 1);
  180. this.updateNotificationState(data);
  181. }
  182. /**
  183. * Sets the given fetchedData as the watchlist
  184. *
  185. * @param data
  186. */
  187. updateNotificationState(data: Array<Object>) {
  188. this.setState({machinesWatched: data});
  189. }
  190. /**
  191. * Checks whether the machine of the given ID has scheduled notifications
  192. *
  193. * @param machineID The machine's ID
  194. * @returns {boolean}
  195. */
  196. isMachineWatched(machineID: string) {
  197. return this.state.machinesWatched.indexOf(machineID) !== -1;
  198. }
  199. /**
  200. * Creates the dataset to be used by the flatlist
  201. *
  202. * @param fetchedData
  203. * @return {*}
  204. */
  205. createDataset = (fetchedData: Object) => {
  206. let data = fetchedData;
  207. if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
  208. data = JSON.parse(JSON.stringify(fetchedData)); // Deep copy
  209. AprilFoolsManager.getNewProxiwashDryerOrderedList(data.dryers);
  210. AprilFoolsManager.getNewProxiwashWasherOrderedList(data.washers);
  211. }
  212. this.fetchedData = fetchedData;
  213. return [
  214. {
  215. title: i18n.t('proxiwashScreen.dryers'),
  216. icon: 'tumble-dryer',
  217. data: data.dryers === undefined ? [] : data.dryers,
  218. keyExtractor: this.getKeyExtractor
  219. },
  220. {
  221. title: i18n.t('proxiwashScreen.washers'),
  222. icon: 'washing-machine',
  223. data: data.washers === undefined ? [] : data.washers,
  224. keyExtractor: this.getKeyExtractor
  225. },
  226. ];
  227. };
  228. /**
  229. * Shows a modal for the given item
  230. *
  231. * @param title The title to use
  232. * @param item The item to display information for in the modal
  233. * @param isDryer True if the given item is a dryer
  234. */
  235. showModal = (title: string, item: Object, isDryer: boolean) => {
  236. this.setState({
  237. modalCurrentDisplayItem: this.getModalContent(title, item, isDryer)
  238. });
  239. if (this.modalRef) {
  240. this.modalRef.open();
  241. }
  242. };
  243. /**
  244. * Callback used when the user clicks on enable notifications for a machine
  245. *
  246. * @param machineId The machine's id to set notifications for
  247. */
  248. onSetupNotificationsPress(machineId: string) {
  249. if (this.modalRef) {
  250. this.modalRef.close();
  251. }
  252. this.setupNotifications(machineId)
  253. }
  254. /**
  255. * Generates the modal content.
  256. * This shows information for the given machine.
  257. *
  258. * @param title The title to use
  259. * @param item The item to display information for in the modal
  260. * @param isDryer True if the given item is a dryer
  261. * @return {*}
  262. */
  263. getModalContent(title: string, item: Object, isDryer: boolean) {
  264. let button = {
  265. text: i18n.t("proxiwashScreen.modal.ok"),
  266. icon: '',
  267. onPress: undefined
  268. };
  269. let message = modalStateStrings[ProxiwashConstants.machineStates[item.state]];
  270. const onPress = this.onSetupNotificationsPress.bind(this, item.number);
  271. if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates["EN COURS"]) {
  272. button =
  273. {
  274. text: this.isMachineWatched(item.number) ?
  275. i18n.t("proxiwashScreen.modal.disableNotifications") :
  276. i18n.t("proxiwashScreen.modal.enableNotifications"),
  277. icon: '',
  278. onPress: onPress
  279. }
  280. ;
  281. message = i18n.t('proxiwashScreen.modal.running',
  282. {
  283. start: item.startTime,
  284. end: item.endTime,
  285. remaining: item.remainingTime
  286. });
  287. } else if (ProxiwashConstants.machineStates[item.state] === ProxiwashConstants.machineStates.DISPONIBLE) {
  288. if (isDryer)
  289. message += '\n' + i18n.t('proxiwashScreen.dryersTariff');
  290. else
  291. message += '\n' + i18n.t('proxiwashScreen.washersTariff');
  292. }
  293. return (
  294. <View style={{
  295. flex: 1,
  296. padding: 20
  297. }}>
  298. <Card.Title
  299. title={title}
  300. left={() => <Avatar.Icon
  301. icon={isDryer ? 'tumble-dryer' : 'washing-machine'}
  302. color={this.props.theme.colors.text}
  303. style={{backgroundColor: 'transparent'}}/>}
  304. />
  305. <Card.Content>
  306. <Text>{message}</Text>
  307. </Card.Content>
  308. {button.onPress !== undefined ?
  309. <Card.Actions>
  310. <Button
  311. icon={button.icon}
  312. mode="contained"
  313. onPress={button.onPress}
  314. style={{marginLeft: 'auto', marginRight: 'auto'}}
  315. >
  316. {button.text}
  317. </Button>
  318. </Card.Actions> : null}
  319. </View>
  320. );
  321. }
  322. /**
  323. * Callback used when receiving modal ref
  324. *
  325. * @param ref
  326. */
  327. onModalRef = (ref: Object) => {
  328. this.modalRef = ref;
  329. };
  330. /**
  331. * Gets the number of machines available
  332. *
  333. * @param isDryer True if we are only checking for dryer, false for washers
  334. * @return {number} The number of machines available
  335. */
  336. getMachineAvailableNumber(isDryer: boolean) {
  337. let data;
  338. if (isDryer)
  339. data = this.fetchedData.dryers;
  340. else
  341. data = this.fetchedData.washers;
  342. let count = 0;
  343. for (let i = 0; i < data.length; i++) {
  344. if (ProxiwashConstants.machineStates[data[i].state] === ProxiwashConstants.machineStates["DISPONIBLE"])
  345. count += 1;
  346. }
  347. return count;
  348. }
  349. /**
  350. * Gets the section render item
  351. *
  352. * @param section The section to render
  353. * @return {*}
  354. */
  355. getRenderSectionHeader = ({section}: Object) => {
  356. const isDryer = section.title === i18n.t('proxiwashScreen.dryers');
  357. const nbAvailable = this.getMachineAvailableNumber(isDryer);
  358. return (
  359. <ProxiwashSectionHeader
  360. title={section.title}
  361. nbAvailable={nbAvailable}
  362. isDryer={isDryer}/>
  363. );
  364. };
  365. /**
  366. * Gets the list item to be rendered
  367. *
  368. * @param item The object containing the item's FetchedData
  369. * @param section The object describing the current SectionList section
  370. * @returns {React.Node}
  371. */
  372. getRenderItem = ({item, section}: Object) => {
  373. const isDryer = section.title === i18n.t('proxiwashScreen.dryers');
  374. return (
  375. <ProxiwashListItem
  376. item={item}
  377. onPress={this.showModal}
  378. isWatched={this.isMachineWatched(item.number)}
  379. isDryer={isDryer}
  380. height={LIST_ITEM_HEIGHT}
  381. />
  382. );
  383. };
  384. render() {
  385. const nav = this.props.navigation;
  386. const {containerPaddingTop} = this.props.collapsibleStack;
  387. return (
  388. <View
  389. style={{flex: 1}}
  390. >
  391. <View style={{
  392. position: "absolute",
  393. width: "100%",
  394. height: "100%",
  395. }}>
  396. <WebSectionList
  397. createDataset={this.createDataset}
  398. navigation={nav}
  399. fetchUrl={DATA_URL}
  400. renderItem={this.getRenderItem}
  401. renderSectionHeader={this.getRenderSectionHeader}
  402. autoRefreshTime={REFRESH_TIME}
  403. refreshOnFocus={true}
  404. updateData={this.state.machinesWatched.length}/>
  405. </View>
  406. <Banner
  407. style={{marginTop: containerPaddingTop,}}
  408. visible={this.state.bannerVisible}
  409. actions={[
  410. {
  411. label: 'OK',
  412. onPress: this.onHideBanner,
  413. },
  414. ]}
  415. icon={() => <Avatar.Icon
  416. icon={'information'}
  417. size={40}
  418. />}
  419. >
  420. {i18n.t('proxiwashScreen.enableNotificationsTip')}
  421. </Banner>
  422. <CustomModal onRef={this.onModalRef}>
  423. {this.state.modalCurrentDisplayItem}
  424. </CustomModal>
  425. </View>
  426. );
  427. }
  428. }
  429. export default withCollapsible(withTheme(ProxiwashScreen));