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

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