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

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