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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. // @flow
  2. import * as React from 'react';
  3. import {Alert, View, Platform} 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 * 60); // Refresh every 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. componentDidMount() {
  67. if (Platform.OS === 'android') {
  68. Expo.Notifications.createChannelAndroidAsync('reminders', {
  69. name: 'Reminders',
  70. priority: 'max',
  71. vibrate: [0, 250, 250, 250],
  72. });
  73. }
  74. // Remove machine from watch list when receiving last notification
  75. Expo.Notifications.addListener((notification) => {
  76. if (notification.data !== undefined) {
  77. if (this.isMachineWatched(notification.data.id) && notification.data.isMachineFinished === true) {
  78. this.removeNotificationFromPrefs(this.getMachineIndexInWatchList(notification.data.id));
  79. }
  80. }
  81. });
  82. }
  83. getHeaderTranslation() {
  84. return i18n.t("screens.proxiwash");
  85. }
  86. getUpdateToastTranslations() {
  87. return [i18n.t("proxiwashScreen.listUpdated"), i18n.t("proxiwashScreen.listUpdateFail")];
  88. }
  89. getKeyExtractor(item: Object) {
  90. return item !== undefined ? item.number : undefined;
  91. }
  92. getDryersKeyExtractor(item: Object) {
  93. return item !== undefined ? "dryer" + item.number : undefined;
  94. }
  95. getWashersKeyExtractor(item: Object) {
  96. return item !== undefined ? "washer" + item.number : undefined;
  97. }
  98. /**
  99. * Get the time remaining based on start/end time and done percent
  100. *
  101. * @param startString The string representing the start time. Format: hh:mm
  102. * @param endString The string representing the end time. Format: hh:mm
  103. * @param percentDone The percentage done
  104. * @returns {number} How many minutes are remaining for this machine
  105. */
  106. static getRemainingTime(startString: string, endString: string, percentDone: string): number {
  107. let startArray = startString.split(':');
  108. let endArray = endString.split(':');
  109. let startDate = new Date().setHours(parseInt(startArray[0]), parseInt(startArray[1]), 0, 0);
  110. let endDate = new Date().setHours(parseInt(endArray[0]), parseInt(endArray[1]), 0, 0);
  111. // Convert milliseconds into minutes
  112. let time: string = (((100 - parseFloat(percentDone)) / 100) * (endDate - startDate) / (60 * 1000)).toFixed(0);
  113. return parseInt(time);
  114. }
  115. /**
  116. * Setup notifications for the machine with the given ID.
  117. * One notification will be sent at the end of the program.
  118. * Another will be send a few minutes before the end, based on the value of reminderNotifTime
  119. *
  120. * @param machineId The machine's ID
  121. * @param remainingTime The time remaining for this machine
  122. * @returns {Promise<void>}
  123. */
  124. async setupNotifications(machineId: string, remainingTime: number) {
  125. if (!this.isMachineWatched(machineId)) {
  126. let endNotificationID = await NotificationsManager.scheduleNotification(
  127. i18n.t('proxiwashScreen.notifications.machineFinishedTitle'),
  128. i18n.t('proxiwashScreen.notifications.machineFinishedBody', {number: machineId}),
  129. new Date().getTime() + remainingTime * (60 * 1000), // Convert back to milliseconds
  130. {id: machineId, isMachineFinished: true},
  131. 'reminders'
  132. );
  133. let reminderNotificationID = await ProxiwashScreen.setupReminderNotification(machineId, remainingTime);
  134. this.saveNotificationToPrefs(machineId, endNotificationID, reminderNotificationID);
  135. } else
  136. this.disableNotification(machineId);
  137. }
  138. static async setupReminderNotification(machineId: string, remainingTime: number): Promise<string | null> {
  139. let reminderNotificationID: string | null = null;
  140. let reminderNotificationTime = ProxiwashScreen.getReminderNotificationTime();
  141. if (remainingTime > reminderNotificationTime && reminderNotificationTime > 0) {
  142. reminderNotificationID = await NotificationsManager.scheduleNotification(
  143. i18n.t('proxiwashScreen.notifications.machineRunningTitle', {time: reminderNotificationTime}),
  144. i18n.t('proxiwashScreen.notifications.machineRunningBody', {number: machineId}),
  145. new Date().getTime() + (remainingTime - reminderNotificationTime) * (60 * 1000), // Convert back to milliseconds
  146. {id: machineId, isMachineFinished: false},
  147. 'reminders'
  148. );
  149. }
  150. return reminderNotificationID;
  151. }
  152. static getReminderNotificationTime(): number {
  153. let val = AsyncStorageManager.getInstance().preferences.proxiwashNotifications.current;
  154. if (val !== "never")
  155. reminderNotifTime = parseInt(val);
  156. else
  157. reminderNotifTime = -1;
  158. return reminderNotifTime;
  159. }
  160. /**
  161. * Stop scheduled notifications for the machine of the given ID.
  162. * This will also remove the notification if it was already shown.
  163. *
  164. * @param machineId The machine's ID
  165. */
  166. disableNotification(machineId: string) {
  167. let data = this.state.machinesWatched;
  168. if (data.length > 0) {
  169. let arrayIndex = this.getMachineIndexInWatchList(machineId);
  170. if (arrayIndex !== -1) {
  171. NotificationsManager.cancelScheduledNotification(data[arrayIndex].endNotificationID);
  172. if (data[arrayIndex].reminderNotificationID !== null)
  173. NotificationsManager.cancelScheduledNotification(data[arrayIndex].reminderNotificationID);
  174. this.removeNotificationFromPrefs(arrayIndex);
  175. }
  176. }
  177. }
  178. getMachineIndexInWatchList(machineId: string) {
  179. let elem = this.state.machinesWatched.find(function (elem) {
  180. return elem.machineNumber === machineId
  181. });
  182. return this.state.machinesWatched.indexOf(elem);
  183. }
  184. saveNotificationToPrefs(machineId: string, endNotificationID: string, reminderNotificationID: string | null) {
  185. let data = this.state.machinesWatched;
  186. data.push({
  187. machineNumber: machineId,
  188. endNotificationID: endNotificationID,
  189. reminderNotificationID: reminderNotificationID
  190. });
  191. this.updateNotificationPrefs(data);
  192. }
  193. removeNotificationFromPrefs(index: number) {
  194. let data = this.state.machinesWatched;
  195. data.splice(index, 1);
  196. this.updateNotificationPrefs(data);
  197. }
  198. updateNotificationPrefs(data: Object) {
  199. this.setState({machinesWatched: data});
  200. let prefKey = AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.key;
  201. AsyncStorageManager.getInstance().savePref(prefKey, JSON.stringify(data));
  202. }
  203. /**
  204. * Checks whether the machine of the given ID has scheduled notifications
  205. *
  206. * @param machineID The machine's ID
  207. * @returns {boolean}
  208. */
  209. isMachineWatched(machineID: string) {
  210. return this.state.machinesWatched.find(function (elem) {
  211. return elem.machineNumber === machineID
  212. }) !== undefined;
  213. }
  214. createDataset(fetchedData: Object) {
  215. return [
  216. {
  217. title: i18n.t('proxiwashScreen.dryers'),
  218. icon: 'tumble-dryer',
  219. data: fetchedData.dryers === undefined ? [] : fetchedData.dryers,
  220. extraData: super.state,
  221. keyExtractor: this.getDryersKeyExtractor
  222. },
  223. {
  224. title: i18n.t('proxiwashScreen.washers'),
  225. icon: 'washing-machine',
  226. data: fetchedData.washers === undefined ? [] : fetchedData.washers,
  227. extraData: super.state,
  228. keyExtractor: this.getWashersKeyExtractor
  229. },
  230. ];
  231. }
  232. hasTabs(): boolean {
  233. return true;
  234. }
  235. showAlert(title: string, item: Object, remainingTime: number) {
  236. let buttons = [{text: i18n.t("proxiwashScreen.modal.ok")}];
  237. let message = modalStateStrings[MACHINE_STATES[item.state]];
  238. if (MACHINE_STATES[item.state] === MACHINE_STATES.FONCTIONNE) {
  239. buttons = [
  240. {
  241. text: this.isMachineWatched(item.number) ?
  242. i18n.t("proxiwashScreen.modal.disableNotifications") :
  243. i18n.t("proxiwashScreen.modal.enableNotifications"),
  244. onPress: () => this.setupNotifications(item.number, remainingTime)
  245. },
  246. {
  247. text: i18n.t("proxiwashScreen.modal.cancel")
  248. }
  249. ];
  250. message = i18n.t('proxiwashScreen.modal.running',
  251. {
  252. start: item.startTime,
  253. end: item.endTime,
  254. remaining: remainingTime
  255. });
  256. }
  257. Alert.alert(
  258. title,
  259. message,
  260. buttons
  261. );
  262. }
  263. /**
  264. * Get list item to be rendered
  265. *
  266. * @param item The object containing the item's FetchedData
  267. * @param section The object describing the current SectionList section
  268. * @param data The full FetchedData used by the SectionList
  269. * @returns {React.Node}
  270. */
  271. getRenderItem(item: Object, section: Object, data: Object) {
  272. let isMachineRunning = MACHINE_STATES[item.state] === MACHINE_STATES.FONCTIONNE;
  273. let machineName = (section.title === i18n.t('proxiwashScreen.dryers') ? i18n.t('proxiwashScreen.dryer') : i18n.t('proxiwashScreen.washer')) + ' n°' + item.number;
  274. let remainingTime = 0;
  275. if (isMachineRunning)
  276. remainingTime = ProxiwashScreen.getRemainingTime(item.startTime, item.endTime, item.donePercent);
  277. return (
  278. <Card style={{
  279. flex: 0,
  280. height: 64,
  281. marginLeft: 10,
  282. marginRight: 10
  283. }}>
  284. <CardItem
  285. style={{
  286. backgroundColor: stateColors[MACHINE_STATES[item.state]],
  287. paddingRight: 0,
  288. paddingLeft: 0,
  289. height: '100%',
  290. }}
  291. >
  292. <View style={{
  293. height: 64,
  294. position: 'absolute',
  295. right: 0,
  296. width: item.donePercent !== '' ? (100 - parseInt(item.donePercent)).toString() + '%' : 0,
  297. backgroundColor: ThemeManager.getCurrentThemeVariables().containerBgColor
  298. }}/>
  299. <PlatformTouchable
  300. onPress={() => this.showAlert(machineName, item, remainingTime)}
  301. style={{
  302. height: 64,
  303. position: 'absolute',
  304. zIndex: 10, // Make sure the button is above the text
  305. right: 0,
  306. width: '100%'
  307. }}
  308. >
  309. <View/>
  310. </PlatformTouchable>
  311. <Left style={{marginLeft: 10}}>
  312. <CustomMaterialIcon
  313. icon={section.title === i18n.t('proxiwashScreen.dryers') ? 'tumble-dryer' : 'washing-machine'}
  314. fontSize={30}
  315. />
  316. <Body>
  317. <Text>
  318. {machineName + ' '}
  319. {this.isMachineWatched(item.number) ?
  320. <CustomMaterialIcon
  321. icon='bell-ring'
  322. color={ThemeManager.getCurrentThemeVariables().brandPrimary}
  323. fontSize={20}
  324. /> : ''}
  325. </Text>
  326. <Text note>
  327. {isMachineRunning ? item.startTime + '/' + item.endTime : ''}
  328. </Text>
  329. </Body>
  330. </Left>
  331. <Right style={{marginRight: 10}}>
  332. <Text style={MACHINE_STATES[item.state] === MACHINE_STATES.TERMINE ?
  333. {fontWeight: 'bold'} : {}}
  334. >
  335. {stateStrings[MACHINE_STATES[item.state]]}
  336. </Text>
  337. <CustomMaterialIcon icon={stateIcons[MACHINE_STATES[item.state]]}
  338. fontSize={25}
  339. />
  340. </Right>
  341. </CardItem>
  342. </Card>);
  343. }
  344. }