From 869a8e5ec0d8a8043445bfe6bb1acf2983472895 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Mon, 29 Jun 2020 12:12:14 +0200 Subject: [PATCH] Improved doc and typing --- src/components/Screens/WebViewScreen.js | 48 +++- src/screens/Other/FeedbackScreen.js | 8 +- src/screens/Other/SettingsScreen.js | 7 +- src/screens/Planex/GroupSelectionScreen.js | 49 +++- src/screens/Planex/PlanexScreen.js | 211 +++++++++++------- src/screens/Planning/PlanningDisplayScreen.js | 45 +++- src/screens/Planning/PlanningScreen.js | 41 +--- src/screens/Proxiwash/ProxiwashAboutScreen.js | 4 +- .../Services/Proximo/ProximoAboutScreen.js | 3 +- .../Services/Proximo/ProximoListScreen.js | 11 +- .../Services/Proximo/ProximoMainScreen.js | 34 +-- src/screens/Services/ServicesScreen.js | 49 ++-- src/screens/Services/ServicesSectionScreen.js | 8 +- src/utils/WebData.js | 2 +- 14 files changed, 338 insertions(+), 182 deletions(-) diff --git a/src/components/Screens/WebViewScreen.js b/src/components/Screens/WebViewScreen.js index 5af746d..8dfb5f8 100644 --- a/src/components/Screens/WebViewScreen.js +++ b/src/components/Screens/WebViewScreen.js @@ -41,7 +41,7 @@ class WebViewScreen extends React.PureComponent { customPaddingFunction: null, }; - webviewRef: Object; + webviewRef: { current: null | WebView }; canGoBack: boolean; @@ -52,7 +52,7 @@ class WebViewScreen extends React.PureComponent { } /** - * Creates refresh button after mounting + * Creates header buttons and listens to events after mounting */ componentDidMount() { this.props.navigation.setOptions({ @@ -78,6 +78,11 @@ class WebViewScreen extends React.PureComponent { ); } + /** + * Goes back on the webview or on the navigation stack if we cannot go back anymore + * + * @returns {boolean} + */ onBackButtonPressAndroid = () => { if (this.canGoBack) { this.onGoBackClicked(); @@ -87,7 +92,7 @@ class WebViewScreen extends React.PureComponent { }; /** - * Gets a header refresh button + * Gets header refresh and open in browser buttons * * @return {*} */ @@ -106,6 +111,12 @@ class WebViewScreen extends React.PureComponent { ); }; + /** + * Creates advanced header control buttons. + * These buttons allows the user to refresh, go back, go forward and open in the browser. + * + * @returns {*} + */ getAdvancedButtons = () => { return ( @@ -141,14 +152,28 @@ class WebViewScreen extends React.PureComponent { /** * Callback to use when refresh button is clicked. Reloads the webview. */ - onRefreshClicked = () => this.webviewRef.current.reload(); - onGoBackClicked = () => this.webviewRef.current.goBack(); - onGoForwardClicked = () => this.webviewRef.current.goForward(); - + onRefreshClicked = () => { + if (this.webviewRef.current != null) + this.webviewRef.current.reload(); + } + onGoBackClicked = () => { + if (this.webviewRef.current != null) + this.webviewRef.current.goBack(); + } + onGoForwardClicked = () => { + if (this.webviewRef.current != null) + this.webviewRef.current.goForward(); + } onOpenClicked = () => Linking.openURL(this.props.url); + /** + * Injects the given javascript string into the web page + * + * @param script The script to inject + */ injectJavaScript = (script: string) => { - this.webviewRef.current.injectJavaScript(script); + if (this.webviewRef.current != null) + this.webviewRef.current.injectJavaScript(script); } /** @@ -158,6 +183,13 @@ class WebViewScreen extends React.PureComponent { */ getRenderLoading = () => ; + /** + * Gets the javascript needed to generate a padding on top of the page + * This adds padding to the body and runs the custom padding function given in props + * + * @param padding The padding to add in pixels + * @returns {string} + */ getJavascriptPadding(padding: number) { const customPadding = this.props.customPaddingFunction != null ? this.props.customPaddingFunction(padding) : ""; return ( diff --git a/src/screens/Other/FeedbackScreen.js b/src/screens/Other/FeedbackScreen.js index 50ea16a..148b466 100644 --- a/src/screens/Other/FeedbackScreen.js +++ b/src/screens/Other/FeedbackScreen.js @@ -26,6 +26,12 @@ Stp corrige le pb, bien cordialement.`, class FeedbackScreen extends React.Component { + /** + * Gets link buttons + * + * @param isBug True if buttons should redirect to bug report methods + * @returns {*} + */ getButtons(isBug: boolean) { return ( { } } -export default withTheme(FeedbackScreen); \ No newline at end of file +export default withTheme(FeedbackScreen); diff --git a/src/screens/Other/SettingsScreen.js b/src/screens/Other/SettingsScreen.js index 6d5bdd0..fbe01b6 100644 --- a/src/screens/Other/SettingsScreen.js +++ b/src/screens/Other/SettingsScreen.js @@ -31,6 +31,9 @@ class SettingsScreen extends React.Component { savedNotificationReminder: number; + /** + * Loads user preferences into state + */ constructor() { super(); let notifReminder = AsyncStorageManager.getInstance().preferences.proxiwashNotifications.current; @@ -49,7 +52,7 @@ class SettingsScreen extends React.Component { } /** - * Unlocks debug mode + * Unlocks debug mode and saves its state to user preferences */ unlockDebugMode = () => { this.setState({isDebugUnlocked: true}); @@ -227,7 +230,7 @@ class SettingsScreen extends React.Component { left={props => } onPress={() => this.props.navigation.navigate("debug")} /> - :null} + : null} { @@ -106,10 +106,21 @@ class GroupSelectionScreen extends React.Component { }); }; + /** + * Callback used when the user clicks on the favorite button + * + * @param item The item to add/remove from favorites + */ onListFavoritePress = (item: group) => { this.updateGroupFavorites(item); }; + /** + * Checks if the given group is in the favorites list + * + * @param group The group to check + * @returns {boolean} + */ isGroupInFavorites(group: group) { let isFav = false; for (let i = 0; i < this.state.favoriteGroups.length; i++) { @@ -121,6 +132,12 @@ class GroupSelectionScreen extends React.Component { return isFav; } + /** + * Removes the given group from the given array + * + * @param favorites The array containing favorites groups + * @param group The group to remove from the array + */ removeGroupFromFavorites(favorites: Array, group: group) { for (let i = 0; i < favorites.length; i++) { if (group.id === favorites[i].id) { @@ -130,12 +147,24 @@ class GroupSelectionScreen extends React.Component { } } + /** + * Adds the given group to the given array + * + * @param favorites The array containing favorites groups + * @param group The group to add to the array + */ addGroupToFavorites(favorites: Array, group: group) { group.isFav = true; favorites.push(group); favorites.sort(sortName); } + /** + * Adds or removes the given group to the favorites list, depending on whether it is already in it or not. + * Favorites are then saved in user preferences + * + * @param group The group to add/remove to favorites + */ updateGroupFavorites(group: group) { let newFavorites = [...this.state.favoriteGroups] if (this.isGroupInFavorites(group)) @@ -148,6 +177,12 @@ class GroupSelectionScreen extends React.Component { JSON.stringify(newFavorites)); } + /** + * Checks whether to display the given group category, depending on user search query + * + * @param item The group category + * @returns {boolean} + */ shouldDisplayAccordion(item: groupCategory) { let shouldDisplay = false; for (let i = 0; i < item.content.length; i++) { @@ -181,6 +216,13 @@ class GroupSelectionScreen extends React.Component { return null; }; + /** + * Generates the dataset to be used in the FlatList. + * This improves formatting of group names, sorts alphabetically the categories, and adds favorites at the top. + * + * @param fetchedData The raw data fetched from the server + * @returns {[]} + */ generateData(fetchedData: { [key: string]: groupCategory }) { let data = []; for (let key in fetchedData) { @@ -192,6 +234,11 @@ class GroupSelectionScreen extends React.Component { return data; } + /** + * Replaces underscore by spaces and sets the favorite state of every group in the given category + * + * @param item The category containing groups to format + */ formatGroups(item: groupCategory) { for (let i = 0; i < item.content.length; i++) { item.content[i].name = item.content[i].name.replace(REPLACE_REGEX, " ") diff --git a/src/screens/Planex/PlanexScreen.js b/src/screens/Planex/PlanexScreen.js index e05f5b2..0542d9d 100644 --- a/src/screens/Planex/PlanexScreen.js +++ b/src/screens/Planex/PlanexScreen.js @@ -1,6 +1,7 @@ // @flow import * as React from 'react'; +import type {CustomTheme} from "../../managers/ThemeManager"; import ThemeManager from "../../managers/ThemeManager"; import WebViewScreen from "../../components/Screens/WebViewScreen"; import {Avatar, Banner, withTheme} from "react-native-paper"; @@ -14,12 +15,15 @@ import DateManager from "../../managers/DateManager"; import AnimatedBottomBar from "../../components/Animations/AnimatedBottomBar"; import {CommonActions} from "@react-navigation/native"; import ErrorView from "../../components/Screens/ErrorView"; +import {StackNavigationProp} from "@react-navigation/stack"; +import {Collapsible} from "react-navigation-collapsible"; +import type {group} from "./GroupSelectionScreen"; type Props = { - navigation: Object, - route: Object, - theme: Object, - collapsibleStack: Object, + navigation: StackNavigationProp, + route: { params: { group: group } }, + theme: CustomTheme, + collapsibleStack: Collapsible, } type State = { @@ -27,7 +31,7 @@ type State = { dialogVisible: boolean, dialogTitle: string, dialogMessage: string, - currentGroup: Object, + currentGroup: group, } @@ -62,7 +66,7 @@ const PLANEX_URL = 'http://planex.insa-toulouse.fr/'; // removeAlpha($(this)); // }); -// Watch for changes in the calendar and call the remove alpha function +// Watch for changes in the calendar and call the remove alpha function to prevent invisible events const OBSERVE_MUTATIONS_INJECTED = 'function removeAlpha(node) {\n' + ' let bg = node.css("background-color");\n' + @@ -91,6 +95,7 @@ const OBSERVE_MUTATIONS_INJECTED = ' removeAlpha($(this));\n' + '});'; +// Overrides default settings to send a message to the webview when clicking on an event const FULL_CALENDAR_SETTINGS = ` var calendar = $('#calendar').fullCalendar('getCalendar'); calendar.option({ @@ -105,16 +110,6 @@ calendar.option({ } });`; -const EXEC_COMMAND = ` -function execCommand(event) { - alert(JSON.stringify(event)); - var data = JSON.parse(event.data); - if (data.action === "setGroup") - displayAde(data.data); - else - $('#calendar').fullCalendar(data.action, data.data); -};` - const CUSTOM_CSS = "body>.container{padding-top:20px; padding-bottom: 50px}header,#entite,#groupe_visibility,#calendar .fc-left,#calendar .fc-right{display:none}.fc-toolbar .fc-center{width:100%}.fc-toolbar .fc-center>*{float:none;width:100%;margin:0}#entite{margin-bottom:5px!important}#entite,#groupe{width:calc(100% - 20px);margin:0 10px}#groupe_visibility{width:100%}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-time{font-size:.5rem}#calendar .fc-month-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-month-view .fc-content-skeleton .fc-time{font-size:.7rem}.fc-axis{font-size:.8rem;width:15px!important}.fc-day-header{font-size:.8rem}.fc-unthemed td.fc-today{background:#be1522; opacity:0.4}"; const CUSTOM_CSS_DARK = "body{background-color:#121212}.fc-unthemed .fc-content,.fc-unthemed .fc-divider,.fc-unthemed .fc-list-heading td,.fc-unthemed .fc-list-view,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#222}.fc-toolbar .fc-center>*,h2,table{color:#fff}.fc-event-container{color:#121212}.fc-event-container .fc-bg{opacity:0.2;background-color:#000}.fc-unthemed td.fc-today{background:#be1522; opacity:0.4}"; @@ -129,8 +124,8 @@ $('head').append(''); */ class PlanexScreen extends React.Component { - webScreenRef: Object; - barRef: Object; + webScreenRef: { current: null | WebViewScreen }; + barRef: { current: null | AnimatedBottomBar }; customInjectedJS: string; @@ -144,7 +139,7 @@ class PlanexScreen extends React.Component { let currentGroup = AsyncStorageManager.getInstance().preferences.planexCurrentGroup.current; if (currentGroup === '') - currentGroup = {name: "SELECT GROUP", id: -1}; + currentGroup = {name: "SELECT GROUP", id: -1, isFav: false}; else { currentGroup = JSON.parse(currentGroup); props.navigation.setOptions({title: currentGroup.name}) @@ -159,6 +154,9 @@ class PlanexScreen extends React.Component { this.generateInjectedJS(currentGroup.id); } + /** + * Register for events and show the banner after 2 seconds + */ componentDidMount() { this.props.navigation.addListener('focus', this.onScreenFocus); setTimeout(this.onBannerTimeout, 2000); @@ -172,54 +170,6 @@ class PlanexScreen extends React.Component { }) } - onScreenFocus = () => { - this.handleNavigationParams(); - }; - - handleNavigationParams = () => { - if (this.props.route.params !== undefined) { - if (this.props.route.params.group !== undefined && this.props.route.params.group !== null) { - // reset params to prevent infinite loop - this.selectNewGroup(this.props.route.params.group); - this.props.navigation.dispatch(CommonActions.setParams({group: null})); - } - } - }; - - selectNewGroup(group: Object) { - this.sendMessage('setGroup', group.id); - this.setState({currentGroup: group}); - AsyncStorageManager.getInstance().savePref( - AsyncStorageManager.getInstance().preferences.planexCurrentGroup.key, - JSON.stringify(group)); - this.props.navigation.setOptions({title: group.name}) - this.generateInjectedJS(group.id); - } - - generateInjectedJS(groupID: number) { - this.customInjectedJS = "$(document).ready(function() {" - + OBSERVE_MUTATIONS_INJECTED - + FULL_CALENDAR_SETTINGS - + "displayAde(" + groupID + ");" // Reset Ade - + (DateManager.isWeekend(new Date()) ? "calendar.next()" : "") - + INJECT_STYLE; - - if (ThemeManager.getNightMode()) - this.customInjectedJS += "$('head').append('');"; - - this.customInjectedJS += - 'removeAlpha();' - + '});' - + EXEC_COMMAND - + 'true;'; // Prevents crash on ios - } - - shouldComponentUpdate(nextProps: Props): boolean { - if (nextProps.theme.dark !== this.props.theme.dark) - this.generateInjectedJS(this.state.currentGroup.id); - return true; - } - /** * Callback used when closing the banner. * This hides the banner and saves to preferences to prevent it from reopening @@ -232,34 +182,122 @@ class PlanexScreen extends React.Component { ); }; + /** - * Callback used when the used click on the navigate to settings button. + * Callback used when the user clicks on the navigate to settings button. * This will hide the banner and open the SettingsScreen - * */ onGoToSettings = () => { this.onHideBanner(); this.props.navigation.navigate('settings'); }; + onScreenFocus = () => { + this.handleNavigationParams(); + }; + + /** + * If navigations parameters contain a group, set it as selected + */ + handleNavigationParams = () => { + if (this.props.route.params != null) { + if (this.props.route.params.group !== undefined && this.props.route.params.group !== null) { + // reset params to prevent infinite loop + this.selectNewGroup(this.props.route.params.group); + this.props.navigation.dispatch(CommonActions.setParams({group: null})); + } + } + }; + + /** + * Sends the webpage a message with the new group to select and save it to preferences + * + * @param group The group object selected + */ + selectNewGroup(group: group) { + this.sendMessage('setGroup', group.id); + this.setState({currentGroup: group}); + AsyncStorageManager.getInstance().savePref( + AsyncStorageManager.getInstance().preferences.planexCurrentGroup.key, + JSON.stringify(group) + ); + this.props.navigation.setOptions({title: group.name}); + this.generateInjectedJS(group.id); + } + + /** + * Generates custom JavaScript to be injected into the webpage + * + * @param groupID The current group selected + */ + generateInjectedJS(groupID: number) { + this.customInjectedJS = "$(document).ready(function() {" + + OBSERVE_MUTATIONS_INJECTED + + FULL_CALENDAR_SETTINGS + + "displayAde(" + groupID + ");" // Reset Ade + + (DateManager.isWeekend(new Date()) ? "calendar.next()" : "") + + INJECT_STYLE; + + if (ThemeManager.getNightMode()) + this.customInjectedJS += "$('head').append('');"; + + this.customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios + } + + /** + * Only update the screen if the dark theme changed + * + * @param nextProps + * @returns {boolean} + */ + shouldComponentUpdate(nextProps: Props): boolean { + if (nextProps.theme.dark !== this.props.theme.dark) + this.generateInjectedJS(this.state.currentGroup.id); + return true; + } + + + /** + * Sends a FullCalendar action to the web page inside the webview. + * + * @param action The action to perform, as described in the FullCalendar doc https://fullcalendar.io/docs/v3. + * Or "setGroup" with the group id as data to set the selected group + * @param data Data to pass to the action + */ sendMessage = (action: string, data: any) => { let command; if (action === "setGroup") command = "displayAde(" + data + ")"; else command = "$('#calendar').fullCalendar('" + action + "', '" + data + "')"; - this.webScreenRef.current.injectJavaScript(command + ';true;'); - } + if (this.webScreenRef.current != null) + this.webScreenRef.current.injectJavaScript(command + ';true;'); // Injected javascript must end with true + }; + + /** + * Shows a dialog when the user clicks on an event. + * + * @param event + */ + onMessage = (event: { nativeEvent: { data: string } }) => { + const data: { start: string, end: string, title: string, color: string } = JSON.parse(event.nativeEvent.data); + const startDate = dateToString(new Date(data.start), true); + const endDate = dateToString(new Date(data.end), true); + const startString = getTimeOnlyString(startDate); + const endString = getTimeOnlyString(endDate); - onMessage = (event: Object) => { - let data = JSON.parse(event.nativeEvent.data); - let startDate = dateToString(new Date(data.start), true); - let endDate = dateToString(new Date(data.end), true); let msg = DateManager.getInstance().getTranslatedDate(startDate) + "\n"; - msg += getTimeOnlyString(startDate) + ' - ' + getTimeOnlyString(endDate); + if (startString != null && endString != null) + msg += startString + ' - ' + endDate; this.showDialog(data.title, msg) }; + /** + * Shows a simple dialog to the user. + * + * @param title The dialog's title + * @param message The message to show + */ showDialog = (title: string, message: string) => { this.setState({ dialogVisible: true, @@ -268,16 +306,30 @@ class PlanexScreen extends React.Component { }); }; + /** + * Hides the dialog + */ hideDialog = () => { this.setState({ dialogVisible: false, }); }; - onScroll = (event: Object) => { - this.barRef.current.onScroll(event); + /** + * Binds the onScroll event to the control bar for automatic hiding based on scroll direction and speed + * + * @param event + */ + onScroll = (event: SyntheticEvent) => { + if (this.barRef.current != null) + this.barRef.current.onScroll(event); }; + /** + * Gets the Webview, with an error view on top if no group is selected. + * + * @returns {*} + */ getWebView() { const showWebview = this.state.currentGroup.id !== -1; @@ -291,7 +343,6 @@ class PlanexScreen extends React.Component { showRetryButton={false} /> : null} - { height: '100%', width: '100%', }}> - {this.props.theme.dark // Force component theme update + {this.props.theme.dark // Force component theme update by recreating it on theme change ? this.getWebView() : {this.getWebView()}} diff --git a/src/screens/Planning/PlanningDisplayScreen.js b/src/screens/Planning/PlanningDisplayScreen.js index 904557c..00b787a 100644 --- a/src/screens/Planning/PlanningDisplayScreen.js +++ b/src/screens/Planning/PlanningDisplayScreen.js @@ -12,10 +12,13 @@ import ErrorView from "../../components/Screens/ErrorView"; import CustomHTML from "../../components/Overrides/CustomHTML"; import CustomTabBar from "../../components/Tabbar/CustomTabBar"; import i18n from 'i18n-js'; +import {StackNavigationProp} from "@react-navigation/stack"; +import type {CustomTheme} from "../../managers/ThemeManager"; type Props = { - navigation: Object, - route: Object + navigation: StackNavigationProp, + route: { params: { data: Object, id: number, eventId: number } }, + theme: CustomTheme }; type State = { @@ -34,13 +37,15 @@ class PlanningDisplayScreen extends React.Component { eventId: number; errorCode: number; - colors: Object; - + /** + * Generates data depending on whether the screen was opened from the planning or from a link + * + * @param props + */ constructor(props) { super(props); - this.colors = props.theme.colors; - if (this.props.route.params.data !== undefined) { + if (this.props.route.params.data != null) { this.displayData = this.props.route.params.data; this.eventId = this.displayData.id; this.shouldFetchData = false; @@ -61,6 +66,9 @@ class PlanningDisplayScreen extends React.Component { } } + /** + * Fetches data for the current event id from the API + */ fetchData = () => { this.setState({loading: true}); apiRequest(CLUB_INFO_PATH, 'POST', {id: this.eventId}) @@ -68,16 +76,31 @@ class PlanningDisplayScreen extends React.Component { .catch(this.onFetchError); }; + /** + * Hides loading and saves fetched data + * + * @param data Received data + */ onFetchSuccess = (data: Object) => { this.displayData = data; this.setState({loading: false}); }; + /** + * Hides loading and saves the error code + * + * @param error + */ onFetchError = (error: number) => { this.errorCode = error; this.setState({loading: false}); }; + /** + * Gets content to display + * + * @returns {*} + */ getContent() { let subtitle = getFormattedEventTime( this.displayData["date_begin"], this.displayData["date_end"]); @@ -94,7 +117,7 @@ class PlanningDisplayScreen extends React.Component { { ); } + /** + * Shows an error view and use a custom message if the event does not exist + * + * @returns {*} + */ getErrorView() { if (this.errorCode === ERROR_TYPE.BAD_INPUT) - return ; + return ; else return ; } diff --git a/src/screens/Planning/PlanningScreen.js b/src/screens/Planning/PlanningScreen.js index 438d9d6..ae420d5 100644 --- a/src/screens/Planning/PlanningScreen.js +++ b/src/screens/Planning/PlanningScreen.js @@ -14,6 +14,7 @@ import { } from '../../utils/Planning'; import {Avatar, Divider, List} from 'react-native-paper'; import CustomAgenda from "../../components/Overrides/CustomAgenda"; +import {StackNavigationProp} from "@react-navigation/stack"; LocaleConfig.locales['fr'] = { monthNames: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'], @@ -25,8 +26,7 @@ LocaleConfig.locales['fr'] = { type Props = { - navigation: Object, - route: Object, + navigation: StackNavigationProp, } type State = { @@ -48,22 +48,12 @@ class PlanningScreen extends React.Component { lastRefresh: Date; minTimeBetweenRefresh = 60; - didFocusSubscription: Function; - willBlurSubscription: Function; - state = { refreshing: false, agendaItems: {}, calendarShowing: false, }; - onRefresh: Function; - onCalendarToggled: Function; - getRenderItem: Function; - getRenderEmptyDate: Function; - onAgendaRef: Function; - onCalendarToggled: Function; - onBackButtonPressAndroid: Function; currentDate = getDateOnlyString(getCurrentDateString()); constructor(props: any) { @@ -71,15 +61,6 @@ class PlanningScreen extends React.Component { if (i18n.currentLocale().startsWith("fr")) { LocaleConfig.defaultLocale = 'fr'; } - - // Create references for functions required in the render function - this.onRefresh = this.onRefresh.bind(this); - this.onCalendarToggled = this.onCalendarToggled.bind(this); - this.getRenderItem = this.getRenderItem.bind(this); - this.getRenderEmptyDate = this.getRenderEmptyDate.bind(this); - this.onAgendaRef = this.onAgendaRef.bind(this); - this.onCalendarToggled = this.onCalendarToggled.bind(this); - this.onBackButtonPressAndroid = this.onBackButtonPressAndroid.bind(this); } /** @@ -87,7 +68,7 @@ class PlanningScreen extends React.Component { */ componentDidMount() { this.onRefresh(); - this.didFocusSubscription = this.props.navigation.addListener( + this.props.navigation.addListener( 'focus', () => BackHandler.addEventListener( @@ -95,7 +76,7 @@ class PlanningScreen extends React.Component { this.onBackButtonPressAndroid ) ); - this.willBlurSubscription = this.props.navigation.addListener( + this.props.navigation.addListener( 'blur', () => BackHandler.removeEventListener( @@ -110,7 +91,7 @@ class PlanningScreen extends React.Component { * * @return {boolean} */ - onBackButtonPressAndroid() { + onBackButtonPressAndroid = () => { if (this.state.calendarShowing) { this.agendaRef.chooseDay(this.agendaRef.state.selectedDay); return true; @@ -166,7 +147,7 @@ class PlanningScreen extends React.Component { * * @param ref */ - onAgendaRef(ref: Object) { + onAgendaRef = (ref: Object) => { this.agendaRef = ref; } @@ -175,7 +156,7 @@ class PlanningScreen extends React.Component { * * @param isCalendarOpened True is the calendar is already open, false otherwise */ - onCalendarToggled(isCalendarOpened: boolean) { + onCalendarToggled = (isCalendarOpened: boolean) => { this.setState({calendarShowing: isCalendarOpened}); } @@ -185,7 +166,7 @@ class PlanningScreen extends React.Component { * @param item The current event to render * @return {*} */ - getRenderItem(item: eventObject) { + getRenderItem = (item: eventObject) => { const onPress = this.props.navigation.navigate.bind(this, 'planning-information', {data: item}); if (item.logo !== null) { return ( @@ -221,11 +202,7 @@ class PlanningScreen extends React.Component { * * @return {*} */ - getRenderEmptyDate() { - return ( - - ); - } + getRenderEmptyDate = () => ; render() { return ( diff --git a/src/screens/Proxiwash/ProxiwashAboutScreen.js b/src/screens/Proxiwash/ProxiwashAboutScreen.js index 2f4a040..1bf87b6 100644 --- a/src/screens/Proxiwash/ProxiwashAboutScreen.js +++ b/src/screens/Proxiwash/ProxiwashAboutScreen.js @@ -6,9 +6,7 @@ import i18n from "i18n-js"; import {Card, List, Paragraph, Text, Title} from 'react-native-paper'; import CustomTabBar from "../../components/Tabbar/CustomTabBar"; -type Props = { - navigation: Object, -}; +type Props = {}; const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/Proxiwash.png"; diff --git a/src/screens/Services/Proximo/ProximoAboutScreen.js b/src/screens/Services/Proximo/ProximoAboutScreen.js index 93a5a98..c51b6af 100644 --- a/src/screens/Services/Proximo/ProximoAboutScreen.js +++ b/src/screens/Services/Proximo/ProximoAboutScreen.js @@ -5,9 +5,10 @@ import {Image, ScrollView, View} from 'react-native'; import i18n from "i18n-js"; import {Card, List, Paragraph, Text} from 'react-native-paper'; import CustomTabBar from "../../../components/Tabbar/CustomTabBar"; +import {StackNavigationProp} from "@react-navigation/stack"; type Props = { - navigation: Object, + navigation: StackNavigationProp, }; const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png"; diff --git a/src/screens/Services/Proximo/ProximoListScreen.js b/src/screens/Services/Proximo/ProximoListScreen.js index c8d6735..f62a4b9 100644 --- a/src/screens/Services/Proximo/ProximoListScreen.js +++ b/src/screens/Services/Proximo/ProximoListScreen.js @@ -9,6 +9,9 @@ import {stringMatchQuery} from "../../../utils/Search"; import ProximoListItem from "../../../components/Lists/Proximo/ProximoListItem"; import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton"; import {withCollapsible} from "../../../utils/withCollapsible"; +import {StackNavigationProp} from "@react-navigation/stack"; +import type {CustomTheme} from "../../../managers/ThemeManager"; +import {Collapsible} from "react-navigation-collapsible"; function sortPrice(a, b) { return a.price - b.price; @@ -37,10 +40,10 @@ function sortNameReverse(a, b) { const LIST_ITEM_HEIGHT = 84; type Props = { - navigation: Object, - route: Object, - theme: Object, - collapsibleStack: Object, + navigation: StackNavigationProp, + route: { params: { data: { data: Object }, shouldFocusSearchBar: boolean } }, + theme: CustomTheme, + collapsibleStack: Collapsible, } type State = { diff --git a/src/screens/Services/Proximo/ProximoMainScreen.js b/src/screens/Services/Proximo/ProximoMainScreen.js index 423deb8..0f6322d 100644 --- a/src/screens/Services/Proximo/ProximoMainScreen.js +++ b/src/screens/Services/Proximo/ProximoMainScreen.js @@ -6,13 +6,15 @@ import i18n from "i18n-js"; import WebSectionList from "../../../components/Screens/WebSectionList"; import {List, withTheme} from 'react-native-paper'; import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton"; +import {StackNavigationProp} from "@react-navigation/stack"; +import type {CustomTheme} from "../../../managers/ThemeManager"; const DATA_URL = "https://etud.insa-toulouse.fr/~proximo/data/stock-v2.json"; const LIST_ITEM_HEIGHT = 84; type Props = { - navigation: Object, - route: Object, + navigation: StackNavigationProp, + theme: CustomTheme, } type State = { @@ -27,22 +29,6 @@ class ProximoMainScreen extends React.Component { articles: Object; - onPressSearchBtn: Function; - onPressAboutBtn: Function; - getRenderItem: Function; - createDataset: Function; - - colors: Object; - - constructor(props) { - super(props); - this.onPressSearchBtn = this.onPressSearchBtn.bind(this); - this.onPressAboutBtn = this.onPressAboutBtn.bind(this); - this.getRenderItem = this.getRenderItem.bind(this); - this.createDataset = this.createDataset.bind(this); - this.colors = props.theme.colors; - } - /** * Function used to sort items in the list. * Makes the All category stick to the top and sorts the others by name ascending @@ -83,7 +69,7 @@ class ProximoMainScreen extends React.Component { * Callback used when the search button is pressed. * This will open a new ProximoListScreen with all items displayed */ - onPressSearchBtn() { + onPressSearchBtn = () => { let searchScreenData = { shouldFocusSearchBar: true, data: { @@ -97,13 +83,13 @@ class ProximoMainScreen extends React.Component { }, }; this.props.navigation.navigate('proximo-list', searchScreenData); - } + }; /** * Callback used when the about button is pressed. * This will open the ProximoAboutScreen */ - onPressAboutBtn() { + onPressAboutBtn = () => { this.props.navigation.navigate('proximo-about'); } @@ -134,7 +120,7 @@ class ProximoMainScreen extends React.Component { * @param fetchedData * @return {*} * */ - createDataset(fetchedData: Object) { + createDataset = (fetchedData: Object) => { return [ { title: '', @@ -202,7 +188,7 @@ class ProximoMainScreen extends React.Component { * @param item The category to render * @return {*} */ - getRenderItem({item}: Object) { + getRenderItem = ({item}: Object) => { let dataToSend = { shouldFocusSearchBar: false, data: item, @@ -218,7 +204,7 @@ class ProximoMainScreen extends React.Component { left={props => } + color={this.props.theme.colors.primary}/>} right={props => } style={{ height: LIST_ITEM_HEIGHT, diff --git a/src/screens/Services/ServicesScreen.js b/src/screens/Services/ServicesScreen.js index 8def775..5677719 100644 --- a/src/screens/Services/ServicesScreen.js +++ b/src/screens/Services/ServicesScreen.js @@ -12,14 +12,22 @@ import type {CustomTheme} from "../../managers/ThemeManager"; import i18n from 'i18n-js'; import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton"; import ConnectionManager from "../../managers/ConnectionManager"; +import {StackNavigationProp} from "@react-navigation/stack"; type Props = { - navigation: Object, - route: Object, + navigation: StackNavigationProp, collapsibleStack: Collapsible, theme: CustomTheme, } +export type listItem = { + title: string, + description: string, + image: string | number, + shouldLogin: boolean, + content: cardList, +} + const CLUBS_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Clubs.png"; const PROFILE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ProfilAmicaliste.png"; const VOTE_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Vote.png"; @@ -36,17 +44,7 @@ const ROOM_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Salles.png const EMAIL_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/Bluemind.png"; const ENT_IMAGE = "https://etud.insa-toulouse.fr/~amicale_app/images/ENT.png"; -export type listItem = { - title: string, - description: string, - image: string | number, - shouldLogin: boolean, - content: cardList, -} - -type State = {} - -class ServicesScreen extends React.Component { +class ServicesScreen extends React.Component { amicaleDataset: cardList; studentsDataset: cardList; @@ -179,7 +177,17 @@ class ServicesScreen extends React.Component { onAboutPress = () => this.props.navigation.navigate('amicale-contact'); - getAvatar(props, source: string | number) { + /** + * Gets the list title image for the list. + * + * If the source is a string, we are using an icon. + * If the source is a number, we are using an internal image. + * + * @param props Props to pass to the component + * @param source The source image to display. Can be a string for icons or a number for local images + * @returns {*} + */ + getListTitleImage(props, source: string | number) { if (typeof source === "number") return { /> } + /** + * Redirects to the given route or to the login screen if user is not logged in. + * + * @param route The route to navigate to + */ onAmicaleServicePress(route: string) { if (ConnectionManager.getInstance().isLoggedIn()) this.props.navigation.navigate(route); @@ -204,6 +217,12 @@ class ServicesScreen extends React.Component { this.props.navigation.navigate("login", {nextScreen: route}); } + /** + * A list item showing a list of available services for the current category + * + * @param item + * @returns {*} + */ renderItem = ({item}: { item: listItem }) => { return ( { this.getAvatar(props, item.image)} + left={(props) => this.getListTitleImage(props, item.image)} right={(props) => } /> { this.handleNavigationParams(); } + /** + * Recover the list to display from navigation parameters + */ handleNavigationParams() { if (this.props.route.params != null) { if (this.props.route.params.data != null) { diff --git a/src/utils/WebData.js b/src/utils/WebData.js index c28d231..03bef6f 100644 --- a/src/utils/WebData.js +++ b/src/utils/WebData.js @@ -32,7 +32,7 @@ const API_ENDPOINT = "https://www.amicale-insat.fr/api/"; * @param params The params to use for this request * @returns {Promise} */ -export async function apiRequest(path: string, method: string, params: ?{ [key: string]: string }) { +export async function apiRequest(path: string, method: string, params: ?{ [key: string]: string | number }) { if (params === undefined || params === null) params = {};