Update prettier and eslint config

This commit is contained in:
Arnaud Vergnet 2021-05-07 15:10:36 +02:00
parent 18f7a6abbd
commit 02f9241d28
151 changed files with 5434 additions and 5013 deletions

View file

@ -1,6 +0,0 @@
module.exports = {
root: true,
extends: '@react-native-community',
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
};

View file

@ -1,6 +0,0 @@
module.exports = {
bracketSpacing: false,
jsxBracketSameLine: true,
singleQuote: true,
trailingComma: 'all',
};

4
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"i18n-ally.localesPaths": "locales",
"i18n-ally.keystyle": "nested"
}

49
App.tsx
View file

@ -17,13 +17,13 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
import * as React from 'react'; import React from 'react';
import {LogBox, Platform, SafeAreaView, View} from 'react-native'; import { LogBox, Platform, SafeAreaView, View } from 'react-native';
import {NavigationContainer} from '@react-navigation/native'; import { NavigationContainer } from '@react-navigation/native';
import {Provider as PaperProvider} from 'react-native-paper'; import { Provider as PaperProvider } from 'react-native-paper';
import {setSafeBounceHeight} from 'react-navigation-collapsible'; import { setSafeBounceHeight } from 'react-navigation-collapsible';
import SplashScreen from 'react-native-splash-screen'; import SplashScreen from 'react-native-splash-screen';
import {OverflowMenuProvider} from 'react-navigation-header-buttons'; import { OverflowMenuProvider } from 'react-navigation-header-buttons';
import AsyncStorageManager from './src/managers/AsyncStorageManager'; import AsyncStorageManager from './src/managers/AsyncStorageManager';
import CustomIntroSlider from './src/components/Overrides/CustomIntroSlider'; import CustomIntroSlider from './src/components/Overrides/CustomIntroSlider';
import ThemeManager from './src/managers/ThemeManager'; import ThemeManager from './src/managers/ThemeManager';
@ -31,11 +31,12 @@ import MainNavigator from './src/navigation/MainNavigator';
import AprilFoolsManager from './src/managers/AprilFoolsManager'; import AprilFoolsManager from './src/managers/AprilFoolsManager';
import Update from './src/constants/Update'; import Update from './src/constants/Update';
import ConnectionManager from './src/managers/ConnectionManager'; import ConnectionManager from './src/managers/ConnectionManager';
import type {ParsedUrlDataType} from './src/utils/URLHandler'; import type { ParsedUrlDataType } from './src/utils/URLHandler';
import URLHandler from './src/utils/URLHandler'; import URLHandler from './src/utils/URLHandler';
import {setupStatusBar} from './src/utils/Utils'; import { setupStatusBar } from './src/utils/Utils';
import initLocales from './src/utils/Locales'; import initLocales from './src/utils/Locales';
import {NavigationContainerRef} from '@react-navigation/core'; import { NavigationContainerRef } from '@react-navigation/core';
import GENERAL_STYLES from './src/constants/Styles';
// Native optimizations https://reactnavigation.org/docs/react-native-screens // Native optimizations https://reactnavigation.org/docs/react-native-screens
// Crashes app when navigating away from webview on android 9+ // Crashes app when navigating away from webview on android 9+
@ -56,11 +57,11 @@ type StateType = {
}; };
export default class App extends React.Component<{}, StateType> { export default class App extends React.Component<{}, StateType> {
navigatorRef: {current: null | NavigationContainerRef}; navigatorRef: { current: null | NavigationContainerRef };
defaultHomeRoute: string | null; defaultHomeRoute: string | null;
defaultHomeData: {[key: string]: string}; defaultHomeData: { [key: string]: string };
urlHandler: URLHandler; urlHandler: URLHandler;
@ -106,7 +107,7 @@ export default class App extends React.Component<{}, StateType> {
if (nav != null) { if (nav != null) {
nav.navigate('home', { nav.navigate('home', {
screen: 'index', screen: 'index',
params: {nextScreen: parsedData.route, data: parsedData.data}, params: { nextScreen: parsedData.route, data: parsedData.data },
}); });
} }
}; };
@ -132,15 +133,15 @@ export default class App extends React.Component<{}, StateType> {
}); });
AsyncStorageManager.set( AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.showIntro.key, AsyncStorageManager.PREFERENCES.showIntro.key,
false, false
); );
AsyncStorageManager.set( AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.updateNumber.key, AsyncStorageManager.PREFERENCES.updateNumber.key,
Update.number, Update.number
); );
AsyncStorageManager.set( AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.showAprilFoolsStart.key, AsyncStorageManager.PREFERENCES.showAprilFoolsStart.key,
false, false
); );
}; };
@ -161,16 +162,16 @@ export default class App extends React.Component<{}, StateType> {
isLoading: false, isLoading: false,
currentTheme: ThemeManager.getCurrentTheme(), currentTheme: ThemeManager.getCurrentTheme(),
showIntro: AsyncStorageManager.getBool( showIntro: AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.showIntro.key, AsyncStorageManager.PREFERENCES.showIntro.key
), ),
showUpdate: showUpdate:
AsyncStorageManager.getNumber( AsyncStorageManager.getNumber(
AsyncStorageManager.PREFERENCES.updateNumber.key, AsyncStorageManager.PREFERENCES.updateNumber.key
) !== Update.number, ) !== Update.number,
showAprilFools: showAprilFools:
AprilFoolsManager.getInstance().isAprilFoolsEnabled() && AprilFoolsManager.getInstance().isAprilFoolsEnabled() &&
AsyncStorageManager.getBool( AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.showAprilFoolsStart.key, AsyncStorageManager.PREFERENCES.showAprilFoolsStart.key
), ),
}); });
SplashScreen.hide(); SplashScreen.hide();
@ -194,7 +195,7 @@ export default class App extends React.Component<{}, StateType> {
* Renders the app based on loading state * Renders the app based on loading state
*/ */
render() { render() {
const {state} = this; const { state } = this;
if (state.isLoading) { if (state.isLoading) {
return null; return null;
} }
@ -213,12 +214,14 @@ export default class App extends React.Component<{}, StateType> {
<View <View
style={{ style={{
backgroundColor: ThemeManager.getCurrentTheme().colors.background, backgroundColor: ThemeManager.getCurrentTheme().colors.background,
flex: 1, ...GENERAL_STYLES.flex,
}}> }}
<SafeAreaView style={{flex: 1}}> >
<SafeAreaView style={GENERAL_STYLES.flex}>
<NavigationContainer <NavigationContainer
theme={state.currentTheme} theme={state.currentTheme}
ref={this.navigatorRef}> ref={this.navigatorRef}
>
<MainNavigator <MainNavigator
defaultHomeRoute={this.defaultHomeRoute} defaultHomeRoute={this.defaultHomeRoute}
defaultHomeData={this.defaultHomeData} defaultHomeData={this.defaultHomeData}

View file

@ -1,7 +1,7 @@
const keychainMock = { const keychainMock = {
SECURITY_LEVEL_ANY: "MOCK_SECURITY_LEVEL_ANY", SECURITY_LEVEL_ANY: 'MOCK_SECURITY_LEVEL_ANY',
SECURITY_LEVEL_SECURE_SOFTWARE: "MOCK_SECURITY_LEVEL_SECURE_SOFTWARE", SECURITY_LEVEL_SECURE_SOFTWARE: 'MOCK_SECURITY_LEVEL_SECURE_SOFTWARE',
SECURITY_LEVEL_SECURE_HARDWARE: "MOCK_SECURITY_LEVEL_SECURE_HARDWARE", SECURITY_LEVEL_SECURE_HARDWARE: 'MOCK_SECURITY_LEVEL_SECURE_HARDWARE',
} };
export default keychainMock; export default keychainMock;

View file

@ -1,11 +1,9 @@
/* eslint-disable */
import React from 'react';
import ConnectionManager from '../../src/managers/ConnectionManager'; import ConnectionManager from '../../src/managers/ConnectionManager';
import {ERROR_TYPE} from '../../src/utils/WebData'; import { ERROR_TYPE } from '../../src/utils/WebData';
jest.mock('react-native-keychain'); jest.mock('react-native-keychain');
// eslint-disable-next-line no-unused-vars
const fetch = require('isomorphic-fetch'); // fetch is not implemented in nodeJS but in react-native const fetch = require('isomorphic-fetch'); // fetch is not implemented in nodeJS but in react-native
const c = ConnectionManager.getInstance(); const c = ConnectionManager.getInstance();
@ -44,7 +42,7 @@ test('connect bad credentials', () => {
}); });
}); });
return expect(c.connect('email', 'password')).rejects.toBe( return expect(c.connect('email', 'password')).rejects.toBe(
ERROR_TYPE.BAD_CREDENTIALS, ERROR_TYPE.BAD_CREDENTIALS
); );
}); });
@ -54,7 +52,7 @@ test('connect good credentials', () => {
json: () => { json: () => {
return { return {
error: ERROR_TYPE.SUCCESS, error: ERROR_TYPE.SUCCESS,
data: {token: 'token'}, data: { token: 'token' },
}; };
}, },
}); });
@ -79,7 +77,7 @@ test('connect good credentials no consent', () => {
}); });
}); });
return expect(c.connect('email', 'password')).rejects.toBe( return expect(c.connect('email', 'password')).rejects.toBe(
ERROR_TYPE.NO_CONSENT, ERROR_TYPE.NO_CONSENT
); );
}); });
@ -89,7 +87,7 @@ test('connect good credentials, fail save token', () => {
json: () => { json: () => {
return { return {
error: ERROR_TYPE.SUCCESS, error: ERROR_TYPE.SUCCESS,
data: {token: 'token'}, data: { token: 'token' },
}; };
}, },
}); });
@ -100,7 +98,7 @@ test('connect good credentials, fail save token', () => {
return Promise.reject(false); return Promise.reject(false);
}); });
return expect(c.connect('email', 'password')).rejects.toBe( return expect(c.connect('email', 'password')).rejects.toBe(
ERROR_TYPE.TOKEN_SAVE, ERROR_TYPE.TOKEN_SAVE
); );
}); });
@ -109,7 +107,7 @@ test('connect connection error', () => {
return Promise.reject(); return Promise.reject();
}); });
return expect(c.connect('email', 'password')).rejects.toBe( return expect(c.connect('email', 'password')).rejects.toBe(
ERROR_TYPE.CONNECTION_ERROR, ERROR_TYPE.CONNECTION_ERROR
); );
}); });
@ -125,7 +123,7 @@ test('connect bogus response 1', () => {
}); });
}); });
return expect(c.connect('email', 'password')).rejects.toBe( return expect(c.connect('email', 'password')).rejects.toBe(
ERROR_TYPE.SERVER_ERROR, ERROR_TYPE.SERVER_ERROR
); );
}); });
@ -140,14 +138,14 @@ test('authenticatedRequest success', () => {
json: () => { json: () => {
return { return {
error: ERROR_TYPE.SUCCESS, error: ERROR_TYPE.SUCCESS,
data: {coucou: 'toi'}, data: { coucou: 'toi' },
}; };
}, },
}); });
}); });
return expect( return expect(
c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'), c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check')
).resolves.toStrictEqual({coucou: 'toi'}); ).resolves.toStrictEqual({ coucou: 'toi' });
}); });
test('authenticatedRequest error wrong token', () => { test('authenticatedRequest error wrong token', () => {
@ -167,7 +165,7 @@ test('authenticatedRequest error wrong token', () => {
}); });
}); });
return expect( return expect(
c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'), c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check')
).rejects.toBe(ERROR_TYPE.BAD_TOKEN); ).rejects.toBe(ERROR_TYPE.BAD_TOKEN);
}); });
@ -187,7 +185,7 @@ test('authenticatedRequest error bogus response', () => {
}); });
}); });
return expect( return expect(
c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'), c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check')
).rejects.toBe(ERROR_TYPE.SERVER_ERROR); ).rejects.toBe(ERROR_TYPE.SERVER_ERROR);
}); });
@ -201,7 +199,7 @@ test('authenticatedRequest connection error', () => {
return Promise.reject(); return Promise.reject();
}); });
return expect( return expect(
c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'), c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check')
).rejects.toBe(ERROR_TYPE.CONNECTION_ERROR); ).rejects.toBe(ERROR_TYPE.CONNECTION_ERROR);
}); });
@ -212,6 +210,6 @@ test('authenticatedRequest error no token', () => {
return null; return null;
}); });
return expect( return expect(
c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check'), c.authenticatedRequest('https://www.amicale-insat.fr/api/token/check')
).rejects.toBe(ERROR_TYPE.TOKEN_RETRIEVE); ).rejects.toBe(ERROR_TYPE.TOKEN_RETRIEVE);
}); });

View file

