Compare commits
No commits in common. "5d692c68408c57fd5ad52b0c1d77b486b8ecc47a" and "64e643f5c6b9b0da2d2d12b1070c234f261f7ab5" have entirely different histories.
5d692c6840
...
64e643f5c6
170 changed files with 5444 additions and 5542 deletions
46
.eslintrc.js
46
.eslintrc.js
|
|
@ -1,6 +1,46 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: '@react-native-community',
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'airbnb',
|
||||
'plugin:flowtype/recommended',
|
||||
'prettier',
|
||||
'prettier/flowtype',
|
||||
'prettier/react',
|
||||
],
|
||||
parser: 'babel-eslint',
|
||||
plugins: ['flowtype'],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'react/jsx-filename-extension': [1, {extensions: ['.js', '.jsx']}],
|
||||
'react/static-property-placement': [2, 'static public field'],
|
||||
'flowtype/define-flow-type': 1,
|
||||
'flowtype/no-mixed': 2,
|
||||
'flowtype/no-primitive-constructor-types': 2,
|
||||
'flowtype/no-types-missing-file-annotation': 2,
|
||||
'flowtype/no-weak-types': 2,
|
||||
'flowtype/require-parameter-type': 2,
|
||||
'flowtype/require-readonly-react-props': 0,
|
||||
'flowtype/require-return-type': [
|
||||
2,
|
||||
'always',
|
||||
{
|
||||
annotateUndefined: 'never',
|
||||
},
|
||||
],
|
||||
'flowtype/require-valid-file-annotation': 2,
|
||||
'flowtype/type-id-match': [2, '^([A-Z][a-z0-9]+)+Type$'],
|
||||
'flowtype/use-flow-type': 1,
|
||||
'flowtype/valid-syntax': 1,
|
||||
},
|
||||
settings: {
|
||||
flowtype: {
|
||||
onlyFilesWithFlowAnnotation: false,
|
||||
},
|
||||
},
|
||||
globals: {
|
||||
fetch: false,
|
||||
Headers: false,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
0
.flowconfig
Normal file
0
.flowconfig
Normal file
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {LogBox, Platform, SafeAreaView, View} from 'react-native';
|
||||
import {NavigationContainer} from '@react-navigation/native';
|
||||
|
|
@ -26,6 +28,7 @@ import SplashScreen from 'react-native-splash-screen';
|
|||
import {OverflowMenuProvider} from 'react-navigation-header-buttons';
|
||||
import AsyncStorageManager from './src/managers/AsyncStorageManager';
|
||||
import CustomIntroSlider from './src/components/Overrides/CustomIntroSlider';
|
||||
import type {CustomThemeType} from './src/managers/ThemeManager';
|
||||
import ThemeManager from './src/managers/ThemeManager';
|
||||
import MainNavigator from './src/navigation/MainNavigator';
|
||||
import AprilFoolsManager from './src/managers/AprilFoolsManager';
|
||||
|
|
@ -35,7 +38,6 @@ import type {ParsedUrlDataType} from './src/utils/URLHandler';
|
|||
import URLHandler from './src/utils/URLHandler';
|
||||
import {setupStatusBar} from './src/utils/Utils';
|
||||
import initLocales from './src/utils/Locales';
|
||||
import {NavigationContainerRef} from '@react-navigation/core';
|
||||
|
||||
// Native optimizations https://reactnavigation.org/docs/react-native-screens
|
||||
// Crashes app when navigating away from webview on android 9+
|
||||
|
|
@ -48,15 +50,15 @@ LogBox.ignoreLogs([
|
|||
]);
|
||||
|
||||
type StateType = {
|
||||
isLoading: boolean;
|
||||
showIntro: boolean;
|
||||
showUpdate: boolean;
|
||||
showAprilFools: boolean;
|
||||
currentTheme: ReactNativePaper.Theme | undefined;
|
||||
isLoading: boolean,
|
||||
showIntro: boolean,
|
||||
showUpdate: boolean,
|
||||
showAprilFools: boolean,
|
||||
currentTheme: CustomThemeType | null,
|
||||
};
|
||||
|
||||
export default class App extends React.Component<{}, StateType> {
|
||||
navigatorRef: {current: null | NavigationContainerRef};
|
||||
export default class App extends React.Component<null, StateType> {
|
||||
navigatorRef: {current: null | NavigationContainer};
|
||||
|
||||
defaultHomeRoute: string | null;
|
||||
|
||||
|
|
@ -64,14 +66,14 @@ export default class App extends React.Component<{}, StateType> {
|
|||
|
||||
urlHandler: URLHandler;
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
showIntro: true,
|
||||
showUpdate: true,
|
||||
showAprilFools: false,
|
||||
currentTheme: undefined,
|
||||
currentTheme: null,
|
||||
};
|
||||
initLocales();
|
||||
this.navigatorRef = React.createRef();
|
||||
|
|
@ -153,11 +155,8 @@ export default class App extends React.Component<{}, StateType> {
|
|||
// Only show intro if this is the first time starting the app
|
||||
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
|
||||
// Status bar goes dark if set too fast on ios
|
||||
if (Platform.OS === 'ios') {
|
||||
setTimeout(setupStatusBar, 1000);
|
||||
} else {
|
||||
setupStatusBar();
|
||||
}
|
||||
if (Platform.OS === 'ios') setTimeout(setupStatusBar, 1000);
|
||||
else setupStatusBar();
|
||||
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
|
|
@ -193,7 +192,7 @@ export default class App extends React.Component<{}, StateType> {
|
|||
/**
|
||||
* Renders the app based on loading state
|
||||
*/
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {state} = this;
|
||||
if (state.isLoading) {
|
||||
return null;
|
||||
|
|
@ -1,8 +1,3 @@
|
|||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
env: {
|
||||
production: {
|
||||
plugins: ['react-native-paper/babel'],
|
||||
},
|
||||
},
|
||||
presets: ['module:metro-react-native-babel-preset', '@babel/preset-flow'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,15 +8,14 @@ Le strict minimum pour pouvoir comprendre le code de l'application. Il n'est pas
|
|||
|
||||
* [**Des cours d'anglais**](https://www.wikihow.com/Be-Good-at-English) : Toutes les ressources sont en anglais, le code est en anglais, tu trouveras presque rien en français, donc profite-en pour t'améliorer !
|
||||
* [**Tutoriel Git**](https://learngitbranching.js.org/) : Le système utilisé pour synchroniser le code entre plusieurs ordinateurs. Tout le projet repose sur cette technologie, une compréhension minimale de son fonctionnement est nécessaire. Si tu ne sais pas ce que veut dire commit, pull, push, merge, ou branch, alors lis ce tuto !
|
||||
* [**Tutoriel JavaScript**](https://www.w3schools.com/js) : Un minimum de connaissances en JavaScript est nécessaire pour pouvoir comprendre le code. Pas besoin de lire tout le tutoriel. Pour les bases, tu peux t'arrêter à la partie `JS Dates` ou un peu avant. Il est utile de revenir souvent vers ce guide quand tu rencontres des difficultés.
|
||||
* [**Tutoriel TypeScript**](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) : Un tuto rapide de cette surcouche à JavaScript, permettant de le rendre typé statique.
|
||||
* [**Tutoriel JavaScript**](https://www.w3schools.com/js) : Un minimum de connaissances en JavaScript est nécessaire pour pouvoir comprendre le code. Pas besoin de lire tout le tutoriel. Pour les bases, tu peux t'arrêter à la partie `JS Dates` ou un peu avant. Il est utile de revenir souvent vers ce guide quand tu rencontres des difficultés.
|
||||
* [**Documentation React Native**](https://reactnative.dev/docs/getting-started) : La techno de base, qui utilise JavaScript. Lire au moins les articles de la catégorie `The Basics`, tout est interactif c'est plutôt simple et rapide à comprendre.
|
||||
|
||||
## 🤔 Comprendre les librairies
|
||||
|
||||
Si tu as compris les bases et que tu veux te plonger un peu plus en profondeur dans le code, tu peux utiliser les liens ci-dessous pour accéder aux frameworks les plus importants.
|
||||
|
||||
* [**TypeScript Handbook**](https://www.typescriptlang.org/docs/handbook/intro.html) : Un tuto TypeScript complet permettant de bien maitriser cette technologie.
|
||||
* [**Documentation Flow**](https://flow.org/en/docs/react/) : Un utilitaire pour rendre JavaScript typé statique (c'est-à-dire plus robuste pour de gros projets). Flow permet de rajouter des annotations pour donner un type aux variables.
|
||||
* [**Documentation React Native Paper**](https://callstack.github.io/react-native-paper/) : Le framework utilisé pour créer l'interface utilisateur (UI). Paper met à disposition de nombreux composants respectant les normes Material Design. Comparé à d'autres frameworks, paper est léger et facile à utiliser.
|
||||
* [**Documentation React Navigation**](https://reactnavigation.org/docs/getting-started) : Le framework utilisé pour faciliter la navigation classique entre différents écrans. Permet de créer facilement une navigation par onglets/menu déroulant.
|
||||
* [**Liste des librairies**](../package.json) : Tu trouveras dans ce fichier la liste de toutes les librairies utilisées dans ce projet (catégorie `dependencies`). Pour accéder à leur documentation, fais une simple recherche de leur nom dans un moteur de recherche.
|
||||
|
|
|
|||
10
doc/NOTES.md
10
doc/NOTES.md
|
|
@ -4,16 +4,6 @@ Ce fichier permet de regrouper les différentes informations sur des décisions
|
|||
|
||||
Ces notes pouvant évoluer dans le temps, leur date d'écriture est aussi indiquée.
|
||||
|
||||
## _2020-09-24_ | Flow
|
||||
|
||||
Flow est un système d'annotation permettant de rendre JavaScript typé statique. Développée par Facebook, cette technologie à initialement été adoptée. En revanche, de nombreux problèmes sont apparus :
|
||||
* Système très complexe donnant de nombreuses erreurs inconnues, rendant la contribution complexe pour les non-initiés
|
||||
* Manque de compatibilité avec les librairies existantes (la majorité utilisant TypeScript)
|
||||
* Utilisation excessive du système lors du développement
|
||||
* Plantage régulier du service Flow, nécessitant un redémarrage manuel
|
||||
|
||||
Ainsi, il a été décidé de migrer le projet vers Typescript.
|
||||
|
||||
## _2020-06-23_ | Expo
|
||||
|
||||
Expo est une surcouche à react native permettant de simplifier le processus de build. Le projet à commencé en l'utilisant, mais de nombreux problèmes ont été rencontrés :
|
||||
|
|
|
|||
|
|
@ -475,8 +475,8 @@
|
|||
"loading": "Chargement...",
|
||||
"retry": "Réessayer",
|
||||
"networkError": "Impossible de contacter les serveurs. Assure-toi d'être connecté à Internet.",
|
||||
"goBack": "Précédent",
|
||||
"goForward": "Suivant",
|
||||
"goBack": "Suivant",
|
||||
"goForward": "Précédent",
|
||||
"openInBrowser": "Ouvrir dans le navigateur",
|
||||
"notAvailable": "Non disponible",
|
||||
"listUpdateFail": "Erreur lors de la mise à jour de la liste"
|
||||
|
|
|
|||
365
package-lock.json
generated
365
package-lock.json
generated
|
|
@ -810,6 +810,16 @@
|
|||
"@babel/helper-plugin-utils": "^7.10.4"
|
||||
}
|
||||
},
|
||||
"@babel/preset-flow": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.10.4.tgz",
|
||||
"integrity": "sha512-XI6l1CptQCOBv+ZKYwynyswhtOKwpZZp5n0LG1QKCo8erRhqjoQV6nvx61Eg30JHpysWQSBwA2AWRU3pBbSY5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.10.4",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.10.4"
|
||||
}
|
||||
},
|
||||
"@babel/register": {
|
||||
"version": "7.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/register/-/register-7.10.5.tgz",
|
||||
|
|
@ -2287,79 +2297,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-4.10.1.tgz",
|
||||
"integrity": "sha512-ael2f1onoPF3vF7YqHGWy7NnafzGu+yp88BbFbP0ydoCP2xGSUzmZVw0zakPTC040Id+JQ9WeFczujMkDy6jYQ=="
|
||||
},
|
||||
"@react-native-community/eslint-config": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/eslint-config/-/eslint-config-1.1.0.tgz",
|
||||
"integrity": "sha512-hwb1hC28BhkwLwnO6vDISV6XZbipw2RIEhBVBN+pE7AUG9HjFXxoksiiOSoYgox9C8g86VJwHnKpak/3NnVBkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@react-native-community/eslint-plugin": "^1.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.25.0",
|
||||
"@typescript-eslint/parser": "^2.25.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-plugin-eslint-comments": "^3.1.2",
|
||||
"eslint-plugin-flowtype": "2.50.3",
|
||||
"eslint-plugin-jest": "22.4.1",
|
||||
"eslint-plugin-prettier": "3.1.2",
|
||||
"eslint-plugin-react": "7.19.0",
|
||||
"eslint-plugin-react-hooks": "^3.0.0",
|
||||
"eslint-plugin-react-native": "3.8.1",
|
||||
"prettier": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"doctrine": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
||||
"integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-flowtype": {
|
||||
"version": "2.50.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.50.3.tgz",
|
||||
"integrity": "sha512-X+AoKVOr7Re0ko/yEXyM5SSZ0tazc6ffdIOocp2fFUlWoDt7DV0Bz99mngOkAFLOAWjqRA5jPwqUCbrx13XoxQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.10"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-react": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz",
|
||||
"integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-includes": "^3.1.1",
|
||||
"doctrine": "^2.1.0",
|
||||
"has": "^1.0.3",
|
||||
"jsx-ast-utils": "^2.2.3",
|
||||
"object.entries": "^1.1.1",
|
||||
"object.fromentries": "^2.0.2",
|
||||
"object.values": "^1.1.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"resolve": "^1.15.1",
|
||||
"semver": "^6.3.0",
|
||||
"string.prototype.matchall": "^4.0.2",
|
||||
"xregexp": "^4.3.0"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-react-hooks": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-3.0.0.tgz",
|
||||
"integrity": "sha512-EjxTHxjLKIBWFgDJdhKKzLh5q+vjTFrqNZX36uIxWS4OfyXe5DawqPj3U5qeJ1ngLwatjzQnmR0Lz0J0YH3kxw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@react-native-community/eslint-plugin": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/eslint-plugin/-/eslint-plugin-1.1.0.tgz",
|
||||
"integrity": "sha512-W/J0fNYVO01tioHjvYWQ9m6RgndVtbElzYozBq1ZPrHO/iCzlqoySHl4gO/fpCl9QEFjvJfjPgtPMTMlsoq5DQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@react-native-community/masked-view": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.10.tgz",
|
||||
|
|
@ -2481,12 +2418,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"@types/eslint-visitor-keys": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
|
||||
"integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/graceful-fs": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz",
|
||||
|
|
@ -2501,12 +2432,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
|
||||
"integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
|
||||
},
|
||||
"@types/i18n-js": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/i18n-js/-/i18n-js-3.0.3.tgz",
|
||||
"integrity": "sha512-GiZzazvxQ5j+EA4Zf4MtDsSaokAR/gW7FxxTlHi2p2xKFUhwAUT0B/MB8WL77P1TcqAO3MefWorFFyZS8F7s0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
|
|
@ -2529,36 +2454,6 @@
|
|||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"@types/jest": {
|
||||
"version": "25.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz",
|
||||
"integrity": "sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"jest-diff": "^25.2.1",
|
||||
"pretty-format": "^25.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"pretty-format": {
|
||||
"version": "25.5.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz",
|
||||
"integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jest/types": "^25.5.0",
|
||||
"ansi-regex": "^5.0.0",
|
||||
"ansi-styles": "^4.0.0",
|
||||
"react-is": "^16.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
|
||||
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
|
|
@ -2583,72 +2478,11 @@
|
|||
"integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
|
||||
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "16.9.49",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.49.tgz",
|
||||
"integrity": "sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@types/react-native": {
|
||||
"version": "0.63.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.20.tgz",
|
||||
"integrity": "sha512-APnxRTDxbWw/IYjvwvXkhYJiz1gahyVA579pJqAVsEfZ+ZUwUHZpWKnexobyH5NmRJHuA/8LrThyps/BW3SYXA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-native-calendars": {
|
||||
"version": "1.20.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native-calendars/-/react-native-calendars-1.20.10.tgz",
|
||||
"integrity": "sha512-bmWlkFa/6SNF98aM9rjKMGUOSDb15VBsfxBW5oo/iJ5tm5THf+eAGlxH72hGZFqJpr93plBs+ctkRVHQA7fx1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"@types/react-native": "*",
|
||||
"@types/xdate": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-native-vector-icons": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.6.tgz",
|
||||
"integrity": "sha512-lAyxNfMd5L1xZvXWsGcJmNegDf61TAp40uL6ashNNWj9W3IrDJO59L9+9inh0Y2MsEZpLTdxzVU8Unb4/0FQng==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"@types/react-native": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-test-renderer": {
|
||||
"version": "16.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.3.tgz",
|
||||
"integrity": "sha512-wJ7IlN5NI82XMLOyHSa+cNN4Z0I+8/YaLl04uDgcZ+W+ExWCmCiVTLT/7fRNqzy4OhStZcUwIqLNF7q+AdW43Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
|
||||
},
|
||||
"@types/xdate": {
|
||||
"version": "0.8.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/xdate/-/xdate-0.8.31.tgz",
|
||||
"integrity": "sha512-iZYRKKK8UZXoepNh2kwK6TPITMj/dwdv0NzNi9DFMt2foGkU7h+ncaCpGsdD2fp/CXMs9dxPAzV9uddFy7c4QA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "15.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
|
||||
|
|
@ -2662,80 +2496,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz",
|
||||
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz",
|
||||
"integrity": "sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/experimental-utils": "2.34.0",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"regexpp": "^3.0.0",
|
||||
"tsutils": "^3.17.1"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/experimental-utils": {
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz",
|
||||
"integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.3",
|
||||
"@typescript-eslint/typescript-estree": "2.34.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz",
|
||||
"integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/eslint-visitor-keys": "^1.0.0",
|
||||
"@typescript-eslint/experimental-utils": "2.34.0",
|
||||
"@typescript-eslint/typescript-estree": "2.34.0",
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz",
|
||||
"integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"eslint-visitor-keys": "^1.1.0",
|
||||
"glob": "^7.1.6",
|
||||
"is-glob": "^4.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
|
||||
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"abab": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.4.tgz",
|
||||
|
|
@ -3794,12 +3554,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz",
|
||||
"integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==",
|
||||
"dev": true
|
||||
},
|
||||
"damerau-levenshtein": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz",
|
||||
|
|
@ -4488,30 +4242,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-eslint-comments": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz",
|
||||
"integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"ignore": "^5.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-flowtype": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.2.0.tgz",
|
||||
|
|
@ -4555,12 +4285,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-jest": {
|
||||
"version": "22.4.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.4.1.tgz",
|
||||
"integrity": "sha512-gcLfn6P2PrFAVx3AobaOzlIEevpAEf9chTpFZz7bYfc7pz8XRv7vuKTIE4hxPKZSha6XWKKplDQ0x9Pq8xX2mg==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-plugin-jsx-a11y": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz",
|
||||
|
|
@ -4588,15 +4312,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"eslint-plugin-prettier": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz",
|
||||
"integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prettier-linter-helpers": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-react": {
|
||||
"version": "7.20.6",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz",
|
||||
|
|
@ -4633,21 +4348,6 @@
|
|||
"integrity": "sha512-6SSb5AiMCPd8FDJrzah+Z4F44P2CdOaK026cXFV+o/xSRzfOiV1FNFeLl2z6xm3yqWOQEZ5OfVgiec90qV2xrQ==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-plugin-react-native": {
|
||||
"version": "3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-3.8.1.tgz",
|
||||
"integrity": "sha512-6Z4s4nvgFRdda/1s1+uu4a6EMZwEjjJ9Bk/1yBImv0fd9U2CsGu2cUakAtV83cZKhizbWhSouXoaK4JtlScdFg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-plugin-react-native-globals": "^0.1.1"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-react-native-globals": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz",
|
||||
"integrity": "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
|
||||
|
|
@ -5008,12 +4708,6 @@
|
|||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-diff": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
|
||||
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
|
|
@ -5247,6 +4941,12 @@
|
|||
"integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
|
||||
"dev": true
|
||||
},
|
||||
"flow-bin": {
|
||||
"version": "0.123.0",
|
||||
"resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.123.0.tgz",
|
||||
"integrity": "sha512-Ylcf8YDIM/KrqtxkPuq+f8O+6sdYA2Nuz5f+sWHlp539DatZz3YMcsO1EiXaf1C11HJgpT/3YGYe7xZ9/UZmvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||
|
|
@ -10332,15 +10032,6 @@
|
|||
"integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier-linter-helpers": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-diff": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz",
|
||||
|
|
@ -12216,15 +11907,6 @@
|
|||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
|
||||
},
|
||||
"tsutils": {
|
||||
"version": "3.17.1",
|
||||
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
|
||||
"integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^1.8.1"
|
||||
}
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
|
|
@ -12274,12 +11956,6 @@
|
|||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
|
||||
"dev": true
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.21",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
|
||||
|
|
@ -12699,15 +12375,6 @@
|
|||
"resolved": "https://registry.npmjs.org/xpipe/-/xpipe-1.0.5.tgz",
|
||||
"integrity": "sha1-jdi/Rfw/f1Xw4FS4ePQ6YmFNr98="
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz",
|
||||
"integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime-corejs3": "^7.8.3"
|
||||
}
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
|
|
|||
26
package.json
26
package.json
|
|
@ -8,17 +8,12 @@
|
|||
"android-release": "react-native run-android --variant=release",
|
||||
"ios": "react-native run-ios",
|
||||
"test": "jest",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"jest": {
|
||||
"preset": "react-native",
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base)"
|
||||
],
|
||||
"setupFilesAfterEnv": [
|
||||
"jest-extended"
|
||||
|
|
@ -64,16 +59,9 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.0",
|
||||
"@babel/preset-flow": "^7.10.4",
|
||||
"@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-eslint": "^10.1.0",
|
||||
"babel-jest": "^25.1.0",
|
||||
"eslint": "^7.2.0",
|
||||
"eslint-config-airbnb": "^18.2.0",
|
||||
|
|
@ -83,11 +71,11 @@
|
|||
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||
"eslint-plugin-react": "^7.20.5",
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"flow-bin": "^0.123.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"
|
||||
"react-test-renderer": "16.13.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,34 +17,37 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import ConnectionManager from '../../managers/ConnectionManager';
|
||||
import type {ApiGenericDataType} from '../../utils/WebData';
|
||||
import {ERROR_TYPE} from '../../utils/WebData';
|
||||
import ErrorView from '../Screens/ErrorView';
|
||||
import BasicLoadingScreen from '../Screens/BasicLoadingScreen';
|
||||
|
||||
type PropsType<T> = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
requests: Array<{
|
||||
link: string;
|
||||
params: object;
|
||||
mandatory: boolean;
|
||||
}>;
|
||||
renderFunction: (data: Array<T | null>) => React.ReactNode;
|
||||
link: string,
|
||||
params: {...},
|
||||
mandatory: boolean,
|
||||
}>,
|
||||
renderFunction: (Array<ApiGenericDataType | null>) => React.Node,
|
||||
errorViewOverride?: Array<{
|
||||
errorCode: number;
|
||||
message: string;
|
||||
icon: string;
|
||||
showRetryButton: boolean;
|
||||
}> | null;
|
||||
errorCode: number,
|
||||
message: string,
|
||||
icon: string,
|
||||
showRetryButton: boolean,
|
||||
}> | null,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
loading: boolean;
|
||||
loading: boolean,
|
||||
};
|
||||
|
||||
class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
||||
class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
||||
static defaultProps = {
|
||||
errorViewOverride: null,
|
||||
};
|
||||
|
|
@ -55,14 +58,13 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
|||
|
||||
errors: Array<number>;
|
||||
|
||||
fetchedData: Array<T | null>;
|
||||
fetchedData: Array<ApiGenericDataType | null>;
|
||||
|
||||
constructor(props: PropsType<T>) {
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
};
|
||||
this.currentUserToken = null;
|
||||
this.connectionManager = ConnectionManager.getInstance();
|
||||
props.navigation.addListener('focus', this.onScreenFocus);
|
||||
this.fetchedData = new Array(props.requests.length);
|
||||
|
|
@ -89,20 +91,20 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
|||
* @param index The index for the data
|
||||
* @param error The error code received
|
||||
*/
|
||||
onRequestFinished(data: T | null, index: number, error?: number) {
|
||||
onRequestFinished(
|
||||
data: ApiGenericDataType | null,
|
||||
index: number,
|
||||
error?: number,
|
||||
) {
|
||||
const {props} = this;
|
||||
if (index >= 0 && index < props.requests.length) {
|
||||
this.fetchedData[index] = data;
|
||||
this.errors[index] = error != null ? error : ERROR_TYPE.SUCCESS;
|
||||
}
|
||||
// Token expired, logout user
|
||||
if (error === ERROR_TYPE.BAD_TOKEN) {
|
||||
this.connectionManager.disconnect();
|
||||
}
|
||||
if (error === ERROR_TYPE.BAD_TOKEN) this.connectionManager.disconnect();
|
||||
|
||||
if (this.allRequestsFinished()) {
|
||||
this.setState({loading: false});
|
||||
}
|
||||
if (this.allRequestsFinished()) this.setState({loading: false});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -130,7 +132,7 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getErrorRender() {
|
||||
getErrorRender(): React.Node {
|
||||
const {props} = this;
|
||||
const errorCode = this.getError();
|
||||
let shouldOverride = false;
|
||||
|
|
@ -167,18 +169,18 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
|||
*/
|
||||
fetchData = () => {
|
||||
const {state, props} = this;
|
||||
if (!state.loading) {
|
||||
this.setState({loading: true});
|
||||
}
|
||||
if (!state.loading) this.setState({loading: true});
|
||||
|
||||
if (this.connectionManager.isLoggedIn()) {
|
||||
for (let i = 0; i < props.requests.length; i += 1) {
|
||||
this.connectionManager
|
||||
.authenticatedRequest<T>(
|
||||
.authenticatedRequest(
|
||||
props.requests[i].link,
|
||||
props.requests[i].params,
|
||||
)
|
||||
.then((response: T): void => this.onRequestFinished(response, i))
|
||||
.then((response: ApiGenericDataType): void =>
|
||||
this.onRequestFinished(response, i),
|
||||
)
|
||||
.catch((error: number): void =>
|
||||
this.onRequestFinished(null, i, error),
|
||||
);
|
||||
|
|
@ -198,9 +200,7 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
|||
allRequestsFinished(): boolean {
|
||||
let finished = true;
|
||||
this.errors.forEach((error: number | null) => {
|
||||
if (error == null) {
|
||||
finished = false;
|
||||
}
|
||||
if (error == null) finished = false;
|
||||
});
|
||||
return finished;
|
||||
}
|
||||
|
|
@ -212,14 +212,11 @@ class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
|||
this.fetchData();
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {state, props} = this;
|
||||
if (state.loading) {
|
||||
return <BasicLoadingScreen />;
|
||||
}
|
||||
if (this.getError() === ERROR_TYPE.SUCCESS) {
|
||||
if (state.loading) return <BasicLoadingScreen />;
|
||||
if (this.getError() === ERROR_TYPE.SUCCESS)
|
||||
return props.renderFunction(this.fetchedData);
|
||||
}
|
||||
return this.getErrorRender();
|
||||
}
|
||||
}
|
||||
|
|
@ -17,25 +17,28 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import i18n from 'i18n-js';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import LoadingConfirmDialog from '../Dialogs/LoadingConfirmDialog';
|
||||
import ConnectionManager from '../../managers/ConnectionManager';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
|
||||
type PropsType = {
|
||||
visible: boolean;
|
||||
onDismiss: () => void;
|
||||
navigation: StackNavigationProp,
|
||||
visible: boolean,
|
||||
onDismiss: () => void,
|
||||
};
|
||||
|
||||
function LogoutDialog(props: PropsType) {
|
||||
const navigation = useNavigation();
|
||||
const onClickAccept = async (): Promise<void> => {
|
||||
class LogoutDialog extends React.PureComponent<PropsType> {
|
||||
onClickAccept = async (): Promise<void> => {
|
||||
const {props} = this;
|
||||
return new Promise((resolve: () => void) => {
|
||||
ConnectionManager.getInstance()
|
||||
.disconnect()
|
||||
.then(() => {
|
||||
navigation.reset({
|
||||
props.navigation.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'main'}],
|
||||
});
|
||||
|
|
@ -45,16 +48,19 @@ function LogoutDialog(props: PropsType) {
|
|||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<LoadingConfirmDialog
|
||||
visible={props.visible}
|
||||
onDismiss={props.onDismiss}
|
||||
onAccept={onClickAccept}
|
||||
title={i18n.t('dialog.disconnect.title')}
|
||||
titleLoading={i18n.t('dialog.disconnect.titleLoading')}
|
||||
message={i18n.t('dialog.disconnect.message')}
|
||||
/>
|
||||
);
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<LoadingConfirmDialog
|
||||
visible={props.visible}
|
||||
onDismiss={props.onDismiss}
|
||||
onAccept={this.onClickAccept}
|
||||
title={i18n.t('dialog.disconnect.title')}
|
||||
titleLoading={i18n.t('dialog.disconnect.titleLoading')}
|
||||
message={i18n.t('dialog.disconnect.message')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LogoutDialog;
|
||||
|
|
@ -17,29 +17,42 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {Headline, useTheme} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
// @flow
|
||||
|
||||
function VoteNotAvailable() {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<Headline
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {Headline, withTheme} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
class VoteNotAvailable extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
color: theme.colors.textDisabled,
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
{i18n.t('screens.vote.noVote')}
|
||||
</Headline>
|
||||
</View>
|
||||
);
|
||||
<Headline
|
||||
style={{
|
||||
color: props.theme.colors.textDisabled,
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
{i18n.t('screens.vote.noVote')}
|
||||
</Headline>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default VoteNotAvailable;
|
||||
export default withTheme(VoteNotAvailable);
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
|
|
@ -29,11 +31,16 @@ import {
|
|||
import {FlatList, StyleSheet} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import type {VoteTeamType} from '../../../screens/Amicale/VoteScreen';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
import type {
|
||||
CardTitleIconPropsType,
|
||||
ListIconPropsType,
|
||||
} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
teams: Array<VoteTeamType>;
|
||||
dateEnd: string;
|
||||
theme: ReactNativePaper.Theme;
|
||||
teams: Array<VoteTeamType>,
|
||||
dateEnd: string,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -51,10 +58,10 @@ class VoteResults extends React.Component<PropsType> {
|
|||
winnerIds: Array<number>;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
super();
|
||||
props.teams.sort(this.sortByVotes);
|
||||
this.totalVotes = this.getTotalVotes(props.teams);
|
||||
this.winnerIds = this.getWinnerIds(props.teams);
|
||||
this.getTotalVotes(props.teams);
|
||||
this.getWinnerIds(props.teams);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(): boolean {
|
||||
|
|
@ -62,31 +69,26 @@ class VoteResults extends React.Component<PropsType> {
|
|||
}
|
||||
|
||||
getTotalVotes(teams: Array<VoteTeamType>) {
|
||||
let totalVotes = 0;
|
||||
this.totalVotes = 0;
|
||||
for (let i = 0; i < teams.length; i += 1) {
|
||||
totalVotes += teams[i].votes;
|
||||
this.totalVotes += teams[i].votes;
|
||||
}
|
||||
return totalVotes;
|
||||
}
|
||||
|
||||
getWinnerIds(teams: Array<VoteTeamType>) {
|
||||
const max = teams[0].votes;
|
||||
let winnerIds = [];
|
||||
this.winnerIds = [];
|
||||
for (let i = 0; i < teams.length; i += 1) {
|
||||
if (teams[i].votes === max) {
|
||||
winnerIds.push(teams[i].id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (teams[i].votes === max) this.winnerIds.push(teams[i].id);
|
||||
else break;
|
||||
}
|
||||
return winnerIds;
|
||||
}
|
||||
|
||||
sortByVotes = (a: VoteTeamType, b: VoteTeamType): number => b.votes - a.votes;
|
||||
|
||||
voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
|
||||
|
||||
resultRenderItem = ({item}: {item: VoteTeamType}) => {
|
||||
resultRenderItem = ({item}: {item: VoteTeamType}): React.Node => {
|
||||
const isWinner = this.winnerIds.indexOf(item.id) !== -1;
|
||||
const isDraw = this.winnerIds.length > 1;
|
||||
const {props} = this;
|
||||
|
|
@ -99,7 +101,7 @@ class VoteResults extends React.Component<PropsType> {
|
|||
<List.Item
|
||||
title={item.name}
|
||||
description={`${item.votes} ${i18n.t('screens.vote.results.votes')}`}
|
||||
left={(iconProps) =>
|
||||
left={(iconProps: ListIconPropsType): React.Node =>
|
||||
isWinner ? (
|
||||
<List.Icon
|
||||
style={iconProps.style}
|
||||
|
|
@ -123,7 +125,7 @@ class VoteResults extends React.Component<PropsType> {
|
|||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
|
|
@ -132,14 +134,15 @@ class VoteResults extends React.Component<PropsType> {
|
|||
subtitle={`${i18n.t('screens.vote.results.subtitle')} ${
|
||||
props.dateEnd
|
||||
}`}
|
||||
left={(iconProps) => (
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon size={iconProps.size} icon="podium-gold" />
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Subheading>
|
||||
{`${i18n.t('screens.vote.results.totalVotes')} ${this.totalVotes}`}
|
||||
</Subheading>
|
||||
<Subheading>{`${i18n.t('screens.vote.results.totalVotes')} ${
|
||||
this.totalVotes
|
||||
}`}</Subheading>
|
||||
{/* $FlowFixMe */}
|
||||
<FlatList
|
||||
data={props.teams}
|
||||
keyExtractor={this.voteKeyExtractor}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Button, Card, RadioButton} from 'react-native-paper';
|
||||
import {FlatList, StyleSheet, View} from 'react-native';
|
||||
|
|
@ -25,18 +27,19 @@ import ConnectionManager from '../../../managers/ConnectionManager';
|
|||
import LoadingConfirmDialog from '../../Dialogs/LoadingConfirmDialog';
|
||||
import ErrorDialog from '../../Dialogs/ErrorDialog';
|
||||
import type {VoteTeamType} from '../../../screens/Amicale/VoteScreen';
|
||||
import type {CardTitleIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
teams: Array<VoteTeamType>;
|
||||
onVoteSuccess: () => void;
|
||||
onVoteError: () => void;
|
||||
teams: Array<VoteTeamType>,
|
||||
onVoteSuccess: () => void,
|
||||
onVoteError: () => void,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
selectedTeam: string;
|
||||
voteDialogVisible: boolean;
|
||||
errorDialogVisible: boolean;
|
||||
currentError: number;
|
||||
selectedTeam: string,
|
||||
voteDialogVisible: boolean,
|
||||
errorDialogVisible: boolean,
|
||||
currentError: number,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -50,10 +53,10 @@ const styles = StyleSheet.create({
|
|||
|
||||
export default class VoteSelect extends React.PureComponent<
|
||||
PropsType,
|
||||
StateType
|
||||
StateType,
|
||||
> {
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
selectedTeam: 'none',
|
||||
voteDialogVisible: false,
|
||||
|
|
@ -67,7 +70,7 @@ export default class VoteSelect extends React.PureComponent<
|
|||
|
||||
voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
|
||||
|
||||
voteRenderItem = ({item}: {item: VoteTeamType}) => (
|
||||
voteRenderItem = ({item}: {item: VoteTeamType}): React.Node => (
|
||||
<RadioButton.Item label={item.name} value={item.id.toString()} />
|
||||
);
|
||||
|
||||
|
|
@ -108,7 +111,7 @@ export default class VoteSelect extends React.PureComponent<
|
|||
props.onVoteError();
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {state, props} = this;
|
||||
return (
|
||||
<View>
|
||||
|
|
@ -116,7 +119,7 @@ export default class VoteSelect extends React.PureComponent<
|
|||
<Card.Title
|
||||
title={i18n.t('screens.vote.select.title')}
|
||||
subtitle={i18n.t('screens.vote.select.subtitle')}
|
||||
left={(iconProps) => (
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon size={iconProps.size} icon="alert-decagram" />
|
||||
)}
|
||||
/>
|
||||
|
|
@ -124,6 +127,7 @@ export default class VoteSelect extends React.PureComponent<
|
|||
<RadioButton.Group
|
||||
onValueChange={this.onVoteSelectionChange}
|
||||
value={state.selectedTeam}>
|
||||
{/* $FlowFixMe */}
|
||||
<FlatList
|
||||
data={props.teams}
|
||||
keyExtractor={this.voteKeyExtractor}
|
||||
|
|
@ -17,13 +17,16 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Card, Paragraph} from 'react-native-paper';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import type {CardTitleIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
startDate: string;
|
||||
startDate: string,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -35,19 +38,28 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
export default function VoteTease(props: PropsType) {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.vote.tease.title')}
|
||||
subtitle={i18n.t('screens.vote.tease.subtitle')}
|
||||
left={(iconProps) => <Avatar.Icon size={iconProps.size} icon="vote" />}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Paragraph>
|
||||
{`${i18n.t('screens.vote.tease.message')} ${props.startDate}`}
|
||||
</Paragraph>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
export default class VoteTease extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.vote.tease.title')}
|
||||
subtitle={i18n.t('screens.vote.tease.subtitle')}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon size={iconProps.size} icon="vote" />
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Paragraph>
|
||||
{`${i18n.t('screens.vote.tease.message')} ${props.startDate}`}
|
||||
</Paragraph>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
93
src/components/Amicale/Vote/VoteWait.js
Normal file
93
src/components/Amicale/Vote/VoteWait.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Card, Paragraph, withTheme} from 'react-native-paper';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
import type {CardTitleIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
startDate: string | null,
|
||||
justVoted: boolean,
|
||||
hasVoted: boolean,
|
||||
isVoteRunning: boolean,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
margin: 10,
|
||||
},
|
||||
icon: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
class VoteWait extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {startDate} = props;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={
|
||||
props.isVoteRunning
|
||||
? i18n.t('screens.vote.wait.titleSubmitted')
|
||||
: i18n.t('screens.vote.wait.titleEnded')
|
||||
}
|
||||
subtitle={i18n.t('screens.vote.wait.subtitle')}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon size={iconProps.size} icon="progress-check" />
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
{props.justVoted ? (
|
||||
<Paragraph style={{color: props.theme.colors.success}}>
|
||||
{i18n.t('screens.vote.wait.messageSubmitted')}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{props.hasVoted ? (
|
||||
<Paragraph style={{color: props.theme.colors.success}}>
|
||||
{i18n.t('screens.vote.wait.messageVoted')}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{startDate != null ? (
|
||||
<Paragraph>
|
||||
{`${i18n.t('screens.vote.wait.messageDate')} ${startDate}`}
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph>
|
||||
{i18n.t('screens.vote.wait.messageDateUndefined')}
|
||||
</Paragraph>
|
||||
)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(VoteWait);
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Card, Paragraph, useTheme} from 'react-native-paper';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
type PropsType = {
|
||||
startDate: string | null;
|
||||
justVoted: boolean;
|
||||
hasVoted: boolean;
|
||||
isVoteRunning: boolean;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
margin: 10,
|
||||
},
|
||||
icon: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
export default function VoteWait(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
const {startDate} = props;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={
|
||||
props.isVoteRunning
|
||||
? i18n.t('screens.vote.wait.titleSubmitted')
|
||||
: i18n.t('screens.vote.wait.titleEnded')
|
||||
}
|
||||
subtitle={i18n.t('screens.vote.wait.subtitle')}
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon size={iconProps.size} icon="progress-check" />
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
{props.justVoted ? (
|
||||
<Paragraph style={{color: theme.colors.success}}>
|
||||
{i18n.t('screens.vote.wait.messageSubmitted')}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{props.hasVoted ? (
|
||||
<Paragraph style={{color: theme.colors.success}}>
|
||||
{i18n.t('screens.vote.wait.messageVoted')}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{startDate != null ? (
|
||||
<Paragraph>
|
||||
{`${i18n.t('screens.vote.wait.messageDate')} ${startDate}`}
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph>
|
||||
{i18n.t('screens.vote.wait.messageDateUndefined')}
|
||||
</Paragraph>
|
||||
)}
|
||||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,37 +17,42 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View, ViewStyle} from 'react-native';
|
||||
import {View} from 'react-native';
|
||||
import {List, withTheme} from 'react-native-paper';
|
||||
import Collapsible from 'react-native-collapsible';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import type {ListIconPropsType} from '../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
theme: ReactNativePaper.Theme;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
style?: ViewStyle;
|
||||
left?: (props: {
|
||||
color: string;
|
||||
style?: {
|
||||
marginRight: number;
|
||||
marginVertical?: number;
|
||||
};
|
||||
}) => React.ReactNode;
|
||||
opened?: boolean;
|
||||
unmountWhenCollapsed?: boolean;
|
||||
children?: React.ReactNode;
|
||||
theme: CustomThemeType,
|
||||
title: string,
|
||||
subtitle?: string,
|
||||
left?: () => React.Node,
|
||||
opened?: boolean,
|
||||
unmountWhenCollapsed?: boolean,
|
||||
children?: React.Node,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
expanded: boolean;
|
||||
expanded: boolean,
|
||||
};
|
||||
|
||||
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
|
||||
|
||||
class AnimatedAccordion extends React.Component<PropsType, StateType> {
|
||||
chevronRef: {current: null | (typeof AnimatedListIcon & List.Icon)};
|
||||
static defaultProps = {
|
||||
subtitle: '',
|
||||
left: null,
|
||||
opened: null,
|
||||
unmountWhenCollapsed: false,
|
||||
children: null,
|
||||
};
|
||||
|
||||
chevronRef: {current: null | AnimatedListIcon};
|
||||
|
||||
chevronIcon: string;
|
||||
|
||||
|
|
@ -57,9 +62,6 @@ class AnimatedAccordion extends React.Component<PropsType, StateType> {
|
|||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.chevronIcon = '';
|
||||
this.animStart = '';
|
||||
this.animEnd = '';
|
||||
this.state = {
|
||||
expanded: props.opened != null ? props.opened : false,
|
||||
};
|
||||
|
|
@ -69,9 +71,8 @@ class AnimatedAccordion extends React.Component<PropsType, StateType> {
|
|||
|
||||
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||
const {state, props} = this;
|
||||
if (nextProps.opened != null && nextProps.opened !== props.opened) {
|
||||
if (nextProps.opened != null && nextProps.opened !== props.opened)
|
||||
state.expanded = nextProps.opened;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -100,17 +101,17 @@ class AnimatedAccordion extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props, state} = this;
|
||||
const {colors} = props.theme;
|
||||
return (
|
||||
<View style={props.style}>
|
||||
<View>
|
||||
<List.Item
|
||||
title={props.title}
|
||||
description={props.subtitle}
|
||||
subtitle={props.subtitle}
|
||||
titleStyle={state.expanded ? {color: colors.primary} : null}
|
||||
onPress={this.toggleAccordion}
|
||||
right={(iconProps) => (
|
||||
right={(iconProps: ListIconPropsType): React.Node => (
|
||||
<AnimatedListIcon
|
||||
ref={this.chevronRef}
|
||||
style={iconProps.style}
|
||||
|
|
@ -17,30 +17,29 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import {FAB, IconButton, Surface, withTheme} from 'react-native-paper';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import AutoHideHandler from '../../utils/AutoHideHandler';
|
||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import type {OnScrollType} from '../../utils/AutoHideHandler';
|
||||
|
||||
const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
theme: ReactNativePaper.Theme;
|
||||
onPress: (action: string, data?: string) => void;
|
||||
seekAttention: boolean;
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomThemeType,
|
||||
onPress: (action: string, data?: string) => void,
|
||||
seekAttention: boolean,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
currentMode: string;
|
||||
currentMode: string,
|
||||
};
|
||||
|
||||
const DISPLAY_MODES = {
|
||||
|
|
@ -79,14 +78,14 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class AnimatedBottomBar extends React.Component<PropsType, StateType> {
|
||||
ref: {current: null | (Animatable.View & View)};
|
||||
ref: {current: null | Animatable.View};
|
||||
|
||||
hideHandler: AutoHideHandler;
|
||||
|
||||
displayModeIcons: {[key: string]: string};
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
currentMode: DISPLAY_MODES.WEEK,
|
||||
};
|
||||
|
|
@ -109,17 +108,13 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
|
||||
onHideChange = (shouldHide: boolean) => {
|
||||
const ref = this.ref;
|
||||
if (ref && ref.current && ref.current.fadeOutDown && ref.current.fadeInUp) {
|
||||
if (shouldHide) {
|
||||
ref.current.fadeOutDown(500);
|
||||
} else {
|
||||
ref.current.fadeInUp(500);
|
||||
}
|
||||
if (this.ref.current != null) {
|
||||
if (shouldHide) this.ref.current.fadeOutDown(500);
|
||||
else this.ref.current.fadeInUp(500);
|
||||
}
|
||||
};
|
||||
|
||||
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
onScroll = (event: OnScrollType) => {
|
||||
this.hideHandler.onScroll(event);
|
||||
};
|
||||
|
||||
|
|
@ -144,7 +139,7 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
|
|||
props.onPress('changeView', newMode);
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props, state} = this;
|
||||
const buttonColor = props.theme.colors.primary;
|
||||
return (
|
||||
|
|
@ -17,23 +17,22 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import {FAB} from 'react-native-paper';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import AutoHideHandler from '../../utils/AutoHideHandler';
|
||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
||||
|
||||
type PropsType = {
|
||||
icon: string;
|
||||
onPress: () => void;
|
||||
icon: string,
|
||||
onPress: () => void,
|
||||
};
|
||||
|
||||
const AnimatedFab = Animatable.createAnimatableComponent(FAB);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fab: {
|
||||
position: 'absolute',
|
||||
|
|
@ -43,49 +42,41 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
export default class AnimatedFAB extends React.Component<PropsType> {
|
||||
ref: {current: null | (Animatable.View & View)};
|
||||
ref: {current: null | Animatable.View};
|
||||
|
||||
hideHandler: AutoHideHandler;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super();
|
||||
this.ref = React.createRef();
|
||||
this.hideHandler = new AutoHideHandler(false);
|
||||
this.hideHandler.addListener(this.onHideChange);
|
||||
}
|
||||
|
||||
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
this.hideHandler.onScroll(event);
|
||||
};
|
||||
|
||||
onHideChange = (shouldHide: boolean) => {
|
||||
const ref = this.ref;
|
||||
if (
|
||||
ref &&
|
||||
ref.current &&
|
||||
ref.current.bounceOutDown &&
|
||||
ref.current.bounceInUp
|
||||
) {
|
||||
if (shouldHide) {
|
||||
ref.current.bounceOutDown(1000);
|
||||
} else {
|
||||
ref.current.bounceInUp(1000);
|
||||
}
|
||||
if (this.ref.current != null) {
|
||||
if (shouldHide) this.ref.current.bounceOutDown(1000);
|
||||
else this.ref.current.bounceInUp(1000);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Animatable.View
|
||||
<AnimatedFab
|
||||
ref={this.ref}
|
||||
useNativeDriver={true}
|
||||
useNativeDriver
|
||||
icon={props.icon}
|
||||
onPress={props.onPress}
|
||||
style={{
|
||||
...styles.fab,
|
||||
bottom: CustomTabBar.TAB_BAR_HEIGHT,
|
||||
}}>
|
||||
<FAB icon={props.icon} onPress={props.onPress} />
|
||||
</Animatable.View>
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
78
src/components/Collapsible/CollapsibleComponent.js
Normal file
78
src/components/Collapsible/CollapsibleComponent.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Collapsible} from 'react-navigation-collapsible';
|
||||
import withCollapsible from '../../utils/withCollapsible';
|
||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
||||
|
||||
export type CollapsibleComponentPropsType = {
|
||||
children?: React.Node,
|
||||
hasTab?: boolean,
|
||||
onScroll?: (event: SyntheticEvent<EventTarget>) => void,
|
||||
};
|
||||
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
collapsibleStack: Collapsible,
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
component: any,
|
||||
};
|
||||
|
||||
class CollapsibleComponent extends React.Component<PropsType> {
|
||||
static defaultProps = {
|
||||
children: null,
|
||||
hasTab: false,
|
||||
onScroll: null,
|
||||
};
|
||||
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
const {props} = this;
|
||||
if (props.onScroll) props.onScroll(event);
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const Comp = props.component;
|
||||
const {
|
||||
containerPaddingTop,
|
||||
scrollIndicatorInsetTop,
|
||||
onScrollWithListener,
|
||||
} = props.collapsibleStack;
|
||||
|
||||
return (
|
||||
<Comp
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
onScroll={onScrollWithListener(this.onScroll)}
|
||||
contentContainerStyle={{
|
||||
paddingTop: containerPaddingTop,
|
||||
paddingBottom: props.hasTab ? CustomTabBar.TAB_BAR_HEIGHT : 0,
|
||||
minHeight: '100%',
|
||||
}}
|
||||
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}>
|
||||
{props.children}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withCollapsible(CollapsibleComponent);
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {useCollapsibleStack} from 'react-navigation-collapsible';
|
||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
||||
import {NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
|
||||
|
||||
export interface CollapsibleComponentPropsType {
|
||||
children?: React.ReactNode;
|
||||
hasTab?: boolean;
|
||||
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||
}
|
||||
|
||||
interface PropsType extends CollapsibleComponentPropsType {
|
||||
component: React.ComponentType<any>;
|
||||
}
|
||||
|
||||
function CollapsibleComponent(props: PropsType) {
|
||||
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (props.onScroll) {
|
||||
props.onScroll(event);
|
||||
}
|
||||
};
|
||||
const Comp = props.component;
|
||||
const {
|
||||
containerPaddingTop,
|
||||
scrollIndicatorInsetTop,
|
||||
onScrollWithListener,
|
||||
} = useCollapsibleStack();
|
||||
|
||||
return (
|
||||
<Comp
|
||||
{...props}
|
||||
onScroll={onScrollWithListener(onScroll)}
|
||||
contentContainerStyle={{
|
||||
paddingTop: containerPaddingTop,
|
||||
paddingBottom: props.hasTab ? CustomTabBar.TAB_BAR_HEIGHT : 0,
|
||||
minHeight: '100%',
|
||||
}}
|
||||
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}>
|
||||
{props.children}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollapsibleComponent;
|
||||
|
|
@ -17,19 +17,29 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated, FlatListProps} from 'react-native';
|
||||
import {Animated} from 'react-native';
|
||||
import type {CollapsibleComponentPropsType} from './CollapsibleComponent';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
|
||||
type Props<T> = FlatListProps<T> & CollapsibleComponentPropsType;
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
};
|
||||
|
||||
function CollapsibleFlatList<T>(props: Props<T>) {
|
||||
return (
|
||||
<CollapsibleComponent {...props} component={Animated.FlatList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class CollapsibleFlatList extends React.Component<PropsType> {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<CollapsibleComponent // eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
component={Animated.FlatList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CollapsibleFlatList;
|
||||
|
|
@ -17,19 +17,29 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated, ScrollViewProps} from 'react-native';
|
||||
import {Animated} from 'react-native';
|
||||
import type {CollapsibleComponentPropsType} from './CollapsibleComponent';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
|
||||
type Props = ScrollViewProps & CollapsibleComponentPropsType;
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
};
|
||||
|
||||
function CollapsibleScrollView(props: Props) {
|
||||
return (
|
||||
<CollapsibleComponent {...props} component={Animated.ScrollView}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class CollapsibleScrollView extends React.Component<PropsType> {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<CollapsibleComponent // eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
component={Animated.ScrollView}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CollapsibleScrollView;
|
||||
|
|
@ -17,19 +17,29 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated, SectionListProps} from 'react-native';
|
||||
import {Animated} from 'react-native';
|
||||
import type {CollapsibleComponentPropsType} from './CollapsibleComponent';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
|
||||
type Props<T> = SectionListProps<T> & CollapsibleComponentPropsType;
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
};
|
||||
|
||||
function CollapsibleSectionList<T>(props: Props<T>) {
|
||||
return (
|
||||
<CollapsibleComponent {...props} component={Animated.SectionList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class CollapsibleSectionList extends React.Component<PropsType> {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<CollapsibleComponent // eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
component={Animated.SectionList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CollapsibleSectionList;
|
||||
|
|
@ -17,31 +17,36 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Dialog, Paragraph, Portal} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
type PropsType = {
|
||||
visible: boolean;
|
||||
onDismiss: () => void;
|
||||
title: string | React.ReactNode;
|
||||
message: string | React.ReactNode;
|
||||
visible: boolean,
|
||||
onDismiss: () => void,
|
||||
title: string | React.Node,
|
||||
message: string | React.Node,
|
||||
};
|
||||
|
||||
function AlertDialog(props: PropsType) {
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog visible={props.visible} onDismiss={props.onDismiss}>
|
||||
<Dialog.Title>{props.title}</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
<Paragraph>{props.message}</Paragraph>
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button onPress={props.onDismiss}>{i18n.t('dialog.ok')}</Button>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
class AlertDialog extends React.PureComponent<PropsType> {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog visible={props.visible} onDismiss={props.onDismiss}>
|
||||
<Dialog.Title>{props.title}</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
<Paragraph>{props.message}</Paragraph>
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button onPress={props.onDismiss}>{i18n.t('dialog.ok')}</Button>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AlertDialog;
|
||||
90
src/components/Dialogs/ErrorDialog.js
Normal file
90
src/components/Dialogs/ErrorDialog.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import i18n from 'i18n-js';
|
||||
import {ERROR_TYPE} from '../../utils/WebData';
|
||||
import AlertDialog from './AlertDialog';
|
||||
|
||||
type PropsType = {
|
||||
visible: boolean,
|
||||
onDismiss: () => void,
|
||||
errorCode: number,
|
||||
};
|
||||
|
||||
class ErrorDialog extends React.PureComponent<PropsType> {
|
||||
title: string;
|
||||
|
||||
message: string;
|
||||
|
||||
generateMessage() {
|
||||
const {props} = this;
|
||||
this.title = i18n.t('errors.title');
|
||||
switch (props.errorCode) {
|
||||
case ERROR_TYPE.BAD_CREDENTIALS:
|
||||
this.message = i18n.t('errors.badCredentials');
|
||||
break;
|
||||
case ERROR_TYPE.BAD_TOKEN:
|
||||
this.message = i18n.t('errors.badToken');
|
||||
break;
|
||||
case ERROR_TYPE.NO_CONSENT:
|
||||
this.message = i18n.t('errors.noConsent');
|
||||
break;
|
||||
case ERROR_TYPE.TOKEN_SAVE:
|
||||
this.message = i18n.t('errors.tokenSave');
|
||||
break;
|
||||
case ERROR_TYPE.TOKEN_RETRIEVE:
|
||||
this.message = i18n.t('errors.unknown');
|
||||
break;
|
||||
case ERROR_TYPE.BAD_INPUT:
|
||||
this.message = i18n.t('errors.badInput');
|
||||
break;
|
||||
case ERROR_TYPE.FORBIDDEN:
|
||||
this.message = i18n.t('errors.forbidden');
|
||||
break;
|
||||
case ERROR_TYPE.CONNECTION_ERROR:
|
||||
this.message = i18n.t('errors.connectionError');
|
||||
break;
|
||||
case ERROR_TYPE.SERVER_ERROR:
|
||||
this.message = i18n.t('errors.serverError');
|
||||
break;
|
||||
default:
|
||||
this.message = i18n.t('errors.unknown');
|
||||
break;
|
||||
}
|
||||
this.message += `\n\nCode ${props.errorCode}`;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
this.generateMessage();
|
||||
const {props} = this;
|
||||
return (
|
||||
<AlertDialog
|
||||
visible={props.visible}
|
||||
onDismiss={props.onDismiss}
|
||||
title={this.title}
|
||||
message={this.message}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorDialog;
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import i18n from 'i18n-js';
|
||||
import {ERROR_TYPE} from '../../utils/WebData';
|
||||
import AlertDialog from './AlertDialog';
|
||||
|
||||
type PropsType = {
|
||||
visible: boolean;
|
||||
onDismiss: () => void;
|
||||
errorCode: number;
|
||||
};
|
||||
|
||||
function ErrorDialog(props: PropsType) {
|
||||
let title: string;
|
||||
let message: string;
|
||||
|
||||
title = i18n.t('errors.title');
|
||||
switch (props.errorCode) {
|
||||
case ERROR_TYPE.BAD_CREDENTIALS:
|
||||
message = i18n.t('errors.badCredentials');
|
||||
break;
|
||||
case ERROR_TYPE.BAD_TOKEN:
|
||||
message = i18n.t('errors.badToken');
|
||||
break;
|
||||
case ERROR_TYPE.NO_CONSENT:
|
||||
message = i18n.t('errors.noConsent');
|
||||
break;
|
||||
case ERROR_TYPE.TOKEN_SAVE:
|
||||
message = i18n.t('errors.tokenSave');
|
||||
break;
|
||||
case ERROR_TYPE.TOKEN_RETRIEVE:
|
||||
message = i18n.t('errors.unknown');
|
||||
break;
|
||||
case ERROR_TYPE.BAD_INPUT:
|
||||
message = i18n.t('errors.badInput');
|
||||
break;
|
||||
case ERROR_TYPE.FORBIDDEN:
|
||||
message = i18n.t('errors.forbidden');
|
||||
break;
|
||||
case ERROR_TYPE.CONNECTION_ERROR:
|
||||
message = i18n.t('errors.connectionError');
|
||||
break;
|
||||
case ERROR_TYPE.SERVER_ERROR:
|
||||
message = i18n.t('errors.serverError');
|
||||
break;
|
||||
default:
|
||||
message = i18n.t('errors.unknown');
|
||||
break;
|
||||
}
|
||||
message += `\n\nCode ${props.errorCode}`;
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
visible={props.visible}
|
||||
onDismiss={props.onDismiss}
|
||||
title={title}
|
||||
message={message}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorDialog;
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
|
|
@ -28,23 +30,20 @@ import {
|
|||
import i18n from 'i18n-js';
|
||||
|
||||
type PropsType = {
|
||||
visible: boolean;
|
||||
onDismiss?: () => void;
|
||||
onAccept?: () => Promise<void>; // async function to be executed
|
||||
title?: string;
|
||||
titleLoading?: string;
|
||||
message?: string;
|
||||
startLoading?: boolean;
|
||||
visible: boolean,
|
||||
onDismiss?: () => void,
|
||||
onAccept?: () => Promise<void>, // async function to be executed
|
||||
title?: string,
|
||||
titleLoading?: string,
|
||||
message?: string,
|
||||
startLoading?: boolean,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
loading: boolean;
|
||||
loading: boolean,
|
||||
};
|
||||
|
||||
export default class LoadingConfirmDialog extends React.PureComponent<
|
||||
PropsType,
|
||||
StateType
|
||||
> {
|
||||
class LoadingConfirmDialog extends React.PureComponent<PropsType, StateType> {
|
||||
static defaultProps = {
|
||||
onDismiss: () => {},
|
||||
onAccept: (): Promise<void> => {
|
||||
|
|
@ -72,16 +71,14 @@ export default class LoadingConfirmDialog extends React.PureComponent<
|
|||
onClickAccept = () => {
|
||||
const {props} = this;
|
||||
this.setState({loading: true});
|
||||
if (props.onAccept != null) {
|
||||
props.onAccept().then(this.hideLoading);
|
||||
}
|
||||
if (props.onAccept != null) props.onAccept().then(this.hideLoading);
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for fade out animations to finish before hiding loading
|
||||
* @returns {NodeJS.Timeout}
|
||||
* @returns {TimeoutID}
|
||||
*/
|
||||
hideLoading = (): NodeJS.Timeout =>
|
||||
hideLoading = (): TimeoutID =>
|
||||
setTimeout(() => {
|
||||
this.setState({loading: false});
|
||||
}, 200);
|
||||
|
|
@ -91,12 +88,10 @@ export default class LoadingConfirmDialog extends React.PureComponent<
|
|||
*/
|
||||
onDismiss = () => {
|
||||
const {state, props} = this;
|
||||
if (!state.loading && props.onDismiss != null) {
|
||||
props.onDismiss();
|
||||
}
|
||||
if (!state.loading && props.onDismiss != null) props.onDismiss();
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {state, props} = this;
|
||||
return (
|
||||
<Portal>
|
||||
|
|
@ -126,3 +121,5 @@ export default class LoadingConfirmDialog extends React.PureComponent<
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LoadingConfirmDialog;
|
||||
|
|
@ -17,26 +17,28 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Dialog, Paragraph, Portal} from 'react-native-paper';
|
||||
import {FlatList} from 'react-native';
|
||||
|
||||
export type OptionsDialogButtonType = {
|
||||
title: string;
|
||||
icon?: string;
|
||||
onPress: () => void;
|
||||
title: string,
|
||||
icon?: string,
|
||||
onPress: () => void,
|
||||
};
|
||||
|
||||
type PropsType = {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
buttons: Array<OptionsDialogButtonType>;
|
||||
onDismiss: () => void;
|
||||
visible: boolean,
|
||||
title: string,
|
||||
message: string,
|
||||
buttons: Array<OptionsDialogButtonType>,
|
||||
onDismiss: () => void,
|
||||
};
|
||||
|
||||
function OptionsDialog(props: PropsType) {
|
||||
const getButtonRender = ({item}: {item: OptionsDialogButtonType}) => {
|
||||
class OptionsDialog extends React.PureComponent<PropsType> {
|
||||
getButtonRender = ({item}: {item: OptionsDialogButtonType}): React.Node => {
|
||||
return (
|
||||
<Button onPress={item.onPress} icon={item.icon}>
|
||||
{item.title}
|
||||
|
|
@ -44,32 +46,35 @@ function OptionsDialog(props: PropsType) {
|
|||
);
|
||||
};
|
||||
|
||||
const keyExtractor = (item: OptionsDialogButtonType): string => {
|
||||
keyExtractor = (item: OptionsDialogButtonType): string => {
|
||||
if (item.icon != null) {
|
||||
return item.title + item.icon;
|
||||
}
|
||||
return item.title;
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog visible={props.visible} onDismiss={props.onDismiss}>
|
||||
<Dialog.Title>{props.title}</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
<Paragraph>{props.message}</Paragraph>
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<FlatList
|
||||
data={props.buttons}
|
||||
renderItem={getButtonRender}
|
||||
keyExtractor={keyExtractor}
|
||||
horizontal
|
||||
inverted
|
||||
/>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog visible={props.visible} onDismiss={props.onDismiss}>
|
||||
<Dialog.Title>{props.title}</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
<Paragraph>{props.message}</Paragraph>
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<FlatList
|
||||
data={props.buttons}
|
||||
renderItem={this.getButtonRender}
|
||||
keyExtractor={this.keyExtractor}
|
||||
horizontal
|
||||
inverted
|
||||
/>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OptionsDialog;
|
||||
75
src/components/Home/ActionsDashboardItem.js
Normal file
75
src/components/Home/ActionsDashboardItem.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {List, withTheme} from 'react-native-paper';
|
||||
import {View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import type {ListIconPropsType} from '../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
class ActionsDashBoardItem extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||
const {props} = this;
|
||||
return nextProps.theme.dark !== props.theme.dark;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {navigation} = this.props;
|
||||
return (
|
||||
<View>
|
||||
<List.Item
|
||||
title={i18n.t('screens.feedback.homeButtonTitle')}
|
||||
description={i18n.t('screens.feedback.homeButtonSubtitle')}
|
||||
left={(props: ListIconPropsType): React.Node => (
|
||||
<List.Icon
|
||||
color={props.color}
|
||||
style={props.style}
|
||||
icon="comment-quote"
|
||||
/>
|
||||
)}
|
||||
right={(props: ListIconPropsType): React.Node => (
|
||||
<List.Icon
|
||||
color={props.color}
|
||||
style={props.style}
|
||||
icon="chevron-right"
|
||||
/>
|
||||
)}
|
||||
onPress={(): void => navigation.navigate('feedback')}
|
||||
style={{
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(ActionsDashBoardItem);
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {List} from 'react-native-paper';
|
||||
import {View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
|
||||
function ActionsDashBoardItem() {
|
||||
const navigation = useNavigation();
|
||||
return (
|
||||
<View>
|
||||
<List.Item
|
||||
title={i18n.t('screens.feedback.homeButtonTitle')}
|
||||
description={i18n.t('screens.feedback.homeButtonSubtitle')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
color={props.color}
|
||||
style={props.style}
|
||||
icon="comment-quote"
|
||||
/>
|
||||
)}
|
||||
right={(props) => (
|
||||
<List.Icon
|
||||
color={props.color}
|
||||
style={props.style}
|
||||
icon="chevron-right"
|
||||
/>
|
||||
)}
|
||||
onPress={(): void => navigation.navigate('feedback')}
|
||||
style={{
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default ActionsDashBoardItem;
|
||||
116
src/components/Home/EventDashboardItem.js
Normal file
116
src/components/Home/EventDashboardItem.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Card,
|
||||
Text,
|
||||
TouchableRipple,
|
||||
withTheme,
|
||||
} from 'react-native-paper';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import type {CardTitleIconPropsType} from '../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
eventNumber: number,
|
||||
clickAction: () => void,
|
||||
theme: CustomThemeType,
|
||||
children?: React.Node,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
width: 'auto',
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
marginTop: 10,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
avatar: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component used to display a dashboard item containing a preview event
|
||||
*/
|
||||
class EventDashBoardItem extends React.Component<PropsType> {
|
||||
static defaultProps = {
|
||||
children: null,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||
const {props} = this;
|
||||
return (
|
||||
nextProps.theme.dark !== props.theme.dark ||
|
||||
nextProps.eventNumber !== props.eventNumber
|
||||
);
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {colors} = props.theme;
|
||||
const isAvailable = props.eventNumber > 0;
|
||||
const iconColor = isAvailable ? colors.planningColor : colors.textDisabled;
|
||||
const textColor = isAvailable ? colors.text : colors.textDisabled;
|
||||
let subtitle;
|
||||
if (isAvailable) {
|
||||
subtitle = (
|
||||
<Text>
|
||||
<Text style={{fontWeight: 'bold'}}>{props.eventNumber}</Text>
|
||||
<Text>
|
||||
{props.eventNumber > 1
|
||||
? i18n.t('screens.home.dashboard.todayEventsSubtitlePlural')
|
||||
: i18n.t('screens.home.dashboard.todayEventsSubtitle')}
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
} else subtitle = i18n.t('screens.home.dashboard.todayEventsSubtitleNA');
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<TouchableRipple style={{flex: 1}} onPress={props.clickAction}>
|
||||
<View>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.home.dashboard.todayEventsTitle')}
|
||||
titleStyle={{color: textColor}}
|
||||
subtitle={subtitle}
|
||||
subtitleStyle={{color: textColor}}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon
|
||||
icon="calendar-range"
|
||||
color={iconColor}
|
||||
size={iconProps.size}
|
||||
style={styles.avatar}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Card.Content>{props.children}</Card.Content>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(EventDashBoardItem);
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Card,
|
||||
Text,
|
||||
TouchableRipple,
|
||||
useTheme,
|
||||
} from 'react-native-paper';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
type PropsType = {
|
||||
eventNumber: number;
|
||||
clickAction: () => void;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
width: 'auto',
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
marginTop: 10,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
avatar: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component used to display a dashboard item containing a preview event
|
||||
*/
|
||||
function EventDashBoardItem(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
const isAvailable = props.eventNumber > 0;
|
||||
const iconColor = isAvailable
|
||||
? theme.colors.planningColor
|
||||
: theme.colors.textDisabled;
|
||||
const textColor = isAvailable ? theme.colors.text : theme.colors.textDisabled;
|
||||
let subtitle;
|
||||
if (isAvailable) {
|
||||
subtitle = (
|
||||
<Text>
|
||||
<Text style={{fontWeight: 'bold'}}>{props.eventNumber}</Text>
|
||||
<Text>
|
||||
{props.eventNumber > 1
|
||||
? i18n.t('screens.home.dashboard.todayEventsSubtitlePlural')
|
||||
: i18n.t('screens.home.dashboard.todayEventsSubtitle')}
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
subtitle = i18n.t('screens.home.dashboard.todayEventsSubtitleNA');
|
||||
}
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<TouchableRipple style={{flex: 1}} onPress={props.clickAction}>
|
||||
<View>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.home.dashboard.todayEventsTitle')}
|
||||
titleStyle={{color: textColor}}
|
||||
subtitle={subtitle}
|
||||
subtitleStyle={{color: textColor}}
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon
|
||||
icon="calendar-range"
|
||||
color={iconColor}
|
||||
size={iconProps.size}
|
||||
style={styles.avatar}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Card.Content>{props.children}</Card.Content>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const areEqual = (prevProps: PropsType, nextProps: PropsType): boolean => {
|
||||
return nextProps.eventNumber === prevProps.eventNumber;
|
||||
};
|
||||
|
||||
export default React.memo(EventDashBoardItem, areEqual);
|
||||
139
src/components/Home/FeedItem.js
Normal file
139
src/components/Home/FeedItem.js
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Card, Text, TouchableRipple} from 'react-native-paper';
|
||||
import {Image, View} from 'react-native';
|
||||
import Autolink from 'react-native-autolink';
|
||||
import i18n from 'i18n-js';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import type {FeedItemType} from '../../screens/Home/HomeScreen';
|
||||
import NewsSourcesConstants from '../../constants/NewsSourcesConstants';
|
||||
import type {NewsSourceType} from '../../constants/NewsSourcesConstants';
|
||||
import ImageGalleryButton from '../Media/ImageGalleryButton';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
item: FeedItemType,
|
||||
height: number,
|
||||
};
|
||||
|
||||
/**
|
||||
* Component used to display a feed item
|
||||
*/
|
||||
class FeedItem extends React.Component<PropsType> {
|
||||
/**
|
||||
* Converts a dateString using Unix Timestamp to a formatted date
|
||||
*
|
||||
* @param dateString {string} The Unix Timestamp representation of a date
|
||||
* @return {string} The formatted output date
|
||||
*/
|
||||
static getFormattedDate(dateString: number): string {
|
||||
const date = new Date(dateString * 1000);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
onPress = () => {
|
||||
const {item, navigation} = this.props;
|
||||
navigation.navigate('feed-information', {
|
||||
data: item,
|
||||
date: FeedItem.getFormattedDate(item.time),
|
||||
});
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
const {item, height, navigation} = this.props;
|
||||
const image = item.image !== '' && item.image != null ? item.image : null;
|
||||
const pageSource: NewsSourceType = NewsSourcesConstants[item.page_id];
|
||||
const cardMargin = 10;
|
||||
const cardHeight = height - 2 * cardMargin;
|
||||
const imageSize = 250;
|
||||
const titleHeight = 80;
|
||||
const actionsHeight = 60;
|
||||
const textHeight =
|
||||
image != null
|
||||
? cardHeight - titleHeight - actionsHeight - imageSize
|
||||
: cardHeight - titleHeight - actionsHeight;
|
||||
return (
|
||||
<Card
|
||||
style={{
|
||||
margin: cardMargin,
|
||||
height: cardHeight,
|
||||
}}>
|
||||
<TouchableRipple style={{flex: 1}} onPress={this.onPress}>
|
||||
<View>
|
||||
<Card.Title
|
||||
title={pageSource.name}
|
||||
subtitle={FeedItem.getFormattedDate(item.time)}
|
||||
left={(): React.Node => (
|
||||
<Image
|
||||
size={48}
|
||||
source={pageSource.icon}
|
||||
style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
style={{height: titleHeight}}
|
||||
/>
|
||||
{image != null ? (
|
||||
<ImageGalleryButton
|
||||
navigation={navigation}
|
||||
images={[{url: image}]}
|
||||
style={{
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Card.Content>
|
||||
{item.message !== undefined ? (
|
||||
<Autolink
|
||||
text={item.message}
|
||||
hashtag="facebook"
|
||||
component={Text}
|
||||
style={{height: textHeight}}
|
||||
/>
|
||||
) : null}
|
||||
</Card.Content>
|
||||
<Card.Actions style={{height: actionsHeight}}>
|
||||
<Button
|
||||
onPress={this.onPress}
|
||||
icon="plus"
|
||||
style={{marginLeft: 'auto'}}>
|
||||
{i18n.t('screens.home.dashboard.seeMore')}
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FeedItem;
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Card, Text, TouchableRipple} from 'react-native-paper';
|
||||
import {Image, View} from 'react-native';
|
||||
import Autolink from 'react-native-autolink';
|
||||
import i18n from 'i18n-js';
|
||||
import type {FeedItemType} from '../../screens/Home/HomeScreen';
|
||||
import NewsSourcesConstants, {
|
||||
AvailablePages,
|
||||
} from '../../constants/NewsSourcesConstants';
|
||||
import type {NewsSourceType} from '../../constants/NewsSourcesConstants';
|
||||
import ImageGalleryButton from '../Media/ImageGalleryButton';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
|
||||
type PropsType = {
|
||||
item: FeedItemType;
|
||||
height: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a dateString using Unix Timestamp to a formatted date
|
||||
*
|
||||
* @param dateString {string} The Unix Timestamp representation of a date
|
||||
* @return {string} The formatted output date
|
||||
*/
|
||||
function getFormattedDate(dateString: number): string {
|
||||
const date = new Date(dateString * 1000);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component used to display a feed item
|
||||
*/
|
||||
function FeedItem(props: PropsType) {
|
||||
const navigation = useNavigation();
|
||||
const onPress = () => {
|
||||
navigation.navigate('feed-information', {
|
||||
data: item,
|
||||
date: getFormattedDate(props.item.time),
|
||||
});
|
||||
};
|
||||
|
||||
const {item, height} = props;
|
||||
const image = item.image !== '' && item.image != null ? item.image : null;
|
||||
const pageSource: NewsSourceType =
|
||||
NewsSourcesConstants[item.page_id as AvailablePages];
|
||||
const cardMargin = 10;
|
||||
const cardHeight = height - 2 * cardMargin;
|
||||
const imageSize = 250;
|
||||
const titleHeight = 80;
|
||||
const actionsHeight = 60;
|
||||
const textHeight =
|
||||
image != null
|
||||
? cardHeight - titleHeight - actionsHeight - imageSize
|
||||
: cardHeight - titleHeight - actionsHeight;
|
||||
return (
|
||||
<Card
|
||||
style={{
|
||||
margin: cardMargin,
|
||||
height: cardHeight,
|
||||
}}>
|
||||
<TouchableRipple style={{flex: 1}} onPress={onPress}>
|
||||
<View>
|
||||
<Card.Title
|
||||
title={pageSource.name}
|
||||
subtitle={getFormattedDate(item.time)}
|
||||
left={() => (
|
||||
<Image
|
||||
source={pageSource.icon}
|
||||
style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
style={{height: titleHeight}}
|
||||
/>
|
||||
{image != null ? (
|
||||
<ImageGalleryButton
|
||||
images={[{url: image}]}
|
||||
style={{
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Card.Content>
|
||||
{item.message !== undefined ? (
|
||||
<Autolink<typeof Text>
|
||||
text={item.message}
|
||||
hashtag="facebook"
|
||||
component={Text}
|
||||
style={{height: textHeight}}
|
||||
/>
|
||||
) : null}
|
||||
</Card.Content>
|
||||
<Card.Actions style={{height: actionsHeight}}>
|
||||
<Button onPress={onPress} icon="plus" style={{marginLeft: 'auto'}}>
|
||||
{i18n.t('screens.home.dashboard.seeMore')}
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(FeedItem, () => true);
|
||||
113
src/components/Home/PreviewEventDashboardItem.js
Normal file
113
src/components/Home/PreviewEventDashboardItem.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import {Avatar, Button, Card, TouchableRipple} from 'react-native-paper';
|
||||
import {getTimeOnlyString, isDescriptionEmpty} from '../../utils/Planning';
|
||||
import CustomHTML from '../Overrides/CustomHTML';
|
||||
import type {PlanningEventType} from '../../utils/Planning';
|
||||
|
||||
type PropsType = {
|
||||
event?: PlanningEventType | null,
|
||||
clickAction: () => void,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
content: {
|
||||
maxHeight: 150,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
actions: {
|
||||
marginLeft: 'auto',
|
||||
marginTop: 'auto',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
avatar: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component used to display an event preview if an event is available
|
||||
*/
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class PreviewEventDashboardItem extends React.Component<PropsType> {
|
||||
static defaultProps = {
|
||||
event: null,
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {event} = props;
|
||||
const isEmpty =
|
||||
event == null ? true : isDescriptionEmpty(event.description);
|
||||
|
||||
if (event != null) {
|
||||
const hasImage = event.logo !== '' && event.logo != null;
|
||||
const getImage = (): React.Node => (
|
||||
<Avatar.Image
|
||||
source={{uri: event.logo}}
|
||||
size={50}
|
||||
style={styles.avatar}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Card style={styles.card} elevation={3}>
|
||||
<TouchableRipple style={{flex: 1}} onPress={props.clickAction}>
|
||||
<View>
|
||||
{hasImage ? (
|
||||
<Card.Title
|
||||
title={event.title}
|
||||
subtitle={getTimeOnlyString(event.date_begin)}
|
||||
left={getImage}
|
||||
/>
|
||||
) : (
|
||||
<Card.Title
|
||||
title={event.title}
|
||||
subtitle={getTimeOnlyString(event.date_begin)}
|
||||
/>
|
||||
)}
|
||||
{!isEmpty ? (
|
||||
<Card.Content style={styles.content}>
|
||||
<CustomHTML html={event.description} />
|
||||
</Card.Content>
|
||||
) : null}
|
||||
|
||||
<Card.Actions style={styles.actions}>
|
||||
<Button icon="chevron-right">
|
||||
{i18n.t('screens.home.dashboard.seeMore')}
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default PreviewEventDashboardItem;
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import {Avatar, Button, Card, TouchableRipple} from 'react-native-paper';
|
||||
import {getTimeOnlyString, isDescriptionEmpty} from '../../utils/Planning';
|
||||
import CustomHTML from '../Overrides/CustomHTML';
|
||||
import type {PlanningEventType} from '../../utils/Planning';
|
||||
|
||||
type PropsType = {
|
||||
event?: PlanningEventType | null;
|
||||
clickAction: () => void;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
content: {
|
||||
maxHeight: 150,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
actions: {
|
||||
marginLeft: 'auto',
|
||||
marginTop: 'auto',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
avatar: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component used to display an event preview if an event is available
|
||||
*/
|
||||
function PreviewEventDashboardItem(props: PropsType) {
|
||||
const {event} = props;
|
||||
const isEmpty = event == null ? true : isDescriptionEmpty(event.description);
|
||||
|
||||
if (event != null) {
|
||||
const logo = event.logo;
|
||||
const getImage = logo
|
||||
? () => (
|
||||
<Avatar.Image source={{uri: logo}} size={50} style={styles.avatar} />
|
||||
)
|
||||
: () => null;
|
||||
return (
|
||||
<Card style={styles.card} elevation={3}>
|
||||
<TouchableRipple style={{flex: 1}} onPress={props.clickAction}>
|
||||
<View>
|
||||
<Card.Title
|
||||
title={event.title}
|
||||
subtitle={getTimeOnlyString(event.date_begin)}
|
||||
left={getImage}
|
||||
/>
|
||||
{!isEmpty ? (
|
||||
<Card.Content style={styles.content}>
|
||||
<CustomHTML html={event.description} />
|
||||
</Card.Content>
|
||||
) : null}
|
||||
|
||||
<Card.Actions style={styles.actions}>
|
||||
<Button icon="chevron-right">
|
||||
{i18n.t('screens.home.dashboard.seeMore')}
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default PreviewEventDashboardItem;
|
||||
106
src/components/Home/SmallDashboardItem.js
Normal file
106
src/components/Home/SmallDashboardItem.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Badge, TouchableRipple, withTheme} from 'react-native-paper';
|
||||
import {Dimensions, Image, View} from 'react-native';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
image: string | null,
|
||||
onPress: () => void | null,
|
||||
badgeCount: number | null,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
/**
|
||||
* Component used to render a small dashboard item
|
||||
*/
|
||||
class SmallDashboardItem extends React.Component<PropsType> {
|
||||
itemSize: number;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.itemSize = Dimensions.get('window').width / 8;
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||
const {props} = this;
|
||||
return (
|
||||
nextProps.theme.dark !== props.theme.dark ||
|
||||
nextProps.badgeCount !== props.badgeCount
|
||||
);
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<TouchableRipple
|
||||
onPress={props.onPress}
|
||||
borderless
|
||||
style={{
|
||||
marginLeft: this.itemSize / 6,
|
||||
marginRight: this.itemSize / 6,
|
||||
}}>
|
||||
<View
|
||||
style={{
|
||||
width: this.itemSize,
|
||||
height: this.itemSize,
|
||||
}}>
|
||||
<Image
|
||||
source={{uri: props.image}}
|
||||
style={{
|
||||
width: '80%',
|
||||
height: '80%',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
}}
|
||||
/>
|
||||
{props.badgeCount != null && props.badgeCount > 0 ? (
|
||||
<Animatable.View
|
||||
animation="zoomIn"
|
||||
duration={300}
|
||||
useNativeDriver
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
}}>
|
||||
<Badge
|
||||
style={{
|
||||
backgroundColor: props.theme.colors.primary,
|
||||
borderColor: props.theme.colors.background,
|
||||
borderWidth: 2,
|
||||
}}>
|
||||
{props.badgeCount}
|
||||
</Badge>
|
||||
</Animatable.View>
|
||||
) : null}
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(SmallDashboardItem);
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Badge, TouchableRipple, useTheme} from 'react-native-paper';
|
||||
import {Dimensions, Image, View} from 'react-native';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
|
||||
type PropsType = {
|
||||
image?: string | number;
|
||||
onPress?: () => void;
|
||||
badgeCount?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component used to render a small dashboard item
|
||||
*/
|
||||
function SmallDashboardItem(props: PropsType) {
|
||||
const itemSize = Dimensions.get('window').width / 8;
|
||||
const theme = useTheme();
|
||||
const {image} = props;
|
||||
return (
|
||||
<TouchableRipple
|
||||
onPress={props.onPress}
|
||||
borderless
|
||||
style={{
|
||||
marginLeft: itemSize / 6,
|
||||
marginRight: itemSize / 6,
|
||||
}}>
|
||||
<View
|
||||
style={{
|
||||
width: itemSize,
|
||||
height: itemSize,
|
||||
}}>
|
||||
{image ? (
|
||||
<Image
|
||||
source={typeof image === 'string' ? {uri: image} : image}
|
||||
style={{
|
||||
width: '80%',
|
||||
height: '80%',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{props.badgeCount != null && props.badgeCount > 0 ? (
|
||||
<Animatable.View
|
||||
animation="zoomIn"
|
||||
duration={300}
|
||||
useNativeDriver
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
}}>
|
||||
<Badge
|
||||
visible={true}
|
||||
style={{
|
||||
backgroundColor: theme.colors.primary,
|
||||
borderColor: theme.colors.background,
|
||||
borderWidth: 2,
|
||||
}}>
|
||||
{props.badgeCount}
|
||||
</Badge>
|
||||
</Animatable.View>
|
||||
) : null}
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
|
||||
const areEqual = (prevProps: PropsType, nextProps: PropsType): boolean => {
|
||||
return nextProps.badgeCount === prevProps.badgeCount;
|
||||
};
|
||||
|
||||
export default React.memo(SmallDashboardItem, areEqual);
|
||||
|
|
@ -17,13 +17,15 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
||||
type PropsType = {
|
||||
icon: string;
|
||||
icon: string,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -35,14 +37,24 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
function IntroIcon(props: PropsType) {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Animatable.View useNativeDriver style={styles.center} animation="fadeIn">
|
||||
<MaterialCommunityIcons name={props.icon} color="#fff" size={200} />
|
||||
</Animatable.View>
|
||||
</View>
|
||||
);
|
||||
class IntroIcon extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {icon} = this.props;
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Animatable.View
|
||||
useNativeDriver
|
||||
style={styles.center}
|
||||
animation="fadeIn">
|
||||
<MaterialCommunityIcons name={icon} color="#fff" size={200} />
|
||||
</Animatable.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default IntroIcon;
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import Mascot, {MASCOT_STYLE} from '../Mascot/Mascot';
|
||||
|
|
@ -30,28 +32,34 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
function MascotIntroEnd() {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Mascot
|
||||
style={{
|
||||
...styles.center,
|
||||
width: '80%',
|
||||
}}
|
||||
emotion={MASCOT_STYLE.COOL}
|
||||
animated
|
||||
entryAnimation={{
|
||||
animation: 'slideInDown',
|
||||
duration: 2000,
|
||||
}}
|
||||
loopAnimation={{
|
||||
animation: 'pulse',
|
||||
duration: 2000,
|
||||
iterationCount: 'infinite',
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
class MascotIntroEnd extends React.Component<null> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Mascot
|
||||
style={{
|
||||
...styles.center,
|
||||
width: '80%',
|
||||
}}
|
||||
emotion={MASCOT_STYLE.COOL}
|
||||
animated
|
||||
entryAnimation={{
|
||||
animation: 'slideInDown',
|
||||
duration: 2000,
|
||||
}}
|
||||
loopAnimation={{
|
||||
animation: 'pulse',
|
||||
duration: 2000,
|
||||
iterationCount: 'infinite',
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MascotIntroEnd;
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
|
|
@ -32,56 +34,62 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
function MascotIntroWelcome() {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Mascot
|
||||
style={{
|
||||
...styles.center,
|
||||
width: '80%',
|
||||
}}
|
||||
emotion={MASCOT_STYLE.NORMAL}
|
||||
animated
|
||||
entryAnimation={{
|
||||
animation: 'bounceIn',
|
||||
duration: 2000,
|
||||
}}
|
||||
/>
|
||||
<Animatable.Text
|
||||
useNativeDriver
|
||||
animation="fadeInUp"
|
||||
duration={500}
|
||||
style={{
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
fontSize: 25,
|
||||
}}>
|
||||
PABLO
|
||||
</Animatable.Text>
|
||||
<Animatable.View
|
||||
useNativeDriver
|
||||
animation="fadeInUp"
|
||||
duration={500}
|
||||
delay={200}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 30,
|
||||
right: '20%',
|
||||
width: 50,
|
||||
height: 50,
|
||||
}}>
|
||||
<MaterialCommunityIcons
|
||||
class MascotIntroWelcome extends React.Component<null> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Mascot
|
||||
style={{
|
||||
...styles.center,
|
||||
transform: [{rotateZ: '70deg'}],
|
||||
width: '80%',
|
||||
}}
|
||||
emotion={MASCOT_STYLE.NORMAL}
|
||||
animated
|
||||
entryAnimation={{
|
||||
animation: 'bounceIn',
|
||||
duration: 2000,
|
||||
}}
|
||||
name="undo"
|
||||
color="#fff"
|
||||
size={40}
|
||||
/>
|
||||
</Animatable.View>
|
||||
</View>
|
||||
);
|
||||
<Animatable.Text
|
||||
useNativeDriver
|
||||
animation="fadeInUp"
|
||||
duration={500}
|
||||
style={{
|
||||
color: '#fff',
|
||||
textAlign: 'center',
|
||||
fontSize: 25,
|
||||
}}>
|
||||
PABLO
|
||||
</Animatable.Text>
|
||||
<Animatable.View
|
||||
useNativeDriver
|
||||
animation="fadeInUp"
|
||||
duration={500}
|
||||
delay={200}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 30,
|
||||
right: '20%',
|
||||
width: 50,
|
||||
height: 50,
|
||||
}}>
|
||||
<MaterialCommunityIcons
|
||||
style={{
|
||||
...styles.center,
|
||||
transform: [{rotateZ: '70deg'}],
|
||||
}}
|
||||
name="undo"
|
||||
color="#fff"
|
||||
size={40}
|
||||
/>
|
||||
</Animatable.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MascotIntroWelcome;
|
||||
|
|
@ -17,16 +17,19 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated, Dimensions, ViewStyle} from 'react-native';
|
||||
import {Animated, Dimensions} from 'react-native';
|
||||
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
import ImageListItem from './ImageListItem';
|
||||
import CardListItem from './CardListItem';
|
||||
import type {ServiceItemType} from '../../../managers/ServicesManager';
|
||||
|
||||
type PropsType = {
|
||||
dataset: Array<ServiceItemType>;
|
||||
isHorizontal?: boolean;
|
||||
contentContainerStyle?: ViewStyle;
|
||||
dataset: Array<ServiceItemType>,
|
||||
isHorizontal?: boolean,
|
||||
contentContainerStyle?: ViewStyle | null,
|
||||
};
|
||||
|
||||
export default class CardList extends React.Component<PropsType> {
|
||||
|
|
@ -42,12 +45,12 @@ export default class CardList extends React.Component<PropsType> {
|
|||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.windowWidth = Dimensions.get('window').width;
|
||||
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}): React.Node => {
|
||||
const {props} = this;
|
||||
if (props.isHorizontal) {
|
||||
if (props.isHorizontal)
|
||||
return (
|
||||
<ImageListItem
|
||||
item={item}
|
||||
|
|
@ -55,13 +58,12 @@ export default class CardList extends React.Component<PropsType> {
|
|||
width={this.horizontalItemSize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <CardListItem item={item} key={item.title} />;
|
||||
};
|
||||
|
||||
keyExtractor = (item: ServiceItemType): string => item.key;
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
let containerStyle = {};
|
||||
if (props.isHorizontal) {
|
||||
|
|
@ -82,7 +84,7 @@ export default class CardList extends React.Component<PropsType> {
|
|||
}
|
||||
pagingEnabled={props.isHorizontal}
|
||||
snapToInterval={
|
||||
props.isHorizontal ? (this.horizontalItemSize + 5) * 3 : undefined
|
||||
props.isHorizontal ? (this.horizontalItemSize + 5) * 3 : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
|
@ -17,38 +17,45 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Caption, Card, Paragraph, TouchableRipple} from 'react-native-paper';
|
||||
import {View} from 'react-native';
|
||||
import type {ServiceItemType} from '../../../managers/ServicesManager';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceItemType;
|
||||
item: ServiceItemType,
|
||||
};
|
||||
|
||||
function CardListItem(props: PropsType) {
|
||||
const {item} = props;
|
||||
const source =
|
||||
typeof item.image === 'number' ? item.image : {uri: item.image};
|
||||
return (
|
||||
<Card
|
||||
style={{
|
||||
width: '40%',
|
||||
margin: 5,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}>
|
||||
<TouchableRipple style={{flex: 1}} onPress={item.onPress}>
|
||||
<View>
|
||||
<Card.Cover style={{height: 80}} source={source} />
|
||||
<Card.Content>
|
||||
<Paragraph>{item.title}</Paragraph>
|
||||
<Caption>{item.subtitle}</Caption>
|
||||
</Card.Content>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
export default class CardListItem extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export default React.memo(CardListItem, () => true);
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {item} = props;
|
||||
const source =
|
||||
typeof item.image === 'number' ? item.image : {uri: item.image};
|
||||
return (
|
||||
<Card
|
||||
style={{
|
||||
width: '40%',
|
||||
margin: 5,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}>
|
||||
<TouchableRipple style={{flex: 1}} onPress={item.onPress}>
|
||||
<View>
|
||||
<Card.Cover style={{height: 80}} source={source} />
|
||||
<Card.Content>
|
||||
<Paragraph>{item.title}</Paragraph>
|
||||
<Caption>{item.subtitle}</Caption>
|
||||
</Card.Content>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
73
src/components/Lists/CardList/ImageListItem.js
Normal file
73
src/components/Lists/CardList/ImageListItem.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Text, TouchableRipple} from 'react-native-paper';
|
||||
import {Image, View} from 'react-native';
|
||||
import type {ServiceItemType} from '../../../managers/ServicesManager';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceItemType,
|
||||
width: number,
|
||||
};
|
||||
|
||||
export default class ImageListItem extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {item} = props;
|
||||
const source =
|
||||
typeof item.image === 'number' ? item.image : {uri: item.image};
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={{
|
||||
width: props.width,
|
||||
height: props.width + 40,
|
||||
margin: 5,
|
||||
}}
|
||||
onPress={item.onPress}>
|
||||
<View>
|
||||
<Image
|
||||
style={{
|
||||
width: props.width - 20,
|
||||
height: props.width - 20,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
source={source}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 5,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
{item.title}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Text, TouchableRipple} from 'react-native-paper';
|
||||
import {Image, View} from 'react-native';
|
||||
import type {ServiceItemType} from '../../../managers/ServicesManager';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceItemType;
|
||||
width: number;
|
||||
};
|
||||
|
||||
function ImageListItem(props: PropsType) {
|
||||
const {item} = props;
|
||||
const source =
|
||||
typeof item.image === 'number' ? item.image : {uri: item.image};
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={{
|
||||
width: props.width,
|
||||
height: props.width + 40,
|
||||
margin: 5,
|
||||
}}
|
||||
onPress={item.onPress}>
|
||||
<View>
|
||||
<Image
|
||||
style={{
|
||||
width: props.width - 20,
|
||||
height: props.width - 20,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
source={source}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 5,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
{item.title}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ImageListItem, () => true);
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Card, Chip, List, Text} from 'react-native-paper';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
|
|
@ -24,11 +26,12 @@ import i18n from 'i18n-js';
|
|||
import AnimatedAccordion from '../../Animations/AnimatedAccordion';
|
||||
import {isItemInCategoryFilter} from '../../../utils/Search';
|
||||
import type {ClubCategoryType} from '../../../screens/Amicale/Clubs/ClubListScreen';
|
||||
import type {ListIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
categories: Array<ClubCategoryType>;
|
||||
onChipSelect: (id: number) => void;
|
||||
selectedCategories: Array<number>;
|
||||
categories: Array<ClubCategoryType>,
|
||||
onChipSelect: (id: number) => void,
|
||||
selectedCategories: Array<number>,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -51,8 +54,16 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
function ClubListHeader(props: PropsType) {
|
||||
const getChipRender = (category: ClubCategoryType, key: string) => {
|
||||
class ClubListHeader extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||
const {props} = this;
|
||||
return (
|
||||
nextProps.selectedCategories.length !== props.selectedCategories.length
|
||||
);
|
||||
}
|
||||
|
||||
getChipRender = (category: ClubCategoryType, key: string): React.Node => {
|
||||
const {props} = this;
|
||||
const onPress = (): void => props.onChipSelect(category.id);
|
||||
return (
|
||||
<Chip
|
||||
|
|
@ -69,39 +80,32 @@ function ClubListHeader(props: PropsType) {
|
|||
);
|
||||
};
|
||||
|
||||
const getCategoriesRender = () => {
|
||||
const final: Array<React.ReactNode> = [];
|
||||
getCategoriesRender(): React.Node {
|
||||
const {props} = this;
|
||||
const final = [];
|
||||
props.categories.forEach((cat: ClubCategoryType) => {
|
||||
final.push(getChipRender(cat, cat.id.toString()));
|
||||
final.push(this.getChipRender(cat, cat.id.toString()));
|
||||
});
|
||||
return final;
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<AnimatedAccordion
|
||||
title={i18n.t('screens.clubs.categories')}
|
||||
left={(iconProps) => (
|
||||
<List.Icon
|
||||
color={iconProps.color}
|
||||
style={iconProps.style}
|
||||
icon="star"
|
||||
/>
|
||||
)}
|
||||
opened>
|
||||
<Text style={styles.text}>
|
||||
{i18n.t('screens.clubs.categoriesFilterMessage')}
|
||||
</Text>
|
||||
<View style={styles.chipContainer}>{getCategoriesRender()}</View>
|
||||
</AnimatedAccordion>
|
||||
</Card>
|
||||
);
|
||||
render(): React.Node {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<AnimatedAccordion
|
||||
title={i18n.t('screens.clubs.categories')}
|
||||
left={(props: ListIconPropsType): React.Node => (
|
||||
<List.Icon color={props.color} style={props.style} icon="star" />
|
||||
)}
|
||||
opened>
|
||||
<Text style={styles.text}>
|
||||
{i18n.t('screens.clubs.categoriesFilterMessage')}
|
||||
</Text>
|
||||
<View style={styles.chipContainer}>{this.getCategoriesRender()}</View>
|
||||
</AnimatedAccordion>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const areEqual = (prevProps: PropsType, nextProps: PropsType): boolean => {
|
||||
return (
|
||||
prevProps.selectedCategories.length === nextProps.selectedCategories.length
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ClubListHeader, areEqual);
|
||||
export default ClubListHeader;
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Chip, List, withTheme} from 'react-native-paper';
|
||||
import {View} from 'react-native';
|
||||
|
|
@ -24,13 +26,14 @@ import type {
|
|||
ClubCategoryType,
|
||||
ClubType,
|
||||
} from '../../../screens/Amicale/Clubs/ClubListScreen';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
onPress: () => void;
|
||||
categoryTranslator: (id: number) => ClubCategoryType | null;
|
||||
item: ClubType;
|
||||
height: number;
|
||||
theme: ReactNativePaper.Theme;
|
||||
onPress: () => void,
|
||||
categoryTranslator: (id: number) => ClubCategoryType,
|
||||
item: ClubType,
|
||||
height: number,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
class ClubListItem extends React.Component<PropsType> {
|
||||
|
|
@ -45,29 +48,27 @@ class ClubListItem extends React.Component<PropsType> {
|
|||
return false;
|
||||
}
|
||||
|
||||
getCategoriesRender(categories: Array<number | null>) {
|
||||
getCategoriesRender(categories: Array<number | null>): React.Node {
|
||||
const {props} = this;
|
||||
const final: Array<React.ReactNode> = [];
|
||||
const final = [];
|
||||
categories.forEach((cat: number | null) => {
|
||||
if (cat != null) {
|
||||
const category = props.categoryTranslator(cat);
|
||||
if (category) {
|
||||
final.push(
|
||||
<Chip
|
||||
style={{marginRight: 5, marginBottom: 5}}
|
||||
key={`${props.item.id}:${category.id}`}>
|
||||
{category.name}
|
||||
</Chip>,
|
||||
);
|
||||
}
|
||||
const category: ClubCategoryType = props.categoryTranslator(cat);
|
||||
final.push(
|
||||
<Chip
|
||||
style={{marginRight: 5, marginBottom: 5}}
|
||||
key={`${props.item.id}:${category.id}`}>
|
||||
{category.name}
|
||||
</Chip>,
|
||||
);
|
||||
}
|
||||
});
|
||||
return <View style={{flexDirection: 'row'}}>{final}</View>;
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const categoriesRender = () =>
|
||||
const categoriesRender = (): React.Node =>
|
||||
this.getCategoriesRender(props.item.category);
|
||||
const {colors} = props.theme;
|
||||
return (
|
||||
|
|
@ -75,7 +76,7 @@ class ClubListItem extends React.Component<PropsType> {
|
|||
title={props.item.name}
|
||||
description={categoriesRender}
|
||||
onPress={props.onPress}
|
||||
left={() => (
|
||||
left={(): React.Node => (
|
||||
<Avatar.Image
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
|
|
@ -86,7 +87,7 @@ class ClubListItem extends React.Component<PropsType> {
|
|||
source={{uri: props.item.logo}}
|
||||
/>
|
||||
)}
|
||||
right={() => (
|
||||
right={(): React.Node => (
|
||||
<Avatar.Icon
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
108
src/components/Lists/DashboardEdit/DashboardEditAccordion.js
Normal file
108
src/components/Lists/DashboardEdit/DashboardEditAccordion.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import {FlatList, Image, View} from 'react-native';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import DashboardEditItem from './DashboardEditItem';
|
||||
import AnimatedAccordion from '../../Animations/AnimatedAccordion';
|
||||
import type {
|
||||
ServiceCategoryType,
|
||||
ServiceItemType,
|
||||
} from '../../../managers/ServicesManager';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceCategoryType,
|
||||
activeDashboard: Array<string>,
|
||||
onPress: (service: ServiceItemType) => void,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
const LIST_ITEM_HEIGHT = 64;
|
||||
|
||||
class DashboardEditAccordion extends React.Component<PropsType> {
|
||||
getRenderItem = ({item}: {item: ServiceItemType}): React.Node => {
|
||||
const {props} = this;
|
||||
return (
|
||||
<DashboardEditItem
|
||||
height={LIST_ITEM_HEIGHT}
|
||||
item={item}
|
||||
isActive={props.activeDashboard.includes(item.key)}
|
||||
onPress={() => {
|
||||
props.onPress(item);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
getItemLayout = (
|
||||
data: ?Array<ServiceItemType>,
|
||||
index: number,
|
||||
): {length: number, offset: number, index: number} => ({
|
||||
length: LIST_ITEM_HEIGHT,
|
||||
offset: LIST_ITEM_HEIGHT * index,
|
||||
index,
|
||||
});
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {item} = props;
|
||||
return (
|
||||
<View>
|
||||
<AnimatedAccordion
|
||||
title={item.title}
|
||||
left={(): React.Node =>
|
||||
typeof item.image === 'number' ? (
|
||||
<Image
|
||||
source={item.image}
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MaterialCommunityIcons
|
||||
// $FlowFixMe
|
||||
name={item.image}
|
||||
color={props.theme.colors.primary}
|
||||
size={40}
|
||||
/>
|
||||
)
|
||||
}>
|
||||
{/* $FlowFixMe */}
|
||||
<FlatList
|
||||
data={item.content}
|
||||
extraData={props.activeDashboard.toString()}
|
||||
renderItem={this.getRenderItem}
|
||||
listKey={item.key}
|
||||
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
|
||||
getItemLayout={this.getItemLayout}
|
||||
removeClippedSubviews
|
||||
/>
|
||||
</AnimatedAccordion>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(DashboardEditAccordion);
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {useTheme} from 'react-native-paper';
|
||||
import {FlatList, Image, View} from 'react-native';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import DashboardEditItem from './DashboardEditItem';
|
||||
import AnimatedAccordion from '../../Animations/AnimatedAccordion';
|
||||
import type {
|
||||
ServiceCategoryType,
|
||||
ServiceItemType,
|
||||
} from '../../../managers/ServicesManager';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceCategoryType;
|
||||
activeDashboard: Array<string>;
|
||||
onPress: (service: ServiceItemType) => void;
|
||||
};
|
||||
|
||||
const LIST_ITEM_HEIGHT = 64;
|
||||
|
||||
function DashboardEditAccordion(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
|
||||
const getRenderItem = ({item}: {item: ServiceItemType}) => {
|
||||
return (
|
||||
<DashboardEditItem
|
||||
height={LIST_ITEM_HEIGHT}
|
||||
item={item}
|
||||
isActive={props.activeDashboard.includes(item.key)}
|
||||
onPress={() => {
|
||||
props.onPress(item);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getItemLayout = (
|
||||
data: Array<ServiceItemType> | null | undefined,
|
||||
index: number,
|
||||
): {length: number; offset: number; index: number} => ({
|
||||
length: LIST_ITEM_HEIGHT,
|
||||
offset: LIST_ITEM_HEIGHT * index,
|
||||
index,
|
||||
});
|
||||
|
||||
const {item} = props;
|
||||
return (
|
||||
<View>
|
||||
<AnimatedAccordion
|
||||
title={item.title}
|
||||
left={() =>
|
||||
typeof item.image === 'number' ? (
|
||||
<Image
|
||||
source={item.image}
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MaterialCommunityIcons
|
||||
name={item.image}
|
||||
color={theme.colors.primary}
|
||||
size={40}
|
||||
/>
|
||||
)
|
||||
}>
|
||||
<FlatList
|
||||
data={item.content}
|
||||
extraData={props.activeDashboard.toString()}
|
||||
renderItem={getRenderItem}
|
||||
listKey={item.key}
|
||||
// Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
|
||||
getItemLayout={getItemLayout}
|
||||
removeClippedSubviews
|
||||
/>
|
||||
</AnimatedAccordion>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardEditAccordion;
|
||||
81
src/components/Lists/DashboardEdit/DashboardEditItem.js
Normal file
81
src/components/Lists/DashboardEdit/DashboardEditItem.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Image} from 'react-native';
|
||||
import {List, withTheme} from 'react-native-paper';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
import type {ServiceItemType} from '../../../managers/ServicesManager';
|
||||
import type {ListIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceItemType,
|
||||
isActive: boolean,
|
||||
height: number,
|
||||
onPress: () => void,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
class DashboardEditItem extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||
const {isActive} = this.props;
|
||||
return nextProps.isActive !== isActive;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {item, onPress, height, isActive, theme} = this.props;
|
||||
return (
|
||||
<List.Item
|
||||
title={item.title}
|
||||
description={item.subtitle}
|
||||
onPress={isActive ? null : onPress}
|
||||
left={(): React.Node => (
|
||||
<Image
|
||||
source={{uri: item.image}}
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
right={(props: ListIconPropsType): React.Node =>
|
||||
isActive ? (
|
||||
<List.Icon
|
||||
style={props.style}
|
||||
icon="check"
|
||||
color={theme.colors.success}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
style={{
|
||||
height,
|
||||
justifyContent: 'center',
|
||||
paddingLeft: 30,
|
||||
backgroundColor: isActive
|
||||
? theme.colors.proxiwashFinishedColor
|
||||
: 'transparent',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(DashboardEditItem);
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Image} from 'react-native';
|
||||
import {List, useTheme} from 'react-native-paper';
|
||||
import type {ServiceItemType} from '../../../managers/ServicesManager';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceItemType;
|
||||
isActive: boolean;
|
||||
height: number;
|
||||
onPress: () => void;
|
||||
};
|
||||
|
||||
function DashboardEditItem(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
const {item, onPress, height, isActive} = props;
|
||||
return (
|
||||
<List.Item
|
||||
title={item.title}
|
||||
description={item.subtitle}
|
||||
onPress={isActive ? undefined : onPress}
|
||||
left={() => (
|
||||
<Image
|
||||
source={
|
||||
typeof item.image === 'string' ? {uri: item.image} : item.image
|
||||
}
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
right={(iconProps) =>
|
||||
isActive ? (
|
||||
<List.Icon
|
||||
style={iconProps.style}
|
||||
icon="check"
|
||||
color={theme.colors.success}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
style={{
|
||||
height,
|
||||
justifyContent: 'center',
|
||||
paddingLeft: 30,
|
||||
backgroundColor: isActive
|
||||
? theme.colors.proxiwashFinishedColor
|
||||
: 'transparent',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const areEqual = (prevProps: PropsType, nextProps: PropsType): boolean => {
|
||||
return nextProps.isActive === prevProps.isActive;
|
||||
};
|
||||
|
||||
export default React.memo(DashboardEditItem, areEqual);
|
||||
|
|
@ -17,54 +17,61 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {TouchableRipple, useTheme} from 'react-native-paper';
|
||||
import {TouchableRipple, withTheme} from 'react-native-paper';
|
||||
import {Dimensions, Image, View} from 'react-native';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
image?: string | number;
|
||||
isActive: boolean;
|
||||
onPress: () => void;
|
||||
image: string,
|
||||
isActive: boolean,
|
||||
onPress: () => void,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
/**
|
||||
* Component used to render a small dashboard item
|
||||
*/
|
||||
function DashboardEditPreviewItem(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
const itemSize = Dimensions.get('window').width / 8;
|
||||
class DashboardEditPreviewItem extends React.Component<PropsType> {
|
||||
itemSize: number;
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
onPress={props.onPress}
|
||||
borderless
|
||||
style={{
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
backgroundColor: props.isActive
|
||||
? theme.colors.textDisabled
|
||||
: 'transparent',
|
||||
borderRadius: 5,
|
||||
}}>
|
||||
<View
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.itemSize = Dimensions.get('window').width / 8;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<TouchableRipple
|
||||
onPress={props.onPress}
|
||||
borderless
|
||||
style={{
|
||||
width: itemSize,
|
||||
height: itemSize,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
backgroundColor: props.isActive
|
||||
? props.theme.colors.textDisabled
|
||||
: 'transparent',
|
||||
borderRadius: 5,
|
||||
}}>
|
||||
{props.image ? (
|
||||
<View
|
||||
style={{
|
||||
width: this.itemSize,
|
||||
height: this.itemSize,
|
||||
}}>
|
||||
<Image
|
||||
source={
|
||||
typeof props.image === 'string' ? {uri: props.image} : props.image
|
||||
}
|
||||
source={{uri: props.image}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DashboardEditPreviewItem;
|
||||
export default withTheme(DashboardEditPreviewItem);
|
||||
132
src/components/Lists/Equipment/EquipmentListItem.js
Normal file
132
src/components/Lists/Equipment/EquipmentListItem.js
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, List, withTheme} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
import type {DeviceType} from '../../../screens/Amicale/Equipment/EquipmentListScreen';
|
||||
import {
|
||||
getFirstEquipmentAvailability,
|
||||
getRelativeDateString,
|
||||
isEquipmentAvailable,
|
||||
} from '../../../utils/EquipmentBooking';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
userDeviceRentDates: [string, string],
|
||||
item: DeviceType,
|
||||
height: number,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
class EquipmentListItem extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||
const {userDeviceRentDates} = this.props;
|
||||
return nextProps.userDeviceRentDates !== userDeviceRentDates;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {item, userDeviceRentDates, navigation, height, theme} = this.props;
|
||||
const isRented = userDeviceRentDates != null;
|
||||
const isAvailable = isEquipmentAvailable(item);
|
||||
const firstAvailability = getFirstEquipmentAvailability(item);
|
||||
|
||||
let onPress;
|
||||
if (isRented)
|
||||
onPress = () => {
|
||||
navigation.navigate('equipment-confirm', {
|
||||
item,
|
||||
dates: userDeviceRentDates,
|
||||
});
|
||||
};
|
||||
else
|
||||
onPress = () => {
|
||||
navigation.navigate('equipment-rent', {item});
|
||||
};
|
||||
|
||||
let description;
|
||||
if (isRented) {
|
||||
const start = new Date(userDeviceRentDates[0]);
|
||||
const end = new Date(userDeviceRentDates[1]);
|
||||
if (start.getTime() !== end.getTime())
|
||||
description = i18n.t('screens.equipment.bookingPeriod', {
|
||||
begin: getRelativeDateString(start),
|
||||
end: getRelativeDateString(end),
|
||||
});
|
||||
else
|
||||
description = i18n.t('screens.equipment.bookingDay', {
|
||||
date: getRelativeDateString(start),
|
||||
});
|
||||
} else if (isAvailable)
|
||||
description = i18n.t('screens.equipment.bail', {cost: item.caution});
|
||||
else
|
||||
description = i18n.t('screens.equipment.available', {
|
||||
date: getRelativeDateString(firstAvailability),
|
||||
});
|
||||
|
||||
let icon;
|
||||
if (isRented) icon = 'bookmark-check';
|
||||
else if (isAvailable) icon = 'check-circle-outline';
|
||||
else icon = 'update';
|
||||
|
||||
let color;
|
||||
if (isRented) color = theme.colors.warning;
|
||||
else if (isAvailable) color = theme.colors.success;
|
||||
else color = theme.colors.primary;
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
title={item.name}
|
||||
description={description}
|
||||
onPress={onPress}
|
||||
left={({size}: {size: number}): React.Node => (
|
||||
<Avatar.Icon
|
||||
size={size}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
icon={icon}
|
||||
color={color}
|
||||
/>
|
||||
)}
|
||||
right={(): React.Node => (
|
||||
<Avatar.Icon
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
size={48}
|
||||
icon="chevron-right"
|
||||
/>
|
||||
)}
|
||||
style={{
|
||||
height,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(EquipmentListItem);
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, List, useTheme} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import type {DeviceType} from '../../../screens/Amicale/Equipment/EquipmentListScreen';
|
||||
import {
|
||||
getFirstEquipmentAvailability,
|
||||
getRelativeDateString,
|
||||
isEquipmentAvailable,
|
||||
} from '../../../utils/EquipmentBooking';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
userDeviceRentDates: [string, string] | null;
|
||||
item: DeviceType;
|
||||
height: number;
|
||||
};
|
||||
|
||||
function EquipmentListItem(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
const {item, userDeviceRentDates, navigation, height} = props;
|
||||
const isRented = userDeviceRentDates != null;
|
||||
const isAvailable = isEquipmentAvailable(item);
|
||||
const firstAvailability = getFirstEquipmentAvailability(item);
|
||||
|
||||
let onPress;
|
||||
if (isRented) {
|
||||
onPress = () => {
|
||||
navigation.navigate('equipment-confirm', {
|
||||
item,
|
||||
dates: userDeviceRentDates,
|
||||
});
|
||||
};
|
||||
} else {
|
||||
onPress = () => {
|
||||
navigation.navigate('equipment-rent', {item});
|
||||
};
|
||||
}
|
||||
|
||||
let description;
|
||||
if (isRented && userDeviceRentDates) {
|
||||
const start = new Date(userDeviceRentDates[0]);
|
||||
const end = new Date(userDeviceRentDates[1]);
|
||||
if (start.getTime() !== end.getTime()) {
|
||||
description = i18n.t('screens.equipment.bookingPeriod', {
|
||||
begin: getRelativeDateString(start),
|
||||
end: getRelativeDateString(end),
|
||||
});
|
||||
} else {
|
||||
description = i18n.t('screens.equipment.bookingDay', {
|
||||
date: getRelativeDateString(start),
|
||||
});
|
||||
}
|
||||
} else if (isAvailable) {
|
||||
description = i18n.t('screens.equipment.bail', {cost: item.caution});
|
||||
} else {
|
||||
description = i18n.t('screens.equipment.available', {
|
||||
date: getRelativeDateString(firstAvailability),
|
||||
});
|
||||
}
|
||||
|
||||
let icon: string;
|
||||
if (isRented) {
|
||||
icon = 'bookmark-check';
|
||||
} else if (isAvailable) {
|
||||
icon = 'check-circle-outline';
|
||||
} else {
|
||||
icon = 'update';
|
||||
}
|
||||
|
||||
let color: string;
|
||||
if (isRented) {
|
||||
color = theme.colors.warning;
|
||||
} else if (isAvailable) {
|
||||
color = theme.colors.success;
|
||||
} else {
|
||||
color = theme.colors.primary;
|
||||
}
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
title={item.name}
|
||||
description={description}
|
||||
onPress={onPress}
|
||||
left={() => (
|
||||
<Avatar.Icon
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
icon={icon}
|
||||
color={color}
|
||||
/>
|
||||
)}
|
||||
right={() => (
|
||||
<Avatar.Icon
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
size={48}
|
||||
icon="chevron-right"
|
||||
/>
|
||||
)}
|
||||
style={{
|
||||
height,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const areEqual = (prevProps: PropsType, nextProps: PropsType): boolean => {
|
||||
return nextProps.userDeviceRentDates === prevProps.userDeviceRentDates;
|
||||
};
|
||||
|
||||
export default React.memo(EquipmentListItem, areEqual);
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {List, withTheme} from 'react-native-paper';
|
||||
import {FlatList, View} from 'react-native';
|
||||
|
|
@ -27,14 +29,17 @@ import type {
|
|||
PlanexGroupType,
|
||||
PlanexGroupCategoryType,
|
||||
} from '../../../screens/Planex/GroupSelectionScreen';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
import type {ListIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
item: PlanexGroupCategoryType;
|
||||
favorites: Array<PlanexGroupType>;
|
||||
onGroupPress: (data: PlanexGroupType) => void;
|
||||
onFavoritePress: (data: PlanexGroupType) => void;
|
||||
currentSearchString: string;
|
||||
theme: ReactNativePaper.Theme;
|
||||
item: PlanexGroupCategoryType,
|
||||
favorites: Array<PlanexGroupType>,
|
||||
onGroupPress: (PlanexGroupType) => void,
|
||||
onFavoritePress: (PlanexGroupType) => void,
|
||||
currentSearchString: string,
|
||||
height: number,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
const LIST_ITEM_HEIGHT = 64;
|
||||
|
|
@ -50,7 +55,7 @@ class GroupListAccordion extends React.Component<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
getRenderItem = ({item}: {item: PlanexGroupType}) => {
|
||||
getRenderItem = ({item}: {item: PlanexGroupType}): React.Node => {
|
||||
const {props} = this;
|
||||
const onPress = () => {
|
||||
props.onGroupPress(item);
|
||||
|
|
@ -72,19 +77,18 @@ class GroupListAccordion extends React.Component<PropsType> {
|
|||
getData(): Array<PlanexGroupType> {
|
||||
const {props} = this;
|
||||
const originalData = props.item.content;
|
||||
const displayData: Array<PlanexGroupType> = [];
|
||||
const displayData = [];
|
||||
originalData.forEach((data: PlanexGroupType) => {
|
||||
if (stringMatchQuery(data.name, props.currentSearchString)) {
|
||||
if (stringMatchQuery(data.name, props.currentSearchString))
|
||||
displayData.push(data);
|
||||
}
|
||||
});
|
||||
return displayData;
|
||||
}
|
||||
|
||||
itemLayout = (
|
||||
data: Array<PlanexGroupType> | null | undefined,
|
||||
data: ?Array<PlanexGroupType>,
|
||||
index: number,
|
||||
): {length: number; offset: number; index: number} => ({
|
||||
): {length: number, offset: number, index: number} => ({
|
||||
length: LIST_ITEM_HEIGHT,
|
||||
offset: LIST_ITEM_HEIGHT * index,
|
||||
index,
|
||||
|
|
@ -92,7 +96,7 @@ class GroupListAccordion extends React.Component<PropsType> {
|
|||
|
||||
keyExtractor = (item: PlanexGroupType): string => item.id.toString();
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {item} = this.props;
|
||||
return (
|
||||
|
|
@ -100,9 +104,10 @@ class GroupListAccordion extends React.Component<PropsType> {
|
|||
<AnimatedAccordion
|
||||
title={item.name.replace(REPLACE_REGEX, ' ')}
|
||||
style={{
|
||||
height: props.height,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
left={(iconProps) =>
|
||||
left={(iconProps: ListIconPropsType): React.Node =>
|
||||
item.id === 0 ? (
|
||||
<List.Icon
|
||||
style={iconProps.style}
|
||||
|
|
@ -17,20 +17,23 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {List, TouchableRipple, withTheme} from 'react-native-paper';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
import type {PlanexGroupType} from '../../../screens/Planex/GroupSelectionScreen';
|
||||
import {View} from 'react-native';
|
||||
import type {ListIconPropsType} from '../../../constants/PaperStyles';
|
||||
|
||||
type PropsType = {
|
||||
theme: ReactNativePaper.Theme;
|
||||
onPress: () => void;
|
||||
onStarPress: () => void;
|
||||
item: PlanexGroupType;
|
||||
favorites: Array<PlanexGroupType>;
|
||||
height: number;
|
||||
theme: CustomThemeType,
|
||||
onPress: () => void,
|
||||
onStarPress: () => void,
|
||||
item: PlanexGroupType,
|
||||
favorites: Array<PlanexGroupType>,
|
||||
height: number,
|
||||
};
|
||||
|
||||
const REPLACE_REGEX = /_/g;
|
||||
|
|
@ -38,11 +41,10 @@ const REPLACE_REGEX = /_/g;
|
|||
class GroupListItem extends React.Component<PropsType> {
|
||||
isFav: boolean;
|
||||
|
||||
starRef: {current: null | (Animatable.View & View)};
|
||||
starRef: null | Animatable.View;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.starRef = React.createRef();
|
||||
this.isFav = this.isGroupInFavorites(props.favorites);
|
||||
}
|
||||
|
||||
|
|
@ -50,9 +52,7 @@ class GroupListItem extends React.Component<PropsType> {
|
|||
const {favorites} = this.props;
|
||||
const favChanged = favorites.length !== nextProps.favorites.length;
|
||||
let newFavState = this.isFav;
|
||||
if (favChanged) {
|
||||
newFavState = this.isGroupInFavorites(nextProps.favorites);
|
||||
}
|
||||
if (favChanged) newFavState = this.isGroupInFavorites(nextProps.favorites);
|
||||
const shouldUpdate = this.isFav !== newFavState;
|
||||
this.isFav = newFavState;
|
||||
return shouldUpdate;
|
||||
|
|
@ -61,12 +61,9 @@ class GroupListItem extends React.Component<PropsType> {
|
|||
onStarPress = () => {
|
||||
const {props} = this;
|
||||
const ref = this.starRef;
|
||||
if (ref.current && ref.current.rubberBand && ref.current.swing) {
|
||||
if (this.isFav) {
|
||||
ref.current.rubberBand();
|
||||
} else {
|
||||
ref.current.swing();
|
||||
}
|
||||
if (ref != null) {
|
||||
if (this.isFav) ref.rubberBand();
|
||||
else ref.swing();
|
||||
}
|
||||
props.onStarPress();
|
||||
};
|
||||
|
|
@ -74,29 +71,31 @@ class GroupListItem extends React.Component<PropsType> {
|
|||
isGroupInFavorites(favorites: Array<PlanexGroupType>): boolean {
|
||||
const {item} = this.props;
|
||||
for (let i = 0; i < favorites.length; i += 1) {
|
||||
if (favorites[i].id === item.id) {
|
||||
return true;
|
||||
}
|
||||
if (favorites[i].id === item.id) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {colors} = props.theme;
|
||||
return (
|
||||
<List.Item
|
||||
title={props.item.name.replace(REPLACE_REGEX, ' ')}
|
||||
onPress={props.onPress}
|
||||
left={(iconProps) => (
|
||||
left={(iconProps: ListIconPropsType): React.Node => (
|
||||
<List.Icon
|
||||
color={iconProps.color}
|
||||
style={iconProps.style}
|
||||
icon="chevron-right"
|
||||
/>
|
||||
)}
|
||||
right={(iconProps) => (
|
||||
<Animatable.View ref={this.starRef} useNativeDriver>
|
||||
right={(iconProps: ListIconPropsType): React.Node => (
|
||||
<Animatable.View
|
||||
ref={(ref: Animatable.View) => {
|
||||
this.starRef = ref;
|
||||
}}
|
||||
useNativeDriver>
|
||||
<TouchableRipple
|
||||
onPress={this.onStarPress}
|
||||
style={{
|
||||
68
src/components/Lists/Proximo/ProximoListItem.js
Normal file
68
src/components/Lists/Proximo/ProximoListItem.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, List, Text, withTheme} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
import type {ProximoArticleType} from '../../../screens/Services/Proximo/ProximoMainScreen';
|
||||
|
||||
type PropsType = {
|
||||
onPress: () => void,
|
||||
color: string,
|
||||
item: ProximoArticleType,
|
||||
height: number,
|
||||
};
|
||||
|
||||
class ProximoListItem extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<List.Item
|
||||
title={props.item.name}
|
||||
description={`${props.item.quantity} ${i18n.t(
|
||||
'screens.proximo.inStock',
|
||||
)}`}
|
||||
descriptionStyle={{color: props.color}}
|
||||
onPress={props.onPress}
|
||||
left={(): React.Node => (
|
||||
<Avatar.Image
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
size={64}
|
||||
source={{uri: props.item.image}}
|
||||
/>
|
||||
)}
|
||||
right={(): React.Node => (
|
||||
<Text style={{fontWeight: 'bold'}}>{props.item.price}€</Text>
|
||||
)}
|
||||
style={{
|
||||
height: props.height,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(ProximoListItem);
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, List, Text} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
import type {ProximoArticleType} from '../../../screens/Services/Proximo/ProximoMainScreen';
|
||||
|
||||
type PropsType = {
|
||||
onPress: () => void;
|
||||
color: string;
|
||||
item: ProximoArticleType;
|
||||
height: number;
|
||||
};
|
||||
|
||||
function ProximoListItem(props: PropsType) {
|
||||
return (
|
||||
<List.Item
|
||||
title={props.item.name}
|
||||
description={`${props.item.quantity} ${i18n.t(
|
||||
'screens.proximo.inStock',
|
||||
)}`}
|
||||
descriptionStyle={{color: props.color}}
|
||||
onPress={props.onPress}
|
||||
left={() => (
|
||||
<Avatar.Image
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
size={64}
|
||||
source={{uri: props.item.image}}
|
||||
/>
|
||||
)}
|
||||
right={() => (
|
||||
<Text style={{fontWeight: 'bold'}}>{props.item.price}€</Text>
|
||||
)}
|
||||
style={{
|
||||
height: props.height,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ProximoListItem, () => true);
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
|
|
@ -30,23 +32,22 @@ import {
|
|||
import {StyleSheet, View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import ProxiwashConstants, {
|
||||
MachineStates,
|
||||
} from '../../../constants/ProxiwashConstants';
|
||||
import ProxiwashConstants from '../../../constants/ProxiwashConstants';
|
||||
import AprilFoolsManager from '../../../managers/AprilFoolsManager';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
import type {ProxiwashMachineType} from '../../../screens/Proxiwash/ProxiwashScreen';
|
||||
|
||||
type PropsType = {
|
||||
item: ProxiwashMachineType;
|
||||
theme: ReactNativePaper.Theme;
|
||||
item: ProxiwashMachineType,
|
||||
theme: CustomThemeType,
|
||||
onPress: (
|
||||
title: string,
|
||||
item: ProxiwashMachineType,
|
||||
isDryer: boolean,
|
||||
) => void;
|
||||
isWatched: boolean;
|
||||
isDryer: boolean;
|
||||
height: number;
|
||||
) => void,
|
||||
isWatched: boolean,
|
||||
isDryer: boolean,
|
||||
height: number,
|
||||
};
|
||||
|
||||
const AnimatedIcon = Animatable.createAnimatableComponent(Avatar.Icon);
|
||||
|
|
@ -71,20 +72,10 @@ const styles = StyleSheet.create({
|
|||
* Component used to display a proxiwash item, showing machine progression and state
|
||||
*/
|
||||
class ProxiwashListItem extends React.Component<PropsType> {
|
||||
stateStrings: {[key in MachineStates]: string} = {
|
||||
[MachineStates.AVAILABLE]: i18n.t('screens.proxiwash.states.ready'),
|
||||
[MachineStates.RUNNING]: i18n.t('screens.proxiwash.states.running'),
|
||||
[MachineStates.RUNNING_NOT_STARTED]: i18n.t(
|
||||
'screens.proxiwash.states.runningNotStarted',
|
||||
),
|
||||
[MachineStates.FINISHED]: i18n.t('screens.proxiwash.states.finished'),
|
||||
[MachineStates.UNAVAILABLE]: i18n.t('screens.proxiwash.states.broken'),
|
||||
[MachineStates.ERROR]: i18n.t('screens.proxiwash.states.error'),
|
||||
[MachineStates.UNKNOWN]: i18n.t('screens.proxiwash.states.unknown'),
|
||||
};
|
||||
|
||||
stateColors: {[key: string]: string};
|
||||
|
||||
stateStrings: {[key: string]: string};
|
||||
|
||||
title: string;
|
||||
|
||||
titlePopUp: string;
|
||||
|
|
@ -92,14 +83,16 @@ class ProxiwashListItem extends React.Component<PropsType> {
|
|||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.stateColors = {};
|
||||
this.stateStrings = {};
|
||||
|
||||
this.updateStateStrings();
|
||||
|
||||
let displayNumber = props.item.number;
|
||||
const displayMaxWeight = props.item.maxWeight;
|
||||
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
|
||||
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled())
|
||||
displayNumber = AprilFoolsManager.getProxiwashMachineDisplayNumber(
|
||||
parseInt(props.item.number, 10),
|
||||
);
|
||||
}
|
||||
|
||||
this.title = props.isDryer
|
||||
? i18n.t('screens.proxiwash.dryer')
|
||||
|
|
@ -123,38 +116,65 @@ class ProxiwashListItem extends React.Component<PropsType> {
|
|||
props.onPress(this.titlePopUp, props.item, props.isDryer);
|
||||
};
|
||||
|
||||
updateStateStrings() {
|
||||
this.stateStrings[ProxiwashConstants.machineStates.AVAILABLE] = i18n.t(
|
||||
'screens.proxiwash.states.ready',
|
||||
);
|
||||
this.stateStrings[ProxiwashConstants.machineStates.RUNNING] = i18n.t(
|
||||
'screens.proxiwash.states.running',
|
||||
);
|
||||
this.stateStrings[
|
||||
ProxiwashConstants.machineStates.RUNNING_NOT_STARTED
|
||||
] = i18n.t('screens.proxiwash.states.runningNotStarted');
|
||||
this.stateStrings[ProxiwashConstants.machineStates.FINISHED] = i18n.t(
|
||||
'screens.proxiwash.states.finished',
|
||||
);
|
||||
this.stateStrings[ProxiwashConstants.machineStates.UNAVAILABLE] = i18n.t(
|
||||
'screens.proxiwash.states.broken',
|
||||
);
|
||||
this.stateStrings[ProxiwashConstants.machineStates.ERROR] = i18n.t(
|
||||
'screens.proxiwash.states.error',
|
||||
);
|
||||
this.stateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t(
|
||||
'screens.proxiwash.states.unknown',
|
||||
);
|
||||
}
|
||||
|
||||
updateStateColors() {
|
||||
const {props} = this;
|
||||
const {colors} = props.theme;
|
||||
this.stateColors[MachineStates.AVAILABLE] = colors.proxiwashReadyColor;
|
||||
this.stateColors[MachineStates.RUNNING] = colors.proxiwashRunningColor;
|
||||
this.stateColors[MachineStates.RUNNING_NOT_STARTED] =
|
||||
this.stateColors[ProxiwashConstants.machineStates.AVAILABLE] =
|
||||
colors.proxiwashReadyColor;
|
||||
this.stateColors[ProxiwashConstants.machineStates.RUNNING] =
|
||||
colors.proxiwashRunningColor;
|
||||
this.stateColors[ProxiwashConstants.machineStates.RUNNING_NOT_STARTED] =
|
||||
colors.proxiwashRunningNotStartedColor;
|
||||
this.stateColors[MachineStates.FINISHED] = colors.proxiwashFinishedColor;
|
||||
this.stateColors[MachineStates.UNAVAILABLE] = colors.proxiwashBrokenColor;
|
||||
this.stateColors[MachineStates.ERROR] = colors.proxiwashErrorColor;
|
||||
this.stateColors[MachineStates.UNKNOWN] = colors.proxiwashUnknownColor;
|
||||
this.stateColors[ProxiwashConstants.machineStates.FINISHED] =
|
||||
colors.proxiwashFinishedColor;
|
||||
this.stateColors[ProxiwashConstants.machineStates.UNAVAILABLE] =
|
||||
colors.proxiwashBrokenColor;
|
||||
this.stateColors[ProxiwashConstants.machineStates.ERROR] =
|
||||
colors.proxiwashErrorColor;
|
||||
this.stateColors[ProxiwashConstants.machineStates.UNKNOWN] =
|
||||
colors.proxiwashUnknownColor;
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {colors} = props.theme;
|
||||
const machineState = props.item.state;
|
||||
const isRunning = machineState === MachineStates.RUNNING;
|
||||
const isReady = machineState === MachineStates.AVAILABLE;
|
||||
const isRunning = machineState === ProxiwashConstants.machineStates.RUNNING;
|
||||
const isReady = machineState === ProxiwashConstants.machineStates.AVAILABLE;
|
||||
const description = isRunning
|
||||
? `${props.item.startTime}/${props.item.endTime}`
|
||||
: '';
|
||||
const stateIcon = ProxiwashConstants.stateIcons[machineState];
|
||||
const stateString = this.stateStrings[machineState];
|
||||
let progress;
|
||||
if (isRunning && props.item.donePercent !== '') {
|
||||
if (isRunning && props.item.donePercent !== '')
|
||||
progress = parseFloat(props.item.donePercent) / 100;
|
||||
} else if (isRunning) {
|
||||
progress = 0;
|
||||
} else {
|
||||
progress = 1;
|
||||
}
|
||||
else if (isRunning) progress = 0;
|
||||
else progress = 1;
|
||||
|
||||
const icon = props.isWatched ? (
|
||||
<AnimatedIcon
|
||||
|
|
@ -204,19 +224,19 @@ class ProxiwashListItem extends React.Component<PropsType> {
|
|||
justifyContent: 'center',
|
||||
}}
|
||||
onPress={this.onListItemPress}
|
||||
left={() => icon}
|
||||
right={() => (
|
||||
left={(): React.Node => icon}
|
||||
right={(): React.Node => (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<View style={{justifyContent: 'center'}}>
|
||||
<Text
|
||||
style={
|
||||
machineState === MachineStates.FINISHED
|
||||
machineState === ProxiwashConstants.machineStates.FINISHED
|
||||
? {fontWeight: 'bold'}
|
||||
: {}
|
||||
}>
|
||||
{stateString}
|
||||
</Text>
|
||||
{machineState === MachineStates.RUNNING ? (
|
||||
{machineState === ProxiwashConstants.machineStates.RUNNING ? (
|
||||
<Caption>{props.item.remainingTime} min</Caption>
|
||||
) : null}
|
||||
</View>
|
||||
|
|
@ -17,16 +17,19 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Avatar, Text, withTheme} from 'react-native-paper';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
theme: ReactNativePaper.Theme;
|
||||
title: string;
|
||||
isDryer: boolean;
|
||||
nbAvailable: number;
|
||||
theme: CustomThemeType,
|
||||
title: string,
|
||||
isDryer: boolean,
|
||||
nbAvailable: number,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -58,7 +61,7 @@ class ProxiwashListItem extends React.Component<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const subtitle = `${props.nbAvailable} ${
|
||||
props.nbAvailable <= 1
|
||||
|
|
@ -17,27 +17,27 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import {Image, TouchableWithoutFeedback, View, ViewStyle} from 'react-native';
|
||||
import {AnimatableProperties} from 'react-native-animatable';
|
||||
import {Image, TouchableWithoutFeedback, View} from 'react-native';
|
||||
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
|
||||
export type AnimatableViewRefType = {
|
||||
current: null | (typeof Animatable.View & View);
|
||||
};
|
||||
export type AnimatableViewRefType = {current: null | Animatable.View};
|
||||
|
||||
type PropsType = {
|
||||
emotion?: MASCOT_STYLE;
|
||||
animated?: boolean;
|
||||
style?: ViewStyle;
|
||||
entryAnimation?: AnimatableProperties<ViewStyle>;
|
||||
loopAnimation?: AnimatableProperties<ViewStyle>;
|
||||
onPress?: null | ((viewRef: AnimatableViewRefType) => void);
|
||||
onLongPress?: null | ((viewRef: AnimatableViewRefType) => void);
|
||||
emotion?: number,
|
||||
animated?: boolean,
|
||||
style?: ViewStyle | null,
|
||||
entryAnimation?: Animatable.AnimatableProperties | null,
|
||||
loopAnimation?: Animatable.AnimatableProperties | null,
|
||||
onPress?: null | ((viewRef: AnimatableViewRefType) => void),
|
||||
onLongPress?: null | ((viewRef: AnimatableViewRefType) => void),
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
currentEmotion: MASCOT_STYLE;
|
||||
currentEmotion: number,
|
||||
};
|
||||
|
||||
const MASCOT_IMAGE = require('../../../assets/mascot/mascot.png');
|
||||
|
|
@ -50,32 +50,32 @@ const MASCOT_EYES_ANGRY = require('../../../assets/mascot/mascot_eyes_angry.png'
|
|||
const MASCOT_GLASSES = require('../../../assets/mascot/mascot_glasses.png');
|
||||
const MASCOT_SUNGLASSES = require('../../../assets/mascot/mascot_sunglasses.png');
|
||||
|
||||
enum EYE_STYLE {
|
||||
NORMAL,
|
||||
GIRLY,
|
||||
CUTE,
|
||||
WINK,
|
||||
HEART,
|
||||
ANGRY,
|
||||
}
|
||||
export const EYE_STYLE = {
|
||||
NORMAL: 0,
|
||||
GIRLY: 2,
|
||||
CUTE: 3,
|
||||
WINK: 4,
|
||||
HEART: 5,
|
||||
ANGRY: 6,
|
||||
};
|
||||
|
||||
enum GLASSES_STYLE {
|
||||
NORMAL,
|
||||
COOl,
|
||||
}
|
||||
const GLASSES_STYLE = {
|
||||
NORMAL: 0,
|
||||
COOl: 1,
|
||||
};
|
||||
|
||||
export enum MASCOT_STYLE {
|
||||
NORMAL,
|
||||
HAPPY,
|
||||
GIRLY,
|
||||
WINK,
|
||||
CUTE,
|
||||
INTELLO,
|
||||
LOVE,
|
||||
COOL,
|
||||
ANGRY,
|
||||
RANDOM = 999,
|
||||
}
|
||||
export const MASCOT_STYLE = {
|
||||
NORMAL: 0,
|
||||
HAPPY: 1,
|
||||
GIRLY: 2,
|
||||
WINK: 3,
|
||||
CUTE: 4,
|
||||
INTELLO: 5,
|
||||
LOVE: 6,
|
||||
COOL: 7,
|
||||
ANGRY: 8,
|
||||
RANDOM: 999,
|
||||
};
|
||||
|
||||
class Mascot extends React.Component<PropsType, StateType> {
|
||||
static defaultProps = {
|
||||
|
|
@ -100,9 +100,9 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
|
||||
viewRef: AnimatableViewRefType;
|
||||
|
||||
eyeList: {[key in EYE_STYLE]: number};
|
||||
eyeList: {[key: number]: number | string};
|
||||
|
||||
glassesList: {[key in GLASSES_STYLE]: number};
|
||||
glassesList: {[key: number]: number | string};
|
||||
|
||||
onPress: (viewRef: AnimatableViewRefType) => void;
|
||||
|
||||
|
|
@ -113,25 +113,23 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.viewRef = React.createRef();
|
||||
this.eyeList = {
|
||||
[EYE_STYLE.NORMAL]: MASCOT_EYES_NORMAL,
|
||||
[EYE_STYLE.GIRLY]: MASCOT_EYES_GIRLY,
|
||||
[EYE_STYLE.CUTE]: MASCOT_EYES_CUTE,
|
||||
[EYE_STYLE.WINK]: MASCOT_EYES_WINK,
|
||||
[EYE_STYLE.HEART]: MASCOT_EYES_HEART,
|
||||
[EYE_STYLE.ANGRY]: MASCOT_EYES_ANGRY,
|
||||
};
|
||||
this.glassesList = {
|
||||
[GLASSES_STYLE.NORMAL]: MASCOT_GLASSES,
|
||||
[GLASSES_STYLE.COOl]: MASCOT_SUNGLASSES,
|
||||
};
|
||||
this.initialEmotion = props.emotion
|
||||
? props.emotion
|
||||
: Mascot.defaultProps.emotion;
|
||||
this.eyeList = {};
|
||||
this.glassesList = {};
|
||||
this.eyeList[EYE_STYLE.NORMAL] = MASCOT_EYES_NORMAL;
|
||||
this.eyeList[EYE_STYLE.GIRLY] = MASCOT_EYES_GIRLY;
|
||||
this.eyeList[EYE_STYLE.CUTE] = MASCOT_EYES_CUTE;
|
||||
this.eyeList[EYE_STYLE.WINK] = MASCOT_EYES_WINK;
|
||||
this.eyeList[EYE_STYLE.HEART] = MASCOT_EYES_HEART;
|
||||
this.eyeList[EYE_STYLE.ANGRY] = MASCOT_EYES_ANGRY;
|
||||
|
||||
if (this.initialEmotion === MASCOT_STYLE.RANDOM) {
|
||||
this.glassesList[GLASSES_STYLE.NORMAL] = MASCOT_GLASSES;
|
||||
this.glassesList[GLASSES_STYLE.COOl] = MASCOT_SUNGLASSES;
|
||||
|
||||
this.initialEmotion =
|
||||
props.emotion != null ? props.emotion : Mascot.defaultProps.emotion;
|
||||
|
||||
if (this.initialEmotion === MASCOT_STYLE.RANDOM)
|
||||
this.initialEmotion = Math.floor(Math.random() * MASCOT_STYLE.ANGRY) + 1;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
currentEmotion: this.initialEmotion,
|
||||
|
|
@ -140,33 +138,29 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
if (props.onPress == null) {
|
||||
this.onPress = (viewRef: AnimatableViewRefType) => {
|
||||
const ref = viewRef.current;
|
||||
if (ref && ref.rubberBand) {
|
||||
if (ref != null) {
|
||||
this.setState({currentEmotion: MASCOT_STYLE.LOVE});
|
||||
ref.rubberBand(1500).then(() => {
|
||||
this.setState({currentEmotion: this.initialEmotion});
|
||||
});
|
||||
}
|
||||
};
|
||||
} else {
|
||||
this.onPress = props.onPress;
|
||||
}
|
||||
} else this.onPress = props.onPress;
|
||||
|
||||
if (props.onLongPress == null) {
|
||||
this.onLongPress = (viewRef: AnimatableViewRefType) => {
|
||||
const ref = viewRef.current;
|
||||
if (ref && ref.tada) {
|
||||
if (ref != null) {
|
||||
this.setState({currentEmotion: MASCOT_STYLE.ANGRY});
|
||||
ref.tada(1000).then(() => {
|
||||
this.setState({currentEmotion: this.initialEmotion});
|
||||
});
|
||||
}
|
||||
};
|
||||
} else {
|
||||
this.onLongPress = props.onLongPress;
|
||||
}
|
||||
} else this.onLongPress = props.onLongPress;
|
||||
}
|
||||
|
||||
getGlasses(style: GLASSES_STYLE) {
|
||||
getGlasses(style: number): React.Node {
|
||||
const glasses = this.glassesList[style];
|
||||
return (
|
||||
<Image
|
||||
|
|
@ -185,7 +179,11 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getEye(style: EYE_STYLE, isRight: boolean, rotation: string = '0deg') {
|
||||
getEye(
|
||||
style: number,
|
||||
isRight: boolean,
|
||||
rotation: string = '0deg',
|
||||
): React.Node {
|
||||
const eye = this.eyeList[style];
|
||||
return (
|
||||
<Image
|
||||
|
|
@ -203,7 +201,7 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getEyes(emotion: MASCOT_STYLE) {
|
||||
getEyes(emotion: number): React.Node {
|
||||
const final = [];
|
||||
final.push(
|
||||
<View
|
||||
|
|
@ -248,7 +246,7 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
return final;
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props, state} = this;
|
||||
const entryAnimation = props.animated ? props.entryAnimation : null;
|
||||
const loopAnimation = props.animated ? props.loopAnimation : null;
|
||||
|
|
@ -258,6 +256,7 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
aspectRatio: 1,
|
||||
...props.style,
|
||||
}}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...entryAnimation}>
|
||||
<TouchableWithoutFeedback
|
||||
onPress={() => {
|
||||
|
|
@ -267,7 +266,9 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
this.onLongPress(this.viewRef);
|
||||
}}>
|
||||
<Animatable.View ref={this.viewRef}>
|
||||
<Animatable.View {...loopAnimation}>
|
||||
<Animatable.View
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...loopAnimation}>
|
||||
<Image
|
||||
source={MASCOT_IMAGE}
|
||||
style={{
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
|
|
@ -35,42 +37,48 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import Mascot from './Mascot';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import SpeechArrow from './SpeechArrow';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
|
||||
type PropsType = {
|
||||
theme: ReactNativePaper.Theme;
|
||||
icon: string;
|
||||
title: string;
|
||||
message: string;
|
||||
theme: CustomThemeType,
|
||||
icon: string,
|
||||
title: string,
|
||||
message: string,
|
||||
buttons: {
|
||||
action?: {
|
||||
message: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
onPress?: () => void;
|
||||
};
|
||||
cancel?: {
|
||||
message: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
onPress?: () => void;
|
||||
};
|
||||
};
|
||||
emotion: number;
|
||||
visible?: boolean;
|
||||
prefKey?: string;
|
||||
action: {
|
||||
message: string,
|
||||
icon: string | null,
|
||||
color: string | null,
|
||||
onPress?: () => void,
|
||||
},
|
||||
cancel: {
|
||||
message: string,
|
||||
icon: string | null,
|
||||
color: string | null,
|
||||
onPress?: () => void,
|
||||
},
|
||||
},
|
||||
emotion: number,
|
||||
visible?: boolean,
|
||||
prefKey?: string,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
shouldRenderDialog: boolean; // Used to stop rendering after hide animation
|
||||
dialogVisible: boolean;
|
||||
shouldRenderDialog: boolean, // Used to stop rendering after hide animation
|
||||
dialogVisible: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* Component used to display a popup with the mascot.
|
||||
*/
|
||||
class MascotPopup extends React.Component<PropsType, StateType> {
|
||||
static defaultProps = {
|
||||
visible: null,
|
||||
prefKey: null,
|
||||
};
|
||||
|
||||
mascotSize: number;
|
||||
|
||||
windowWidth: number;
|
||||
|
|
@ -104,7 +112,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
componentDidMount(): * {
|
||||
BackHandler.addEventListener(
|
||||
'hardwareBackPress',
|
||||
this.onBackButtonPressAndroid,
|
||||
|
|
@ -138,20 +146,14 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
if (state.dialogVisible) {
|
||||
const {cancel} = props.buttons;
|
||||
const {action} = props.buttons;
|
||||
if (cancel) {
|
||||
this.onDismiss(cancel.onPress);
|
||||
} else if (action) {
|
||||
this.onDismiss(action.onPress);
|
||||
} else {
|
||||
this.onDismiss();
|
||||
}
|
||||
|
||||
if (cancel != null) this.onDismiss(cancel.onPress);
|
||||
else this.onDismiss(action.onPress);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
getSpeechBubble() {
|
||||
getSpeechBubble(): React.Node {
|
||||
const {state, props} = this;
|
||||
return (
|
||||
<Animatable.View
|
||||
|
|
@ -177,7 +179,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
title={props.title}
|
||||
left={
|
||||
props.icon != null
|
||||
? () => (
|
||||
? (): React.Node => (
|
||||
<Avatar.Icon
|
||||
size={48}
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
|
|
@ -185,7 +187,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
icon={props.icon}
|
||||
/>
|
||||
)
|
||||
: undefined
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<Card.Content
|
||||
|
|
@ -205,7 +207,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getMascot() {
|
||||
getMascot(): React.Node {
|
||||
const {props, state} = this;
|
||||
return (
|
||||
<Animatable.View
|
||||
|
|
@ -221,7 +223,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getButtons() {
|
||||
getButtons(): React.Node {
|
||||
const {props} = this;
|
||||
const {action} = props.buttons;
|
||||
const {cancel} = props.buttons;
|
||||
|
|
@ -268,12 +270,12 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getBackground() {
|
||||
getBackground(): React.Node {
|
||||
const {props, state} = this;
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={() => {
|
||||
this.onDismiss(props.buttons.cancel?.onPress);
|
||||
this.onDismiss(props.buttons.cancel.onPress);
|
||||
}}>
|
||||
<Animatable.View
|
||||
style={{
|
||||
|
|
@ -296,12 +298,10 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
AsyncStorageManager.set(prefKey, false);
|
||||
this.setState({dialogVisible: false});
|
||||
}
|
||||
if (callback != null) {
|
||||
callback();
|
||||
}
|
||||
if (callback != null) callback();
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {shouldRenderDialog} = this.state;
|
||||
if (shouldRenderDialog) {
|
||||
return (
|
||||
62
src/components/Mascot/SpeechArrow.js
Normal file
62
src/components/Mascot/SpeechArrow.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
|
||||
type PropsType = {
|
||||
style?: ViewStyle | null,
|
||||
size: number,
|
||||
color: string,
|
||||
};
|
||||
|
||||
export default class SpeechArrow extends React.Component<PropsType> {
|
||||
static defaultProps = {
|
||||
style: null,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<View style={props.style}>
|
||||
<View
|
||||
style={{
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderLeftWidth: 0,
|
||||
borderRightWidth: props.size,
|
||||
borderBottomWidth: props.size,
|
||||
borderStyle: 'solid',
|
||||
backgroundColor: 'transparent',
|
||||
borderLeftColor: 'transparent',
|
||||
borderRightColor: 'transparent',
|
||||
borderBottomColor: props.color,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,36 +17,41 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {TouchableRipple} from 'react-native-paper';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import {Image} from 'react-native-animatable';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import {ViewStyle} from 'react-native';
|
||||
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
|
||||
type PropsType = {
|
||||
images: Array<{url: string}>;
|
||||
style: ViewStyle;
|
||||
navigation: StackNavigationProp,
|
||||
images: Array<{url: string}>,
|
||||
style: ViewStyleProp,
|
||||
};
|
||||
|
||||
function ImageGalleryButton(props: PropsType) {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const onPress = () => {
|
||||
navigation.navigate('gallery', {images: props.images});
|
||||
class ImageGalleryButton extends React.Component<PropsType> {
|
||||
onPress = () => {
|
||||
const {navigation, images} = this.props;
|
||||
navigation.navigate('gallery', {images});
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableRipple onPress={onPress} style={props.style}>
|
||||
<Image
|
||||
resizeMode="contain"
|
||||
source={{uri: props.images[0].url}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
</TouchableRipple>
|
||||
);
|
||||
render(): React.Node {
|
||||
const {style, images} = this.props;
|
||||
return (
|
||||
<TouchableRipple onPress={this.onPress} style={style}>
|
||||
<Image
|
||||
resizeMode="contain"
|
||||
source={{uri: images[0].url}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ImageGalleryButton;
|
||||
82
src/components/Overrides/CustomAgenda.js
Normal file
82
src/components/Overrides/CustomAgenda.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import {Agenda} from 'react-native-calendars';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
theme: CustomThemeType,
|
||||
onRef: (ref: Agenda) => void,
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstraction layer for Agenda component, using custom configuration
|
||||
*/
|
||||
class CustomAgenda extends React.Component<PropsType> {
|
||||
getAgenda(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Agenda
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
ref={props.onRef}
|
||||
theme={{
|
||||
backgroundColor: props.theme.colors.agendaBackgroundColor,
|
||||
calendarBackground: props.theme.colors.background,
|
||||
textSectionTitleColor: props.theme.colors.agendaDayTextColor,
|
||||
selectedDayBackgroundColor: props.theme.colors.primary,
|
||||
selectedDayTextColor: '#ffffff',
|
||||
todayTextColor: props.theme.colors.primary,
|
||||
dayTextColor: props.theme.colors.text,
|
||||
textDisabledColor: props.theme.colors.agendaDayTextColor,
|
||||
dotColor: props.theme.colors.primary,
|
||||
selectedDotColor: '#ffffff',
|
||||
arrowColor: 'orange',
|
||||
monthTextColor: props.theme.colors.primary,
|
||||
indicatorColor: props.theme.colors.primary,
|
||||
textDayFontWeight: '300',
|
||||
textMonthFontWeight: 'bold',
|
||||
textDayHeaderFontWeight: '300',
|
||||
textDayFontSize: 16,
|
||||
textMonthFontSize: 16,
|
||||
textDayHeaderFontSize: 16,
|
||||
agendaDayTextColor: props.theme.colors.agendaDayTextColor,
|
||||
agendaDayNumColor: props.theme.colors.agendaDayTextColor,
|
||||
agendaTodayColor: props.theme.colors.primary,
|
||||
agendaKnobColor: props.theme.colors.primary,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
// Completely recreate the component on theme change to force theme reload
|
||||
if (props.theme.dark)
|
||||
return <View style={{flex: 1}}>{this.getAgenda()}</View>;
|
||||
return this.getAgenda();
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(CustomAgenda);
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {useTheme} from 'react-native-paper';
|
||||
import {Agenda, AgendaProps} from 'react-native-calendars';
|
||||
|
||||
type PropsType = {
|
||||
onRef: (ref: Agenda<any>) => void;
|
||||
} & AgendaProps<any>;
|
||||
|
||||
/**
|
||||
* Abstraction layer for Agenda component, using custom configuration
|
||||
*/
|
||||
function CustomAgenda(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
function getAgenda() {
|
||||
return (
|
||||
<Agenda
|
||||
{...props}
|
||||
ref={props.onRef}
|
||||
theme={{
|
||||
backgroundColor: theme.colors.agendaBackgroundColor,
|
||||
calendarBackground: theme.colors.background,
|
||||
textSectionTitleColor: theme.colors.agendaDayTextColor,
|
||||
selectedDayBackgroundColor: theme.colors.primary,
|
||||
selectedDayTextColor: '#ffffff',
|
||||
todayTextColor: theme.colors.primary,
|
||||
dayTextColor: theme.colors.text,
|
||||
textDisabledColor: theme.colors.agendaDayTextColor,
|
||||
dotColor: theme.colors.primary,
|
||||
selectedDotColor: '#ffffff',
|
||||
arrowColor: 'orange',
|
||||
monthTextColor: theme.colors.primary,
|
||||
indicatorColor: theme.colors.primary,
|
||||
textDayFontWeight: '300',
|
||||
textMonthFontWeight: 'bold',
|
||||
textDayHeaderFontWeight: '300',
|
||||
textDayFontSize: 16,
|
||||
textMonthFontSize: 16,
|
||||
textDayHeaderFontSize: 16,
|
||||
agendaDayTextColor: theme.colors.agendaDayTextColor,
|
||||
agendaDayNumColor: theme.colors.agendaDayTextColor,
|
||||
agendaTodayColor: theme.colors.primary,
|
||||
agendaKnobColor: theme.colors.primary,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Completely recreate the component on theme change to force theme reload
|
||||
if (theme.dark) {
|
||||
return <View style={{flex: 1}}>{getAgenda()}</View>;
|
||||
}
|
||||
return getAgenda();
|
||||
}
|
||||
|
||||
export default CustomAgenda;
|
||||
77
src/components/Overrides/CustomHTML.js
Normal file
77
src/components/Overrides/CustomHTML.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable flowtype/require-parameter-type */
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Text, withTheme} from 'react-native-paper';
|
||||
import HTML from 'react-native-render-html';
|
||||
import {Linking} from 'react-native';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
theme: CustomThemeType,
|
||||
html: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstraction layer for Agenda component, using custom configuration
|
||||
*/
|
||||
class CustomHTML extends React.Component<PropsType> {
|
||||
openWebLink = (event: {...}, link: string) => {
|
||||
Linking.openURL(link);
|
||||
};
|
||||
|
||||
getBasicText = (
|
||||
htmlAttribs,
|
||||
children,
|
||||
convertedCSSStyles,
|
||||
passProps,
|
||||
): React.Node => {
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return <Text {...passProps}>{children}</Text>;
|
||||
};
|
||||
|
||||
getListBullet = (): React.Node => {
|
||||
return <Text>- </Text>;
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
// Surround description with p to allow text styling if the description is not html
|
||||
return (
|
||||
<HTML
|
||||
html={`<p>${props.html}</p>`}
|
||||
renderers={{
|
||||
p: this.getBasicText,
|
||||
li: this.getBasicText,
|
||||
}}
|
||||
listsPrefixesRenderers={{
|
||||
ul: this.getListBullet,
|
||||
}}
|
||||
ignoredTags={['img']}
|
||||
ignoredStyles={['color', 'background-color']}
|
||||
onLinkPress={this.openWebLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(CustomHTML);
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Text} from 'react-native-paper';
|
||||
import HTML from 'react-native-render-html';
|
||||
import {GestureResponderEvent, Linking} from 'react-native';
|
||||
|
||||
type PropsType = {
|
||||
html: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstraction layer for Agenda component, using custom configuration
|
||||
*/
|
||||
function CustomHTML(props: PropsType) {
|
||||
const openWebLink = (event: GestureResponderEvent, link: string) => {
|
||||
Linking.openURL(link);
|
||||
};
|
||||
|
||||
const getBasicText = (
|
||||
htmlAttribs: any,
|
||||
children: any,
|
||||
convertedCSSStyles: any,
|
||||
passProps: any,
|
||||
) => {
|
||||
return <Text {...passProps}>{children}</Text>;
|
||||
};
|
||||
|
||||
const getListBullet = () => {
|
||||
return <Text>- </Text>;
|
||||
};
|
||||
|
||||
// Surround description with p to allow text styling if the description is not html
|
||||
return (
|
||||
<HTML
|
||||
html={`<p>${props.html}</p>`}
|
||||
renderers={{
|
||||
p: getBasicText,
|
||||
li: getBasicText,
|
||||
}}
|
||||
listsPrefixesRenderers={{
|
||||
ul: getListBullet,
|
||||
}}
|
||||
ignoredTags={['img']}
|
||||
ignoredStyles={['color', 'background-color']}
|
||||
onLinkPress={openWebLink}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomHTML;
|
||||
|
|
@ -17,33 +17,39 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {
|
||||
HeaderButton,
|
||||
HeaderButtonProps,
|
||||
HeaderButtons,
|
||||
HeaderButtonsProps,
|
||||
} from 'react-navigation-header-buttons';
|
||||
import {useTheme} from 'react-native-paper';
|
||||
import {HeaderButton, HeaderButtons} from 'react-navigation-header-buttons';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
const MaterialHeaderButton = (props: HeaderButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const MaterialHeaderButton = (props: {
|
||||
theme: CustomThemeType,
|
||||
color: string,
|
||||
}): React.Node => {
|
||||
const {color, theme} = props;
|
||||
return (
|
||||
// $FlowFixMe
|
||||
<HeaderButton
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
IconComponent={MaterialCommunityIcons}
|
||||
iconSize={26}
|
||||
color={props.color ? props.color : theme.colors.text}
|
||||
color={color != null ? color : theme.colors.text}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MaterialHeaderButtons = (
|
||||
props: HeaderButtonsProps & {children?: React.ReactNode},
|
||||
) => {
|
||||
const MaterialHeaderButtons = (props: {...}): React.Node => {
|
||||
return (
|
||||
<HeaderButtons {...props} HeaderButtonComponent={MaterialHeaderButton} />
|
||||
// $FlowFixMe
|
||||
<HeaderButtons
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
HeaderButtonComponent={withTheme(MaterialHeaderButton)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -17,14 +17,10 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ListRenderItemInfo,
|
||||
Platform,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {Platform, StatusBar, StyleSheet, View} from 'react-native';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import i18n from 'i18n-js';
|
||||
import AppIntroSlider from 'react-native-app-intro-slider';
|
||||
|
|
@ -39,27 +35,26 @@ import IntroIcon from '../Intro/IconIntro';
|
|||
import MascotIntroEnd from '../Intro/MascotIntroEnd';
|
||||
|
||||
type PropsType = {
|
||||
onDone: () => void;
|
||||
isUpdate: boolean;
|
||||
isAprilFools: boolean;
|
||||
onDone: () => void,
|
||||
isUpdate: boolean,
|
||||
isAprilFools: boolean,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
currentSlide: number;
|
||||
currentSlide: number,
|
||||
};
|
||||
|
||||
export type IntroSlideType = {
|
||||
key: string;
|
||||
title: string;
|
||||
text: string;
|
||||
view: () => React.ReactNode;
|
||||
mascotStyle?: number;
|
||||
colors: [string, string];
|
||||
key: string,
|
||||
title: string,
|
||||
text: string,
|
||||
view: () => React.Node,
|
||||
mascotStyle?: number,
|
||||
colors: [string, string],
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
mainContent: {
|
||||
flex: 1,
|
||||
paddingBottom: 100,
|
||||
},
|
||||
text: {
|
||||
|
|
@ -88,7 +83,7 @@ const styles = StyleSheet.create({
|
|||
*/
|
||||
export default class CustomIntroSlider extends React.Component<
|
||||
PropsType,
|
||||
StateType
|
||||
StateType,
|
||||
> {
|
||||
sliderRef: {current: null | AppIntroSlider};
|
||||
|
||||
|
|
@ -103,9 +98,8 @@ export default class CustomIntroSlider extends React.Component<
|
|||
/**
|
||||
* Generates intro slides
|
||||
*/
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.currentSlides = [];
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
currentSlide: 0,
|
||||
};
|
||||
|
|
@ -115,14 +109,14 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '0', // Mascot
|
||||
title: i18n.t('intro.slideMain.title'),
|
||||
text: i18n.t('intro.slideMain.text'),
|
||||
view: () => <MascotIntroWelcome />,
|
||||
view: (): React.Node => <MascotIntroWelcome />,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
title: i18n.t('intro.slidePlanex.title'),
|
||||
text: i18n.t('intro.slidePlanex.text'),
|
||||
view: () => <IntroIcon icon="calendar-clock" />,
|
||||
view: (): React.Node => <IntroIcon icon="calendar-clock" />,
|
||||
mascotStyle: MASCOT_STYLE.INTELLO,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
|
|
@ -130,7 +124,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '2',
|
||||
title: i18n.t('intro.slideEvents.title'),
|
||||
text: i18n.t('intro.slideEvents.text'),
|
||||
view: () => <IntroIcon icon="calendar-star" />,
|
||||
view: (): React.Node => <IntroIcon icon="calendar-star" />,
|
||||
mascotStyle: MASCOT_STYLE.HAPPY,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
|
|
@ -138,7 +132,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '3',
|
||||
title: i18n.t('intro.slideServices.title'),
|
||||
text: i18n.t('intro.slideServices.text'),
|
||||
view: () => <IntroIcon icon="view-dashboard-variant" />,
|
||||
view: (): React.Node => <IntroIcon icon="view-dashboard-variant" />,
|
||||
mascotStyle: MASCOT_STYLE.CUTE,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
|
|
@ -146,7 +140,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '4',
|
||||
title: i18n.t('intro.slideDone.title'),
|
||||
text: i18n.t('intro.slideDone.text'),
|
||||
view: () => <MascotIntroEnd />,
|
||||
view: (): React.Node => <MascotIntroEnd />,
|
||||
colors: ['#9c165b', '#3e042b'],
|
||||
},
|
||||
];
|
||||
|
|
@ -158,7 +152,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '1',
|
||||
title: i18n.t('intro.aprilFoolsSlide.title'),
|
||||
text: i18n.t('intro.aprilFoolsSlide.text'),
|
||||
view: () => <View />,
|
||||
view: (): React.Node => <View />,
|
||||
mascotStyle: MASCOT_STYLE.NORMAL,
|
||||
colors: ['#e01928', '#be1522'],
|
||||
},
|
||||
|
|
@ -168,21 +162,21 @@ export default class CustomIntroSlider extends React.Component<
|
|||
/**
|
||||
* Render item to be used for the intro introSlides
|
||||
*
|
||||
* @param data
|
||||
* @param item The item to be displayed
|
||||
* @param dimensions Dimensions of the item
|
||||
*/
|
||||
getIntroRenderItem = (
|
||||
data:
|
||||
| (ListRenderItemInfo<IntroSlideType> & {
|
||||
dimensions: {width: number; height: number};
|
||||
})
|
||||
| ListRenderItemInfo<IntroSlideType>,
|
||||
) => {
|
||||
const item = data.item;
|
||||
getIntroRenderItem = ({
|
||||
item,
|
||||
dimensions,
|
||||
}: {
|
||||
item: IntroSlideType,
|
||||
dimensions: {width: number, height: number},
|
||||
}): React.Node => {
|
||||
const {state} = this;
|
||||
const index = parseInt(item.key, 10);
|
||||
return (
|
||||
<LinearGradient
|
||||
style={[styles.mainContent]}
|
||||
style={[styles.mainContent, dimensions]}
|
||||
colors={item.colors}
|
||||
start={{x: 0, y: 0.1}}
|
||||
end={{x: 0.1, y: 1}}>
|
||||
|
|
@ -260,9 +254,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
};
|
||||
|
||||
static setStatusBarColor(color: string) {
|
||||
if (Platform.OS === 'android') {
|
||||
StatusBar.setBackgroundColor(color, true);
|
||||
}
|
||||
if (Platform.OS === 'android') StatusBar.setBackgroundColor(color, true);
|
||||
}
|
||||
|
||||
onSlideChange = (index: number) => {
|
||||
|
|
@ -274,9 +266,8 @@ export default class CustomIntroSlider extends React.Component<
|
|||
CustomIntroSlider.setStatusBarColor(
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
onDone = () => {
|
||||
|
|
@ -287,7 +278,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
props.onDone();
|
||||
};
|
||||
|
||||
getRenderNextButton = () => {
|
||||
getRenderNextButton = (): React.Node => {
|
||||
return (
|
||||
<Animatable.View
|
||||
useNativeDriver
|
||||
|
|
@ -302,7 +293,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
);
|
||||
};
|
||||
|
||||
getRenderDoneButton = () => {
|
||||
getRenderDoneButton = (): React.Node => {
|
||||
return (
|
||||
<Animatable.View
|
||||
useNativeDriver
|
||||
|
|
@ -317,14 +308,11 @@ export default class CustomIntroSlider extends React.Component<
|
|||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props, state} = this;
|
||||
this.currentSlides = this.introSlides;
|
||||
if (props.isUpdate) {
|
||||
this.currentSlides = this.updateSlides;
|
||||
} else if (props.isAprilFools) {
|
||||
this.currentSlides = this.aprilFoolsSlides;
|
||||
}
|
||||
if (props.isUpdate) this.currentSlides = this.updateSlides;
|
||||
else if (props.isAprilFools) this.currentSlides = this.aprilFoolsSlides;
|
||||
CustomIntroSlider.setStatusBarColor(this.currentSlides[0].colors[0]);
|
||||
return (
|
||||
<AppIntroSlider
|
||||
|
|
@ -17,11 +17,14 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {useTheme} from 'react-native-paper';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import {Modalize} from 'react-native-modalize';
|
||||
import {View} from 'react-native-animatable';
|
||||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
/**
|
||||
* Abstraction layer for Modalize component, using custom configuration
|
||||
|
|
@ -30,11 +33,11 @@ import CustomTabBar from '../Tabbar/CustomTabBar';
|
|||
* @return {*}
|
||||
*/
|
||||
function CustomModal(props: {
|
||||
onRef: (re: Modalize) => void;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const {onRef, children} = props;
|
||||
theme: CustomThemeType,
|
||||
onRef: (re: Modalize) => void,
|
||||
children?: React.Node,
|
||||
}): React.Node {
|
||||
const {theme, onRef, children} = props;
|
||||
return (
|
||||
<Modalize
|
||||
ref={onRef}
|
||||
|
|
@ -52,4 +55,6 @@ function CustomModal(props: {
|
|||
);
|
||||
}
|
||||
|
||||
export default CustomModal;
|
||||
CustomModal.defaultProps = {children: null};
|
||||
|
||||
export default withTheme(CustomModal);
|
||||
84
src/components/Overrides/CustomSlider.js
Normal file
84
src/components/Overrides/CustomSlider.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Text, withTheme} from 'react-native-paper';
|
||||
import {View} from 'react-native-animatable';
|
||||
import Slider, {SliderProps} from '@react-native-community/slider';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
theme: CustomThemeType,
|
||||
valueSuffix?: string,
|
||||
...SliderProps,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
currentValue: number,
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstraction layer for Modalize component, using custom configuration
|
||||
*
|
||||
* @param props Props to pass to the element. Must specify an onRef prop to get an Modalize ref.
|
||||
* @return {*}
|
||||
*/
|
||||
class CustomSlider extends React.Component<PropsType, StateType> {
|
||||
static defaultProps = {
|
||||
valueSuffix: '',
|
||||
};
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentValue: props.value,
|
||||
};
|
||||
}
|
||||
|
||||
onValueChange = (value: number) => {
|
||||
const {props} = this;
|
||||
this.setState({currentValue: value});
|
||||
if (props.onValueChange != null) props.onValueChange(value);
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
const {props, state} = this;
|
||||
return (
|
||||
<View style={{flex: 1, flexDirection: 'row'}}>
|
||||
<Text
|
||||
style={{
|
||||
marginHorizontal: 10,
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
}}>
|
||||
{state.currentValue}min
|
||||
</Text>
|
||||
<Slider
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
onValueChange={this.onValueChange}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(CustomSlider);
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Text} from 'react-native-paper';
|
||||
import {View} from 'react-native-animatable';
|
||||
import Slider, {SliderProps} from '@react-native-community/slider';
|
||||
import {useState} from 'react';
|
||||
|
||||
type PropsType = {
|
||||
valueSuffix?: string;
|
||||
} & SliderProps;
|
||||
|
||||
/**
|
||||
* Abstraction layer for Modalize component, using custom configuration
|
||||
*
|
||||
* @param props Props to pass to the element. Must specify an onRef prop to get an Modalize ref.
|
||||
* @return {*}
|
||||
*/
|
||||
function CustomSlider(props: PropsType) {
|
||||
const [currentValue, setCurrentValue] = useState(props.value);
|
||||
|
||||
const onValueChange = (value: number) => {
|
||||
setCurrentValue(value);
|
||||
if (props.onValueChange) {
|
||||
props.onValueChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{flex: 1, flexDirection: 'row'}}>
|
||||
<Text
|
||||
style={{
|
||||
marginHorizontal: 10,
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
}}>
|
||||
{currentValue}min
|
||||
</Text>
|
||||
<Slider {...props} ref={undefined} onValueChange={onValueChange} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSlider;
|
||||
|
|
@ -21,11 +21,8 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {ActivityIndicator, useTheme} from 'react-native-paper';
|
||||
|
||||
type Props = {
|
||||
isAbsolute?: boolean;
|
||||
};
|
||||
import {ActivityIndicator, withTheme} from 'react-native-paper';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
/**
|
||||
* Component used to display a header button
|
||||
|
|
@ -33,21 +30,29 @@ type Props = {
|
|||
* @param props Props to pass to the component
|
||||
* @return {*}
|
||||
*/
|
||||
export default function BasicLoadingScreen(props: Props) {
|
||||
const theme = useTheme();
|
||||
const {isAbsolute} = props;
|
||||
function BasicLoadingScreen(props: {
|
||||
theme: CustomThemeType,
|
||||
isAbsolute: boolean,
|
||||
}): React.Node {
|
||||
const {theme, isAbsolute} = props;
|
||||
const {colors} = theme;
|
||||
let position;
|
||||
if (isAbsolute != null && isAbsolute) position = 'absolute';
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.colors.background,
|
||||
position: isAbsolute ? 'absolute' : 'relative',
|
||||
backgroundColor: colors.background,
|
||||
position,
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<ActivityIndicator animating size="large" color={theme.colors.primary} />
|
||||
<ActivityIndicator animating size="large" color={colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(BasicLoadingScreen);
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Subheading, withTheme} from 'react-native-paper';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
|
|
@ -25,16 +27,17 @@ import i18n from 'i18n-js';
|
|||
import * as Animatable from 'react-native-animatable';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import {ERROR_TYPE} from '../../utils/WebData';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
navigation?: StackNavigationProp<any>;
|
||||
theme: ReactNativePaper.Theme;
|
||||
route?: {name: string};
|
||||
onRefresh?: () => void;
|
||||
errorCode?: number;
|
||||
icon?: string;
|
||||
message?: string;
|
||||
showRetryButton?: boolean;
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomThemeType,
|
||||
route: {name: string},
|
||||
onRefresh?: () => void,
|
||||
errorCode?: number,
|
||||
icon?: string,
|
||||
message?: string,
|
||||
showRetryButton?: boolean,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -79,11 +82,9 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.icon = '';
|
||||
this.showLoginButton = false;
|
||||
this.message = '';
|
||||
}
|
||||
|
||||
getRetryButton() {
|
||||
getRetryButton(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Button
|
||||
|
|
@ -96,7 +97,7 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
getLoginButton() {
|
||||
getLoginButton(): React.Node {
|
||||
return (
|
||||
<Button
|
||||
mode="contained"
|
||||
|
|
@ -110,12 +111,10 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
|
||||
goToLogin = () => {
|
||||
const {props} = this;
|
||||
if (props.navigation) {
|
||||
props.navigation.navigate('login', {
|
||||
screen: 'login',
|
||||
params: {nextScreen: props.route ? props.route.name : undefined},
|
||||
});
|
||||
}
|
||||
props.navigation.navigate('login', {
|
||||
screen: 'login',
|
||||
params: {nextScreen: props.route.name},
|
||||
});
|
||||
};
|
||||
|
||||
generateMessage() {
|
||||
|
|
@ -170,17 +169,13 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
this.generateMessage();
|
||||
let button;
|
||||
if (this.showLoginButton) {
|
||||
button = this.getLoginButton();
|
||||
} else if (props.showRetryButton) {
|
||||
button = this.getRetryButton();
|
||||
} else {
|
||||
button = null;
|
||||
}
|
||||
if (this.showLoginButton) button = this.getLoginButton();
|
||||
else if (props.showRetryButton) button = this.getRetryButton();
|
||||
else button = null;
|
||||
|
||||
return (
|
||||
<Animatable.View
|
||||
|
|
@ -17,15 +17,12 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import i18n from 'i18n-js';
|
||||
import {Snackbar} from 'react-native-paper';
|
||||
import {
|
||||
NativeSyntheticEvent,
|
||||
RefreshControl,
|
||||
SectionListData,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {RefreshControl, View} from 'react-native';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import {Collapsible} from 'react-navigation-collapsible';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
|
|
@ -35,44 +32,42 @@ import withCollapsible from '../../utils/withCollapsible';
|
|||
import CustomTabBar from '../Tabbar/CustomTabBar';
|
||||
import {ERROR_TYPE, readData} from '../../utils/WebData';
|
||||
import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
|
||||
import type {ApiGenericDataType} from '../../utils/WebData';
|
||||
|
||||
export type SectionListDataType<ItemT> = Array<{
|
||||
title: string;
|
||||
icon?: string;
|
||||
data: Array<ItemT>;
|
||||
keyExtractor?: (data: ItemT) => string;
|
||||
export type SectionListDataType<T> = Array<{
|
||||
title: string,
|
||||
data: Array<T>,
|
||||
keyExtractor?: (T) => string,
|
||||
}>;
|
||||
|
||||
type PropsType<ItemT, RawData> = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
fetchUrl: string;
|
||||
autoRefreshTime: number;
|
||||
refreshOnFocus: boolean;
|
||||
renderItem: (data: {item: ItemT}) => React.ReactNode;
|
||||
type PropsType<T> = {
|
||||
navigation: StackNavigationProp,
|
||||
fetchUrl: string,
|
||||
autoRefreshTime: number,
|
||||
refreshOnFocus: boolean,
|
||||
renderItem: (data: {item: T}) => React.Node,
|
||||
createDataset: (
|
||||
data: RawData | null,
|
||||
data: ApiGenericDataType | null,
|
||||
isLoading?: boolean,
|
||||
) => SectionListDataType<ItemT>;
|
||||
onScroll: (event: NativeSyntheticEvent<EventTarget>) => void;
|
||||
collapsibleStack: Collapsible;
|
||||
) => SectionListDataType<T>,
|
||||
onScroll: (event: SyntheticEvent<EventTarget>) => void,
|
||||
collapsibleStack: Collapsible,
|
||||
|
||||
showError?: boolean;
|
||||
itemHeight?: number | null;
|
||||
updateData?: number;
|
||||
renderListHeaderComponent?: (
|
||||
data: RawData | null,
|
||||
) => React.ComponentType<any> | React.ReactElement | null;
|
||||
showError?: boolean,
|
||||
itemHeight?: number | null,
|
||||
updateData?: number,
|
||||
renderListHeaderComponent?: (data: ApiGenericDataType | null) => React.Node,
|
||||
renderSectionHeader?: (
|
||||
data: {section: SectionListData<ItemT>},
|
||||
data: {section: {title: string}},
|
||||
isLoading?: boolean,
|
||||
) => React.ReactElement | null;
|
||||
stickyHeader?: boolean;
|
||||
) => React.Node,
|
||||
stickyHeader?: boolean,
|
||||
};
|
||||
|
||||
type StateType<RawData> = {
|
||||
refreshing: boolean;
|
||||
fetchedData: RawData | null;
|
||||
snackbarVisible: boolean;
|
||||
type StateType = {
|
||||
refreshing: boolean,
|
||||
fetchedData: ApiGenericDataType | null,
|
||||
snackbarVisible: boolean,
|
||||
};
|
||||
|
||||
const MIN_REFRESH_TIME = 5 * 1000;
|
||||
|
|
@ -83,25 +78,22 @@ const MIN_REFRESH_TIME = 5 * 1000;
|
|||
* This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
|
||||
* To force the component to update, change the value of updateData.
|
||||
*/
|
||||
class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
||||
PropsType<ItemT, RawData>,
|
||||
StateType<RawData>
|
||||
> {
|
||||
class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
||||
static defaultProps = {
|
||||
showError: true,
|
||||
itemHeight: null,
|
||||
updateData: 0,
|
||||
renderListHeaderComponent: () => null,
|
||||
renderSectionHeader: () => null,
|
||||
renderListHeaderComponent: (): React.Node => null,
|
||||
renderSectionHeader: (): React.Node => null,
|
||||
stickyHeader: false,
|
||||
};
|
||||
|
||||
refreshInterval: NodeJS.Timeout | undefined;
|
||||
refreshInterval: IntervalID;
|
||||
|
||||
lastRefresh: Date | undefined;
|
||||
lastRefresh: Date | null;
|
||||
|
||||
constructor(props: PropsType<ItemT, RawData>) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
refreshing: false,
|
||||
fetchedData: null,
|
||||
|
|
@ -117,7 +109,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
|||
const {navigation} = this.props;
|
||||
navigation.addListener('focus', this.onScreenFocus);
|
||||
navigation.addListener('blur', this.onScreenBlur);
|
||||
this.lastRefresh = undefined;
|
||||
this.lastRefresh = null;
|
||||
this.onRefresh();
|
||||
}
|
||||
|
||||
|
|
@ -129,18 +121,15 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
|||
if (props.refreshOnFocus && this.lastRefresh) {
|
||||
setTimeout(this.onRefresh, 200);
|
||||
}
|
||||
if (props.autoRefreshTime > 0) {
|
||||
if (props.autoRefreshTime > 0)
|
||||
this.refreshInterval = setInterval(this.onRefresh, props.autoRefreshTime);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes any interval on un-focus
|
||||
*/
|
||||
onScreenBlur = () => {
|
||||
if (this.refreshInterval) {
|
||||
clearInterval(this.refreshInterval);
|
||||
}
|
||||
clearInterval(this.refreshInterval);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -149,7 +138,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
|||
*
|
||||
* @param fetchedData The newly fetched data
|
||||
*/
|
||||
onFetchSuccess = (fetchedData: RawData) => {
|
||||
onFetchSuccess = (fetchedData: ApiGenericDataType) => {
|
||||
this.setState({
|
||||
fetchedData,
|
||||
refreshing: false,
|
||||
|
|
@ -178,9 +167,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
|||
if (this.lastRefresh != null) {
|
||||
const last = this.lastRefresh;
|
||||
canRefresh = new Date().getTime() - last.getTime() > MIN_REFRESH_TIME;
|
||||
} else {
|
||||
canRefresh = true;
|
||||
}
|
||||
} else canRefresh = true;
|
||||
if (canRefresh) {
|
||||
this.setState({refreshing: true});
|
||||
readData(fetchUrl).then(this.onFetchSuccess).catch(this.onFetchError);
|
||||
|
|
@ -202,18 +189,19 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
|||
};
|
||||
|
||||
getItemLayout = (
|
||||
height: number,
|
||||
data: Array<SectionListData<ItemT>> | null,
|
||||
data: T,
|
||||
index: number,
|
||||
): {length: number; offset: number; index: number} => {
|
||||
): {length: number, offset: number, index: number} | null => {
|
||||
const {itemHeight} = this.props;
|
||||
if (itemHeight == null) return null;
|
||||
return {
|
||||
length: height,
|
||||
offset: height * index,
|
||||
length: itemHeight,
|
||||
offset: itemHeight * index,
|
||||
index,
|
||||
};
|
||||
};
|
||||
|
||||
getRenderSectionHeader = (data: {section: SectionListData<ItemT>}) => {
|
||||
getRenderSectionHeader = (data: {section: {title: string}}): React.Node => {
|
||||
const {renderSectionHeader} = this.props;
|
||||
const {refreshing} = this.state;
|
||||
if (renderSectionHeader != null) {
|
||||
|
|
@ -226,7 +214,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
|||
return null;
|
||||
};
|
||||
|
||||
getRenderItem = (data: {item: ItemT}) => {
|
||||
getRenderItem = (data: {item: T}): React.Node => {
|
||||
const {renderItem} = this.props;
|
||||
return (
|
||||
<Animatable.View animation="fadeInUp" duration={500} useNativeDriver>
|
||||
|
|
@ -235,23 +223,19 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
|||
);
|
||||
};
|
||||
|
||||
onScroll = (event: NativeSyntheticEvent<EventTarget>) => {
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
const {onScroll} = this.props;
|
||||
if (onScroll != null) {
|
||||
onScroll(event);
|
||||
}
|
||||
if (onScroll != null) onScroll(event);
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props, state} = this;
|
||||
const {itemHeight} = props;
|
||||
let dataset: SectionListDataType<ItemT> = [];
|
||||
let dataset = [];
|
||||
if (
|
||||
state.fetchedData != null ||
|
||||
(state.fetchedData == null && !props.showError)
|
||||
) {
|
||||
)
|
||||
dataset = props.createDataset(state.fetchedData, state.refreshing);
|
||||
}
|
||||
|
||||
const {containerPaddingTop} = props.collapsibleStack;
|
||||
return (
|
||||
|
|
@ -286,11 +270,7 @@ class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
|||
/>
|
||||
)
|
||||
}
|
||||
getItemLayout={
|
||||
itemHeight
|
||||
? (data, index) => this.getItemLayout(itemHeight, data, index)
|
||||
: undefined
|
||||
}
|
||||
getItemLayout={props.itemHeight != null ? this.getItemLayout : null}
|
||||
onScroll={this.onScroll}
|
||||
hasTab
|
||||
/>
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import WebView from 'react-native-webview';
|
||||
import {
|
||||
|
|
@ -25,17 +27,12 @@ import {
|
|||
OverflowMenu,
|
||||
} from 'react-navigation-header-buttons';
|
||||
import i18n from 'i18n-js';
|
||||
import {
|
||||
Animated,
|
||||
BackHandler,
|
||||
Linking,
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
} from 'react-native';
|
||||
import {Animated, BackHandler, Linking} from 'react-native';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import {Collapsible} from 'react-navigation-collapsible';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import withCollapsible from '../../utils/withCollapsible';
|
||||
import MaterialHeaderButtons, {Item} from '../Overrides/CustomHeaderButton';
|
||||
import {ERROR_TYPE} from '../../utils/WebData';
|
||||
|
|
@ -43,15 +40,15 @@ import ErrorView from './ErrorView';
|
|||
import BasicLoadingScreen from './BasicLoadingScreen';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
theme: ReactNativePaper.Theme;
|
||||
url: string;
|
||||
collapsibleStack: Collapsible;
|
||||
onMessage: (event: {nativeEvent: {data: string}}) => void;
|
||||
onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
|
||||
customJS?: string;
|
||||
customPaddingFunction?: null | ((padding: number) => string);
|
||||
showAdvancedControls?: boolean;
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomThemeType,
|
||||
url: string,
|
||||
collapsibleStack: Collapsible,
|
||||
onMessage: (event: {nativeEvent: {data: string}}) => void,
|
||||
onScroll: (event: SyntheticEvent<EventTarget>) => void,
|
||||
customJS?: string,
|
||||
customPaddingFunction?: null | ((padding: number) => string),
|
||||
showAdvancedControls?: boolean,
|
||||
};
|
||||
|
||||
const AnimatedWebView = Animated.createAnimatedComponent(WebView);
|
||||
|
|
@ -66,17 +63,14 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
customPaddingFunction: null,
|
||||
};
|
||||
|
||||
currentUrl: string;
|
||||
|
||||
webviewRef: {current: null | WebView};
|
||||
|
||||
canGoBack: boolean;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super();
|
||||
this.webviewRef = React.createRef();
|
||||
this.canGoBack = false;
|
||||
this.currentUrl = props.url;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -121,7 +115,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getBasicButton = () => {
|
||||
getBasicButton = (): React.Node => {
|
||||
return (
|
||||
<MaterialHeaderButtons>
|
||||
<Item
|
||||
|
|
@ -144,7 +138,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getAdvancedButtons = () => {
|
||||
getAdvancedButtons = (): React.Node => {
|
||||
const {props} = this;
|
||||
return (
|
||||
<MaterialHeaderButtons>
|
||||
|
|
@ -185,7 +179,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getRenderLoading = () => <BasicLoadingScreen isAbsolute />;
|
||||
getRenderLoading = (): React.Node => <BasicLoadingScreen isAbsolute />;
|
||||
|
||||
/**
|
||||
* Gets the javascript needed to generate a padding on top of the page
|
||||
|
|
@ -207,32 +201,25 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
* Callback to use when refresh button is clicked. Reloads the webview.
|
||||
*/
|
||||
onRefreshClicked = () => {
|
||||
if (this.webviewRef.current != null) {
|
||||
this.webviewRef.current.reload();
|
||||
}
|
||||
if (this.webviewRef.current != null) this.webviewRef.current.reload();
|
||||
};
|
||||
|
||||
onGoBackClicked = () => {
|
||||
if (this.webviewRef.current != null) {
|
||||
this.webviewRef.current.goBack();
|
||||
}
|
||||
if (this.webviewRef.current != null) this.webviewRef.current.goBack();
|
||||
};
|
||||
|
||||
onGoForwardClicked = () => {
|
||||
if (this.webviewRef.current != null) {
|
||||
this.webviewRef.current.goForward();
|
||||
}
|
||||
if (this.webviewRef.current != null) this.webviewRef.current.goForward();
|
||||
};
|
||||
|
||||
onOpenClicked = () => {
|
||||
Linking.openURL(this.currentUrl);
|
||||
const {url} = this.props;
|
||||
Linking.openURL(url);
|
||||
};
|
||||
|
||||
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
const {onScroll} = this.props;
|
||||
if (onScroll) {
|
||||
onScroll(event);
|
||||
}
|
||||
if (onScroll) onScroll(event);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -241,12 +228,11 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
* @param script The script to inject
|
||||
*/
|
||||
injectJavaScript = (script: string) => {
|
||||
if (this.webviewRef.current != null) {
|
||||
if (this.webviewRef.current != null)
|
||||
this.webviewRef.current.injectJavaScript(script);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const {containerPaddingTop, onScrollWithListener} = props.collapsibleStack;
|
||||
return (
|
||||
|
|
@ -257,14 +243,13 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
injectedJavaScript={props.customJS}
|
||||
javaScriptEnabled
|
||||
renderLoading={this.getRenderLoading}
|
||||
renderError={() => (
|
||||
renderError={(): React.Node => (
|
||||
<ErrorView
|
||||
errorCode={ERROR_TYPE.CONNECTION_ERROR}
|
||||
onRefresh={this.onRefreshClicked}
|
||||
/>
|
||||
)}
|
||||
onNavigationStateChange={(navState) => {
|
||||
this.currentUrl = navState.url;
|
||||
onNavigationStateChange={(navState: {canGoBack: boolean}) => {
|
||||
this.canGoBack = navState.canGoBack;
|
||||
}}
|
||||
onMessage={props.onMessage}
|
||||
|
|
@ -272,7 +257,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop));
|
||||
}}
|
||||
// Animations
|
||||
onScroll={(event) => onScrollWithListener(this.onScroll)(event)}
|
||||
onScroll={onScrollWithListener(this.onScroll)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,33 +17,49 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated} from 'react-native';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import {Collapsible} from 'react-navigation-collapsible';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import TabIcon from './TabIcon';
|
||||
import TabHomeIcon from './TabHomeIcon';
|
||||
import {BottomTabBarProps} from '@react-navigation/bottom-tabs';
|
||||
import {NavigationState} from '@react-navigation/native';
|
||||
import {
|
||||
PartialState,
|
||||
Route,
|
||||
} from '@react-navigation/routers/lib/typescript/src/types';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
type RouteType = Route<string> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
type RouteType = {
|
||||
name: string,
|
||||
key: string,
|
||||
params: {collapsible: Collapsible},
|
||||
state: {
|
||||
index: number,
|
||||
routes: Array<RouteType>,
|
||||
},
|
||||
};
|
||||
|
||||
interface PropsType extends BottomTabBarProps {
|
||||
theme: ReactNativePaper.Theme;
|
||||
}
|
||||
type PropsType = {
|
||||
state: {
|
||||
index: number,
|
||||
routes: Array<RouteType>,
|
||||
},
|
||||
descriptors: {
|
||||
[key: string]: {
|
||||
options: {
|
||||
tabBarLabel: string,
|
||||
title: string,
|
||||
},
|
||||
},
|
||||
},
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
translateY: any;
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
translateY: any,
|
||||
};
|
||||
|
||||
type validRoutes = 'proxiwash' | 'services' | 'planning' | 'planex';
|
||||
|
||||
const TAB_ICONS = {
|
||||
proxiwash: 'tshirt-crew',
|
||||
services: 'account-circle',
|
||||
|
|
@ -54,13 +70,11 @@ const TAB_ICONS = {
|
|||
class CustomTabBar extends React.Component<PropsType, StateType> {
|
||||
static TAB_BAR_HEIGHT = 48;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
translateY: new Animated.Value(0),
|
||||
};
|
||||
// @ts-ignore
|
||||
props.navigation.addListener('state', this.onRouteChange);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -72,9 +86,13 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
*/
|
||||
onItemPress(route: RouteType, currentIndex: number, destIndex: number) {
|
||||
const {navigation} = this.props;
|
||||
if (currentIndex !== destIndex) {
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
if (currentIndex !== destIndex && !event.defaultPrevented)
|
||||
navigation.navigate(route.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -84,9 +102,13 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
*/
|
||||
onItemLongPress(route: RouteType) {
|
||||
const {navigation} = this.props;
|
||||
if (route.name === 'home') {
|
||||
const event = navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
if (route.name === 'home' && !event.defaultPrevented)
|
||||
navigation.navigate('game-start');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -104,13 +126,11 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
* @param focused
|
||||
* @returns {null}
|
||||
*/
|
||||
getTabBarIcon = (route: RouteType, focused: boolean) => {
|
||||
let icon = TAB_ICONS[route.name as validRoutes];
|
||||
getTabBarIcon = (route: RouteType, focused: boolean): React.Node => {
|
||||
let icon = TAB_ICONS[route.name];
|
||||
icon = focused ? icon : `${icon}-outline`;
|
||||
if (route.name !== 'home') {
|
||||
return icon;
|
||||
}
|
||||
return '';
|
||||
if (route.name !== 'home') return icon;
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -121,18 +141,14 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
* @param index The index of the current route
|
||||
* @returns {*}
|
||||
*/
|
||||
getRenderIcon = (route: RouteType, index: number) => {
|
||||
getRenderIcon = (route: RouteType, index: number): React.Node => {
|
||||
const {props} = this;
|
||||
const {state} = props;
|
||||
const {options} = props.descriptors[route.key];
|
||||
let label;
|
||||
if (options.tabBarLabel != null) {
|
||||
label = options.tabBarLabel;
|
||||
} else if (options.title != null) {
|
||||
label = options.title;
|
||||
} else {
|
||||
label = route.name;
|
||||
}
|
||||
if (options.tabBarLabel != null) label = options.tabBarLabel;
|
||||
else if (options.title != null) label = options.title;
|
||||
else label = route.name;
|
||||
|
||||
const onPress = () => {
|
||||
this.onItemPress(route, state.index, index);
|
||||
|
|
@ -152,7 +168,7 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
onLongPress={onLongPress}
|
||||
icon={this.getTabBarIcon(route, isFocused)}
|
||||
color={color}
|
||||
label={label as string}
|
||||
label={label}
|
||||
focused={isFocused}
|
||||
extraData={state.index > index}
|
||||
key={route.key}
|
||||
|
|
@ -170,7 +186,7 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
};
|
||||
|
||||
getIcons() {
|
||||
getIcons(): React.Node {
|
||||
const {props} = this;
|
||||
return props.state.routes.map(this.getRenderIcon);
|
||||
}
|
||||
|
|
@ -181,12 +197,9 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
if (isFocused) {
|
||||
const stackState = route.state;
|
||||
const stackRoute =
|
||||
stackState && stackState.index != null
|
||||
? stackState.routes[stackState.index]
|
||||
: null;
|
||||
const params: {collapsible: Collapsible} | null | undefined = stackRoute
|
||||
? (stackRoute.params as {collapsible: Collapsible})
|
||||
: null;
|
||||
stackState != null ? stackState.routes[stackState.index] : null;
|
||||
const params: {collapsible: Collapsible} | null =
|
||||
stackRoute != null ? stackRoute.params : null;
|
||||
const collapsible = params != null ? params.collapsible : null;
|
||||
if (collapsible != null) {
|
||||
this.setState({
|
||||
|
|
@ -196,11 +209,14 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props, state} = this;
|
||||
props.navigation.addListener('state', this.onRouteChange);
|
||||
const icons = this.getIcons();
|
||||
return (
|
||||
// $FlowFixMe
|
||||
<Animated.View
|
||||
useNativeDriver
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
height: CustomTabBar.TAB_BAR_HEIGHT,
|
||||
|
|
@ -17,18 +17,20 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Image, View} from 'react-native';
|
||||
import {FAB} from 'react-native-paper';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
const FOCUSED_ICON = require('../../../assets/tab-icon.png');
|
||||
const UNFOCUSED_ICON = require('../../../assets/tab-icon-outline.png');
|
||||
import FOCUSED_ICON from '../../../assets/tab-icon.png';
|
||||
import UNFOCUSED_ICON from '../../../assets/tab-icon-outline.png';
|
||||
|
||||
type PropsType = {
|
||||
focused: boolean;
|
||||
onPress: () => void;
|
||||
onLongPress: () => void;
|
||||
tabBarHeight: number;
|
||||
focused: boolean,
|
||||
onPress: () => void,
|
||||
onLongPress: () => void,
|
||||
tabBarHeight: number,
|
||||
};
|
||||
|
||||
const AnimatedFAB = Animatable.createAnimatableComponent(FAB);
|
||||
|
|
@ -42,7 +44,6 @@ class TabHomeIcon extends React.Component<PropsType> {
|
|||
Animatable.initializeRegistryWithDefinitions({
|
||||
fabFocusIn: {
|
||||
'0': {
|
||||
// @ts-ignore
|
||||
scale: 1,
|
||||
translateY: 0,
|
||||
},
|
||||
|
|
@ -57,7 +58,6 @@ class TabHomeIcon extends React.Component<PropsType> {
|
|||
},
|
||||
fabFocusOut: {
|
||||
'0': {
|
||||
// @ts-ignore
|
||||
scale: 1.1,
|
||||
translateY: -6,
|
||||
},
|
||||
|
|
@ -74,7 +74,13 @@ class TabHomeIcon extends React.Component<PropsType> {
|
|||
return nextProps.focused !== focused;
|
||||
}
|
||||
|
||||
getIconRender = ({size, color}: {size: number; color: string}) => {
|
||||
getIconRender = ({
|
||||
size,
|
||||
color,
|
||||
}: {
|
||||
size: number,
|
||||
color: string,
|
||||
}): React.Node => {
|
||||
const {focused} = this.props;
|
||||
return (
|
||||
<Image
|
||||
|
|
@ -88,7 +94,7 @@ class TabHomeIcon extends React.Component<PropsType> {
|
|||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<View
|
||||
|
|
@ -17,21 +17,25 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {TouchableRipple, withTheme} from 'react-native-paper';
|
||||
import type {MaterialCommunityIconsGlyphs} from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
focused: boolean;
|
||||
color: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
onPress: () => void;
|
||||
onLongPress: () => void;
|
||||
theme: ReactNativePaper.Theme;
|
||||
extraData: null | boolean | number | string;
|
||||
focused: boolean,
|
||||
color: string,
|
||||
label: string,
|
||||
icon: MaterialCommunityIconsGlyphs,
|
||||
onPress: () => void,
|
||||
onLongPress: () => void,
|
||||
theme: CustomThemeType,
|
||||
extraData: null | boolean | number | string,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -45,7 +49,6 @@ class TabIcon extends React.Component<PropsType> {
|
|||
Animatable.initializeRegistryWithDefinitions({
|
||||
focusIn: {
|
||||
'0': {
|
||||
// @ts-ignore
|
||||
scale: 1,
|
||||
translateY: 0,
|
||||
},
|
||||
|
|
@ -60,7 +63,6 @@ class TabIcon extends React.Component<PropsType> {
|
|||
},
|
||||
focusOut: {
|
||||
'0': {
|
||||
// @ts-ignore
|
||||
scale: 1.2,
|
||||
translateY: 6,
|
||||
},
|
||||
|
|
@ -86,7 +88,7 @@ class TabIcon extends React.Component<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<TouchableRipple
|
||||
|
|
@ -97,7 +99,7 @@ class TabIcon extends React.Component<PropsType> {
|
|||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
borderRadius: 10,
|
||||
borderRadius: 10
|
||||
}}>
|
||||
<View>
|
||||
<Animatable.View
|
||||
|
|
@ -19,16 +19,14 @@
|
|||
|
||||
// @flow
|
||||
|
||||
const ICON_AMICALE = require('../../assets/amicale.png');
|
||||
const ICON_CAMPUS = require('../../assets/android.icon.png');
|
||||
import ICON_AMICALE from '../../assets/amicale.png';
|
||||
import ICON_CAMPUS from '../../assets/android.icon.png';
|
||||
|
||||
export type NewsSourceType = {
|
||||
icon: number;
|
||||
name: string;
|
||||
icon: number,
|
||||
name: string,
|
||||
};
|
||||
|
||||
export type AvailablePages = 'amicale.deseleves' | 'campus.insat';
|
||||
|
||||
export default {
|
||||
'amicale.deseleves': {
|
||||
icon: ICON_AMICALE,
|
||||
31
src/constants/PaperStyles.js
Normal file
31
src/constants/PaperStyles.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
|
||||
export type ListIconPropsType = {
|
||||
color: string,
|
||||
style: ViewStyleProp,
|
||||
};
|
||||
|
||||
export type CardTitleIconPropsType = {
|
||||
size: number,
|
||||
};
|
||||
|
|
@ -17,26 +17,25 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export enum MachineStates {
|
||||
AVAILABLE,
|
||||
RUNNING,
|
||||
RUNNING_NOT_STARTED,
|
||||
FINISHED,
|
||||
UNAVAILABLE,
|
||||
ERROR,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
export default {
|
||||
stateIcons: [
|
||||
'radiobox-blank',
|
||||
'progress-check',
|
||||
'alert-circle-outline',
|
||||
'check-circle',
|
||||
'alert-octagram-outline',
|
||||
'alert',
|
||||
'help-circle-outline',
|
||||
],
|
||||
machineStates: {
|
||||
AVAILABLE: 0,
|
||||
RUNNING: 1,
|
||||
RUNNING_NOT_STARTED: 2,
|
||||
FINISHED: 3,
|
||||
UNAVAILABLE: 4,
|
||||
ERROR: 5,
|
||||
UNKNOWN: 6,
|
||||
},
|
||||
stateIcons: {
|
||||
0: 'radiobox-blank',
|
||||
1: 'progress-check',
|
||||
2: 'alert-circle-outline',
|
||||
3: 'check-circle',
|
||||
4: 'alert-octagram-outline',
|
||||
5: 'alert',
|
||||
6: 'help-circle-outline',
|
||||
},
|
||||
washinsa: {
|
||||
id: 'washinsa',
|
||||
title: 'screens.proxiwash.washinsa.title',
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import i18n from 'i18n-js';
|
||||
import type {IntroSlideType} from '../components/Overrides/CustomIntroSlider';
|
||||
|
|
@ -47,16 +49,16 @@ export default class Update {
|
|||
this.updateSlides = [
|
||||
{
|
||||
key: '0',
|
||||
title: i18n.t('intro.updateSlide0.title'),
|
||||
text: i18n.t('intro.updateSlide0.text'),
|
||||
view: () => <MascotIntroWelcome />,
|
||||
title: i18n.t(`intro.updateSlide0.title`),
|
||||
text: i18n.t(`intro.updateSlide0.text`),
|
||||
view: (): React.Node => <MascotIntroWelcome />,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
title: i18n.t('intro.updateSlide1.title'),
|
||||
text: i18n.t('intro.updateSlide1.text'),
|
||||
view: () => <IntroIcon icon="account-heart-outline" />,
|
||||
title: i18n.t(`intro.updateSlide1.title`),
|
||||
text: i18n.t(`intro.updateSlide1.text`),
|
||||
view: (): React.Node => <IntroIcon icon="account-heart-outline" />,
|
||||
colors: ['#9c165b', '#3e042b'],
|
||||
},
|
||||
];
|
||||
|
|
@ -17,7 +17,10 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen';
|
||||
import type {CustomThemeType} from './ThemeManager';
|
||||
import type {RuFoodCategoryType} from '../screens/Services/SelfMenuScreen';
|
||||
|
||||
/**
|
||||
|
|
@ -54,9 +57,8 @@ export default class AprilFoolsManager {
|
|||
* @returns {ThemeManager}
|
||||
*/
|
||||
static getInstance(): AprilFoolsManager {
|
||||
if (AprilFoolsManager.instance == null) {
|
||||
if (AprilFoolsManager.instance == null)
|
||||
AprilFoolsManager.instance = new AprilFoolsManager();
|
||||
}
|
||||
return AprilFoolsManager.instance;
|
||||
}
|
||||
|
||||
|
|
@ -128,9 +130,7 @@ export default class AprilFoolsManager {
|
|||
* @param currentTheme
|
||||
* @returns {{colors: {textDisabled: string, agendaDayTextColor: string, surface: string, background: string, dividerBackground: string, accent: string, agendaBackgroundColor: string, tabIcon: string, card: string, primary: string}}}
|
||||
*/
|
||||
static getAprilFoolsTheme(
|
||||
currentTheme: ReactNativePaper.Theme,
|
||||
): ReactNativePaper.Theme {
|
||||
static getAprilFoolsTheme(currentTheme: CustomThemeType): CustomThemeType {
|
||||
return {
|
||||
...currentTheme,
|
||||
colors: {
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import AsyncStorage from '@react-native-community/async-storage';
|
||||
import {SERVICES_KEY} from './ServicesManager';
|
||||
|
||||
|
|
@ -29,7 +31,7 @@ import {SERVICES_KEY} from './ServicesManager';
|
|||
export default class AsyncStorageManager {
|
||||
static instance: AsyncStorageManager | null = null;
|
||||
|
||||
static PREFERENCES: {[key: string]: {key: string; default: string}} = {
|
||||
static PREFERENCES = {
|
||||
debugUnlocked: {
|
||||
key: 'debugUnlocked',
|
||||
default: '0',
|
||||
|
|
@ -130,10 +132,10 @@ export default class AsyncStorageManager {
|
|||
},
|
||||
};
|
||||
|
||||
private currentPreferences: {[key: string]: string};
|
||||
#currentPreferences: {[key: string]: string};
|
||||
|
||||
constructor() {
|
||||
this.currentPreferences = {};
|
||||
this.#currentPreferences = {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,9 +143,8 @@ export default class AsyncStorageManager {
|
|||
* @returns {AsyncStorageManager}
|
||||
*/
|
||||
static getInstance(): AsyncStorageManager {
|
||||
if (AsyncStorageManager.instance == null) {
|
||||
if (AsyncStorageManager.instance == null)
|
||||
AsyncStorageManager.instance = new AsyncStorageManager();
|
||||
}
|
||||
return AsyncStorageManager.instance;
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +156,8 @@ export default class AsyncStorageManager {
|
|||
*/
|
||||
static set(
|
||||
key: string,
|
||||
value: number | string | boolean | object | Array<any>,
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
value: number | string | boolean | {...} | Array<any>,
|
||||
) {
|
||||
AsyncStorageManager.getInstance().setPreference(key, value);
|
||||
}
|
||||
|
|
@ -198,7 +200,8 @@ export default class AsyncStorageManager {
|
|||
* @param key
|
||||
* @returns {{...}}
|
||||
*/
|
||||
static getObject<T>(key: string): T {
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
static getObject(key: string): any {
|
||||
return JSON.parse(AsyncStorageManager.getString(key));
|
||||
}
|
||||
|
||||
|
|
@ -209,7 +212,7 @@ export default class AsyncStorageManager {
|
|||
* @return {Promise<void>}
|
||||
*/
|
||||
async loadPreferences() {
|
||||
const prefKeys: Array<string> = [];
|
||||
const prefKeys = [];
|
||||
// Get all available keys
|
||||
Object.keys(AsyncStorageManager.PREFERENCES).forEach((key: string) => {
|
||||
prefKeys.push(key);
|
||||
|
|
@ -220,10 +223,8 @@ export default class AsyncStorageManager {
|
|||
resultArray.forEach((item: [string, string | null]) => {
|
||||
const key = item[0];
|
||||
let val = item[1];
|
||||
if (val === null) {
|
||||
val = AsyncStorageManager.PREFERENCES[key].default;
|
||||
}
|
||||
this.currentPreferences[key] = val;
|
||||
if (val === null) val = AsyncStorageManager.PREFERENCES[key].default;
|
||||
this.#currentPreferences[key] = val;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -236,18 +237,16 @@ export default class AsyncStorageManager {
|
|||
*/
|
||||
setPreference(
|
||||
key: string,
|
||||
value: number | string | boolean | object | Array<any>,
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
value: number | string | boolean | {...} | Array<any>,
|
||||
) {
|
||||
if (AsyncStorageManager.PREFERENCES[key] != null) {
|
||||
let convertedValue;
|
||||
if (typeof value === 'string') {
|
||||
convertedValue = value;
|
||||
} else if (typeof value === 'boolean' || typeof value === 'number') {
|
||||
if (typeof value === 'string') convertedValue = value;
|
||||
else if (typeof value === 'boolean' || typeof value === 'number')
|
||||
convertedValue = value.toString();
|
||||
} else {
|
||||
convertedValue = JSON.stringify(value);
|
||||
}
|
||||
this.currentPreferences[key] = convertedValue;
|
||||
else convertedValue = JSON.stringify(value);
|
||||
this.#currentPreferences[key] = convertedValue;
|
||||
AsyncStorage.setItem(key, convertedValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -260,6 +259,6 @@ export default class AsyncStorageManager {
|
|||
* @returns {string|null}
|
||||
*/
|
||||
getPreference(key: string): string | null {
|
||||
return this.currentPreferences[key];
|
||||
return this.#currentPreferences[key];
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
// @flow
|
||||
|
||||
import * as Keychain from 'react-native-keychain';
|
||||
import type {ApiDataLoginType} from '../utils/WebData';
|
||||
import type {ApiDataLoginType, ApiGenericDataType} from '../utils/WebData';
|
||||
import {apiRequest, ERROR_TYPE} from '../utils/WebData';
|
||||
|
||||
/**
|
||||
|
|
@ -40,10 +40,12 @@ const AUTH_PATH = 'password';
|
|||
export default class ConnectionManager {
|
||||
static instance: ConnectionManager | null = null;
|
||||
|
||||
private token: string | null;
|
||||
#email: string;
|
||||
|
||||
#token: string | null;
|
||||
|
||||
constructor() {
|
||||
this.token = null;
|
||||
this.#token = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -52,9 +54,8 @@ export default class ConnectionManager {
|
|||
* @returns {ConnectionManager}
|
||||
*/
|
||||
static getInstance(): ConnectionManager {
|
||||
if (ConnectionManager.instance == null) {
|
||||
if (ConnectionManager.instance == null)
|
||||
ConnectionManager.instance = new ConnectionManager();
|
||||
}
|
||||
return ConnectionManager.instance;
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +65,7 @@ export default class ConnectionManager {
|
|||
* @returns {string | null}
|
||||
*/
|
||||
getToken(): string | null {
|
||||
return this.token;
|
||||
return this.#token;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -76,17 +77,18 @@ export default class ConnectionManager {
|
|||
return new Promise(
|
||||
(resolve: (token: string) => void, reject: () => void) => {
|
||||
const token = this.getToken();
|
||||
if (token != null) {
|
||||
resolve(token);
|
||||
} else {
|
||||
if (token != null) resolve(token);
|
||||
else {
|
||||
Keychain.getInternetCredentials(SERVER_NAME)
|
||||
.then((data: Keychain.UserCredentials | false) => {
|
||||
if (data && data.password != null) {
|
||||
this.token = data.password;
|
||||
resolve(this.token);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
if (
|
||||
data != null &&
|
||||
data.password != null &&
|
||||
typeof data.password === 'string'
|
||||
) {
|
||||
this.#token = data.password;
|
||||
resolve(this.#token);
|
||||
} else reject();
|
||||
})
|
||||
.catch((): void => reject());
|
||||
}
|
||||
|
|
@ -114,7 +116,8 @@ export default class ConnectionManager {
|
|||
return new Promise((resolve: () => void, reject: () => void) => {
|
||||
Keychain.setInternetCredentials(SERVER_NAME, 'token', token)
|
||||
.then(() => {
|
||||
this.token = token;
|
||||
this.#token = token;
|
||||
this.#email = email;
|
||||
resolve();
|
||||
})
|
||||
.catch((): void => reject());
|
||||
|
|
@ -130,7 +133,7 @@ export default class ConnectionManager {
|
|||
return new Promise((resolve: () => void, reject: () => void) => {
|
||||
Keychain.resetInternetCredentials(SERVER_NAME)
|
||||
.then(() => {
|
||||
this.token = null;
|
||||
this.#token = null;
|
||||
resolve();
|
||||
})
|
||||
.catch((): void => reject());
|
||||
|
|
@ -153,15 +156,13 @@ export default class ConnectionManager {
|
|||
email,
|
||||
password,
|
||||
};
|
||||
apiRequest<ApiDataLoginType>(AUTH_PATH, 'POST', data)
|
||||
apiRequest(AUTH_PATH, 'POST', data)
|
||||
.then((response: ApiDataLoginType) => {
|
||||
if (response.token != null) {
|
||||
this.saveLogin(email, response.token)
|
||||
.then((): void => resolve())
|
||||
.catch((): void => reject(ERROR_TYPE.TOKEN_SAVE));
|
||||
} else {
|
||||
reject(ERROR_TYPE.SERVER_ERROR);
|
||||
}
|
||||
} else reject(ERROR_TYPE.SERVER_ERROR);
|
||||
})
|
||||
.catch((error: number): void => reject(error));
|
||||
},
|
||||
|
|
@ -175,23 +176,24 @@ export default class ConnectionManager {
|
|||
* @param params
|
||||
* @returns Promise<ApiGenericDataType>
|
||||
*/
|
||||
async authenticatedRequest<T>(
|
||||
async authenticatedRequest(
|
||||
path: string,
|
||||
params: {[key: string]: any},
|
||||
): Promise<T> {
|
||||
params: {...},
|
||||
): Promise<ApiGenericDataType> {
|
||||
return new Promise(
|
||||
(resolve: (response: T) => void, reject: (error: number) => void) => {
|
||||
(
|
||||
resolve: (response: ApiGenericDataType) => void,
|
||||
reject: (error: number) => void,
|
||||
) => {
|
||||
if (this.getToken() !== null) {
|
||||
const data = {
|
||||
...params,
|
||||
token: this.getToken(),
|
||||
};
|
||||
apiRequest<T>(path, 'POST', data)
|
||||
.then((response: T): void => resolve(response))
|
||||
apiRequest(path, 'POST', data)
|
||||
.then((response: ApiGenericDataType): void => resolve(response))
|
||||
.catch((error: number): void => reject(error));
|
||||
} else {
|
||||
reject(ERROR_TYPE.TOKEN_RETRIEVE);
|
||||
}
|
||||
} else reject(ERROR_TYPE.TOKEN_RETRIEVE);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -21,12 +21,12 @@
|
|||
|
||||
import type {ServiceItemType} from './ServicesManager';
|
||||
import ServicesManager from './ServicesManager';
|
||||
import {getSublistWithIds} from '../utils/Services';
|
||||
import {getSublistWithIds} from '../utils/Utils';
|
||||
import AsyncStorageManager from './AsyncStorageManager';
|
||||
|
||||
export default class DashboardManager extends ServicesManager {
|
||||
getCurrentDashboard(): Array<ServiceItemType | null> {
|
||||
const dashboardIdList = AsyncStorageManager.getObject<Array<string>>(
|
||||
const dashboardIdList = AsyncStorageManager.getObject(
|
||||
AsyncStorageManager.PREFERENCES.dashboardItems.key,
|
||||
);
|
||||
const allDatasets = [
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
/**
|
||||
|
|
@ -26,9 +28,9 @@ import i18n from 'i18n-js';
|
|||
export default class DateManager {
|
||||
static instance: DateManager | null = null;
|
||||
|
||||
daysOfWeek: Array<string> = [];
|
||||
daysOfWeek = [];
|
||||
|
||||
monthsOfYear: Array<string> = [];
|
||||
monthsOfYear = [];
|
||||
|
||||
constructor() {
|
||||
this.daysOfWeek.push(i18n.t('date.daysOfWeek.sunday')); // 0 represents sunday
|
||||
|
|
@ -58,9 +60,7 @@ export default class DateManager {
|
|||
* @returns {DateManager}
|
||||
*/
|
||||
static getInstance(): DateManager {
|
||||
if (DateManager.instance == null) {
|
||||
DateManager.instance = new DateManager();
|
||||
}
|
||||
if (DateManager.instance == null) DateManager.instance = new DateManager();
|
||||
return DateManager.instance;
|
||||
}
|
||||
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import i18n from 'i18n-js';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import AvailableWebsites from '../constants/AvailableWebsites';
|
||||
|
|
@ -91,24 +93,24 @@ export const SERVICES_CATEGORIES_KEY = {
|
|||
};
|
||||
|
||||
export type ServiceItemType = {
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
image: string | number;
|
||||
onPress: () => void;
|
||||
badgeFunction?: (dashboard: FullDashboardType) => number;
|
||||
key: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
image: string,
|
||||
onPress: () => void,
|
||||
badgeFunction?: (dashboard: FullDashboardType) => number,
|
||||
};
|
||||
|
||||
export type ServiceCategoryType = {
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
image: string | number;
|
||||
content: Array<ServiceItemType>;
|
||||
key: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
image: string | number,
|
||||
content: Array<ServiceItemType>,
|
||||
};
|
||||
|
||||
export default class ServicesManager {
|
||||
navigation: StackNavigationProp<any>;
|
||||
navigation: StackNavigationProp;
|
||||
|
||||
amicaleDataset: Array<ServiceItemType>;
|
||||
|
||||
|
|
@ -120,7 +122,7 @@ export default class ServicesManager {
|
|||
|
||||
categoriesDataset: Array<ServiceCategoryType>;
|
||||
|
||||
constructor(nav: StackNavigationProp<any>) {
|
||||
constructor(nav: StackNavigationProp) {
|
||||
this.navigation = nav;
|
||||
this.amicaleDataset = [
|
||||
{
|
||||
|
|
@ -334,11 +336,9 @@ export default class ServicesManager {
|
|||
* @returns {null}
|
||||
*/
|
||||
onAmicaleServicePress(route: string) {
|
||||
if (ConnectionManager.getInstance().isLoggedIn()) {
|
||||
if (ConnectionManager.getInstance().isLoggedIn())
|
||||
this.navigation.navigate(route);
|
||||
} else {
|
||||
this.navigation.navigate('login', {nextScreen: route});
|
||||
}
|
||||
else this.navigation.navigate('login', {nextScreen: route});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -348,9 +348,8 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getAmicaleServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null) {
|
||||
if (excludedItems != null)
|
||||
return getStrippedServicesList(excludedItems, this.amicaleDataset);
|
||||
}
|
||||
return this.amicaleDataset;
|
||||
}
|
||||
|
||||
|
|
@ -361,9 +360,8 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getStudentServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null) {
|
||||
if (excludedItems != null)
|
||||
return getStrippedServicesList(excludedItems, this.studentsDataset);
|
||||
}
|
||||
return this.studentsDataset;
|
||||
}
|
||||
|
||||
|
|
@ -374,9 +372,8 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getINSAServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null) {
|
||||
if (excludedItems != null)
|
||||
return getStrippedServicesList(excludedItems, this.insaDataset);
|
||||
}
|
||||
return this.insaDataset;
|
||||
}
|
||||
|
||||
|
|
@ -387,9 +384,8 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getSpecialServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null) {
|
||||
if (excludedItems != null)
|
||||
return getStrippedServicesList(excludedItems, this.specialDataset);
|
||||
}
|
||||
return this.specialDataset;
|
||||
}
|
||||
|
||||
|
|
@ -400,9 +396,8 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceCategoryType>}
|
||||
*/
|
||||
getCategories(excludedItems?: Array<string>): Array<ServiceCategoryType> {
|
||||
if (excludedItems != null) {
|
||||
if (excludedItems != null)
|
||||
return getStrippedServicesList(excludedItems, this.categoriesDataset);
|
||||
}
|
||||
return this.categoriesDataset;
|
||||
}
|
||||
}
|
||||
307
src/managers/ThemeManager.js
Normal file
307
src/managers/ThemeManager.js
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import {DarkTheme, DefaultTheme} from 'react-native-paper';
|
||||
import {Appearance} from 'react-native-appearance';
|
||||
import AsyncStorageManager from './AsyncStorageManager';
|
||||
import AprilFoolsManager from './AprilFoolsManager';
|
||||
|
||||
const colorScheme = Appearance.getColorScheme();
|
||||
|
||||
export type CustomThemeType = {
|
||||
...DefaultTheme,
|
||||
colors: {
|
||||
primary: string,
|
||||
accent: string,
|
||||
tabIcon: string,
|
||||
card: string,
|
||||
dividerBackground: string,
|
||||
ripple: string,
|
||||
textDisabled: string,
|
||||
icon: string,
|
||||
subtitle: string,
|
||||
success: string,
|
||||
warning: string,
|
||||
danger: string,
|
||||
|
||||
// Calendar/Agenda
|
||||
agendaBackgroundColor: string,
|
||||
agendaDayTextColor: string,
|
||||
|
||||
// PROXIWASH
|
||||
proxiwashFinishedColor: string,
|
||||
proxiwashReadyColor: string,
|
||||
proxiwashRunningColor: string,
|
||||
proxiwashRunningNotStartedColor: string,
|
||||
proxiwashRunningBgColor: string,
|
||||
proxiwashBrokenColor: string,
|
||||
proxiwashErrorColor: string,
|
||||
proxiwashUnknownColor: string,
|
||||
|
||||
// Screens
|
||||
planningColor: string,
|
||||
proximoColor: string,
|
||||
proxiwashColor: string,
|
||||
menuColor: string,
|
||||
tutorinsaColor: string,
|
||||
|
||||
// Tetris
|
||||
tetrisBackground: string,
|
||||
tetrisBorder: string,
|
||||
tetrisScore: string,
|
||||
tetrisI: string,
|
||||
tetrisO: string,
|
||||
tetrisT: string,
|
||||
tetrisS: string,
|
||||
tetrisZ: string,
|
||||
tetrisJ: string,
|
||||
tetrisL: string,
|
||||
|
||||
gameGold: string,
|
||||
gameSilver: string,
|
||||
gameBronze: string,
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: string,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton class used to manage themes
|
||||
*/
|
||||
export default class ThemeManager {
|
||||
static instance: ThemeManager | null = null;
|
||||
|
||||
updateThemeCallback: null | (() => void);
|
||||
|
||||
constructor() {
|
||||
this.updateThemeCallback = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the light theme
|
||||
*
|
||||
* @return {CustomThemeType} Object containing theme variables
|
||||
* */
|
||||
static getWhiteTheme(): CustomThemeType {
|
||||
return {
|
||||
...DefaultTheme,
|
||||
colors: {
|
||||
...DefaultTheme.colors,
|
||||
primary: '#be1522',
|
||||
accent: '#be1522',
|
||||
tabIcon: '#929292',
|
||||
card: '#fff',
|
||||
dividerBackground: '#e2e2e2',
|
||||
ripple: 'rgba(0,0,0,0.2)',
|
||||
textDisabled: '#c1c1c1',
|
||||
icon: '#5d5d5d',
|
||||
subtitle: '#707070',
|
||||
success: '#5cb85c',
|
||||
warning: '#f0ad4e',
|
||||
danger: '#d9534f',
|
||||
cc: 'dst',
|
||||
|
||||
// Calendar/Agenda
|
||||
agendaBackgroundColor: '#f3f3f4',
|
||||
agendaDayTextColor: '#636363',
|
||||
|
||||
// PROXIWASH
|
||||
proxiwashFinishedColor: '#a5dc9d',
|
||||
proxiwashReadyColor: 'transparent',
|
||||
proxiwashRunningColor: '#a0ceff',
|
||||
proxiwashRunningNotStartedColor: '#c9e0ff',
|
||||
proxiwashRunningBgColor: '#c7e3ff',
|
||||
proxiwashBrokenColor: '#ffa8a2',
|
||||
proxiwashErrorColor: '#ffa8a2',
|
||||
proxiwashUnknownColor: '#b6b6b6',
|
||||
|
||||
// Screens
|
||||
planningColor: '#d9b10a',
|
||||
proximoColor: '#ec5904',
|
||||
proxiwashColor: '#1fa5ee',
|
||||
menuColor: '#e91314',
|
||||
tutorinsaColor: '#f93943',
|
||||
|
||||
// Tetris
|
||||
tetrisBackground: '#f0f0f0',
|
||||
tetrisScore: '#e2bd33',
|
||||
tetrisI: '#3cd9e6',
|
||||
tetrisO: '#ffdd00',
|
||||
tetrisT: '#a716e5',
|
||||
tetrisS: '#09c528',
|
||||
tetrisZ: '#ff0009',
|
||||
tetrisJ: '#2a67e3',
|
||||
tetrisL: '#da742d',
|
||||
|
||||
gameGold: '#ffd610',
|
||||
gameSilver: '#7b7b7b',
|
||||
gameBronze: '#a15218',
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: '#dedede',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dark theme
|
||||
*
|
||||
* @return {CustomThemeType} Object containing theme variables
|
||||
* */
|
||||
static getDarkTheme(): CustomThemeType {
|
||||
return {
|
||||
...DarkTheme,
|
||||
colors: {
|
||||
...DarkTheme.colors,
|
||||
primary: '#be1522',
|
||||
accent: '#be1522',
|
||||
tabBackground: '#181818',
|
||||
tabIcon: '#6d6d6d',
|
||||
card: 'rgb(18,18,18)',
|
||||
dividerBackground: '#222222',
|
||||
ripple: 'rgba(255,255,255,0.2)',
|
||||
textDisabled: '#5b5b5b',
|
||||
icon: '#b3b3b3',
|
||||
subtitle: '#aaaaaa',
|
||||
success: '#5cb85c',
|
||||
warning: '#f0ad4e',
|
||||
danger: '#d9534f',
|
||||
|
||||
// Calendar/Agenda
|
||||
agendaBackgroundColor: '#171717',
|
||||
agendaDayTextColor: '#6d6d6d',
|
||||
|
||||
// PROXIWASH
|
||||
proxiwashFinishedColor: '#31682c',
|
||||
proxiwashReadyColor: 'transparent',
|
||||
proxiwashRunningColor: '#213c79',
|
||||
proxiwashRunningNotStartedColor: '#1e263e',
|
||||
proxiwashRunningBgColor: '#1a2033',
|
||||
proxiwashBrokenColor: '#7e2e2f',
|
||||
proxiwashErrorColor: '#7e2e2f',
|
||||
proxiwashUnknownColor: '#535353',
|
||||
|
||||
// Screens
|
||||
planningColor: '#d99e09',
|
||||
proximoColor: '#ec5904',
|
||||
proxiwashColor: '#1fa5ee',
|
||||
menuColor: '#b81213',
|
||||
tutorinsaColor: '#f93943',
|
||||
|
||||
// Tetris
|
||||
tetrisBackground: '#181818',
|
||||
tetrisScore: '#e2d707',
|
||||
tetrisI: '#30b3be',
|
||||
tetrisO: '#c1a700',
|
||||
tetrisT: '#9114c7',
|
||||
tetrisS: '#08a121',
|
||||
tetrisZ: '#b50008',
|
||||
tetrisJ: '#0f37b9',
|
||||
tetrisL: '#b96226',
|
||||
|
||||
gameGold: '#ffd610',
|
||||
gameSilver: '#7b7b7b',
|
||||
gameBronze: '#a15218',
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: '#323232',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this class instance or create one if none is found
|
||||
*
|
||||
* @returns {ThemeManager}
|
||||
*/
|
||||
static getInstance(): ThemeManager {
|
||||
if (ThemeManager.instance == null)
|
||||
ThemeManager.instance = new ThemeManager();
|
||||
return ThemeManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets night mode status.
|
||||
* If Follow System Preferences is enabled, will first use system theme.
|
||||
* If disabled or not available, will use value stored din preferences
|
||||
*
|
||||
* @returns {boolean} Night mode state
|
||||
*/
|
||||
static getNightMode(): boolean {
|
||||
return (
|
||||
(AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightMode.key,
|
||||
) &&
|
||||
(!AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
|
||||
) ||
|
||||
colorScheme === 'no-preference')) ||
|
||||
(AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
|
||||
) &&
|
||||
colorScheme === 'dark')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current theme based on night mode and events
|
||||
*
|
||||
* @returns {CustomThemeType} The current theme
|
||||
*/
|
||||
static getCurrentTheme(): CustomThemeType {
|
||||
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled())
|
||||
return AprilFoolsManager.getAprilFoolsTheme(ThemeManager.getWhiteTheme());
|
||||
return ThemeManager.getBaseTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the theme based on night mode
|
||||
*
|
||||
* @return {CustomThemeType} The theme
|
||||
*/
|
||||
static getBaseTheme(): CustomThemeType {
|
||||
if (ThemeManager.getNightMode()) return ThemeManager.getDarkTheme();
|
||||
return ThemeManager.getWhiteTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the function to be called when the theme is changed (allows for general reload of the app)
|
||||
*
|
||||
* @param callback Function to call after theme change
|
||||
*/
|
||||
setUpdateThemeCallback(callback: () => void) {
|
||||
this.updateThemeCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set night mode and save it to preferences
|
||||
*
|
||||
* @param isNightMode True to enable night mode, false to disable
|
||||
*/
|
||||
setNightMode(isNightMode: boolean) {
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.nightMode.key,
|
||||
isNightMode,
|
||||
);
|
||||
if (this.updateThemeCallback != null) this.updateThemeCallback();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,298 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {DarkTheme, DefaultTheme} from 'react-native-paper';
|
||||
import {Appearance} from 'react-native-appearance';
|
||||
import AsyncStorageManager from './AsyncStorageManager';
|
||||
import AprilFoolsManager from './AprilFoolsManager';
|
||||
|
||||
const colorScheme = Appearance.getColorScheme();
|
||||
|
||||
declare global {
|
||||
namespace ReactNativePaper {
|
||||
interface ThemeColors {
|
||||
primary: string;
|
||||
accent: string;
|
||||
border: string;
|
||||
tabIcon: string;
|
||||
card: string;
|
||||
dividerBackground: string;
|
||||
ripple: string;
|
||||
textDisabled: string;
|
||||
icon: string;
|
||||
subtitle: string;
|
||||
success: string;
|
||||
warning: string;
|
||||
danger: string;
|
||||
|
||||
// Calendar/Agenda
|
||||
agendaBackgroundColor: string;
|
||||
agendaDayTextColor: string;
|
||||
|
||||
// PROXIWASH
|
||||
proxiwashFinishedColor: string;
|
||||
proxiwashReadyColor: string;
|
||||
proxiwashRunningColor: string;
|
||||
proxiwashRunningNotStartedColor: string;
|
||||
proxiwashRunningBgColor: string;
|
||||
proxiwashBrokenColor: string;
|
||||
proxiwashErrorColor: string;
|
||||
proxiwashUnknownColor: string;
|
||||
|
||||
// Screens
|
||||
planningColor: string;
|
||||
proximoColor: string;
|
||||
proxiwashColor: string;
|
||||
menuColor: string;
|
||||
tutorinsaColor: string;
|
||||
|
||||
// Tetris
|
||||
tetrisBackground: string;
|
||||
tetrisScore: string;
|
||||
tetrisI: string;
|
||||
tetrisO: string;
|
||||
tetrisT: string;
|
||||
tetrisS: string;
|
||||
tetrisZ: string;
|
||||
tetrisJ: string;
|
||||
tetrisL: string;
|
||||
|
||||
gameGold: string;
|
||||
gameSilver: string;
|
||||
gameBronze: string;
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CustomWhiteTheme: ReactNativePaper.Theme = {
|
||||
...DefaultTheme,
|
||||
colors: {
|
||||
...DefaultTheme.colors,
|
||||
primary: '#be1522',
|
||||
accent: '#be1522',
|
||||
border: '#e2e2e2',
|
||||
tabIcon: '#929292',
|
||||
card: '#fff',
|
||||
dividerBackground: '#e2e2e2',
|
||||
ripple: 'rgba(0,0,0,0.2)',
|
||||
textDisabled: '#c1c1c1',
|
||||
icon: '#5d5d5d',
|
||||
subtitle: '#707070',
|
||||
success: '#5cb85c',
|
||||
warning: '#f0ad4e',
|
||||
danger: '#d9534f',
|
||||
|
||||
// Calendar/Agenda
|
||||
agendaBackgroundColor: '#f3f3f4',
|
||||
agendaDayTextColor: '#636363',
|
||||
|
||||
// PROXIWASH
|
||||
proxiwashFinishedColor: '#a5dc9d',
|
||||
proxiwashReadyColor: 'transparent',
|
||||
proxiwashRunningColor: '#a0ceff',
|
||||
proxiwashRunningNotStartedColor: '#c9e0ff',
|
||||
proxiwashRunningBgColor: '#c7e3ff',
|
||||
proxiwashBrokenColor: '#ffa8a2',
|
||||
proxiwashErrorColor: '#ffa8a2',
|
||||
proxiwashUnknownColor: '#b6b6b6',
|
||||
|
||||
// Screens
|
||||
planningColor: '#d9b10a',
|
||||
proximoColor: '#ec5904',
|
||||
proxiwashColor: '#1fa5ee',
|
||||
menuColor: '#e91314',
|
||||
tutorinsaColor: '#f93943',
|
||||
|
||||
// Tetris
|
||||
tetrisBackground: '#f0f0f0',
|
||||
tetrisScore: '#e2bd33',
|
||||
tetrisI: '#3cd9e6',
|
||||
tetrisO: '#ffdd00',
|
||||
tetrisT: '#a716e5',
|
||||
tetrisS: '#09c528',
|
||||
tetrisZ: '#ff0009',
|
||||
tetrisJ: '#2a67e3',
|
||||
tetrisL: '#da742d',
|
||||
|
||||
gameGold: '#ffd610',
|
||||
gameSilver: '#7b7b7b',
|
||||
gameBronze: '#a15218',
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: '#dedede',
|
||||
},
|
||||
};
|
||||
|
||||
const CustomDarkTheme: ReactNativePaper.Theme = {
|
||||
...DarkTheme,
|
||||
colors: {
|
||||
...DarkTheme.colors,
|
||||
primary: '#be1522',
|
||||
accent: '#be1522',
|
||||
border: '#222222',
|
||||
tabIcon: '#6d6d6d',
|
||||
card: 'rgb(18,18,18)',
|
||||
dividerBackground: '#222222',
|
||||
ripple: 'rgba(255,255,255,0.2)',
|
||||
textDisabled: '#5b5b5b',
|
||||
icon: '#b3b3b3',
|
||||
subtitle: '#aaaaaa',
|
||||
success: '#5cb85c',
|
||||
warning: '#f0ad4e',
|
||||
danger: '#d9534f',
|
||||
|
||||
// Calendar/Agenda
|
||||
agendaBackgroundColor: '#171717',
|
||||
agendaDayTextColor: '#6d6d6d',
|
||||
|
||||
// PROXIWASH
|
||||
proxiwashFinishedColor: '#31682c',
|
||||
proxiwashReadyColor: 'transparent',
|
||||
proxiwashRunningColor: '#213c79',
|
||||
proxiwashRunningNotStartedColor: '#1e263e',
|
||||
proxiwashRunningBgColor: '#1a2033',
|
||||
proxiwashBrokenColor: '#7e2e2f',
|
||||
proxiwashErrorColor: '#7e2e2f',
|
||||
proxiwashUnknownColor: '#535353',
|
||||
|
||||
// Screens
|
||||
planningColor: '#d99e09',
|
||||
proximoColor: '#ec5904',
|
||||
proxiwashColor: '#1fa5ee',
|
||||
menuColor: '#b81213',
|
||||
tutorinsaColor: '#f93943',
|
||||
|
||||
// Tetris
|
||||
tetrisBackground: '#181818',
|
||||
tetrisScore: '#e2d707',
|
||||
tetrisI: '#30b3be',
|
||||
tetrisO: '#c1a700',
|
||||
tetrisT: '#9114c7',
|
||||
tetrisS: '#08a121',
|
||||
tetrisZ: '#b50008',
|
||||
tetrisJ: '#0f37b9',
|
||||
tetrisL: '#b96226',
|
||||
|
||||
gameGold: '#ffd610',
|
||||
gameSilver: '#7b7b7b',
|
||||
gameBronze: '#a15218',
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: '#323232',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton class used to manage themes
|
||||
*/
|
||||
export default class ThemeManager {
|
||||
static instance: ThemeManager | null = null;
|
||||
|
||||
updateThemeCallback: null | (() => void);
|
||||
|
||||
constructor() {
|
||||
this.updateThemeCallback = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this class instance or create one if none is found
|
||||
*
|
||||
* @returns {ThemeManager}
|
||||
*/
|
||||
static getInstance(): ThemeManager {
|
||||
if (ThemeManager.instance == null) {
|
||||
ThemeManager.instance = new ThemeManager();
|
||||
}
|
||||
return ThemeManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets night mode status.
|
||||
* If Follow System Preferences is enabled, will first use system theme.
|
||||
* If disabled or not available, will use value stored din preferences
|
||||
*
|
||||
* @returns {boolean} Night mode state
|
||||
*/
|
||||
static getNightMode(): boolean {
|
||||
return (
|
||||
(AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightMode.key,
|
||||
) &&
|
||||
(!AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
|
||||
) ||
|
||||
colorScheme === 'no-preference')) ||
|
||||
(AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
|
||||
) &&
|
||||
colorScheme === 'dark')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current theme based on night mode and events
|
||||
*
|
||||
* @returns {ReactNativePaper.Theme} The current theme
|
||||
*/
|
||||
static getCurrentTheme(): ReactNativePaper.Theme {
|
||||
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
|
||||
return AprilFoolsManager.getAprilFoolsTheme(CustomWhiteTheme);
|
||||
}
|
||||
return ThemeManager.getBaseTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the theme based on night mode
|
||||
*
|
||||
* @return {ReactNativePaper.Theme} The theme
|
||||
*/
|
||||
static getBaseTheme(): ReactNativePaper.Theme {
|
||||
if (ThemeManager.getNightMode()) {
|
||||
return CustomDarkTheme;
|
||||
}
|
||||
return CustomWhiteTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the function to be called when the theme is changed (allows for general reload of the app)
|
||||
*
|
||||
* @param callback Function to call after theme change
|
||||
*/
|
||||
setUpdateThemeCallback(callback: () => void) {
|
||||
this.updateThemeCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set night mode and save it to preferences
|
||||
*
|
||||
* @param isNightMode True to enable night mode, false to disable
|
||||
*/
|
||||
setNightMode(isNightMode: boolean) {
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.nightMode.key,
|
||||
isNightMode,
|
||||
);
|
||||
if (this.updateThemeCallback != null) {
|
||||
this.updateThemeCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {createStackNavigator, TransitionPresets} from '@react-navigation/stack';
|
||||
import i18n from 'i18n-js';
|
||||
|
|
@ -38,63 +40,18 @@ import ClubListScreen from '../screens/Amicale/Clubs/ClubListScreen';
|
|||
import ClubAboutScreen from '../screens/Amicale/Clubs/ClubAboutScreen';
|
||||
import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
|
||||
import {
|
||||
CreateScreenCollapsibleStack,
|
||||
createScreenCollapsibleStack,
|
||||
getWebsiteStack,
|
||||
} from '../utils/CollapsibleUtils';
|
||||
import BugReportScreen from '../screens/Other/FeedbackScreen';
|
||||
import WebsiteScreen from '../screens/Services/WebsiteScreen';
|
||||
import EquipmentScreen, {
|
||||
DeviceType,
|
||||
} from '../screens/Amicale/Equipment/EquipmentListScreen';
|
||||
import EquipmentScreen from '../screens/Amicale/Equipment/EquipmentListScreen';
|
||||
import EquipmentLendScreen from '../screens/Amicale/Equipment/EquipmentRentScreen';
|
||||
import EquipmentConfirmScreen from '../screens/Amicale/Equipment/EquipmentConfirmScreen';
|
||||
import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
|
||||
import GameStartScreen from '../screens/Game/screens/GameStartScreen';
|
||||
import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
|
||||
|
||||
export enum MainRoutes {
|
||||
Main = 'main',
|
||||
Gallery = 'gallery',
|
||||
Settings = 'settings',
|
||||
DashboardEdit = 'dashboard-edit',
|
||||
About = 'about',
|
||||
Dependencies = 'dependencies',
|
||||
Debug = 'debug',
|
||||
GameStart = 'game-start',
|
||||
GameMain = 'game-main',
|
||||
Login = 'login',
|
||||
SelfMenu = 'self-menu',
|
||||
Proximo = 'proximo',
|
||||
ProximoList = 'proximo-list',
|
||||
ProximoAbout = 'proximo-about',
|
||||
Profile = 'profile',
|
||||
ClubList = 'club-list',
|
||||
ClubInformation = 'club-information',
|
||||
ClubAbout = 'club-about',
|
||||
EquipmentList = 'equipment-list',
|
||||
EquipmentRent = 'equipment-rent',
|
||||
EquipmentConfirm = 'equipment-confirm',
|
||||
Vote = 'vote',
|
||||
Feedback = 'feedback',
|
||||
}
|
||||
|
||||
type DefaultParams = {[key in MainRoutes]: object | undefined};
|
||||
|
||||
export interface FullParamsList extends DefaultParams {
|
||||
login: {nextScreen: string};
|
||||
'equipment-confirm': {
|
||||
item?: DeviceType;
|
||||
dates: [string, string];
|
||||
};
|
||||
'equipment-rent': {item?: DeviceType};
|
||||
gallery: {images: Array<{url: string}>};
|
||||
}
|
||||
|
||||
// Don't know why but TS is complaining without this
|
||||
// See: https://stackoverflow.com/questions/63652687/interface-does-not-satisfy-the-constraint-recordstring-object-undefined
|
||||
export type MainStackParamsList = FullParamsList &
|
||||
Record<string, object | undefined>;
|
||||
|
||||
const modalTransition =
|
||||
Platform.OS === 'ios'
|
||||
? TransitionPresets.ModalPresentationIOS
|
||||
|
|
@ -106,17 +63,19 @@ const defaultScreenOptions = {
|
|||
...TransitionPresets.SlideFromRightIOS,
|
||||
};
|
||||
|
||||
const MainStack = createStackNavigator<MainStackParamsList>();
|
||||
const MainStack = createStackNavigator();
|
||||
|
||||
function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
|
||||
function MainStackComponent(props: {
|
||||
createTabNavigator: () => React.Node,
|
||||
}): React.Node {
|
||||
const {createTabNavigator} = props;
|
||||
return (
|
||||
<MainStack.Navigator
|
||||
initialRouteName={MainRoutes.Main}
|
||||
initialRouteName="main"
|
||||
headerMode="screen"
|
||||
screenOptions={defaultScreenOptions}>
|
||||
<MainStack.Screen
|
||||
name={MainRoutes.Main}
|
||||
name="main"
|
||||
component={createTabNavigator}
|
||||
options={{
|
||||
headerShown: false,
|
||||
|
|
@ -124,62 +83,62 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
|
|||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name={MainRoutes.Gallery}
|
||||
name="gallery"
|
||||
component={ImageGalleryScreen}
|
||||
options={{
|
||||
headerShown: false,
|
||||
...modalTransition,
|
||||
}}
|
||||
/>
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Settings,
|
||||
{createScreenCollapsibleStack(
|
||||
'settings',
|
||||
MainStack,
|
||||
SettingsScreen,
|
||||
i18n.t('screens.settings.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.DashboardEdit,
|
||||
{createScreenCollapsibleStack(
|
||||
'dashboard-edit',
|
||||
MainStack,
|
||||
DashboardEditScreen,
|
||||
i18n.t('screens.settings.dashboardEdit.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.About,
|
||||
{createScreenCollapsibleStack(
|
||||
'about',
|
||||
MainStack,
|
||||
AboutScreen,
|
||||
i18n.t('screens.about.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Dependencies,
|
||||
{createScreenCollapsibleStack(
|
||||
'dependencies',
|
||||
MainStack,
|
||||
AboutDependenciesScreen,
|
||||
i18n.t('screens.about.libs'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Debug,
|
||||
{createScreenCollapsibleStack(
|
||||
'debug',
|
||||
MainStack,
|
||||
DebugScreen,
|
||||
i18n.t('screens.about.debug'),
|
||||
)}
|
||||
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.GameStart,
|
||||
{createScreenCollapsibleStack(
|
||||
'game-start',
|
||||
MainStack,
|
||||
GameStartScreen,
|
||||
i18n.t('screens.game.title'),
|
||||
true,
|
||||
undefined,
|
||||
null,
|
||||
'transparent',
|
||||
)}
|
||||
<MainStack.Screen
|
||||
name={MainRoutes.GameMain}
|
||||
name="game-main"
|
||||
component={GameMainScreen}
|
||||
options={{
|
||||
title: i18n.t('screens.game.title'),
|
||||
}}
|
||||
/>
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Login,
|
||||
{createScreenCollapsibleStack(
|
||||
'login',
|
||||
MainStack,
|
||||
LoginScreen,
|
||||
i18n.t('screens.login.title'),
|
||||
|
|
@ -189,26 +148,26 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
|
|||
)}
|
||||
{getWebsiteStack('website', MainStack, WebsiteScreen, '')}
|
||||
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.SelfMenu,
|
||||
{createScreenCollapsibleStack(
|
||||
'self-menu',
|
||||
MainStack,
|
||||
SelfMenuScreen,
|
||||
i18n.t('screens.menu.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Proximo,
|
||||
{createScreenCollapsibleStack(
|
||||
'proximo',
|
||||
MainStack,
|
||||
ProximoMainScreen,
|
||||
i18n.t('screens.proximo.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ProximoList,
|
||||
{createScreenCollapsibleStack(
|
||||
'proximo-list',
|
||||
MainStack,
|
||||
ProximoListScreen,
|
||||
i18n.t('screens.proximo.articleList'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ProximoAbout,
|
||||
{createScreenCollapsibleStack(
|
||||
'proximo-about',
|
||||
MainStack,
|
||||
ProximoAboutScreen,
|
||||
i18n.t('screens.proximo.title'),
|
||||
|
|
@ -216,60 +175,60 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
|
|||
{...modalTransition},
|
||||
)}
|
||||
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Profile,
|
||||
{createScreenCollapsibleStack(
|
||||
'profile',
|
||||
MainStack,
|
||||
ProfileScreen,
|
||||
i18n.t('screens.profile.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ClubList,
|
||||
{createScreenCollapsibleStack(
|
||||
'club-list',
|
||||
MainStack,
|
||||
ClubListScreen,
|
||||
i18n.t('screens.clubs.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ClubInformation,
|
||||
{createScreenCollapsibleStack(
|
||||
'club-information',
|
||||
MainStack,
|
||||
ClubDisplayScreen,
|
||||
i18n.t('screens.clubs.details'),
|
||||
true,
|
||||
{...modalTransition},
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ClubAbout,
|
||||
{createScreenCollapsibleStack(
|
||||
'club-about',
|
||||
MainStack,
|
||||
ClubAboutScreen,
|
||||
i18n.t('screens.clubs.title'),
|
||||
true,
|
||||
{...modalTransition},
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.EquipmentList,
|
||||
{createScreenCollapsibleStack(
|
||||
'equipment-list',
|
||||
MainStack,
|
||||
EquipmentScreen,
|
||||
i18n.t('screens.equipment.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.EquipmentRent,
|
||||
{createScreenCollapsibleStack(
|
||||
'equipment-rent',
|
||||
MainStack,
|
||||
EquipmentLendScreen,
|
||||
i18n.t('screens.equipment.book'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.EquipmentConfirm,
|
||||
{createScreenCollapsibleStack(
|
||||
'equipment-confirm',
|
||||
MainStack,
|
||||
EquipmentConfirmScreen,
|
||||
i18n.t('screens.equipment.confirm'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Vote,
|
||||
{createScreenCollapsibleStack(
|
||||
'vote',
|
||||
MainStack,
|
||||
VoteScreen,
|
||||
i18n.t('screens.vote.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Feedback,
|
||||
{createScreenCollapsibleStack(
|
||||
'feedback',
|
||||
MainStack,
|
||||
BugReportScreen,
|
||||
i18n.t('screens.feedback.title'),
|
||||
|
|
@ -279,14 +238,25 @@ function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
|
|||
}
|
||||
|
||||
type PropsType = {
|
||||
defaultHomeRoute: string | null;
|
||||
defaultHomeData: {[key: string]: string};
|
||||
defaultHomeRoute: string | null,
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
defaultHomeData: {[key: string]: string},
|
||||
};
|
||||
|
||||
export default function MainNavigator(props: PropsType) {
|
||||
return (
|
||||
<MainStackComponent
|
||||
createTabNavigator={() => <TabNavigator {...props} />}
|
||||
/>
|
||||
);
|
||||
export default class MainNavigator extends React.Component<PropsType> {
|
||||
createTabNavigator: () => React.Node;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.createTabNavigator = (): React.Node => (
|
||||
<TabNavigator
|
||||
defaultHomeRoute={props.defaultHomeRoute}
|
||||
defaultHomeData={props.defaultHomeData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
return <MainStackComponent createTabNavigator={this.createTabNavigator} />;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {createStackNavigator, TransitionPresets} from '@react-navigation/stack';
|
||||
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
|
||||
|
|
@ -42,7 +44,7 @@ import WebsitesHomeScreen from '../screens/Services/ServicesScreen';
|
|||
import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
|
||||
import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
|
||||
import {
|
||||
CreateScreenCollapsibleStack,
|
||||
createScreenCollapsibleStack,
|
||||
getWebsiteStack,
|
||||
} from '../utils/CollapsibleUtils';
|
||||
import Mascot, {MASCOT_STYLE} from '../components/Mascot/Mascot';
|
||||
|
|
@ -60,25 +62,25 @@ const defaultScreenOptions = {
|
|||
|
||||
const ServicesStack = createStackNavigator();
|
||||
|
||||
function ServicesStackComponent() {
|
||||
function ServicesStackComponent(): React.Node {
|
||||
return (
|
||||
<ServicesStack.Navigator
|
||||
initialRouteName="index"
|
||||
headerMode="screen"
|
||||
screenOptions={defaultScreenOptions}>
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'index',
|
||||
ServicesStack,
|
||||
WebsitesHomeScreen,
|
||||
i18n.t('screens.services.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'services-section',
|
||||
ServicesStack,
|
||||
ServicesSectionScreen,
|
||||
'SECTION',
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'amicale-contact',
|
||||
ServicesStack,
|
||||
AmicaleContactScreen,
|
||||
|
|
@ -90,19 +92,19 @@ function ServicesStackComponent() {
|
|||
|
||||
const ProxiwashStack = createStackNavigator();
|
||||
|
||||
function ProxiwashStackComponent() {
|
||||
function ProxiwashStackComponent(): React.Node {
|
||||
return (
|
||||
<ProxiwashStack.Navigator
|
||||
initialRouteName="index"
|
||||
headerMode="screen"
|
||||
screenOptions={defaultScreenOptions}>
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'index',
|
||||
ProxiwashStack,
|
||||
ProxiwashScreen,
|
||||
i18n.t('screens.proxiwash.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'proxiwash-about',
|
||||
ProxiwashStack,
|
||||
ProxiwashAboutScreen,
|
||||
|
|
@ -114,7 +116,7 @@ function ProxiwashStackComponent() {
|
|||
|
||||
const PlanningStack = createStackNavigator();
|
||||
|
||||
function PlanningStackComponent() {
|
||||
function PlanningStackComponent(): React.Node {
|
||||
return (
|
||||
<PlanningStack.Navigator
|
||||
initialRouteName="index"
|
||||
|
|
@ -125,7 +127,7 @@ function PlanningStackComponent() {
|
|||
component={PlanningScreen}
|
||||
options={{title: i18n.t('screens.planning.title')}}
|
||||
/>
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'planning-information',
|
||||
PlanningStack,
|
||||
PlanningDisplayScreen,
|
||||
|
|
@ -140,11 +142,10 @@ const HomeStack = createStackNavigator();
|
|||
function HomeStackComponent(
|
||||
initialRoute: string | null,
|
||||
defaultData: {[key: string]: string},
|
||||
) {
|
||||
): React.Node {
|
||||
let params;
|
||||
if (initialRoute) {
|
||||
if (initialRoute != null)
|
||||
params = {data: defaultData, nextScreen: initialRoute, shouldOpen: true};
|
||||
}
|
||||
const {colors} = useTheme();
|
||||
return (
|
||||
<HomeStack.Navigator
|
||||
|
|
@ -160,7 +161,7 @@ function HomeStackComponent(
|
|||
headerStyle: {
|
||||
backgroundColor: colors.surface,
|
||||
},
|
||||
headerTitle: () => (
|
||||
headerTitle: (): React.Node => (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Mascot
|
||||
style={{
|
||||
|
|
@ -202,19 +203,19 @@ function HomeStackComponent(
|
|||
options={{title: i18n.t('screens.scanner.title')}}
|
||||
/>
|
||||
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'club-information',
|
||||
HomeStack,
|
||||
ClubDisplayScreen,
|
||||
i18n.t('screens.clubs.details'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'feed-information',
|
||||
HomeStack,
|
||||
FeedItemScreen,
|
||||
i18n.t('screens.home.feed'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'planning-information',
|
||||
HomeStack,
|
||||
PlanningDisplayScreen,
|
||||
|
|
@ -226,7 +227,7 @@ function HomeStackComponent(
|
|||
|
||||
const PlanexStack = createStackNavigator();
|
||||
|
||||
function PlanexStackComponent() {
|
||||
function PlanexStackComponent(): React.Node {
|
||||
return (
|
||||
<PlanexStack.Navigator
|
||||
initialRouteName="index"
|
||||
|
|
@ -238,7 +239,7 @@ function PlanexStackComponent() {
|
|||
PlanexScreen,
|
||||
i18n.t('screens.planex.title'),
|
||||
)}
|
||||
{CreateScreenCollapsibleStack(
|
||||
{createScreenCollapsibleStack(
|
||||
'group-select',
|
||||
PlanexStack,
|
||||
GroupSelectionScreen,
|
||||
|
|
@ -251,33 +252,35 @@ function PlanexStackComponent() {
|
|||
const Tab = createBottomTabNavigator();
|
||||
|
||||
type PropsType = {
|
||||
defaultHomeRoute: string | null;
|
||||
defaultHomeData: {[key: string]: string};
|
||||
defaultHomeRoute: string | null,
|
||||
defaultHomeData: {[key: string]: string},
|
||||
};
|
||||
|
||||
export default class TabNavigator extends React.Component<PropsType> {
|
||||
createHomeStackComponent: () => React.Node;
|
||||
|
||||
defaultRoute: string;
|
||||
createHomeStackComponent: () => any;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.defaultRoute = 'home';
|
||||
if (!props.defaultHomeRoute) {
|
||||
if (props.defaultHomeRoute != null) this.defaultRoute = 'home';
|
||||
else
|
||||
this.defaultRoute = AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.defaultStartScreen.key,
|
||||
).toLowerCase();
|
||||
}
|
||||
this.createHomeStackComponent = () =>
|
||||
this.createHomeStackComponent = (): React.Node =>
|
||||
HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={this.defaultRoute}
|
||||
tabBar={(tabProps) => <CustomTabBar {...tabProps} />}>
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
tabBar={(props: {...}): React.Node => <CustomTabBar {...props} />}>
|
||||
<Tab.Screen
|
||||
name="services"
|
||||
option
|
||||
component={ServicesStackComponent}
|
||||
options={{title: i18n.t('screens.services.title')}}
|
||||
/>
|
||||
|
|
@ -296,6 +299,7 @@ export default class TabNavigator extends React.Component<PropsType> {
|
|||
component={PlanningStackComponent}
|
||||
options={{title: i18n.t('screens.planning.title')}}
|
||||
/>
|
||||
|
||||
<Tab.Screen
|
||||
name="planex"
|
||||
component={PlanexStackComponent}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {List} from 'react-native-paper';
|
||||
import {View} from 'react-native-animatable';
|
||||
|
|
@ -24,8 +26,8 @@ import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatLis
|
|||
import packageJson from '../../../package.json';
|
||||
|
||||
type ListItemType = {
|
||||
name: string;
|
||||
version: string;
|
||||
name: string,
|
||||
version: string,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -35,9 +37,9 @@ type ListItemType = {
|
|||
* @return {Array<ListItemType>}
|
||||
*/
|
||||
function generateListFromObject(object: {
|
||||
[key: string]: string;
|
||||
[key: string]: string,
|
||||
}): Array<ListItemType> {
|
||||
const list: Array<ListItemType> = [];
|
||||
const list = [];
|
||||
const keys = Object.keys(object);
|
||||
keys.forEach((key: string) => {
|
||||
list.push({name: key, version: object[key]});
|
||||
|
|
@ -50,17 +52,17 @@ const LIST_ITEM_HEIGHT = 64;
|
|||
/**
|
||||
* Class defining a screen showing the list of libraries used by the app, taken from package.json
|
||||
*/
|
||||
export default class AboutDependenciesScreen extends React.Component<{}> {
|
||||
export default class AboutDependenciesScreen extends React.Component<null> {
|
||||
data: Array<ListItemType>;
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
constructor() {
|
||||
super();
|
||||
this.data = generateListFromObject(packageJson.dependencies);
|
||||
}
|
||||
|
||||
keyExtractor = (item: ListItemType): string => item.name;
|
||||
|
||||
getRenderItem = ({item}: {item: ListItemType}) => (
|
||||
getRenderItem = ({item}: {item: ListItemType}): React.Node => (
|
||||
<List.Item
|
||||
title={item.name}
|
||||
description={item.version.replace('^', '').replace('~', '')}
|
||||
|
|
@ -69,15 +71,15 @@ export default class AboutDependenciesScreen extends React.Component<{}> {
|
|||
);
|
||||
|
||||
getItemLayout = (
|
||||
data: Array<ListItemType> | null | undefined,
|
||||
data: ListItemType,
|
||||
index: number,
|
||||
): {length: number; offset: number; index: number} => ({
|
||||
): {length: number, offset: number, index: number} => ({
|
||||
length: LIST_ITEM_HEIGHT,
|
||||
offset: LIST_ITEM_HEIGHT * index,
|
||||
index,
|
||||
});
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
return (
|
||||
<View>
|
||||
<CollapsibleFlatList
|
||||
|
|
@ -17,32 +17,37 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {FlatList, Linking, Platform, Image, View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import {Avatar, Card, List} from 'react-native-paper';
|
||||
import {Avatar, Card, List, withTheme} from 'react-native-paper';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import packageJson from '../../../package.json';
|
||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
||||
import APP_LOGO from '../../../assets/android.icon.round.png';
|
||||
import type {
|
||||
CardTitleIconPropsType,
|
||||
ListIconPropsType,
|
||||
} from '../../constants/PaperStyles';
|
||||
import OptionsDialog from '../../components/Dialogs/OptionsDialog';
|
||||
import type {OptionsDialogButtonType} from '../../components/Dialogs/OptionsDialog';
|
||||
|
||||
const APP_LOGO = require('../../../assets/android.icon.round.png');
|
||||
|
||||
type ListItemType = {
|
||||
onPressCallback: () => void;
|
||||
icon: string;
|
||||
text: string;
|
||||
showChevron: boolean;
|
||||
onPressCallback: () => void,
|
||||
icon: string,
|
||||
text: string,
|
||||
showChevron: boolean,
|
||||
};
|
||||
|
||||
type MemberItemType = {
|
||||
name: string;
|
||||
message: string;
|
||||
icon: string;
|
||||
trollLink?: string;
|
||||
linkedin?: string;
|
||||
mail?: string;
|
||||
name: string,
|
||||
message: string,
|
||||
icon: string,
|
||||
trollLink?: string,
|
||||
linkedin?: string,
|
||||
mail?: string,
|
||||
};
|
||||
|
||||
const links = {
|
||||
|
|
@ -59,14 +64,14 @@ const links = {
|
|||
};
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
navigation: StackNavigationProp,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
dialogVisible: boolean;
|
||||
dialogTitle: string;
|
||||
dialogMessage: string;
|
||||
dialogButtons: Array<OptionsDialogButtonType>;
|
||||
dialogVisible: boolean,
|
||||
dialogTitle: string,
|
||||
dialogMessage: string,
|
||||
dialogButtons: Array<OptionsDialogButtonType>,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -81,10 +86,11 @@ function openWebLink(link: string) {
|
|||
* Class defining an about screen. This screen shows the user information about the app and it's author.
|
||||
*/
|
||||
class AboutScreen extends React.Component<PropsType, StateType> {
|
||||
|
||||
/**
|
||||
* Object containing data relative to major contributors
|
||||
*/
|
||||
majorContributors: {[key: string]: MemberItemType} = {
|
||||
static majorContributors: {[key: string]: MemberItemType} = {
|
||||
arnaud: {
|
||||
name: 'Arnaud Vergnet',
|
||||
message: i18n.t('screens.about.user.arnaud'),
|
||||
|
|
@ -103,8 +109,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
message: i18n.t('screens.about.user.yohan'),
|
||||
icon: 'xml',
|
||||
linkedin: 'https://www.linkedin.com/in/yohan-simard',
|
||||
mail:
|
||||
'mailto:ysimard@etud.insa-toulouse.fr?' +
|
||||
mail: 'mailto:ysimard@etud.insa-toulouse.fr?' +
|
||||
'subject=' +
|
||||
'Application Amicale INSA Toulouse' +
|
||||
'&body=' +
|
||||
|
|
@ -115,7 +120,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
/**
|
||||
* Object containing data relative to users who helped during development
|
||||
*/
|
||||
helpfulUsers: {[key: string]: MemberItemType} = {
|
||||
static helpfulUsers: {[key: string]: MemberItemType} = {
|
||||
beranger: {
|
||||
name: 'Béranger Quintana Y Arciosana',
|
||||
message: i18n.t('screens.about.user.beranger'),
|
||||
|
|
@ -199,18 +204,18 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
teamData: Array<ListItemType> = [
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(this.majorContributors.arnaud);
|
||||
this.onContributorListItemPress(AboutScreen.majorContributors.arnaud);
|
||||
},
|
||||
icon: this.majorContributors.arnaud.icon,
|
||||
text: this.majorContributors.arnaud.name,
|
||||
icon: AboutScreen.majorContributors.arnaud.icon,
|
||||
text: AboutScreen.majorContributors.arnaud.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(this.majorContributors.yohan);
|
||||
this.onContributorListItemPress(AboutScreen.majorContributors.yohan);
|
||||
},
|
||||
icon: this.majorContributors.yohan.icon,
|
||||
text: this.majorContributors.yohan.name,
|
||||
icon: AboutScreen.majorContributors.yohan.icon,
|
||||
text: AboutScreen.majorContributors.yohan.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
|
|
@ -230,42 +235,42 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
thanksData: Array<ListItemType> = [
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(this.helpfulUsers.beranger);
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.beranger);
|
||||
},
|
||||
icon: this.helpfulUsers.beranger.icon,
|
||||
text: this.helpfulUsers.beranger.name,
|
||||
icon: AboutScreen.helpfulUsers.beranger.icon,
|
||||
text: AboutScreen.helpfulUsers.beranger.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(this.helpfulUsers.celine);
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.celine);
|
||||
},
|
||||
icon: this.helpfulUsers.celine.icon,
|
||||
text: this.helpfulUsers.celine.name,
|
||||
icon: AboutScreen.helpfulUsers.celine.icon,
|
||||
text: AboutScreen.helpfulUsers.celine.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(this.helpfulUsers.damien);
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.damien);
|
||||
},
|
||||
icon: this.helpfulUsers.damien.icon,
|
||||
text: this.helpfulUsers.damien.name,
|
||||
icon: AboutScreen.helpfulUsers.damien.icon,
|
||||
text: AboutScreen.helpfulUsers.damien.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(this.helpfulUsers.titouan);
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.titouan);
|
||||
},
|
||||
icon: this.helpfulUsers.titouan.icon,
|
||||
text: this.helpfulUsers.titouan.name,
|
||||
icon: AboutScreen.helpfulUsers.titouan.icon,
|
||||
text: AboutScreen.helpfulUsers.titouan.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(this.helpfulUsers.theo);
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.theo);
|
||||
},
|
||||
icon: this.helpfulUsers.theo.icon,
|
||||
text: this.helpfulUsers.theo.name,
|
||||
icon: AboutScreen.helpfulUsers.theo.icon,
|
||||
text: AboutScreen.helpfulUsers.theo.name,
|
||||
showChevron: false,
|
||||
},
|
||||
];
|
||||
|
|
@ -328,7 +333,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
* @param user The member to show information for
|
||||
*/
|
||||
onContributorListItemPress(user: MemberItemType) {
|
||||
const dialogBtn: Array<OptionsDialogButtonType> = [
|
||||
const dialogBtn = [
|
||||
{
|
||||
title: 'OK',
|
||||
onPress: this.onDialogDismiss,
|
||||
|
|
@ -374,14 +379,15 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getAppCard() {
|
||||
getAppCard(): React.Node {
|
||||
return (
|
||||
<Card style={{marginBottom: 10}}>
|
||||
<Card.Title
|
||||
title="Campus"
|
||||
subtitle={packageJson.version}
|
||||
left={(iconProps) => (
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Image
|
||||
size={iconProps.size}
|
||||
source={APP_LOGO}
|
||||
style={{width: iconProps.size, height: iconProps.size}}
|
||||
/>
|
||||
|
|
@ -403,12 +409,12 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getTeamCard() {
|
||||
getTeamCard(): React.Node {
|
||||
return (
|
||||
<Card style={{marginBottom: 10}}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.about.team')}
|
||||
left={(iconProps) => (
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon size={iconProps.size} icon="account-multiple" />
|
||||
)}
|
||||
/>
|
||||
|
|
@ -428,12 +434,12 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getThanksCard() {
|
||||
getThanksCard(): React.Node {
|
||||
return (
|
||||
<Card style={{marginBottom: 10}}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.about.thanks')}
|
||||
left={(iconProps) => (
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon size={iconProps.size} icon="hand-heart" />
|
||||
)}
|
||||
/>
|
||||
|
|
@ -453,12 +459,12 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getTechnoCard() {
|
||||
getTechnoCard(): React.Node {
|
||||
return (
|
||||
<Card style={{marginBottom: 10}}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.about.technologies')}
|
||||
left={(iconProps) => (
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
<Avatar.Icon size={iconProps.size} icon="wrench" />
|
||||
)}
|
||||
/>
|
||||
|
|
@ -479,13 +485,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
* @param props
|
||||
* @return {*}
|
||||
*/
|
||||
static getChevronIcon(props: {
|
||||
color: string;
|
||||
style?: {
|
||||
marginRight: number;
|
||||
marginVertical?: number;
|
||||
};
|
||||
}) {
|
||||
static getChevronIcon(props: ListIconPropsType): React.Node {
|
||||
return (
|
||||
<List.Icon color={props.color} style={props.style} icon="chevron-right" />
|
||||
);
|
||||
|
|
@ -498,16 +498,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
* @param props
|
||||
* @return {*}
|
||||
*/
|
||||
static getItemIcon(
|
||||
item: ListItemType,
|
||||
props: {
|
||||
color: string;
|
||||
style?: {
|
||||
marginRight: number;
|
||||
marginVertical?: number;
|
||||
};
|
||||
},
|
||||
) {
|
||||
static getItemIcon(item: ListItemType, props: ListIconPropsType): React.Node {
|
||||
return (
|
||||
<List.Icon color={props.color} style={props.style} icon={item.icon} />
|
||||
);
|
||||
|
|
@ -518,14 +509,9 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getCardItem = ({item}: {item: ListItemType}) => {
|
||||
const getItemIcon = (props: {
|
||||
color: string;
|
||||
style?: {
|
||||
marginRight: number;
|
||||
marginVertical?: number;
|
||||
};
|
||||
}) => AboutScreen.getItemIcon(item, props);
|
||||
getCardItem = ({item}: {item: ListItemType}): React.Node => {
|
||||
const getItemIcon = (props: ListIconPropsType): React.Node =>
|
||||
AboutScreen.getItemIcon(item, props);
|
||||
if (item.showChevron) {
|
||||
return (
|
||||
<List.Item
|
||||
|
|
@ -551,7 +537,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
* @param item The item to show
|
||||
* @return {*}
|
||||
*/
|
||||
getMainCard = ({item}: {item: {id: string}}) => {
|
||||
getMainCard = ({item}: {item: {id: string}}): React.Node => {
|
||||
switch (item.id) {
|
||||
case 'app':
|
||||
return this.getAppCard();
|
||||
|
|
@ -578,7 +564,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*/
|
||||
keyExtractor = (item: ListItemType): string => item.icon;
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {state} = this;
|
||||
return (
|
||||
<View
|
||||
|
|
@ -602,4 +588,4 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
}
|
||||
|
||||
export default AboutScreen;
|
||||
export default withTheme(AboutScreen);
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {
|
||||
|
|
@ -30,21 +32,22 @@ import {
|
|||
import {Modalize} from 'react-native-modalize';
|
||||
import CustomModal from '../../components/Overrides/CustomModal';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
||||
|
||||
type PreferenceItemType = {
|
||||
key: string;
|
||||
default: string;
|
||||
current: string;
|
||||
key: string,
|
||||
default: string,
|
||||
current: string,
|
||||
};
|
||||
|
||||
type PropsType = {
|
||||
theme: ReactNativePaper.Theme;
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
modalCurrentDisplayItem: PreferenceItemType | null;
|
||||
currentPreferences: Array<PreferenceItemType>;
|
||||
modalCurrentDisplayItem: PreferenceItemType,
|
||||
currentPreferences: Array<PreferenceItemType>,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -52,7 +55,7 @@ type StateType = {
|
|||
* This screen allows the user to get and modify information on the app/device.
|
||||
*/
|
||||
class DebugScreen extends React.Component<PropsType, StateType> {
|
||||
modalRef: Modalize | null;
|
||||
modalRef: Modalize;
|
||||
|
||||
modalInputValue: string;
|
||||
|
||||
|
|
@ -63,16 +66,16 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
*/
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.modalRef = null;
|
||||
this.modalInputValue = '';
|
||||
const currentPreferences: Array<PreferenceItemType> = [];
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
Object.values(AsyncStorageManager.PREFERENCES).forEach((object: any) => {
|
||||
const newObject: PreferenceItemType = {...object};
|
||||
newObject.current = AsyncStorageManager.getString(newObject.key);
|
||||
currentPreferences.push(newObject);
|
||||
});
|
||||
this.state = {
|
||||
modalCurrentDisplayItem: null,
|
||||
modalCurrentDisplayItem: {},
|
||||
currentPreferences,
|
||||
};
|
||||
}
|
||||
|
|
@ -82,27 +85,21 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getModalContent() {
|
||||
getModalContent(): React.Node {
|
||||
const {props, state} = this;
|
||||
let key = '';
|
||||
let defaultValue = '';
|
||||
let current = '';
|
||||
if (state.modalCurrentDisplayItem) {
|
||||
key = state.modalCurrentDisplayItem.key;
|
||||
defaultValue = state.modalCurrentDisplayItem.default;
|
||||
defaultValue = state.modalCurrentDisplayItem.default;
|
||||
current = state.modalCurrentDisplayItem.current;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
}}>
|
||||
<Title>{key}</Title>
|
||||
<Subheading>Default: {defaultValue}</Subheading>
|
||||
<Subheading>Current: {current}</Subheading>
|
||||
<Title>{state.modalCurrentDisplayItem.key}</Title>
|
||||
<Subheading>
|
||||
Default: {state.modalCurrentDisplayItem.default}
|
||||
</Subheading>
|
||||
<Subheading>
|
||||
Current: {state.modalCurrentDisplayItem.current}
|
||||
</Subheading>
|
||||
<TextInput
|
||||
label="New Value"
|
||||
onChangeText={(text: string) => {
|
||||
|
|
@ -119,7 +116,10 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
dark
|
||||
color={props.theme.colors.success}
|
||||
onPress={() => {
|
||||
this.saveNewPrefs(key, this.modalInputValue);
|
||||
this.saveNewPrefs(
|
||||
state.modalCurrentDisplayItem.key,
|
||||
this.modalInputValue,
|
||||
);
|
||||
}}>
|
||||
Save new value
|
||||
</Button>
|
||||
|
|
@ -128,7 +128,10 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
dark
|
||||
color={props.theme.colors.danger}
|
||||
onPress={() => {
|
||||
this.saveNewPrefs(key, defaultValue);
|
||||
this.saveNewPrefs(
|
||||
state.modalCurrentDisplayItem.key,
|
||||
state.modalCurrentDisplayItem.default,
|
||||
);
|
||||
}}>
|
||||
Reset to default
|
||||
</Button>
|
||||
|
|
@ -137,7 +140,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getRenderItem = ({item}: {item: PreferenceItemType}) => {
|
||||
getRenderItem = ({item}: {item: PreferenceItemType}): React.Node => {
|
||||
return (
|
||||
<List.Item
|
||||
title={item.key}
|
||||
|
|
@ -167,9 +170,7 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
this.setState({
|
||||
modalCurrentDisplayItem: item,
|
||||
});
|
||||
if (this.modalRef) {
|
||||
this.modalRef.open();
|
||||
}
|
||||
if (this.modalRef) this.modalRef.open();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -198,25 +199,24 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
*/
|
||||
saveNewPrefs(key: string, value: string) {
|
||||
this.setState((prevState: StateType): {
|
||||
currentPreferences: Array<PreferenceItemType>;
|
||||
currentPreferences: Array<PreferenceItemType>,
|
||||
} => {
|
||||
const currentPreferences = [...prevState.currentPreferences];
|
||||
currentPreferences[this.findIndexOfKey(key)].current = value;
|
||||
return {currentPreferences};
|
||||
});
|
||||
AsyncStorageManager.set(key, value);
|
||||
if (this.modalRef) {
|
||||
this.modalRef.close();
|
||||
}
|
||||
this.modalRef.close();
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): React.Node {
|
||||
const {state} = this;
|
||||
return (
|
||||
<View>
|
||||
<CustomModal onRef={this.onModalRef}>
|
||||
{this.getModalContent()}
|
||||
</CustomModal>
|
||||
{/* $FlowFixMe */}
|
||||
<CollapsibleFlatList
|
||||
data={state.currentPreferences}
|
||||
extraData={state.currentPreferences}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue