Update utility files to use TypeScript

This commit is contained in:
Arnaud Vergnet 2020-09-21 22:40:58 +02:00
parent 375fc8b971
commit 18ec6e0a59
22 changed files with 318 additions and 268 deletions

16
package-lock.json generated
View file

@ -2501,6 +2501,12 @@
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz", "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
"integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ==" "integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
}, },
"@types/i18n-js": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/i18n-js/-/i18n-js-3.0.3.tgz",
"integrity": "sha512-GiZzazvxQ5j+EA4Zf4MtDsSaokAR/gW7FxxTlHi2p2xKFUhwAUT0B/MB8WL77P1TcqAO3MefWorFFyZS8F7s0Q==",
"dev": true
},
"@types/istanbul-lib-coverage": { "@types/istanbul-lib-coverage": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
@ -2602,6 +2608,16 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/react-native-vector-icons": {
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.6.tgz",
"integrity": "sha512-lAyxNfMd5L1xZvXWsGcJmNegDf61TAp40uL6ashNNWj9W3IrDJO59L9+9inh0Y2MsEZpLTdxzVU8Unb4/0FQng==",
"dev": true,
"requires": {
"@types/react": "*",
"@types/react-native": "*"
}
},
"@types/react-test-renderer": { "@types/react-test-renderer": {
"version": "16.9.3", "version": "16.9.3",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.3.tgz", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.3.tgz",

View file

@ -66,6 +66,8 @@
"@babel/core": "^7.11.0", "@babel/core": "^7.11.0",
"@babel/runtime": "^7.11.0", "@babel/runtime": "^7.11.0",
"@react-native-community/eslint-config": "^1.1.0", "@react-native-community/eslint-config": "^1.1.0",
"@types/i18n-js": "^3.0.3",
"@types/react-native-vector-icons": "^6.4.6",
"@types/jest": "^25.2.3", "@types/jest": "^25.2.3",
"@types/react-native": "^0.63.2", "@types/react-native": "^0.63.2",
"@types/react-test-renderer": "^16.9.2", "@types/react-test-renderer": "^16.9.2",

View file

@ -17,10 +17,7 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen'; import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen';
import type {CustomThemeType} from './ThemeManager';
import type {RuFoodCategoryType} from '../screens/Services/SelfMenuScreen'; import type {RuFoodCategoryType} from '../screens/Services/SelfMenuScreen';
/** /**
@ -57,8 +54,9 @@ export default class AprilFoolsManager {
* @returns {ThemeManager} * @returns {ThemeManager}
*/ */
static getInstance(): AprilFoolsManager { static getInstance(): AprilFoolsManager {
if (AprilFoolsManager.instance == null) if (AprilFoolsManager.instance == null) {
AprilFoolsManager.instance = new AprilFoolsManager(); AprilFoolsManager.instance = new AprilFoolsManager();
}
return AprilFoolsManager.instance; return AprilFoolsManager.instance;
} }
@ -130,7 +128,9 @@ export default class AprilFoolsManager {
* @param currentTheme * @param currentTheme
* @returns {{colors: {textDisabled: string, agendaDayTextColor: string, surface: string, background: string, dividerBackground: string, accent: string, agendaBackgroundColor: string, tabIcon: string, card: string, primary: string}}} * @returns {{colors: {textDisabled: string, agendaDayTextColor: string, surface: string, background: string, dividerBackground: string, accent: string, agendaBackgroundColor: string, tabIcon: string, card: string, primary: string}}}
*/ */
static getAprilFoolsTheme(currentTheme: CustomThemeType): CustomThemeType { static getAprilFoolsTheme(
currentTheme: ReactNativePaper.Theme,
): ReactNativePaper.Theme {
return { return {
...currentTheme, ...currentTheme,
colors: { colors: {

View file

@ -198,7 +198,7 @@ export default class AsyncStorageManager {
* @param key * @param key
* @returns {{...}} * @returns {{...}}
*/ */
static getObject(key: string): object | Array<any> { static getObject<T>(key: string): T {
return JSON.parse(AsyncStorageManager.getString(key)); return JSON.parse(AsyncStorageManager.getString(key));
} }

View file

@ -20,7 +20,7 @@
// @flow // @flow
import * as Keychain from 'react-native-keychain'; import * as Keychain from 'react-native-keychain';
import type {ApiDataLoginType, ApiGenericDataType} from '../utils/WebData'; import type {ApiDataLoginType} from '../utils/WebData';
import {apiRequest, ERROR_TYPE} from '../utils/WebData'; import {apiRequest, ERROR_TYPE} from '../utils/WebData';
/** /**
@ -40,12 +40,10 @@ const AUTH_PATH = 'password';
export default class ConnectionManager { export default class ConnectionManager {
static instance: ConnectionManager | null = null; static instance: ConnectionManager | null = null;
#email: string; private token: string | null;
#token: string | null;
constructor() { constructor() {
this.#token = null; this.token = null;
} }
/** /**
@ -54,8 +52,9 @@ export default class ConnectionManager {
* @returns {ConnectionManager} * @returns {ConnectionManager}
*/ */
static getInstance(): ConnectionManager { static getInstance(): ConnectionManager {
if (ConnectionManager.instance == null) if (ConnectionManager.instance == null) {
ConnectionManager.instance = new ConnectionManager(); ConnectionManager.instance = new ConnectionManager();
}
return ConnectionManager.instance; return ConnectionManager.instance;
} }
@ -65,7 +64,7 @@ export default class ConnectionManager {
* @returns {string | null} * @returns {string | null}
*/ */
getToken(): string | null { getToken(): string | null {
return this.#token; return this.token;
} }
/** /**
@ -77,18 +76,17 @@ export default class ConnectionManager {
return new Promise( return new Promise(
(resolve: (token: string) => void, reject: () => void) => { (resolve: (token: string) => void, reject: () => void) => {
const token = this.getToken(); const token = this.getToken();
if (token != null) resolve(token); if (token != null) {
else { resolve(token);
} else {
Keychain.getInternetCredentials(SERVER_NAME) Keychain.getInternetCredentials(SERVER_NAME)
.then((data: Keychain.UserCredentials | false) => { .then((data: Keychain.UserCredentials | false) => {
if ( if (data && data.password != null) {
data != null && this.token = data.password;
data.password != null && resolve(this.token);
typeof data.password === 'string' } else {
) { reject();
this.#token = data.password; }
resolve(this.#token);
} else reject();
}) })
.catch((): void => reject()); .catch((): void => reject());
} }
@ -116,8 +114,7 @@ export default class ConnectionManager {
return new Promise((resolve: () => void, reject: () => void) => { return new Promise((resolve: () => void, reject: () => void) => {
Keychain.setInternetCredentials(SERVER_NAME, 'token', token) Keychain.setInternetCredentials(SERVER_NAME, 'token', token)
.then(() => { .then(() => {
this.#token = token; this.token = token;
this.#email = email;
resolve(); resolve();
}) })
.catch((): void => reject()); .catch((): void => reject());
@ -133,7 +130,7 @@ export default class ConnectionManager {
return new Promise((resolve: () => void, reject: () => void) => { return new Promise((resolve: () => void, reject: () => void) => {
Keychain.resetInternetCredentials(SERVER_NAME) Keychain.resetInternetCredentials(SERVER_NAME)
.then(() => { .then(() => {
this.#token = null; this.token = null;
resolve(); resolve();
}) })
.catch((): void => reject()); .catch((): void => reject());
@ -156,13 +153,15 @@ export default class ConnectionManager {
email, email,
password, password,
}; };
apiRequest(AUTH_PATH, 'POST', data) apiRequest<ApiDataLoginType>(AUTH_PATH, 'POST', data)
.then((response: ApiDataLoginType) => { .then((response: ApiDataLoginType) => {
if (response.token != null) { if (response.token != null) {
this.saveLogin(email, response.token) this.saveLogin(email, response.token)
.then((): void => resolve()) .then((): void => resolve())
.catch((): void => reject(ERROR_TYPE.TOKEN_SAVE)); .catch((): void => reject(ERROR_TYPE.TOKEN_SAVE));
} else reject(ERROR_TYPE.SERVER_ERROR); } else {
reject(ERROR_TYPE.SERVER_ERROR);
}
}) })
.catch((error: number): void => reject(error)); .catch((error: number): void => reject(error));
}, },
@ -176,24 +175,23 @@ export default class ConnectionManager {
* @param params * @param params
* @returns Promise<ApiGenericDataType> * @returns Promise<ApiGenericDataType>
*/ */
async authenticatedRequest( async authenticatedRequest<T>(
path: string, path: string,
params: {...}, params: {[key: string]: any},
): Promise<ApiGenericDataType> { ): Promise<T> {
return new Promise( return new Promise(
( (resolve: (response: T) => void, reject: (error: number) => void) => {
resolve: (response: ApiGenericDataType) => void,
reject: (error: number) => void,
) => {
if (this.getToken() !== null) { if (this.getToken() !== null) {
const data = { const data = {
...params, ...params,
token: this.getToken(), token: this.getToken(),
}; };
apiRequest(path, 'POST', data) apiRequest<T>(path, 'POST', data)
.then((response: ApiGenericDataType): void => resolve(response)) .then((response: T): void => resolve(response))
.catch((error: number): void => reject(error)); .catch((error: number): void => reject(error));
} else reject(ERROR_TYPE.TOKEN_RETRIEVE); } else {
reject(ERROR_TYPE.TOKEN_RETRIEVE);
}
}, },
); );
} }

View file

@ -21,12 +21,12 @@
import type {ServiceItemType} from './ServicesManager'; import type {ServiceItemType} from './ServicesManager';
import ServicesManager from './ServicesManager'; import ServicesManager from './ServicesManager';
import {getSublistWithIds} from '../utils/Utils'; import {getSublistWithIds} from '../utils/Services';
import AsyncStorageManager from './AsyncStorageManager'; import AsyncStorageManager from './AsyncStorageManager';
export default class DashboardManager extends ServicesManager { export default class DashboardManager extends ServicesManager {
getCurrentDashboard(): Array<ServiceItemType | null> { getCurrentDashboard(): Array<ServiceItemType | null> {
const dashboardIdList = AsyncStorageManager.getObject( const dashboardIdList = AsyncStorageManager.getObject<Array<string>>(
AsyncStorageManager.PREFERENCES.dashboardItems.key, AsyncStorageManager.PREFERENCES.dashboardItems.key,
); );
const allDatasets = [ const allDatasets = [

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import i18n from 'i18n-js'; import i18n from 'i18n-js';
/** /**
@ -28,9 +26,9 @@ import i18n from 'i18n-js';
export default class DateManager { export default class DateManager {
static instance: DateManager | null = null; static instance: DateManager | null = null;
daysOfWeek = []; daysOfWeek: Array<string> = [];
monthsOfYear = []; monthsOfYear: Array<string> = [];
constructor() { constructor() {
this.daysOfWeek.push(i18n.t('date.daysOfWeek.sunday')); // 0 represents sunday this.daysOfWeek.push(i18n.t('date.daysOfWeek.sunday')); // 0 represents sunday
@ -60,7 +58,9 @@ export default class DateManager {
* @returns {DateManager} * @returns {DateManager}
*/ */
static getInstance(): DateManager { static getInstance(): DateManager {
if (DateManager.instance == null) DateManager.instance = new DateManager(); if (DateManager.instance == null) {
DateManager.instance = new DateManager();
}
return DateManager.instance; return DateManager.instance;
} }

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack'; import {StackNavigationProp} from '@react-navigation/stack';
import AvailableWebsites from '../constants/AvailableWebsites'; import AvailableWebsites from '../constants/AvailableWebsites';
@ -93,24 +91,24 @@ export const SERVICES_CATEGORIES_KEY = {
}; };
export type ServiceItemType = { export type ServiceItemType = {
key: string, key: string;
title: string, title: string;
subtitle: string, subtitle: string;
image: string, image: string;
onPress: () => void, onPress: () => void;
badgeFunction?: (dashboard: FullDashboardType) => number, badgeFunction?: (dashboard: FullDashboardType) => number;
}; };
export type ServiceCategoryType = { export type ServiceCategoryType = {
key: string, key: string;
title: string, title: string;
subtitle: string, subtitle: string;
image: string | number, image: string | number;
content: Array<ServiceItemType>, content: Array<ServiceItemType>;
}; };
export default class ServicesManager { export default class ServicesManager {
navigation: StackNavigationProp; navigation: StackNavigationProp<any>;
amicaleDataset: Array<ServiceItemType>; amicaleDataset: Array<ServiceItemType>;
@ -122,7 +120,7 @@ export default class ServicesManager {
categoriesDataset: Array<ServiceCategoryType>; categoriesDataset: Array<ServiceCategoryType>;
constructor(nav: StackNavigationProp) { constructor(nav: StackNavigationProp<any>) {
this.navigation = nav; this.navigation = nav;
this.amicaleDataset = [ this.amicaleDataset = [
{ {
@ -336,9 +334,11 @@ export default class ServicesManager {
* @returns {null} * @returns {null}
*/ */
onAmicaleServicePress(route: string) { onAmicaleServicePress(route: string) {
if (ConnectionManager.getInstance().isLoggedIn()) if (ConnectionManager.getInstance().isLoggedIn()) {
this.navigation.navigate(route); this.navigation.navigate(route);
else this.navigation.navigate('login', {nextScreen: route}); } else {
this.navigation.navigate('login', {nextScreen: route});
}
} }
/** /**
@ -348,8 +348,9 @@ export default class ServicesManager {
* @returns {Array<ServiceItemType>} * @returns {Array<ServiceItemType>}
*/ */
getAmicaleServices(excludedItems?: Array<string>): Array<ServiceItemType> { getAmicaleServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null) {
return getStrippedServicesList(excludedItems, this.amicaleDataset); return getStrippedServicesList(excludedItems, this.amicaleDataset);
}
return this.amicaleDataset; return this.amicaleDataset;
} }
@ -360,8 +361,9 @@ export default class ServicesManager {
* @returns {Array<ServiceItemType>} * @returns {Array<ServiceItemType>}
*/ */
getStudentServices(excludedItems?: Array<string>): Array<ServiceItemType> { getStudentServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null) {
return getStrippedServicesList(excludedItems, this.studentsDataset); return getStrippedServicesList(excludedItems, this.studentsDataset);
}
return this.studentsDataset; return this.studentsDataset;
} }
@ -372,8 +374,9 @@ export default class ServicesManager {
* @returns {Array<ServiceItemType>} * @returns {Array<ServiceItemType>}
*/ */
getINSAServices(excludedItems?: Array<string>): Array<ServiceItemType> { getINSAServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null) {
return getStrippedServicesList(excludedItems, this.insaDataset); return getStrippedServicesList(excludedItems, this.insaDataset);
}
return this.insaDataset; return this.insaDataset;
} }
@ -384,8 +387,9 @@ export default class ServicesManager {
* @returns {Array<ServiceItemType>} * @returns {Array<ServiceItemType>}
*/ */
getSpecialServices(excludedItems?: Array<string>): Array<ServiceItemType> { getSpecialServices(excludedItems?: Array<string>): Array<ServiceItemType> {
if (excludedItems != null) if (excludedItems != null) {
return getStrippedServicesList(excludedItems, this.specialDataset); return getStrippedServicesList(excludedItems, this.specialDataset);
}
return this.specialDataset; return this.specialDataset;
} }
@ -396,8 +400,9 @@ export default class ServicesManager {
* @returns {Array<ServiceCategoryType>} * @returns {Array<ServiceCategoryType>}
*/ */
getCategories(excludedItems?: Array<string>): Array<ServiceCategoryType> { getCategories(excludedItems?: Array<string>): Array<ServiceCategoryType> {
if (excludedItems != null) if (excludedItems != null) {
return getStrippedServicesList(excludedItems, this.categoriesDataset); return getStrippedServicesList(excludedItems, this.categoriesDataset);
}
return this.categoriesDataset; return this.categoriesDataset;
} }
} }

View file

@ -17,22 +17,12 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow import {NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
const speedOffset = 5; const speedOffset = 5;
type ListenerFunctionType = (shouldHide: boolean) => void; type ListenerFunctionType = (shouldHide: boolean) => void;
export type OnScrollType = {
nativeEvent: {
contentInset: {bottom: number, left: number, right: number, top: number},
contentOffset: {x: number, y: number},
contentSize: {height: number, width: number},
layoutMeasurement: {height: number, width: number},
zoomScale: number,
},
};
/** /**
* Class used to detect when to show or hide a component based on scrolling * Class used to detect when to show or hide a component based on scrolling
*/ */
@ -46,6 +36,7 @@ export default class AutoHideHandler {
constructor(startHidden: boolean) { constructor(startHidden: boolean) {
this.listeners = []; this.listeners = [];
this.isHidden = startHidden; this.isHidden = startHidden;
this.lastOffset = 0;
} }
/** /**
@ -84,7 +75,7 @@ export default class AutoHideHandler {
* *
* @param event The scroll event generated by the animated component onScroll prop * @param event The scroll event generated by the animated component onScroll prop
*/ */
onScroll(event: OnScrollType) { onScroll(event: NativeSyntheticEvent<NativeScrollEvent>) {
const {nativeEvent} = event; const {nativeEvent} = event;
const speed = const speed =
nativeEvent.contentOffset.y < 0 nativeEvent.contentOffset.y < 0

View file

@ -17,12 +17,20 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {useTheme} from 'react-native-paper'; import {useTheme} from 'react-native-paper';
import {createCollapsibleStack} from 'react-navigation-collapsible'; import {createCollapsibleStack} from 'react-navigation-collapsible';
import StackNavigator, {StackNavigationOptions} from '@react-navigation/stack'; import StackNavigator, {StackNavigationOptions} from '@react-navigation/stack';
import {StackNavigationState} from '@react-navigation/native';
import {StackNavigationEventMap} from '@react-navigation/stack/lib/typescript/src/types';
type StackNavigatorType = import('@react-navigation/native').TypedNavigator<
Record<string, object | undefined>,
StackNavigationState,
StackNavigationOptions,
StackNavigationEventMap,
typeof StackNavigator
>;
/** /**
* Creates a navigation stack with the collapsible library, allowing the header to collapse on scroll. * Creates a navigation stack with the collapsible library, allowing the header to collapse on scroll.
@ -40,18 +48,16 @@ import StackNavigator, {StackNavigationOptions} from '@react-navigation/stack';
* @param headerColor The color of the header. Uses default color if not specified * @param headerColor The color of the header. Uses default color if not specified
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export function createScreenCollapsibleStack( export function CreateScreenCollapsibleStack(
name: string, name: string,
Stack: StackNavigator, Stack: StackNavigatorType,
// eslint-disable-next-line flowtype/no-weak-types
component: React.ComponentType<any>, component: React.ComponentType<any>,
title: string, title: string,
useNativeDriver?: boolean, useNativeDriver: boolean = true,
options?: StackNavigationOptions, options: StackNavigationOptions = {},
headerColor?: string, headerColor?: string,
): React.Node { ) {
const {colors} = useTheme(); const {colors} = useTheme();
const screenOptions = options != null ? options : {};
return createCollapsibleStack( return createCollapsibleStack(
<Stack.Screen <Stack.Screen
name={name} name={name}
@ -61,12 +67,12 @@ export function createScreenCollapsibleStack(
headerStyle: { headerStyle: {
backgroundColor: headerColor != null ? headerColor : colors.surface, backgroundColor: headerColor != null ? headerColor : colors.surface,
}, },
...screenOptions, ...options,
}} }}
/>, />,
{ {
collapsedColor: headerColor != null ? headerColor : colors.surface, collapsedColor: headerColor != null ? headerColor : colors.surface,
useNativeDriver: useNativeDriver != null ? useNativeDriver : true, // native driver does not work with webview useNativeDriver: useNativeDriver, // native driver does not work with webview
}, },
); );
} }
@ -85,10 +91,9 @@ export function createScreenCollapsibleStack(
*/ */
export function getWebsiteStack( export function getWebsiteStack(
name: string, name: string,
Stack: StackNavigator, Stack: StackNavigatorType,
// eslint-disable-next-line flowtype/no-weak-types
component: React.ComponentType<any>, component: React.ComponentType<any>,
title: string, title: string,
): React.Node { ) {
return createScreenCollapsibleStack(name, Stack, component, title, false); return CreateScreenCollapsibleStack(name, Stack, component, title, false);
} }

View file

@ -17,12 +17,9 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {DeviceType} from '../screens/Amicale/Equipment/EquipmentListScreen'; import type {DeviceType} from '../screens/Amicale/Equipment/EquipmentListScreen';
import DateManager from '../managers/DateManager'; import DateManager from '../managers/DateManager';
import type {CustomThemeType} from '../managers/ThemeManager';
import type {MarkedDatesObjectType} from '../screens/Amicale/Equipment/EquipmentRentScreen'; import type {MarkedDatesObjectType} from '../screens/Amicale/Equipment/EquipmentRentScreen';
/** /**
@ -56,10 +53,12 @@ export function isEquipmentAvailable(item: DeviceType): boolean {
let isAvailable = true; let isAvailable = true;
const today = getCurrentDay(); const today = getCurrentDay();
const dates = item.booked_at; const dates = item.booked_at;
dates.forEach((date: {begin: string, end: string}) => { dates.forEach((date: {begin: string; end: string}) => {
const start = new Date(date.begin); const start = new Date(date.begin);
const end = new Date(date.end); const end = new Date(date.end);
if (!(today < start || today > end)) isAvailable = false; if (!(today < start || today > end)) {
isAvailable = false;
}
}); });
return isAvailable; return isAvailable;
} }
@ -73,11 +72,13 @@ export function isEquipmentAvailable(item: DeviceType): boolean {
export function getFirstEquipmentAvailability(item: DeviceType): Date { export function getFirstEquipmentAvailability(item: DeviceType): Date {
let firstAvailability = getCurrentDay(); let firstAvailability = getCurrentDay();
const dates = item.booked_at; const dates = item.booked_at;
dates.forEach((date: {begin: string, end: string}) => { dates.forEach((date: {begin: string; end: string}) => {
const start = new Date(date.begin); const start = new Date(date.begin);
const end = new Date(date.end); const end = new Date(date.end);
end.setDate(end.getDate() + 1); end.setDate(end.getDate() + 1);
if (firstAvailability >= start) firstAvailability = end; if (firstAvailability >= start) {
firstAvailability = end;
}
}); });
return firstAvailability; return firstAvailability;
} }
@ -93,23 +94,24 @@ export function getRelativeDateString(date: Date): string {
const monthDelta = date.getUTCMonth() - today.getUTCMonth(); const monthDelta = date.getUTCMonth() - today.getUTCMonth();
const dayDelta = date.getUTCDate() - today.getUTCDate(); const dayDelta = date.getUTCDate() - today.getUTCDate();
let translatedString = i18n.t('screens.equipment.today'); let translatedString = i18n.t('screens.equipment.today');
if (yearDelta > 0) if (yearDelta > 0) {
translatedString = i18n.t('screens.equipment.otherYear', { translatedString = i18n.t('screens.equipment.otherYear', {
date: date.getDate(), date: date.getDate(),
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()], month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
year: date.getFullYear(), year: date.getFullYear(),
}); });
else if (monthDelta > 0) } else if (monthDelta > 0) {
translatedString = i18n.t('screens.equipment.otherMonth', { translatedString = i18n.t('screens.equipment.otherMonth', {
date: date.getDate(), date: date.getDate(),
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()], month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
}); });
else if (dayDelta > 1) } else if (dayDelta > 1) {
translatedString = i18n.t('screens.equipment.thisMonth', { translatedString = i18n.t('screens.equipment.thisMonth', {
date: date.getDate(), date: date.getDate(),
}); });
else if (dayDelta === 1) } else if (dayDelta === 1) {
translatedString = i18n.t('screens.equipment.tomorrow'); translatedString = i18n.t('screens.equipment.tomorrow');
}
return translatedString; return translatedString;
} }
@ -162,8 +164,11 @@ export function getValidRange(
(direction === 1 && date < limit) || (direction === 1 && date < limit) ||
(direction === -1 && date > limit) (direction === -1 && date > limit)
) { ) {
if (direction === 1) validRange.push(getISODate(date)); if (direction === 1) {
else validRange.unshift(getISODate(date)); validRange.push(getISODate(date));
} else {
validRange.unshift(getISODate(date));
}
date.setDate(date.getDate() + direction); date.setDate(date.getDate() + direction);
} }
return validRange; return validRange;
@ -180,17 +185,27 @@ export function getValidRange(
*/ */
export function generateMarkedDates( export function generateMarkedDates(
isSelection: boolean, isSelection: boolean,
theme: CustomThemeType, theme: ReactNativePaper.Theme,
range: Array<string>, range: Array<string>,
): MarkedDatesObjectType { ): MarkedDatesObjectType {
const markedDates = {}; const markedDates: {
[key: string]: {
startingDay: boolean;
endingDay: boolean;
color: string;
};
} = {};
for (let i = 0; i < range.length; i += 1) { for (let i = 0; i < range.length; i += 1) {
const isStart = i === 0; const isStart = i === 0;
const isEnd = i === range.length - 1; const isEnd = i === range.length - 1;
let color; let color;
if (isSelection && (isStart || isEnd)) color = theme.colors.primary; if (isSelection && (isStart || isEnd)) {
else if (isSelection) color = theme.colors.danger; color = theme.colors.primary;
else color = theme.colors.textDisabled; } else if (isSelection) {
color = theme.colors.danger;
} else {
color = theme.colors.textDisabled;
}
markedDates[range[i]] = { markedDates[range[i]] = {
startingDay: isStart, startingDay: isStart,
endingDay: isEnd, endingDay: isEnd,

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import {stringToDate} from './Planning'; import {stringToDate} from './Planning';
import type {PlanningEventType} from './Planning'; import type {PlanningEventType} from './Planning';
@ -29,13 +27,15 @@ import type {PlanningEventType} from './Planning';
*/ */
export function getTodayEventTimeLimit(): Date { export function getTodayEventTimeLimit(): Date {
const now = new Date(); const now = new Date();
if (now.getDay() === 4) if (now.getDay() === 4) {
// Thursday // Thursday
now.setHours(11, 30, 0); now.setHours(11, 30, 0);
else if (now.getDay() === 6 || now.getDay() === 0) } else if (now.getDay() === 6 || now.getDay() === 0) {
// Weekend // Weekend
now.setHours(0, 0, 0); now.setHours(0, 0, 0);
else now.setHours(17, 30, 0); } else {
now.setHours(17, 30, 0);
}
return now; return now;
} }
@ -50,7 +50,7 @@ export function getEventsAfterLimit(
events: Array<PlanningEventType>, events: Array<PlanningEventType>,
limit: Date, limit: Date,
): Array<PlanningEventType> { ): Array<PlanningEventType> {
const validEvents = []; const validEvents: Array<PlanningEventType> = [];
events.forEach((event: PlanningEventType) => { events.forEach((event: PlanningEventType) => {
const startDate = stringToDate(event.date_begin); const startDate = stringToDate(event.date_begin);
if (startDate != null && startDate >= limit) { if (startDate != null && startDate >= limit) {
@ -68,11 +68,13 @@ export function getEventsAfterLimit(
export function getFutureEvents( export function getFutureEvents(
events: Array<PlanningEventType>, events: Array<PlanningEventType>,
): Array<PlanningEventType> { ): Array<PlanningEventType> {
const validEvents = []; const validEvents: Array<PlanningEventType> = [];
const now = new Date(); const now = new Date();
events.forEach((event: PlanningEventType) => { events.forEach((event: PlanningEventType) => {
const startDate = stringToDate(event.date_begin); const startDate = stringToDate(event.date_begin);
if (startDate != null && startDate > now) validEvents.push(event); if (startDate != null && startDate > now) {
validEvents.push(event);
}
}); });
return validEvents; return validEvents;
} }
@ -92,8 +94,13 @@ export function getDisplayEvent(
events, events,
getTodayEventTimeLimit(), getTodayEventTimeLimit(),
); );
if (eventsAfterLimit.length > 0) [displayEvent] = eventsAfterLimit; if (eventsAfterLimit.length > 0) {
else [displayEvent] = events; [displayEvent] = eventsAfterLimit;
} else if (events.length === 1) [displayEvent] = events; } else {
[displayEvent] = events;
}
} else if (events.length === 1) {
[displayEvent] = events;
}
return displayEvent; return displayEvent;
} }

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import * as RNLocalize from 'react-native-localize'; import * as RNLocalize from 'react-native-localize';
@ -28,9 +26,7 @@ import fr from '../../locales/fr.json';
const initLocales = () => { const initLocales = () => {
i18n.fallbacks = true; i18n.fallbacks = true;
i18n.translations = {fr, en}; i18n.translations = {fr, en};
i18n.locale = RNLocalize.findBestAvailableLanguage([ const bestLanguage = RNLocalize.findBestAvailableLanguage(['en', 'fr']);
'en', i18n.locale = bestLanguage ? bestLanguage.languageTag : 'en';
'fr', };
]).languageTag;
}
export default initLocales; export default initLocales;

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import { import {
checkNotifications, checkNotifications,
requestNotifications, requestNotifications,
@ -41,12 +39,17 @@ const reminderIdFactor = 100;
export async function askPermissions(): Promise<void> { export async function askPermissions(): Promise<void> {
return new Promise((resolve: () => void, reject: () => void) => { return new Promise((resolve: () => void, reject: () => void) => {
checkNotifications().then(({status}: {status: string}) => { checkNotifications().then(({status}: {status: string}) => {
if (status === RESULTS.GRANTED) resolve(); if (status === RESULTS.GRANTED) {
else if (status === RESULTS.BLOCKED) reject(); resolve();
else { } else if (status === RESULTS.BLOCKED) {
requestNotifications().then((result: {status: string}) => { reject();
if (result.status === RESULTS.GRANTED) resolve(); } else {
else reject(); requestNotifications([]).then((result: {status: string}) => {
if (result.status === RESULTS.GRANTED) {
resolve();
} else {
reject();
}
}); });
} }
}); });

View file

@ -17,18 +17,16 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
export type PlanningEventType = { export type PlanningEventType = {
id: number, id: number;
title: string, title: string;
date_begin: string, date_begin: string;
club: string, club: string;
category_id: number, category_id: number;
description: string, description: string;
place: string, place: string;
url: string, url: string;
logo: string | null, logo: string | null;
}; };
// Regex used to check date string validity // Regex used to check date string validity
@ -41,7 +39,7 @@ const dateRegExp = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/;
* @param dateString The string to check * @param dateString The string to check
* @return {boolean} * @return {boolean}
*/ */
export function isEventDateStringFormatValid(dateString: ?string): boolean { export function isEventDateStringFormatValid(dateString?: string): boolean {
return ( return (
dateString !== undefined && dateString !== undefined &&
dateString !== null && dateString !== null &&
@ -57,7 +55,7 @@ export function isEventDateStringFormatValid(dateString: ?string): boolean {
* @return {Date|null} The date object or null if the given string is invalid * @return {Date|null} The date object or null if the given string is invalid
*/ */
export function stringToDate(dateString: string): Date | null { export function stringToDate(dateString: string): Date | null {
let date = new Date(); let date: Date | null = new Date();
if (isEventDateStringFormatValid(dateString)) { if (isEventDateStringFormatValid(dateString)) {
const stringArray = dateString.split(' '); const stringArray = dateString.split(' ');
const dateArray = stringArray[0].split('-'); const dateArray = stringArray[0].split('-');
@ -68,7 +66,9 @@ export function stringToDate(dateString: string): Date | null {
parseInt(dateArray[2], 10), parseInt(dateArray[2], 10),
); );
date.setHours(parseInt(timeArray[0], 10), parseInt(timeArray[1], 10), 0, 0); date.setHours(parseInt(timeArray[0], 10), parseInt(timeArray[1], 10), 0, 0);
} else date = null; } else {
date = null;
}
return date; return date;
} }
@ -111,7 +111,9 @@ export function getCurrentDateString(): string {
export function isEventBefore(event1Date: string, event2Date: string): boolean { export function isEventBefore(event1Date: string, event2Date: string): boolean {
const date1 = stringToDate(event1Date); const date1 = stringToDate(event1Date);
const date2 = stringToDate(event2Date); const date2 = stringToDate(event2Date);
if (date1 !== null && date2 !== null) return date1 < date2; if (date1 !== null && date2 !== null) {
return date1 < date2;
}
return false; return false;
} }
@ -123,7 +125,9 @@ export function isEventBefore(event1Date: string, event2Date: string): boolean {
* @return {string|null} Date in format YYYY:MM:DD or null if given string is invalid * @return {string|null} Date in format YYYY:MM:DD or null if given string is invalid
*/ */
export function getDateOnlyString(dateString: string): string | null { export function getDateOnlyString(dateString: string): string | null {
if (isEventDateStringFormatValid(dateString)) return dateString.split(' ')[0]; if (isEventDateStringFormatValid(dateString)) {
return dateString.split(' ')[0];
}
return null; return null;
} }
@ -135,7 +139,9 @@ export function getDateOnlyString(dateString: string): string | null {
* @return {string|null} Time in format HH:MM or null if given string is invalid * @return {string|null} Time in format HH:MM or null if given string is invalid
*/ */
export function getTimeOnlyString(dateString: string): string | null { export function getTimeOnlyString(dateString: string): string | null {
if (isEventDateStringFormatValid(dateString)) return dateString.split(' ')[1]; if (isEventDateStringFormatValid(dateString)) {
return dateString.split(' ')[1];
}
return null; return null;
} }
@ -148,7 +154,7 @@ export function getTimeOnlyString(dateString: string): string | null {
* @param description The text to check * @param description The text to check
* @return {boolean} * @return {boolean}
*/ */
export function isDescriptionEmpty(description: ?string): boolean { export function isDescriptionEmpty(description?: string): boolean {
if (description !== undefined && description !== null) { if (description !== undefined && description !== null) {
return ( return (
description description
@ -177,10 +183,12 @@ export function generateEmptyCalendar(
): {[key: string]: Array<PlanningEventType>} { ): {[key: string]: Array<PlanningEventType>} {
const end = new Date(Date.now()); const end = new Date(Date.now());
end.setMonth(end.getMonth() + numberOfMonths); end.setMonth(end.getMonth() + numberOfMonths);
const daysOfYear = {}; const daysOfYear: {[key: string]: Array<PlanningEventType>} = {};
for (let d = new Date(Date.now()); d <= end; d.setDate(d.getDate() + 1)) { for (let d = new Date(Date.now()); d <= end; d.setDate(d.getDate() + 1)) {
const dateString = getDateOnlyString(dateToString(new Date(d), false)); const dateString = getDateOnlyString(dateToString(new Date(d), false));
if (dateString !== null) daysOfYear[dateString] = []; if (dateString !== null) {
daysOfYear[dateString] = [];
}
} }
return daysOfYear; return daysOfYear;
} }
@ -197,8 +205,9 @@ export function pushEventInOrder(
eventArray: Array<PlanningEventType>, eventArray: Array<PlanningEventType>,
event: PlanningEventType, event: PlanningEventType,
) { ) {
if (eventArray.length === 0) eventArray.push(event); if (eventArray.length === 0) {
else { eventArray.push(event);
} else {
for (let i = 0; i < eventArray.length; i += 1) { for (let i = 0; i < eventArray.length; i += 1) {
if (isEventBefore(event.date_begin, eventArray[i].date_begin)) { if (isEventBefore(event.date_begin, eventArray[i].date_begin)) {
eventArray.splice(i, 0, event); eventArray.splice(i, 0, event);
@ -231,7 +240,9 @@ export function generateEventAgenda(
const dateString = getDateOnlyString(eventList[i].date_begin); const dateString = getDateOnlyString(eventList[i].date_begin);
if (dateString != null) { if (dateString != null) {
const eventArray = agendaItems[dateString]; const eventArray = agendaItems[dateString];
if (eventArray != null) pushEventInOrder(eventArray, eventList[i]); if (eventArray != null) {
pushEventInOrder(eventArray, eventList[i]);
}
} }
} }
return agendaItems; return agendaItems;

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen'; import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen';
/** /**
@ -32,16 +30,21 @@ import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen';
*/ */
export function getMachineEndDate(machine: ProxiwashMachineType): Date | null { export function getMachineEndDate(machine: ProxiwashMachineType): Date | null {
const array = machine.endTime.split(':'); const array = machine.endTime.split(':');
let endDate = new Date(Date.now()); let endDate: Date | null = new Date(Date.now());
endDate.setHours(parseInt(array[0], 10), parseInt(array[1], 10)); endDate.setHours(parseInt(array[0], 10), parseInt(array[1], 10));
const limit = new Date(Date.now()); const limit = new Date(Date.now());
if (endDate < limit) { if (endDate < limit) {
if (limit.getHours() > 12) { if (limit.getHours() > 12) {
limit.setHours(limit.getHours() - 12); limit.setHours(limit.getHours() - 12);
if (endDate < limit) endDate.setDate(endDate.getDate() + 1); if (endDate < limit) {
else endDate = null; endDate.setDate(endDate.getDate() + 1);
} else endDate = null; } else {
endDate = null;
}
} else {
endDate = null;
}
} }
return endDate; return endDate;
@ -63,8 +66,9 @@ export function isMachineWatched(
if ( if (
watchedMachine.number === machine.number && watchedMachine.number === machine.number &&
watchedMachine.endTime === machine.endTime watchedMachine.endTime === machine.endTime
) ) {
watched = true; watched = true;
}
}); });
return watched; return watched;
} }
@ -82,7 +86,9 @@ export function getMachineOfId(
): ProxiwashMachineType | null { ): ProxiwashMachineType | null {
let machineFound = null; let machineFound = null;
allMachines.forEach((machine: ProxiwashMachineType) => { allMachines.forEach((machine: ProxiwashMachineType) => {
if (machine.number === id) machineFound = machine; if (machine.number === id) {
machineFound = machine;
}
}); });
return machineFound; return machineFound;
} }
@ -100,7 +106,7 @@ export function getCleanedMachineWatched(
machineWatchedList: Array<ProxiwashMachineType>, machineWatchedList: Array<ProxiwashMachineType>,
allMachines: Array<ProxiwashMachineType>, allMachines: Array<ProxiwashMachineType>,
): Array<ProxiwashMachineType> { ): Array<ProxiwashMachineType> {
const newList = []; const newList: Array<ProxiwashMachineType> = [];
machineWatchedList.forEach((watchedMachine: ProxiwashMachineType) => { machineWatchedList.forEach((watchedMachine: ProxiwashMachineType) => {
const machine = getMachineOfId(watchedMachine.number, allMachines); const machine = getMachineOfId(watchedMachine.number, allMachines);
if ( if (

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
/** /**
* Sanitizes the given string to improve search performance. * Sanitizes the given string to improve search performance.
* *
@ -60,7 +58,9 @@ export function isItemInCategoryFilter(
): boolean { ): boolean {
let itemFound = false; let itemFound = false;
categories.forEach((cat: number | null) => { categories.forEach((cat: number | null) => {
if (cat != null && filter.indexOf(cat) !== -1) itemFound = true; if (cat != null && filter.indexOf(cat) !== -1) {
itemFound = true;
}
}); });
return itemFound; return itemFound;
} }

View file

@ -17,10 +17,25 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow /**
* Gets the given services list without items of the given ids
import {Platform, StatusBar} from 'react-native'; *
import ThemeManager from '../managers/ThemeManager'; * @param idList The ids of items to remove
* @param sourceList The item list to use as source
* @returns {[]}
*/
export default function getStrippedServicesList<T extends {key: string}>(
idList: Array<string>,
sourceList: Array<T>,
) {
const newArray: Array<T> = [];
sourceList.forEach((item: T) => {
if (!idList.includes(item.key)) {
newArray.push(item);
}
});
return newArray;
}
/** /**
* Gets a sublist of the given list with items of the given ids only * Gets a sublist of the given list with items of the given ids only
@ -31,11 +46,11 @@ import ThemeManager from '../managers/ThemeManager';
* @param originalList The original list * @param originalList The original list
* @returns {[]} * @returns {[]}
*/ */
export function getSublistWithIds<T>( export function getSublistWithIds<T extends {key: string}>(
idList: Array<string>, idList: Array<string>,
originalList: Array<{key: string, ...T}>, originalList: Array<T>,
): Array<{key: string, ...T} | null> { ) {
const subList = []; const subList: Array<T | null> = [];
for (let i = 0; i < idList.length; i += 1) { for (let i = 0; i < idList.length; i += 1) {
subList.push(null); subList.push(null);
} }
@ -45,26 +60,10 @@ export function getSublistWithIds<T>(
if (idList.includes(item.key)) { if (idList.includes(item.key)) {
subList[idList.indexOf(item.key)] = item; subList[idList.indexOf(item.key)] = item;
itemsAdded += 1; itemsAdded += 1;
if (itemsAdded === idList.length) break; if (itemsAdded === idList.length) {
break;
}
} }
} }
return subList; return subList;
} }
/**
* Updates status bar content color if on iOS only,
* as the android status bar is always set to black.
*/
export function setupStatusBar() {
if (ThemeManager.getNightMode()) {
StatusBar.setBarStyle('light-content', true);
} else {
StatusBar.setBarStyle('dark-content', true);
}
if (Platform.OS === 'android') {
StatusBar.setBackgroundColor(
ThemeManager.getCurrentTheme().colors.surface,
true,
);
}
}

View file

@ -17,20 +17,18 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import {Linking} from 'react-native'; import {Linking} from 'react-native';
export type ParsedUrlDataType = { export type ParsedUrlDataType = {
route: string, route: string;
data: {[key: string]: string}, data: {[key: string]: string};
}; };
export type ParsedUrlCallbackType = (parsedData: ParsedUrlDataType) => void; export type ParsedUrlCallbackType = (parsedData: ParsedUrlDataType) => void;
type RawParsedUrlDataType = { type RawParsedUrlDataType = {
path: string, path: string;
queryParams: {[key: string]: string}, queryParams: {[key: string]: string};
}; };
/** /**
@ -69,7 +67,7 @@ export default class URLHandler {
let parsedData: RawParsedUrlDataType | null = null; let parsedData: RawParsedUrlDataType | null = null;
const urlNoScheme = url.replace(URLHandler.SCHEME, ''); const urlNoScheme = url.replace(URLHandler.SCHEME, '');
if (urlNoScheme != null) { if (urlNoScheme != null) {
const params = {}; const params: {[key: string]: string} = {};
const [path, fullParamsString] = urlNoScheme.split('?'); const [path, fullParamsString] = urlNoScheme.split('?');
if (fullParamsString != null) { if (fullParamsString != null) {
const paramsStringArray = fullParamsString.split('&'); const paramsStringArray = fullParamsString.split('&');
@ -80,7 +78,9 @@ export default class URLHandler {
} }
}); });
} }
if (path != null) parsedData = {path, queryParams: params}; if (path != null) {
parsedData = {path, queryParams: params};
}
} }
return parsedData; return parsedData;
} }
@ -99,10 +99,11 @@ export default class URLHandler {
if (rawParsedUrlData != null) { if (rawParsedUrlData != null) {
const {path} = rawParsedUrlData; const {path} = rawParsedUrlData;
const {queryParams} = rawParsedUrlData; const {queryParams} = rawParsedUrlData;
if (URLHandler.isClubInformationLink(path)) if (URLHandler.isClubInformationLink(path)) {
parsedData = URLHandler.generateClubInformationData(queryParams); parsedData = URLHandler.generateClubInformationData(queryParams);
else if (URLHandler.isPlanningInformationLink(path)) } else if (URLHandler.isPlanningInformationLink(path)) {
parsedData = URLHandler.generatePlanningInformationData(queryParams); parsedData = URLHandler.generatePlanningInformationData(queryParams);
}
} }
return parsedData; return parsedData;
@ -145,15 +146,16 @@ export default class URLHandler {
* @returns {null|{route: string, data: {clubId: number}}} * @returns {null|{route: string, data: {clubId: number}}}
*/ */
static generateClubInformationData(params: { static generateClubInformationData(params: {
[key: string]: string, [key: string]: string;
}): ParsedUrlDataType | null { }): ParsedUrlDataType | null {
if (params.id != null) { if (params.id != null) {
const id = parseInt(params.id, 10); const id = parseInt(params.id, 10);
if (!Number.isNaN(id)) if (!Number.isNaN(id)) {
return { return {
route: URLHandler.CLUB_INFO_ROUTE, route: URLHandler.CLUB_INFO_ROUTE,
data: {clubId: id.toString()}, data: {clubId: id.toString()},
}; };
}
} }
return null; return null;
} }
@ -165,7 +167,7 @@ export default class URLHandler {
* @returns {null|{route: string, data: {clubId: number}}} * @returns {null|{route: string, data: {clubId: number}}}
*/ */
static generatePlanningInformationData(params: { static generatePlanningInformationData(params: {
[key: string]: string, [key: string]: string;
}): ParsedUrlDataType | null { }): ParsedUrlDataType | null {
if (params.id != null) { if (params.id != null) {
const id = parseInt(params.id, 10); const id = parseInt(params.id, 10);
@ -201,7 +203,9 @@ export default class URLHandler {
onUrl = ({url}: {url: string}) => { onUrl = ({url}: {url: string}) => {
if (url != null) { if (url != null) {
const data = URLHandler.getUrlData(URLHandler.parseUrl(url)); const data = URLHandler.getUrlData(URLHandler.parseUrl(url));
if (data != null) this.onDetectURL(data); if (data != null) {
this.onDetectURL(data);
}
} }
}; };
@ -210,10 +214,12 @@ export default class URLHandler {
* *
* @param url The url detected * @param url The url detected
*/ */
onInitialUrl = (url: ?string) => { onInitialUrl = (url: string | null) => {
if (url != null) { if (url != null) {
const data = URLHandler.getUrlData(URLHandler.parseUrl(url)); const data = URLHandler.getUrlData(URLHandler.parseUrl(url));
if (data != null) this.onInitialURLParsed(data); if (data != null) {
this.onInitialURLParsed(data);
}
} }
}; };
} }

View file

@ -17,22 +17,23 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow import {Platform, StatusBar} from 'react-native';
import ThemeManager from '../managers/ThemeManager';
/** /**
* Gets the given services list without items of the given ids * Updates status bar content color if on iOS only,
* * as the android status bar is always set to black.
* @param idList The ids of items to remove
* @param sourceList The item list to use as source
* @returns {[]}
*/ */
export default function getStrippedServicesList<T>( export function setupStatusBar() {
idList: Array<string>, if (ThemeManager.getNightMode()) {
sourceList: Array<{key: string, ...T}>, StatusBar.setBarStyle('light-content', true);
): Array<{key: string, ...T}> { } else {
const newArray = []; StatusBar.setBarStyle('dark-content', true);
sourceList.forEach((item: {key: string, ...T}) => { }
if (!idList.includes(item.key)) newArray.push(item); if (Platform.OS === 'android') {
}); StatusBar.setBackgroundColor(
return newArray; ThemeManager.getCurrentTheme().colors.surface,
true,
);
}
} }

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
export const ERROR_TYPE = { export const ERROR_TYPE = {
SUCCESS: 0, SUCCESS: 0,
BAD_CREDENTIALS: 1, BAD_CREDENTIALS: 1,
@ -34,15 +32,14 @@ export const ERROR_TYPE = {
}; };
export type ApiDataLoginType = { export type ApiDataLoginType = {
token: string, token: string;
}; };
// eslint-disable-next-line flowtype/no-weak-types
export type ApiGenericDataType = {[key: string]: any}; export type ApiGenericDataType = {[key: string]: any};
type ApiResponseType = { type ApiResponseType<T> = {
error: number, error: number;
data: ApiGenericDataType, data: T;
}; };
const API_ENDPOINT = 'https://www.amicale-insat.fr/api/'; const API_ENDPOINT = 'https://www.amicale-insat.fr/api/';
@ -55,11 +52,10 @@ const API_ENDPOINT = 'https://www.amicale-insat.fr/api/';
* @param response * @param response
* @returns {boolean} * @returns {boolean}
*/ */
export function isApiResponseValid(response: ApiResponseType): boolean { export function isApiResponseValid<T>(response: ApiResponseType<T>): boolean {
return ( return (
response != null && response != null &&
response.error != null && response.error != null &&
typeof response.error === 'number' &&
response.data != null && response.data != null &&
typeof response.data === 'object' typeof response.data === 'object'
); );
@ -76,18 +72,17 @@ export function isApiResponseValid(response: ApiResponseType): boolean {
* @param params The params to use for this request * @param params The params to use for this request
* @returns {Promise<ApiGenericDataType>} * @returns {Promise<ApiGenericDataType>}
*/ */
export async function apiRequest( export async function apiRequest<T>(
path: string, path: string,
method: string, method: string,
params?: {...}, params?: object,
): Promise<ApiGenericDataType> { ): Promise<T> {
return new Promise( return new Promise(
( (resolve: (data: T) => void, reject: (error: number) => void) => {
resolve: (data: ApiGenericDataType) => void,
reject: (error: number) => void,
) => {
let requestParams = {}; let requestParams = {};
if (params != null) requestParams = {...params}; if (params != null) {
requestParams = {...params};
}
fetch(API_ENDPOINT + path, { fetch(API_ENDPOINT + path, {
method, method,
headers: new Headers({ headers: new Headers({
@ -96,14 +91,20 @@ export async function apiRequest(
}), }),
body: JSON.stringify(requestParams), body: JSON.stringify(requestParams),
}) })
.then(async (response: Response): Promise<ApiResponseType> => .then(
response.json(), async (response: Response): Promise<ApiResponseType<T>> =>
response.json(),
) )
.then((response: ApiResponseType) => { .then((response: ApiResponseType<T>) => {
if (isApiResponseValid(response)) { if (isApiResponseValid(response)) {
if (response.error === ERROR_TYPE.SUCCESS) resolve(response.data); if (response.error === ERROR_TYPE.SUCCESS) {
else reject(response.error); resolve(response.data);
} else reject(ERROR_TYPE.SERVER_ERROR); } else {
reject(response.error);
}
} else {
reject(ERROR_TYPE.SERVER_ERROR);
}
}) })
.catch((): void => reject(ERROR_TYPE.CONNECTION_ERROR)); .catch((): void => reject(ERROR_TYPE.CONNECTION_ERROR));
}, },
@ -121,14 +122,10 @@ export async function apiRequest(
* @param url The urls to fetch data from * @param url The urls to fetch data from
* @return Promise<any> * @return Promise<any>
*/ */
// eslint-disable-next-line flowtype/no-weak-types
export async function readData(url: string): Promise<any> { export async function readData(url: string): Promise<any> {
// eslint-disable-next-line flowtype/no-weak-types
return new Promise((resolve: (response: any) => void, reject: () => void) => { return new Promise((resolve: (response: any) => void, reject: () => void) => {
fetch(url) fetch(url)
// eslint-disable-next-line flowtype/no-weak-types
.then(async (response: Response): Promise<any> => response.json()) .then(async (response: Response): Promise<any> => response.json())
// eslint-disable-next-line flowtype/no-weak-types
.then((data: any): void => resolve(data)) .then((data: any): void => resolve(data))
.catch((): void => reject()); .catch((): void => reject());
}); });

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
// @flow
import * as React from 'react'; import * as React from 'react';
import {useCollapsibleStack} from 'react-navigation-collapsible'; import {useCollapsibleStack} from 'react-navigation-collapsible';
@ -35,18 +33,12 @@ import {useCollapsibleStack} from 'react-navigation-collapsible';
* @param Component The component to use Collapsible with * @param Component The component to use Collapsible with
* @returns {React.ComponentType<any>} * @returns {React.ComponentType<any>}
*/ */
export default function withCollapsible( export default function withCollapsible(Component: React.ComponentType<any>) {
// eslint-disable-next-line flowtype/no-weak-types return React.forwardRef((props: any, ref: any) => {
Component: React.ComponentType<any>,
// eslint-disable-next-line flowtype/no-weak-types
): React$AbstractComponent<any, any> {
// eslint-disable-next-line flowtype/no-weak-types
return React.forwardRef((props: any, ref: any): React.Node => {
return ( return (
<Component <Component
collapsibleStack={useCollapsibleStack()} collapsibleStack={useCollapsibleStack()}
ref={ref} ref={ref}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props} {...props}
/> />
); );