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.

ProxiwashScreen.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // @flow
  2. import * as React from 'react';
  3. import {Alert, Platform, View} from 'react-native';
  4. import {Body, Card, CardItem, Left, Right, Text} from 'native-base';
  5. import ThemeManager from '../utils/ThemeManager';
  6. import i18n from "i18n-js";
  7. import CustomMaterialIcon from "../components/CustomMaterialIcon";
  8. import FetchedDataSectionList from "../components/FetchedDataSectionList";
  9. import NotificationsManager from "../utils/NotificationsManager";
  10. import PlatformTouchable from "react-native-platform-touchable";
  11. import AsyncStorageManager from "../utils/AsyncStorageManager";
  12. import * as Expo from "expo";
  13. const DATA_URL = "https://etud.insa-toulouse.fr/~vergnet/appli-amicale/dataProxiwash.json";
  14. let reminderNotifTime = 5;
  15. const MACHINE_STATES = {
  16. TERMINE: "0",
  17. DISPONIBLE: "1",
  18. FONCTIONNE: "2",
  19. HS: "3",
  20. ERREUR: "4"
  21. };
  22. let stateStrings = {};
  23. let modalStateStrings = {};
  24. let stateIcons = {};
  25. let stateColors = {};
  26. /**
  27. * Class defining the app's proxiwash screen. This screen shows information about washing machines and
  28. * dryers, taken from a scrapper reading proxiwash website
  29. */
  30. export default class ProxiwashScreen extends FetchedDataSectionList {
  31. refreshInterval: IntervalID;
  32. /**
  33. * Creates machine state parameters using current theme and translations
  34. */
  35. constructor() {
  36. super(DATA_URL, 1000 * 30); // Refresh every half minute
  37. let colors = ThemeManager.getCurrentThemeVariables();
  38. stateColors[MACHINE_STATES.TERMINE] = colors.proxiwashFinishedColor;
  39. stateColors[MACHINE_STATES.DISPONIBLE] = colors.proxiwashReadyColor;
  40. stateColors[MACHINE_STATES.FONCTIONNE] = colors.proxiwashRunningColor;
  41. stateColors[MACHINE_STATES.HS] = colors.proxiwashBrokenColor;
  42. stateColors[MACHINE_STATES.ERREUR] = colors.proxiwashErrorColor;
  43. stateStrings[MACHINE_STATES.TERMINE] = i18n.t('proxiwashScreen.states.finished');
  44. stateStrings[MACHINE_STATES.DISPONIBLE] = i18n.t('proxiwashScreen.states.ready');
  45. stateStrings[MACHINE_STATES.FONCTIONNE] = i18n.t('proxiwashScreen.states.running');
  46. stateStrings[MACHINE_STATES.HS] = i18n.t('proxiwashScreen.states.broken');
  47. stateStrings[MACHINE_STATES.ERREUR] = i18n.t('proxiwashScreen.states.error');
  48. modalStateStrings[MACHINE_STATES.TERMINE] = i18n.t('proxiwashScreen.modal.finished');
  49. modalStateStrings[MACHINE_STATES.DISPONIBLE] = i18n.t('proxiwashScreen.modal.ready');
  50. modalStateStrings[MACHINE_STATES.FONCTIONNE] = i18n.t('proxiwashScreen.modal.running');
  51. modalStateStrings[MACHINE_STATES.HS] = i18n.t('proxiwashScreen.modal.broken');
  52. modalStateStrings[MACHINE_STATES.ERREUR] = i18n.t('proxiwashScreen.modal.error');
  53. stateIcons[MACHINE_STATES.TERMINE] = 'check-circle';
  54. stateIcons[MACHINE_STATES.DISPONIBLE] = 'radiobox-blank';
  55. stateIcons[MACHINE_STATES.FONCTIONNE] = 'progress-check';
  56. stateIcons[MACHINE_STATES.HS] = 'alert-octagram-outline';
  57. stateIcons[MACHINE_STATES.ERREUR] = 'alert';
  58. let dataString = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current;
  59. this.state = {
  60. refreshing: false,
  61. firstLoading: true,
  62. fetchedData: {},
  63. machinesWatched: JSON.parse(dataString),
  64. };
  65. }
  66. /**
  67. * Setup notification channel for android and add listeners to detect notifications fired
  68. */
  69. componentDidMount() {
  70. super.componentDidMount();
  71. if (Platform.OS === 'android') {
  72. Expo.Notifications.createChannelAndroidAsync('reminders', {
  73. name: 'Reminders',
  74. priority: 'max',
  75. vibrate: [0, 250, 250, 250],
  76. });
  77. }
  78. // Remove machine from watch list when receiving last notification
  79. Expo.Notifications.addListener((notification) => {
  80. if (notification.data !== undefined) {
  81. if (this.isMachineWatched(notification.data.id) && notification.data.isMachineFinished === true) {
  82. this.removeNotificationFromPrefs(this.getMachineIndexInWatchList(notification.data.id));
  83. }
  84. }
  85. });
  86. }
  87. getHeaderTranslation() {
  88. return i18n.t("screens.proxiwash");
  89. }
  90. getUpdateToastTranslations() {
  91. return [i18n.t("proxiwashScreen.listUpdated"), i18n.t("proxiwashScreen.listUpdateFail")];
  92. }
  93. getDryersKeyExtractor(item: Object) {
  94. return item !== undefined ? "dryer" + item.number : undefined;
  95. }
  96. getWashersKeyExtractor(item: Object) {
  97. return item !== undefined ? "washer" + item.number : undefined;
  98. }
  99. /**
  100. * Get the time remaining based on start/end time and done percent
  101. *
  102. * @param startString The string representing the start time. Format: hh:mm
  103. * @param endString The string representing the end time. Format: hh:mm
  104. * @param percentDone The percentage done
  105. * @returns {number} How many minutes are remaining for this machine
  106. */
  107. static getRemainingTime(startString: string, endString: string, percentDone: string): number {
  108. let startArray = startString.split(':');
  109. let endArray = endString.split(':');
  110. let startDate = new Date().setHours(parseInt(startArray[0]), parseInt(startArray[1]), 0, 0);
  111. let endDate = new Date().setHours(parseInt(endArray[0]), parseInt(endArray[1]), 0, 0);
  112. // Convert milliseconds into minutes
  113. let time: string = (((100 - parseFloat(percentDone)) / 100) * (endDate - startDate) / (60 * 1000)).toFixed(0);
  114. return parseInt(time);
  115. }
  116. /**
  117. * Setup notifications for the machine with the given ID.
  118. * One notification will be sent at the end of the program.
  119. * Another will be send a few minutes before the end, based on the value of reminderNotifTime
  120. *
  121. * @param machineId The machine's ID
  122. * @param remainingTime The time remaining for this machine
  123. * @returns {Promise<void>}
  124. */
  125. async setupNotifications(machineId: string, remainingTime: number) {
  126. if (!this.isMachineWatched(machineId)) {
  127. let endNotificationID = await NotificationsManager.scheduleNotification(
  128. i18n.t('proxiwashScreen.notifications.machineFinishedTitle'),
  129. i18n.t('proxiwashScreen.notifications.machineFinishedBody', {number: machineId}),
  130. new Date().getTime() + remainingTime * (60 * 1000), // Convert back to milliseconds
  131. {id: machineId, isMachineFinished: true},
  132. 'reminders'
  133. );
  134. let reminderNotificationID = await ProxiwashScreen.setupReminderNotification(machineId, remainingTime);
  135. this.saveNotificationToPrefs(machineId, endNotificationID, reminderNotificationID);
  136. } else
  137. this.disableNotification(machineId);
  138. }
  139. static async setupReminderNotification(machineId: string, remainingTime: number): Promise<string | null> {
  140. let reminderNotificationID: string | null = null;
  141. let reminderNotificationTime = ProxiwashScreen.getReminderNotificationTime();
  142. if (remainingTime > reminderNotificationTime && reminderNotificationTime > 0) {
  143. reminderNotificationID = await NotificationsManager.scheduleNotification(
  144. i18n.t('proxiwashScreen.notifications.machineRunningTitle', {time: reminderNotificationTime}),
  145. i18n.t('proxiwashScreen.notifications.machineRunningBody', {number: machineId}),
  146. new Date().getTime() + (remainingTime - reminderNotificationTime) * (60 * 1000), // Convert back to milliseconds
  147. {id: machineId, isMachineFinished: false},
  148. 'reminders'
  149. );
  150. }
  151. return reminderNotificationID;
  152. }
  153. static getReminderNotificationTime(): number {
  154. let val = AsyncStorageManager.getInstance().preferences.proxiwashNotifications.current;
  155. if (val !== "never")
  156. reminderNotifTime = parseInt(val);
  157. else
  158. reminderNotifTime = -1;
  159. return reminderNotifTime;
  160. }
  161. /**
  162. * Stop scheduled notifications for the machine of the given ID.
  163. * This will also remove the notification if it was already shown.
  164. *
  165. * @param machineId The machine's ID
  166. */
  167. disableNotification(machineId: string) {
  168. let data = this.state.machinesWatched;
  169. if (data.length > 0) {
  170. let arrayIndex = this.getMachineIndexInWatchList(machineId);
  171. if (arrayIndex !== -1) {
  172. NotificationsManager.cancelScheduledNotification(data[arrayIndex].endNotificationID);
  173. if (data[arrayIndex].reminderNotificationID !== null)
  174. NotificationsManager.cancelScheduledNotification(data[arrayIndex].reminderNotificationID);
  175. this.removeNotificationFromPrefs(arrayIndex);
  176. }
  177. }
  178. }
  179. /**
  180. * Get the index of the given machine ID in the watchlist array
  181. *
  182. * @param machineId
  183. * @return
  184. */
  185. getMachineIndexInWatchList(machineId: string): number {
  186. let elem = this.state.machinesWatched.find(function (elem) {
  187. return elem.machineNumber === machineId
  188. });
  189. return this.state.machinesWatched.indexOf(elem);
  190. }
  191. /**
  192. * Add the given notifications associated to a machine ID to the watchlist, and save the array to the preferences
  193. *
  194. * @param machineId
  195. * @param endNotificationID
  196. * @param reminderNotificationID
  197. */
  198. saveNotificationToPrefs(machineId: string, endNotificationID: string, reminderNotificationID: string | null) {
  199. let data = this.state.machinesWatched;
  200. data.push({
  201. machineNumber: machineId,
  202. endNotificationID: endNotificationID,
  203. reminderNotificationID: reminderNotificationID
  204. });
  205. this.updateNotificationPrefs(data);
  206. }
  207. /**
  208. * remove the given index from the watchlist array and save it to preferences
  209. *
  210. * @param index
  211. */
  212. removeNotificationFromPrefs(index: number) {
  213. let data = this.state.machinesWatched;
  214. data.splice(index, 1);
  215. this.updateNotificationPrefs(data);
  216. }
  217. /**
  218. * Set the given data as the watchlist and save it to preferences
  219. *
  220. * @param data
  221. */
  222. updateNotificationPrefs(data: Array<Object>) {
  223. this.setState({machinesWatched: data});
  224. let prefKey = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key;
  225. AsyncStorageManager.getInstance().savePref(prefKey, JSON.stringify(data));
  226. }
  227. /**
  228. * Checks whether the machine of the given ID has scheduled notifications
  229. *
  230. * @param machineID The machine's ID
  231. * @returns {boolean}
  232. */
  233. isMachineWatched(machineID: string) {
  234. return this.state.machinesWatched.find(function (elem) {
  235. return elem.machineNumber === machineID
  236. }) !== undefined;
  237. }
  238. createDataset(fetchedData: Object) {
  239. return [
  240. {
  241. title: i18n.t('proxiwashScreen.washers'),
  242. icon: 'washing-machine',
  243. data: fetchedData.washers === undefined ? [] : fetchedData.washers,
  244. extraData: super.state,
  245. keyExtractor: this.getWashersKeyExtractor
  246. },
  247. {
  248. title: i18n.t('proxiwashScreen.dryers'),
  249. icon: 'tumble-dryer',
  250. data: fetchedData.dryers === undefined ? [] : fetchedData.dryers,
  251. extraData: super.state,
  252. keyExtractor: this.getDryersKeyExtractor
  253. },
  254. ];
  255. }
  256. hasTabs(): boolean {
  257. return true;
  258. }
  259. /**
  260. * Show an alert fo a machine, allowing to enable/disable notifications if running
  261. *
  262. * @param title
  263. * @param item
  264. * @param remainingTime
  265. */
  266. showAlert(title: string, item: Object, remainingTime: number) {
  267. let buttons = [{text: i18n.t("proxiwashScreen.modal.ok")}];
  268. let message = modalStateStrings[MACHINE_STATES[item.state]];
  269. if (MACHINE_STATES[item.state] === MACHINE_STATES.FONCTIONNE) {
  270. buttons = [
  271. {
  272. text: this.isMachineWatched(item.number) ?
  273. i18n.t("proxiwashScreen.modal.disableNotifications") :
  274. i18n.t("proxiwashScreen.modal.enableNotifications"),
  275. onPress: () => this.setupNotifications(item.number, remainingTime)
  276. },
  277. {
  278. text: i18n.t("proxiwashScreen.modal.cancel")
  279. }
  280. ];
  281. message = i18n.t('proxiwashScreen.modal.running',
  282. {
  283. start: item.startTime,
  284. end: item.endTime,
  285. remaining: remainingTime
  286. });
  287. }
  288. Alert.alert(
  289. title,
  290. message,
  291. buttons
  292. );
  293. }
  294. /**
  295. * Get list item to be rendered
  296. *
  297. * @param item The object containing the item's FetchedData
  298. * @param section The object describing the current SectionList section
  299. * @param data The full FetchedData used by the SectionList
  300. * @returns {React.Node}
  301. */
  302. getRenderItem(item: Object, section: Object, data: Object) {
  303. let isMachineRunning = MACHINE_STATES[item.state] === MACHINE_STATES.FONCTIONNE;
  304. let machineName = (section.title === i18n.t('proxiwashScreen.dryers') ? i18n.t('proxiwashScreen.dryer') : i18n.t('proxiwashScreen.washer')) + ' n°' + item.number;
  305. let remainingTime = 0;
  306. if (isMachineRunning)
  307. remainingTime = ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent);
  308. return (
  309. <Card style={{
  310. flex: 0,
  311. height: 64,
  312. marginLeft: 10,
  313. marginRight: 10
  314. }}>
  315. <CardItem
  316. style={{
  317. backgroundColor: stateColors[MACHINE_STATES[item.state]],
  318. paddingRight: 0,
  319. paddingLeft: 0,
  320. height: '100%',
  321. }}
  322. >
  323. <View style={{
  324. height: 64,
  325. position: 'absolute',
  326. right: 0,
  327. width: item.donePercent !== '' ? (100 - parseInt(item.donePercent)).toString() + '%' : 0,
  328. backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor
  329. }}/>
  330. <PlatformTouchable
  331. onPress={() => this.showAlert(machineName, item, remainingTime)}
  332. style={{
  333. height: 64,
  334. position: 'absolute',
  335. zIndex: 10, // Make sure the button is above the text
  336. right: 0,
  337. width: '100%'
  338. }}
  339. >
  340. <View/>
  341. </PlatformTouchable>
  342. <Left style={{marginLeft: 10}}>
  343. <CustomMaterialIcon
  344. icon={section.title === i18n.t('proxiwashScreen.dryers') ? 'tumble-dryer' : 'washing-machine'}
  345. fontSize={30}
  346. />
  347. <Body>
  348. <Text>
  349. {machineName + ' '}
  350. {this.isMachineWatched(item.number) ?
  351. <CustomMaterialIcon
  352. icon='bell-ring'
  353. color={ThemeManager.getCurrentThemeVariables().brandPrimary}
  354. fontSize={20}
  355. /> : ''}
  356. </Text>
  357. <Text note>
  358. {isMachineRunning ? item.startTime + '/' + item.endTime : ''}
  359. </Text>
  360. </Body>
  361. </Left>
  362. <Right style={{marginRight: 10}}>
  363. <Text style={MACHINE_STATES[item.state] === MACHINE_STATES.TERMINE ?
  364. {fontWeight: 'bold'} : {}}
  365. >
  366. {stateStrings[MACHINE_STATES[item.state]]}
  367. </Text>
  368. <CustomMaterialIcon icon={stateIcons[MACHINE_STATES[item.state]]}
  369. fontSize={25}
  370. />
  371. </Right>
  372. </CardItem>
  373. </Card>);
  374. }
  375. }