@ -1,6 +1,3 @@
/* eslint-disable */
import React from 'react';
import * as EquipmentBooking from '../../src/utils/EquipmentBooking'; import * as EquipmentBooking from '../../src/utils/EquipmentBooking';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
@ -18,7 +15,7 @@ test('getCurrentDay', () => {
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => new Date('2020-01-14 14:50:35').getTime()); .mockImplementation(() => new Date('2020-01-14 14:50:35').getTime());
expect(EquipmentBooking.getCurrentDay().getTime()).toBe( expect(EquipmentBooking.getCurrentDay().getTime()).toBe(
new Date('2020-01-14').getTime(), new Date('2020-01-14').getTime()
); );
}); });
@ -30,19 +27,19 @@ test('isEquipmentAvailable', () => {
id: 1, id: 1,
name: 'Petit barbecue', name: 'Petit barbecue',
caution: 100, caution: 100,
booked_at: [{begin: '2020-07-07', end: '2020-07-10'}], booked_at: [{ begin: '2020-07-07', end: '2020-07-10' }],
}; };
expect(EquipmentBooking.isEquipmentAvailable(testDevice)).toBeFalse(); expect(EquipmentBooking.isEquipmentAvailable(testDevice)).toBeFalse();
testDevice.booked_at = [{begin: '2020-07-07', end: '2020-07-09'}]; testDevice.booked_at = [{ begin: '2020-07-07', end: '2020-07-09' }];
expect(EquipmentBooking.isEquipmentAvailable(testDevice)).toBeFalse(); expect(EquipmentBooking.isEquipmentAvailable(testDevice)).toBeFalse();
testDevice.booked_at = [{begin: '2020-07-09', end: '2020-07-10'}]; testDevice.booked_at = [{ begin: '2020-07-09', end: '2020-07-10' }];
expect(EquipmentBooking.isEquipmentAvailable(testDevice)).toBeFalse(); expect(EquipmentBooking.isEquipmentAvailable(testDevice)).toBeFalse();
testDevice.booked_at = [ testDevice.booked_at = [
{begin: '2020-07-07', end: '2020-07-8'}, { begin: '2020-07-07', end: '2020-07-8' },
{begin: '2020-07-10', end: '2020-07-12'}, { begin: '2020-07-10', end: '2020-07-12' },
]; ];
expect(EquipmentBooking.isEquipmentAvailable(testDevice)).toBeTrue(); expect(EquipmentBooking.isEquipmentAvailable(testDevice)).toBeTrue();
}); });
@ -55,29 +52,29 @@ test('getFirstEquipmentAvailability', () => {
id: 1, id: 1,
name: 'Petit barbecue', name: 'Petit barbecue',
caution: 100, caution: 100,
booked_at: [{begin: '2020-07-07', end: '2020-07-10'}], booked_at: [{ begin: '2020-07-07', end: '2020-07-10' }],
}; };
expect( expect(
EquipmentBooking.getFirstEquipmentAvailability(testDevice).getTime(), EquipmentBooking.getFirstEquipmentAvailability(testDevice).getTime()
).toBe(new Date('2020-07-11').getTime()); ).toBe(new Date('2020-07-11').getTime());
testDevice.booked_at = [{begin: '2020-07-07', end: '2020-07-09'}]; testDevice.booked_at = [{ begin: '2020-07-07', end: '2020-07-09' }];
expect( expect(
EquipmentBooking.getFirstEquipmentAvailability(testDevice).getTime(), EquipmentBooking.getFirstEquipmentAvailability(testDevice).getTime()
).toBe(new Date('2020-07-10').getTime()); ).toBe(new Date('2020-07-10').getTime());
testDevice.booked_at = [ testDevice.booked_at = [
{begin: '2020-07-07', end: '2020-07-09'}, { begin: '2020-07-07', end: '2020-07-09' },
{begin: '2020-07-10', end: '2020-07-16'}, { begin: '2020-07-10', end: '2020-07-16' },
]; ];
expect( expect(
EquipmentBooking.getFirstEquipmentAvailability(testDevice).getTime(), EquipmentBooking.getFirstEquipmentAvailability(testDevice).getTime()
).toBe(new Date('2020-07-17').getTime()); ).toBe(new Date('2020-07-17').getTime());
testDevice.booked_at = [ testDevice.booked_at = [
{begin: '2020-07-07', end: '2020-07-09'}, { begin: '2020-07-07', end: '2020-07-09' },
{begin: '2020-07-10', end: '2020-07-12'}, { begin: '2020-07-10', end: '2020-07-12' },
{begin: '2020-07-14', end: '2020-07-16'}, { begin: '2020-07-14', end: '2020-07-16' },
]; ];
expect( expect(
EquipmentBooking.getFirstEquipmentAvailability(testDevice).getTime(), EquipmentBooking.getFirstEquipmentAvailability(testDevice).getTime()
).toBe(new Date('2020-07-13').getTime()); ).toBe(new Date('2020-07-13').getTime());
}); });
@ -85,7 +82,7 @@ test('getRelativeDateString', () => {
jest jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => new Date('2020-07-09').getTime()); .mockImplementation(() => new Date('2020-07-09').getTime());
jest.spyOn(i18n, 't').mockImplementation((translationString: string) => { jest.spyOn(i18n, 't').mockImplementation((translationString) => {
const prefix = 'screens.equipment.'; const prefix = 'screens.equipment.';
if (translationString === prefix + 'otherYear') return '0'; if (translationString === prefix + 'otherYear') return '0';
else if (translationString === prefix + 'otherMonth') return '1'; else if (translationString === prefix + 'otherMonth') return '1';
@ -95,25 +92,25 @@ test('getRelativeDateString', () => {
else return null; else return null;
}); });
expect(EquipmentBooking.getRelativeDateString(new Date('2020-07-09'))).toBe( expect(EquipmentBooking.getRelativeDateString(new Date('2020-07-09'))).toBe(
'4', '4'
); );
expect(EquipmentBooking.getRelativeDateString(new Date('2020-07-10'))).toBe( expect(EquipmentBooking.getRelativeDateString(new Date('2020-07-10'))).toBe(
'3', '3'
); );
expect(EquipmentBooking.getRelativeDateString(new Date('2020-07-11'))).toBe( expect(EquipmentBooking.getRelativeDateString(new Date('2020-07-11'))).toBe(
'2', '2'
); );
expect(EquipmentBooking.getRelativeDateString(new Date('2020-07-30'))).toBe( expect(EquipmentBooking.getRelativeDateString(new Date('2020-07-30'))).toBe(
'2', '2'
); );
expect(EquipmentBooking.getRelativeDateString(new Date('2020-08-30'))).toBe( expect(EquipmentBooking.getRelativeDateString(new Date('2020-08-30'))).toBe(
'1', '1'
); );
expect(EquipmentBooking.getRelativeDateString(new Date('2020-11-10'))).toBe( expect(EquipmentBooking.getRelativeDateString(new Date('2020-11-10'))).toBe(
'1', '1'
); );
expect(EquipmentBooking.getRelativeDateString(new Date('2021-11-10'))).toBe( expect(EquipmentBooking.getRelativeDateString(new Date('2021-11-10'))).toBe(
'0', '0'
); );
}); });
@ -122,7 +119,7 @@ test('getValidRange', () => {
id: 1, id: 1,
name: 'Petit barbecue', name: 'Petit barbecue',
caution: 100, caution: 100,
booked_at: [{begin: '2020-07-07', end: '2020-07-10'}], booked_at: [{ begin: '2020-07-07', end: '2020-07-10' }],
}; };
let start = new Date('2020-07-11'); let start = new Date('2020-07-11');
let end = new Date('2020-07-15'); let end = new Date('2020-07-15');
@ -134,62 +131,62 @@ test('getValidRange', () => {
'2020-07-15', '2020-07-15',
]; ];
expect(EquipmentBooking.getValidRange(start, end, testDevice)).toStrictEqual( expect(EquipmentBooking.getValidRange(start, end, testDevice)).toStrictEqual(
result, result
); );
testDevice.booked_at = [ testDevice.booked_at = [
{begin: '2020-07-07', end: '2020-07-10'}, { begin: '2020-07-07', end: '2020-07-10' },
{begin: '2020-07-13', end: '2020-07-15'}, { begin: '2020-07-13', end: '2020-07-15' },
]; ];
result = ['2020-07-11', '2020-07-12']; result = ['2020-07-11', '2020-07-12'];
expect(EquipmentBooking.getValidRange(start, end, testDevice)).toStrictEqual( expect(EquipmentBooking.getValidRange(start, end, testDevice)).toStrictEqual(
result, result
); );
testDevice.booked_at = [{begin: '2020-07-12', end: '2020-07-13'}]; testDevice.booked_at = [{ begin: '2020-07-12', end: '2020-07-13' }];
result = ['2020-07-11']; result = ['2020-07-11'];
expect(EquipmentBooking.getValidRange(start, end, testDevice)).toStrictEqual( expect(EquipmentBooking.getValidRange(start, end, testDevice)).toStrictEqual(
result, result
); );
testDevice.booked_at = [{begin: '2020-07-07', end: '2020-07-12'}]; testDevice.booked_at = [{ begin: '2020-07-07', end: '2020-07-12' }];
result = ['2020-07-13', '2020-07-14', '2020-07-15']; result = ['2020-07-13', '2020-07-14', '2020-07-15'];
expect(EquipmentBooking.getValidRange(end, start, testDevice)).toStrictEqual( expect(EquipmentBooking.getValidRange(end, start, testDevice)).toStrictEqual(
result, result
); );
start = new Date('2020-07-14'); start = new Date('2020-07-14');
end = new Date('2020-07-14'); end = new Date('2020-07-14');
result = ['2020-07-14']; result = ['2020-07-14'];
expect( expect(
EquipmentBooking.getValidRange(start, start, testDevice), EquipmentBooking.getValidRange(start, start, testDevice)
).toStrictEqual(result); ).toStrictEqual(result);
expect(EquipmentBooking.getValidRange(end, start, testDevice)).toStrictEqual( expect(EquipmentBooking.getValidRange(end, start, testDevice)).toStrictEqual(
result, result
); );
expect(EquipmentBooking.getValidRange(start, end, null)).toStrictEqual( expect(EquipmentBooking.getValidRange(start, end, null)).toStrictEqual(
result, result
); );
start = new Date('2020-07-14'); start = new Date('2020-07-14');
end = new Date('2020-07-17'); end = new Date('2020-07-17');
result = ['2020-07-14', '2020-07-15', '2020-07-16', '2020-07-17']; result = ['2020-07-14', '2020-07-15', '2020-07-16', '2020-07-17'];
expect(EquipmentBooking.getValidRange(start, end, null)).toStrictEqual( expect(EquipmentBooking.getValidRange(start, end, null)).toStrictEqual(
result, result
); );
testDevice.booked_at = [{begin: '2020-07-17', end: '2020-07-17'}]; testDevice.booked_at = [{ begin: '2020-07-17', end: '2020-07-17' }];
result = ['2020-07-14', '2020-07-15', '2020-07-16']; result = ['2020-07-14', '2020-07-15', '2020-07-16'];
expect(EquipmentBooking.getValidRange(start, end, testDevice)).toStrictEqual( expect(EquipmentBooking.getValidRange(start, end, testDevice)).toStrictEqual(
result, result
); );
testDevice.booked_at = [ testDevice.booked_at = [
{begin: '2020-07-12', end: '2020-07-13'}, { begin: '2020-07-12', end: '2020-07-13' },
{begin: '2020-07-15', end: '2020-07-20'}, { begin: '2020-07-15', end: '2020-07-20' },
]; ];
start = new Date('2020-07-11'); start = new Date('2020-07-11');
end = new Date('2020-07-23'); end = new Date('2020-07-23');
result = ['2020-07-21', '2020-07-22', '2020-07-23']; result = ['2020-07-21', '2020-07-22', '2020-07-23'];
expect(EquipmentBooking.getValidRange(end, start, testDevice)).toStrictEqual( expect(EquipmentBooking.getValidRange(end, start, testDevice)).toStrictEqual(
result, result
); );
}); });
@ -205,7 +202,7 @@ test('generateMarkedDates', () => {
id: 1, id: 1,
name: 'Petit barbecue', name: 'Petit barbecue',
caution: 100, caution: 100,
booked_at: [{begin: '2020-07-07', end: '2020-07-10'}], booked_at: [{ begin: '2020-07-07', end: '2020-07-10' }],
}; };
let start = new Date('2020-07-11'); let start = new Date('2020-07-11');
let end = new Date('2020-07-13'); let end = new Date('2020-07-13');
@ -228,7 +225,7 @@ test('generateMarkedDates', () => {
}, },
}; };
expect( expect(
EquipmentBooking.generateMarkedDates(true, theme, range), EquipmentBooking.generateMarkedDates(true, theme, range)
).toStrictEqual(result); ).toStrictEqual(result);
result = { result = {
'2020-07-11': { '2020-07-11': {
@ -248,7 +245,7 @@ test('generateMarkedDates', () => {
}, },
}; };
expect( expect(
EquipmentBooking.generateMarkedDates(false, theme, range), EquipmentBooking.generateMarkedDates(false, theme, range)
).toStrictEqual(result); ).toStrictEqual(result);
result = { result = {
'2020-07-11': { '2020-07-11': {
@ -269,10 +266,10 @@ test('generateMarkedDates', () => {
}; };
range = EquipmentBooking.getValidRange(end, start, testDevice); range = EquipmentBooking.getValidRange(end, start, testDevice);
expect( expect(
EquipmentBooking.generateMarkedDates(false, theme, range), EquipmentBooking.generateMarkedDates(false, theme, range)
).toStrictEqual(result); ).toStrictEqual(result);
testDevice.booked_at = [{begin: '2020-07-13', end: '2020-07-15'}]; testDevice.booked_at = [{ begin: '2020-07-13', end: '2020-07-15' }];
result = { result = {
'2020-07-11': { '2020-07-11': {
startingDay: true, startingDay: true,
@ -287,10 +284,10 @@ test('generateMarkedDates', () => {
}; };
range = EquipmentBooking.getValidRange(start, end, testDevice); range = EquipmentBooking.getValidRange(start, end, testDevice);
expect( expect(
EquipmentBooking.generateMarkedDates(true, theme, range), EquipmentBooking.generateMarkedDates(true, theme, range)
).toStrictEqual(result); ).toStrictEqual(result);
testDevice.booked_at = [{begin: '2020-07-12', end: '2020-07-13'}]; testDevice.booked_at = [{ begin: '2020-07-12', end: '2020-07-13' }];
result = { result = {
'2020-07-11': { '2020-07-11': {
startingDay: true, startingDay: true,
@ -300,12 +297,12 @@ test('generateMarkedDates', () => {
}; };
range = EquipmentBooking.getValidRange(start, end, testDevice); range = EquipmentBooking.getValidRange(start, end, testDevice);
expect( expect(
EquipmentBooking.generateMarkedDates(true, theme, range), EquipmentBooking.generateMarkedDates(true, theme, range)
).toStrictEqual(result); ).toStrictEqual(result);
testDevice.booked_at = [ testDevice.booked_at = [
{begin: '2020-07-12', end: '2020-07-13'}, { begin: '2020-07-12', end: '2020-07-13' },
{begin: '2020-07-15', end: '2020-07-20'}, { begin: '2020-07-15', end: '2020-07-20' },
]; ];
start = new Date('2020-07-11'); start = new Date('2020-07-11');
end = new Date('2020-07-23'); end = new Date('2020-07-23');
@ -318,7 +315,7 @@ test('generateMarkedDates', () => {
}; };
range = EquipmentBooking.getValidRange(start, end, testDevice); range = EquipmentBooking.getValidRange(start, end, testDevice);
expect( expect(
EquipmentBooking.generateMarkedDates(true, theme, range), EquipmentBooking.generateMarkedDates(true, theme, range)
).toStrictEqual(result); ).toStrictEqual(result);
result = { result = {
@ -340,6 +337,6 @@ test('generateMarkedDates', () => {
}; };
range = EquipmentBooking.getValidRange(end, start, testDevice); range = EquipmentBooking.getValidRange(end, start, testDevice);
expect( expect(
EquipmentBooking.generateMarkedDates(true, theme, range), EquipmentBooking.generateMarkedDates(true, theme, range)
).toStrictEqual(result); ).toStrictEqual(result);
}); });

View file

@ -1,6 +1,3 @@
/* eslint-disable */
import React from 'react';
import * as Planning from '../../src/utils/Planning'; import * as Planning from '../../src/utils/Planning';
test('isDescriptionEmpty', () => { test('isDescriptionEmpty', () => {
@ -24,7 +21,7 @@ test('isEventDateStringFormatValid', () => {
expect(Planning.isEventDateStringFormatValid('3214-64-12 01:16')).toBeTrue(); expect(Planning.isEventDateStringFormatValid('3214-64-12 01:16')).toBeTrue();
expect( expect(
Planning.isEventDateStringFormatValid('3214-64-12 01:16:00'), Planning.isEventDateStringFormatValid('3214-64-12 01:16:00')
).toBeFalse(); ).toBeFalse();
expect(Planning.isEventDateStringFormatValid('3214-64-12 1:16')).toBeFalse(); expect(Planning.isEventDateStringFormatValid('3214-64-12 1:16')).toBeFalse();
expect(Planning.isEventDateStringFormatValid('3214-f4-12 01:16')).toBeFalse(); expect(Planning.isEventDateStringFormatValid('3214-f4-12 01:16')).toBeFalse();
@ -32,7 +29,7 @@ test('isEventDateStringFormatValid', () => {
expect(Planning.isEventDateStringFormatValid('2020-03-21')).toBeFalse(); expect(Planning.isEventDateStringFormatValid('2020-03-21')).toBeFalse();
expect(Planning.isEventDateStringFormatValid('2020-03-21 truc')).toBeFalse(); expect(Planning.isEventDateStringFormatValid('2020-03-21 truc')).toBeFalse();
expect( expect(
Planning.isEventDateStringFormatValid('3214-64-12 1:16:65'), Planning.isEventDateStringFormatValid('3214-64-12 1:16:65')
).toBeFalse(); ).toBeFalse();
expect(Planning.isEventDateStringFormatValid('garbage')).toBeFalse(); expect(Planning.isEventDateStringFormatValid('garbage')).toBeFalse();
expect(Planning.isEventDateStringFormatValid('')).toBeFalse(); expect(Planning.isEventDateStringFormatValid('')).toBeFalse();
@ -65,17 +62,17 @@ test('getFormattedEventTime', () => {
expect(Planning.getFormattedEventTime(undefined, undefined)).toBe('/ - /'); expect(Planning.getFormattedEventTime(undefined, undefined)).toBe('/ - /');
expect(Planning.getFormattedEventTime('20:30', '23:00')).toBe('/ - /'); expect(Planning.getFormattedEventTime('20:30', '23:00')).toBe('/ - /');
expect(Planning.getFormattedEventTime('2020-03-30', '2020-03-31')).toBe( expect(Planning.getFormattedEventTime('2020-03-30', '2020-03-31')).toBe(
'/ - /', '/ - /'
); );
expect( expect(
Planning.getFormattedEventTime('2020-03-21 09:00', '2020-03-21 09:00'), Planning.getFormattedEventTime('2020-03-21 09:00', '2020-03-21 09:00')
).toBe('09:00'); ).toBe('09:00');
expect( expect(
Planning.getFormattedEventTime('2020-03-21 09:00', '2020-03-22 17:00'), Planning.getFormattedEventTime('2020-03-21 09:00', '2020-03-22 17:00')
).toBe('09:00 - 23:59'); ).toBe('09:00 - 23:59');
expect( expect(
Planning.getFormattedEventTime('2020-03-30 20:30', '2020-03-30 23:00'), Planning.getFormattedEventTime('2020-03-30 20:30', '2020-03-30 23:00')
).toBe('20:30 - 23:00'); ).toBe('20:30 - 23:00');
}); });
@ -90,38 +87,38 @@ test('getDateOnlyString', () => {
test('isEventBefore', () => { test('isEventBefore', () => {
expect( expect(
Planning.isEventBefore('2020-03-21 09:00', '2020-03-21 10:00'), Planning.isEventBefore('2020-03-21 09:00', '2020-03-21 10:00')
).toBeTrue(); ).toBeTrue();
expect( expect(
Planning.isEventBefore('2020-03-21 10:00', '2020-03-21 10:15'), Planning.isEventBefore('2020-03-21 10:00', '2020-03-21 10:15')
).toBeTrue(); ).toBeTrue();
expect( expect(
Planning.isEventBefore('2020-03-21 10:15', '2021-03-21 10:15'), Planning.isEventBefore('2020-03-21 10:15', '2021-03-21 10:15')
).toBeTrue(); ).toBeTrue();
expect( expect(
Planning.isEventBefore('2020-03-21 10:15', '2020-05-21 10:15'), Planning.isEventBefore('2020-03-21 10:15', '2020-05-21 10:15')
).toBeTrue(); ).toBeTrue();
expect( expect(
Planning.isEventBefore('2020-03-21 10:15', '2020-03-30 10:15'), Planning.isEventBefore('2020-03-21 10:15', '2020-03-30 10:15')
).toBeTrue(); ).toBeTrue();
expect( expect(
Planning.isEventBefore('2020-03-21 10:00', '2020-03-21 10:00'), Planning.isEventBefore('2020-03-21 10:00', '2020-03-21 10:00')
).toBeFalse(); ).toBeFalse();
expect( expect(
Planning.isEventBefore('2020-03-21 10:00', '2020-03-21 09:00'), Planning.isEventBefore('2020-03-21 10:00', '2020-03-21 09:00')
).toBeFalse(); ).toBeFalse();
expect( expect(
Planning.isEventBefore('2020-03-21 10:15', '2020-03-21 10:00'), Planning.isEventBefore('2020-03-21 10:15', '2020-03-21 10:00')
).toBeFalse(); ).toBeFalse();
expect( expect(
Planning.isEventBefore('2021-03-21 10:15', '2020-03-21 10:15'), Planning.isEventBefore('2021-03-21 10:15', '2020-03-21 10:15')
).toBeFalse(); ).toBeFalse();
expect( expect(
Planning.isEventBefore('2020-05-21 10:15', '2020-03-21 10:15'), Planning.isEventBefore('2020-05-21 10:15', '2020-03-21 10:15')
).toBeFalse(); ).toBeFalse();
expect( expect(
Planning.isEventBefore('2020-03-30 10:15', '2020-03-21 10:15'), Planning.isEventBefore('2020-03-30 10:15', '2020-03-21 10:15')
).toBeFalse(); ).toBeFalse();
expect(Planning.isEventBefore('garbage', '2020-03-21 10:15')).toBeFalse(); expect(Planning.isEventBefore('garbage', '2020-03-21 10:15')).toBeFalse();
@ -162,25 +159,25 @@ test('generateEmptyCalendar', () => {
test('pushEventInOrder', () => { test('pushEventInOrder', () => {
let eventArray = []; let eventArray = [];
let event1 = {date_begin: '2020-01-14 09:15'}; let event1 = { date_begin: '2020-01-14 09:15' };
Planning.pushEventInOrder(eventArray, event1); Planning.pushEventInOrder(eventArray, event1);
expect(eventArray.length).toBe(1); expect(eventArray.length).toBe(1);
expect(eventArray[0]).toBe(event1); expect(eventArray[0]).toBe(event1);
let event2 = {date_begin: '2020-01-14 10:15'}; let event2 = { date_begin: '2020-01-14 10:15' };
Planning.pushEventInOrder(eventArray, event2); Planning.pushEventInOrder(eventArray, event2);
expect(eventArray.length).toBe(2); expect(eventArray.length).toBe(2);
expect(eventArray[0]).toBe(event1); expect(eventArray[0]).toBe(event1);
expect(eventArray[1]).toBe(event2); expect(eventArray[1]).toBe(event2);
let event3 = {date_begin: '2020-01-14 10:15', title: 'garbage'}; let event3 = { date_begin: '2020-01-14 10:15', title: 'garbage' };
Planning.pushEventInOrder(eventArray, event3); Planning.pushEventInOrder(eventArray, event3);
expect(eventArray.length).toBe(3); expect(eventArray.length).toBe(3);
expect(eventArray[0]).toBe(event1); expect(eventArray[0]).toBe(event1);
expect(eventArray[1]).toBe(event2); expect(eventArray[1]).toBe(event2);
expect(eventArray[2]).toBe(event3); expect(eventArray[2]).toBe(event3);
let event4 = {date_begin: '2020-01-13 09:00'}; let event4 = { date_begin: '2020-01-13 09:00' };
Planning.pushEventInOrder(eventArray, event4); Planning.pushEventInOrder(eventArray, event4);
expect(eventArray.length).toBe(4); expect(eventArray.length).toBe(4);
expect(eventArray[0]).toBe(event4); expect(eventArray[0]).toBe(event4);
@ -194,11 +191,11 @@ test('generateEventAgenda', () => {
.spyOn(Date, 'now') .spyOn(Date, 'now')
.mockImplementation(() => new Date('2020-01-14T00:00:00.000Z').getTime()); .mockImplementation(() => new Date('2020-01-14T00:00:00.000Z').getTime());
let eventList = [ let eventList = [
{date_begin: '2020-01-14 09:15'}, { date_begin: '2020-01-14 09:15' },
{date_begin: '2020-02-01 09:15'}, { date_begin: '2020-02-01 09:15' },
{date_begin: '2020-01-15 09:15'}, { date_begin: '2020-01-15 09:15' },
{date_begin: '2020-02-01 09:30'}, { date_begin: '2020-02-01 09:30' },
{date_begin: '2020-02-01 08:30'}, { date_begin: '2020-02-01 08:30' },
]; ];
const calendar = Planning.generateEventAgenda(eventList, 2); const calendar = Planning.generateEventAgenda(eventList, 2);
expect(calendar['2020-01-14'].length).toBe(1); expect(calendar['2020-01-14'].length).toBe(1);

View file

@ -1,6 +1,3 @@
/* eslint-disable */
import React from 'react';
import { import {
getCleanedMachineWatched, getCleanedMachineWatched,
getMachineEndDate, getMachineEndDate,
@ -15,19 +12,19 @@ test('getMachineEndDate', () => {
let expectDate = new Date('2020-01-14T15:00:00.000Z'); let expectDate = new Date('2020-01-14T15:00:00.000Z');
expectDate.setHours(23); expectDate.setHours(23);
expectDate.setMinutes(10); expectDate.setMinutes(10);
expect(getMachineEndDate({endTime: '23:10'}).getTime()).toBe( expect(getMachineEndDate({ endTime: '23:10' }).getTime()).toBe(
expectDate.getTime(), expectDate.getTime()
); );
expectDate.setHours(16); expectDate.setHours(16);
expectDate.setMinutes(30); expectDate.setMinutes(30);
expect(getMachineEndDate({endTime: '16:30'}).getTime()).toBe( expect(getMachineEndDate({ endTime: '16:30' }).getTime()).toBe(
expectDate.getTime(), expectDate.getTime()
); );
expect(getMachineEndDate({endTime: '15:30'})).toBeNull(); expect(getMachineEndDate({ endTime: '15:30' })).toBeNull();
expect(getMachineEndDate({endTime: '13:10'})).toBeNull(); expect(getMachineEndDate({ endTime: '13:10' })).toBeNull();
jest jest
.spyOn(Date, 'now') .spyOn(Date, 'now')
@ -35,8 +32,8 @@ test('getMachineEndDate', () => {
expectDate = new Date('2020-01-14T23:00:00.000Z'); expectDate = new Date('2020-01-14T23:00:00.000Z');
expectDate.setHours(0); expectDate.setHours(0);
expectDate.setMinutes(30); expectDate.setMinutes(30);
expect(getMachineEndDate({endTime: '00:30'}).getTime()).toBe( expect(getMachineEndDate({ endTime: '00:30' }).getTime()).toBe(
expectDate.getTime(), expectDate.getTime()
); );
}); });
@ -52,16 +49,16 @@ test('isMachineWatched', () => {
}, },
]; ];
expect( expect(
isMachineWatched({number: '0', endTime: '23:30'}, machineList), isMachineWatched({ number: '0', endTime: '23:30' }, machineList)
).toBeTrue(); ).toBeTrue();
expect( expect(
isMachineWatched({number: '1', endTime: '20:30'}, machineList), isMachineWatched({ number: '1', endTime: '20:30' }, machineList)
).toBeTrue(); ).toBeTrue();
expect( expect(
isMachineWatched({number: '3', endTime: '20:30'}, machineList), isMachineWatched({ number: '3', endTime: '20:30' }, machineList)
).toBeFalse(); ).toBeFalse();
expect( expect(
isMachineWatched({number: '1', endTime: '23:30'}, machineList), isMachineWatched({ number: '1', endTime: '23:30' }, machineList)
).toBeFalse(); ).toBeFalse();
}); });
@ -74,8 +71,8 @@ test('getMachineOfId', () => {
number: '1', number: '1',
}, },
]; ];
expect(getMachineOfId('0', machineList)).toStrictEqual({number: '0'}); expect(getMachineOfId('0', machineList)).toStrictEqual({ number: '0' });
expect(getMachineOfId('1', machineList)).toStrictEqual({number: '1'}); expect(getMachineOfId('1', machineList)).toStrictEqual({ number: '1' });
expect(getMachineOfId('3', machineList)).toBeNull(); expect(getMachineOfId('3', machineList)).toBeNull();
}); });
@ -110,7 +107,7 @@ test('getCleanedMachineWatched', () => {
]; ];
let cleanedList = watchList; let cleanedList = watchList;
expect(getCleanedMachineWatched(watchList, machineList)).toStrictEqual( expect(getCleanedMachineWatched(watchList, machineList)).toStrictEqual(
cleanedList, cleanedList
); );
watchList = [ watchList = [
@ -138,7 +135,7 @@ test('getCleanedMachineWatched', () => {
}, },
]; ];
expect(getCleanedMachineWatched(watchList, machineList)).toStrictEqual( expect(getCleanedMachineWatched(watchList, machineList)).toStrictEqual(
cleanedList, cleanedList
); );
watchList = [ watchList = [
@ -162,6 +159,6 @@ test('getCleanedMachineWatched', () => {
}, },
]; ];
expect(getCleanedMachineWatched(watchList, machineList)).toStrictEqual( expect(getCleanedMachineWatched(watchList, machineList)).toStrictEqual(
cleanedList, cleanedList
); );
}); });

View file

@ -1,8 +1,6 @@
/* eslint-disable */ import { isApiResponseValid } from '../../src/utils/WebData';
import React from 'react';
import {isApiResponseValid} from '../../src/utils/WebData';
// eslint-disable-next-line no-unused-vars
const fetch = require('isomorphic-fetch'); // fetch is not implemented in nodeJS but in react-native const fetch = require('isomorphic-fetch'); // fetch is not implemented in nodeJS but in react-native
test('isRequestResponseValid', () => { test('isRequestResponseValid', () => {
@ -23,7 +21,7 @@ test('isRequestResponseValid', () => {
expect(isApiResponseValid(json)).toBeTrue(); expect(isApiResponseValid(json)).toBeTrue();
json = { json = {
error: 50, error: 50,
data: {truc: 'machin'}, data: { truc: 'machin' },
}; };
expect(isApiResponseValid(json)).toBeTrue(); expect(isApiResponseValid(json)).toBeTrue();
json = { json = {
@ -32,7 +30,7 @@ test('isRequestResponseValid', () => {
expect(isApiResponseValid(json)).toBeFalse(); expect(isApiResponseValid(json)).toBeFalse();
json = { json = {
error: 'coucou', error: 'coucou',
data: {truc: 'machin'}, data: { truc: 'machin' },
}; };
expect(isApiResponseValid(json)).toBeFalse(); expect(isApiResponseValid(json)).toBeFalse();
json = { json = {

View file

@ -1,18 +0,0 @@
#!/bin/bash
echo "Removing node_modules..."
rm -rf node_modules/
echo -e "Done\n"
echo "Removing locks..."
rm -f package-lock.json && rm -f yarn.lock
echo -e "Done\n"
#echo "Verifying npm cache..."
#npm cache verify
#echo -e "Done\n"
echo "Installing dependencies..."
npm install
echo -e "Done\n"

View file

@ -21,9 +21,8 @@
* @format * @format
*/ */
import {AppRegistry} from 'react-native'; import { AppRegistry } from 'react-native';
import App from './App'; import App from './App';
import {name as appName} from './app.json'; import { name as appName } from './app.json';
// eslint-disable-next-line flowtype/require-return-type
AppRegistry.registerComponent(appName, () => App); AppRegistry.registerComponent(appName, () => App);

View file

@ -7,11 +7,10 @@
module.exports = { module.exports = {
transformer: { transformer: {
// eslint-disable-next-line flowtype/require-return-type
getTransformOptions: async () => ({ getTransformOptions: async () => ({
transform: { transform: {
experimentalImportSupport: false, experimentalImportSupport: false,
inlineRequires: false, inlineRequires: true,
}, },
}), }),
}, },

4676
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,12 +3,113 @@
"version": "4.1.0", "version": "4.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "react-native start",
"android": "react-native run-android", "android": "react-native run-android",
"android-release": "react-native run-android --variant=release", "android-release": "react-native run-android --variant=release",
"ios": "react-native run-ios", "ios": "react-native run-ios",
"start": "react-native start",
"start-no-cache": "react-native start --reset-cache",
"test": "jest", "test": "jest",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx" "typescript": "tsc --noEmit",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint-fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"full-check": "npm run typescript && npm run lint && npm run test",
"pod": "cd ios && pod install && cd ..",
"bundle": "npm run full-check && cd android && ./gradlew bundleRelease",
"postversion": "react-native-version"
},
"dependencies": {
"@nartc/react-native-barcode-mask": "1.2.0",
"@react-native-community/async-storage": "1.12.0",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/push-notification-ios": "1.5.0",
"@react-native-community/slider": "3.0.3",
"@react-navigation/bottom-tabs": "5.8.0",
"@react-navigation/native": "5.7.3",
"@react-navigation/stack": "5.9.0",
"i18n-js": "3.7.1",
"react": "16.13.1",
"react-native": "0.63.2",
"react-native-animatable": "1.3.3",
"react-native-app-intro-slider": "4.0.4",
"react-native-appearance": "0.3.4",
"react-native-autolink": "3.0.0",
"react-native-calendars": "1.403.0",
"react-native-camera": "3.40.0",
"react-native-collapsible": "1.5.3",
"react-native-gesture-handler": "1.8.0",
"react-native-image-zoom-viewer": "3.0.1",
"react-native-keychain": "4.0.5",
"react-native-linear-gradient": "2.5.6",
"react-native-localize": "1.4.1",
"react-native-modalize": "2.0.6",
"react-native-paper": "4.2.0",
"react-native-permissions": "2.2.1",
"react-native-push-notification": "5.1.1",
"react-native-reanimated": "1.13.0",
"react-native-render-html": "4.2.3",
"react-native-safe-area-context": "3.1.8",
"react-native-screens": "2.11.0",
"react-native-splash-screen": "3.2.0",
"react-native-vector-icons": "7.1.0",
"react-native-webview": "10.9.0",
"react-navigation-collapsible": "5.6.4",
"react-navigation-header-buttons": "5.0.2"
},
"devDependencies": {
"@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/jest": "25.2.3",
"@types/react-native": "0.63.2",
"@types/react-native-calendars": "1.20.10",
"@types/react-native-vector-icons": "6.4.6",
"@types/react-test-renderer": "16.9.2",
"@typescript-eslint/eslint-plugin": "2.27.0",
"@typescript-eslint/parser": "2.27.0",
"babel-jest": "25.1.0",
"eslint": "7.2.0",
"jest": "25.1.0",
"jest-extended": "0.11.5",
"jest-fetch-mock": "3.0.3",
"metro-react-native-babel-preset": "0.59.0",
"prettier": "2.0.5",
"react-native-version": "4.0.0",
"react-test-renderer": "16.13.1",
"typescript": "3.8.3"
},
"eslintConfig": {
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"@react-native-community",
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"quoteProps": "consistent",
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}
]
}
},
"eslintIgnore": [
"node_modules/"
],
"prettier": {
"quoteProps": "consistent",
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
}, },
"jest": { "jest": {
"preset": "react-native", "preset": "react-native",
@ -23,71 +124,5 @@
"setupFilesAfterEnv": [ "setupFilesAfterEnv": [
"jest-extended" "jest-extended"
] ]
},
"dependencies": {
"@nartc/react-native-barcode-mask": "^1.2.0",
"@react-native-community/async-storage": "^1.12.0",
"@react-native-community/masked-view": "^0.1.10",
"@react-native-community/push-notification-ios": "^1.5.0",
"@react-native-community/slider": "^3.0.3",
"@react-navigation/bottom-tabs": "^5.8.0",
"@react-navigation/native": "^5.7.3",
"@react-navigation/stack": "^5.9.0",
"i18n-js": "^3.7.1",
"react": "16.13.1",
"react-native": "0.63.2",
"react-native-animatable": "^1.3.3",
"react-native-app-intro-slider": "^4.0.4",
"react-native-appearance": "^0.3.4",
"react-native-autolink": "^3.0.0",
"react-native-calendars": "^1.403.0",
"react-native-camera": "^3.40.0",
"react-native-collapsible": "^1.5.3",
"react-native-gesture-handler": "^1.8.0",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-keychain": "4.0.5",
"react-native-linear-gradient": "^2.5.6",
"react-native-localize": "^1.4.1",
"react-native-modalize": "^2.0.6",
"react-native-paper": "^4.2.0",
"react-native-permissions": "^2.2.1",
"react-native-push-notification": "^5.1.1",
"react-native-reanimated": "^1.13.0",
"react-native-render-html": "^4.2.3",
"react-native-safe-area-context": "^3.1.8",
"react-native-screens": "^2.11.0",
"react-native-splash-screen": "^3.2.0",
"react-native-vector-icons": "^7.1.0",
"react-native-webview": "^10.9.0",
"react-navigation-collapsible": "^5.6.4",
"react-navigation-header-buttons": "^5.0.2"
},
"devDependencies": {
"@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/jest": "^25.2.3",
"@types/react-native": "^0.63.2",
"@types/react-native-calendars": "^1.20.10",
"@types/react-native-vector-icons": "^6.4.6",
"@types/react-test-renderer": "^16.9.2",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"babel-jest": "^25.1.0",
"eslint": "^7.2.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-flowtype": "^5.2.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-react": "^7.20.5",
"eslint-plugin-react-hooks": "^4.0.0",
"jest": "^25.1.0",
"jest-extended": "^0.11.5",
"metro-react-native-babel-preset": "^0.59.0",
"prettier": "2.0.5",
"react-test-renderer": "16.13.1",
"typescript": "^3.8.3"
} }
} }

View file

@ -18,9 +18,9 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import ConnectionManager from '../../managers/ConnectionManager'; import ConnectionManager from '../../managers/ConnectionManager';
import {ERROR_TYPE} from '../../utils/WebData'; import { ERROR_TYPE } from '../../utils/WebData';
import ErrorView from '../Screens/ErrorView'; import ErrorView from '../Screens/ErrorView';
import BasicLoadingScreen from '../Screens/BasicLoadingScreen'; import BasicLoadingScreen from '../Screens/BasicLoadingScreen';
@ -90,7 +90,7 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
* @param error The error code received * @param error The error code received
*/ */
onRequestFinished(data: T | null, index: number, error?: number) { onRequestFinished(data: T | null, index: number, error?: number) {
const {props} = this; const { props } = this;
if (index >= 0 && index < props.requests.length) { if (index >= 0 && index < props.requests.length) {
this.fetchedData[index] = data; this.fetchedData[index] = data;
this.errors[index] = error != null ? error : ERROR_TYPE.SUCCESS; this.errors[index] = error != null ? error : ERROR_TYPE.SUCCESS;
@ -101,7 +101,7 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
} }
if (this.allRequestsFinished()) { if (this.allRequestsFinished()) {
this.setState({loading: false}); this.setState({ loading: false });
} }
} }
@ -113,7 +113,7 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
* @return {number} The error code or ERROR_TYPE.SUCCESS if no error was found * @return {number} The error code or ERROR_TYPE.SUCCESS if no error was found
*/ */
getError(): number { getError(): number {
const {props} = this; const { props } = this;
for (let i = 0; i < this.errors.length; i += 1) { for (let i = 0; i < this.errors.length; i += 1) {
if ( if (
this.errors[i] !== ERROR_TYPE.SUCCESS && this.errors[i] !== ERROR_TYPE.SUCCESS &&
@ -131,7 +131,7 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
* @return {*} * @return {*}
*/ */
getErrorRender() { getErrorRender() {
const {props} = this; const { props } = this;
const errorCode = this.getError(); const errorCode = this.getError();
let shouldOverride = false; let shouldOverride = false;
let override = null; let override = null;
@ -166,9 +166,9 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
* If the user is logged in, send all requests. * If the user is logged in, send all requests.
*/ */
fetchData = () => { fetchData = () => {
const {state, props} = this; const { state, props } = this;
if (!state.loading) { if (!state.loading) {
this.setState({loading: true}); this.setState({ loading: true });
} }
if (this.connectionManager.isLoggedIn()) { if (this.connectionManager.isLoggedIn()) {
@ -176,11 +176,11 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
this.connectionManager this.connectionManager
.authenticatedRequest<T>( .authenticatedRequest<T>(
props.requests[i].link, props.requests[i].link,
props.requests[i].params, props.requests[i].params
) )
.then((response: T): void => this.onRequestFinished(response, i)) .then((response: T): void => this.onRequestFinished(response, i))
.catch((error: number): void => .catch((error: number): void =>
this.onRequestFinished(null, i, error), this.onRequestFinished(null, i, error)
); );
} }
} else { } else {
@ -213,7 +213,7 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
} }
render() { render() {
const {state, props} = this; const { state, props } = this;
if (state.loading) { if (state.loading) {
return <BasicLoadingScreen />; return <BasicLoadingScreen />;
} }

View file

@ -21,7 +21,7 @@ import * as React from 'react';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import LoadingConfirmDialog from '../Dialogs/LoadingConfirmDialog'; import LoadingConfirmDialog from '../Dialogs/LoadingConfirmDialog';
import ConnectionManager from '../../managers/ConnectionManager'; import ConnectionManager from '../../managers/ConnectionManager';
import {useNavigation} from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
type PropsType = { type PropsType = {
visible: boolean; visible: boolean;
@ -37,7 +37,7 @@ function LogoutDialog(props: PropsType) {
.then(() => { .then(() => {
navigation.reset({ navigation.reset({
index: 0, index: 0,
routes: [{name: 'main'}], routes: [{ name: 'main' }],
}); });
props.onDismiss(); props.onDismiss();
resolve(); resolve();

View file

@ -18,24 +18,31 @@
*/ */
import React from 'react'; import React from 'react';
import {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import {Headline, useTheme} from 'react-native-paper'; import { Headline, useTheme } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
const styles = StyleSheet.create({
container: {
width: '100%',
marginTop: 10,
marginBottom: 10,
},
headline: {
textAlign: 'center',
},
});
function VoteNotAvailable() { function VoteNotAvailable() {
const theme = useTheme(); const theme = useTheme();
return ( return (
<View <View style={styles.container}>
style={{
width: '100%',
marginTop: 10,
marginBottom: 10,
}}>
<Headline <Headline
style={{ style={{
color: theme.colors.textDisabled, color: theme.colors.textDisabled,
textAlign: 'center', ...styles.headline,
}}> }}
>
{i18n.t('screens.vote.noVote')} {i18n.t('screens.vote.noVote')}
</Headline> </Headline>
</View> </View>

View file

@ -26,9 +26,9 @@ import {
Subheading, Subheading,
withTheme, withTheme,
} from 'react-native-paper'; } from 'react-native-paper';
import {FlatList, StyleSheet} from 'react-native'; import { FlatList, StyleSheet } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {VoteTeamType} from '../../../screens/Amicale/VoteScreen'; import type { VoteTeamType } from '../../../screens/Amicale/VoteScreen';
type PropsType = { type PropsType = {
teams: Array<VoteTeamType>; teams: Array<VoteTeamType>;
@ -40,8 +40,11 @@ const styles = StyleSheet.create({
card: { card: {
margin: 10, margin: 10,
}, },
icon: { itemCard: {
backgroundColor: 'transparent', marginTop: 10,
},
item: {
padding: 0,
}, },
}); });
@ -86,16 +89,18 @@ class VoteResults extends React.Component<PropsType> {
voteKeyExtractor = (item: VoteTeamType): string => item.id.toString(); voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
resultRenderItem = ({item}: {item: VoteTeamType}) => { resultRenderItem = ({ item }: { item: VoteTeamType }) => {
const isWinner = this.winnerIds.indexOf(item.id) !== -1; const isWinner = this.winnerIds.indexOf(item.id) !== -1;
const isDraw = this.winnerIds.length > 1; const isDraw = this.winnerIds.length > 1;
const {props} = this; const { props } = this;
const elevation = isWinner ? 5 : 3;
return ( return (
<Card <Card
style={{ style={{
marginTop: 10, ...styles.itemCard,
elevation: isWinner ? 5 : 3, elevation: elevation,
}}> }}
>
<List.Item <List.Item
title={item.name} title={item.name}
description={`${item.votes} ${i18n.t('screens.vote.results.votes')}`} description={`${item.votes} ${i18n.t('screens.vote.results.votes')}`}
@ -113,7 +118,7 @@ class VoteResults extends React.Component<PropsType> {
? props.theme.colors.primary ? props.theme.colors.primary
: props.theme.colors.text, : props.theme.colors.text,
}} }}
style={{padding: 0}} style={styles.item}
/> />
<ProgressBar <ProgressBar
progress={item.votes / this.totalVotes} progress={item.votes / this.totalVotes}
@ -124,7 +129,7 @@ class VoteResults extends React.Component<PropsType> {
}; };
render() { render() {
const {props} = this; const { props } = this;
return ( return (
<Card style={styles.card}> <Card style={styles.card}>
<Card.Title <Card.Title

View file

@ -18,13 +18,13 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Avatar, Button, Card, RadioButton} from 'react-native-paper'; import { Avatar, Button, Card, RadioButton } from 'react-native-paper';
import {FlatList, StyleSheet, View} from 'react-native'; import { FlatList, StyleSheet, View } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import ConnectionManager from '../../../managers/ConnectionManager'; import ConnectionManager from '../../../managers/ConnectionManager';
import LoadingConfirmDialog from '../../Dialogs/LoadingConfirmDialog'; import LoadingConfirmDialog from '../../Dialogs/LoadingConfirmDialog';
import ErrorDialog from '../../Dialogs/ErrorDialog'; import ErrorDialog from '../../Dialogs/ErrorDialog';
import type {VoteTeamType} from '../../../screens/Amicale/VoteScreen'; import type { VoteTeamType } from '../../../screens/Amicale/VoteScreen';
type PropsType = { type PropsType = {
teams: Array<VoteTeamType>; teams: Array<VoteTeamType>;
@ -43,8 +43,8 @@ const styles = StyleSheet.create({
card: { card: {
margin: 10, margin: 10,
}, },
icon: { button: {
backgroundColor: 'transparent', marginLeft: 'auto',
}, },
}); });
@ -63,28 +63,28 @@ export default class VoteSelect extends React.PureComponent<
} }
onVoteSelectionChange = (teamName: string): void => onVoteSelectionChange = (teamName: string): void =>
this.setState({selectedTeam: teamName}); this.setState({ selectedTeam: teamName });
voteKeyExtractor = (item: VoteTeamType): string => item.id.toString(); voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
voteRenderItem = ({item}: {item: VoteTeamType}) => ( voteRenderItem = ({ item }: { item: VoteTeamType }) => (
<RadioButton.Item label={item.name} value={item.id.toString()} /> <RadioButton.Item label={item.name} value={item.id.toString()} />
); );
showVoteDialog = (): void => this.setState({voteDialogVisible: true}); showVoteDialog = (): void => this.setState({ voteDialogVisible: true });
onVoteDialogDismiss = (): void => this.setState({voteDialogVisible: false}); onVoteDialogDismiss = (): void => this.setState({ voteDialogVisible: false });
onVoteDialogAccept = async (): Promise<void> => { onVoteDialogAccept = async (): Promise<void> => {
return new Promise((resolve: () => void) => { return new Promise((resolve: () => void) => {
const {state} = this; const { state } = this;
ConnectionManager.getInstance() ConnectionManager.getInstance()
.authenticatedRequest('elections/vote', { .authenticatedRequest('elections/vote', {
team: parseInt(state.selectedTeam, 10), team: parseInt(state.selectedTeam, 10),
}) })
.then(() => { .then(() => {
this.onVoteDialogDismiss(); this.onVoteDialogDismiss();
const {props} = this; const { props } = this;
props.onVoteSuccess(); props.onVoteSuccess();
resolve(); resolve();
}) })
@ -103,13 +103,13 @@ export default class VoteSelect extends React.PureComponent<
}); });
onErrorDialogDismiss = () => { onErrorDialogDismiss = () => {
this.setState({errorDialogVisible: false}); this.setState({ errorDialogVisible: false });
const {props} = this; const { props } = this;
props.onVoteError(); props.onVoteError();
}; };
render() { render() {
const {state, props} = this; const { state, props } = this;
return ( return (
<View> <View>
<Card style={styles.card}> <Card style={styles.card}>
@ -123,7 +123,8 @@ export default class VoteSelect extends React.PureComponent<
<Card.Content> <Card.Content>
<RadioButton.Group <RadioButton.Group
onValueChange={this.onVoteSelectionChange} onValueChange={this.onVoteSelectionChange}
value={state.selectedTeam}> value={state.selectedTeam}
>
<FlatList <FlatList
data={props.teams} data={props.teams}
keyExtractor={this.voteKeyExtractor} keyExtractor={this.voteKeyExtractor}
@ -137,8 +138,9 @@ export default class VoteSelect extends React.PureComponent<
icon="send" icon="send"
mode="contained" mode="contained"
onPress={this.showVoteDialog} onPress={this.showVoteDialog}
style={{marginLeft: 'auto'}} style={styles.button}
disabled={state.selectedTeam === 'none'}> disabled={state.selectedTeam === 'none'}
>
{i18n.t('screens.vote.select.sendButton')} {i18n.t('screens.vote.select.sendButton')}
</Button> </Button>
</Card.Actions> </Card.Actions>

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Avatar, Card, Paragraph} from 'react-native-paper'; import { Avatar, Card, Paragraph } from 'react-native-paper';
import {StyleSheet} from 'react-native'; import { StyleSheet } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
type PropsType = { type PropsType = {
@ -30,9 +30,6 @@ const styles = StyleSheet.create({
card: { card: {
margin: 10, margin: 10,
}, },
icon: {
backgroundColor: 'transparent',
},
}); });
export default function VoteTease(props: PropsType) { export default function VoteTease(props: PropsType) {

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Avatar, Card, Paragraph, useTheme} from 'react-native-paper'; import { Avatar, Card, Paragraph, useTheme } from 'react-native-paper';
import {StyleSheet} from 'react-native'; import { StyleSheet } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
type PropsType = { type PropsType = {
@ -33,14 +33,11 @@ const styles = StyleSheet.create({
card: { card: {
margin: 10, margin: 10,
}, },
icon: {
backgroundColor: 'transparent',
},
}); });
export default function VoteWait(props: PropsType) { export default function VoteWait(props: PropsType) {
const theme = useTheme(); const theme = useTheme();
const {startDate} = props; const { startDate } = props;
return ( return (
<Card style={styles.card}> <Card style={styles.card}>
<Card.Title <Card.Title
@ -56,12 +53,12 @@ export default function VoteWait(props: PropsType) {
/> />
<Card.Content> <Card.Content>
{props.justVoted ? ( {props.justVoted ? (
<Paragraph style={{color: theme.colors.success}}> <Paragraph style={{ color: theme.colors.success }}>
{i18n.t('screens.vote.wait.messageSubmitted')} {i18n.t('screens.vote.wait.messageSubmitted')}
</Paragraph> </Paragraph>
) : null} ) : null}
{props.hasVoted ? ( {props.hasVoted ? (
<Paragraph style={{color: theme.colors.success}}> <Paragraph style={{ color: theme.colors.success }}>
{i18n.t('screens.vote.wait.messageVoted')} {i18n.t('screens.vote.wait.messageVoted')}
</Paragraph> </Paragraph>
) : null} ) : null}

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {View, ViewStyle} from 'react-native'; import { View, ViewStyle } from 'react-native';
import {List, withTheme} from 'react-native-paper'; import { List, withTheme } from 'react-native-paper';
import Collapsible from 'react-native-collapsible'; import Collapsible from 'react-native-collapsible';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
@ -47,7 +47,7 @@ type StateType = {
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon); const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
class AnimatedAccordion extends React.Component<PropsType, StateType> { class AnimatedAccordion extends React.Component<PropsType, StateType> {
chevronRef: {current: null | (typeof AnimatedListIcon & List.Icon)}; chevronRef: { current: null | (typeof AnimatedListIcon & List.Icon) };
chevronIcon: string; chevronIcon: string;
@ -68,7 +68,7 @@ class AnimatedAccordion extends React.Component<PropsType, StateType> {
} }
shouldComponentUpdate(nextProps: PropsType): boolean { shouldComponentUpdate(nextProps: PropsType): boolean {
const {state, props} = this; const { state, props } = this;
if (nextProps.opened != null && nextProps.opened !== props.opened) { if (nextProps.opened != null && nextProps.opened !== props.opened) {
state.expanded = nextProps.opened; state.expanded = nextProps.opened;
} }
@ -76,7 +76,7 @@ class AnimatedAccordion extends React.Component<PropsType, StateType> {
} }
setupChevron() { setupChevron() {
const {expanded} = this.state; const { expanded } = this.state;
if (expanded) { if (expanded) {
this.chevronIcon = 'chevron-up'; this.chevronIcon = 'chevron-up';
this.animStart = '180deg'; this.animStart = '180deg';
@ -89,26 +89,26 @@ class AnimatedAccordion extends React.Component<PropsType, StateType> {
} }
toggleAccordion = () => { toggleAccordion = () => {
const {expanded} = this.state; const { expanded } = this.state;
if (this.chevronRef.current != null) { if (this.chevronRef.current != null) {
this.chevronRef.current.transitionTo({ this.chevronRef.current.transitionTo({
rotate: expanded ? this.animStart : this.animEnd, rotate: expanded ? this.animStart : this.animEnd,
}); });
this.setState((prevState: StateType): {expanded: boolean} => ({ this.setState((prevState: StateType): { expanded: boolean } => ({
expanded: !prevState.expanded, expanded: !prevState.expanded,
})); }));
} }
}; };
render() { render() {
const {props, state} = this; const { props, state } = this;
const {colors} = props.theme; const { colors } = props.theme;
return ( return (
<View style={props.style}> <View style={props.style}>
<List.Item <List.Item
title={props.title} title={props.title}
description={props.subtitle} description={props.subtitle}
titleStyle={state.expanded ? {color: colors.primary} : null} titleStyle={state.expanded ? { color: colors.primary } : null}
onPress={this.toggleAccordion} onPress={this.toggleAccordion}
right={(iconProps) => ( right={(iconProps) => (
<AnimatedListIcon <AnimatedListIcon

View file

@ -24,9 +24,9 @@ import {
StyleSheet, StyleSheet,
View, View,
} from 'react-native'; } from 'react-native';
import {FAB, IconButton, Surface, withTheme} from 'react-native-paper'; import { FAB, IconButton, Surface, withTheme } from 'react-native-paper';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import AutoHideHandler from '../../utils/AutoHideHandler'; import AutoHideHandler from '../../utils/AutoHideHandler';
import CustomTabBar from '../Tabbar/CustomTabBar'; import CustomTabBar from '../Tabbar/CustomTabBar';
@ -76,14 +76,20 @@ const styles = StyleSheet.create({
alignSelf: 'center', alignSelf: 'center',
top: '-25%', top: '-25%',
}, },
side: {
flexDirection: 'row',
},
icon: {
marginLeft: 5,
},
}); });
class AnimatedBottomBar extends React.Component<PropsType, StateType> { class AnimatedBottomBar extends React.Component<PropsType, StateType> {
ref: {current: null | (Animatable.View & View)}; ref: { current: null | (Animatable.View & View) };
hideHandler: AutoHideHandler; hideHandler: AutoHideHandler;
displayModeIcons: {[key: string]: string}; displayModeIcons: { [key: string]: string };
constructor(props: PropsType) { constructor(props: PropsType) {
super(props); super(props);
@ -101,7 +107,7 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
} }
shouldComponentUpdate(nextProps: PropsType, nextState: StateType): boolean { shouldComponentUpdate(nextProps: PropsType, nextState: StateType): boolean {
const {props, state} = this; const { props, state } = this;
return ( return (
nextProps.seekAttention !== props.seekAttention || nextProps.seekAttention !== props.seekAttention ||
nextState.currentMode !== state.currentMode nextState.currentMode !== state.currentMode
@ -124,7 +130,7 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
}; };
changeDisplayMode = () => { changeDisplayMode = () => {
const {props, state} = this; const { props, state } = this;
let newMode; let newMode;
switch (state.currentMode) { switch (state.currentMode) {
case DISPLAY_MODES.DAY: case DISPLAY_MODES.DAY:
@ -140,12 +146,12 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
newMode = DISPLAY_MODES.WEEK; newMode = DISPLAY_MODES.WEEK;
break; break;
} }
this.setState({currentMode: newMode}); this.setState({ currentMode: newMode });
props.onPress('changeView', newMode); props.onPress('changeView', newMode);
}; };
render() { render() {
const {props, state} = this; const { props, state } = this;
const buttonColor = props.theme.colors.primary; const buttonColor = props.theme.colors.primary;
return ( return (
<Animatable.View <Animatable.View
@ -154,7 +160,8 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
style={{ style={{
...styles.container, ...styles.container,
bottom: 10 + CustomTabBar.TAB_BAR_HEIGHT, bottom: 10 + CustomTabBar.TAB_BAR_HEIGHT,
}}> }}
>
<Surface style={styles.surface}> <Surface style={styles.surface}>
<View style={styles.fabContainer}> <View style={styles.fabContainer}>
<AnimatedFAB <AnimatedFAB
@ -165,10 +172,10 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
useNativeDriver useNativeDriver
style={styles.fab} style={styles.fab}
icon="account-clock" icon="account-clock"
onPress={(): void => props.navigation.navigate('group-select')} onPress={() => props.navigation.navigate('group-select')}
/> />
</View> </View>
<View style={{flexDirection: 'row'}}> <View style={styles.side}>
<IconButton <IconButton
icon={this.displayModeIcons[state.currentMode]} icon={this.displayModeIcons[state.currentMode]}
color={buttonColor} color={buttonColor}
@ -177,21 +184,21 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
<IconButton <IconButton
icon="clock-in" icon="clock-in"
color={buttonColor} color={buttonColor}
style={{marginLeft: 5}} style={styles.icon}
onPress={(): void => props.onPress('today')} onPress={() => props.onPress('today')}
/> />
</View> </View>
<View style={{flexDirection: 'row'}}> <View style={styles.side}>
<IconButton <IconButton
icon="chevron-left" icon="chevron-left"
color={buttonColor} color={buttonColor}
onPress={(): void => props.onPress('prev')} onPress={() => props.onPress('prev')}
/> />
<IconButton <IconButton
icon="chevron-right" icon="chevron-right"
color={buttonColor} color={buttonColor}
style={{marginLeft: 5}} style={styles.icon}
onPress={(): void => props.onPress('next')} onPress={() => props.onPress('next')}
/> />
</View> </View>
</Surface> </Surface>

View file

@ -24,7 +24,7 @@ import {
StyleSheet, StyleSheet,
View, View,
} from 'react-native'; } from 'react-native';
import {FAB} from 'react-native-paper'; import { FAB } from 'react-native-paper';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import AutoHideHandler from '../../utils/AutoHideHandler'; import AutoHideHandler from '../../utils/AutoHideHandler';
import CustomTabBar from '../Tabbar/CustomTabBar'; import CustomTabBar from '../Tabbar/CustomTabBar';
@ -43,7 +43,7 @@ const styles = StyleSheet.create({
}); });
export default class AnimatedFAB extends React.Component<PropsType> { export default class AnimatedFAB extends React.Component<PropsType> {
ref: {current: null | (Animatable.View & View)}; ref: { current: null | (Animatable.View & View) };
hideHandler: AutoHideHandler; hideHandler: AutoHideHandler;
@ -75,7 +75,7 @@ export default class AnimatedFAB extends React.Component<PropsType> {
}; };
render() { render() {
const {props} = this; const { props } = this;
return ( return (
<Animatable.View <Animatable.View
ref={this.ref} ref={this.ref}
@ -83,7 +83,8 @@ export default class AnimatedFAB extends React.Component<PropsType> {
style={{ style={{
...styles.fab, ...styles.fab,
bottom: CustomTabBar.TAB_BAR_HEIGHT, bottom: CustomTabBar.TAB_BAR_HEIGHT,
}}> }}
>
<FAB icon={props.icon} onPress={props.onPress} /> <FAB icon={props.icon} onPress={props.onPress} />
</Animatable.View> </Animatable.View>
); );

View file

@ -18,19 +18,29 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {useCollapsibleStack} from 'react-navigation-collapsible'; import { useCollapsibleStack } from 'react-navigation-collapsible';
import CustomTabBar from '../Tabbar/CustomTabBar'; import CustomTabBar from '../Tabbar/CustomTabBar';
import {NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; import {
NativeScrollEvent,
NativeSyntheticEvent,
StyleSheet,
} from 'react-native';
export interface CollapsibleComponentPropsType { export type CollapsibleComponentPropsType = {
children?: React.ReactNode; children?: React.ReactNode;
hasTab?: boolean; hasTab?: boolean;
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void; onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
} };
interface PropsType extends CollapsibleComponentPropsType { type PropsType = CollapsibleComponentPropsType & {
component: React.ComponentType<any>; component: React.ComponentType<any>;
} };
const styles = StyleSheet.create({
main: {
minHeight: '100%',
},
});
function CollapsibleComponent(props: PropsType) { function CollapsibleComponent(props: PropsType) {
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => { const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
@ -44,17 +54,18 @@ function CollapsibleComponent(props: PropsType) {
scrollIndicatorInsetTop, scrollIndicatorInsetTop,
onScrollWithListener, onScrollWithListener,
} = useCollapsibleStack(); } = useCollapsibleStack();
const paddingBottom = props.hasTab ? CustomTabBar.TAB_BAR_HEIGHT : 0;
return ( return (
<Comp <Comp
{...props} {...props}
onScroll={onScrollWithListener(onScroll)} onScroll={onScrollWithListener(onScroll)}
contentContainerStyle={{ contentContainerStyle={{
paddingTop: containerPaddingTop, paddingTop: containerPaddingTop,
paddingBottom: props.hasTab ? CustomTabBar.TAB_BAR_HEIGHT : 0, paddingBottom: paddingBottom,
minHeight: '100%', ...styles.main,
}} }}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}> scrollIndicatorInsets={{ top: scrollIndicatorInsetTop }}
>
{props.children} {props.children}
</Comp> </Comp>
); );

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Animated, FlatListProps} from 'react-native'; import { Animated, FlatListProps } from 'react-native';
import type {CollapsibleComponentPropsType} from './CollapsibleComponent'; import type { CollapsibleComponentPropsType } from './CollapsibleComponent';
import CollapsibleComponent from './CollapsibleComponent'; import CollapsibleComponent from './CollapsibleComponent';
type Props<T> = FlatListProps<T> & CollapsibleComponentPropsType; type Props<T> = FlatListProps<T> & CollapsibleComponentPropsType;

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Animated, ScrollViewProps} from 'react-native'; import { Animated, ScrollViewProps } from 'react-native';
import type {CollapsibleComponentPropsType} from './CollapsibleComponent'; import type { CollapsibleComponentPropsType } from './CollapsibleComponent';
import CollapsibleComponent from './CollapsibleComponent'; import CollapsibleComponent from './CollapsibleComponent';
type Props = ScrollViewProps & CollapsibleComponentPropsType; type Props = ScrollViewProps & CollapsibleComponentPropsType;

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Animated, SectionListProps} from 'react-native'; import { Animated, SectionListProps } from 'react-native';
import type {CollapsibleComponentPropsType} from './CollapsibleComponent'; import type { CollapsibleComponentPropsType } from './CollapsibleComponent';
import CollapsibleComponent from './CollapsibleComponent'; import CollapsibleComponent from './CollapsibleComponent';
type Props<T> = SectionListProps<T> & CollapsibleComponentPropsType; type Props<T> = SectionListProps<T> & CollapsibleComponentPropsType;

View file

@ -18,7 +18,7 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Button, Dialog, Paragraph, Portal} from 'react-native-paper'; import { Button, Dialog, Paragraph, Portal } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
type PropsType = { type PropsType = {

View file

@ -19,7 +19,7 @@
import * as React from 'react'; import * as React from 'react';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {ERROR_TYPE} from '../../utils/WebData'; import { ERROR_TYPE } from '../../utils/WebData';
import AlertDialog from './AlertDialog'; import AlertDialog from './AlertDialog';
type PropsType = { type PropsType = {

View file

@ -26,6 +26,7 @@ import {
Portal, Portal,
} from 'react-native-paper'; } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import { StyleSheet } from 'react-native';
type PropsType = { type PropsType = {
visible: boolean; visible: boolean;
@ -41,6 +42,12 @@ type StateType = {
loading: boolean; loading: boolean;
}; };
const styles = StyleSheet.create({
button: {
marginRight: 10,
},
});
export default class LoadingConfirmDialog extends React.PureComponent< export default class LoadingConfirmDialog extends React.PureComponent<
PropsType, PropsType,
StateType StateType
@ -70,8 +77,8 @@ export default class LoadingConfirmDialog extends React.PureComponent<
* Set the dialog into loading state and closes it when operation finishes * Set the dialog into loading state and closes it when operation finishes
*/ */
onClickAccept = () => { onClickAccept = () => {
const {props} = this; const { props } = this;
this.setState({loading: true}); this.setState({ loading: true });
if (props.onAccept != null) { if (props.onAccept != null) {
props.onAccept().then(this.hideLoading); props.onAccept().then(this.hideLoading);
} }
@ -83,21 +90,21 @@ export default class LoadingConfirmDialog extends React.PureComponent<
*/ */
hideLoading = (): NodeJS.Timeout => hideLoading = (): NodeJS.Timeout =>
setTimeout(() => { setTimeout(() => {
this.setState({loading: false}); this.setState({ loading: false });
}, 200); }, 200);
/** /**
* Hide the dialog if it is not loading * Hide the dialog if it is not loading
*/ */
onDismiss = () => { onDismiss = () => {
const {state, props} = this; const { state, props } = this;
if (!state.loading && props.onDismiss != null) { if (!state.loading && props.onDismiss != null) {
props.onDismiss(); props.onDismiss();
} }
}; };
render() { render() {
const {state, props} = this; const { state, props } = this;
return ( return (
<Portal> <Portal>
<Dialog visible={props.visible} onDismiss={this.onDismiss}> <Dialog visible={props.visible} onDismiss={this.onDismiss}>
@ -113,7 +120,7 @@ export default class LoadingConfirmDialog extends React.PureComponent<
</Dialog.Content> </Dialog.Content>
{state.loading ? null : ( {state.loading ? null : (
<Dialog.Actions> <Dialog.Actions>
<Button onPress={this.onDismiss} style={{marginRight: 10}}> <Button onPress={this.onDismiss} style={styles.button}>
{i18n.t('dialog.cancel')} {i18n.t('dialog.cancel')}
</Button> </Button>
<Button onPress={this.onClickAccept}> <Button onPress={this.onClickAccept}>

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Button, Dialog, Paragraph, Portal} from 'react-native-paper'; import { Button, Dialog, Paragraph, Portal } from 'react-native-paper';
import {FlatList} from 'react-native'; import { FlatList } from 'react-native';
export type OptionsDialogButtonType = { export type OptionsDialogButtonType = {
title: string; title: string;
@ -36,7 +36,7 @@ type PropsType = {
}; };
function OptionsDialog(props: PropsType) { function OptionsDialog(props: PropsType) {
const getButtonRender = ({item}: {item: OptionsDialogButtonType}) => { const getButtonRender = ({ item }: { item: OptionsDialogButtonType }) => {
return ( return (
<Button onPress={item.onPress} icon={item.icon}> <Button onPress={item.onPress} icon={item.icon}>
{item.title} {item.title}

View file

@ -18,10 +18,19 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {List} from 'react-native-paper'; import { List } from 'react-native-paper';
import {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {useNavigation} from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
const styles = StyleSheet.create({
item: {
paddingTop: 0,
paddingBottom: 0,
marginLeft: 10,
marginRight: 10,
},
});
function ActionsDashBoardItem() { function ActionsDashBoardItem() {
const navigation = useNavigation(); const navigation = useNavigation();
@ -45,12 +54,7 @@ function ActionsDashBoardItem() {
/> />
)} )}
onPress={(): void => navigation.navigate('feedback')} onPress={(): void => navigation.navigate('feedback')}
style={{ style={styles.item}
paddingTop: 0,
paddingBottom: 0,
marginLeft: 10,
marginRight: 10,
}}
/> />
</View> </View>
); );

View file

@ -25,8 +25,9 @@ import {
TouchableRipple, TouchableRipple,
useTheme, useTheme,
} from 'react-native-paper'; } from 'react-native-paper';
import {StyleSheet, View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import GENERAL_STYLES from '../../constants/Styles';
type PropsType = { type PropsType = {
eventNumber: number; eventNumber: number;
@ -45,6 +46,9 @@ const styles = StyleSheet.create({
avatar: { avatar: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
}, },
text: {
fontWeight: 'bold',
},
}); });
/** /**
@ -61,7 +65,7 @@ function EventDashBoardItem(props: PropsType) {
if (isAvailable) { if (isAvailable) {
subtitle = ( subtitle = (
<Text> <Text>
<Text style={{fontWeight: 'bold'}}>{props.eventNumber}</Text> <Text style={styles.text}>{props.eventNumber}</Text>
<Text> <Text>
{props.eventNumber > 1 {props.eventNumber > 1
? i18n.t('screens.home.dashboard.todayEventsSubtitlePlural') ? i18n.t('screens.home.dashboard.todayEventsSubtitlePlural')
@ -74,13 +78,13 @@ function EventDashBoardItem(props: PropsType) {
} }
return ( return (
<Card style={styles.card}> <Card style={styles.card}>
<TouchableRipple style={{flex: 1}} onPress={props.clickAction}> <TouchableRipple style={GENERAL_STYLES.flex} onPress={props.clickAction}>
<View> <View>
<Card.Title <Card.Title
title={i18n.t('screens.home.dashboard.todayEventsTitle')} title={i18n.t('screens.home.dashboard.todayEventsTitle')}
titleStyle={{color: textColor}} titleStyle={{ color: textColor }}
subtitle={subtitle} subtitle={subtitle}
subtitleStyle={{color: textColor}} subtitleStyle={{ color: textColor }}
left={(iconProps) => ( left={(iconProps) => (
<Avatar.Icon <Avatar.Icon
icon="calendar-range" icon="calendar-range"

View file

@ -18,17 +18,18 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Button, Card, Text, TouchableRipple} from 'react-native-paper'; import { Button, Card, Text, TouchableRipple } from 'react-native-paper';
import {Image, View} from 'react-native'; import { Image, StyleSheet, View } from 'react-native';
import Autolink from 'react-native-autolink'; import Autolink from 'react-native-autolink';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {FeedItemType} from '../../screens/Home/HomeScreen'; import type { FeedItemType } from '../../screens/Home/HomeScreen';
import NewsSourcesConstants, { import NewsSourcesConstants, {
AvailablePages, AvailablePages,
} from '../../constants/NewsSourcesConstants'; } from '../../constants/NewsSourcesConstants';
import type {NewsSourceType} from '../../constants/NewsSourcesConstants'; import type { NewsSourceType } from '../../constants/NewsSourcesConstants';
import ImageGalleryButton from '../Media/ImageGalleryButton'; import ImageGalleryButton from '../Media/ImageGalleryButton';
import {useNavigation} from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import GENERAL_STYLES from '../../constants/Styles';
type PropsType = { type PropsType = {
item: FeedItemType; item: FeedItemType;
@ -46,6 +47,20 @@ function getFormattedDate(dateString: number): string {
return date.toLocaleString(); return date.toLocaleString();
} }
const styles = StyleSheet.create({
image: {
width: 48,
height: 48,
},
button: {
marginLeft: 'auto',
marginRight: 'auto',
},
action: {
marginLeft: 'auto',
},
});
/** /**
* Component used to display a feed item * Component used to display a feed item
*/ */
@ -58,7 +73,7 @@ function FeedItem(props: PropsType) {
}); });
}; };
const {item, height} = props; const { item, height } = props;
const image = item.image !== '' && item.image != null ? item.image : null; const image = item.image !== '' && item.image != null ? item.image : null;
const pageSource: NewsSourceType = const pageSource: NewsSourceType =
NewsSourcesConstants[item.page_id as AvailablePages]; NewsSourcesConstants[item.page_id as AvailablePages];
@ -76,31 +91,23 @@ function FeedItem(props: PropsType) {
style={{ style={{
margin: cardMargin, margin: cardMargin,
height: cardHeight, height: cardHeight,
}}> }}
<TouchableRipple style={{flex: 1}} onPress={onPress}> >
<TouchableRipple style={GENERAL_STYLES.flex} onPress={onPress}>
<View> <View>
<Card.Title <Card.Title
title={pageSource.name} title={pageSource.name}
subtitle={getFormattedDate(item.time)} subtitle={getFormattedDate(item.time)}
left={() => ( left={() => <Image source={pageSource.icon} style={styles.image} />}
<Image style={{ height: titleHeight }}
source={pageSource.icon}
style={{
width: 48,
height: 48,
}}
/>
)}
style={{height: titleHeight}}
/> />
{image != null ? ( {image != null ? (
<ImageGalleryButton <ImageGalleryButton
images={[{url: image}]} images={[{ url: image }]}
style={{ style={{
...styles.button,
width: imageSize, width: imageSize,
height: imageSize, height: imageSize,
marginLeft: 'auto',
marginRight: 'auto',
}} }}
/> />
) : null} ) : null}
@ -110,12 +117,12 @@ function FeedItem(props: PropsType) {
text={item.message} text={item.message}
hashtag="facebook" hashtag="facebook"
component={Text} component={Text}
style={{height: textHeight}} style={{ height: textHeight }}
/> />
) : null} ) : null}
</Card.Content> </Card.Content>
<Card.Actions style={{height: actionsHeight}}> <Card.Actions style={{ height: actionsHeight }}>
<Button onPress={onPress} icon="plus" style={{marginLeft: 'auto'}}> <Button onPress={onPress} icon="plus" style={styles.action}>
{i18n.t('screens.home.dashboard.seeMore')} {i18n.t('screens.home.dashboard.seeMore')}
</Button> </Button>
</Card.Actions> </Card.Actions>

View file

@ -18,12 +18,13 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {StyleSheet, View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {Avatar, Button, Card, TouchableRipple} from 'react-native-paper'; import { Avatar, Button, Card, TouchableRipple } from 'react-native-paper';
import {getTimeOnlyString, isDescriptionEmpty} from '../../utils/Planning'; import { getTimeOnlyString, isDescriptionEmpty } from '../../utils/Planning';
import CustomHTML from '../Overrides/CustomHTML'; import CustomHTML from '../Overrides/CustomHTML';
import type {PlanningEventType} from '../../utils/Planning'; import type { PlanningEventType } from '../../utils/Planning';
import GENERAL_STYLES from '../../constants/Styles';
type PropsType = { type PropsType = {
event?: PlanningEventType | null; event?: PlanningEventType | null;
@ -52,19 +53,26 @@ const styles = StyleSheet.create({
* Component used to display an event preview if an event is available * Component used to display an event preview if an event is available
*/ */
function PreviewEventDashboardItem(props: PropsType) { function PreviewEventDashboardItem(props: PropsType) {
const {event} = props; const { event } = props;
const isEmpty = event == null ? true : isDescriptionEmpty(event.description); const isEmpty = event == null ? true : isDescriptionEmpty(event.description);
if (event != null) { if (event != null) {
const logo = event.logo; const logo = event.logo;
const getImage = logo const getImage = logo
? () => ( ? () => (
<Avatar.Image source={{uri: logo}} size={50} style={styles.avatar} /> <Avatar.Image
source={{ uri: logo }}
size={50}
style={styles.avatar}
/>
) )
: () => null; : () => null;
return ( return (
<Card style={styles.card} elevation={3}> <Card style={styles.card} elevation={3}>
<TouchableRipple style={{flex: 1}} onPress={props.clickAction}> <TouchableRipple
style={GENERAL_STYLES.flex}
onPress={props.clickAction}
>
<View> <View>
<Card.Title <Card.Title
title={event.title} title={event.title}

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Badge, TouchableRipple, useTheme} from 'react-native-paper'; import { Badge, TouchableRipple, useTheme } from 'react-native-paper';
import {Dimensions, Image, View} from 'react-native'; import { Dimensions, Image, StyleSheet, View } from 'react-native';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
type PropsType = { type PropsType = {
@ -28,13 +28,32 @@ type PropsType = {
badgeCount?: number; badgeCount?: number;
}; };
const styles = StyleSheet.create({
image: {
width: '80%',
height: '80%',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 'auto',
marginBottom: 'auto',
},
badgeContainer: {
position: 'absolute',
top: 0,
right: 0,
},
badge: {
borderWidth: 2,
},
});
/** /**
* Component used to render a small dashboard item * Component used to render a small dashboard item
*/ */
function SmallDashboardItem(props: PropsType) { function SmallDashboardItem(props: PropsType) {
const itemSize = Dimensions.get('window').width / 8; const itemSize = Dimensions.get('window').width / 8;
const theme = useTheme(); const theme = useTheme();
const {image} = props; const { image } = props;
return ( return (
<TouchableRipple <TouchableRipple
onPress={props.onPress} onPress={props.onPress}
@ -42,23 +61,18 @@ function SmallDashboardItem(props: PropsType) {
style={{ style={{
marginLeft: itemSize / 6, marginLeft: itemSize / 6,
marginRight: itemSize / 6, marginRight: itemSize / 6,
}}> }}
>
<View <View
style={{ style={{
width: itemSize, width: itemSize,
height: itemSize, height: itemSize,
}}> }}
>
{image ? ( {image ? (
<Image <Image
source={typeof image === 'string' ? {uri: image} : image} source={typeof image === 'string' ? { uri: image } : image}
style={{ style={styles.image}
width: '80%',
height: '80%',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 'auto',
marginBottom: 'auto',
}}
/> />
) : null} ) : null}
{props.badgeCount != null && props.badgeCount > 0 ? ( {props.badgeCount != null && props.badgeCount > 0 ? (
@ -66,18 +80,16 @@ function SmallDashboardItem(props: PropsType) {
animation="zoomIn" animation="zoomIn"
duration={300} duration={300}
useNativeDriver useNativeDriver
style={{ style={styles.badgeContainer}
position: 'absolute', >
top: 0,
right: 0,
}}>
<Badge <Badge
visible={true} visible={true}
style={{ style={{
backgroundColor: theme.colors.primary, backgroundColor: theme.colors.primary,
borderColor: theme.colors.background, borderColor: theme.colors.background,
borderWidth: 2, ...styles.badge,
}}> }}
>
{props.badgeCount} {props.badgeCount}
</Badge> </Badge>
</Animatable.View> </Animatable.View>

View file

@ -18,9 +18,10 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {StyleSheet, View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import GENERAL_STYLES from '../../constants/Styles';
type PropsType = { type PropsType = {
icon: string; icon: string;
@ -37,7 +38,7 @@ const styles = StyleSheet.create({
function IntroIcon(props: PropsType) { function IntroIcon(props: PropsType) {
return ( return (
<View style={{flex: 1}}> <View style={GENERAL_STYLES.flex}>
<Animatable.View useNativeDriver style={styles.center} animation="fadeIn"> <Animatable.View useNativeDriver style={styles.center} animation="fadeIn">
<MaterialCommunityIcons name={props.icon} color="#fff" size={200} /> <MaterialCommunityIcons name={props.icon} color="#fff" size={200} />
</Animatable.View> </Animatable.View>

View file

@ -18,25 +18,23 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {StyleSheet, View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import Mascot, {MASCOT_STYLE} from '../Mascot/Mascot'; import GENERAL_STYLES from '../../constants/Styles';
import Mascot, { MASCOT_STYLE } from '../Mascot/Mascot';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
center: { center: {
marginTop: 'auto', ...GENERAL_STYLES.center,
marginBottom: 'auto', width: '80%',
marginRight: 'auto',
marginLeft: 'auto',
}, },
}); });
function MascotIntroEnd() { function MascotIntroEnd() {
return ( return (
<View style={{flex: 1}}> <View style={GENERAL_STYLES.flex}>
<Mascot <Mascot
style={{ style={{
...styles.center, ...styles.center,
width: '80%',
}} }}
emotion={MASCOT_STYLE.COOL} emotion={MASCOT_STYLE.COOL}
animated animated

View file

@ -18,28 +18,40 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {StyleSheet, View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import Mascot, {MASCOT_STYLE} from '../Mascot/Mascot'; import GENERAL_STYLES from '../../constants/Styles';
import Mascot, { MASCOT_STYLE } from '../Mascot/Mascot';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
center: { mascot: {
marginTop: 'auto', ...GENERAL_STYLES.center,
marginBottom: 'auto', width: '80%',
marginRight: 'auto', },
marginLeft: 'auto', text: {
color: '#fff',
textAlign: 'center',
fontSize: 25,
},
container: {
position: 'absolute',
bottom: 30,
right: '20%',
width: 50,
height: 50,
},
icon: {
...GENERAL_STYLES.center,
transform: [{ rotateZ: '70deg' }],
}, },
}); });
function MascotIntroWelcome() { function MascotIntroWelcome() {
return ( return (
<View style={{flex: 1}}> <View style={GENERAL_STYLES.flex}>
<Mascot <Mascot
style={{ style={styles.mascot}
...styles.center,
width: '80%',
}}
emotion={MASCOT_STYLE.NORMAL} emotion={MASCOT_STYLE.NORMAL}
animated animated
entryAnimation={{ entryAnimation={{
@ -51,11 +63,8 @@ function MascotIntroWelcome() {
useNativeDriver useNativeDriver
animation="fadeInUp" animation="fadeInUp"
duration={500} duration={500}
style={{ style={styles.text}
color: '#fff', >
textAlign: 'center',
fontSize: 25,
}}>
PABLO PABLO
</Animatable.Text> </Animatable.Text>
<Animatable.View <Animatable.View
@ -63,18 +72,10 @@ function MascotIntroWelcome() {
animation="fadeInUp" animation="fadeInUp"
duration={500} duration={500}
delay={200} delay={200}
style={{ style={styles.container}
position: 'absolute', >
bottom: 30,
right: '20%',
width: 50,
height: 50,
}}>
<MaterialCommunityIcons <MaterialCommunityIcons
style={{ style={styles.icon}
...styles.center,
transform: [{rotateZ: '70deg'}],
}}
name="undo" name="undo"
color="#fff" color="#fff"
size={40} size={40}

View file

@ -18,10 +18,10 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Animated, Dimensions, ViewStyle} from 'react-native'; import { Animated, Dimensions, ViewStyle } from 'react-native';
import ImageListItem from './ImageListItem'; import ImageListItem from './ImageListItem';
import CardListItem from './CardListItem'; import CardListItem from './CardListItem';
import type {ServiceItemType} from '../../../managers/ServicesManager'; import type { ServiceItemType } from '../../../managers/ServicesManager';
type PropsType = { type PropsType = {
dataset: Array<ServiceItemType>; dataset: Array<ServiceItemType>;
@ -45,8 +45,8 @@ export default class CardList extends React.Component<PropsType> {
this.horizontalItemSize = this.windowWidth / 4; // So that we can fit 3 items, and a part of the 4th => user knows he can scroll this.horizontalItemSize = this.windowWidth / 4; // So that we can fit 3 items, and a part of the 4th => user knows he can scroll
} }
getRenderItem = ({item}: {item: ServiceItemType}) => { getRenderItem = ({ item }: { item: ServiceItemType }) => {
const {props} = this; const { props } = this;
if (props.isHorizontal) { if (props.isHorizontal) {
return ( return (
<ImageListItem <ImageListItem
@ -62,7 +62,7 @@ export default class CardList extends React.Component<PropsType> {
keyExtractor = (item: ServiceItemType): string => item.key; keyExtractor = (item: ServiceItemType): string => item.key;
render() { render() {
const {props} = this; const { props } = this;
let containerStyle = {}; let containerStyle = {};
if (props.isHorizontal) { if (props.isHorizontal) {
containerStyle = { containerStyle = {

View file

@ -18,29 +18,36 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Caption, Card, Paragraph, TouchableRipple} from 'react-native-paper'; import { Caption, Card, Paragraph, TouchableRipple } from 'react-native-paper';
import {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import type {ServiceItemType} from '../../../managers/ServicesManager'; import type { ServiceItemType } from '../../../managers/ServicesManager';
import GENERAL_STYLES from '../../../constants/Styles';
type PropsType = { type PropsType = {
item: ServiceItemType; item: ServiceItemType;
}; };
const styles = StyleSheet.create({
card: {
width: '40%',
margin: 5,
marginLeft: 'auto',
marginRight: 'auto',
},
cover: {
height: 80,
},
});
function CardListItem(props: PropsType) { function CardListItem(props: PropsType) {
const {item} = props; const { item } = props;
const source = const source =
typeof item.image === 'number' ? item.image : {uri: item.image}; typeof item.image === 'number' ? item.image : { uri: item.image };
return ( return (
<Card <Card style={styles.card}>
style={{ <TouchableRipple style={GENERAL_STYLES.flex} onPress={item.onPress}>
width: '40%',
margin: 5,
marginLeft: 'auto',
marginRight: 'auto',
}}>
<TouchableRipple style={{flex: 1}} onPress={item.onPress}>
<View> <View>
<Card.Cover style={{height: 80}} source={source} /> <Card.Cover style={styles.cover} source={source} />
<Card.Content> <Card.Content>
<Paragraph>{item.title}</Paragraph> <Paragraph>{item.title}</Paragraph>
<Caption>{item.subtitle}</Caption> <Caption>{item.subtitle}</Caption>

View file

@ -18,46 +18,50 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Text, TouchableRipple} from 'react-native-paper'; import { Text, TouchableRipple } from 'react-native-paper';
import {Image, View} from 'react-native'; import { Image, StyleSheet, View } from 'react-native';
import type {ServiceItemType} from '../../../managers/ServicesManager'; import type { ServiceItemType } from '../../../managers/ServicesManager';
import GENERAL_STYLES from '../../../constants/Styles';
type PropsType = { type PropsType = {
item: ServiceItemType; item: ServiceItemType;
width: number; width: number;
}; };
const styles = StyleSheet.create({
ripple: {
margin: 5,
},
text: {
...GENERAL_STYLES.centerHorizontal,
marginTop: 5,
textAlign: 'center',
},
});
function ImageListItem(props: PropsType) { function ImageListItem(props: PropsType) {
const {item} = props; const { item } = props;
const source = const source =
typeof item.image === 'number' ? item.image : {uri: item.image}; typeof item.image === 'number' ? item.image : { uri: item.image };
return ( return (
<TouchableRipple <TouchableRipple
style={{ style={{
width: props.width, width: props.width,
height: props.width + 40, height: props.width + 40,
margin: 5, ...styles.ripple,
}} }}
onPress={item.onPress}> onPress={item.onPress}
>
<View> <View>
<Image <Image
style={{ style={{
width: props.width - 20, width: props.width - 20,
height: props.width - 20, height: props.width - 20,
marginLeft: 'auto', ...GENERAL_STYLES.centerHorizontal,
marginRight: 'auto',
}} }}
source={source} source={source}
/> />
<Text <Text style={styles.text}>{item.title}</Text>
style={{
marginTop: 5,
marginLeft: 'auto',
marginRight: 'auto',
textAlign: 'center',
}}>
{item.title}
</Text>
</View> </View>
</TouchableRipple> </TouchableRipple>
); );

View file

@ -18,12 +18,13 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Card, Chip, List, Text} from 'react-native-paper'; import { Card, Chip, List, Text } from 'react-native-paper';
import {StyleSheet, View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import AnimatedAccordion from '../../Animations/AnimatedAccordion'; import AnimatedAccordion from '../../Animations/AnimatedAccordion';
import {isItemInCategoryFilter} from '../../../utils/Search'; import { isItemInCategoryFilter } from '../../../utils/Search';
import type {ClubCategoryType} from '../../../screens/Amicale/Clubs/ClubListScreen'; import type { ClubCategoryType } from '../../../screens/Amicale/Clubs/ClubListScreen';
import GENERAL_STYLES from '../../../constants/Styles';
type PropsType = { type PropsType = {
categories: Array<ClubCategoryType>; categories: Array<ClubCategoryType>;
@ -39,8 +40,7 @@ const styles = StyleSheet.create({
paddingLeft: 0, paddingLeft: 0,
marginTop: 5, marginTop: 5,
marginBottom: 10, marginBottom: 10,
marginLeft: 'auto', ...GENERAL_STYLES.centerHorizontal,
marginRight: 'auto',
}, },
chipContainer: { chipContainer: {
justifyContent: 'space-around', justifyContent: 'space-around',
@ -49,6 +49,11 @@ const styles = StyleSheet.create({
paddingLeft: 0, paddingLeft: 0,
marginBottom: 5, marginBottom: 5,
}, },
chip: {
marginRight: 5,
marginLeft: 5,
marginBottom: 5,
},
}); });
function ClubListHeader(props: PropsType) { function ClubListHeader(props: PropsType) {
@ -62,8 +67,9 @@ function ClubListHeader(props: PropsType) {
])} ])}
mode="outlined" mode="outlined"
onPress={onPress} onPress={onPress}
style={{marginRight: 5, marginLeft: 5, marginBottom: 5}} style={styles.chip}
key={key}> key={key}
>
{category.name} {category.name}
</Chip> </Chip>
); );
@ -88,7 +94,8 @@ function ClubListHeader(props: PropsType) {
icon="star" icon="star"
/> />
)} )}
opened> opened
>
<Text style={styles.text}> <Text style={styles.text}>
{i18n.t('screens.clubs.categoriesFilterMessage')} {i18n.t('screens.clubs.categoriesFilterMessage')}
</Text> </Text>

View file

@ -18,12 +18,13 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Avatar, Chip, List, withTheme} from 'react-native-paper'; import { Avatar, Chip, List, withTheme } from 'react-native-paper';
import {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import type { import type {
ClubCategoryType, ClubCategoryType,
ClubType, ClubType,
} from '../../../screens/Amicale/Clubs/ClubListScreen'; } from '../../../screens/Amicale/Clubs/ClubListScreen';
import GENERAL_STYLES from '../../../constants/Styles';
type PropsType = { type PropsType = {
onPress: () => void; onPress: () => void;
@ -33,6 +34,28 @@ type PropsType = {
theme: ReactNativePaper.Theme; theme: ReactNativePaper.Theme;
}; };
const styles = StyleSheet.create({
chip: {
marginRight: 5,
marginBottom: 5,
},
chipContainer: {
flexDirection: 'row',
},
avatar: {
backgroundColor: 'transparent',
marginLeft: 10,
marginRight: 10,
},
icon: {
...GENERAL_STYLES.centerVertical,
backgroundColor: 'transparent',
},
item: {
justifyContent: 'center',
},
});
class ClubListItem extends React.Component<PropsType> { class ClubListItem extends React.Component<PropsType> {
hasManagers: boolean; hasManagers: boolean;
@ -46,30 +69,28 @@ class ClubListItem extends React.Component<PropsType> {
} }
getCategoriesRender(categories: Array<number | null>) { getCategoriesRender(categories: Array<number | null>) {
const {props} = this; const { props } = this;
const final: Array<React.ReactNode> = []; const final: Array<React.ReactNode> = [];
categories.forEach((cat: number | null) => { categories.forEach((cat: number | null) => {
if (cat != null) { if (cat != null) {
const category = props.categoryTranslator(cat); const category = props.categoryTranslator(cat);
if (category) { if (category) {
final.push( final.push(
<Chip <Chip style={styles.chip} key={`${props.item.id}:${category.id}`}>
style={{marginRight: 5, marginBottom: 5}}
key={`${props.item.id}:${category.id}`}>
{category.name} {category.name}
</Chip>, </Chip>
); );
} }
} }
}); });
return <View style={{flexDirection: 'row'}}>{final}</View>; return <View style={styles.chipContainer}>{final}</View>;
} }
render() { render() {
const {props} = this; const { props } = this;
const categoriesRender = () => const categoriesRender = () =>
this.getCategoriesRender(props.item.category); this.getCategoriesRender(props.item.category);
const {colors} = props.theme; const { colors } = props.theme;
return ( return (
<List.Item <List.Item
title={props.item.name} title={props.item.name}
@ -77,22 +98,14 @@ class ClubListItem extends React.Component<PropsType> {
onPress={props.onPress} onPress={props.onPress}
left={() => ( left={() => (
<Avatar.Image <Avatar.Image
style={{ style={styles.avatar}
backgroundColor: 'transparent',
marginLeft: 10,
marginRight: 10,
}}
size={64} size={64}
source={{uri: props.item.logo}} source={{ uri: props.item.logo }}
/> />
)} )}
right={() => ( right={() => (
<Avatar.Icon <Avatar.Icon
style={{ style={styles.icon}
marginTop: 'auto',
marginBottom: 'auto',
backgroundColor: 'transparent',
}}
size={48} size={48}
icon={ icon={
this.hasManagers ? 'check-circle-outline' : 'alert-circle-outline' this.hasManagers ? 'check-circle-outline' : 'alert-circle-outline'
@ -102,7 +115,7 @@ class ClubListItem extends React.Component<PropsType> {
)} )}
style={{ style={{
height: props.height, height: props.height,
justifyContent: 'center', ...styles.item,
}} }}
/> />
); );

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {useTheme} from 'react-native-paper'; import { useTheme } from 'react-native-paper';
import {FlatList, Image, View} from 'react-native'; import { FlatList, Image, StyleSheet, View } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import DashboardEditItem from './DashboardEditItem'; import DashboardEditItem from './DashboardEditItem';
import AnimatedAccordion from '../../Animations/AnimatedAccordion'; import AnimatedAccordion from '../../Animations/AnimatedAccordion';
@ -34,12 +34,19 @@ type PropsType = {
onPress: (service: ServiceItemType) => void; onPress: (service: ServiceItemType) => void;
}; };
const styles = StyleSheet.create({
image: {
width: 40,
height: 40,
},
});
const LIST_ITEM_HEIGHT = 64; const LIST_ITEM_HEIGHT = 64;
function DashboardEditAccordion(props: PropsType) { function DashboardEditAccordion(props: PropsType) {
const theme = useTheme(); const theme = useTheme();
const getRenderItem = ({item}: {item: ServiceItemType}) => { const getRenderItem = ({ item }: { item: ServiceItemType }) => {
return ( return (
<DashboardEditItem <DashboardEditItem
height={LIST_ITEM_HEIGHT} height={LIST_ITEM_HEIGHT}
@ -54,27 +61,21 @@ function DashboardEditAccordion(props: PropsType) {
const getItemLayout = ( const getItemLayout = (
data: Array<ServiceItemType> | null | undefined, data: Array<ServiceItemType> | null | undefined,
index: number, index: number
): {length: number; offset: number; index: number} => ({ ): { length: number; offset: number; index: number } => ({
length: LIST_ITEM_HEIGHT, length: LIST_ITEM_HEIGHT,
offset: LIST_ITEM_HEIGHT * index, offset: LIST_ITEM_HEIGHT * index,
index, index,
}); });
const {item} = props; const { item } = props;
return ( return (
<View> <View>
<AnimatedAccordion <AnimatedAccordion
title={item.title} title={item.title}
left={() => left={() =>
typeof item.image === 'number' ? ( typeof item.image === 'number' ? (
<Image <Image source={item.image} style={styles.image} />
source={item.image}
style={{
width: 40,
height: 40,
}}
/>
) : ( ) : (
<MaterialCommunityIcons <MaterialCommunityIcons
name={item.image} name={item.image}
@ -82,7 +83,8 @@ function DashboardEditAccordion(props: PropsType) {
size={40} size={40}
/> />
) )
}> }
>
<FlatList <FlatList
data={item.content} data={item.content}
extraData={props.activeDashboard.toString()} extraData={props.activeDashboard.toString()}

View file

@ -18,9 +18,9 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Image} from 'react-native'; import { Image, StyleSheet } from 'react-native';
import {List, useTheme} from 'react-native-paper'; import { List, useTheme } from 'react-native-paper';
import type {ServiceItemType} from '../../../managers/ServicesManager'; import type { ServiceItemType } from '../../../managers/ServicesManager';
type PropsType = { type PropsType = {
item: ServiceItemType; item: ServiceItemType;
@ -29,9 +29,23 @@ type PropsType = {
onPress: () => void; onPress: () => void;
}; };
const styles = StyleSheet.create({
image: {
width: 40,
height: 40,
},
item: {
justifyContent: 'center',
paddingLeft: 30,
},
});
function DashboardEditItem(props: PropsType) { function DashboardEditItem(props: PropsType) {
const theme = useTheme(); const theme = useTheme();
const {item, onPress, height, isActive} = props; const { item, onPress, height, isActive } = props;
const backgroundColor = isActive
? theme.colors.proxiwashFinishedColor
: 'transparent';
return ( return (
<List.Item <List.Item
title={item.title} title={item.title}
@ -40,12 +54,9 @@ function DashboardEditItem(props: PropsType) {
left={() => ( left={() => (
<Image <Image
source={ source={
typeof item.image === 'string' ? {uri: item.image} : item.image typeof item.image === 'string' ? { uri: item.image } : item.image
} }
style={{ style={styles.image}
width: 40,
height: 40,
}}
/> />
)} )}
right={(iconProps) => right={(iconProps) =>
@ -58,12 +69,9 @@ function DashboardEditItem(props: PropsType) {
) : null ) : null
} }
style={{ style={{
height, ...styles.image,
justifyContent: 'center', height: height,
paddingLeft: 30, backgroundColor: backgroundColor,
backgroundColor: isActive
? theme.colors.proxiwashFinishedColor
: 'transparent',
}} }}
/> />
); );

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {TouchableRipple, useTheme} from 'react-native-paper'; import { TouchableRipple, useTheme } from 'react-native-paper';
import {Dimensions, Image, View} from 'react-native'; import { Dimensions, Image, StyleSheet, View } from 'react-native';
type PropsType = { type PropsType = {
image?: string | number; image?: string | number;
@ -27,39 +27,50 @@ type PropsType = {
onPress: () => void; onPress: () => void;
}; };
const styles = StyleSheet.create({
ripple: {
marginLeft: 5,
marginRight: 5,
borderRadius: 5,
},
image: {
width: '100%',
height: '100%',
},
});
/** /**
* Component used to render a small dashboard item * Component used to render a small dashboard item
*/ */
function DashboardEditPreviewItem(props: PropsType) { function DashboardEditPreviewItem(props: PropsType) {
const theme = useTheme(); const theme = useTheme();
const itemSize = Dimensions.get('window').width / 8; const itemSize = Dimensions.get('window').width / 8;
const backgroundColor = props.isActive
? theme.colors.textDisabled
: 'transparent';
return ( return (
<TouchableRipple <TouchableRipple
onPress={props.onPress} onPress={props.onPress}
borderless borderless
style={{ style={{
marginLeft: 5, ...styles.ripple,
marginRight: 5, backgroundColor: backgroundColor,
backgroundColor: props.isActive }}
? theme.colors.textDisabled >
: 'transparent',
borderRadius: 5,
}}>
<View <View
style={{ style={{
width: itemSize, width: itemSize,
height: itemSize, height: itemSize,
}}> }}
>
{props.image ? ( {props.image ? (
<Image <Image
source={ source={
typeof props.image === 'string' ? {uri: props.image} : props.image typeof props.image === 'string'
? { uri: props.image }
: props.image
} }
style={{ style={styles.image}
width: '100%',
height: '100%',
}}
/> />
) : null} ) : null}
</View> </View>

View file

@ -18,15 +18,17 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Avatar, List, useTheme} from 'react-native-paper'; import { Avatar, List, useTheme } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import type {DeviceType} from '../../../screens/Amicale/Equipment/EquipmentListScreen'; import type { DeviceType } from '../../../screens/Amicale/Equipment/EquipmentListScreen';
import { import {
getFirstEquipmentAvailability, getFirstEquipmentAvailability,
getRelativeDateString, getRelativeDateString,
isEquipmentAvailable, isEquipmentAvailable,
} from '../../../utils/EquipmentBooking'; } from '../../../utils/EquipmentBooking';
import { StyleSheet } from 'react-native';
import GENERAL_STYLES from '../../../constants/Styles';
type PropsType = { type PropsType = {
navigation: StackNavigationProp<any>; navigation: StackNavigationProp<any>;
@ -35,9 +37,18 @@ type PropsType = {
height: number; height: number;
}; };
const styles = StyleSheet.create({
icon: {
backgroundColor: 'transparent',
},
item: {
justifyContent: 'center',
},
});
function EquipmentListItem(props: PropsType) { function EquipmentListItem(props: PropsType) {
const theme = useTheme(); const theme = useTheme();
const {item, userDeviceRentDates, navigation, height} = props; const { item, userDeviceRentDates, navigation, height } = props;
const isRented = userDeviceRentDates != null; const isRented = userDeviceRentDates != null;
const isAvailable = isEquipmentAvailable(item); const isAvailable = isEquipmentAvailable(item);
const firstAvailability = getFirstEquipmentAvailability(item); const firstAvailability = getFirstEquipmentAvailability(item);
@ -52,7 +63,7 @@ function EquipmentListItem(props: PropsType) {
}; };
} else { } else {
onPress = () => { onPress = () => {
navigation.navigate('equipment-rent', {item}); navigation.navigate('equipment-rent', { item });
}; };
} }
@ -71,7 +82,7 @@ function EquipmentListItem(props: PropsType) {
}); });
} }
} else if (isAvailable) { } else if (isAvailable) {
description = i18n.t('screens.equipment.bail', {cost: item.caution}); description = i18n.t('screens.equipment.bail', { cost: item.caution });
} else { } else {
description = i18n.t('screens.equipment.available', { description = i18n.t('screens.equipment.available', {
date: getRelativeDateString(firstAvailability), date: getRelativeDateString(firstAvailability),
@ -101,21 +112,12 @@ function EquipmentListItem(props: PropsType) {
title={item.name} title={item.name}
description={description} description={description}
onPress={onPress} onPress={onPress}
left={() => ( left={() => <Avatar.Icon style={styles.icon} icon={icon} color={color} />}
<Avatar.Icon
style={{
backgroundColor: 'transparent',
}}
icon={icon}
color={color}
/>
)}
right={() => ( right={() => (
<Avatar.Icon <Avatar.Icon
style={{ style={{
marginTop: 'auto', ...GENERAL_STYLES.centerVertical,
marginBottom: 'auto', ...styles.icon,
backgroundColor: 'transparent',
}} }}
size={48} size={48}
icon="chevron-right" icon="chevron-right"
@ -123,7 +125,7 @@ function EquipmentListItem(props: PropsType) {
)} )}
style={{ style={{
height, height,
justifyContent: 'center', ...styles.item,
}} }}
/> />
); );

View file

@ -18,9 +18,9 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {List, withTheme} from 'react-native-paper'; import { List, withTheme } from 'react-native-paper';
import {FlatList, View} from 'react-native'; import { FlatList, StyleSheet, View } from 'react-native';
import {stringMatchQuery} from '../../../utils/Search'; import { stringMatchQuery } from '../../../utils/Search';
import GroupListItem from './GroupListItem'; import GroupListItem from './GroupListItem';
import AnimatedAccordion from '../../Animations/AnimatedAccordion'; import AnimatedAccordion from '../../Animations/AnimatedAccordion';
import type { import type {
@ -40,9 +40,15 @@ type PropsType = {
const LIST_ITEM_HEIGHT = 64; const LIST_ITEM_HEIGHT = 64;
const REPLACE_REGEX = /_/g; const REPLACE_REGEX = /_/g;
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
},
});
class GroupListAccordion extends React.Component<PropsType> { class GroupListAccordion extends React.Component<PropsType> {
shouldComponentUpdate(nextProps: PropsType): boolean { shouldComponentUpdate(nextProps: PropsType): boolean {
const {props} = this; const { props } = this;
return ( return (
nextProps.currentSearchString !== props.currentSearchString || nextProps.currentSearchString !== props.currentSearchString ||
nextProps.favorites.length !== props.favorites.length || nextProps.favorites.length !== props.favorites.length ||
@ -50,8 +56,8 @@ class GroupListAccordion extends React.Component<PropsType> {
); );
} }
getRenderItem = ({item}: {item: PlanexGroupType}) => { getRenderItem = ({ item }: { item: PlanexGroupType }) => {
const {props} = this; const { props } = this;
const onPress = () => { const onPress = () => {
props.onGroupPress(item); props.onGroupPress(item);
}; };
@ -70,7 +76,7 @@ class GroupListAccordion extends React.Component<PropsType> {
}; };
getData(): Array<PlanexGroupType> { getData(): Array<PlanexGroupType> {
const {props} = this; const { props } = this;
const originalData = props.item.content; const originalData = props.item.content;
const displayData: Array<PlanexGroupType> = []; const displayData: Array<PlanexGroupType> = [];
originalData.forEach((data: PlanexGroupType) => { originalData.forEach((data: PlanexGroupType) => {
@ -83,8 +89,8 @@ class GroupListAccordion extends React.Component<PropsType> {
itemLayout = ( itemLayout = (
data: Array<PlanexGroupType> | null | undefined, data: Array<PlanexGroupType> | null | undefined,
index: number, index: number
): {length: number; offset: number; index: number} => ({ ): { length: number; offset: number; index: number } => ({
length: LIST_ITEM_HEIGHT, length: LIST_ITEM_HEIGHT,
offset: LIST_ITEM_HEIGHT * index, offset: LIST_ITEM_HEIGHT * index,
index, index,
@ -93,15 +99,13 @@ class GroupListAccordion extends React.Component<PropsType> {
keyExtractor = (item: PlanexGroupType): string => item.id.toString(); keyExtractor = (item: PlanexGroupType): string => item.id.toString();
render() { render() {
const {props} = this; const { props } = this;
const {item} = this.props; const { item } = this.props;
return ( return (
<View> <View>
<AnimatedAccordion <AnimatedAccordion
title={item.name.replace(REPLACE_REGEX, ' ')} title={item.name.replace(REPLACE_REGEX, ' ')}
style={{ style={styles.container}
justifyContent: 'center',
}}
left={(iconProps) => left={(iconProps) =>
item.id === 0 ? ( item.id === 0 ? (
<List.Icon <List.Icon
@ -112,7 +116,8 @@ class GroupListAccordion extends React.Component<PropsType> {
) : null ) : null
} }
unmountWhenCollapsed={item.id !== 0} // Only render list if expanded for increased performance unmountWhenCollapsed={item.id !== 0} // Only render list if expanded for increased performance
opened={props.currentSearchString.length > 0}> opened={props.currentSearchString.length > 0}
>
<FlatList <FlatList
data={this.getData()} data={this.getData()}
extraData={props.currentSearchString + props.favorites.length} extraData={props.currentSearchString + props.favorites.length}

View file

@ -18,12 +18,12 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {List, TouchableRipple, withTheme} from 'react-native-paper'; import { List, TouchableRipple, withTheme } from 'react-native-paper';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import type {PlanexGroupType} from '../../../screens/Planex/GroupSelectionScreen'; import type { PlanexGroupType } from '../../../screens/Planex/GroupSelectionScreen';
import {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import {getPrettierPlanexGroupName} from '../../../utils/Utils'; import { getPrettierPlanexGroupName } from '../../../utils/Utils';
type PropsType = { type PropsType = {
theme: ReactNativePaper.Theme; theme: ReactNativePaper.Theme;
@ -34,10 +34,25 @@ type PropsType = {
height: number; height: number;
}; };
const styles = StyleSheet.create({
item: {
justifyContent: 'center',
},
icon: {
padding: 10,
},
iconContainer: {
marginRight: 10,
marginLeft: 'auto',
marginTop: 'auto',
marginBottom: 'auto',
},
});
class GroupListItem extends React.Component<PropsType> { class GroupListItem extends React.Component<PropsType> {
isFav: boolean; isFav: boolean;
starRef: {current: null | (Animatable.View & View)}; starRef: { current: null | (Animatable.View & View) };
constructor(props: PropsType) { constructor(props: PropsType) {
super(props); super(props);
@ -46,7 +61,7 @@ class GroupListItem extends React.Component<PropsType> {
} }
shouldComponentUpdate(nextProps: PropsType): boolean { shouldComponentUpdate(nextProps: PropsType): boolean {
const {favorites} = this.props; const { favorites } = this.props;
const favChanged = favorites.length !== nextProps.favorites.length; const favChanged = favorites.length !== nextProps.favorites.length;
let newFavState = this.isFav; let newFavState = this.isFav;
if (favChanged) { if (favChanged) {
@ -58,7 +73,7 @@ class GroupListItem extends React.Component<PropsType> {
} }
onStarPress = () => { onStarPress = () => {
const {props} = this; const { props } = this;
const ref = this.starRef; const ref = this.starRef;
if (ref.current && ref.current.rubberBand && ref.current.swing) { if (ref.current && ref.current.rubberBand && ref.current.swing) {
if (this.isFav) { if (this.isFav) {
@ -71,7 +86,7 @@ class GroupListItem extends React.Component<PropsType> {
}; };
isGroupInFavorites(favorites: Array<PlanexGroupType>): boolean { isGroupInFavorites(favorites: Array<PlanexGroupType>): boolean {
const {item} = this.props; const { item } = this.props;
for (let i = 0; i < favorites.length; i += 1) { for (let i = 0; i < favorites.length; i += 1) {
if (favorites[i].id === item.id) { if (favorites[i].id === item.id) {
return true; return true;
@ -81,8 +96,8 @@ class GroupListItem extends React.Component<PropsType> {
} }
render() { render() {
const {props} = this; const { props } = this;
const {colors} = props.theme; const { colors } = props.theme;
return ( return (
<List.Item <List.Item
title={getPrettierPlanexGroupName(props.item.name)} title={getPrettierPlanexGroupName(props.item.name)}
@ -98,15 +113,11 @@ class GroupListItem extends React.Component<PropsType> {
<Animatable.View ref={this.starRef} useNativeDriver> <Animatable.View ref={this.starRef} useNativeDriver>
<TouchableRipple <TouchableRipple
onPress={this.onStarPress} onPress={this.onStarPress}
style={{ style={styles.iconContainer}
marginRight: 10, >
marginLeft: 'auto',
marginTop: 'auto',
marginBottom: 'auto',
}}>
<MaterialCommunityIcons <MaterialCommunityIcons
size={30} size={30}
style={{padding: 10}} style={styles.icon}
name="star" name="star"
color={this.isFav ? colors.tetrisScore : iconProps.color} color={this.isFav ? colors.tetrisScore : iconProps.color}
/> />
@ -115,7 +126,7 @@ class GroupListItem extends React.Component<PropsType> {
)} )}
style={{ style={{
height: props.height, height: props.height,
justifyContent: 'center', ...styles.item,
}} }}
/> />
); );

View file

@ -18,9 +18,10 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Avatar, List, Text} from 'react-native-paper'; import { Avatar, List, Text } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {ProximoArticleType} from '../../../screens/Services/Proximo/ProximoMainScreen'; import type { ProximoArticleType } from '../../../screens/Services/Proximo/ProximoMainScreen';
import { StyleSheet } from 'react-native';
type PropsType = { type PropsType = {
onPress: () => void; onPress: () => void;
@ -29,28 +30,38 @@ type PropsType = {
height: number; height: number;
}; };
const styles = StyleSheet.create({
avatar: {
backgroundColor: 'transparent',
},
text: {
fontWeight: 'bold',
},
item: {
justifyContent: 'center',
},
});
function ProximoListItem(props: PropsType) { function ProximoListItem(props: PropsType) {
return ( return (
<List.Item <List.Item
title={props.item.name} title={props.item.name}
description={`${props.item.quantity} ${i18n.t( description={`${props.item.quantity} ${i18n.t(
'screens.proximo.inStock', 'screens.proximo.inStock'
)}`} )}`}
descriptionStyle={{color: props.color}} descriptionStyle={{ color: props.color }}
onPress={props.onPress} onPress={props.onPress}
left={() => ( left={() => (
<Avatar.Image <Avatar.Image
style={{backgroundColor: 'transparent'}} style={styles.avatar}
size={64} size={64}
source={{uri: props.item.image}} source={{ uri: props.item.image }}
/> />
)} )}
right={() => ( right={() => <Text style={styles.text}>{props.item.price}</Text>}
<Text style={{fontWeight: 'bold'}}>{props.item.price}</Text>
)}
style={{ style={{
height: props.height, height: props.height,
justifyContent: 'center', ...styles.item,
}} }}
/> />
); );

View file

@ -27,14 +27,14 @@ import {
Text, Text,
withTheme, withTheme,
} from 'react-native-paper'; } from 'react-native-paper';
import {StyleSheet, View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import ProxiwashConstants, { import ProxiwashConstants, {
MachineStates, MachineStates,
} from '../../../constants/ProxiwashConstants'; } from '../../../constants/ProxiwashConstants';
import AprilFoolsManager from '../../../managers/AprilFoolsManager'; import AprilFoolsManager from '../../../managers/AprilFoolsManager';
import type {ProxiwashMachineType} from '../../../screens/Proxiwash/ProxiwashScreen'; import type { ProxiwashMachineType } from '../../../screens/Proxiwash/ProxiwashScreen';
type PropsType = { type PropsType = {
item: ProxiwashMachineType; item: ProxiwashMachineType;
@ -42,7 +42,7 @@ type PropsType = {
onPress: ( onPress: (
title: string, title: string,
item: ProxiwashMachineType, item: ProxiwashMachineType,
isDryer: boolean, isDryer: boolean
) => void; ) => void;
isWatched: boolean; isWatched: boolean;
isDryer: boolean; isDryer: boolean;
@ -56,6 +56,7 @@ const styles = StyleSheet.create({
margin: 5, margin: 5,
justifyContent: 'center', justifyContent: 'center',
elevation: 1, elevation: 1,
borderRadius: 4,
}, },
icon: { icon: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
@ -65,17 +66,29 @@ const styles = StyleSheet.create({
left: 0, left: 0,
borderRadius: 4, borderRadius: 4,
}, },
item: {
justifyContent: 'center',
},
text: {
fontWeight: 'bold',
},
textRow: {
flexDirection: 'row',
},
textContainer: {
justifyContent: 'center',
},
}); });
/** /**
* Component used to display a proxiwash item, showing machine progression and state * Component used to display a proxiwash item, showing machine progression and state
*/ */
class ProxiwashListItem extends React.Component<PropsType> { class ProxiwashListItem extends React.Component<PropsType> {
stateStrings: {[key in MachineStates]: string} = { stateStrings: { [key in MachineStates]: string } = {
[MachineStates.AVAILABLE]: i18n.t('screens.proxiwash.states.ready'), [MachineStates.AVAILABLE]: i18n.t('screens.proxiwash.states.ready'),
[MachineStates.RUNNING]: i18n.t('screens.proxiwash.states.running'), [MachineStates.RUNNING]: i18n.t('screens.proxiwash.states.running'),
[MachineStates.RUNNING_NOT_STARTED]: i18n.t( [MachineStates.RUNNING_NOT_STARTED]: i18n.t(
'screens.proxiwash.states.runningNotStarted', 'screens.proxiwash.states.runningNotStarted'
), ),
[MachineStates.FINISHED]: i18n.t('screens.proxiwash.states.finished'), [MachineStates.FINISHED]: i18n.t('screens.proxiwash.states.finished'),
[MachineStates.UNAVAILABLE]: i18n.t('screens.proxiwash.states.broken'), [MachineStates.UNAVAILABLE]: i18n.t('screens.proxiwash.states.broken'),
@ -83,7 +96,7 @@ class ProxiwashListItem extends React.Component<PropsType> {
[MachineStates.UNKNOWN]: i18n.t('screens.proxiwash.states.unknown'), [MachineStates.UNKNOWN]: i18n.t('screens.proxiwash.states.unknown'),
}; };
stateColors: {[key: string]: string}; stateColors: { [key: string]: string };
title: string; title: string;
@ -97,7 +110,7 @@ class ProxiwashListItem extends React.Component<PropsType> {
const displayMaxWeight = props.item.maxWeight; const displayMaxWeight = props.item.maxWeight;
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) { if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
displayNumber = AprilFoolsManager.getProxiwashMachineDisplayNumber( displayNumber = AprilFoolsManager.getProxiwashMachineDisplayNumber(
parseInt(props.item.number, 10), parseInt(props.item.number, 10)
); );
} }
@ -109,7 +122,7 @@ class ProxiwashListItem extends React.Component<PropsType> {
} }
shouldComponentUpdate(nextProps: PropsType): boolean { shouldComponentUpdate(nextProps: PropsType): boolean {
const {props} = this; const { props } = this;
return ( return (
nextProps.theme.dark !== props.theme.dark || nextProps.theme.dark !== props.theme.dark ||
nextProps.item.state !== props.item.state || nextProps.item.state !== props.item.state ||
@ -119,13 +132,13 @@ class ProxiwashListItem extends React.Component<PropsType> {
} }
onListItemPress = () => { onListItemPress = () => {
const {props} = this; const { props } = this;
props.onPress(this.titlePopUp, props.item, props.isDryer); props.onPress(this.titlePopUp, props.item, props.isDryer);
}; };
updateStateColors() { updateStateColors() {
const {props} = this; const { props } = this;
const {colors} = props.theme; const { colors } = props.theme;
this.stateColors[MachineStates.AVAILABLE] = colors.proxiwashReadyColor; this.stateColors[MachineStates.AVAILABLE] = colors.proxiwashReadyColor;
this.stateColors[MachineStates.RUNNING] = colors.proxiwashRunningColor; this.stateColors[MachineStates.RUNNING] = colors.proxiwashRunningColor;
this.stateColors[MachineStates.RUNNING_NOT_STARTED] = this.stateColors[MachineStates.RUNNING_NOT_STARTED] =
@ -137,8 +150,8 @@ class ProxiwashListItem extends React.Component<PropsType> {
} }
render() { render() {
const {props} = this; const { props } = this;
const {colors} = props.theme; const { colors } = props.theme;
const machineState = props.item.state; const machineState = props.item.state;
const isRunning = machineState === MachineStates.RUNNING; const isRunning = machineState === MachineStates.RUNNING;
const isReady = machineState === MachineStates.AVAILABLE; const isReady = machineState === MachineStates.AVAILABLE;
@ -184,8 +197,8 @@ class ProxiwashListItem extends React.Component<PropsType> {
style={{ style={{
...styles.container, ...styles.container,
height: props.height, height: props.height,
borderRadius: 4, }}
}}> >
{!isReady ? ( {!isReady ? (
<ProgressBar <ProgressBar
style={{ style={{
@ -201,26 +214,27 @@ class ProxiwashListItem extends React.Component<PropsType> {
description={description} description={description}
style={{ style={{
height: props.height, height: props.height,
justifyContent: 'center', ...styles.item,
}} }}
onPress={this.onListItemPress} onPress={this.onListItemPress}
left={() => icon} left={() => icon}
right={() => ( right={() => (
<View style={{flexDirection: 'row'}}> <View style={styles.textRow}>
<View style={{justifyContent: 'center'}}> <View style={styles.textContainer}>
<Text <Text
style={ style={
machineState === MachineStates.FINISHED machineState === MachineStates.FINISHED
? {fontWeight: 'bold'} ? styles.text
: {} : undefined
}> }
>
{stateString} {stateString}
</Text> </Text>
{machineState === MachineStates.RUNNING ? ( {machineState === MachineStates.RUNNING ? (
<Caption>{props.item.remainingTime} min</Caption> <Caption>{props.item.remainingTime} min</Caption>
) : null} ) : null}
</View> </View>
<View style={{justifyContent: 'center'}}> <View style={styles.textContainer}>
<Avatar.Icon <Avatar.Icon
icon={stateIcon} icon={stateIcon}
color={colors.text} color={colors.text}

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Avatar, Text, withTheme} from 'react-native-paper'; import { Avatar, Text, withTheme } from 'react-native-paper';
import {StyleSheet, View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
type PropsType = { type PropsType = {
@ -44,6 +44,9 @@ const styles = StyleSheet.create({
fontSize: 20, fontSize: 20,
fontWeight: 'bold', fontWeight: 'bold',
}, },
textContainer: {
justifyContent: 'center',
},
}); });
/** /**
@ -51,7 +54,7 @@ const styles = StyleSheet.create({
*/ */
class ProxiwashListItem extends React.Component<PropsType> { class ProxiwashListItem extends React.Component<PropsType> {
shouldComponentUpdate(nextProps: PropsType): boolean { shouldComponentUpdate(nextProps: PropsType): boolean {
const {props} = this; const { props } = this;
return ( return (
nextProps.theme.dark !== props.theme.dark || nextProps.theme.dark !== props.theme.dark ||
nextProps.nbAvailable !== props.nbAvailable nextProps.nbAvailable !== props.nbAvailable
@ -59,7 +62,7 @@ class ProxiwashListItem extends React.Component<PropsType> {
} }
render() { render() {
const {props} = this; const { props } = this;
const subtitle = `${props.nbAvailable} ${ const subtitle = `${props.nbAvailable} ${
props.nbAvailable <= 1 props.nbAvailable <= 1
? i18n.t('screens.proxiwash.numAvailable') ? i18n.t('screens.proxiwash.numAvailable')
@ -76,9 +79,9 @@ class ProxiwashListItem extends React.Component<PropsType> {
color={iconColor} color={iconColor}
style={styles.icon} style={styles.icon}
/> />
<View style={{justifyContent: 'center'}}> <View style={styles.textContainer}>
<Text style={styles.text}>{props.title}</Text> <Text style={styles.text}>{props.title}</Text>
<Text style={{color: props.theme.colors.subtitle}}>{subtitle}</Text> <Text style={{ color: props.theme.colors.subtitle }}>{subtitle}</Text>
</View> </View>
</View> </View>
); );

View file

@ -19,8 +19,14 @@
import * as React from 'react'; import * as React from 'react';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import {Image, TouchableWithoutFeedback, View, ViewStyle} from 'react-native'; import {
import {AnimatableProperties} from 'react-native-animatable'; Image,
StyleSheet,
TouchableWithoutFeedback,
View,
ViewStyle,
} from 'react-native';
import { AnimatableProperties } from 'react-native-animatable';
export type AnimatableViewRefType = { export type AnimatableViewRefType = {
current: null | (typeof Animatable.View & View); current: null | (typeof Animatable.View & View);
@ -77,6 +83,34 @@ export enum MASCOT_STYLE {
RANDOM = 999, RANDOM = 999,
} }
const styles = StyleSheet.create({
container: {
aspectRatio: 1,
},
mascot: {
width: '100%',
height: '100%',
},
glassesImage: {
position: 'absolute',
top: '15%',
left: 0,
width: '100%',
height: '100%',
},
eyesImage: {
position: 'absolute',
top: '15%',
width: '100%',
height: '100%',
},
eyesContainer: {
position: 'absolute',
width: '100%',
height: '100%',
},
});
class Mascot extends React.Component<PropsType, StateType> { class Mascot extends React.Component<PropsType, StateType> {
static defaultProps = { static defaultProps = {
emotion: MASCOT_STYLE.NORMAL, emotion: MASCOT_STYLE.NORMAL,
@ -100,9 +134,9 @@ class Mascot extends React.Component<PropsType, StateType> {
viewRef: AnimatableViewRefType; viewRef: AnimatableViewRefType;
eyeList: {[key in EYE_STYLE]: number}; eyeList: { [key in EYE_STYLE]: number };
glassesList: {[key in GLASSES_STYLE]: number}; glassesList: { [key in GLASSES_STYLE]: number };
onPress: (viewRef: AnimatableViewRefType) => void; onPress: (viewRef: AnimatableViewRefType) => void;
@ -141,9 +175,9 @@ class Mascot extends React.Component<PropsType, StateType> {
this.onPress = (viewRef: AnimatableViewRefType) => { this.onPress = (viewRef: AnimatableViewRefType) => {
const ref = viewRef.current; const ref = viewRef.current;
if (ref && ref.rubberBand) { if (ref && ref.rubberBand) {
this.setState({currentEmotion: MASCOT_STYLE.LOVE}); this.setState({ currentEmotion: MASCOT_STYLE.LOVE });
ref.rubberBand(1500).then(() => { ref.rubberBand(1500).then(() => {
this.setState({currentEmotion: this.initialEmotion}); this.setState({ currentEmotion: this.initialEmotion });
}); });
} }
}; };
@ -155,9 +189,9 @@ class Mascot extends React.Component<PropsType, StateType> {
this.onLongPress = (viewRef: AnimatableViewRefType) => { this.onLongPress = (viewRef: AnimatableViewRefType) => {
const ref = viewRef.current; const ref = viewRef.current;
if (ref && ref.tada) { if (ref && ref.tada) {
this.setState({currentEmotion: MASCOT_STYLE.ANGRY}); this.setState({ currentEmotion: MASCOT_STYLE.ANGRY });
ref.tada(1000).then(() => { ref.tada(1000).then(() => {
this.setState({currentEmotion: this.initialEmotion}); this.setState({ currentEmotion: this.initialEmotion });
}); });
} }
}; };
@ -174,30 +208,22 @@ class Mascot extends React.Component<PropsType, StateType> {
source={ source={
glasses != null ? glasses : this.glassesList[GLASSES_STYLE.NORMAL] glasses != null ? glasses : this.glassesList[GLASSES_STYLE.NORMAL]
} }
style={{ style={styles.glassesImage}
position: 'absolute',
top: '15%',
left: 0,
width: '100%',
height: '100%',
}}
/> />
); );
} }
getEye(style: EYE_STYLE, isRight: boolean, rotation: string = '0deg') { getEye(style: EYE_STYLE, isRight: boolean, rotation: string = '0deg') {
const eye = this.eyeList[style]; const eye = this.eyeList[style];
const left = isRight ? '-11%' : '11%';
return ( return (
<Image <Image
key={isRight ? 'right' : 'left'} key={isRight ? 'right' : 'left'}
source={eye != null ? eye : this.eyeList[EYE_STYLE.NORMAL]} source={eye != null ? eye : this.eyeList[EYE_STYLE.NORMAL]}
style={{ style={{
position: 'absolute', ...styles.eyesImage,
top: '15%', left: left,
left: isRight ? '-11%' : '11%', transform: [{ rotateY: rotation }],
width: '100%',
height: '100%',
transform: [{rotateY: rotation}],
}} }}
/> />
); );
@ -205,16 +231,7 @@ class Mascot extends React.Component<PropsType, StateType> {
getEyes(emotion: MASCOT_STYLE) { getEyes(emotion: MASCOT_STYLE) {
const final = []; const final = [];
final.push( final.push(<View key="container" style={styles.eyesContainer} />);
<View
key="container"
style={{
position: 'absolute',
width: '100%',
height: '100%',
}}
/>,
);
if (emotion === MASCOT_STYLE.CUTE) { if (emotion === MASCOT_STYLE.CUTE) {
final.push(this.getEye(EYE_STYLE.CUTE, true)); final.push(this.getEye(EYE_STYLE.CUTE, true));
final.push(this.getEye(EYE_STYLE.CUTE, false)); final.push(this.getEye(EYE_STYLE.CUTE, false));
@ -249,32 +266,28 @@ class Mascot extends React.Component<PropsType, StateType> {
} }
render() { render() {
const {props, state} = this; const { props, state } = this;
const entryAnimation = props.animated ? props.entryAnimation : null; const entryAnimation = props.animated ? props.entryAnimation : null;
const loopAnimation = props.animated ? props.loopAnimation : null; const loopAnimation = props.animated ? props.loopAnimation : null;
return ( return (
<Animatable.View <Animatable.View
style={{ style={{
aspectRatio: 1, ...styles.container,
...props.style, ...props.style,
}} }}
{...entryAnimation}> {...entryAnimation}
>
<TouchableWithoutFeedback <TouchableWithoutFeedback
onPress={() => { onPress={() => {
this.onPress(this.viewRef); this.onPress(this.viewRef);
}} }}
onLongPress={() => { onLongPress={() => {
this.onLongPress(this.viewRef); this.onLongPress(this.viewRef);
}}> }}
>
<Animatable.View ref={this.viewRef}> <Animatable.View ref={this.viewRef}>
<Animatable.View {...loopAnimation}> <Animatable.View {...loopAnimation}>
<Image <Image source={MASCOT_IMAGE} style={styles.mascot} />
source={MASCOT_IMAGE}
style={{
width: '100%',
height: '100%',
}}
/>
{this.getEyes(state.currentEmotion)} {this.getEyes(state.currentEmotion)}
</Animatable.View> </Animatable.View>
</Animatable.View> </Animatable.View>

View file

@ -31,12 +31,14 @@ import {
BackHandler, BackHandler,
Dimensions, Dimensions,
ScrollView, ScrollView,
StyleSheet,
TouchableWithoutFeedback, TouchableWithoutFeedback,
View, View,
} from 'react-native'; } from 'react-native';
import Mascot from './Mascot'; import Mascot from './Mascot';
import SpeechArrow from './SpeechArrow'; import SpeechArrow from './SpeechArrow';
import AsyncStorageManager from '../../managers/AsyncStorageManager'; import AsyncStorageManager from '../../managers/AsyncStorageManager';
import GENERAL_STYLES from '../../constants/Styles';
type PropsType = { type PropsType = {
theme: ReactNativePaper.Theme; theme: ReactNativePaper.Theme;
@ -67,6 +69,41 @@ type StateType = {
dialogVisible: boolean; dialogVisible: boolean;
}; };
const styles = StyleSheet.create({
speechBubbleContainer: {
marginLeft: '10%',
marginRight: '10%',
},
speechBubbleCard: {
borderWidth: 4,
borderRadius: 10,
},
speechBubbleIcon: {
backgroundColor: 'transparent',
},
speechBubbleText: {
marginBottom: 10,
},
actionsContainer: {
marginTop: 10,
marginBottom: 10,
},
button: {
...GENERAL_STYLES.centerHorizontal,
marginBottom: 10,
},
background: {
position: 'absolute',
backgroundColor: 'rgba(0,0,0,0.7)',
width: '100%',
height: '100%',
},
container: {
marginTop: -80,
width: '100%',
},
});
/** /**
* Component used to display a popup with the mascot. * Component used to display a popup with the mascot.
*/ */
@ -107,12 +144,13 @@ class MascotPopup extends React.Component<PropsType, StateType> {
componentDidMount() { componentDidMount() {
BackHandler.addEventListener( BackHandler.addEventListener(
'hardwareBackPress', 'hardwareBackPress',
this.onBackButtonPressAndroid, this.onBackButtonPressAndroid
); );
} }
shouldComponentUpdate(nextProps: PropsType, nextState: StateType): boolean { shouldComponentUpdate(nextProps: PropsType, nextState: StateType): boolean {
const {props, state} = this; // TODO this is so dirty it shouldn't even work
const { props, state } = this;
if (nextProps.visible) { if (nextProps.visible) {
this.state.shouldRenderDialog = true; this.state.shouldRenderDialog = true;
this.state.dialogVisible = true; this.state.dialogVisible = true;
@ -134,10 +172,10 @@ class MascotPopup extends React.Component<PropsType, StateType> {
}; };
onBackButtonPressAndroid = (): boolean => { onBackButtonPressAndroid = (): boolean => {
const {state, props} = this; const { state, props } = this;
if (state.dialogVisible) { if (state.dialogVisible) {
const {cancel} = props.buttons; const { cancel } = props.buttons;
const {action} = props.buttons; const { action } = props.buttons;
if (cancel) { if (cancel) {
this.onDismiss(cancel.onPress); this.onDismiss(cancel.onPress);
} else if (action) { } else if (action) {
@ -152,27 +190,25 @@ class MascotPopup extends React.Component<PropsType, StateType> {
}; };
getSpeechBubble() { getSpeechBubble() {
const {state, props} = this; const { state, props } = this;
return ( return (
<Animatable.View <Animatable.View
style={{ style={styles.speechBubbleContainer}
marginLeft: '10%',
marginRight: '10%',
}}
useNativeDriver useNativeDriver
animation={state.dialogVisible ? 'bounceInLeft' : 'bounceOutLeft'} animation={state.dialogVisible ? 'bounceInLeft' : 'bounceOutLeft'}
duration={state.dialogVisible ? 1000 : 300}> duration={state.dialogVisible ? 1000 : 300}
>
<SpeechArrow <SpeechArrow
style={{marginLeft: this.mascotSize / 3}} style={{ marginLeft: this.mascotSize / 3 }}
size={20} size={20}
color={props.theme.colors.mascotMessageArrow} color={props.theme.colors.mascotMessageArrow}
/> />
<Card <Card
style={{ style={{
borderColor: props.theme.colors.mascotMessageArrow, borderColor: props.theme.colors.mascotMessageArrow,
borderWidth: 4, ...styles.speechBubbleCard,
borderRadius: 10, }}
}}> >
<Card.Title <Card.Title
title={props.title} title={props.title}
left={ left={
@ -180,7 +216,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
? () => ( ? () => (
<Avatar.Icon <Avatar.Icon
size={48} size={48}
style={{backgroundColor: 'transparent'}} style={styles.speechBubbleIcon}
color={props.theme.colors.primary} color={props.theme.colors.primary}
icon={props.icon} icon={props.icon}
/> />
@ -191,13 +227,16 @@ class MascotPopup extends React.Component<PropsType, StateType> {
<Card.Content <Card.Content
style={{ style={{
maxHeight: this.windowHeight / 3, maxHeight: this.windowHeight / 3,
}}> }}
>
<ScrollView> <ScrollView>
<Paragraph style={{marginBottom: 10}}>{props.message}</Paragraph> <Paragraph style={styles.speechBubbleText}>
{props.message}
</Paragraph>
</ScrollView> </ScrollView>
</Card.Content> </Card.Content>
<Card.Actions style={{marginTop: 10, marginBottom: 10}}> <Card.Actions style={styles.actionsContainer}>
{this.getButtons()} {this.getButtons()}
</Card.Actions> </Card.Actions>
</Card> </Card>
@ -206,14 +245,15 @@ class MascotPopup extends React.Component<PropsType, StateType> {
} }
getMascot() { getMascot() {
const {props, state} = this; const { props, state } = this;
return ( return (
<Animatable.View <Animatable.View
useNativeDriver useNativeDriver
animation={state.dialogVisible ? 'bounceInLeft' : 'bounceOutLeft'} animation={state.dialogVisible ? 'bounceInLeft' : 'bounceOutLeft'}
duration={state.dialogVisible ? 1500 : 200}> duration={state.dialogVisible ? 1500 : 200}
>
<Mascot <Mascot
style={{width: this.mascotSize}} style={{ width: this.mascotSize }}
animated animated
emotion={props.emotion} emotion={props.emotion}
/> />
@ -222,45 +262,34 @@ class MascotPopup extends React.Component<PropsType, StateType> {
} }
getButtons() { getButtons() {
const {props} = this; const { props } = this;
const {action} = props.buttons; const { action } = props.buttons;
const {cancel} = props.buttons; const { cancel } = props.buttons;
return ( return (
<View <View style={GENERAL_STYLES.center}>
style={{
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 'auto',
marginBottom: 'auto',
}}>
{action != null ? ( {action != null ? (
<Button <Button
style={{ style={styles.button}
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 10,
}}
mode="contained" mode="contained"
icon={action.icon} icon={action.icon}
color={action.color} color={action.color}
onPress={() => { onPress={() => {
this.onDismiss(action.onPress); this.onDismiss(action.onPress);
}}> }}
>
{action.message} {action.message}
</Button> </Button>
) : null} ) : null}
{cancel != null ? ( {cancel != null ? (
<Button <Button
style={{ style={styles.button}
marginLeft: 'auto',
marginRight: 'auto',
}}
mode="contained" mode="contained"
icon={cancel.icon} icon={cancel.icon}
color={cancel.color} color={cancel.color}
onPress={() => { onPress={() => {
this.onDismiss(cancel.onPress); this.onDismiss(cancel.onPress);
}}> }}
>
{cancel.message} {cancel.message}
</Button> </Button>
) : null} ) : null}
@ -269,19 +298,15 @@ class MascotPopup extends React.Component<PropsType, StateType> {
} }
getBackground() { getBackground() {
const {props, state} = this; const { props, state } = this;
return ( return (
<TouchableWithoutFeedback <TouchableWithoutFeedback
onPress={() => { onPress={() => {
this.onDismiss(props.buttons.cancel?.onPress); this.onDismiss(props.buttons.cancel?.onPress);
}}> }}
>
<Animatable.View <Animatable.View
style={{ style={styles.background}
position: 'absolute',
backgroundColor: 'rgba(0,0,0,0.7)',
width: '100%',
height: '100%',
}}
useNativeDriver useNativeDriver
animation={state.dialogVisible ? 'fadeIn' : 'fadeOut'} animation={state.dialogVisible ? 'fadeIn' : 'fadeOut'}
duration={state.dialogVisible ? 300 : 300} duration={state.dialogVisible ? 300 : 300}
@ -291,10 +316,10 @@ class MascotPopup extends React.Component<PropsType, StateType> {
} }
onDismiss = (callback?: () => void) => { onDismiss = (callback?: () => void) => {
const {prefKey} = this.props; const { prefKey } = this.props;
if (prefKey != null) { if (prefKey != null) {
AsyncStorageManager.set(prefKey, false); AsyncStorageManager.set(prefKey, false);
this.setState({dialogVisible: false}); this.setState({ dialogVisible: false });
} }
if (callback != null) { if (callback != null) {
callback(); callback();
@ -302,21 +327,13 @@ class MascotPopup extends React.Component<PropsType, StateType> {
}; };
render() { render() {
const {shouldRenderDialog} = this.state; const { shouldRenderDialog } = this.state;
if (shouldRenderDialog) { if (shouldRenderDialog) {
return ( return (
<Portal> <Portal>
{this.getBackground()} {this.getBackground()}
<View <View style={GENERAL_STYLES.centerVertical}>
style={{ <View style={styles.container}>
marginTop: 'auto',
marginBottom: 'auto',
}}>
<View
style={{
marginTop: -80,
width: '100%',
}}>
{this.getMascot()} {this.getMascot()}
{this.getSpeechBubble()} {this.getSpeechBubble()}
</View> </View>

View file

@ -18,7 +18,7 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {View, ViewStyle} from 'react-native'; import { StyleSheet, View, ViewStyle } from 'react-native';
type PropsType = { type PropsType = {
style?: ViewStyle; style?: ViewStyle;
@ -26,20 +26,26 @@ type PropsType = {
color: string; color: string;
}; };
const styles = StyleSheet.create({
arrow: {
width: 0,
height: 0,
borderLeftWidth: 0,
borderStyle: 'solid',
backgroundColor: 'transparent',
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
},
});
export default function SpeechArrow(props: PropsType) { export default function SpeechArrow(props: PropsType) {
return ( return (
<View style={props.style}> <View style={props.style}>
<View <View
style={{ style={{
width: 0, ...styles.arrow,
height: 0,
borderLeftWidth: 0,
borderRightWidth: props.size, borderRightWidth: props.size,
borderBottomWidth: props.size, borderBottomWidth: props.size,
borderStyle: 'solid',
backgroundColor: 'transparent',
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: props.color, borderBottomColor: props.color,
}} }}
/> />

View file

@ -18,32 +18,36 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {TouchableRipple} from 'react-native-paper'; import { TouchableRipple } from 'react-native-paper';
import {Image} from 'react-native-animatable'; import { Image } from 'react-native-animatable';
import {useNavigation} from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import {ViewStyle} from 'react-native'; import { StyleSheet, ViewStyle } from 'react-native';
type PropsType = { type PropsType = {
images: Array<{url: string}>; images: Array<{ url: string }>;
style: ViewStyle; style: ViewStyle;
}; };
const styles = StyleSheet.create({
image: {
width: '100%',
height: '100%',
},
});
function ImageGalleryButton(props: PropsType) { function ImageGalleryButton(props: PropsType) {
const navigation = useNavigation(); const navigation = useNavigation();
const onPress = () => { const onPress = () => {
navigation.navigate('gallery', {images: props.images}); navigation.navigate('gallery', { images: props.images });
}; };
return ( return (
<TouchableRipple onPress={onPress} style={props.style}> <TouchableRipple onPress={onPress} style={props.style}>
<Image <Image
resizeMode="contain" resizeMode="contain"
source={{uri: props.images[0].url}} source={{ uri: props.images[0].url }}
style={{ style={styles.image}
width: '100%',
height: '100%',
}}
/> />
</TouchableRipple> </TouchableRipple>
); );

View file

@ -18,9 +18,10 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native'; import { View } from 'react-native';
import {useTheme} from 'react-native-paper'; import { useTheme } from 'react-native-paper';
import {Agenda, AgendaProps} from 'react-native-calendars'; import { Agenda, AgendaProps } from 'react-native-calendars';
import GENERAL_STYLES from '../../constants/Styles';
type PropsType = { type PropsType = {
onRef: (ref: Agenda<any>) => void; onRef: (ref: Agenda<any>) => void;
@ -67,7 +68,7 @@ function CustomAgenda(props: PropsType) {
// Completely recreate the component on theme change to force theme reload // Completely recreate the component on theme change to force theme reload
if (theme.dark) { if (theme.dark) {
return <View style={{flex: 1}}>{getAgenda()}</View>; return <View style={GENERAL_STYLES.flex}>{getAgenda()}</View>;
} }
return getAgenda(); return getAgenda();
} }

View file

@ -18,9 +18,9 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Text} from 'react-native-paper'; import { Text } from 'react-native-paper';
import HTML from 'react-native-render-html'; import HTML from 'react-native-render-html';
import {GestureResponderEvent, Linking} from 'react-native'; import { GestureResponderEvent, Linking } from 'react-native';
type PropsType = { type PropsType = {
html: string; html: string;
@ -38,7 +38,7 @@ function CustomHTML(props: PropsType) {
htmlAttribs: any, htmlAttribs: any,
children: any, children: any,
convertedCSSStyles: any, convertedCSSStyles: any,
passProps: any, passProps: any
) => { ) => {
return <Text {...passProps}>{children}</Text>; return <Text {...passProps}>{children}</Text>;
}; };

View file

@ -25,7 +25,7 @@ import {
HeaderButtons, HeaderButtons,
HeaderButtonsProps, HeaderButtonsProps,
} from 'react-navigation-header-buttons'; } from 'react-navigation-header-buttons';
import {useTheme} from 'react-native-paper'; import { useTheme } from 'react-native-paper';
const MaterialHeaderButton = (props: HeaderButtonProps) => { const MaterialHeaderButton = (props: HeaderButtonProps) => {
const theme = useTheme(); const theme = useTheme();
@ -40,7 +40,7 @@ const MaterialHeaderButton = (props: HeaderButtonProps) => {
}; };
const MaterialHeaderButtons = ( const MaterialHeaderButtons = (
props: HeaderButtonsProps & {children?: React.ReactNode}, props: HeaderButtonsProps & { children?: React.ReactNode }
) => { ) => {
return ( return (
<HeaderButtons {...props} HeaderButtonComponent={MaterialHeaderButton} /> <HeaderButtons {...props} HeaderButtonComponent={MaterialHeaderButton} />
@ -49,4 +49,4 @@ const MaterialHeaderButtons = (
export default MaterialHeaderButtons; export default MaterialHeaderButtons;
export {Item} from 'react-navigation-header-buttons'; export { Item } from 'react-navigation-header-buttons';

View file

@ -30,13 +30,14 @@ import i18n from 'i18n-js';
import AppIntroSlider from 'react-native-app-intro-slider'; import AppIntroSlider from 'react-native-app-intro-slider';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import {Card} from 'react-native-paper'; import { Card } from 'react-native-paper';
import Update from '../../constants/Update'; import Update from '../../constants/Update';
import ThemeManager from '../../managers/ThemeManager'; import ThemeManager from '../../managers/ThemeManager';
import Mascot, {MASCOT_STYLE} from '../Mascot/Mascot'; import Mascot, { MASCOT_STYLE } from '../Mascot/Mascot';
import MascotIntroWelcome from '../Intro/MascotIntroWelcome'; import MascotIntroWelcome from '../Intro/MascotIntroWelcome';
import IntroIcon from '../Intro/IconIntro'; import IntroIcon from '../Intro/IconIntro';
import MascotIntroEnd from '../Intro/MascotIntroEnd'; import MascotIntroEnd from '../Intro/MascotIntroEnd';
import GENERAL_STYLES from '../../constants/Styles';
type PropsType = { type PropsType = {
onDone: () => void; onDone: () => void;
@ -75,11 +76,42 @@ const styles = StyleSheet.create({
textAlign: 'center', textAlign: 'center',
marginBottom: 16, marginBottom: 16,
}, },
center: { mascot: {
marginTop: 'auto', marginLeft: 30,
marginBottom: 'auto', marginBottom: 0,
marginRight: 'auto', width: 100,
marginLeft: 'auto', marginTop: -30,
},
speechArrow: {
marginLeft: 50,
width: 0,
height: 0,
borderLeftWidth: 20,
borderRightWidth: 0,
borderBottomWidth: 20,
borderStyle: 'solid',
backgroundColor: 'transparent',
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: 'rgba(0,0,0,0.60)',
},
card: {
backgroundColor: 'rgba(0,0,0,0.38)',
marginHorizontal: 20,
borderColor: 'rgba(0,0,0,0.60)',
borderWidth: 4,
borderRadius: 10,
elevation: 0,
},
nextButtonContainer: {
borderRadius: 25,
padding: 5,
backgroundColor: 'rgba(0,0,0,0.2)',
},
doneButtonContainer: {
borderRadius: 25,
padding: 5,
backgroundColor: 'rgb(190,21,34)',
}, },
}); });
@ -90,7 +122,7 @@ export default class CustomIntroSlider extends React.Component<
PropsType, PropsType,
StateType StateType
> { > {
sliderRef: {current: null | AppIntroSlider}; sliderRef: { current: null | AppIntroSlider };
introSlides: Array<IntroSlideType>; introSlides: Array<IntroSlideType>;
@ -173,31 +205,27 @@ export default class CustomIntroSlider extends React.Component<
getIntroRenderItem = ( getIntroRenderItem = (
data: data:
| (ListRenderItemInfo<IntroSlideType> & { | (ListRenderItemInfo<IntroSlideType> & {
dimensions: {width: number; height: number}; dimensions: { width: number; height: number };
}) })
| ListRenderItemInfo<IntroSlideType>, | ListRenderItemInfo<IntroSlideType>
) => { ) => {
const item = data.item; const item = data.item;
const {state} = this; const { state } = this;
const index = parseInt(item.key, 10); const index = parseInt(item.key, 10);
return ( return (
<LinearGradient <LinearGradient
style={[styles.mainContent]} style={[styles.mainContent]}
colors={item.colors} colors={item.colors}
start={{x: 0, y: 0.1}} start={{ x: 0, y: 0.1 }}
end={{x: 0.1, y: 1}}> end={{ x: 0.1, y: 1 }}
>
{state.currentSlide === index ? ( {state.currentSlide === index ? (
<View style={{height: '100%', flex: 1}}> <View style={GENERAL_STYLES.flex}>
<View style={{flex: 1}}>{item.view()}</View> <View style={GENERAL_STYLES.flex}>{item.view()}</View>
<Animatable.View useNativeDriver animation="fadeIn"> <Animatable.View useNativeDriver animation="fadeIn">
{item.mascotStyle != null ? ( {item.mascotStyle != null ? (
<Mascot <Mascot
style={{ style={styles.mascot}
marginLeft: 30,
marginBottom: 0,
width: 100,
marginTop: -30,
}}
emotion={item.mascotStyle} emotion={item.mascotStyle}
animated animated
entryAnimation={{ entryAnimation={{
@ -211,43 +239,23 @@ export default class CustomIntroSlider extends React.Component<
}} }}
/> />
) : null} ) : null}
<View <View style={styles.speechArrow} />
style={{ <Card style={styles.card}>
marginLeft: 50,
width: 0,
height: 0,
borderLeftWidth: 20,
borderRightWidth: 0,
borderBottomWidth: 20,
borderStyle: 'solid',
backgroundColor: 'transparent',
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: 'rgba(0,0,0,0.60)',
}}
/>
<Card
style={{
backgroundColor: 'rgba(0,0,0,0.38)',
marginHorizontal: 20,
borderColor: 'rgba(0,0,0,0.60)',
borderWidth: 4,
borderRadius: 10,
elevation: 0,
}}>
<Card.Content> <Card.Content>
<Animatable.Text <Animatable.Text
useNativeDriver useNativeDriver
animation="fadeIn" animation="fadeIn"
delay={100} delay={100}
style={styles.title}> style={styles.title}
>
{item.title} {item.title}
</Animatable.Text> </Animatable.Text>
<Animatable.Text <Animatable.Text
useNativeDriver useNativeDriver
animation="fadeIn" animation="fadeIn"
delay={200} delay={200}
style={styles.text}> style={styles.text}
>
{item.text} {item.text}
</Animatable.Text> </Animatable.Text>
</Card.Content> </Card.Content>
@ -267,12 +275,12 @@ export default class CustomIntroSlider extends React.Component<
onSlideChange = (index: number) => { onSlideChange = (index: number) => {
CustomIntroSlider.setStatusBarColor(this.currentSlides[index].colors[0]); CustomIntroSlider.setStatusBarColor(this.currentSlides[index].colors[0]);
this.setState({currentSlide: index}); this.setState({ currentSlide: index });
}; };
onSkip = () => { onSkip = () => {
CustomIntroSlider.setStatusBarColor( CustomIntroSlider.setStatusBarColor(
this.currentSlides[this.currentSlides.length - 1].colors[0], this.currentSlides[this.currentSlides.length - 1].colors[0]
); );
if (this.sliderRef.current != null) { if (this.sliderRef.current != null) {
this.sliderRef.current.goToSlide(this.currentSlides.length - 1); this.sliderRef.current.goToSlide(this.currentSlides.length - 1);
@ -280,9 +288,9 @@ export default class CustomIntroSlider extends React.Component<
}; };
onDone = () => { onDone = () => {
const {props} = this; const { props } = this;
CustomIntroSlider.setStatusBarColor( CustomIntroSlider.setStatusBarColor(
ThemeManager.getCurrentTheme().colors.surface, ThemeManager.getCurrentTheme().colors.surface
); );
props.onDone(); props.onDone();
}; };
@ -292,11 +300,8 @@ export default class CustomIntroSlider extends React.Component<
<Animatable.View <Animatable.View
useNativeDriver useNativeDriver
animation="fadeIn" animation="fadeIn"
style={{ style={styles.nextButtonContainer}
borderRadius: 25, >
padding: 5,
backgroundColor: 'rgba(0,0,0,0.2)',
}}>
<MaterialCommunityIcons name="arrow-right" color="#fff" size={40} /> <MaterialCommunityIcons name="arrow-right" color="#fff" size={40} />
</Animatable.View> </Animatable.View>
); );
@ -307,18 +312,15 @@ export default class CustomIntroSlider extends React.Component<
<Animatable.View <Animatable.View
useNativeDriver useNativeDriver
animation="bounceIn" animation="bounceIn"
style={{ style={styles.doneButtonContainer}
borderRadius: 25, >
padding: 5,
backgroundColor: 'rgb(190,21,34)',
}}>
<MaterialCommunityIcons name="check" color="#fff" size={40} /> <MaterialCommunityIcons name="check" color="#fff" size={40} />
</Animatable.View> </Animatable.View>
); );
}; };
render() { render() {
const {props, state} = this; const { props, state } = this;
this.currentSlides = this.introSlides; this.currentSlides = this.introSlides;
if (props.isUpdate) { if (props.isUpdate) {
this.currentSlides = this.updateSlides; this.currentSlides = this.updateSlides;

View file

@ -18,9 +18,9 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {useTheme} from 'react-native-paper'; import { useTheme } from 'react-native-paper';
import {Modalize} from 'react-native-modalize'; import { Modalize } from 'react-native-modalize';
import {View} from 'react-native-animatable'; import { View } from 'react-native-animatable';
import CustomTabBar from '../Tabbar/CustomTabBar'; import CustomTabBar from '../Tabbar/CustomTabBar';
/** /**
@ -34,18 +34,20 @@ function CustomModal(props: {
children?: React.ReactNode; children?: React.ReactNode;
}) { }) {
const theme = useTheme(); const theme = useTheme();
const {onRef, children} = props; const { onRef, children } = props;
return ( return (
<Modalize <Modalize
ref={onRef} ref={onRef}
adjustToContentHeight adjustToContentHeight
handlePosition="inside" handlePosition="inside"
modalStyle={{backgroundColor: theme.colors.card}} modalStyle={{ backgroundColor: theme.colors.card }}
handleStyle={{backgroundColor: theme.colors.primary}}> handleStyle={{ backgroundColor: theme.colors.primary }}
>
<View <View
style={{ style={{
paddingBottom: CustomTabBar.TAB_BAR_HEIGHT, paddingBottom: CustomTabBar.TAB_BAR_HEIGHT,
}}> }}
>
{children} {children}
</View> </View>
</Modalize> </Modalize>

View file

@ -18,15 +18,28 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Text} from 'react-native-paper'; import { Text } from 'react-native-paper';
import {View} from 'react-native-animatable'; import { View } from 'react-native-animatable';
import Slider, {SliderProps} from '@react-native-community/slider'; import Slider, { SliderProps } from '@react-native-community/slider';
import {useState} from 'react'; import { useState } from 'react';
import { StyleSheet } from 'react-native';
type PropsType = { type PropsType = {
valueSuffix?: string; valueSuffix?: string;
} & SliderProps; } & SliderProps;
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
},
text: {
marginHorizontal: 10,
marginTop: 'auto',
marginBottom: 'auto',
},
});
/** /**
* Abstraction layer for Modalize component, using custom configuration * Abstraction layer for Modalize component, using custom configuration
* *
@ -44,15 +57,8 @@ function CustomSlider(props: PropsType) {
}; };
return ( return (
<View style={{flex: 1, flexDirection: 'row'}}> <View style={styles.container}>
<Text <Text style={styles.text}>{currentValue}min</Text>
style={{
marginHorizontal: 10,
marginTop: 'auto',
marginBottom: 'auto',
}}>
{currentValue}min
</Text>
<Slider {...props} ref={undefined} onValueChange={onValueChange} /> <Slider {...props} ref={undefined} onValueChange={onValueChange} />
</View> </View>
); );

View file

@ -17,16 +17,24 @@
* 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 {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import {ActivityIndicator, useTheme} from 'react-native-paper'; import { ActivityIndicator, useTheme } from 'react-native-paper';
type Props = { type Props = {
isAbsolute?: boolean; isAbsolute?: boolean;
}; };
const styles = StyleSheet.create({
container: {
top: 0,
right: 0,
width: '100%',
height: '100%',
justifyContent: 'center',
},
});
/** /**
* Component used to display a header button * Component used to display a header button
* *
@ -35,18 +43,16 @@ type Props = {
*/ */
export default function BasicLoadingScreen(props: Props) { export default function BasicLoadingScreen(props: Props) {
const theme = useTheme(); const theme = useTheme();
const {isAbsolute} = props; const { isAbsolute } = props;
const position = isAbsolute ? 'absolute' : 'relative';
return ( return (
<View <View
style={{ style={{
backgroundColor: theme.colors.background, backgroundColor: theme.colors.background,
position: isAbsolute ? 'absolute' : 'relative', position: position,
top: 0, ...styles.container,
right: 0, }}
width: '100%', >
height: '100%',
justifyContent: 'center',
}}>
<ActivityIndicator animating size="large" color={theme.colors.primary} /> <ActivityIndicator animating size="large" color={theme.colors.primary} />
</View> </View>
); );

View file

@ -18,18 +18,18 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Button, Subheading, withTheme} from 'react-native-paper'; import { Button, Subheading, withTheme } from 'react-native-paper';
import {StyleSheet, View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import {ERROR_TYPE} from '../../utils/WebData'; import { ERROR_TYPE } from '../../utils/WebData';
type PropsType = { type PropsType = {
navigation?: StackNavigationProp<any>; navigation?: StackNavigationProp<any>;
theme: ReactNativePaper.Theme; theme: ReactNativePaper.Theme;
route?: {name: string}; route?: { name: string };
onRefresh?: () => void; onRefresh?: () => void;
errorCode?: number; errorCode?: number;
icon?: string; icon?: string;
@ -84,13 +84,14 @@ class ErrorView extends React.PureComponent<PropsType> {
} }
getRetryButton() { getRetryButton() {
const {props} = this; const { props } = this;
return ( return (
<Button <Button
mode="contained" mode="contained"
icon="refresh" icon="refresh"
onPress={props.onRefresh} onPress={props.onRefresh}
style={styles.button}> style={styles.button}
>
{i18n.t('general.retry')} {i18n.t('general.retry')}
</Button> </Button>
); );
@ -102,24 +103,25 @@ class ErrorView extends React.PureComponent<PropsType> {
mode="contained" mode="contained"
icon="login" icon="login"
onPress={this.goToLogin} onPress={this.goToLogin}
style={styles.button}> style={styles.button}
>
{i18n.t('screens.login.title')} {i18n.t('screens.login.title')}
</Button> </Button>
); );
} }
goToLogin = () => { goToLogin = () => {
const {props} = this; const { props } = this;
if (props.navigation) { if (props.navigation) {
props.navigation.navigate('login', { props.navigation.navigate('login', {
screen: 'login', screen: 'login',
params: {nextScreen: props.route ? props.route.name : undefined}, params: { nextScreen: props.route ? props.route.name : undefined },
}); });
} }
}; };
generateMessage() { generateMessage() {
const {props} = this; const { props } = this;
this.showLoginButton = false; this.showLoginButton = false;
if (props.errorCode !== 0) { if (props.errorCode !== 0) {
switch (props.errorCode) { switch (props.errorCode) {
@ -171,7 +173,7 @@ class ErrorView extends React.PureComponent<PropsType> {
} }
render() { render() {
const {props} = this; const { props } = this;
this.generateMessage(); this.generateMessage();
let button; let button;
if (this.showLoginButton) { if (this.showLoginButton) {
@ -190,7 +192,8 @@ class ErrorView extends React.PureComponent<PropsType> {
}} }}
animation="zoomIn" animation="zoomIn"
duration={200} duration={200}
useNativeDriver> useNativeDriver
>
<View style={styles.inner}> <View style={styles.inner}>
<View style={styles.iconContainer}> <View style={styles.iconContainer}>
<MaterialCommunityIcons <MaterialCommunityIcons
@ -204,7 +207,8 @@ class ErrorView extends React.PureComponent<PropsType> {
style={{ style={{
...styles.subheading, ...styles.subheading,
color: props.theme.colors.textDisabled, color: props.theme.colors.textDisabled,
}}> }}
>
{this.message} {this.message}
</Subheading> </Subheading>
{button} {button}

View file

@ -19,21 +19,22 @@
import * as React from 'react'; import * as React from 'react';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {Snackbar} from 'react-native-paper'; import { Snackbar } from 'react-native-paper';
import { import {
NativeSyntheticEvent, NativeSyntheticEvent,
RefreshControl, RefreshControl,
SectionListData, SectionListData,
StyleSheet,
View, View,
} from 'react-native'; } from 'react-native';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import {Collapsible} from 'react-navigation-collapsible'; import { Collapsible } from 'react-navigation-collapsible';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import ErrorView from './ErrorView'; import ErrorView from './ErrorView';
import BasicLoadingScreen from './BasicLoadingScreen'; import BasicLoadingScreen from './BasicLoadingScreen';
import withCollapsible from '../../utils/withCollapsible'; import withCollapsible from '../../utils/withCollapsible';
import CustomTabBar from '../Tabbar/CustomTabBar'; import CustomTabBar from '../Tabbar/CustomTabBar';
import {ERROR_TYPE, readData} from '../../utils/WebData'; import { ERROR_TYPE, readData } from '../../utils/WebData';
import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList'; import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
export type SectionListDataType<ItemT> = Array<{ export type SectionListDataType<ItemT> = Array<{
@ -48,10 +49,10 @@ type PropsType<ItemT, RawData> = {
fetchUrl: string; fetchUrl: string;
autoRefreshTime: number; autoRefreshTime: number;
refreshOnFocus: boolean; refreshOnFocus: boolean;
renderItem: (data: {item: ItemT}) => React.ReactNode; renderItem: (data: { item: ItemT }) => React.ReactNode;
createDataset: ( createDataset: (
data: RawData | null, data: RawData | null,
isLoading?: boolean, isLoading?: boolean
) => SectionListDataType<ItemT>; ) => SectionListDataType<ItemT>;
onScroll: (event: NativeSyntheticEvent<EventTarget>) => void; onScroll: (event: NativeSyntheticEvent<EventTarget>) => void;
collapsibleStack: Collapsible; collapsibleStack: Collapsible;
@ -60,11 +61,11 @@ type PropsType<ItemT, RawData> = {
itemHeight?: number | null; itemHeight?: number | null;
updateData?: number; updateData?: number;
renderListHeaderComponent?: ( renderListHeaderComponent?: (
data: RawData | null, data: RawData | null
) => React.ComponentType<any> | React.ReactElement | null; ) => React.ComponentType<any> | React.ReactElement | null;
renderSectionHeader?: ( renderSectionHeader?: (
data: {section: SectionListData<ItemT>}, data: { section: SectionListData<ItemT> },
isLoading?: boolean, isLoading?: boolean
) => React.ReactElement | null; ) => React.ReactElement | null;
stickyHeader?: boolean; stickyHeader?: boolean;
}; };
@ -77,6 +78,12 @@ type StateType<RawData> = {
const MIN_REFRESH_TIME = 5 * 1000; const MIN_REFRESH_TIME = 5 * 1000;
const styles = StyleSheet.create({
container: {
minHeight: '100%',
},
});
/** /**
* Component used to render a SectionList with data fetched from the web * Component used to render a SectionList with data fetched from the web
* *
@ -114,7 +121,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
* Allows to detect when the screen is focused * Allows to detect when the screen is focused
*/ */
componentDidMount() { componentDidMount() {
const {navigation} = this.props; const { navigation } = this.props;
navigation.addListener('focus', this.onScreenFocus); navigation.addListener('focus', this.onScreenFocus);
navigation.addListener('blur', this.onScreenBlur); navigation.addListener('blur', this.onScreenBlur);
this.lastRefresh = undefined; this.lastRefresh = undefined;
@ -125,7 +132,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
* Refreshes data when focusing the screen and setup a refresh interval if asked to * Refreshes data when focusing the screen and setup a refresh interval if asked to
*/ */
onScreenFocus = () => { onScreenFocus = () => {
const {props} = this; const { props } = this;
if (props.refreshOnFocus && this.lastRefresh) { if (props.refreshOnFocus && this.lastRefresh) {
setTimeout(this.onRefresh, 200); setTimeout(this.onRefresh, 200);
} }
@ -173,7 +180,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
* Refreshes data and shows an animations while doing it * Refreshes data and shows an animations while doing it
*/ */
onRefresh = () => { onRefresh = () => {
const {fetchUrl} = this.props; const { fetchUrl } = this.props;
let canRefresh; let canRefresh;
if (this.lastRefresh != null) { if (this.lastRefresh != null) {
const last = this.lastRefresh; const last = this.lastRefresh;
@ -182,7 +189,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
canRefresh = true; canRefresh = true;
} }
if (canRefresh) { if (canRefresh) {
this.setState({refreshing: true}); this.setState({ refreshing: true });
readData(fetchUrl).then(this.onFetchSuccess).catch(this.onFetchError); readData(fetchUrl).then(this.onFetchSuccess).catch(this.onFetchError);
} }
}; };
@ -191,21 +198,21 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
* Shows the error popup * Shows the error popup
*/ */
showSnackBar = () => { showSnackBar = () => {
this.setState({snackbarVisible: true}); this.setState({ snackbarVisible: true });
}; };
/** /**
* Hides the error popup * Hides the error popup
*/ */
hideSnackBar = () => { hideSnackBar = () => {
this.setState({snackbarVisible: false}); this.setState({ snackbarVisible: false });
}; };
getItemLayout = ( getItemLayout = (
height: number, height: number,
data: Array<SectionListData<ItemT>> | null, data: Array<SectionListData<ItemT>> | null,
index: number, index: number
): {length: number; offset: number; index: number} => { ): { length: number; offset: number; index: number } => {
return { return {
length: height, length: height,
offset: height * index, offset: height * index,
@ -213,9 +220,9 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
}; };
}; };
getRenderSectionHeader = (data: {section: SectionListData<ItemT>}) => { getRenderSectionHeader = (data: { section: SectionListData<ItemT> }) => {
const {renderSectionHeader} = this.props; const { renderSectionHeader } = this.props;
const {refreshing} = this.state; const { refreshing } = this.state;
if (renderSectionHeader != null) { if (renderSectionHeader != null) {
return ( return (
<Animatable.View animation="fadeInUp" duration={500} useNativeDriver> <Animatable.View animation="fadeInUp" duration={500} useNativeDriver>
@ -226,8 +233,8 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
return null; return null;
}; };
getRenderItem = (data: {item: ItemT}) => { getRenderItem = (data: { item: ItemT }) => {
const {renderItem} = this.props; const { renderItem } = this.props;
return ( return (
<Animatable.View animation="fadeInUp" duration={500} useNativeDriver> <Animatable.View animation="fadeInUp" duration={500} useNativeDriver>
{renderItem(data)} {renderItem(data)}
@ -236,15 +243,15 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
}; };
onScroll = (event: NativeSyntheticEvent<EventTarget>) => { onScroll = (event: NativeSyntheticEvent<EventTarget>) => {
const {onScroll} = this.props; const { onScroll } = this.props;
if (onScroll != null) { if (onScroll != null) {
onScroll(event); onScroll(event);
} }
}; };
render() { render() {
const {props, state} = this; const { props, state } = this;
const {itemHeight} = props; const { itemHeight } = props;
let dataset: SectionListDataType<ItemT> = []; let dataset: SectionListDataType<ItemT> = [];
if ( if (
state.fetchedData != null || state.fetchedData != null ||
@ -253,7 +260,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
dataset = props.createDataset(state.fetchedData, state.refreshing); dataset = props.createDataset(state.fetchedData, state.refreshing);
} }
const {containerPaddingTop} = props.collapsibleStack; const { containerPaddingTop } = props.collapsibleStack;
return ( return (
<View> <View>
<CollapsibleSectionList <CollapsibleSectionList
@ -269,7 +276,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
renderSectionHeader={this.getRenderSectionHeader} renderSectionHeader={this.getRenderSectionHeader}
renderItem={this.getRenderItem} renderItem={this.getRenderItem}
stickySectionHeadersEnabled={props.stickyHeader} stickySectionHeadersEnabled={props.stickyHeader}
style={{minHeight: '100%'}} style={styles.container}
ListHeaderComponent={ ListHeaderComponent={
props.renderListHeaderComponent != null props.renderListHeaderComponent != null
? props.renderListHeaderComponent(state.fetchedData) ? props.renderListHeaderComponent(state.fetchedData)
@ -304,7 +311,8 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
duration={4000} duration={4000}
style={{ style={{
bottom: CustomTabBar.TAB_BAR_HEIGHT, bottom: CustomTabBar.TAB_BAR_HEIGHT,
}}> }}
>
{i18n.t('general.listUpdateFail')} {i18n.t('general.listUpdateFail')}
</Snackbar> </Snackbar>
</View> </View>

View file

@ -31,14 +31,15 @@ import {
Linking, Linking,
NativeScrollEvent, NativeScrollEvent,
NativeSyntheticEvent, NativeSyntheticEvent,
StyleSheet,
} from 'react-native'; } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import {withTheme} from 'react-native-paper'; import { withTheme } from 'react-native-paper';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import {Collapsible} from 'react-navigation-collapsible'; import { Collapsible } from 'react-navigation-collapsible';
import withCollapsible from '../../utils/withCollapsible'; import withCollapsible from '../../utils/withCollapsible';
import MaterialHeaderButtons, {Item} from '../Overrides/CustomHeaderButton'; import MaterialHeaderButtons, { Item } from '../Overrides/CustomHeaderButton';
import {ERROR_TYPE} from '../../utils/WebData'; import { ERROR_TYPE } from '../../utils/WebData';
import ErrorView from './ErrorView'; import ErrorView from './ErrorView';
import BasicLoadingScreen from './BasicLoadingScreen'; import BasicLoadingScreen from './BasicLoadingScreen';
@ -47,7 +48,7 @@ type PropsType = {
theme: ReactNativePaper.Theme; theme: ReactNativePaper.Theme;
url: string; url: string;
collapsibleStack: Collapsible; collapsibleStack: Collapsible;
onMessage: (event: {nativeEvent: {data: string}}) => void; onMessage: (event: { nativeEvent: { data: string } }) => void;
onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void; onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
customJS?: string; customJS?: string;
customPaddingFunction?: null | ((padding: number) => string); customPaddingFunction?: null | ((padding: number) => string);
@ -56,6 +57,12 @@ type PropsType = {
const AnimatedWebView = Animated.createAnimatedComponent(WebView); const AnimatedWebView = Animated.createAnimatedComponent(WebView);
const styles = StyleSheet.create({
overflow: {
marginHorizontal: 10,
},
});
/** /**
* Class defining a webview screen. * Class defining a webview screen.
*/ */
@ -68,7 +75,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
currentUrl: string; currentUrl: string;
webviewRef: {current: null | WebView}; webviewRef: { current: null | WebView };
canGoBack: boolean; canGoBack: boolean;
@ -83,7 +90,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
* Creates header buttons and listens to events after mounting * Creates header buttons and listens to events after mounting
*/ */
componentDidMount() { componentDidMount() {
const {props} = this; const { props } = this;
props.navigation.setOptions({ props.navigation.setOptions({
headerRight: props.showAdvancedControls headerRight: props.showAdvancedControls
? this.getAdvancedButtons ? this.getAdvancedButtons
@ -92,13 +99,13 @@ class WebViewScreen extends React.PureComponent<PropsType> {
props.navigation.addListener('focus', () => { props.navigation.addListener('focus', () => {
BackHandler.addEventListener( BackHandler.addEventListener(
'hardwareBackPress', 'hardwareBackPress',
this.onBackButtonPressAndroid, this.onBackButtonPressAndroid
); );
}); });
props.navigation.addListener('blur', () => { props.navigation.addListener('blur', () => {
BackHandler.removeEventListener( BackHandler.removeEventListener(
'hardwareBackPress', 'hardwareBackPress',
this.onBackButtonPressAndroid, this.onBackButtonPressAndroid
); );
}); });
} }
@ -145,7 +152,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
* @returns {*} * @returns {*}
*/ */
getAdvancedButtons = () => { getAdvancedButtons = () => {
const {props} = this; const { props } = this;
return ( return (
<MaterialHeaderButtons> <MaterialHeaderButtons>
<Item <Item
@ -154,14 +161,15 @@ class WebViewScreen extends React.PureComponent<PropsType> {
onPress={this.onRefreshClicked} onPress={this.onRefreshClicked}
/> />
<OverflowMenu <OverflowMenu
style={{marginHorizontal: 10}} style={styles.overflow}
OverflowIcon={ OverflowIcon={
<MaterialCommunityIcons <MaterialCommunityIcons
name="dots-vertical" name="dots-vertical"
size={26} size={26}
color={props.theme.colors.text} color={props.theme.colors.text}
/> />
}> }
>
<HiddenItem <HiddenItem
title={i18n.t('general.goBack')} title={i18n.t('general.goBack')}
onPress={this.onGoBackClicked} onPress={this.onGoBackClicked}
@ -195,7 +203,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
* @returns {string} * @returns {string}
*/ */
getJavascriptPadding(padding: number): string { getJavascriptPadding(padding: number): string {
const {props} = this; const { props } = this;
const customPadding = const customPadding =
props.customPaddingFunction != null props.customPaddingFunction != null
? props.customPaddingFunction(padding) ? props.customPaddingFunction(padding)
@ -229,7 +237,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
}; };
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => { onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const {onScroll} = this.props; const { onScroll } = this.props;
if (onScroll) { if (onScroll) {
onScroll(event); onScroll(event);
} }
@ -247,12 +255,15 @@ class WebViewScreen extends React.PureComponent<PropsType> {
}; };
render() { render() {
const {props} = this; const { props } = this;
const {containerPaddingTop, onScrollWithListener} = props.collapsibleStack; const {
containerPaddingTop,
onScrollWithListener,
} = props.collapsibleStack;
return ( return (
<AnimatedWebView <AnimatedWebView
ref={this.webviewRef} ref={this.webviewRef}
source={{uri: props.url}} source={{ uri: props.url }}
startInLoadingState startInLoadingState
injectedJavaScript={props.customJS} injectedJavaScript={props.customJS}
javaScriptEnabled javaScriptEnabled

View file

@ -18,13 +18,13 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Animated} from 'react-native'; import { Animated, StyleSheet } from 'react-native';
import {withTheme} from 'react-native-paper'; import { withTheme } from 'react-native-paper';
import {Collapsible} from 'react-navigation-collapsible'; import { Collapsible } from 'react-navigation-collapsible';
import TabIcon from './TabIcon'; import TabIcon from './TabIcon';
import TabHomeIcon from './TabHomeIcon'; import TabHomeIcon from './TabHomeIcon';
import {BottomTabBarProps} from '@react-navigation/bottom-tabs'; import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
import {NavigationState} from '@react-navigation/native'; import { NavigationState } from '@react-navigation/native';
import { import {
PartialState, PartialState,
Route, Route,
@ -51,6 +51,16 @@ const TAB_ICONS = {
planex: 'clock', planex: 'clock',
}; };
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
width: '100%',
position: 'absolute',
bottom: 0,
left: 0,
},
});
class CustomTabBar extends React.Component<PropsType, StateType> { class CustomTabBar extends React.Component<PropsType, StateType> {
static TAB_BAR_HEIGHT = 48; static TAB_BAR_HEIGHT = 48;
@ -71,7 +81,7 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
* @param destIndex The destination route index * @param destIndex The destination route index
*/ */
onItemPress(route: RouteType, currentIndex: number, destIndex: number) { onItemPress(route: RouteType, currentIndex: number, destIndex: number) {
const {navigation} = this.props; const { navigation } = this.props;
if (currentIndex !== destIndex) { if (currentIndex !== destIndex) {
navigation.navigate(route.name); navigation.navigate(route.name);
} }
@ -83,7 +93,7 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
* @param route * @param route
*/ */
onItemLongPress(route: RouteType) { onItemLongPress(route: RouteType) {
const {navigation} = this.props; const { navigation } = this.props;
if (route.name === 'home') { if (route.name === 'home') {
navigation.navigate('game-start'); navigation.navigate('game-start');
} }
@ -93,7 +103,7 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
* Finds the active route and syncs the tab bar animation with the header bar * Finds the active route and syncs the tab bar animation with the header bar
*/ */
onRouteChange = () => { onRouteChange = () => {
const {props} = this; const { props } = this;
props.state.routes.map(this.syncTabBar); props.state.routes.map(this.syncTabBar);
}; };
@ -122,9 +132,9 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
* @returns {*} * @returns {*}
*/ */
getRenderIcon = (route: RouteType, index: number) => { getRenderIcon = (route: RouteType, index: number) => {
const {props} = this; const { props } = this;
const {state} = props; const { state } = props;
const {options} = props.descriptors[route.key]; const { options } = props.descriptors[route.key];
let label; let label;
if (options.tabBarLabel != null) { if (options.tabBarLabel != null) {
label = options.tabBarLabel; label = options.tabBarLabel;
@ -171,12 +181,12 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
}; };
getIcons() { getIcons() {
const {props} = this; const { props } = this;
return props.state.routes.map(this.getRenderIcon); return props.state.routes.map(this.getRenderIcon);
} }
syncTabBar = (route: RouteType, index: number) => { syncTabBar = (route: RouteType, index: number) => {
const {state} = this.props; const { state } = this.props;
const isFocused = state.index === index; const isFocused = state.index === index;
if (isFocused) { if (isFocused) {
const stackState = route.state; const stackState = route.state;
@ -184,8 +194,8 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
stackState && stackState.index != null stackState && stackState.index != null
? stackState.routes[stackState.index] ? stackState.routes[stackState.index]
: null; : null;
const params: {collapsible: Collapsible} | null | undefined = stackRoute const params: { collapsible: Collapsible } | null | undefined = stackRoute
? (stackRoute.params as {collapsible: Collapsible}) ? (stackRoute.params as { collapsible: Collapsible })
: null; : null;
const collapsible = params != null ? params.collapsible : null; const collapsible = params != null ? params.collapsible : null;
if (collapsible != null) { if (collapsible != null) {
@ -197,20 +207,17 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
}; };
render() { render() {
const {props, state} = this; const { props, state } = this;
const icons = this.getIcons(); const icons = this.getIcons();
return ( return (
<Animated.View <Animated.View
style={{ style={{
flexDirection: 'row',
height: CustomTabBar.TAB_BAR_HEIGHT, height: CustomTabBar.TAB_BAR_HEIGHT,
width: '100%',
position: 'absolute',
bottom: 0,
left: 0,
backgroundColor: props.theme.colors.surface, backgroundColor: props.theme.colors.surface,
transform: [{translateY: state.translateY}], transform: [{ translateY: state.translateY }],
}}> ...styles.container,
}}
>
{icons} {icons}
</Animated.View> </Animated.View>
); );

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Image, View} from 'react-native'; import { Image, StyleSheet, View } from 'react-native';
import {FAB} from 'react-native-paper'; import { FAB } from 'react-native-paper';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
const FOCUSED_ICON = require('../../../assets/tab-icon.png'); const FOCUSED_ICON = require('../../../assets/tab-icon.png');
const UNFOCUSED_ICON = require('../../../assets/tab-icon-outline.png'); const UNFOCUSED_ICON = require('../../../assets/tab-icon-outline.png');
@ -33,6 +33,25 @@ type PropsType = {
const AnimatedFAB = Animatable.createAnimatableComponent(FAB); const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
subcontainer: {
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
marginBottom: -15,
},
fab: {
marginTop: 15,
marginLeft: 'auto',
marginRight: 'auto',
},
});
/** /**
* Abstraction layer for Agenda component, using custom configuration * Abstraction layer for Agenda component, using custom configuration
*/ */
@ -70,12 +89,12 @@ class TabHomeIcon extends React.Component<PropsType> {
} }
shouldComponentUpdate(nextProps: PropsType): boolean { shouldComponentUpdate(nextProps: PropsType): boolean {
const {focused} = this.props; const { focused } = this.props;
return nextProps.focused !== focused; return nextProps.focused !== focused;
} }
getIconRender = ({size, color}: {size: number; color: string}) => { getIconRender = ({ size, color }: { size: number; color: string }) => {
const {focused} = this.props; const { focused } = this.props;
return ( return (
<Image <Image
source={focused ? FOCUSED_ICON : UNFOCUSED_ICON} source={focused ? FOCUSED_ICON : UNFOCUSED_ICON}
@ -89,22 +108,15 @@ class TabHomeIcon extends React.Component<PropsType> {
}; };
render() { render() {
const {props} = this; const { props } = this;
return ( return (
<View <View style={styles.container}>
style={{
flex: 1,
justifyContent: 'center',
}}>
<View <View
style={{ style={{
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
height: props.tabBarHeight + 30, height: props.tabBarHeight + 30,
marginBottom: -15, ...styles.subcontainer,
}}> }}
>
<AnimatedFAB <AnimatedFAB
duration={200} duration={200}
easing="ease-out" easing="ease-out"
@ -112,11 +124,7 @@ class TabHomeIcon extends React.Component<PropsType> {
icon={this.getIconRender} icon={this.getIconRender}
onPress={props.onPress} onPress={props.onPress}
onLongPress={props.onLongPress} onLongPress={props.onLongPress}
style={{ style={styles.fab}
marginTop: 15,
marginLeft: 'auto',
marginRight: 'auto',
}}
/> />
</View> </View>
</View> </View>

View file

@ -18,10 +18,11 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import {TouchableRipple, withTheme} from 'react-native-paper'; import { TouchableRipple, withTheme } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import GENERAL_STYLES from '../../constants/Styles';
type PropsType = { type PropsType = {
focused: boolean; focused: boolean;
@ -34,6 +35,19 @@ type PropsType = {
extraData: null | boolean | number | string; extraData: null | boolean | number | string;
}; };
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
borderRadius: 10,
},
text: {
marginLeft: 'auto',
marginRight: 'auto',
fontSize: 10,
},
});
/** /**
* Abstraction layer for Agenda component, using custom configuration * Abstraction layer for Agenda component, using custom configuration
*/ */
@ -78,7 +92,7 @@ class TabIcon extends React.Component<PropsType> {
} }
shouldComponentUpdate(nextProps: PropsType): boolean { shouldComponentUpdate(nextProps: PropsType): boolean {
const {props} = this; const { props } = this;
return ( return (
nextProps.focused !== props.focused || nextProps.focused !== props.focused ||
nextProps.theme.dark !== props.theme.dark || nextProps.theme.dark !== props.theme.dark ||
@ -87,32 +101,27 @@ class TabIcon extends React.Component<PropsType> {
} }
render() { render() {
const {props} = this; const { props } = this;
return ( return (
<TouchableRipple <TouchableRipple
onPress={props.onPress} onPress={props.onPress}
onLongPress={props.onLongPress} onLongPress={props.onLongPress}
rippleColor={props.theme.colors.primary} rippleColor={props.theme.colors.primary}
borderless borderless={true}
style={{ style={styles.container}
flex: 1, >
justifyContent: 'center',
borderRadius: 10,
}}>
<View> <View>
<Animatable.View <Animatable.View
duration={200} duration={200}
easing="ease-out" easing="ease-out"
animation={props.focused ? 'focusIn' : 'focusOut'} animation={props.focused ? 'focusIn' : 'focusOut'}
useNativeDriver> useNativeDriver
>
<MaterialCommunityIcons <MaterialCommunityIcons
name={props.icon} name={props.icon}
color={props.color} color={props.color}
size={26} size={26}
style={{ style={GENERAL_STYLES.centerHorizontal}
marginLeft: 'auto',
marginRight: 'auto',
}}
/> />
</Animatable.View> </Animatable.View>
<Animatable.Text <Animatable.Text
@ -120,10 +129,9 @@ class TabIcon extends React.Component<PropsType> {
useNativeDriver useNativeDriver
style={{ style={{
color: props.color, color: props.color,
marginLeft: 'auto', ...styles.text,
marginRight: 'auto', }}
fontSize: 10, >
}}>
{props.label} {props.label}
</Animatable.Text> </Animatable.Text>
</View> </View>

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
const ICON_AMICALE = require('../../assets/amicale.png'); const ICON_AMICALE = require('../../assets/amicale.png');
const ICON_CAMPUS = require('../../assets/android.icon.png'); const ICON_CAMPUS = require('../../assets/android.icon.png');

23
src/constants/Styles.tsx Normal file
View file

@ -0,0 +1,23 @@
import { StyleSheet } from 'react-native';
const GENERAL_STYLES = StyleSheet.create({
centerHorizontal: {
marginLeft: 'auto',
marginRight: 'auto',
},
centerVertical: {
marginTop: 'auto',
marginBottom: 'auto',
},
center: {
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 'auto',
marginBottom: 'auto',
},
flex: {
flex: 1,
},
});
export default GENERAL_STYLES;

View file

@ -19,7 +19,7 @@
import * as React from 'react'; import * as React from 'react';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import type {IntroSlideType} from '../components/Overrides/CustomIntroSlider'; import type { IntroSlideType } from '../components/Overrides/CustomIntroSlider';
import MascotIntroWelcome from '../components/Intro/MascotIntroWelcome'; import MascotIntroWelcome from '../components/Intro/MascotIntroWelcome';
import IntroIcon from '../components/Intro/IconIntro'; import IntroIcon from '../components/Intro/IconIntro';

View file

@ -17,8 +17,8 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen'; import type { ProxiwashMachineType } from '../screens/Proxiwash/ProxiwashScreen';
import type {RuFoodCategoryType} from '../screens/Services/SelfMenuScreen'; import type { RuFoodCategoryType } from '../screens/Services/SelfMenuScreen';
/** /**
* Singleton class used to manage april fools * Singleton class used to manage april fools
@ -67,13 +67,13 @@ export default class AprilFoolsManager {
* @returns {Object} * @returns {Object}
*/ */
static getFakeMenuItem( static getFakeMenuItem(
menu: Array<RuFoodCategoryType>, menu: Array<RuFoodCategoryType>
): Array<RuFoodCategoryType> { ): Array<RuFoodCategoryType> {
menu[1].dishes.splice(4, 0, {name: 'Coq au vin'}); menu[1].dishes.splice(4, 0, { name: 'Coq au vin' });
menu[1].dishes.splice(2, 0, {name: "Bat'Soupe"}); menu[1].dishes.splice(2, 0, { name: "Bat'Soupe" });
menu[1].dishes.splice(1, 0, {name: 'Pave de loup'}); menu[1].dishes.splice(1, 0, { name: 'Pave de loup' });
menu[1].dishes.splice(0, 0, {name: 'Béranger à point'}); menu[1].dishes.splice(0, 0, { name: 'Béranger à point' });
menu[1].dishes.splice(0, 0, {name: "Pieds d'Arnaud"}); menu[1].dishes.splice(0, 0, { name: "Pieds d'Arnaud" });
return menu; return menu;
} }
@ -83,7 +83,7 @@ export default class AprilFoolsManager {
* @param dryers * @param dryers
*/ */
static getNewProxiwashDryerOrderedList( static getNewProxiwashDryerOrderedList(
dryers: Array<ProxiwashMachineType> | null, dryers: Array<ProxiwashMachineType> | null
) { ) {
if (dryers != null) { if (dryers != null) {
const second = dryers[1]; const second = dryers[1];
@ -98,7 +98,7 @@ export default class AprilFoolsManager {
* @param washers * @param washers
*/ */
static getNewProxiwashWasherOrderedList( static getNewProxiwashWasherOrderedList(
washers: Array<ProxiwashMachineType> | null, washers: Array<ProxiwashMachineType> | null
) { ) {
if (washers != null) { if (washers != null) {
const first = washers[0]; const first = washers[0];
@ -129,7 +129,7 @@ export default class AprilFoolsManager {
* @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( static getAprilFoolsTheme(
currentTheme: ReactNativePaper.Theme, currentTheme: ReactNativePaper.Theme
): ReactNativePaper.Theme { ): ReactNativePaper.Theme {
return { return {
...currentTheme, ...currentTheme,

View file

@ -18,7 +18,7 @@
*/ */
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
import {SERVICES_KEY} from './ServicesManager'; import { SERVICES_KEY } from './ServicesManager';
/** /**
* Singleton used to manage preferences. * Singleton used to manage preferences.
@ -29,7 +29,7 @@ import {SERVICES_KEY} from './ServicesManager';
export default class AsyncStorageManager { export default class AsyncStorageManager {
static instance: AsyncStorageManager | null = null; static instance: AsyncStorageManager | null = null;
static PREFERENCES: {[key: string]: {key: string; default: string}} = { static PREFERENCES: { [key: string]: { key: string; default: string } } = {
debugUnlocked: { debugUnlocked: {
key: 'debugUnlocked', key: 'debugUnlocked',
default: '0', default: '0',
@ -130,7 +130,7 @@ export default class AsyncStorageManager {
}, },
}; };
private currentPreferences: {[key: string]: string}; private currentPreferences: { [key: string]: string };
constructor() { constructor() {
this.currentPreferences = {}; this.currentPreferences = {};
@ -155,7 +155,7 @@ export default class AsyncStorageManager {
*/ */
static set( static set(
key: string, key: string,
value: number | string | boolean | object | Array<any>, value: number | string | boolean | object | Array<any>
) { ) {
AsyncStorageManager.getInstance().setPreference(key, value); AsyncStorageManager.getInstance().setPreference(key, value);
} }
@ -209,7 +209,7 @@ export default class AsyncStorageManager {
* @return {Promise<void>} * @return {Promise<void>}
*/ */
async loadPreferences() { async loadPreferences() {
return new Promise((resolve: () => void) => { return new Promise((resolve: (val: void) => void) => {
const prefKeys: Array<string> = []; const prefKeys: Array<string> = [];
// Get all available keys // Get all available keys
Object.keys(AsyncStorageManager.PREFERENCES).forEach((key: string) => { Object.keys(AsyncStorageManager.PREFERENCES).forEach((key: string) => {
@ -240,7 +240,7 @@ export default class AsyncStorageManager {
*/ */
setPreference( setPreference(
key: string, key: string,
value: number | string | boolean | object | Array<any>, value: number | string | boolean | object | Array<any>
) { ) {
if (AsyncStorageManager.PREFERENCES[key] != null) { if (AsyncStorageManager.PREFERENCES[key] != null) {
let convertedValue; let convertedValue;

View file

@ -17,11 +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 * as Keychain from 'react-native-keychain'; import * as Keychain from 'react-native-keychain';
import type {ApiDataLoginType} from '../utils/WebData'; import type { ApiDataLoginType } from '../utils/WebData';
import {apiRequest, ERROR_TYPE} from '../utils/WebData'; import { apiRequest, ERROR_TYPE } from '../utils/WebData';
/** /**
* champ: error * champ: error
@ -84,7 +82,7 @@ export default class ConnectionManager {
} }
resolve(); resolve();
}) })
.catch(resolve); .catch(() => resolve());
} }
}); });
} }
@ -159,7 +157,7 @@ export default class ConnectionManager {
} }
}) })
.catch((error: number): void => reject(error)); .catch((error: number): void => reject(error));
}, }
); );
} }
@ -172,7 +170,7 @@ export default class ConnectionManager {
*/ */
async authenticatedRequest<T>( async authenticatedRequest<T>(
path: string, path: string,
params: {[key: string]: any}, params: { [key: string]: any }
): Promise<T> { ): Promise<T> {
return new Promise( return new Promise(
(resolve: (response: T) => void, reject: (error: number) => void) => { (resolve: (response: T) => void, reject: (error: number) => void) => {
@ -187,7 +185,7 @@ export default class ConnectionManager {
} else { } else {
reject(ERROR_TYPE.TOKEN_RETRIEVE); reject(ERROR_TYPE.TOKEN_RETRIEVE);
} }
}, }
); );
} }
} }

View file

@ -17,17 +17,15 @@
* 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 { ServiceItemType } from './ServicesManager';
import type {ServiceItemType} from './ServicesManager';
import ServicesManager from './ServicesManager'; import ServicesManager from './ServicesManager';
import {getSublistWithIds} from '../utils/Services'; 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<Array<string>>( const dashboardIdList = AsyncStorageManager.getObject<Array<string>>(
AsyncStorageManager.PREFERENCES.dashboardItems.key, AsyncStorageManager.PREFERENCES.dashboardItems.key
); );
const allDatasets = [ const allDatasets = [
...this.amicaleDataset, ...this.amicaleDataset,

View file

@ -84,7 +84,7 @@ export default class DateManager {
date.setFullYear( date.setFullYear(
parseInt(dateArray[0], 10), parseInt(dateArray[0], 10),
parseInt(dateArray[1], 10) - 1, parseInt(dateArray[1], 10) - 1,
parseInt(dateArray[2], 10), parseInt(dateArray[2], 10)
); );
return `${this.daysOfWeek[date.getDay()]} ${date.getDate()} ${ return `${this.daysOfWeek[date.getDay()]} ${date.getDate()} ${
this.monthsOfYear[date.getMonth()] this.monthsOfYear[date.getMonth()]

View file

@ -18,10 +18,10 @@
*/ */
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';
import ConnectionManager from './ConnectionManager'; import ConnectionManager from './ConnectionManager';
import type {FullDashboardType} from '../screens/Home/HomeScreen'; import type { FullDashboardType } from '../screens/Home/HomeScreen';
import getStrippedServicesList from '../utils/Services'; import getStrippedServicesList from '../utils/Services';
// AMICALE // AMICALE
@ -337,7 +337,7 @@ export default class ServicesManager {
if (ConnectionManager.getInstance().isLoggedIn()) { if (ConnectionManager.getInstance().isLoggedIn()) {
this.navigation.navigate(route); this.navigation.navigate(route);
} else { } else {
this.navigation.navigate('login', {nextScreen: route}); this.navigation.navigate('login', { nextScreen: route });
} }
} }

View file

@ -17,8 +17,8 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {DarkTheme, DefaultTheme} from 'react-native-paper'; import { DarkTheme, DefaultTheme } from 'react-native-paper';
import {Appearance} from 'react-native-appearance'; import { Appearance } from 'react-native-appearance';
import AsyncStorageManager from './AsyncStorageManager'; import AsyncStorageManager from './AsyncStorageManager';
import AprilFoolsManager from './AprilFoolsManager'; import AprilFoolsManager from './AprilFoolsManager';
@ -235,14 +235,14 @@ export default class ThemeManager {
static getNightMode(): boolean { static getNightMode(): boolean {
return ( return (
(AsyncStorageManager.getBool( (AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.nightMode.key, AsyncStorageManager.PREFERENCES.nightMode.key
) && ) &&
(!AsyncStorageManager.getBool( (!AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key, AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key
) || ) ||
colorScheme === 'no-preference')) || colorScheme === 'no-preference')) ||
(AsyncStorageManager.getBool( (AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key, AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key
) && ) &&
colorScheme === 'dark') colorScheme === 'dark')
); );
@ -289,7 +289,7 @@ export default class ThemeManager {
setNightMode(isNightMode: boolean) { setNightMode(isNightMode: boolean) {
AsyncStorageManager.set( AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.nightMode.key, AsyncStorageManager.PREFERENCES.nightMode.key,
isNightMode, isNightMode
); );
if (this.updateThemeCallback != null) { if (this.updateThemeCallback != null) {
this.updateThemeCallback(); this.updateThemeCallback();

View file

@ -18,9 +18,12 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {createStackNavigator, TransitionPresets} from '@react-navigation/stack'; import {
createStackNavigator,
TransitionPresets,
} from '@react-navigation/stack';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {Platform} from 'react-native'; import { Platform } from 'react-native';
import SettingsScreen from '../screens/Other/Settings/SettingsScreen'; import SettingsScreen from '../screens/Other/Settings/SettingsScreen';
import AboutScreen from '../screens/About/AboutScreen'; import AboutScreen from '../screens/About/AboutScreen';
import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen'; import AboutDependenciesScreen from '../screens/About/AboutDependenciesScreen';
@ -78,16 +81,16 @@ export enum MainRoutes {
Feedback = 'feedback', Feedback = 'feedback',
} }
type DefaultParams = {[key in MainRoutes]: object | undefined}; type DefaultParams = { [key in MainRoutes]: object | undefined };
export interface FullParamsList extends DefaultParams { export interface FullParamsList extends DefaultParams {
login: {nextScreen: string}; 'login': { nextScreen: string };
'equipment-confirm': { 'equipment-confirm': {
item?: DeviceType; item?: DeviceType;
dates: [string, string]; dates: [string, string];
}; };
'equipment-rent': {item?: DeviceType}; 'equipment-rent': { item?: DeviceType };
gallery: {images: Array<{url: string}>}; 'gallery': { images: Array<{ url: string }> };
} }
// Don't know why but TS is complaining without this // Don't know why but TS is complaining without this
@ -108,13 +111,14 @@ const defaultScreenOptions = {
const MainStack = createStackNavigator<MainStackParamsList>(); const MainStack = createStackNavigator<MainStackParamsList>();
function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) { function MainStackComponent(props: { createTabNavigator: () => JSX.Element }) {
const {createTabNavigator} = props; const { createTabNavigator } = props;
return ( return (
<MainStack.Navigator <MainStack.Navigator
initialRouteName={MainRoutes.Main} initialRouteName={MainRoutes.Main}
headerMode="screen" headerMode="screen"
screenOptions={defaultScreenOptions}> screenOptions={defaultScreenOptions}
>
<MainStack.Screen <MainStack.Screen
name={MainRoutes.Main} name={MainRoutes.Main}
component={createTabNavigator} component={createTabNavigator}
@ -135,31 +139,31 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
MainRoutes.Settings, MainRoutes.Settings,
MainStack, MainStack,
SettingsScreen, SettingsScreen,
i18n.t('screens.settings.title'), i18n.t('screens.settings.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.DashboardEdit, MainRoutes.DashboardEdit,
MainStack, MainStack,
DashboardEditScreen, DashboardEditScreen,
i18n.t('screens.settings.dashboardEdit.title'), i18n.t('screens.settings.dashboardEdit.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.About, MainRoutes.About,
MainStack, MainStack,
AboutScreen, AboutScreen,
i18n.t('screens.about.title'), i18n.t('screens.about.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.Dependencies, MainRoutes.Dependencies,
MainStack, MainStack,
AboutDependenciesScreen, AboutDependenciesScreen,
i18n.t('screens.about.libs'), i18n.t('screens.about.libs')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.Debug, MainRoutes.Debug,
MainStack, MainStack,
DebugScreen, DebugScreen,
i18n.t('screens.about.debug'), i18n.t('screens.about.debug')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
@ -169,7 +173,7 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
i18n.t('screens.game.title'), i18n.t('screens.game.title'),
true, true,
undefined, undefined,
'transparent', 'transparent'
)} )}
<MainStack.Screen <MainStack.Screen
name={MainRoutes.GameMain} name={MainRoutes.GameMain}
@ -184,8 +188,8 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
LoginScreen, LoginScreen,
i18n.t('screens.login.title'), i18n.t('screens.login.title'),
true, true,
{headerTintColor: '#fff'}, { headerTintColor: '#fff' },
'transparent', 'transparent'
)} )}
{getWebsiteStack('website', MainStack, WebsiteScreen, '')} {getWebsiteStack('website', MainStack, WebsiteScreen, '')}
@ -193,19 +197,19 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
MainRoutes.SelfMenu, MainRoutes.SelfMenu,
MainStack, MainStack,
SelfMenuScreen, SelfMenuScreen,
i18n.t('screens.menu.title'), i18n.t('screens.menu.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.Proximo, MainRoutes.Proximo,
MainStack, MainStack,
ProximoMainScreen, ProximoMainScreen,
i18n.t('screens.proximo.title'), i18n.t('screens.proximo.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.ProximoList, MainRoutes.ProximoList,
MainStack, MainStack,
ProximoListScreen, ProximoListScreen,
i18n.t('screens.proximo.articleList'), i18n.t('screens.proximo.articleList')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.ProximoAbout, MainRoutes.ProximoAbout,
@ -213,20 +217,20 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
ProximoAboutScreen, ProximoAboutScreen,
i18n.t('screens.proximo.title'), i18n.t('screens.proximo.title'),
true, true,
{...modalTransition}, { ...modalTransition }
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.Profile, MainRoutes.Profile,
MainStack, MainStack,
ProfileScreen, ProfileScreen,
i18n.t('screens.profile.title'), i18n.t('screens.profile.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.ClubList, MainRoutes.ClubList,
MainStack, MainStack,
ClubListScreen, ClubListScreen,
i18n.t('screens.clubs.title'), i18n.t('screens.clubs.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.ClubInformation, MainRoutes.ClubInformation,
@ -234,7 +238,7 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
ClubDisplayScreen, ClubDisplayScreen,
i18n.t('screens.clubs.details'), i18n.t('screens.clubs.details'),
true, true,
{...modalTransition}, { ...modalTransition }
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.ClubAbout, MainRoutes.ClubAbout,
@ -242,37 +246,37 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
ClubAboutScreen, ClubAboutScreen,
i18n.t('screens.clubs.title'), i18n.t('screens.clubs.title'),
true, true,
{...modalTransition}, { ...modalTransition }
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.EquipmentList, MainRoutes.EquipmentList,
MainStack, MainStack,
EquipmentScreen, EquipmentScreen,
i18n.t('screens.equipment.title'), i18n.t('screens.equipment.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.EquipmentRent, MainRoutes.EquipmentRent,
MainStack, MainStack,
EquipmentLendScreen, EquipmentLendScreen,
i18n.t('screens.equipment.book'), i18n.t('screens.equipment.book')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.EquipmentConfirm, MainRoutes.EquipmentConfirm,
MainStack, MainStack,
EquipmentConfirmScreen, EquipmentConfirmScreen,
i18n.t('screens.equipment.confirm'), i18n.t('screens.equipment.confirm')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.Vote, MainRoutes.Vote,
MainStack, MainStack,
VoteScreen, VoteScreen,
i18n.t('screens.vote.title'), i18n.t('screens.vote.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
MainRoutes.Feedback, MainRoutes.Feedback,
MainStack, MainStack,
BugReportScreen, BugReportScreen,
i18n.t('screens.feedback.title'), i18n.t('screens.feedback.title')
)} )}
</MainStack.Navigator> </MainStack.Navigator>
); );
@ -280,7 +284,7 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
type PropsType = { type PropsType = {
defaultHomeRoute: string | null; defaultHomeRoute: string | null;
defaultHomeData: {[key: string]: string}; defaultHomeData: { [key: string]: string };
}; };
export default function MainNavigator(props: PropsType) { export default function MainNavigator(props: PropsType) {

View file

@ -18,14 +18,17 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {createStackNavigator, TransitionPresets} from '@react-navigation/stack'; import {
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; createStackNavigator,
TransitionPresets,
} from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import {Title, useTheme} from 'react-native-paper'; import { Title, useTheme } from 'react-native-paper';
import {Platform} from 'react-native'; import { Platform, StyleSheet } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {createCollapsibleStack} from 'react-navigation-collapsible'; import { createCollapsibleStack } from 'react-navigation-collapsible';
import {View} from 'react-native-animatable'; import { View } from 'react-native-animatable';
import HomeScreen from '../screens/Home/HomeScreen'; import HomeScreen from '../screens/Home/HomeScreen';
import PlanningScreen from '../screens/Planning/PlanningScreen'; import PlanningScreen from '../screens/Planning/PlanningScreen';
import PlanningDisplayScreen from '../screens/Planning/PlanningDisplayScreen'; import PlanningDisplayScreen from '../screens/Planning/PlanningDisplayScreen';
@ -45,7 +48,7 @@ import {
CreateScreenCollapsibleStack, CreateScreenCollapsibleStack,
getWebsiteStack, getWebsiteStack,
} from '../utils/CollapsibleUtils'; } from '../utils/CollapsibleUtils';
import Mascot, {MASCOT_STYLE} from '../components/Mascot/Mascot'; import Mascot, { MASCOT_STYLE } from '../components/Mascot/Mascot';
const modalTransition = const modalTransition =
Platform.OS === 'ios' Platform.OS === 'ios'
@ -58,6 +61,20 @@ const defaultScreenOptions = {
...modalTransition, ...modalTransition,
}; };
const styles = StyleSheet.create({
header: {
flexDirection: 'row',
},
mascot: {
width: 50,
},
title: {
marginLeft: 10,
marginTop: 'auto',
marginBottom: 'auto',
},
});
const ServicesStack = createStackNavigator(); const ServicesStack = createStackNavigator();
function ServicesStackComponent() { function ServicesStackComponent() {
@ -65,24 +82,25 @@ function ServicesStackComponent() {
<ServicesStack.Navigator <ServicesStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode="screen" headerMode="screen"
screenOptions={defaultScreenOptions}> screenOptions={defaultScreenOptions}
>
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'index', 'index',
ServicesStack, ServicesStack,
WebsitesHomeScreen, WebsitesHomeScreen,
i18n.t('screens.services.title'), i18n.t('screens.services.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'services-section', 'services-section',
ServicesStack, ServicesStack,
ServicesSectionScreen, ServicesSectionScreen,
'SECTION', 'SECTION'
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'amicale-contact', 'amicale-contact',
ServicesStack, ServicesStack,
AmicaleContactScreen, AmicaleContactScreen,
i18n.t('screens.amicaleAbout.title'), i18n.t('screens.amicaleAbout.title')
)} )}
</ServicesStack.Navigator> </ServicesStack.Navigator>
); );
@ -95,18 +113,19 @@ function ProxiwashStackComponent() {
<ProxiwashStack.Navigator <ProxiwashStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode="screen" headerMode="screen"
screenOptions={defaultScreenOptions}> screenOptions={defaultScreenOptions}
>
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'index', 'index',
ProxiwashStack, ProxiwashStack,
ProxiwashScreen, ProxiwashScreen,
i18n.t('screens.proxiwash.title'), i18n.t('screens.proxiwash.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'proxiwash-about', 'proxiwash-about',
ProxiwashStack, ProxiwashStack,
ProxiwashAboutScreen, ProxiwashAboutScreen,
i18n.t('screens.proxiwash.title'), i18n.t('screens.proxiwash.title')
)} )}
</ProxiwashStack.Navigator> </ProxiwashStack.Navigator>
); );
@ -119,17 +138,18 @@ function PlanningStackComponent() {
<PlanningStack.Navigator <PlanningStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode="screen" headerMode="screen"
screenOptions={defaultScreenOptions}> screenOptions={defaultScreenOptions}
>
<PlanningStack.Screen <PlanningStack.Screen
name="index" name="index"
component={PlanningScreen} component={PlanningScreen}
options={{title: i18n.t('screens.planning.title')}} options={{ title: i18n.t('screens.planning.title') }}
/> />
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'planning-information', 'planning-information',
PlanningStack, PlanningStack,
PlanningDisplayScreen, PlanningDisplayScreen,
i18n.t('screens.planning.eventDetails'), i18n.t('screens.planning.eventDetails')
)} )}
</PlanningStack.Navigator> </PlanningStack.Navigator>
); );
@ -139,18 +159,19 @@ const HomeStack = createStackNavigator();
function HomeStackComponent( function HomeStackComponent(
initialRoute: string | null, initialRoute: string | null,
defaultData: {[key: string]: string}, defaultData: { [key: string]: string }
) { ) {
let params; let params;
if (initialRoute) { if (initialRoute) {
params = {data: defaultData, nextScreen: initialRoute, shouldOpen: true}; params = { data: defaultData, nextScreen: initialRoute, shouldOpen: true };
} }
const {colors} = useTheme(); const { colors } = useTheme();
return ( return (
<HomeStack.Navigator <HomeStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode="screen" headerMode="screen"
screenOptions={defaultScreenOptions}> screenOptions={defaultScreenOptions}
>
{createCollapsibleStack( {createCollapsibleStack(
<HomeStack.Screen <HomeStack.Screen
name="index" name="index"
@ -161,11 +182,9 @@ function HomeStackComponent(
backgroundColor: colors.surface, backgroundColor: colors.surface,
}, },
headerTitle: () => ( headerTitle: () => (
<View style={{flexDirection: 'row'}}> <View style={styles.header}>
<Mascot <Mascot
style={{ style={styles.mascot}
width: 50,
}}
emotion={MASCOT_STYLE.RANDOM} emotion={MASCOT_STYLE.RANDOM}
animated animated
entryAnimation={{ entryAnimation={{
@ -178,12 +197,7 @@ function HomeStackComponent(
iterationCount: 'infinite', iterationCount: 'infinite',
}} }}
/> />
<Title <Title style={styles.title}>
style={{
marginLeft: 10,
marginTop: 'auto',
marginBottom: 'auto',
}}>
{i18n.t('screens.home.title')} {i18n.t('screens.home.title')}
</Title> </Title>
</View> </View>
@ -194,31 +208,31 @@ function HomeStackComponent(
{ {
collapsedColor: colors.surface, collapsedColor: colors.surface,
useNativeDriver: true, useNativeDriver: true,
}, }
)} )}
<HomeStack.Screen <HomeStack.Screen
name="scanner" name="scanner"
component={ScannerScreen} component={ScannerScreen}
options={{title: i18n.t('screens.scanner.title')}} options={{ title: i18n.t('screens.scanner.title') }}
/> />
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'club-information', 'club-information',
HomeStack, HomeStack,
ClubDisplayScreen, ClubDisplayScreen,
i18n.t('screens.clubs.details'), i18n.t('screens.clubs.details')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'feed-information', 'feed-information',
HomeStack, HomeStack,
FeedItemScreen, FeedItemScreen,
i18n.t('screens.home.feed'), i18n.t('screens.home.feed')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'planning-information', 'planning-information',
HomeStack, HomeStack,
PlanningDisplayScreen, PlanningDisplayScreen,
i18n.t('screens.planning.eventDetails'), i18n.t('screens.planning.eventDetails')
)} )}
</HomeStack.Navigator> </HomeStack.Navigator>
); );
@ -231,18 +245,19 @@ function PlanexStackComponent() {
<PlanexStack.Navigator <PlanexStack.Navigator
initialRouteName="index" initialRouteName="index"
headerMode="screen" headerMode="screen"
screenOptions={defaultScreenOptions}> screenOptions={defaultScreenOptions}
>
{getWebsiteStack( {getWebsiteStack(
'index', 'index',
PlanexStack, PlanexStack,
PlanexScreen, PlanexScreen,
i18n.t('screens.planex.title'), i18n.t('screens.planex.title')
)} )}
{CreateScreenCollapsibleStack( {CreateScreenCollapsibleStack(
'group-select', 'group-select',
PlanexStack, PlanexStack,
GroupSelectionScreen, GroupSelectionScreen,
'', ''
)} )}
</PlanexStack.Navigator> </PlanexStack.Navigator>
); );
@ -252,7 +267,7 @@ const Tab = createBottomTabNavigator();
type PropsType = { type PropsType = {
defaultHomeRoute: string | null; defaultHomeRoute: string | null;
defaultHomeData: {[key: string]: string}; defaultHomeData: { [key: string]: string };
}; };
export default class TabNavigator extends React.Component<PropsType> { export default class TabNavigator extends React.Component<PropsType> {
@ -264,7 +279,7 @@ export default class TabNavigator extends React.Component<PropsType> {
this.defaultRoute = 'home'; this.defaultRoute = 'home';
if (!props.defaultHomeRoute) { if (!props.defaultHomeRoute) {
this.defaultRoute = AsyncStorageManager.getString( this.defaultRoute = AsyncStorageManager.getString(
AsyncStorageManager.PREFERENCES.defaultStartScreen.key, AsyncStorageManager.PREFERENCES.defaultStartScreen.key
).toLowerCase(); ).toLowerCase();
} }
this.createHomeStackComponent = () => this.createHomeStackComponent = () =>
@ -275,31 +290,32 @@ export default class TabNavigator extends React.Component<PropsType> {
return ( return (
<Tab.Navigator <Tab.Navigator
initialRouteName={this.defaultRoute} initialRouteName={this.defaultRoute}
tabBar={(tabProps) => <CustomTabBar {...tabProps} />}> tabBar={(tabProps) => <CustomTabBar {...tabProps} />}
>
<Tab.Screen <Tab.Screen
name="services" name="services"
component={ServicesStackComponent} component={ServicesStackComponent}
options={{title: i18n.t('screens.services.title')}} options={{ title: i18n.t('screens.services.title') }}
/> />
<Tab.Screen <Tab.Screen
name="proxiwash" name="proxiwash"
component={ProxiwashStackComponent} component={ProxiwashStackComponent}
options={{title: i18n.t('screens.proxiwash.title')}} options={{ title: i18n.t('screens.proxiwash.title') }}
/> />
<Tab.Screen <Tab.Screen
name="home" name="home"
component={this.createHomeStackComponent} component={this.createHomeStackComponent}
options={{title: i18n.t('screens.home.title')}} options={{ title: i18n.t('screens.home.title') }}
/> />
<Tab.Screen <Tab.Screen
name="planning" name="planning"
component={PlanningStackComponent} component={PlanningStackComponent}
options={{title: i18n.t('screens.planning.title')}} options={{ title: i18n.t('screens.planning.title') }}
/> />
<Tab.Screen <Tab.Screen
name="planex" name="planex"
component={PlanexStackComponent} component={PlanexStackComponent}
options={{title: i18n.t('screens.planex.title')}} options={{ title: i18n.t('screens.planex.title') }}
/> />
</Tab.Navigator> </Tab.Navigator>
); );

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {List} from 'react-native-paper'; import { List } from 'react-native-paper';
import {View} from 'react-native-animatable'; import { View } from 'react-native-animatable';
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
import packageJson from '../../../package.json'; import packageJson from '../../../package.json';
@ -40,7 +40,7 @@ function generateListFromObject(object: {
const list: Array<ListItemType> = []; const list: Array<ListItemType> = [];
const keys = Object.keys(object); const keys = Object.keys(object);
keys.forEach((key: string) => { keys.forEach((key: string) => {
list.push({name: key, version: object[key]}); list.push({ name: key, version: object[key] });
}); });
return list; return list;
} }
@ -60,18 +60,18 @@ export default class AboutDependenciesScreen extends React.Component<{}> {
keyExtractor = (item: ListItemType): string => item.name; keyExtractor = (item: ListItemType): string => item.name;
getRenderItem = ({item}: {item: ListItemType}) => ( getRenderItem = ({ item }: { item: ListItemType }) => (
<List.Item <List.Item
title={item.name} title={item.name}
description={item.version.replace('^', '').replace('~', '')} description={item.version.replace('^', '').replace('~', '')}
style={{height: LIST_ITEM_HEIGHT}} style={{ height: LIST_ITEM_HEIGHT }}
/> />
); );
getItemLayout = ( getItemLayout = (
data: Array<ListItemType> | null | undefined, data: Array<ListItemType> | null | undefined,
index: number, index: number
): {length: number; offset: number; index: number} => ({ ): { length: number; offset: number; index: number } => ({
length: LIST_ITEM_HEIGHT, length: LIST_ITEM_HEIGHT,
offset: LIST_ITEM_HEIGHT * index, offset: LIST_ITEM_HEIGHT * index,
index, index,

View file

@ -18,14 +18,22 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {FlatList, Linking, Platform, Image, View} from 'react-native'; import {
FlatList,
Linking,
Platform,
Image,
View,
StyleSheet,
} from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {Avatar, Card, List} from 'react-native-paper'; import { Avatar, Card, List } from 'react-native-paper';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import packageJson from '../../../package.json'; import packageJson from '../../../package.json';
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
import OptionsDialog from '../../components/Dialogs/OptionsDialog'; import OptionsDialog from '../../components/Dialogs/OptionsDialog';
import type {OptionsDialogButtonType} from '../../components/Dialogs/OptionsDialog'; import type { OptionsDialogButtonType } from '../../components/Dialogs/OptionsDialog';
import GENERAL_STYLES from '../../constants/Styles';
const APP_LOGO = require('../../../assets/android.icon.round.png'); const APP_LOGO = require('../../../assets/android.icon.round.png');
@ -69,6 +77,15 @@ type StateType = {
dialogButtons: Array<OptionsDialogButtonType>; dialogButtons: Array<OptionsDialogButtonType>;
}; };
const styles = StyleSheet.create({
card: {
marginBottom: 10,
},
list: {
padding: 5,
},
});
/** /**
* Opens a link in the device's browser * Opens a link in the device's browser
* @param link The link to open * @param link The link to open
@ -171,7 +188,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
}, },
{ {
onPressCallback: () => { onPressCallback: () => {
const {navigation} = this.props; const { navigation } = this.props;
navigation.navigate('feedback'); navigation.navigate('feedback');
}, },
icon: 'bug', icon: 'bug',
@ -228,7 +245,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
}, },
{ {
onPressCallback: () => { onPressCallback: () => {
const {navigation} = this.props; const { navigation } = this.props;
navigation.navigate('dependencies'); navigation.navigate('dependencies');
}, },
icon: 'developer-board', icon: 'developer-board',
@ -267,7 +284,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
...this.getMemberData(this.majorContributors), ...this.getMemberData(this.majorContributors),
{ {
onPressCallback: () => { onPressCallback: () => {
const {navigation} = this.props; const { navigation } = this.props;
navigation.navigate('feedback'); navigation.navigate('feedback');
}, },
icon: 'hand-pointing-right', icon: 'hand-pointing-right',
@ -306,7 +323,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
onPress: this.onDialogDismiss, onPress: this.onDialogDismiss,
}, },
]; ];
const {linkedin, trollLink, mail} = user; const { linkedin, trollLink, mail } = user;
if (linkedin != null) { if (linkedin != null) {
dialogBtn.push({ dialogBtn.push({
title: '', title: '',
@ -348,14 +365,14 @@ class AboutScreen extends React.Component<PropsType, StateType> {
*/ */
getAppCard() { getAppCard() {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={styles.card}>
<Card.Title <Card.Title
title="Campus" title="Campus"
subtitle={packageJson.version} subtitle={packageJson.version}
left={(iconProps) => ( left={(iconProps) => (
<Image <Image
source={APP_LOGO} source={APP_LOGO}
style={{width: iconProps.size, height: iconProps.size}} style={{ width: iconProps.size, height: iconProps.size }}
/> />
)} )}
/> />
@ -377,7 +394,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
*/ */
getTeamCard() { getTeamCard() {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={styles.card}>
<Card.Title <Card.Title
title={i18n.t('screens.about.team')} title={i18n.t('screens.about.team')}
left={(iconProps) => ( left={(iconProps) => (
@ -402,7 +419,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
*/ */
getThanksCard() { getThanksCard() {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={styles.card}>
<Card.Title <Card.Title
title={i18n.t('screens.about.thanks')} title={i18n.t('screens.about.thanks')}
left={(iconProps) => ( left={(iconProps) => (
@ -427,7 +444,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
*/ */
getTechnoCard() { getTechnoCard() {
return ( return (
<Card style={{marginBottom: 10}}> <Card style={styles.card}>
<Card.Title <Card.Title
title={i18n.t('screens.about.technologies')} title={i18n.t('screens.about.technologies')}
left={(iconProps) => ( left={(iconProps) => (
@ -478,7 +495,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
marginRight: number; marginRight: number;
marginVertical?: number; marginVertical?: number;
}; };
}, }
) { ) {
return ( return (
<List.Icon color={props.color} style={props.style} icon={item.icon} /> <List.Icon color={props.color} style={props.style} icon={item.icon} />
@ -490,7 +507,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* *
* @returns {*} * @returns {*}
*/ */
getCardItem = ({item}: {item: ListItemType}) => { getCardItem = ({ item }: { item: ListItemType }) => {
const getItemIcon = (props: { const getItemIcon = (props: {
color: string; color: string;
style?: { style?: {
@ -523,7 +540,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
* @param item The item to show * @param item The item to show
* @return {*} * @return {*}
*/ */
getMainCard = ({item}: {item: {id: string}}) => { getMainCard = ({ item }: { item: { id: string } }) => {
switch (item.id) { switch (item.id) {
case 'app': case 'app':
return this.getAppCard(); return this.getAppCard();
@ -539,7 +556,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
}; };
onDialogDismiss = () => { onDialogDismiss = () => {
this.setState({dialogVisible: false}); this.setState({ dialogVisible: false });
}; };
/** /**
@ -551,14 +568,11 @@ class AboutScreen extends React.Component<PropsType, StateType> {
keyExtractor = (item: ListItemType): string => item.icon; keyExtractor = (item: ListItemType): string => item.icon;
render() { render() {
const {state} = this; const { state } = this;
return ( return (
<View <View style={GENERAL_STYLES.flex}>
style={{
height: '100%',
}}>
<CollapsibleFlatList <CollapsibleFlatList
style={{padding: 5}} style={styles.list}
data={this.dataOrder} data={this.dataOrder}
renderItem={this.getMainCard} renderItem={this.getMainCard}
/> />

View file

@ -18,7 +18,7 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import { import {
Button, Button,
List, List,
@ -27,7 +27,7 @@ import {
Title, Title,
withTheme, withTheme,
} from 'react-native-paper'; } from 'react-native-paper';
import {Modalize} from 'react-native-modalize'; import { Modalize } from 'react-native-modalize';
import CustomModal from '../../components/Overrides/CustomModal'; import CustomModal from '../../components/Overrides/CustomModal';
import AsyncStorageManager from '../../managers/AsyncStorageManager'; import AsyncStorageManager from '../../managers/AsyncStorageManager';
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
@ -47,6 +47,17 @@ type StateType = {
currentPreferences: Array<PreferenceItemType>; currentPreferences: Array<PreferenceItemType>;
}; };
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
buttonContainer: {
flexDirection: 'row',
marginTop: 10,
},
});
/** /**
* Class defining the Debug screen. * Class defining the Debug screen.
* This screen allows the user to get and modify information on the app/device. * This screen allows the user to get and modify information on the app/device.
@ -67,7 +78,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
this.modalInputValue = ''; this.modalInputValue = '';
const currentPreferences: Array<PreferenceItemType> = []; const currentPreferences: Array<PreferenceItemType> = [];
Object.values(AsyncStorageManager.PREFERENCES).forEach((object: any) => { Object.values(AsyncStorageManager.PREFERENCES).forEach((object: any) => {
const newObject: PreferenceItemType = {...object}; const newObject: PreferenceItemType = { ...object };
newObject.current = AsyncStorageManager.getString(newObject.key); newObject.current = AsyncStorageManager.getString(newObject.key);
currentPreferences.push(newObject); currentPreferences.push(newObject);
}); });
@ -83,7 +94,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
* @return {*} * @return {*}
*/ */
getModalContent() { getModalContent() {
const {props, state} = this; const { props, state } = this;
let key = ''; let key = '';
let defaultValue = ''; let defaultValue = '';
let current = ''; let current = '';
@ -95,11 +106,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
} }
return ( return (
<View <View style={styles.container}>
style={{
flex: 1,
padding: 20,
}}>
<Title>{key}</Title> <Title>{key}</Title>
<Subheading>Default: {defaultValue}</Subheading> <Subheading>Default: {defaultValue}</Subheading>
<Subheading>Current: {current}</Subheading> <Subheading>Current: {current}</Subheading>
@ -109,18 +116,15 @@ class DebugScreen extends React.Component<PropsType, StateType> {
this.modalInputValue = text; this.modalInputValue = text;
}} }}
/> />
<View <View style={styles.buttonContainer}>
style={{
flexDirection: 'row',
marginTop: 10,
}}>
<Button <Button
mode="contained" mode="contained"
dark dark
color={props.theme.colors.success} color={props.theme.colors.success}
onPress={() => { onPress={() => {
this.saveNewPrefs(key, this.modalInputValue); this.saveNewPrefs(key, this.modalInputValue);
}}> }}
>
Save new value Save new value
</Button> </Button>
<Button <Button
@ -129,7 +133,8 @@ class DebugScreen extends React.Component<PropsType, StateType> {
color={props.theme.colors.danger} color={props.theme.colors.danger}
onPress={() => { onPress={() => {
this.saveNewPrefs(key, defaultValue); this.saveNewPrefs(key, defaultValue);
}}> }}
>
Reset to default Reset to default
</Button> </Button>
</View> </View>
@ -137,7 +142,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
); );
} }
getRenderItem = ({item}: {item: PreferenceItemType}) => { getRenderItem = ({ item }: { item: PreferenceItemType }) => {
return ( return (
<List.Item <List.Item
title={item.key} title={item.key}
@ -179,7 +184,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
* @returns {number} * @returns {number}
*/ */
findIndexOfKey(key: string): number { findIndexOfKey(key: string): number {
const {currentPreferences} = this.state; const { currentPreferences } = this.state;
let index = -1; let index = -1;
for (let i = 0; i < currentPreferences.length; i += 1) { for (let i = 0; i < currentPreferences.length; i += 1) {
if (currentPreferences[i].key === key) { if (currentPreferences[i].key === key) {
@ -202,7 +207,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
} => { } => {
const currentPreferences = [...prevState.currentPreferences]; const currentPreferences = [...prevState.currentPreferences];
currentPreferences[this.findIndexOfKey(key)].current = value; currentPreferences[this.findIndexOfKey(key)].current = value;
return {currentPreferences}; return { currentPreferences };
}); });
AsyncStorageManager.set(key, value); AsyncStorageManager.set(key, value);
if (this.modalRef) { if (this.modalRef) {
@ -211,7 +216,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
} }
render() { render() {
const {state} = this; const { state } = this;
return ( return (
<View> <View>
<CustomModal onRef={this.onModalRef}> <CustomModal onRef={this.onModalRef}>

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {FlatList, Image, Linking, View} from 'react-native'; import { FlatList, Image, Linking, StyleSheet, View } from 'react-native';
import {Avatar, Card, List, Text} from 'react-native-paper'; import { Avatar, Card, List, Text } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
@ -31,6 +31,24 @@ type DatasetItemType = {
icon: string; icon: string;
}; };
const styles = StyleSheet.create({
imageContainer: {
width: '100%',
height: 100,
marginTop: 20,
marginBottom: 20,
justifyContent: 'center',
alignItems: 'center',
},
image: {
flex: 1,
resizeMode: 'contain',
},
card: {
margin: 5,
},
});
/** /**
* Class defining a planning event information page. * Class defining a planning event information page.
*/ */
@ -105,7 +123,7 @@ class AmicaleContactScreen extends React.Component<{}> {
/> />
); );
getRenderItem = ({item}: {item: DatasetItemType}) => { getRenderItem = ({ item }: { item: DatasetItemType }) => {
const onPress = () => { const onPress = () => {
Linking.openURL(`mailto:${item.email}`); Linking.openURL(`mailto:${item.email}`);
}; };
@ -129,22 +147,14 @@ class AmicaleContactScreen extends React.Component<{}> {
getScreen = () => { getScreen = () => {
return ( return (
<View> <View>
<View <View style={styles.imageContainer}>
style={{
width: '100%',
height: 100,
marginTop: 20,
marginBottom: 20,
justifyContent: 'center',
alignItems: 'center',
}}>
<Image <Image
source={AMICALE_LOGO} source={AMICALE_LOGO}
style={{flex: 1, resizeMode: 'contain'}} style={styles.image}
resizeMode="contain" resizeMode="contain"
/> />
</View> </View>
<Card style={{margin: 5}}> <Card style={styles.card}>
<Card.Title <Card.Title
title={i18n.t('screens.amicaleAbout.title')} title={i18n.t('screens.amicaleAbout.title')}
subtitle={i18n.t('screens.amicaleAbout.subtitle')} subtitle={i18n.t('screens.amicaleAbout.subtitle')}
@ -168,7 +178,7 @@ class AmicaleContactScreen extends React.Component<{}> {
render() { render() {
return ( return (
<CollapsibleFlatList <CollapsibleFlatList
data={[{key: '1'}]} data={[{ key: '1' }]}
renderItem={this.getScreen} renderItem={this.getScreen}
hasTab hasTab
/> />

View file

@ -18,8 +18,8 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Image, View} from 'react-native'; import { Image, StyleSheet, View } from 'react-native';
import {Card, Avatar, Text} from 'react-native-paper'; import { Card, Avatar, Text } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import Autolink from 'react-native-autolink'; import Autolink from 'react-native-autolink';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
@ -27,26 +27,39 @@ const AMICALE_ICON = require('../../../../assets/amicale.png');
const CONTACT_LINK = 'clubs@amicale-insat.fr'; const CONTACT_LINK = 'clubs@amicale-insat.fr';
const styles = StyleSheet.create({
container: {
padding: 5,
},
imageContainer: {
width: '100%',
height: 100,
marginTop: 20,
marginBottom: 20,
justifyContent: 'center',
alignItems: 'center',
},
image: {
flex: 1,
resizeMode: 'contain',
},
card: {
margin: 5,
},
});
function ClubAboutScreen() { function ClubAboutScreen() {
return ( return (
<CollapsibleScrollView style={{padding: 5}}> <CollapsibleScrollView style={styles.container}>
<View <View style={styles.imageContainer}>
style={{
width: '100%',
height: 100,
marginTop: 20,
marginBottom: 20,
justifyContent: 'center',
alignItems: 'center',
}}>
<Image <Image
source={AMICALE_ICON} source={AMICALE_ICON}
style={{flex: 1, resizeMode: 'contain'}} style={styles.image}
resizeMode="contain" resizeMode="contain"
/> />
</View> </View>
<Text>{i18n.t('screens.clubs.about.text')}</Text> <Text>{i18n.t('screens.clubs.about.text')}</Text>
<Card style={{margin: 5}}> <Card style={styles.card}>
<Card.Title <Card.Title
title={i18n.t('screens.clubs.about.title')} title={i18n.t('screens.clubs.about.title')}
subtitle={i18n.t('screens.clubs.about.subtitle')} subtitle={i18n.t('screens.clubs.about.subtitle')}

View file

@ -18,7 +18,7 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Linking, View} from 'react-native'; import { Linking, StyleSheet, View } from 'react-native';
import { import {
Avatar, Avatar,
Button, Button,
@ -28,12 +28,12 @@ import {
withTheme, withTheme,
} from 'react-native-paper'; } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen'; import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
import CustomHTML from '../../../components/Overrides/CustomHTML'; import CustomHTML from '../../../components/Overrides/CustomHTML';
import CustomTabBar from '../../../components/Tabbar/CustomTabBar'; import CustomTabBar from '../../../components/Tabbar/CustomTabBar';
import type {ClubCategoryType, ClubType} from './ClubListScreen'; import type { ClubCategoryType, ClubType } from './ClubListScreen';
import {ERROR_TYPE} from '../../../utils/WebData'; import { ERROR_TYPE } from '../../../utils/WebData';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
import ImageGalleryButton from '../../../components/Media/ImageGalleryButton'; import ImageGalleryButton from '../../../components/Media/ImageGalleryButton';
@ -51,6 +51,37 @@ type PropsType = {
const AMICALE_MAIL = 'clubs@amicale-insat.fr'; const AMICALE_MAIL = 'clubs@amicale-insat.fr';
const styles = StyleSheet.create({
category: {
marginRight: 5,
},
categoryContainer: {
flexDirection: 'row',
marginTop: 5,
},
card: {
marginTop: 10,
},
icon: {
backgroundColor: 'transparent',
},
emailButton: {
marginLeft: 'auto',
},
scroll: {
paddingLeft: 5,
paddingRight: 5,
},
imageButton: {
width: 300,
height: 300,
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 10,
marginBottom: 10,
},
});
/** /**
* Class defining a club event information page. * Class defining a club event information page.
* If called with data and categories navigation parameters, will use those to display the data. * If called with data and categories navigation parameters, will use those to display the data.
@ -117,13 +148,13 @@ class ClubDisplayScreen extends React.Component<PropsType> {
categories.forEach((cat: number | null) => { categories.forEach((cat: number | null) => {
if (cat != null) { if (cat != null) {
final.push( final.push(
<Chip style={{marginRight: 5}} key={cat}> <Chip style={styles.category} key={cat}>
{this.getCategoryName(cat)} {this.getCategoryName(cat)}
</Chip>, </Chip>
); );
} }
}); });
return <View style={{flexDirection: 'row', marginTop: 5}}>{final}</View>; return <View style={styles.categoryContainer}>{final}</View>;
} }
/** /**
@ -134,7 +165,7 @@ class ClubDisplayScreen extends React.Component<PropsType> {
* @returns {*} * @returns {*}
*/ */
getManagersRender(managers: Array<string>, email: string | null) { getManagersRender(managers: Array<string>, email: string | null) {
const {props} = this; const { props } = this;
const managersListView: Array<React.ReactNode> = []; const managersListView: Array<React.ReactNode> = [];
managers.forEach((item: string) => { managers.forEach((item: string) => {
managersListView.push(<Paragraph key={item}>{item}</Paragraph>); managersListView.push(<Paragraph key={item}>{item}</Paragraph>);
@ -142,7 +173,11 @@ class ClubDisplayScreen extends React.Component<PropsType> {
const hasManagers = managers.length > 0; const hasManagers = managers.length > 0;
return ( return (
<Card <Card
style={{marginTop: 10, marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}> style={{
marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20,
...styles.card,
}}
>
<Card.Title <Card.Title
title={i18n.t('screens.clubs.managers')} title={i18n.t('screens.clubs.managers')}
subtitle={ subtitle={
@ -153,7 +188,7 @@ class ClubDisplayScreen extends React.Component<PropsType> {
left={(iconProps) => ( left={(iconProps) => (
<Avatar.Icon <Avatar.Icon
size={iconProps.size} size={iconProps.size}
style={{backgroundColor: 'transparent'}} style={styles.icon}
color={ color={
hasManagers hasManagers
? props.theme.colors.success ? props.theme.colors.success
@ -193,7 +228,8 @@ class ClubDisplayScreen extends React.Component<PropsType> {
onPress={() => { onPress={() => {
Linking.openURL(`mailto:${destinationEmail}`); Linking.openURL(`mailto:${destinationEmail}`);
}} }}
style={{marginLeft: 'auto'}}> style={styles.emailButton}
>
{text} {text}
</Button> </Button>
</Card.Actions> </Card.Actions>
@ -205,19 +241,12 @@ class ClubDisplayScreen extends React.Component<PropsType> {
if (data != null) { if (data != null) {
this.updateHeaderTitle(data); this.updateHeaderTitle(data);
return ( return (
<CollapsibleScrollView style={{paddingLeft: 5, paddingRight: 5}} hasTab> <CollapsibleScrollView style={styles.scroll} hasTab>
{this.getCategoriesRender(data.category)} {this.getCategoriesRender(data.category)}
{data.logo !== null ? ( {data.logo !== null ? (
<ImageGalleryButton <ImageGalleryButton
images={[{url: data.logo}]} images={[{ url: data.logo }]}
style={{ style={styles.imageButton}
width: 300,
height: 300,
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 10,
marginBottom: 10,
}}
/> />
) : ( ) : (
<View /> <View />
@ -244,12 +273,12 @@ class ClubDisplayScreen extends React.Component<PropsType> {
* @param data The club data * @param data The club data
*/ */
updateHeaderTitle(data: ClubType) { updateHeaderTitle(data: ClubType) {
const {props} = this; const { props } = this;
props.navigation.setOptions({title: data.name}); props.navigation.setOptions({ title: data.name });
} }
render() { render() {
const {props} = this; const { props } = this;
if (this.shouldFetchData) { if (this.shouldFetchData) {
return ( return (
<AuthenticatedScreen <AuthenticatedScreen
@ -257,7 +286,7 @@ class ClubDisplayScreen extends React.Component<PropsType> {
requests={[ requests={[
{ {
link: 'clubs/info', link: 'clubs/info',
params: {id: this.clubId}, params: { id: this.clubId },
mandatory: true, mandatory: true,
}, },
]} ]}

View file

@ -18,13 +18,16 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Platform} from 'react-native'; import { Platform } from 'react-native';
import {Searchbar} from 'react-native-paper'; import { Searchbar } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen'; import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
import ClubListItem from '../../../components/Lists/Clubs/ClubListItem'; import ClubListItem from '../../../components/Lists/Clubs/ClubListItem';
import {isItemInCategoryFilter, stringMatchQuery} from '../../../utils/Search'; import {
isItemInCategoryFilter,
stringMatchQuery,
} from '../../../utils/Search';
import ClubListHeader from '../../../components/Lists/Clubs/ClubListHeader'; import ClubListHeader from '../../../components/Lists/Clubs/ClubListHeader';
import MaterialHeaderButtons, { import MaterialHeaderButtons, {
Item, Item,
@ -73,15 +76,15 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
* Creates the header content * Creates the header content
*/ */
componentDidMount() { componentDidMount() {
const {props} = this; const { props } = this;
props.navigation.setOptions({ props.navigation.setOptions({
headerTitle: this.getSearchBar, headerTitle: this.getSearchBar,
headerRight: this.getHeaderButtons, headerRight: this.getHeaderButtons,
headerBackTitleVisible: false, headerBackTitleVisible: false,
headerTitleContainerStyle: headerTitleContainerStyle:
Platform.OS === 'ios' Platform.OS === 'ios'
? {marginHorizontal: 0, width: '70%'} ? { marginHorizontal: 0, width: '70%' }
: {marginHorizontal: 0, right: 50, left: 50}, : { marginHorizontal: 0, right: 50, left: 50 },
}); });
} }
@ -92,7 +95,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
* @param item The article pressed * @param item The article pressed
*/ */
onListItemPress(item: ClubType) { onListItemPress(item: ClubType) {
const {props} = this; const { props } = this;
props.navigation.navigate('club-information', { props.navigation.navigate('club-information', {
data: item, data: item,
categories: this.categories, categories: this.categories,
@ -133,7 +136,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
*/ */
getHeaderButtons = () => { getHeaderButtons = () => {
const onPress = () => { const onPress = () => {
const {props} = this; const { props } = this;
props.navigation.navigate('club-about'); props.navigation.navigate('club-about');
}; };
return ( return (
@ -147,7 +150,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
data: Array<{ data: Array<{
categories: Array<ClubCategoryType>; categories: Array<ClubCategoryType>;
clubs: Array<ClubType>; clubs: Array<ClubType>;
} | null>, } | null>
) => { ) => {
let categoryList: Array<ClubCategoryType> = []; let categoryList: Array<ClubCategoryType> = [];
let clubList: Array<ClubType> = []; let clubList: Array<ClubType> = [];
@ -175,7 +178,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
* @returns {*} * @returns {*}
*/ */
getListHeader() { getListHeader() {
const {state} = this; const { state } = this;
return ( return (
<ClubListHeader <ClubListHeader
categories={this.categories} categories={this.categories}
@ -201,7 +204,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
return cat; return cat;
}; };
getRenderItem = ({item}: {item: ClubType}) => { getRenderItem = ({ item }: { item: ClubType }) => {
const onPress = () => { const onPress = () => {
this.onListItemPress(item); this.onListItemPress(item);
}; };
@ -222,8 +225,8 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
itemLayout = ( itemLayout = (
data: Array<ClubType> | null | undefined, data: Array<ClubType> | null | undefined,
index: number, index: number
): {length: number; offset: number; index: number} => ({ ): { length: number; offset: number; index: number } => ({
length: LIST_ITEM_HEIGHT, length: LIST_ITEM_HEIGHT,
offset: LIST_ITEM_HEIGHT * index, offset: LIST_ITEM_HEIGHT * index,
index, index,
@ -239,7 +242,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
* @param categoryId The category to add/remove from the filter * @param categoryId The category to add/remove from the filter
*/ */
updateFilteredData(filterStr: string | null, categoryId: number | null) { updateFilteredData(filterStr: string | null, categoryId: number | null) {
const {state} = this; const { state } = this;
const newCategoriesState = [...state.currentlySelectedCategories]; const newCategoriesState = [...state.currentlySelectedCategories];
let newStrState = state.currentSearchString; let newStrState = state.currentSearchString;
if (filterStr !== null) { if (filterStr !== null) {
@ -268,7 +271,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
* @returns {boolean} * @returns {boolean}
*/ */
shouldRenderItem(item: ClubType): boolean { shouldRenderItem(item: ClubType): boolean {
const {state} = this; const { state } = this;
let shouldRender = let shouldRender =
state.currentlySelectedCategories.length === 0 || state.currentlySelectedCategories.length === 0 ||
isItemInCategoryFilter(state.currentlySelectedCategories, item.category); isItemInCategoryFilter(state.currentlySelectedCategories, item.category);
@ -279,7 +282,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
} }
render() { render() {
const {props} = this; const { props } = this;
return ( return (
<AuthenticatedScreen <AuthenticatedScreen
navigation={props.navigation} navigation={props.navigation}

View file

@ -26,12 +26,13 @@ import {
Title, Title,
useTheme, useTheme,
} from 'react-native-paper'; } from 'react-native-paper';
import {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {getRelativeDateString} from '../../../utils/EquipmentBooking'; import { getRelativeDateString } from '../../../utils/EquipmentBooking';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
import {StackScreenProps} from '@react-navigation/stack'; import { StackScreenProps } from '@react-navigation/stack';
import {MainStackParamsList} from '../../../navigation/MainNavigator'; import { MainStackParamsList } from '../../../navigation/MainNavigator';
import GENERAL_STYLES from '../../../constants/Styles';
type EquipmentConfirmScreenNavigationProp = StackScreenProps< type EquipmentConfirmScreenNavigationProp = StackScreenProps<
MainStackParamsList, MainStackParamsList,
@ -40,6 +41,32 @@ type EquipmentConfirmScreenNavigationProp = StackScreenProps<
type Props = EquipmentConfirmScreenNavigationProp; type Props = EquipmentConfirmScreenNavigationProp;
const styles = StyleSheet.create({
card: {
margin: 5,
},
titleContainer: {
marginLeft: 'auto',
marginRight: 'auto',
flexDirection: 'row',
flexWrap: 'wrap',
},
title: {
textAlign: 'center',
},
caption: {
textAlign: 'center',
lineHeight: 35,
marginLeft: 10,
},
subtitle: {
textAlign: 'center',
},
text: {
textAlign: 'center',
},
});
function EquipmentConfirmScreen(props: Props) { function EquipmentConfirmScreen(props: Props) {
const theme = useTheme(); const theme = useTheme();
const item = props.route.params?.item; const item = props.route.params?.item;
@ -63,31 +90,20 @@ function EquipmentConfirmScreen(props: Props) {
} }
return ( return (
<CollapsibleScrollView> <CollapsibleScrollView>
<Card style={{margin: 5}}> <Card style={styles.card}>
<Card.Content> <Card.Content>
<View style={{flex: 1}}> <View style={GENERAL_STYLES.flex}>
<View <View style={styles.titleContainer}>
style={{ <Headline style={styles.title}>{item.name}</Headline>
marginLeft: 'auto', <Caption style={styles.caption}>
marginRight: 'auto', ({i18n.t('screens.equipment.bail', { cost: item.caution })})
flexDirection: 'row',
flexWrap: 'wrap',
}}>
<Headline style={{textAlign: 'center'}}>{item.name}</Headline>
<Caption
style={{
textAlign: 'center',
lineHeight: 35,
marginLeft: 10,
}}>
({i18n.t('screens.equipment.bail', {cost: item.caution})})
</Caption> </Caption>
</View> </View>
</View> </View>
<Title style={{color: theme.colors.success, textAlign: 'center'}}> <Title style={{ color: theme.colors.success, ...styles.subtitle }}>
{buttonText} {buttonText}
</Title> </Title>
<Paragraph style={{textAlign: 'center'}}> <Paragraph style={styles.text}>
{i18n.t('screens.equipment.bookingConfirmedMessage')} {i18n.t('screens.equipment.bookingConfirmedMessage')}
</Paragraph> </Paragraph>
</Card.Content> </Card.Content>

View file

@ -18,16 +18,17 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native'; import { StyleSheet, View } from 'react-native';
import {Button} from 'react-native-paper'; import { Button } from 'react-native-paper';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen'; import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem'; import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
import MascotPopup from '../../../components/Mascot/MascotPopup'; import MascotPopup from '../../../components/Mascot/MascotPopup';
import {MASCOT_STYLE} from '../../../components/Mascot/Mascot'; import { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
import AsyncStorageManager from '../../../managers/AsyncStorageManager'; import AsyncStorageManager from '../../../managers/AsyncStorageManager';
import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
import GENERAL_STYLES from '../../../constants/Styles';
type PropsType = { type PropsType = {
navigation: StackNavigationProp<any>; navigation: StackNavigationProp<any>;
@ -41,7 +42,7 @@ export type DeviceType = {
id: number; id: number;
name: string; name: string;
caution: number; caution: number;
booked_at: Array<{begin: string; end: string}>; booked_at: Array<{ begin: string; end: string }>;
}; };
export type RentedDeviceType = { export type RentedDeviceType = {
@ -53,10 +54,18 @@ export type RentedDeviceType = {
const LIST_ITEM_HEIGHT = 64; const LIST_ITEM_HEIGHT = 64;
const styles = StyleSheet.create({
headerContainer: {
width: '100%',
marginTop: 10,
marginBottom: 10,
},
});
class EquipmentListScreen extends React.Component<PropsType, StateType> { class EquipmentListScreen extends React.Component<PropsType, StateType> {
userRents: null | Array<RentedDeviceType>; userRents: null | Array<RentedDeviceType>;
authRef: {current: null | AuthenticatedScreen<any>}; authRef: { current: null | AuthenticatedScreen<any> };
canRefresh: boolean; canRefresh: boolean;
@ -65,7 +74,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
this.userRents = null; this.userRents = null;
this.state = { this.state = {
mascotDialogVisible: AsyncStorageManager.getBool( mascotDialogVisible: AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.equipmentShowMascot.key, AsyncStorageManager.PREFERENCES.equipmentShowMascot.key
), ),
}; };
this.canRefresh = false; this.canRefresh = false;
@ -84,8 +93,8 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
this.canRefresh = true; this.canRefresh = true;
}; };
getRenderItem = ({item}: {item: DeviceType}) => { getRenderItem = ({ item }: { item: DeviceType }) => {
const {navigation} = this.props; const { navigation } = this.props;
return ( return (
<EquipmentListItem <EquipmentListItem
navigation={navigation} navigation={navigation}
@ -115,20 +124,13 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
*/ */
getListHeader() { getListHeader() {
return ( return (
<View <View style={styles.headerContainer}>
style={{
width: '100%',
marginTop: 10,
marginBottom: 10,
}}>
<Button <Button
mode="contained" mode="contained"
icon="help-circle" icon="help-circle"
onPress={this.showMascotDialog} onPress={this.showMascotDialog}
style={{ style={GENERAL_STYLES.centerHorizontal}
marginRight: 'auto', >
marginLeft: 'auto',
}}>
{i18n.t('screens.equipment.mascotDialog.title')} {i18n.t('screens.equipment.mascotDialog.title')}
</Button> </Button>
</View> </View>
@ -145,8 +147,10 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
*/ */
getScreen = ( getScreen = (
data: Array< data: Array<
{devices: Array<DeviceType>} | {locations: Array<RentedDeviceType>} | null | { devices: Array<DeviceType> }
>, | { locations: Array<RentedDeviceType> }
| null
>
) => { ) => {
const [allDevices, userRents] = data; const [allDevices, userRents] = data;
if (userRents) { if (userRents) {
@ -161,7 +165,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
ListHeaderComponent={this.getListHeader()} ListHeaderComponent={this.getListHeader()}
data={ data={
allDevices allDevices
? (allDevices as {devices: Array<DeviceType>}).devices ? (allDevices as { devices: Array<DeviceType> }).devices
: null : null
} }
/> />
@ -169,21 +173,21 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
}; };
showMascotDialog = () => { showMascotDialog = () => {
this.setState({mascotDialogVisible: true}); this.setState({ mascotDialogVisible: true });
}; };
hideMascotDialog = () => { hideMascotDialog = () => {
AsyncStorageManager.set( AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.equipmentShowMascot.key, AsyncStorageManager.PREFERENCES.equipmentShowMascot.key,
false, false
); );
this.setState({mascotDialogVisible: false}); this.setState({ mascotDialogVisible: false });
}; };
render() { render() {
const {props, state} = this; const { props, state } = this;
return ( return (
<View style={{flex: 1}}> <View style={GENERAL_STYLES.flex}>
<AuthenticatedScreen <AuthenticatedScreen
navigation={props.navigation} navigation={props.navigation}
ref={this.authRef} ref={this.authRef}

View file

@ -26,12 +26,12 @@ import {
Subheading, Subheading,
withTheme, withTheme,
} from 'react-native-paper'; } from 'react-native-paper';
import {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack';
import {BackHandler, View} from 'react-native'; import { BackHandler, StyleSheet, View } from 'react-native';
import * as Animatable from 'react-native-animatable'; import * as Animatable from 'react-native-animatable';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {CalendarList, PeriodMarking} from 'react-native-calendars'; import { CalendarList, PeriodMarking } from 'react-native-calendars';
import type {DeviceType} from './EquipmentListScreen'; import type { DeviceType } from './EquipmentListScreen';
import LoadingConfirmDialog from '../../../components/Dialogs/LoadingConfirmDialog'; import LoadingConfirmDialog from '../../../components/Dialogs/LoadingConfirmDialog';
import ErrorDialog from '../../../components/Dialogs/ErrorDialog'; import ErrorDialog from '../../../components/Dialogs/ErrorDialog';
import { import {
@ -44,7 +44,8 @@ import {
} from '../../../utils/EquipmentBooking'; } from '../../../utils/EquipmentBooking';
import ConnectionManager from '../../../managers/ConnectionManager'; import ConnectionManager from '../../../managers/ConnectionManager';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
import {MainStackParamsList} from '../../../navigation/MainNavigator'; import { MainStackParamsList } from '../../../navigation/MainNavigator';
import GENERAL_STYLES from '../../../constants/Styles';
type EquipmentRentScreenNavigationProp = StackScreenProps< type EquipmentRentScreenNavigationProp = StackScreenProps<
MainStackParamsList, MainStackParamsList,
@ -67,12 +68,56 @@ type StateType = {
currentError: number; currentError: number;
}; };
const styles = StyleSheet.create({
titleContainer: {
marginLeft: 'auto',
marginRight: 'auto',
flexDirection: 'row',
flexWrap: 'wrap',
},
title: {
textAlign: 'center',
},
caption: {
textAlign: 'center',
lineHeight: 35,
marginLeft: 10,
},
card: {
margin: 5,
},
subtitle: {
textAlign: 'center',
marginBottom: 10,
minHeight: 50,
},
calendar: {
marginBottom: 50,
},
buttonContainer: {
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
flex: 1,
transform: [{ translateY: 100 }],
},
button: {
width: '80%',
flex: 1,
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 20,
borderRadius: 10,
},
});
class EquipmentRentScreen extends React.Component<Props, StateType> { class EquipmentRentScreen extends React.Component<Props, StateType> {
item: DeviceType | null; item: DeviceType | null;
bookedDates: Array<string>; bookedDates: Array<string>;
bookRef: {current: null | (Animatable.View & View)}; bookRef: { current: null | (Animatable.View & View) };
canBookEquipment: boolean; canBookEquipment: boolean;
@ -101,14 +146,14 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
this.item = null; this.item = null;
} }
} }
const {item} = this; const { item } = this;
if (item != null) { if (item != null) {
this.lockedDates = {}; this.lockedDates = {};
item.booked_at.forEach((date: {begin: string; end: string}) => { item.booked_at.forEach((date: { begin: string; end: string }) => {
const range = getValidRange( const range = getValidRange(
new Date(date.begin), new Date(date.begin),
new Date(date.end), new Date(date.end),
null, null
); );
this.lockedDates = { this.lockedDates = {
...this.lockedDates, ...this.lockedDates,
@ -122,17 +167,17 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
* Captures focus and blur events to hook on android back button * Captures focus and blur events to hook on android back button
*/ */
componentDidMount() { componentDidMount() {
const {navigation} = this.props; const { navigation } = this.props;
navigation.addListener('focus', () => { navigation.addListener('focus', () => {
BackHandler.addEventListener( BackHandler.addEventListener(
'hardwareBackPress', 'hardwareBackPress',
this.onBackButtonPressAndroid, this.onBackButtonPressAndroid
); );
}); });
navigation.addListener('blur', () => { navigation.addListener('blur', () => {
BackHandler.removeEventListener( BackHandler.removeEventListener(
'hardwareBackPress', 'hardwareBackPress',
this.onBackButtonPressAndroid, this.onBackButtonPressAndroid
); );
}); });
} }
@ -152,11 +197,11 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
}; };
onDialogDismiss = () => { onDialogDismiss = () => {
this.setState({dialogVisible: false}); this.setState({ dialogVisible: false });
}; };
onErrorDialogDismiss = () => { onErrorDialogDismiss = () => {
this.setState({errorDialogVisible: false}); this.setState({ errorDialogVisible: false });
}; };
/** /**
@ -168,7 +213,7 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
*/ */
onDialogAccept = (): Promise<void> => { onDialogAccept = (): Promise<void> => {
return new Promise((resolve: () => void) => { return new Promise((resolve: () => void) => {
const {item, props} = this; const { item, props } = this;
const start = this.getBookStartDate(); const start = this.getBookStartDate();
const end = this.getBookEndDate(); const end = this.getBookEndDate();
if (item != null && start != null && end != null) { if (item != null && start != null && end != null) {
@ -203,7 +248,7 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
} }
getBookEndDate(): Date | null { getBookEndDate(): Date | null {
const {length} = this.bookedDates; const { length } = this.bookedDates;
return length > 0 ? new Date(this.bookedDates[length - 1]) : null; return length > 0 ? new Date(this.bookedDates[length - 1]) : null;
} }
@ -247,7 +292,7 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
}; };
showDialog = () => { showDialog = () => {
this.setState({dialogVisible: true}); this.setState({ dialogVisible: true });
}; };
/** /**
@ -288,14 +333,14 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
} }
updateMarkedSelection() { updateMarkedSelection() {
const {theme} = this.props; const { theme } = this.props;
this.setState({ this.setState({
markedDates: generateMarkedDates(true, theme, this.bookedDates), markedDates: generateMarkedDates(true, theme, this.bookedDates),
}); });
} }
render() { render() {
const {item, props, state} = this; const { item, props, state } = this;
const start = this.getBookStartDate(); const start = this.getBookStartDate();
const end = this.getBookEndDate(); const end = this.getBookEndDate();
let subHeadingText; let subHeadingText;
@ -315,28 +360,17 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
const isAvailable = isEquipmentAvailable(item); const isAvailable = isEquipmentAvailable(item);
const firstAvailability = getFirstEquipmentAvailability(item); const firstAvailability = getFirstEquipmentAvailability(item);
return ( return (
<View style={{flex: 1}}> <View style={GENERAL_STYLES.flex}>
<CollapsibleScrollView> <CollapsibleScrollView>
<Card style={{margin: 5}}> <Card style={styles.card}>
<Card.Content> <Card.Content>
<View style={{flex: 1}}> <View style={GENERAL_STYLES.flex}>
<View <View style={styles.titleContainer}>
style={{ <Headline style={styles.title}>{item.name}</Headline>
marginLeft: 'auto', <Caption style={styles.caption}>
marginRight: 'auto', (
flexDirection: 'row', {i18n.t('screens.equipment.bail', { cost: item.caution })}
flexWrap: 'wrap', )
}}>
<Headline style={{textAlign: 'center'}}>
{item.name}
</Headline>
<Caption
style={{
textAlign: 'center',
lineHeight: 35,
marginLeft: 10,
}}>
({i18n.t('screens.equipment.bail', {cost: item.caution})})
</Caption> </Caption>
</View> </View>
</View> </View>
@ -348,17 +382,13 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
? props.theme.colors.success ? props.theme.colors.success
: props.theme.colors.primary : props.theme.colors.primary
} }
mode="text"> mode="text"
>
{i18n.t('screens.equipment.available', { {i18n.t('screens.equipment.available', {
date: getRelativeDateString(firstAvailability), date: getRelativeDateString(firstAvailability),
})} })}
</Button> </Button>
<Subheading <Subheading style={styles.subtitle}>
style={{
textAlign: 'center',
marginBottom: 10,
minHeight: 50,
}}>
{subHeadingText} {subHeadingText}
</Subheading> </Subheading>
</Card.Content> </Card.Content>
@ -382,30 +412,30 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
hideArrows={false} hideArrows={false}
// Date marking style [simple/period/multi-dot/custom]. Default = 'simple' // Date marking style [simple/period/multi-dot/custom]. Default = 'simple'
markingType={'period'} markingType={'period'}
markedDates={{...this.lockedDates, ...state.markedDates}} markedDates={{ ...this.lockedDates, ...state.markedDates }}
theme={{ theme={{
backgroundColor: props.theme.colors.agendaBackgroundColor, 'backgroundColor': props.theme.colors.agendaBackgroundColor,
calendarBackground: props.theme.colors.background, 'calendarBackground': props.theme.colors.background,
textSectionTitleColor: props.theme.colors.agendaDayTextColor, 'textSectionTitleColor': props.theme.colors.agendaDayTextColor,
selectedDayBackgroundColor: props.theme.colors.primary, 'selectedDayBackgroundColor': props.theme.colors.primary,
selectedDayTextColor: '#ffffff', 'selectedDayTextColor': '#ffffff',
todayTextColor: props.theme.colors.text, 'todayTextColor': props.theme.colors.text,
dayTextColor: props.theme.colors.text, 'dayTextColor': props.theme.colors.text,
textDisabledColor: props.theme.colors.agendaDayTextColor, 'textDisabledColor': props.theme.colors.agendaDayTextColor,
dotColor: props.theme.colors.primary, 'dotColor': props.theme.colors.primary,
selectedDotColor: '#ffffff', 'selectedDotColor': '#ffffff',
arrowColor: props.theme.colors.primary, 'arrowColor': props.theme.colors.primary,
monthTextColor: props.theme.colors.text, 'monthTextColor': props.theme.colors.text,
indicatorColor: props.theme.colors.primary, 'indicatorColor': props.theme.colors.primary,
textDayFontFamily: 'monospace', 'textDayFontFamily': 'monospace',
textMonthFontFamily: 'monospace', 'textMonthFontFamily': 'monospace',
textDayHeaderFontFamily: 'monospace', 'textDayHeaderFontFamily': 'monospace',
textDayFontWeight: '300', 'textDayFontWeight': '300',
textMonthFontWeight: 'bold', 'textMonthFontWeight': 'bold',
textDayHeaderFontWeight: '300', 'textDayHeaderFontWeight': '300',
textDayFontSize: 16, 'textDayFontSize': 16,
textMonthFontSize: 16, 'textMonthFontSize': 16,
textDayHeaderFontSize: 16, 'textDayHeaderFontSize': 16,
'stylesheet.day.period': { 'stylesheet.day.period': {
base: { base: {
overflow: 'hidden', overflow: 'hidden',
@ -415,7 +445,7 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
}, },
}, },
}} }}
style={{marginBottom: 50}} style={styles.calendar}
/> />
</CollapsibleScrollView> </CollapsibleScrollView>
<LoadingConfirmDialog <LoadingConfirmDialog
@ -435,26 +465,14 @@ class EquipmentRentScreen extends React.Component<Props, StateType> {
<Animatable.View <Animatable.View
ref={this.bookRef} ref={this.bookRef}
useNativeDriver useNativeDriver
style={{ style={styles.buttonContainer}
position: 'absolute', >
bottom: 0,
left: 0,
width: '100%',
flex: 1,
transform: [{translateY: 100}],
}}>
<Button <Button
icon="bookmark-check" icon="bookmark-check"
mode="contained" mode="contained"
onPress={this.showDialog} onPress={this.showDialog}
style={{ style={styles.button}
width: '80%', >
flex: 1,
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 20,
borderRadius: 10,
}}>
{i18n.t('screens.equipment.bookButton')} {i18n.t('screens.equipment.bookButton')}
</Button> </Button>
</Animatable.View> </Animatable.View>

View file

@ -18,7 +18,7 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {Image, KeyboardAvoidingView, StyleSheet, View} from 'react-native'; import { Image, KeyboardAvoidingView, StyleSheet, View } from 'react-native';
import { import {
Button, Button,
Card, Card,
@ -27,16 +27,17 @@ import {
withTheme, withTheme,
} from 'react-native-paper'; } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import ConnectionManager from '../../managers/ConnectionManager'; import ConnectionManager from '../../managers/ConnectionManager';
import ErrorDialog from '../../components/Dialogs/ErrorDialog'; import ErrorDialog from '../../components/Dialogs/ErrorDialog';
import AsyncStorageManager from '../../managers/AsyncStorageManager'; import AsyncStorageManager from '../../managers/AsyncStorageManager';
import AvailableWebsites from '../../constants/AvailableWebsites'; import AvailableWebsites from '../../constants/AvailableWebsites';
import {MASCOT_STYLE} from '../../components/Mascot/Mascot'; import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
import MascotPopup from '../../components/Mascot/MascotPopup'; import MascotPopup from '../../components/Mascot/MascotPopup';
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView'; import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
import {MainStackParamsList} from '../../navigation/MainNavigator'; import { MainStackParamsList } from '../../navigation/MainNavigator';
import GENERAL_STYLES from '../../constants/Styles';
type LoginScreenNavigationProp = StackScreenProps<MainStackParamsList, 'login'>; type LoginScreenNavigationProp = StackScreenProps<MainStackParamsList, 'login'>;
@ -63,9 +64,6 @@ const RESET_PASSWORD_PATH = 'https://www.amicale-insat.fr/password/reset';
const emailRegex = /^.+@.+\..+$/; const emailRegex = /^.+@.+\..+$/;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: {
flex: 1,
},
card: { card: {
marginTop: 'auto', marginTop: 'auto',
marginBottom: 'auto', marginBottom: 'auto',
@ -74,10 +72,18 @@ const styles = StyleSheet.create({
fontSize: 36, fontSize: 36,
marginBottom: 48, marginBottom: 48,
}, },
textInput: {}, text: {
btnContainer: { color: '#ffffff',
marginTop: 5, },
marginBottom: 10, buttonContainer: {
flexWrap: 'wrap',
},
lockButton: {
marginRight: 'auto',
marginBottom: 20,
},
sendButton: {
marginLeft: 'auto',
}, },
}); });
@ -113,7 +119,7 @@ class LoginScreen extends React.Component<Props, StateType> {
dialogVisible: false, dialogVisible: false,
dialogError: 0, dialogError: 0,
mascotDialogVisible: AsyncStorageManager.getBool( mascotDialogVisible: AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.loginShowMascot.key, AsyncStorageManager.PREFERENCES.loginShowMascot.key
), ),
}; };
} }
@ -126,7 +132,7 @@ class LoginScreen extends React.Component<Props, StateType> {
* Navigates to the Amicale website screen with the reset password link as navigation parameters * Navigates to the Amicale website screen with the reset password link as navigation parameters
*/ */
onResetPasswordClick = () => { onResetPasswordClick = () => {
const {navigation} = this.props; const { navigation } = this.props;
navigation.navigate('website', { navigation.navigate('website', {
host: AvailableWebsites.websites.AMICALE, host: AvailableWebsites.websites.AMICALE,
path: RESET_PASSWORD_PATH, path: RESET_PASSWORD_PATH,
@ -174,15 +180,15 @@ class LoginScreen extends React.Component<Props, StateType> {
* *
*/ */
onSubmit = () => { onSubmit = () => {
const {email, password} = this.state; const { email, password } = this.state;
if (this.shouldEnableLogin()) { if (this.shouldEnableLogin()) {
this.setState({loading: true}); this.setState({ loading: true });
ConnectionManager.getInstance() ConnectionManager.getInstance()
.connect(email, password) .connect(email, password)
.then(this.handleSuccess) .then(this.handleSuccess)
.catch(this.showErrorDialog) .catch(this.showErrorDialog)
.finally(() => { .finally(() => {
this.setState({loading: false}); this.setState({ loading: false });
}); });
} }
}; };
@ -193,7 +199,7 @@ class LoginScreen extends React.Component<Props, StateType> {
* @returns {*} * @returns {*}
*/ */
getFormInput() { getFormInput() {
const {email, password} = this.state; const { email, password } = this.state;
return ( return (
<View> <View>
<TextInput <TextInput
@ -244,15 +250,15 @@ class LoginScreen extends React.Component<Props, StateType> {
* @returns {*} * @returns {*}
*/ */
getMainCard() { getMainCard() {
const {props, state} = this; const { props, state } = this;
return ( return (
<View style={styles.card}> <View style={styles.card}>
<Card.Title <Card.Title
title={i18n.t('screens.login.title')} title={i18n.t('screens.login.title')}
titleStyle={{color: '#fff'}} titleStyle={styles.text}
subtitle={i18n.t('screens.login.subtitle')} subtitle={i18n.t('screens.login.subtitle')}
subtitleStyle={{color: '#fff'}} subtitleStyle={styles.text}
left={({size}) => ( left={({ size }) => (
<Image <Image
source={ICON_AMICALE} source={ICON_AMICALE}
style={{ style={{
@ -264,13 +270,14 @@ class LoginScreen extends React.Component<Props, StateType> {
/> />
<Card.Content> <Card.Content>
{this.getFormInput()} {this.getFormInput()}
<Card.Actions style={{flexWrap: 'wrap'}}> <Card.Actions style={styles.buttonContainer}>
<Button <Button
icon="lock-question" icon="lock-question"
mode="contained" mode="contained"
onPress={this.onResetPasswordClick} onPress={this.onResetPasswordClick}
color={props.theme.colors.warning} color={props.theme.colors.warning}
style={{marginRight: 'auto', marginBottom: 20}}> style={styles.lockButton}
>
{i18n.t('screens.login.resetPassword')} {i18n.t('screens.login.resetPassword')}
</Button> </Button>
<Button <Button
@ -279,7 +286,8 @@ class LoginScreen extends React.Component<Props, StateType> {
disabled={!this.shouldEnableLogin()} disabled={!this.shouldEnableLogin()}
loading={state.loading} loading={state.loading}
onPress={this.onSubmit} onPress={this.onSubmit}
style={{marginLeft: 'auto'}}> style={styles.sendButton}
>
{i18n.t('screens.login.title')} {i18n.t('screens.login.title')}
</Button> </Button>
</Card.Actions> </Card.Actions>
@ -288,10 +296,8 @@ class LoginScreen extends React.Component<Props, StateType> {
icon="help-circle" icon="help-circle"
mode="contained" mode="contained"
onPress={this.showMascotDialog} onPress={this.showMascotDialog}
style={{ style={GENERAL_STYLES.centerHorizontal}
marginLeft: 'auto', >
marginRight: 'auto',
}}>
{i18n.t('screens.login.mascotDialog.title')} {i18n.t('screens.login.mascotDialog.title')}
</Button> </Button>
</Card.Actions> </Card.Actions>
@ -304,26 +310,26 @@ class LoginScreen extends React.Component<Props, StateType> {
* The user has unfocused the input, his email is ready to be validated * The user has unfocused the input, his email is ready to be validated
*/ */
validateEmail = () => { validateEmail = () => {
this.setState({isEmailValidated: true}); this.setState({ isEmailValidated: true });
}; };
/** /**
* The user has unfocused the input, his password is ready to be validated * The user has unfocused the input, his password is ready to be validated
*/ */
validatePassword = () => { validatePassword = () => {
this.setState({isPasswordValidated: true}); this.setState({ isPasswordValidated: true });
}; };
hideMascotDialog = () => { hideMascotDialog = () => {
AsyncStorageManager.set( AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.loginShowMascot.key, AsyncStorageManager.PREFERENCES.loginShowMascot.key,
false, false
); );
this.setState({mascotDialogVisible: false}); this.setState({ mascotDialogVisible: false });
}; };
showMascotDialog = () => { showMascotDialog = () => {
this.setState({mascotDialogVisible: true}); this.setState({ mascotDialogVisible: true });
}; };
/** /**
@ -339,7 +345,7 @@ class LoginScreen extends React.Component<Props, StateType> {
}; };
hideErrorDialog = () => { hideErrorDialog = () => {
this.setState({dialogVisible: false}); this.setState({ dialogVisible: false });
}; };
/** /**
@ -347,11 +353,11 @@ class LoginScreen extends React.Component<Props, StateType> {
* Saves in user preferences to not show the login banner again. * Saves in user preferences to not show the login banner again.
*/ */
handleSuccess = () => { handleSuccess = () => {
const {navigation} = this.props; const { navigation } = this.props;
// Do not show the home login banner again // Do not show the home login banner again
AsyncStorageManager.set( AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.homeShowMascot.key, AsyncStorageManager.PREFERENCES.homeShowMascot.key,
false, false
); );
if (this.nextScreen == null) { if (this.nextScreen == null) {
navigation.goBack(); navigation.goBack();
@ -373,7 +379,7 @@ class LoginScreen extends React.Component<Props, StateType> {
* @returns {boolean} * @returns {boolean}
*/ */
isEmailValid(): boolean { isEmailValid(): boolean {
const {email} = this.state; const { email } = this.state;
return emailRegex.test(email); return emailRegex.test(email);
} }
@ -384,7 +390,7 @@ class LoginScreen extends React.Component<Props, StateType> {
* @returns {boolean|boolean} * @returns {boolean|boolean}
*/ */
shouldShowEmailError(): boolean { shouldShowEmailError(): boolean {
const {isEmailValidated} = this.state; const { isEmailValidated } = this.state;
return isEmailValidated && !this.isEmailValid(); return isEmailValidated && !this.isEmailValid();
} }
@ -394,7 +400,7 @@ class LoginScreen extends React.Component<Props, StateType> {
* @returns {boolean} * @returns {boolean}
*/ */
isPasswordValid(): boolean { isPasswordValid(): boolean {
const {password} = this.state; const { password } = this.state;
return password !== ''; return password !== '';
} }
@ -405,7 +411,7 @@ class LoginScreen extends React.Component<Props, StateType> {
* @returns {boolean|boolean} * @returns {boolean|boolean}
*/ */
shouldShowPasswordError(): boolean { shouldShowPasswordError(): boolean {
const {isPasswordValidated} = this.state; const { isPasswordValidated } = this.state;
return isPasswordValidated && !this.isPasswordValid(); return isPasswordValidated && !this.isPasswordValid();
} }
@ -415,28 +421,28 @@ class LoginScreen extends React.Component<Props, StateType> {
* @returns {boolean} * @returns {boolean}
*/ */
shouldEnableLogin(): boolean { shouldEnableLogin(): boolean {
const {loading} = this.state; const { loading } = this.state;
return this.isEmailValid() && this.isPasswordValid() && !loading; return this.isEmailValid() && this.isPasswordValid() && !loading;
} }
render() { render() {
const {mascotDialogVisible, dialogVisible, dialogError} = this.state; const { mascotDialogVisible, dialogVisible, dialogError } = this.state;
return ( return (
<LinearGradient <LinearGradient
style={{ style={GENERAL_STYLES.flex}
height: '100%',
}}
colors={['#9e0d18', '#530209']} colors={['#9e0d18', '#530209']}
start={{x: 0, y: 0.1}} start={{ x: 0, y: 0.1 }}
end={{x: 0.1, y: 1}}> end={{ x: 0.1, y: 1 }}
>
<KeyboardAvoidingView <KeyboardAvoidingView
behavior="height" behavior="height"
contentContainerStyle={styles.container} contentContainerStyle={GENERAL_STYLES.flex}
style={styles.container} style={GENERAL_STYLES.flex}
enabled enabled
keyboardVerticalOffset={100}> keyboardVerticalOffset={100}
>
<CollapsibleScrollView> <CollapsibleScrollView>
<View style={{height: '100%'}}>{this.getMainCard()}</View> <View style={GENERAL_STYLES.flex}>{this.getMainCard()}</View>
<MascotPopup <MascotPopup
visible={mascotDialogVisible} visible={mascotDialogVisible}
title={i18n.t('screens.login.mascotDialog.title')} title={i18n.t('screens.login.mascotDialog.title')}

View file

@ -18,7 +18,7 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {FlatList, StyleSheet, View} from 'react-native'; import { FlatList, StyleSheet, View } from 'react-native';
import { import {
Avatar, Avatar,
Button, Button,
@ -29,7 +29,7 @@ import {
withTheme, withTheme,
} from 'react-native-paper'; } from 'react-native-paper';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import AuthenticatedScreen from '../../components/Amicale/AuthenticatedScreen'; import AuthenticatedScreen from '../../components/Amicale/AuthenticatedScreen';
import LogoutDialog from '../../components/Amicale/LogoutDialog'; import LogoutDialog from '../../components/Amicale/LogoutDialog';
import MaterialHeaderButtons, { import MaterialHeaderButtons, {
@ -37,10 +37,11 @@ import MaterialHeaderButtons, {
} from '../../components/Overrides/CustomHeaderButton'; } from '../../components/Overrides/CustomHeaderButton';
import CardList from '../../components/Lists/CardList/CardList'; import CardList from '../../components/Lists/CardList/CardList';
import AvailableWebsites from '../../constants/AvailableWebsites'; import AvailableWebsites from '../../constants/AvailableWebsites';
import Mascot, {MASCOT_STYLE} from '../../components/Mascot/Mascot'; import Mascot, { MASCOT_STYLE } from '../../components/Mascot/Mascot';
import ServicesManager, {SERVICES_KEY} from '../../managers/ServicesManager'; import ServicesManager, { SERVICES_KEY } from '../../managers/ServicesManager';
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
import type {ServiceItemType} from '../../managers/ServicesManager'; import type { ServiceItemType } from '../../managers/ServicesManager';
import GENERAL_STYLES from '../../constants/Styles';
type PropsType = { type PropsType = {
navigation: StackNavigationProp<any>; navigation: StackNavigationProp<any>;
@ -79,19 +80,25 @@ const styles = StyleSheet.create({
editButton: { editButton: {
marginLeft: 'auto', marginLeft: 'auto',
}, },
mascot: {
width: 60,
},
title: {
marginLeft: 10,
},
}); });
class ProfileScreen extends React.Component<PropsType, StateType> { class ProfileScreen extends React.Component<PropsType, StateType> {
data: ProfileDataType | null; data: ProfileDataType | null;
flatListData: Array<{id: string}>; flatListData: Array<{ id: string }>;
amicaleDataset: Array<ServiceItemType>; amicaleDataset: Array<ServiceItemType>;
constructor(props: PropsType) { constructor(props: PropsType) {
super(props); super(props);
this.data = null; this.data = null;
this.flatListData = [{id: '0'}, {id: '1'}, {id: '2'}, {id: '3'}]; this.flatListData = [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }];
const services = new ServicesManager(props.navigation); const services = new ServicesManager(props.navigation);
this.amicaleDataset = services.getAmicaleServices([SERVICES_KEY.PROFILE]); this.amicaleDataset = services.getAmicaleServices([SERVICES_KEY.PROFILE]);
this.state = { this.state = {
@ -100,7 +107,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
} }
componentDidMount() { componentDidMount() {
const {navigation} = this.props; const { navigation } = this.props;
navigation.setOptions({ navigation.setOptions({
headerRight: this.getHeaderButton, headerRight: this.getHeaderButton,
}); });
@ -128,10 +135,10 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
* @returns {*} * @returns {*}
*/ */
getScreen = (data: Array<ProfileDataType | null>) => { getScreen = (data: Array<ProfileDataType | null>) => {
const {dialogVisible} = this.state; const { dialogVisible } = this.state;
this.data = data[0]; this.data = data[0];
return ( return (
<View style={{flex: 1}}> <View style={GENERAL_STYLES.flex}>
<CollapsibleFlatList <CollapsibleFlatList
renderItem={this.getRenderItem} renderItem={this.getRenderItem}
data={this.flatListData} data={this.flatListData}
@ -144,7 +151,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
); );
}; };
getRenderItem = ({item}: {item: {id: string}}) => { getRenderItem = ({ item }: { item: { id: string } }) => {
switch (item.id) { switch (item.id) {
case '0': case '0':
return this.getWelcomeCard(); return this.getWelcomeCard();
@ -172,7 +179,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
* @returns {*} * @returns {*}
*/ */
getWelcomeCard() { getWelcomeCard() {
const {navigation} = this.props; const { navigation } = this.props;
return ( return (
<Card style={styles.card}> <Card style={styles.card}>
<Card.Title <Card.Title
@ -181,9 +188,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
})} })}
left={() => ( left={() => (
<Mascot <Mascot
style={{ style={styles.mascot}
width: 60,
}}
emotion={MASCOT_STYLE.COOL} emotion={MASCOT_STYLE.COOL}
animated animated
entryAnimation={{ entryAnimation={{
@ -192,7 +197,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
}} }}
/> />
)} )}
titleStyle={{marginLeft: 10}} titleStyle={styles.title}
/> />
<Card.Content> <Card.Content>
<Divider /> <Divider />
@ -207,7 +212,8 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
onPress={() => { onPress={() => {
navigation.navigate('feedback'); navigation.navigate('feedback');
}} }}
style={styles.editButton}> style={styles.editButton}
>
{i18n.t('screens.feedback.homeButtonTitle')} {i18n.t('screens.feedback.homeButtonTitle')}
</Button> </Button>
</Card.Actions> </Card.Actions>
@ -235,7 +241,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
* @return {*} * @return {*}
*/ */
getPersonalListItem(field: string | undefined, icon: string) { getPersonalListItem(field: string | undefined, icon: string) {
const {theme} = this.props; const { theme } = this.props;
const title = field != null ? ProfileScreen.getFieldValue(field) : ':('; const title = field != null ? ProfileScreen.getFieldValue(field) : ':(';
const subtitle = field != null ? '' : ProfileScreen.getFieldValue(field); const subtitle = field != null ? '' : ProfileScreen.getFieldValue(field);
return ( return (
@ -259,7 +265,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
* @return {*} * @return {*}
*/ */
getPersonalCard() { getPersonalCard() {
const {theme, navigation} = this.props; const { theme, navigation } = this.props;
return ( return (
<Card style={styles.card}> <Card style={styles.card}>
<Card.Title <Card.Title
@ -297,7 +303,8 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
title: i18n.t('screens.websites.amicale'), title: i18n.t('screens.websites.amicale'),
}); });
}} }}
style={styles.editButton}> style={styles.editButton}
>
{i18n.t('screens.profile.editInformation')} {i18n.t('screens.profile.editInformation')}
</Button> </Button>
</Card.Actions> </Card.Actions>
@ -312,7 +319,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
* @return {*} * @return {*}
*/ */
getClubCard() { getClubCard() {
const {theme} = this.props; const { theme } = this.props;
return ( return (
<Card style={styles.card}> <Card style={styles.card}>
<Card.Title <Card.Title
@ -341,7 +348,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
* @return {*} * @return {*}
*/ */
getMembershipCar() { getMembershipCar() {
const {theme} = this.props; const { theme } = this.props;
return ( return (
<Card style={styles.card}> <Card style={styles.card}>
<Card.Title <Card.Title
@ -371,7 +378,7 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
* @return {*} * @return {*}
*/ */
getMembershipItem(state: boolean) { getMembershipItem(state: boolean) {
const {theme} = this.props; const { theme } = this.props;
return ( return (
<List.Item <List.Item
title={ title={
@ -396,8 +403,8 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
* @param item The club to render * @param item The club to render
* @return {*} * @return {*}
*/ */
getClubListItem = ({item}: {item: ClubType}) => { getClubListItem = ({ item }: { item: ClubType }) => {
const {theme} = this.props; const { theme } = this.props;
const onPress = () => { const onPress = () => {
this.openClubDetailsScreen(item.id); this.openClubDetailsScreen(item.id);
}; };
@ -458,11 +465,11 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
sortClubList = (a: ClubType): number => (a.is_manager ? -1 : 1); sortClubList = (a: ClubType): number => (a.is_manager ? -1 : 1);
showDisconnectDialog = () => { showDisconnectDialog = () => {
this.setState({dialogVisible: true}); this.setState({ dialogVisible: true });
}; };
hideDisconnectDialog = () => { hideDisconnectDialog = () => {
this.setState({dialogVisible: false}); this.setState({ dialogVisible: false });
}; };
/** /**
@ -470,12 +477,12 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
* @param id The club's id to open * @param id The club's id to open
*/ */
openClubDetailsScreen(id: number) { openClubDetailsScreen(id: number) {
const {navigation} = this.props; const { navigation } = this.props;
navigation.navigate('club-information', {clubId: id}); navigation.navigate('club-information', { clubId: id });
} }
render() { render() {
const {navigation} = this.props; const { navigation } = this.props;
return ( return (
<AuthenticatedScreen <AuthenticatedScreen
navigation={navigation} navigation={navigation}

View file

@ -18,21 +18,22 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import {RefreshControl, View} from 'react-native'; import { RefreshControl, StyleSheet, View } from 'react-native';
import {StackNavigationProp} from '@react-navigation/stack'; import { StackNavigationProp } from '@react-navigation/stack';
import i18n from 'i18n-js'; import i18n from 'i18n-js';
import {Button} from 'react-native-paper'; import { Button } from 'react-native-paper';
import AuthenticatedScreen from '../../components/Amicale/AuthenticatedScreen'; import AuthenticatedScreen from '../../components/Amicale/AuthenticatedScreen';
import {getTimeOnlyString, stringToDate} from '../../utils/Planning'; import { getTimeOnlyString, stringToDate } from '../../utils/Planning';
import VoteTease from '../../components/Amicale/Vote/VoteTease'; import VoteTease from '../../components/Amicale/Vote/VoteTease';
import VoteSelect from '../../components/Amicale/Vote/VoteSelect'; import VoteSelect from '../../components/Amicale/Vote/VoteSelect';
import VoteResults from '../../components/Amicale/Vote/VoteResults'; import VoteResults from '../../components/Amicale/Vote/VoteResults';
import VoteWait from '../../components/Amicale/Vote/VoteWait'; import VoteWait from '../../components/Amicale/Vote/VoteWait';
import {MASCOT_STYLE} from '../../components/Mascot/Mascot'; import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
import MascotPopup from '../../components/Mascot/MascotPopup'; import MascotPopup from '../../components/Mascot/MascotPopup';
import AsyncStorageManager from '../../managers/AsyncStorageManager'; import AsyncStorageManager from '../../managers/AsyncStorageManager';
import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable'; import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList'; import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
import GENERAL_STYLES from '../../constants/Styles';
export type VoteTeamType = { export type VoteTeamType = {
id: number; id: number;
@ -118,6 +119,14 @@ type StateType = {
mascotDialogVisible: boolean; mascotDialogVisible: boolean;
}; };
const styles = StyleSheet.create({
button: {
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 20,
},
});
/** /**
* Screen displaying vote information and controls * Screen displaying vote information and controls
*/ */
@ -132,11 +141,11 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
today: Date; today: Date;
mainFlatListData: Array<{key: string}>; mainFlatListData: Array<{ key: string }>;
lastRefresh: Date | null; lastRefresh: Date | null;
authRef: {current: null | AuthenticatedScreen<any>}; authRef: { current: null | AuthenticatedScreen<any> };
constructor(props: PropsType) { constructor(props: PropsType) {
super(props); super(props);
@ -146,14 +155,14 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
this.state = { this.state = {
hasVoted: false, hasVoted: false,
mascotDialogVisible: AsyncStorageManager.getBool( mascotDialogVisible: AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.voteShowMascot.key, AsyncStorageManager.PREFERENCES.voteShowMascot.key
), ),
}; };
this.hasVoted = false; this.hasVoted = false;
this.today = new Date(); this.today = new Date();
this.authRef = React.createRef(); this.authRef = React.createRef();
this.lastRefresh = null; this.lastRefresh = null;
this.mainFlatListData = [{key: 'main'}, {key: 'info'}]; this.mainFlatListData = [{ key: 'main' }, { key: 'info' }];
} }
/** /**
@ -174,7 +183,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
return dateString; return dateString;
} }
getMainRenderItem = ({item}: {item: {key: string}}) => { getMainRenderItem = ({ item }: { item: { key: string } }) => {
if (item.key === 'info') { if (item.key === 'info') {
return ( return (
<View> <View>
@ -182,11 +191,8 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
mode="contained" mode="contained"
icon="help-circle" icon="help-circle"
onPress={this.showMascotDialog} onPress={this.showMascotDialog}
style={{ style={styles.button}
marginLeft: 'auto', >
marginRight: 'auto',
marginTop: 20,
}}>
{i18n.t('screens.vote.mascotDialog.title')} {i18n.t('screens.vote.mascotDialog.title')}
</Button> </Button>
</View> </View>
@ -196,7 +202,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
}; };
getScreen = (data: Array<TeamResponseType | VoteDatesStringType | null>) => { getScreen = (data: Array<TeamResponseType | VoteDatesStringType | null>) => {
const {state} = this; const { state } = this;
// data[0] = FAKE_TEAMS2; // data[0] = FAKE_TEAMS2;
// data[1] = FAKE_DATE; // data[1] = FAKE_DATE;
this.lastRefresh = new Date(); this.lastRefresh = new Date();
@ -229,7 +235,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
}; };
getContent() { getContent() {
const {state} = this; const { state } = this;
if (!this.isVoteStarted()) { if (!this.isVoteStarted()) {
return this.getTeaseVoteCard(); return this.getTeaseVoteCard();
} }
@ -245,7 +251,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
return <VoteNotAvailable />; return <VoteNotAvailable />;
} }
onVoteSuccess = (): void => this.setState({hasVoted: true}); onVoteSuccess = (): void => this.setState({ hasVoted: true });
/** /**
* The user has not voted yet, and the votes are open * The user has not voted yet, and the votes are open
@ -270,7 +276,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
teams={this.teams} teams={this.teams}
dateEnd={this.getDateString( dateEnd={this.getDateString(
this.dates.date_result_end, this.dates.date_result_end,
this.datesString.date_result_end, this.datesString.date_result_end
)} )}
/> />
); );
@ -287,7 +293,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
<VoteTease <VoteTease
startDate={this.getDateString( startDate={this.getDateString(
this.dates.date_begin, this.dates.date_begin,
this.datesString.date_begin, this.datesString.date_begin
)} )}
/> />
); );
@ -299,7 +305,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
* Votes have ended, or user has voted waiting for results * Votes have ended, or user has voted waiting for results
*/ */
getWaitVoteCard() { getWaitVoteCard() {
const {state} = this; const { state } = this;
let startDate = null; let startDate = null;
if ( if (
this.dates != null && this.dates != null &&
@ -308,7 +314,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
) { ) {
startDate = this.getDateString( startDate = this.getDateString(
this.dates.date_result_begin, this.dates.date_result_begin,
this.datesString.date_result_begin, this.datesString.date_result_begin
); );
} }
return ( return (
@ -326,7 +332,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
*/ */
reloadData = () => { reloadData = () => {
let canRefresh; let canRefresh;
const {lastRefresh} = this; const { lastRefresh } = this;
if (lastRefresh != null) { if (lastRefresh != null) {
canRefresh = canRefresh =
new Date().getTime() - lastRefresh.getTime() > MIN_REFRESH_TIME; new Date().getTime() - lastRefresh.getTime() > MIN_REFRESH_TIME;
@ -339,15 +345,15 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
}; };
showMascotDialog = () => { showMascotDialog = () => {
this.setState({mascotDialogVisible: true}); this.setState({ mascotDialogVisible: true });
}; };
hideMascotDialog = () => { hideMascotDialog = () => {
AsyncStorageManager.set( AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.voteShowMascot.key, AsyncStorageManager.PREFERENCES.voteShowMascot.key,
false, false
); );
this.setState({mascotDialogVisible: false}); this.setState({ mascotDialogVisible: false });
}; };
isVoteStarted(): boolean { isVoteStarted(): boolean {
@ -412,9 +418,9 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
* @returns {*} * @returns {*}
*/ */
render() { render() {
const {props, state} = this; const { props, state } = this;
return ( return (
<View style={{flex: 1}}> <View style={GENERAL_STYLES.flex}>
<AuthenticatedScreen<TeamResponseType | VoteDatesStringType> <AuthenticatedScreen<TeamResponseType | VoteDatesStringType>
navigation={props.navigation} navigation={props.navigation}
ref={this.authRef} ref={this.authRef}

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 type CoordinatesType = { export type CoordinatesType = {
x: number; x: number;
y: number; y: number;
@ -49,7 +47,7 @@ export default class BaseShape {
} }
this.theme = theme; this.theme = theme;
this.#rotation = 0; this.#rotation = 0;
this.position = {x: 0, y: 0}; this.position = { x: 0, y: 0 };
this.#currentShape = this.getShapes()[this.#rotation]; this.#currentShape = this.getShapes()[this.#rotation];
} }
@ -96,7 +94,7 @@ export default class BaseShape {
y: this.position.y + row, y: this.position.y + row,
}); });
} else { } else {
coordinates.push({x: col, y: row}); coordinates.push({ x: col, y: row });
} }
} }
} }

View file

@ -18,7 +18,7 @@
*/ */
import BaseShape from './BaseShape'; import BaseShape from './BaseShape';
import type {ShapeType} from './BaseShape'; import type { ShapeType } from './BaseShape';
export default class ShapeI extends BaseShape { export default class ShapeI extends BaseShape {
constructor(theme: ReactNativePaper.Theme) { constructor(theme: ReactNativePaper.Theme) {

View file

@ -18,7 +18,7 @@
*/ */
import BaseShape from './BaseShape'; import BaseShape from './BaseShape';
import type {ShapeType} from './BaseShape'; import type { ShapeType } from './BaseShape';
export default class ShapeJ extends BaseShape { export default class ShapeJ extends BaseShape {
constructor(theme: ReactNativePaper.Theme) { constructor(theme: ReactNativePaper.Theme) {

Some files were not shown because too many files have changed in this diff Show more