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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. // @flow
  2. import * as React from 'react';
  3. import {Alert, View} from 'react-native';
  4. import i18n from 'i18n-js';
  5. import {Avatar, Button, Card, List, Text, withTheme} from 'react-native-paper';
  6. import {StackNavigationProp} from '@react-navigation/stack';
  7. import {Modalize} from 'react-native-modalize';
  8. import WebSectionList from '../../components/Screens/WebSectionList';
  9. import * as Notifications from '../../utils/Notifications';
  10. import AsyncStorageManager from '../../managers/AsyncStorageManager';
  11. import ProxiwashListItem from '../../components/Lists/Proxiwash/ProxiwashListItem';
  12. import ProxiwashConstants from '../../constants/ProxiwashConstants';
  13. import CustomModal from '../../components/Overrides/CustomModal';
  14. import AprilFoolsManager from '../../managers/AprilFoolsManager';
  15. import MaterialHeaderButtons, {
  16. Item,
  17. } from '../../components/Overrides/CustomHeaderButton';
  18. import ProxiwashSectionHeader from '../../components/Lists/Proxiwash/ProxiwashSectionHeader';
  19. import type {CustomThemeType} from '../../managers/ThemeManager';
  20. import {
  21. getCleanedMachineWatched,
  22. getMachineEndDate,
  23. isMachineWatched,
  24. } from '../../utils/Proxiwash';
  25. import {MASCOT_STYLE} from '../../components/Mascot/Mascot';
  26. import MascotPopup from '../../components/Mascot/MascotPopup';
  27. import type {SectionListDataType} from '../../components/Screens/WebSectionList';
  28. import type {ListIconPropsType} from '../../constants/PaperStyles';
  29. import {PROXIWASH_DATA} from './ProxiwashSettingsScreen';
  30. const modalStateStrings = {};
  31. const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds
  32. const LIST_ITEM_HEIGHT = 64;
  33. export type ProxiwashMachineType = {
  34. number: string,
  35. state: string,
  36. 'maxWeight ': number,
  37. startTime: string,
  38. endTime: string,
  39. donePercent: string,
  40. remainingTime: string,
  41. program: string,
  42. };
  43. type PropsType = {
  44. navigation: StackNavigationProp,
  45. theme: CustomThemeType,
  46. };
  47. type StateType = {
  48. modalCurrentDisplayItem: React.Node,
  49. machinesWatched: Array<ProxiwashMachineType>,
  50. selectedWash: string,
  51. };
  52. /**
  53. * Class defining the app's proxiwash screen. This screen shows information about washing machines and
  54. * dryers, taken from a scrapper reading proxiwash website
  55. */
  56. class ProxiwashScreen extends React.Component<PropsType, StateType> {
  57. /**
  58. * Shows a warning telling the user notifications are disabled for the app
  59. */
  60. static showNotificationsDisabledWarning() {
  61. Alert.alert(
  62. i18n.t('screens.proxiwash.modal.notificationErrorTitle'),
  63. i18n.t('screens.proxiwash.modal.notificationErrorDescription'),
  64. );
  65. }
  66. modalRef: null | Modalize;
  67. fetchedData: {
  68. dryers: Array<ProxiwashMachineType>,
  69. washers: Array<ProxiwashMachineType>,
  70. };
  71. /**
  72. * Creates machine state parameters using current theme and translations
  73. */
  74. constructor() {
  75. super();
  76. this.state = {
  77. modalCurrentDisplayItem: null,
  78. machinesWatched: AsyncStorageManager.getObject(
  79. AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key,
  80. ),
  81. selectedWash: AsyncStorageManager.getString(
  82. AsyncStorageManager.PREFERENCES.selectedWash.key,
  83. ),
  84. };
  85. modalStateStrings[ProxiwashConstants.machineStates.AVAILABLE] = i18n.t(
  86. 'screens.proxiwash.modal.ready',
  87. );
  88. modalStateStrings[ProxiwashConstants.machineStates.RUNNING] = i18n.t(
  89. 'screens.proxiwash.modal.running',
  90. );
  91. modalStateStrings[
  92. ProxiwashConstants.machineStates.RUNNING_NOT_STARTED
  93. ] = i18n.t('screens.proxiwash.modal.runningNotStarted');
  94. modalStateStrings[ProxiwashConstants.machineStates.FINISHED] = i18n.t(
  95. 'screens.proxiwash.modal.finished',
  96. );
  97. modalStateStrings[ProxiwashConstants.machineStates.UNAVAILABLE] = i18n.t(
  98. 'screens.proxiwash.modal.broken',
  99. );
  100. modalStateStrings[ProxiwashConstants.machineStates.ERROR] = i18n.t(
  101. 'screens.proxiwash.modal.error',
  102. );
  103. modalStateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t(
  104. 'screens.proxiwash.modal.unknown',
  105. );
  106. }
  107. /**
  108. * Setup notification channel for android and add listeners to detect notifications fired
  109. */
  110. componentDidMount() {
  111. const {navigation} = this.props;
  112. navigation.setOptions({
  113. headerRight: (): React.Node => (
  114. <MaterialHeaderButtons>
  115. <Item
  116. title="information"
  117. iconName="information"
  118. onPress={this.onAboutPress}
  119. />
  120. <Item
  121. title="settings"
  122. iconName="settings"
  123. onPress={this.onSettingsPress}
  124. />
  125. </MaterialHeaderButtons>
  126. ),
  127. });
  128. }
  129. /**
  130. * Callback used when pressing the about button.
  131. * This will open the ProxiwashAboutScreen.
  132. */
  133. onAboutPress = () => {
  134. const {navigation} = this.props;
  135. navigation.navigate('proxiwash-about');
  136. };
  137. /**
  138. * Callback used when pressing the settings button.
  139. * This will open the ProxiwashSettingsScreen.
  140. */
  141. onSettingsPress = () => {
  142. const {navigation} = this.props;
  143. navigation.navigate('proxiwash-settings');
  144. };
  145. /**
  146. * Callback used when the user clicks on enable notifications for a machine
  147. *
  148. * @param machine The machine to set notifications for
  149. */
  150. onSetupNotificationsPress(machine: ProxiwashMachineType) {
  151. if (this.modalRef) {
  152. this.modalRef.close();
  153. }
  154. this.setupNotifications(machine);
  155. }
  156. /**
  157. * Callback used when receiving modal ref
  158. *
  159. * @param ref
  160. */
  161. onModalRef = (ref: Modalize) => {
  162. this.modalRef = ref;
  163. };
  164. /**
  165. * Generates the modal content.
  166. * This shows information for the given machine.
  167. *
  168. * @param title The title to use
  169. * @param item The item to display information for in the modal
  170. * @param isDryer True if the given item is a dryer
  171. * @return {*}
  172. */
  173. getModalContent(
  174. title: string,
  175. item: ProxiwashMachineType,
  176. isDryer: boolean,
  177. ): React.Node {
  178. const {props, state} = this;
  179. let button = {
  180. text: i18n.t('screens.proxiwash.modal.ok'),
  181. icon: '',
  182. onPress: undefined,
  183. };
  184. let message = modalStateStrings[item.state];
  185. const onPress = this.onSetupNotificationsPress.bind(this, item);
  186. if (item.state === ProxiwashConstants.machineStates.RUNNING) {
  187. let remainingTime = parseInt(item.remainingTime, 10);
  188. if (remainingTime < 0) remainingTime = 0;
  189. button = {
  190. text: isMachineWatched(item, state.machinesWatched)
  191. ? i18n.t('screens.proxiwash.modal.disableNotifications')
  192. : i18n.t('screens.proxiwash.modal.enableNotifications'),
  193. icon: '',
  194. onPress,
  195. };
  196. message = i18n.t('screens.proxiwash.modal.running', {
  197. start: item.startTime,
  198. end: item.endTime,
  199. remaining: remainingTime,
  200. program: item.program,
  201. });
  202. }
  203. return (
  204. <View
  205. style={{
  206. flex: 1,
  207. padding: 20,
  208. }}>
  209. <Card.Title
  210. title={title}
  211. left={(): React.Node => (
  212. <Avatar.Icon
  213. icon={isDryer ? 'tumble-dryer' : 'washing-machine'}
  214. color={props.theme.colors.text}
  215. style={{backgroundColor: 'transparent'}}
  216. />
  217. )}
  218. />
  219. <Card.Content>
  220. <Text>{message}</Text>
  221. </Card.Content>
  222. {button.onPress !== undefined ? (
  223. <Card.Actions>
  224. <Button
  225. icon={button.icon}
  226. mode="contained"
  227. onPress={button.onPress}
  228. style={{marginLeft: 'auto', marginRight: 'auto'}}>
  229. {button.text}
  230. </Button>
  231. </Card.Actions>
  232. ) : null}
  233. </View>
  234. );
  235. }
  236. /**
  237. * Gets the section render item
  238. *
  239. * @param section The section to render
  240. * @return {*}
  241. */
  242. getRenderSectionHeader = ({
  243. section,
  244. }: {
  245. section: {title: string},
  246. }): React.Node => {
  247. const isDryer = section.title === i18n.t('screens.proxiwash.dryers');
  248. const nbAvailable = this.getMachineAvailableNumber(isDryer);
  249. return (
  250. <ProxiwashSectionHeader
  251. title={section.title}
  252. nbAvailable={nbAvailable}
  253. isDryer={isDryer}
  254. />
  255. );
  256. };
  257. /**
  258. * Gets the list item to be rendered
  259. *
  260. * @param item The object containing the item's FetchedData
  261. * @param section The object describing the current SectionList section
  262. * @returns {React.Node}
  263. */
  264. getRenderItem = ({
  265. item,
  266. section,
  267. }: {
  268. item: ProxiwashMachineType,
  269. section: {title: string},
  270. }): React.Node => {
  271. const {machinesWatched} = this.state;
  272. const isDryer = section.title === i18n.t('screens.proxiwash.dryers');
  273. return (
  274. <ProxiwashListItem
  275. item={item}
  276. onPress={this.showModal}
  277. isWatched={isMachineWatched(item, machinesWatched)}
  278. isDryer={isDryer}
  279. height={LIST_ITEM_HEIGHT}
  280. />
  281. );
  282. };
  283. /**
  284. * Extracts the key for the given item
  285. *
  286. * @param item The item to extract the key from
  287. * @return {*} The extracted key
  288. */
  289. getKeyExtractor = (item: ProxiwashMachineType): string => item.number;
  290. /**
  291. * Setups notifications for the machine with the given ID.
  292. * One notification will be sent at the end of the program.
  293. * Another will be send a few minutes before the end, based on the value of reminderNotifTime
  294. *
  295. * @param machine The machine to watch
  296. */
  297. setupNotifications(machine: ProxiwashMachineType) {
  298. const {machinesWatched} = this.state;
  299. if (!isMachineWatched(machine, machinesWatched)) {
  300. Notifications.setupMachineNotification(
  301. machine.number,
  302. true,
  303. getMachineEndDate(machine),
  304. )
  305. .then(() => {
  306. this.saveNotificationToState(machine);
  307. })
  308. .catch(() => {
  309. ProxiwashScreen.showNotificationsDisabledWarning();
  310. });
  311. } else {
  312. Notifications.setupMachineNotification(machine.number, false, null).then(
  313. () => {
  314. this.removeNotificationFromState(machine);
  315. },
  316. );
  317. }
  318. }
  319. /**
  320. * Gets the number of machines available
  321. *
  322. * @param isDryer True if we are only checking for dryer, false for washers
  323. * @return {number} The number of machines available
  324. */
  325. getMachineAvailableNumber(isDryer: boolean): number {
  326. let data;
  327. if (isDryer) data = this.fetchedData.dryers;
  328. else data = this.fetchedData.washers;
  329. let count = 0;
  330. data.forEach((machine: ProxiwashMachineType) => {
  331. if (machine.state === ProxiwashConstants.machineStates.AVAILABLE)
  332. count += 1;
  333. });
  334. return count;
  335. }
  336. /**
  337. * Gets a chevron icon
  338. *
  339. * @param props
  340. * @return {*}
  341. */
  342. static getChevronIcon(props: ListIconPropsType): React.Node {
  343. return (
  344. <List.Icon color={props.color} style={props.style} icon="chevron-right" />
  345. );
  346. }
  347. /**
  348. * Gets a custom list item icon
  349. *
  350. * @param item The item to show the icon for
  351. * @param props
  352. * @return {*}
  353. */
  354. static getItemIcon(item: ListItemType, props: ListIconPropsType): React.Node {
  355. return (
  356. <List.Icon color={props.color} style={props.style} icon={item.icon} />
  357. );
  358. }
  359. /**
  360. * Creates the dataset to be used by the FlatList
  361. *
  362. * @param fetchedData
  363. * @return {*}
  364. */
  365. createDataset = (fetchedData: {
  366. dryers: Array<ProxiwashMachineType>,
  367. washers: Array<ProxiwashMachineType>,
  368. }): SectionListDataType<ProxiwashMachineType> => {
  369. const {state} = this;
  370. let data = fetchedData;
  371. if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
  372. data = JSON.parse(JSON.stringify(fetchedData)); // Deep copy
  373. AprilFoolsManager.getNewProxiwashDryerOrderedList(data.dryers);
  374. AprilFoolsManager.getNewProxiwashWasherOrderedList(data.washers);
  375. }
  376. this.fetchedData = data;
  377. this.state.machinesWatched = getCleanedMachineWatched(
  378. state.machinesWatched,
  379. [...data.dryers, ...data.washers],
  380. );
  381. return [
  382. {
  383. title: i18n.t('screens.proxiwash.dryers'),
  384. icon: 'tumble-dryer',
  385. data: data.dryers === undefined ? [] : data.dryers,
  386. keyExtractor: this.getKeyExtractor,
  387. },
  388. {
  389. title: i18n.t('screens.proxiwash.washers'),
  390. icon: 'washing-machine',
  391. data: data.washers === undefined ? [] : data.washers,
  392. keyExtractor: this.getKeyExtractor,
  393. },
  394. ];
  395. };
  396. /**
  397. * Shows a modal for the given item
  398. *
  399. * @param title The title to use
  400. * @param item The item to display information for in the modal
  401. * @param isDryer True if the given item is a dryer
  402. */
  403. showModal = (title: string, item: ProxiwashMachineType, isDryer: boolean) => {
  404. this.setState({
  405. modalCurrentDisplayItem: this.getModalContent(title, item, isDryer),
  406. });
  407. if (this.modalRef) {
  408. this.modalRef.open();
  409. }
  410. };
  411. /**
  412. * Adds the given notifications associated to a machine ID to the watchlist, and saves the array to the preferences
  413. *
  414. * @param machine
  415. */
  416. saveNotificationToState(machine: ProxiwashMachineType) {
  417. const {machinesWatched} = this.state;
  418. const data = machinesWatched;
  419. data.push(machine);
  420. this.saveNewWatchedList(data);
  421. }
  422. /**
  423. * Removes the given index from the watchlist array and saves it to preferences
  424. *
  425. * @param selectedMachine
  426. */
  427. removeNotificationFromState(selectedMachine: ProxiwashMachineType) {
  428. const {machinesWatched} = this.state;
  429. const newList = [...machinesWatched];
  430. machinesWatched.forEach((machine: ProxiwashMachineType, index: number) => {
  431. if (
  432. machine.number === selectedMachine.number &&
  433. machine.endTime === selectedMachine.endTime
  434. )
  435. newList.splice(index, 1);
  436. });
  437. this.saveNewWatchedList(newList);
  438. }
  439. saveNewWatchedList(list: Array<ProxiwashMachineType>) {
  440. this.setState({machinesWatched: list});
  441. AsyncStorageManager.set(
  442. AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key,
  443. list,
  444. );
  445. }
  446. render(): React.Node {
  447. const {state} = this;
  448. const {navigation} = this.props;
  449. let data: LaverieType;
  450. switch (state.selectedWash) {
  451. case 'tripodeB':
  452. data = PROXIWASH_DATA.tripodeB;
  453. break;
  454. default:
  455. data = PROXIWASH_DATA.washinsa;
  456. }
  457. return (
  458. <View
  459. style={{
  460. flex: 1,
  461. }}>
  462. <View
  463. style={{
  464. position: 'absolute',
  465. width: '100%',
  466. height: '100%',
  467. }}>
  468. <WebSectionList
  469. createDataset={this.createDataset}
  470. navigation={navigation}
  471. fetchUrl={data.url}
  472. renderItem={this.getRenderItem}
  473. renderSectionHeader={this.getRenderSectionHeader}
  474. autoRefreshTime={REFRESH_TIME}
  475. refreshOnFocus
  476. updateData={state.machinesWatched.length}
  477. />
  478. </View>
  479. <MascotPopup
  480. prefKey={AsyncStorageManager.PREFERENCES.proxiwashShowMascot.key}
  481. title={i18n.t('screens.proxiwash.mascotDialog.title')}
  482. message={i18n.t('screens.proxiwash.mascotDialog.message')}
  483. icon="information"
  484. buttons={{
  485. action: null,
  486. cancel: {
  487. message: i18n.t('screens.proxiwash.mascotDialog.ok'),
  488. icon: 'check',
  489. },
  490. }}
  491. emotion={MASCOT_STYLE.NORMAL}
  492. />
  493. <CustomModal onRef={this.onModalRef}>
  494. {state.modalCurrentDisplayItem}
  495. </CustomModal>
  496. </View>
  497. );
  498. }
  499. }
  500. export default withTheme(ProxiwashScreen);