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",
"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": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
@ -2602,6 +2608,16 @@
"@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": {
"version": "16.9.3",
"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/runtime": "^7.11.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/react-native": "^0.63.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/>.
*/
// @flow
import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen';
import type {CustomThemeType} from './ThemeManager';
import type {RuFoodCategoryType} from '../screens/Services/SelfMenuScreen';
/**
@ -57,8 +54,9 @@ export default class AprilFoolsManager {
* @returns {ThemeManager}
*/
static getInstance(): AprilFoolsManager {
if (AprilFoolsManager.instance == null)
if (AprilFoolsManager.instance == null) {
AprilFoolsManager.instance = new AprilFoolsManager();
}
return AprilFoolsManager.instance;
}
@ -130,7 +128,9 @@ export default class AprilFoolsManager {
* @param currentTheme
* @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 {
...currentTheme,
colors: {

View file

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

View file

@ -20,7 +20,7 @@
// @flow
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';
/**
@ -40,12 +40,10 @@ const AUTH_PATH = 'password';
export default class ConnectionManager {
static instance: ConnectionManager | null = null;
#email: string;
#token: string | null;
private token: string | null;
constructor() {
this.#token = null;
this.token = null;
}
/**
@ -54,8 +52,9 @@ export default class ConnectionManager {
* @returns {ConnectionManager}
*/
static getInstance(): ConnectionManager {
if (ConnectionManager.instance == null)
if (ConnectionManager.instance == null) {
ConnectionManager.instance = new ConnectionManager();
}
return ConnectionManager.instance;
}
@ -65,7 +64,7 @@ export default class ConnectionManager {
* @returns {string | null}
*/
getToken(): string | null {
return this.#token;
return this.token;
}
/**
@ -77,18 +76,17 @@ export default class ConnectionManager {
return new Promise(
(resolve: (token: string) => void, reject: () => void) => {
const token = this.getToken();
if (token != null) resolve(token);
else {
if (token != null) {
resolve(token);
} else {
Keychain.getInternetCredentials(SERVER_NAME)
.then((data: Keychain.UserCredentials | false) => {
if (
data != null &&
data.password != null &&
typeof data.password === 'string'
) {
this.#token = data.password;
resolve(this.#token);
} else reject();
if (data && data.password != null) {
this.token = data.password;
resolve(this.token);
} else {
reject();
}
})
.catch((): void => reject());
}
@ -116,8 +114,7 @@ export default class ConnectionManager {
return new Promise((resolve: () => void, reject: () => void) => {
Keychain.setInternetCredentials(SERVER_NAME, 'token', token)
.then(() => {
this.#token = token;
this.#email = email;
this.token = token;
resolve();
})
.catch((): void => reject());
@ -133,7 +130,7 @@ export default class ConnectionManager {
return new Promise((resolve: () => void, reject: () => void) => {
Keychain.resetInternetCredentials(SERVER_NAME)
.then(() => {
this.#token = null;
this.token = null;
resolve();
})
.catch((): void => reject());
@ -156,13 +153,15 @@ export default class ConnectionManager {
email,
password,
};
apiRequest(AUTH_PATH, 'POST', data)
apiRequest<ApiDataLoginType>(AUTH_PATH, 'POST', data)
.then((response: ApiDataLoginType) => {
if (response.token != null) {
this.saveLogin(email, response.token)
.then((): void => resolve())
.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));
},
@ -176,24 +175,23 @@ export default class ConnectionManager {
* @param params
* @returns Promise<ApiGenericDataType>
*/
async authenticatedRequest(
async authenticatedRequest<T>(
path: string,
params: {...},
): Promise<ApiGenericDataType> {
params: {[key: string]: any},
): Promise<T> {
return new Promise(
(
resolve: (response: ApiGenericDataType) => void,
reject: (error: number) => void,
) => {
(resolve: (response: T) => void, reject: (error: number) => void) => {
if (this.getToken() !== null) {
const data = {
...params,
token: this.getToken(),
};
apiRequest(path, 'POST', data)
.then((response: ApiGenericDataType): void => resolve(response))
apiRequest<T>(path, 'POST', data)
.then((response: T): void => resolve(response))
.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 ServicesManager from './ServicesManager';
import {getSublistWithIds} from '../utils/Utils';
import {getSublistWithIds} from '../utils/Services';
import AsyncStorageManager from './AsyncStorageManager';
export default class DashboardManager extends ServicesManager {
getCurrentDashboard(): Array<ServiceItemType | null> {
const dashboardIdList = AsyncStorageManager.getObject(
const dashboardIdList = AsyncStorageManager.getObject<Array<string>>(
AsyncStorageManager.PREFERENCES.dashboardItems.key,
);
const allDatasets = [

View file

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

View file

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

View file

@ -17,22 +17,12 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @flow
import {NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
const speedOffset = 5;
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
*/
@ -46,6 +36,7 @@ export default class AutoHideHandler {
constructor(startHidden: boolean) {
this.listeners = [];
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
*/
onScroll(event: OnScrollType) {
onScroll(event: NativeSyntheticEvent<NativeScrollEvent>) {
const {nativeEvent} = event;
const speed =
nativeEvent.contentOffset.y < 0

View file

@ -17,12 +17,20 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @flow
import * as React from 'react';
import {useTheme} from 'react-native-paper';
import {createCollapsibleStack} from 'react-navigation-collapsible';
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.
@ -40,18 +48,16 @@ import StackNavigator, {StackNavigationOptions} from '@react-navigation/stack';
* @param headerColor The color of the header. Uses default color if not specified
* @returns {JSX.Element}
*/
export function createScreenCollapsibleStack(
export function CreateScreenCollapsibleStack(
name: string,
Stack: StackNavigator,
// eslint-disable-next-line flowtype/no-weak-types
Stack: StackNavigatorType,
component: React.ComponentType<any>,
title: string,
useNativeDriver?: boolean,
options?: StackNavigationOptions,
useNativeDriver: boolean = true,
options: StackNavigationOptions = {},
headerColor?: string,
): React.Node {
) {
const {colors} = useTheme();
const screenOptions = options != null ? options : {};
return createCollapsibleStack(
<Stack.Screen
name={name}
@ -61,12 +67,12 @@ export function createScreenCollapsibleStack(
headerStyle: {
backgroundColor: headerColor != null ? headerColor : colors.surface,
},
...screenOptions,
...options,
}}
/>,
{
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(
name: string,
Stack: StackNavigator,
// eslint-disable-next-line flowtype/no-weak-types
Stack: StackNavigatorType,
component: React.ComponentType<any>,
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/>.
*/
// @flow
import i18n from 'i18n-js';
import type {DeviceType} from '../screens/Amicale/Equipment/EquipmentListScreen';
import DateManager from '../managers/DateManager';
import type {CustomThemeType} from '../managers/ThemeManager';
import type {MarkedDatesObjectType} from '../screens/Amicale/Equipment/EquipmentRentScreen';
/**
@ -56,10 +53,12 @@ export function isEquipmentAvailable(item: DeviceType): boolean {
let isAvailable = true;
const today = getCurrentDay();
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 end = new Date(date.end);
if (!(today < start || today > end)) isAvailable = false;
if (!(today < start || today > end)) {
isAvailable = false;
}
});
return isAvailable;
}
@ -73,11 +72,13 @@ export function isEquipmentAvailable(item: DeviceType): boolean {
export function getFirstEquipmentAvailability(item: DeviceType): Date {
let firstAvailability = getCurrentDay();
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 end = new Date(date.end);
end.setDate(end.getDate() + 1);
if (firstAvailability >= start) firstAvailability = end;
if (firstAvailability >= start) {
firstAvailability = end;
}
});
return firstAvailability;
}
@ -93,23 +94,24 @@ export function getRelativeDateString(date: Date): string {
const monthDelta = date.getUTCMonth() - today.getUTCMonth();
const dayDelta = date.getUTCDate() - today.getUTCDate();
let translatedString = i18n.t('screens.equipment.today');
if (yearDelta > 0)
if (yearDelta > 0) {
translatedString = i18n.t('screens.equipment.otherYear', {
date: date.getDate(),
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
year: date.getFullYear(),
});
else if (monthDelta > 0)
} else if (monthDelta > 0) {
translatedString = i18n.t('screens.equipment.otherMonth', {
date: date.getDate(),
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
});
else if (dayDelta > 1)
} else if (dayDelta > 1) {
translatedString = i18n.t('screens.equipment.thisMonth', {
date: date.getDate(),
});
else if (dayDelta === 1)
} else if (dayDelta === 1) {
translatedString = i18n.t('screens.equipment.tomorrow');
}
return translatedString;
}
@ -162,8 +164,11 @@ export function getValidRange(
(direction === 1 && date < limit) ||
(direction === -1 && date > limit)
) {
if (direction === 1) validRange.push(getISODate(date));
else validRange.unshift(getISODate(date));
if (direction === 1) {
validRange.push(getISODate(date));
} else {
validRange.unshift(getISODate(date));
}
date.setDate(date.getDate() + direction);
}
return validRange;
@ -180,17 +185,27 @@ export function getValidRange(
*/
export function generateMarkedDates(
isSelection: boolean,
theme: CustomThemeType,
theme: ReactNativePaper.Theme,
range: Array<string>,
): MarkedDatesObjectType {
const markedDates = {};
const markedDates: {
[key: string]: {
startingDay: boolean;
endingDay: boolean;
color: string;
};
} = {};
for (let i = 0; i < range.length; i += 1) {
const isStart = i === 0;
const isEnd = i === range.length - 1;
let color;
if (isSelection && (isStart || isEnd)) color = theme.colors.primary;
else if (isSelection) color = theme.colors.danger;
else color = theme.colors.textDisabled;
if (isSelection && (isStart || isEnd)) {
color = theme.colors.primary;
} else if (isSelection) {
color = theme.colors.danger;
} else {
color = theme.colors.textDisabled;
}
markedDates[range[i]] = {
startingDay: isStart,
endingDay: isEnd,

View file

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

View file

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

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @flow
import {
checkNotifications,
requestNotifications,
@ -41,12 +39,17 @@ const reminderIdFactor = 100;
export async function askPermissions(): Promise<void> {
return new Promise((resolve: () => void, reject: () => void) => {
checkNotifications().then(({status}: {status: string}) => {
if (status === RESULTS.GRANTED) resolve();
else if (status === RESULTS.BLOCKED) reject();
else {
requestNotifications().then((result: {status: string}) => {
if (result.status === RESULTS.GRANTED) resolve();
else reject();
if (status === RESULTS.GRANTED) {
resolve();
} else if (status === RESULTS.BLOCKED) {
reject();
} else {
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/>.
*/
// @flow
export type PlanningEventType = {
id: number,
title: string,
date_begin: string,
club: string,
category_id: number,
description: string,
place: string,
url: string,
logo: string | null,
id: number;
title: string;
date_begin: string;
club: string;
category_id: number;
description: string;
place: string;
url: string;
logo: string | null;
};
// 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
* @return {boolean}
*/
export function isEventDateStringFormatValid(dateString: ?string): boolean {
export function isEventDateStringFormatValid(dateString?: string): boolean {
return (
dateString !== undefined &&
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
*/
export function stringToDate(dateString: string): Date | null {
let date = new Date();
let date: Date | null = new Date();
if (isEventDateStringFormatValid(dateString)) {
const stringArray = dateString.split(' ');
const dateArray = stringArray[0].split('-');
@ -68,7 +66,9 @@ export function stringToDate(dateString: string): Date | null {
parseInt(dateArray[2], 10),
);
date.setHours(parseInt(timeArray[0], 10), parseInt(timeArray[1], 10), 0, 0);
} else date = null;
} else {
date = null;
}
return date;
}
@ -111,7 +111,9 @@ export function getCurrentDateString(): string {
export function isEventBefore(event1Date: string, event2Date: string): boolean {
const date1 = stringToDate(event1Date);
const date2 = stringToDate(event2Date);
if (date1 !== null && date2 !== null) return date1 < date2;
if (date1 !== null && date2 !== null) {
return date1 < date2;
}
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
*/
export function getDateOnlyString(dateString: string): string | null {
if (isEventDateStringFormatValid(dateString)) return dateString.split(' ')[0];
if (isEventDateStringFormatValid(dateString)) {
return dateString.split(' ')[0];
}
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
*/
export function getTimeOnlyString(dateString: string): string | null {
if (isEventDateStringFormatValid(dateString)) return dateString.split(' ')[1];
if (isEventDateStringFormatValid(dateString)) {
return dateString.split(' ')[1];
}
return null;
}
@ -148,7 +154,7 @@ export function getTimeOnlyString(dateString: string): string | null {
* @param description The text to check
* @return {boolean}
*/
export function isDescriptionEmpty(description: ?string): boolean {
export function isDescriptionEmpty(description?: string): boolean {
if (description !== undefined && description !== null) {
return (
description
@ -177,10 +183,12 @@ export function generateEmptyCalendar(
): {[key: string]: Array<PlanningEventType>} {
const end = new Date(Date.now());
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)) {
const dateString = getDateOnlyString(dateToString(new Date(d), false));
if (dateString !== null) daysOfYear[dateString] = [];
if (dateString !== null) {
daysOfYear[dateString] = [];
}
}
return daysOfYear;
}
@ -197,8 +205,9 @@ export function pushEventInOrder(
eventArray: Array<PlanningEventType>,
event: PlanningEventType,
) {
if (eventArray.length === 0) eventArray.push(event);
else {
if (eventArray.length === 0) {
eventArray.push(event);
} else {
for (let i = 0; i < eventArray.length; i += 1) {
if (isEventBefore(event.date_begin, eventArray[i].date_begin)) {
eventArray.splice(i, 0, event);
@ -231,7 +240,9 @@ export function generateEventAgenda(
const dateString = getDateOnlyString(eventList[i].date_begin);
if (dateString != null) {
const eventArray = agendaItems[dateString];
if (eventArray != null) pushEventInOrder(eventArray, eventList[i]);
if (eventArray != null) {
pushEventInOrder(eventArray, eventList[i]);
}
}
}
return agendaItems;

View file

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

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @flow
/**
* Sanitizes the given string to improve search performance.
*
@ -60,7 +58,9 @@ export function isItemInCategoryFilter(
): boolean {
let itemFound = false;
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;
}

View file

@ -17,10 +17,25 @@
* 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
*
* @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
@ -31,11 +46,11 @@ import ThemeManager from '../managers/ThemeManager';
* @param originalList The original list
* @returns {[]}
*/
export function getSublistWithIds<T>(
export function getSublistWithIds<T extends {key: string}>(
idList: Array<string>,
originalList: Array<{key: string, ...T}>,
): Array<{key: string, ...T} | null> {
const subList = [];
originalList: Array<T>,
) {
const subList: Array<T | null> = [];
for (let i = 0; i < idList.length; i += 1) {
subList.push(null);
}
@ -45,26 +60,10 @@ export function getSublistWithIds<T>(
if (idList.includes(item.key)) {
subList[idList.indexOf(item.key)] = item;
itemsAdded += 1;
if (itemsAdded === idList.length) break;
if (itemsAdded === idList.length) {
break;
}
}
}
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/>.
*/
// @flow
import {Linking} from 'react-native';
export type ParsedUrlDataType = {
route: string,
data: {[key: string]: string},
route: string;
data: {[key: string]: string};
};
export type ParsedUrlCallbackType = (parsedData: ParsedUrlDataType) => void;
type RawParsedUrlDataType = {
path: string,
queryParams: {[key: string]: string},
path: string;
queryParams: {[key: string]: string};
};
/**
@ -69,7 +67,7 @@ export default class URLHandler {
let parsedData: RawParsedUrlDataType | null = null;
const urlNoScheme = url.replace(URLHandler.SCHEME, '');
if (urlNoScheme != null) {
const params = {};
const params: {[key: string]: string} = {};
const [path, fullParamsString] = urlNoScheme.split('?');
if (fullParamsString != null) {
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;
}
@ -99,10 +99,11 @@ export default class URLHandler {
if (rawParsedUrlData != null) {
const {path} = rawParsedUrlData;
const {queryParams} = rawParsedUrlData;
if (URLHandler.isClubInformationLink(path))
if (URLHandler.isClubInformationLink(path)) {
parsedData = URLHandler.generateClubInformationData(queryParams);
else if (URLHandler.isPlanningInformationLink(path))
} else if (URLHandler.isPlanningInformationLink(path)) {
parsedData = URLHandler.generatePlanningInformationData(queryParams);
}
}
return parsedData;
@ -145,15 +146,16 @@ export default class URLHandler {
* @returns {null|{route: string, data: {clubId: number}}}
*/
static generateClubInformationData(params: {
[key: string]: string,
[key: string]: string;
}): ParsedUrlDataType | null {
if (params.id != null) {
const id = parseInt(params.id, 10);
if (!Number.isNaN(id))
if (!Number.isNaN(id)) {
return {
route: URLHandler.CLUB_INFO_ROUTE,
data: {clubId: id.toString()},
};
}
}
return null;
}
@ -165,7 +167,7 @@ export default class URLHandler {
* @returns {null|{route: string, data: {clubId: number}}}
*/
static generatePlanningInformationData(params: {
[key: string]: string,
[key: string]: string;
}): ParsedUrlDataType | null {
if (params.id != null) {
const id = parseInt(params.id, 10);
@ -201,7 +203,9 @@ export default class URLHandler {
onUrl = ({url}: {url: string}) => {
if (url != null) {
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
*/
onInitialUrl = (url: ?string) => {
onInitialUrl = (url: string | null) => {
if (url != null) {
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/>.
*/
// @flow
import {Platform, StatusBar} from 'react-native';
import ThemeManager from '../managers/ThemeManager';
/**
* Gets the given services list without items of the given ids
*
* @param idList The ids of items to remove
* @param sourceList The item list to use as source
* @returns {[]}
* Updates status bar content color if on iOS only,
* as the android status bar is always set to black.
*/
export default function getStrippedServicesList<T>(
idList: Array<string>,
sourceList: Array<{key: string, ...T}>,
): Array<{key: string, ...T}> {
const newArray = [];
sourceList.forEach((item: {key: string, ...T}) => {
if (!idList.includes(item.key)) newArray.push(item);
});
return newArray;
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,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @flow
export const ERROR_TYPE = {
SUCCESS: 0,
BAD_CREDENTIALS: 1,
@ -34,15 +32,14 @@ export const ERROR_TYPE = {
};
export type ApiDataLoginType = {
token: string,
token: string;
};
// eslint-disable-next-line flowtype/no-weak-types
export type ApiGenericDataType = {[key: string]: any};
type ApiResponseType = {
error: number,
data: ApiGenericDataType,
type ApiResponseType<T> = {
error: number;
data: T;
};
const API_ENDPOINT = 'https://www.amicale-insat.fr/api/';
@ -55,11 +52,10 @@ const API_ENDPOINT = 'https://www.amicale-insat.fr/api/';
* @param response
* @returns {boolean}
*/
export function isApiResponseValid(response: ApiResponseType): boolean {
export function isApiResponseValid<T>(response: ApiResponseType<T>): boolean {
return (
response != null &&
response.error != null &&
typeof response.error === 'number' &&
response.data != null &&
typeof response.data === 'object'
);
@ -76,18 +72,17 @@ export function isApiResponseValid(response: ApiResponseType): boolean {
* @param params The params to use for this request
* @returns {Promise<ApiGenericDataType>}
*/
export async function apiRequest(
export async function apiRequest<T>(
path: string,
method: string,
params?: {...},
): Promise<ApiGenericDataType> {
params?: object,
): Promise<T> {
return new Promise(
(
resolve: (data: ApiGenericDataType) => void,
reject: (error: number) => void,
) => {
(resolve: (data: T) => void, reject: (error: number) => void) => {
let requestParams = {};
if (params != null) requestParams = {...params};
if (params != null) {
requestParams = {...params};
}
fetch(API_ENDPOINT + path, {
method,
headers: new Headers({
@ -96,14 +91,20 @@ export async function apiRequest(
}),
body: JSON.stringify(requestParams),
})
.then(async (response: Response): Promise<ApiResponseType> =>
response.json(),
.then(
async (response: Response): Promise<ApiResponseType<T>> =>
response.json(),
)
.then((response: ApiResponseType) => {
.then((response: ApiResponseType<T>) => {
if (isApiResponseValid(response)) {
if (response.error === ERROR_TYPE.SUCCESS) resolve(response.data);
else reject(response.error);
} else reject(ERROR_TYPE.SERVER_ERROR);
if (response.error === ERROR_TYPE.SUCCESS) {
resolve(response.data);
} else {
reject(response.error);
}
} else {
reject(ERROR_TYPE.SERVER_ERROR);
}
})
.catch((): void => reject(ERROR_TYPE.CONNECTION_ERROR));
},
@ -121,14 +122,10 @@ export async function apiRequest(
* @param url The urls to fetch data from
* @return Promise<any>
*/
// eslint-disable-next-line flowtype/no-weak-types
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) => {
fetch(url)
// eslint-disable-next-line flowtype/no-weak-types
.then(async (response: Response): Promise<any> => response.json())
// eslint-disable-next-line flowtype/no-weak-types
.then((data: any): void => resolve(data))
.catch((): void => reject());
});

View file

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