Compare commits
41 commits
64e643f5c6
...
5d692c6840
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d692c6840 | ||
|
|
09ed0058ae | ||
|
|
1639544810 | ||
|
|
4492debad9 | ||
|
|
faac5688f8 | ||
|
|
346b00defd | ||
|
|
11609f8277 | ||
|
|
74f757ce66 | ||
|
|
df8f1cab24 | ||
|
|
eaf1c52af5 | ||
|
|
da55abed8f | ||
|
|
f5233c53f8 | ||
|
|
c5287611c4 | ||
|
|
fde9a12ef9 | ||
|
|
c198a40148 | ||
|
|
38afbf02a3 | ||
|
|
2eb4a3e0c1 | ||
|
|
9f4dcda7d9 | ||
|
|
b78357968a | ||
|
|
742cb1802d | ||
|
|
4d0df7a5b7 | ||
|
|
b8e7272d2c | ||
|
|
300558ac56 | ||
|
|
d70f22bdae | ||
|
|
67cb96dd03 | ||
|
|
5977ce257b | ||
|
|
f7e767748a | ||
|
|
172b7e8187 | ||
|
|
e4530ded18 | ||
|
|
140bcf3675 | ||
|
|
f43dc55735 | ||
|
|
8ac19f36de | ||
|
|
5261e85254 | ||
|
|
e4adcd0057 | ||
|
|
acc4f8cdcc | ||
|
|
18f8c64302 | ||
|
|
98518c46b6 | ||
|
|
f95635136e | ||
|
|
18ec6e0a59 | ||
|
|
375fc8b971 | ||
|
|
54486d1deb |
169 changed files with 5520 additions and 5422 deletions
46
.eslintrc.js
46
.eslintrc.js
|
|
@ -1,46 +1,6 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
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,
|
||||
},
|
||||
extends: '@react-native-community',
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -28,7 +26,6 @@ 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';
|
||||
|
|
@ -38,6 +35,7 @@ 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+
|
||||
|
|
@ -50,15 +48,15 @@ LogBox.ignoreLogs([
|
|||
]);
|
||||
|
||||
type StateType = {
|
||||
isLoading: boolean,
|
||||
showIntro: boolean,
|
||||
showUpdate: boolean,
|
||||
showAprilFools: boolean,
|
||||
currentTheme: CustomThemeType | null,
|
||||
isLoading: boolean;
|
||||
showIntro: boolean;
|
||||
showUpdate: boolean;
|
||||
showAprilFools: boolean;
|
||||
currentTheme: ReactNativePaper.Theme | undefined;
|
||||
};
|
||||
|
||||
export default class App extends React.Component<null, StateType> {
|
||||
navigatorRef: {current: null | NavigationContainer};
|
||||
export default class App extends React.Component<{}, StateType> {
|
||||
navigatorRef: {current: null | NavigationContainerRef};
|
||||
|
||||
defaultHomeRoute: string | null;
|
||||
|
||||
|
|
@ -66,14 +64,14 @@ export default class App extends React.Component<null, StateType> {
|
|||
|
||||
urlHandler: URLHandler;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
showIntro: true,
|
||||
showUpdate: true,
|
||||
showAprilFools: false,
|
||||
currentTheme: null,
|
||||
currentTheme: undefined,
|
||||
};
|
||||
initLocales();
|
||||
this.navigatorRef = React.createRef();
|
||||
|
|
@ -155,8 +153,11 @@ export default class App extends React.Component<null, 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,
|
||||
|
|
@ -192,7 +193,7 @@ export default class App extends React.Component<null, StateType> {
|
|||
/**
|
||||
* Renders the app based on loading state
|
||||
*/
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {state} = this;
|
||||
if (state.isLoading) {
|
||||
return null;
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset', '@babel/preset-flow'],
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
env: {
|
||||
production: {
|
||||
plugins: ['react-native-paper/babel'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,14 +8,15 @@ 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 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.
|
||||
* [**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.
|
||||
|
||||
* [**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.
|
||||
* [**TypeScript Handbook**](https://www.typescriptlang.org/docs/handbook/intro.html) : Un tuto TypeScript complet permettant de bien maitriser cette technologie.
|
||||
* [**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,6 +4,16 @@ 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": "Suivant",
|
||||
"goForward": "Précédent",
|
||||
"goBack": "Précédent",
|
||||
"goForward": "Suivant",
|
||||
"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,16 +810,6 @@
|
|||
"@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",
|
||||
|
|
@ -2297,6 +2287,79 @@
|
|||
"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",
|
||||
|
|
@ -2418,6 +2481,12 @@
|
|||
"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",
|
||||
|
|
@ -2432,6 +2501,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
|
||||
"integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
|
||||
},
|
||||
"@types/i18n-js": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/i18n-js/-/i18n-js-3.0.3.tgz",
|
||||
"integrity": "sha512-GiZzazvxQ5j+EA4Zf4MtDsSaokAR/gW7FxxTlHi2p2xKFUhwAUT0B/MB8WL77P1TcqAO3MefWorFFyZS8F7s0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
|
||||
|
|
@ -2454,6 +2529,36 @@
|
|||
"@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",
|
||||
|
|
@ -2478,11 +2583,72 @@
|
|||
"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",
|
||||
|
|
@ -2496,6 +2662,80 @@
|
|||
"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",
|
||||
|
|
@ -3554,6 +3794,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
|
@ -4242,6 +4488,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
|
@ -4285,6 +4555,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
|
@ -4312,6 +4588,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
|
@ -4348,6 +4633,21 @@
|
|||
"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",
|
||||
|
|
@ -4708,6 +5008,12 @@
|
|||
"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",
|
||||
|
|
@ -4941,12 +5247,6 @@
|
|||
"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",
|
||||
|
|
@ -10032,6 +10332,15 @@
|
|||
"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",
|
||||
|
|
@ -11907,6 +12216,15 @@
|
|||
"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",
|
||||
|
|
@ -11956,6 +12274,12 @@
|
|||
"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",
|
||||
|
|
@ -12375,6 +12699,15 @@
|
|||
"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,12 +8,17 @@
|
|||
"android-release": "react-native run-android --variant=release",
|
||||
"ios": "react-native run-ios",
|
||||
"test": "jest",
|
||||
"lint": "eslint ."
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "react-native",
|
||||
"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)"
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
],
|
||||
"setupFilesAfterEnv": [
|
||||
"jest-extended"
|
||||
|
|
@ -59,9 +64,16 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.0",
|
||||
"@babel/preset-flow": "^7.10.4",
|
||||
"@babel/runtime": "^7.11.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"@react-native-community/eslint-config": "^1.1.0",
|
||||
"@types/i18n-js": "^3.0.3",
|
||||
"@types/jest": "^25.2.3",
|
||||
"@types/react-native": "^0.63.2",
|
||||
"@types/react-native-calendars": "^1.20.10",
|
||||
"@types/react-native-vector-icons": "^6.4.6",
|
||||
"@types/react-test-renderer": "^16.9.2",
|
||||
"@typescript-eslint/eslint-plugin": "^2.27.0",
|
||||
"@typescript-eslint/parser": "^2.27.0",
|
||||
"babel-jest": "^25.1.0",
|
||||
"eslint": "^7.2.0",
|
||||
"eslint-config-airbnb": "^18.2.0",
|
||||
|
|
@ -71,11 +83,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"
|
||||
"react-test-renderer": "16.13.1",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,37 +17,34 @@
|
|||
* 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 = {
|
||||
navigation: StackNavigationProp,
|
||||
type PropsType<T> = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
requests: Array<{
|
||||
link: string,
|
||||
params: {...},
|
||||
mandatory: boolean,
|
||||
}>,
|
||||
renderFunction: (Array<ApiGenericDataType | null>) => React.Node,
|
||||
link: string;
|
||||
params: object;
|
||||
mandatory: boolean;
|
||||
}>;
|
||||
renderFunction: (data: Array<T | null>) => React.ReactNode;
|
||||
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 extends React.Component<PropsType, StateType> {
|
||||
class AuthenticatedScreen<T> extends React.Component<PropsType<T>, StateType> {
|
||||
static defaultProps = {
|
||||
errorViewOverride: null,
|
||||
};
|
||||
|
|
@ -58,13 +55,14 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
|
||||
errors: Array<number>;
|
||||
|
||||
fetchedData: Array<ApiGenericDataType | null>;
|
||||
fetchedData: Array<T | null>;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
constructor(props: PropsType<T>) {
|
||||
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);
|
||||
|
|
@ -91,20 +89,20 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
* @param index The index for the data
|
||||
* @param error The error code received
|
||||
*/
|
||||
onRequestFinished(
|
||||
data: ApiGenericDataType | null,
|
||||
index: number,
|
||||
error?: number,
|
||||
) {
|
||||
onRequestFinished(data: T | 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});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -132,7 +130,7 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getErrorRender(): React.Node {
|
||||
getErrorRender() {
|
||||
const {props} = this;
|
||||
const errorCode = this.getError();
|
||||
let shouldOverride = false;
|
||||
|
|
@ -169,18 +167,18 @@ class AuthenticatedScreen extends React.Component<PropsType, 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(
|
||||
.authenticatedRequest<T>(
|
||||
props.requests[i].link,
|
||||
props.requests[i].params,
|
||||
)
|
||||
.then((response: ApiGenericDataType): void =>
|
||||
this.onRequestFinished(response, i),
|
||||
)
|
||||
.then((response: T): void => this.onRequestFinished(response, i))
|
||||
.catch((error: number): void =>
|
||||
this.onRequestFinished(null, i, error),
|
||||
);
|
||||
|
|
@ -200,7 +198,9 @@ class AuthenticatedScreen extends React.Component<PropsType, 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,11 +212,14 @@ class AuthenticatedScreen extends React.Component<PropsType, StateType> {
|
|||
this.fetchData();
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
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,28 +17,25 @@
|
|||
* 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 = {
|
||||
navigation: StackNavigationProp,
|
||||
visible: boolean,
|
||||
onDismiss: () => void,
|
||||
visible: boolean;
|
||||
onDismiss: () => void;
|
||||
};
|
||||
|
||||
class LogoutDialog extends React.PureComponent<PropsType> {
|
||||
onClickAccept = async (): Promise<void> => {
|
||||
const {props} = this;
|
||||
function LogoutDialog(props: PropsType) {
|
||||
const navigation = useNavigation();
|
||||
const onClickAccept = async (): Promise<void> => {
|
||||
return new Promise((resolve: () => void) => {
|
||||
ConnectionManager.getInstance()
|
||||
.disconnect()
|
||||
.then(() => {
|
||||
props.navigation.reset({
|
||||
navigation.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'main'}],
|
||||
});
|
||||
|
|
@ -48,19 +45,16 @@ class LogoutDialog extends React.PureComponent<PropsType> {
|
|||
});
|
||||
};
|
||||
|
||||
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')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
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')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default LogoutDialog;
|
||||
|
|
@ -1,58 +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/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
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={{
|
||||
width: '100%',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<Headline
|
||||
style={{
|
||||
color: props.theme.colors.textDisabled,
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
{i18n.t('screens.vote.noVote')}
|
||||
</Headline>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(VoteNotAvailable);
|
||||
45
src/components/Amicale/Vote/VoteNotAvailable.tsx
Normal file
45
src/components/Amicale/Vote/VoteNotAvailable.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {Headline, useTheme} from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
function VoteNotAvailable() {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<Headline
|
||||
style={{
|
||||
color: theme.colors.textDisabled,
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
{i18n.t('screens.vote.noVote')}
|
||||
</Headline>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default VoteNotAvailable;
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
|
|
@ -31,16 +29,11 @@ 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: CustomThemeType,
|
||||
teams: Array<VoteTeamType>;
|
||||
dateEnd: string;
|
||||
theme: ReactNativePaper.Theme;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -58,10 +51,10 @@ class VoteResults extends React.Component<PropsType> {
|
|||
winnerIds: Array<number>;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super();
|
||||
super(props);
|
||||
props.teams.sort(this.sortByVotes);
|
||||
this.getTotalVotes(props.teams);
|
||||
this.getWinnerIds(props.teams);
|
||||
this.totalVotes = this.getTotalVotes(props.teams);
|
||||
this.winnerIds = this.getWinnerIds(props.teams);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(): boolean {
|
||||
|
|
@ -69,26 +62,31 @@ class VoteResults extends React.Component<PropsType> {
|
|||
}
|
||||
|
||||
getTotalVotes(teams: Array<VoteTeamType>) {
|
||||
this.totalVotes = 0;
|
||||
let totalVotes = 0;
|
||||
for (let i = 0; i < teams.length; i += 1) {
|
||||
this.totalVotes += teams[i].votes;
|
||||
totalVotes += teams[i].votes;
|
||||
}
|
||||
return totalVotes;
|
||||
}
|
||||
|
||||
getWinnerIds(teams: Array<VoteTeamType>) {
|
||||
const max = teams[0].votes;
|
||||
this.winnerIds = [];
|
||||
let winnerIds = [];
|
||||
for (let i = 0; i < teams.length; i += 1) {
|
||||
if (teams[i].votes === max) this.winnerIds.push(teams[i].id);
|
||||
else break;
|
||||
if (teams[i].votes === max) {
|
||||
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}): React.Node => {
|
||||
resultRenderItem = ({item}: {item: VoteTeamType}) => {
|
||||
const isWinner = this.winnerIds.indexOf(item.id) !== -1;
|
||||
const isDraw = this.winnerIds.length > 1;
|
||||
const {props} = this;
|
||||
|
|
@ -101,7 +99,7 @@ class VoteResults extends React.Component<PropsType> {
|
|||
<List.Item
|
||||
title={item.name}
|
||||
description={`${item.votes} ${i18n.t('screens.vote.results.votes')}`}
|
||||
left={(iconProps: ListIconPropsType): React.Node =>
|
||||
left={(iconProps) =>
|
||||
isWinner ? (
|
||||
<List.Icon
|
||||
style={iconProps.style}
|
||||
|
|
@ -125,7 +123,7 @@ class VoteResults extends React.Component<PropsType> {
|
|||
);
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
|
|
@ -134,15 +132,14 @@ class VoteResults extends React.Component<PropsType> {
|
|||
subtitle={`${i18n.t('screens.vote.results.subtitle')} ${
|
||||
props.dateEnd
|
||||
}`}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon size={iconProps.size} icon="podium-gold" />
|
||||
)}
|
||||
/>
|
||||
<Card.Content>
|
||||
<Subheading>{`${i18n.t('screens.vote.results.totalVotes')} ${
|
||||
this.totalVotes
|
||||
}`}</Subheading>
|
||||
{/* $FlowFixMe */}
|
||||
<Subheading>
|
||||
{`${i18n.t('screens.vote.results.totalVotes')} ${this.totalVotes}`}
|
||||
</Subheading>
|
||||
<FlatList
|
||||
data={props.teams}
|
||||
keyExtractor={this.voteKeyExtractor}
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -27,19 +25,18 @@ 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({
|
||||
|
|
@ -53,10 +50,10 @@ const styles = StyleSheet.create({
|
|||
|
||||
export default class VoteSelect extends React.PureComponent<
|
||||
PropsType,
|
||||
StateType,
|
||||
StateType
|
||||
> {
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedTeam: 'none',
|
||||
voteDialogVisible: false,
|
||||
|
|
@ -70,7 +67,7 @@ export default class VoteSelect extends React.PureComponent<
|
|||
|
||||
voteKeyExtractor = (item: VoteTeamType): string => item.id.toString();
|
||||
|
||||
voteRenderItem = ({item}: {item: VoteTeamType}): React.Node => (
|
||||
voteRenderItem = ({item}: {item: VoteTeamType}) => (
|
||||
<RadioButton.Item label={item.name} value={item.id.toString()} />
|
||||
);
|
||||
|
||||
|
|
@ -111,7 +108,7 @@ export default class VoteSelect extends React.PureComponent<
|
|||
props.onVoteError();
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {state, props} = this;
|
||||
return (
|
||||
<View>
|
||||
|
|
@ -119,7 +116,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: CardTitleIconPropsType): React.Node => (
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon size={iconProps.size} icon="alert-decagram" />
|
||||
)}
|
||||
/>
|
||||
|
|
@ -127,7 +124,6 @@ export default class VoteSelect extends React.PureComponent<
|
|||
<RadioButton.Group
|
||||
onValueChange={this.onVoteSelectionChange}
|
||||
value={state.selectedTeam}>
|
||||
{/* $FlowFixMe */}
|
||||
<FlatList
|
||||
data={props.teams}
|
||||
keyExtractor={this.voteKeyExtractor}
|
||||
|
|
@ -17,16 +17,13 @@
|
|||
* 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({
|
||||
|
|
@ -38,28 +35,19 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
80
src/components/Amicale/Vote/VoteWait.tsx
Normal file
80
src/components/Amicale/Vote/VoteWait.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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,42 +17,37 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {View, ViewStyle} 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: CustomThemeType,
|
||||
title: string,
|
||||
subtitle?: string,
|
||||
left?: () => React.Node,
|
||||
opened?: boolean,
|
||||
unmountWhenCollapsed?: boolean,
|
||||
children?: React.Node,
|
||||
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;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
expanded: boolean,
|
||||
expanded: boolean;
|
||||
};
|
||||
|
||||
const AnimatedListIcon = Animatable.createAnimatableComponent(List.Icon);
|
||||
|
||||
class AnimatedAccordion extends React.Component<PropsType, StateType> {
|
||||
static defaultProps = {
|
||||
subtitle: '',
|
||||
left: null,
|
||||
opened: null,
|
||||
unmountWhenCollapsed: false,
|
||||
children: null,
|
||||
};
|
||||
|
||||
chevronRef: {current: null | AnimatedListIcon};
|
||||
chevronRef: {current: null | (typeof AnimatedListIcon & List.Icon)};
|
||||
|
||||
chevronIcon: string;
|
||||
|
||||
|
|
@ -62,6 +57,9 @@ 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,
|
||||
};
|
||||
|
|
@ -71,8 +69,9 @@ 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;
|
||||
}
|
||||
|
||||
|
|
@ -101,17 +100,17 @@ class AnimatedAccordion extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props, state} = this;
|
||||
const {colors} = props.theme;
|
||||
return (
|
||||
<View>
|
||||
<View style={props.style}>
|
||||
<List.Item
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
description={props.subtitle}
|
||||
titleStyle={state.expanded ? {color: colors.primary} : null}
|
||||
onPress={this.toggleAccordion}
|
||||
right={(iconProps: ListIconPropsType): React.Node => (
|
||||
right={(iconProps) => (
|
||||
<AnimatedListIcon
|
||||
ref={this.chevronRef}
|
||||
style={iconProps.style}
|
||||
|
|
@ -17,29 +17,30 @@
|
|||
* 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 {
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
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,
|
||||
theme: CustomThemeType,
|
||||
onPress: (action: string, data?: string) => void,
|
||||
seekAttention: boolean,
|
||||
navigation: StackNavigationProp<any>;
|
||||
theme: ReactNativePaper.Theme;
|
||||
onPress: (action: string, data?: string) => void;
|
||||
seekAttention: boolean;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
currentMode: string,
|
||||
currentMode: string;
|
||||
};
|
||||
|
||||
const DISPLAY_MODES = {
|
||||
|
|
@ -78,14 +79,14 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
class AnimatedBottomBar extends React.Component<PropsType, StateType> {
|
||||
ref: {current: null | Animatable.View};
|
||||
ref: {current: null | (Animatable.View & View)};
|
||||
|
||||
hideHandler: AutoHideHandler;
|
||||
|
||||
displayModeIcons: {[key: string]: string};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentMode: DISPLAY_MODES.WEEK,
|
||||
};
|
||||
|
|
@ -108,13 +109,17 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
|
||||
onHideChange = (shouldHide: boolean) => {
|
||||
if (this.ref.current != null) {
|
||||
if (shouldHide) this.ref.current.fadeOutDown(500);
|
||||
else this.ref.current.fadeInUp(500);
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onScroll = (event: OnScrollType) => {
|
||||
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
this.hideHandler.onScroll(event);
|
||||
};
|
||||
|
||||
|
|
@ -139,7 +144,7 @@ class AnimatedBottomBar extends React.Component<PropsType, StateType> {
|
|||
props.onPress('changeView', newMode);
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props, state} = this;
|
||||
const buttonColor = props.theme.colors.primary;
|
||||
return (
|
||||
|
|
@ -17,22 +17,23 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import {
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
StyleSheet,
|
||||
View,
|
||||
} 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',
|
||||
|
|
@ -42,41 +43,49 @@ const styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
export default class AnimatedFAB extends React.Component<PropsType> {
|
||||
ref: {current: null | Animatable.View};
|
||||
ref: {current: null | (Animatable.View & View)};
|
||||
|
||||
hideHandler: AutoHideHandler;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.ref = React.createRef();
|
||||
this.hideHandler = new AutoHideHandler(false);
|
||||
this.hideHandler.addListener(this.onHideChange);
|
||||
}
|
||||
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
this.hideHandler.onScroll(event);
|
||||
};
|
||||
|
||||
onHideChange = (shouldHide: boolean) => {
|
||||
if (this.ref.current != null) {
|
||||
if (shouldHide) this.ref.current.bounceOutDown(1000);
|
||||
else this.ref.current.bounceInUp(1000);
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
return (
|
||||
<AnimatedFab
|
||||
<Animatable.View
|
||||
ref={this.ref}
|
||||
useNativeDriver
|
||||
icon={props.icon}
|
||||
onPress={props.onPress}
|
||||
useNativeDriver={true}
|
||||
style={{
|
||||
...styles.fab,
|
||||
bottom: CustomTabBar.TAB_BAR_HEIGHT,
|
||||
}}
|
||||
/>
|
||||
}}>
|
||||
<FAB icon={props.icon} onPress={props.onPress} />
|
||||
</Animatable.View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
63
src/components/Collapsible/CollapsibleComponent.tsx
Normal file
63
src/components/Collapsible/CollapsibleComponent.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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,29 +17,19 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated} from 'react-native';
|
||||
import {Animated, FlatListProps} from 'react-native';
|
||||
import type {CollapsibleComponentPropsType} from './CollapsibleComponent';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
};
|
||||
type Props<T> = FlatListProps<T> & CollapsibleComponentPropsType;
|
||||
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
function CollapsibleFlatList<T>(props: Props<T>) {
|
||||
return (
|
||||
<CollapsibleComponent {...props} component={Animated.FlatList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollapsibleFlatList;
|
||||
|
|
@ -17,29 +17,19 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated} from 'react-native';
|
||||
import {Animated, ScrollViewProps} from 'react-native';
|
||||
import type {CollapsibleComponentPropsType} from './CollapsibleComponent';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
};
|
||||
type Props = ScrollViewProps & CollapsibleComponentPropsType;
|
||||
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
function CollapsibleScrollView(props: Props) {
|
||||
return (
|
||||
<CollapsibleComponent {...props} component={Animated.ScrollView}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollapsibleScrollView;
|
||||
|
|
@ -17,29 +17,19 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated} from 'react-native';
|
||||
import {Animated, SectionListProps} from 'react-native';
|
||||
import type {CollapsibleComponentPropsType} from './CollapsibleComponent';
|
||||
import CollapsibleComponent from './CollapsibleComponent';
|
||||
|
||||
type PropsType = {
|
||||
...CollapsibleComponentPropsType,
|
||||
};
|
||||
type Props<T> = SectionListProps<T> & CollapsibleComponentPropsType;
|
||||
|
||||
// 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>
|
||||
);
|
||||
}
|
||||
function CollapsibleSectionList<T>(props: Props<T>) {
|
||||
return (
|
||||
<CollapsibleComponent {...props} component={Animated.SectionList}>
|
||||
{props.children}
|
||||
</CollapsibleComponent>
|
||||
);
|
||||
}
|
||||
|
||||
export default CollapsibleSectionList;
|
||||
|
|
@ -17,36 +17,31 @@
|
|||
* 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.Node,
|
||||
message: string | React.Node,
|
||||
visible: boolean;
|
||||
onDismiss: () => void;
|
||||
title: string | React.ReactNode;
|
||||
message: string | React.ReactNode;
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default AlertDialog;
|
||||
|
|
@ -1,90 +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/>.
|
||||
*/
|
||||
|
||||
// @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;
|
||||
80
src/components/Dialogs/ErrorDialog.tsx
Normal file
80
src/components/Dialogs/ErrorDialog.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
|
|
@ -30,20 +28,23 @@ 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;
|
||||
};
|
||||
|
||||
class LoadingConfirmDialog extends React.PureComponent<PropsType, StateType> {
|
||||
export default class LoadingConfirmDialog extends React.PureComponent<
|
||||
PropsType,
|
||||
StateType
|
||||
> {
|
||||
static defaultProps = {
|
||||
onDismiss: () => {},
|
||||
onAccept: (): Promise<void> => {
|
||||
|
|
@ -71,14 +72,16 @@ class LoadingConfirmDialog extends React.PureComponent<PropsType, StateType> {
|
|||
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 {TimeoutID}
|
||||
* @returns {NodeJS.Timeout}
|
||||
*/
|
||||
hideLoading = (): TimeoutID =>
|
||||
hideLoading = (): NodeJS.Timeout =>
|
||||
setTimeout(() => {
|
||||
this.setState({loading: false});
|
||||
}, 200);
|
||||
|
|
@ -88,10 +91,12 @@ class LoadingConfirmDialog extends React.PureComponent<PropsType, StateType> {
|
|||
*/
|
||||
onDismiss = () => {
|
||||
const {state, props} = this;
|
||||
if (!state.loading && props.onDismiss != null) props.onDismiss();
|
||||
if (!state.loading && props.onDismiss != null) {
|
||||
props.onDismiss();
|
||||
}
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {state, props} = this;
|
||||
return (
|
||||
<Portal>
|
||||
|
|
@ -121,5 +126,3 @@ class LoadingConfirmDialog extends React.PureComponent<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default LoadingConfirmDialog;
|
||||
|
|
@ -17,28 +17,26 @@
|
|||
* 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;
|
||||
};
|
||||
|
||||
class OptionsDialog extends React.PureComponent<PropsType> {
|
||||
getButtonRender = ({item}: {item: OptionsDialogButtonType}): React.Node => {
|
||||
function OptionsDialog(props: PropsType) {
|
||||
const getButtonRender = ({item}: {item: OptionsDialogButtonType}) => {
|
||||
return (
|
||||
<Button onPress={item.onPress} icon={item.icon}>
|
||||
{item.title}
|
||||
|
|
@ -46,35 +44,32 @@ class OptionsDialog extends React.PureComponent<PropsType> {
|
|||
);
|
||||
};
|
||||
|
||||
keyExtractor = (item: OptionsDialogButtonType): string => {
|
||||
const keyExtractor = (item: OptionsDialogButtonType): string => {
|
||||
if (item.icon != null) {
|
||||
return item.title + item.icon;
|
||||
}
|
||||
return item.title;
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default OptionsDialog;
|
||||
|
|
@ -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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
59
src/components/Home/ActionsDashboardItem.tsx
Normal file
59
src/components/Home/ActionsDashboardItem.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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;
|
||||
|
|
@ -1,116 +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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
104
src/components/Home/EventDashboardItem.tsx
Normal file
104
src/components/Home/EventDashboardItem.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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);
|
||||
|
|
@ -1,139 +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/>.
|
||||
*/
|
||||
|
||||
// @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;
|
||||
128
src/components/Home/FeedItem.tsx
Normal file
128
src/components/Home/FeedItem.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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);
|
||||
|
|
@ -1,113 +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/>.
|
||||
*/
|
||||
|
||||
// @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;
|
||||
93
src/components/Home/PreviewEventDashboardItem.tsx
Normal file
93
src/components/Home/PreviewEventDashboardItem.tsx
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/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
|
@ -1,106 +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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
94
src/components/Home/SmallDashboardItem.tsx
Normal file
94
src/components/Home/SmallDashboardItem.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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,15 +17,13 @@
|
|||
* 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({
|
||||
|
|
@ -37,24 +35,14 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntroIcon;
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -32,34 +30,28 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default MascotIntroEnd;
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -34,62 +32,56 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
class MascotIntroWelcome extends React.Component<null> {
|
||||
shouldComponentUpdate(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Mascot
|
||||
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
|
||||
style={{
|
||||
...styles.center,
|
||||
width: '80%',
|
||||
}}
|
||||
emotion={MASCOT_STYLE.NORMAL}
|
||||
animated
|
||||
entryAnimation={{
|
||||
animation: 'bounceIn',
|
||||
duration: 2000,
|
||||
transform: [{rotateZ: '70deg'}],
|
||||
}}
|
||||
name="undo"
|
||||
color="#fff"
|
||||
size={40}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
</Animatable.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default MascotIntroWelcome;
|
||||
|
|
@ -17,19 +17,16 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Animated, Dimensions} from 'react-native';
|
||||
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
import {Animated, Dimensions, ViewStyle} from 'react-native';
|
||||
import ImageListItem from './ImageListItem';
|
||||
import CardListItem from './CardListItem';
|
||||
import type {ServiceItemType} from '../../../managers/ServicesManager';
|
||||
|
||||
type PropsType = {
|
||||
dataset: Array<ServiceItemType>,
|
||||
isHorizontal?: boolean,
|
||||
contentContainerStyle?: ViewStyle | null,
|
||||
dataset: Array<ServiceItemType>;
|
||||
isHorizontal?: boolean;
|
||||
contentContainerStyle?: ViewStyle;
|
||||
};
|
||||
|
||||
export default class CardList extends React.Component<PropsType> {
|
||||
|
|
@ -45,12 +42,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}): React.Node => {
|
||||
getRenderItem = ({item}: {item: ServiceItemType}) => {
|
||||
const {props} = this;
|
||||
if (props.isHorizontal)
|
||||
if (props.isHorizontal) {
|
||||
return (
|
||||
<ImageListItem
|
||||
item={item}
|
||||
|
|
@ -58,12 +55,13 @@ 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(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
let containerStyle = {};
|
||||
if (props.isHorizontal) {
|
||||
|
|
@ -84,7 +82,7 @@ export default class CardList extends React.Component<PropsType> {
|
|||
}
|
||||
pagingEnabled={props.isHorizontal}
|
||||
snapToInterval={
|
||||
props.isHorizontal ? (this.horizontalItemSize + 5) * 3 : null
|
||||
props.isHorizontal ? (this.horizontalItemSize + 5) * 3 : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
|
@ -17,45 +17,38 @@
|
|||
* 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;
|
||||
};
|
||||
|
||||
export default class CardListItem 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 (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
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 React.memo(CardListItem, () => true);
|
||||
|
|
@ -1,73 +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/>.
|
||||
*/
|
||||
|
||||
// @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>
|
||||
);
|
||||
}
|
||||
}
|
||||
66
src/components/Lists/CardList/ImageListItem.tsx
Normal file
66
src/components/Lists/CardList/ImageListItem.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -26,12 +24,11 @@ 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({
|
||||
|
|
@ -54,16 +51,8 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
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;
|
||||
function ClubListHeader(props: PropsType) {
|
||||
const getChipRender = (category: ClubCategoryType, key: string) => {
|
||||
const onPress = (): void => props.onChipSelect(category.id);
|
||||
return (
|
||||
<Chip
|
||||
|
|
@ -80,32 +69,39 @@ class ClubListHeader extends React.Component<PropsType> {
|
|||
);
|
||||
};
|
||||
|
||||
getCategoriesRender(): React.Node {
|
||||
const {props} = this;
|
||||
const final = [];
|
||||
const getCategoriesRender = () => {
|
||||
const final: Array<React.ReactNode> = [];
|
||||
props.categories.forEach((cat: ClubCategoryType) => {
|
||||
final.push(this.getChipRender(cat, cat.id.toString()));
|
||||
final.push(getChipRender(cat, cat.id.toString()));
|
||||
});
|
||||
return final;
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export default ClubListHeader;
|
||||
const areEqual = (prevProps: PropsType, nextProps: PropsType): boolean => {
|
||||
return (
|
||||
prevProps.selectedCategories.length === nextProps.selectedCategories.length
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ClubListHeader, areEqual);
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -26,14 +24,13 @@ import type {
|
|||
ClubCategoryType,
|
||||
ClubType,
|
||||
} from '../../../screens/Amicale/Clubs/ClubListScreen';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
onPress: () => void,
|
||||
categoryTranslator: (id: number) => ClubCategoryType,
|
||||
item: ClubType,
|
||||
height: number,
|
||||
theme: CustomThemeType,
|
||||
onPress: () => void;
|
||||
categoryTranslator: (id: number) => ClubCategoryType | null;
|
||||
item: ClubType;
|
||||
height: number;
|
||||
theme: ReactNativePaper.Theme;
|
||||
};
|
||||
|
||||
class ClubListItem extends React.Component<PropsType> {
|
||||
|
|
@ -48,27 +45,29 @@ class ClubListItem extends React.Component<PropsType> {
|
|||
return false;
|
||||
}
|
||||
|
||||
getCategoriesRender(categories: Array<number | null>): React.Node {
|
||||
getCategoriesRender(categories: Array<number | null>) {
|
||||
const {props} = this;
|
||||
const final = [];
|
||||
const final: Array<React.ReactNode> = [];
|
||||
categories.forEach((cat: number | null) => {
|
||||
if (cat != null) {
|
||||
const category: ClubCategoryType = props.categoryTranslator(cat);
|
||||
final.push(
|
||||
<Chip
|
||||
style={{marginRight: 5, marginBottom: 5}}
|
||||
key={`${props.item.id}:${category.id}`}>
|
||||
{category.name}
|
||||
</Chip>,
|
||||
);
|
||||
const category = props.categoryTranslator(cat);
|
||||
if (category) {
|
||||
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(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
const categoriesRender = (): React.Node =>
|
||||
const categoriesRender = () =>
|
||||
this.getCategoriesRender(props.item.category);
|
||||
const {colors} = props.theme;
|
||||
return (
|
||||
|
|
@ -76,7 +75,7 @@ class ClubListItem extends React.Component<PropsType> {
|
|||
title={props.item.name}
|
||||
description={categoriesRender}
|
||||
onPress={props.onPress}
|
||||
left={(): React.Node => (
|
||||
left={() => (
|
||||
<Avatar.Image
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
|
|
@ -87,7 +86,7 @@ class ClubListItem extends React.Component<PropsType> {
|
|||
source={{uri: props.item.logo}}
|
||||
/>
|
||||
)}
|
||||
right={(): React.Node => (
|
||||
right={() => (
|
||||
<Avatar.Icon
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
|
|
@ -1,108 +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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
100
src/components/Lists/DashboardEdit/DashboardEditAccordion.tsx
Normal file
100
src/components/Lists/DashboardEdit/DashboardEditAccordion.tsx
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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;
|
||||
|
|
@ -1,81 +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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
76
src/components/Lists/DashboardEdit/DashboardEditItem.tsx
Normal file
76
src/components/Lists/DashboardEdit/DashboardEditItem.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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,61 +17,54 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {TouchableRipple, withTheme} from 'react-native-paper';
|
||||
import {TouchableRipple, useTheme} from 'react-native-paper';
|
||||
import {Dimensions, Image, View} from 'react-native';
|
||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||
|
||||
type PropsType = {
|
||||
image: string,
|
||||
isActive: boolean,
|
||||
onPress: () => void,
|
||||
theme: CustomThemeType,
|
||||
image?: string | number;
|
||||
isActive: boolean;
|
||||
onPress: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component used to render a small dashboard item
|
||||
*/
|
||||
class DashboardEditPreviewItem extends React.Component<PropsType> {
|
||||
itemSize: number;
|
||||
function DashboardEditPreviewItem(props: PropsType) {
|
||||
const theme = useTheme();
|
||||
const itemSize = Dimensions.get('window').width / 8;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.itemSize = Dimensions.get('window').width / 8;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
return (
|
||||
<TouchableRipple
|
||||
onPress={props.onPress}
|
||||
borderless
|
||||
return (
|
||||
<TouchableRipple
|
||||
onPress={props.onPress}
|
||||
borderless
|
||||
style={{
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
backgroundColor: props.isActive
|
||||
? theme.colors.textDisabled
|
||||
: 'transparent',
|
||||
borderRadius: 5,
|
||||
}}>
|
||||
<View
|
||||
style={{
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
backgroundColor: props.isActive
|
||||
? props.theme.colors.textDisabled
|
||||
: 'transparent',
|
||||
borderRadius: 5,
|
||||
width: itemSize,
|
||||
height: itemSize,
|
||||
}}>
|
||||
<View
|
||||
style={{
|
||||
width: this.itemSize,
|
||||
height: this.itemSize,
|
||||
}}>
|
||||
{props.image ? (
|
||||
<Image
|
||||
source={{uri: props.image}}
|
||||
source={
|
||||
typeof props.image === 'string' ? {uri: props.image} : props.image
|
||||
}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
) : null}
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(DashboardEditPreviewItem);
|
||||
export default DashboardEditPreviewItem;
|
||||
|
|
@ -1,132 +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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
136
src/components/Lists/Equipment/EquipmentListItem.tsx
Normal file
136
src/components/Lists/Equipment/EquipmentListItem.tsx
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* 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,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -29,17 +27,14 @@ 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: (PlanexGroupType) => void,
|
||||
onFavoritePress: (PlanexGroupType) => void,
|
||||
currentSearchString: string,
|
||||
height: number,
|
||||
theme: CustomThemeType,
|
||||
item: PlanexGroupCategoryType;
|
||||
favorites: Array<PlanexGroupType>;
|
||||
onGroupPress: (data: PlanexGroupType) => void;
|
||||
onFavoritePress: (data: PlanexGroupType) => void;
|
||||
currentSearchString: string;
|
||||
theme: ReactNativePaper.Theme;
|
||||
};
|
||||
|
||||
const LIST_ITEM_HEIGHT = 64;
|
||||
|
|
@ -55,7 +50,7 @@ class GroupListAccordion extends React.Component<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
getRenderItem = ({item}: {item: PlanexGroupType}): React.Node => {
|
||||
getRenderItem = ({item}: {item: PlanexGroupType}) => {
|
||||
const {props} = this;
|
||||
const onPress = () => {
|
||||
props.onGroupPress(item);
|
||||
|
|
@ -77,18 +72,19 @@ class GroupListAccordion extends React.Component<PropsType> {
|
|||
getData(): Array<PlanexGroupType> {
|
||||
const {props} = this;
|
||||
const originalData = props.item.content;
|
||||
const displayData = [];
|
||||
const displayData: Array<PlanexGroupType> = [];
|
||||
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>,
|
||||
data: Array<PlanexGroupType> | null | undefined,
|
||||
index: number,
|
||||
): {length: number, offset: number, index: number} => ({
|
||||
): {length: number; offset: number; index: number} => ({
|
||||
length: LIST_ITEM_HEIGHT,
|
||||
offset: LIST_ITEM_HEIGHT * index,
|
||||
index,
|
||||
|
|
@ -96,7 +92,7 @@ class GroupListAccordion extends React.Component<PropsType> {
|
|||
|
||||
keyExtractor = (item: PlanexGroupType): string => item.id.toString();
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
const {item} = this.props;
|
||||
return (
|
||||
|
|
@ -104,10 +100,9 @@ class GroupListAccordion extends React.Component<PropsType> {
|
|||
<AnimatedAccordion
|
||||
title={item.name.replace(REPLACE_REGEX, ' ')}
|
||||
style={{
|
||||
height: props.height,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
left={(iconProps: ListIconPropsType): React.Node =>
|
||||
left={(iconProps) =>
|
||||
item.id === 0 ? (
|
||||
<List.Icon
|
||||
style={iconProps.style}
|
||||
|
|
@ -17,23 +17,20 @@
|
|||
* 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 type {ListIconPropsType} from '../../../constants/PaperStyles';
|
||||
import {View} from 'react-native';
|
||||
|
||||
type PropsType = {
|
||||
theme: CustomThemeType,
|
||||
onPress: () => void,
|
||||
onStarPress: () => void,
|
||||
item: PlanexGroupType,
|
||||
favorites: Array<PlanexGroupType>,
|
||||
height: number,
|
||||
theme: ReactNativePaper.Theme;
|
||||
onPress: () => void;
|
||||
onStarPress: () => void;
|
||||
item: PlanexGroupType;
|
||||
favorites: Array<PlanexGroupType>;
|
||||
height: number;
|
||||
};
|
||||
|
||||
const REPLACE_REGEX = /_/g;
|
||||
|
|
@ -41,10 +38,11 @@ const REPLACE_REGEX = /_/g;
|
|||
class GroupListItem extends React.Component<PropsType> {
|
||||
isFav: boolean;
|
||||
|
||||
starRef: null | Animatable.View;
|
||||
starRef: {current: null | (Animatable.View & View)};
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.starRef = React.createRef();
|
||||
this.isFav = this.isGroupInFavorites(props.favorites);
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +50,9 @@ 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,9 +61,12 @@ class GroupListItem extends React.Component<PropsType> {
|
|||
onStarPress = () => {
|
||||
const {props} = this;
|
||||
const ref = this.starRef;
|
||||
if (ref != null) {
|
||||
if (this.isFav) ref.rubberBand();
|
||||
else ref.swing();
|
||||
if (ref.current && ref.current.rubberBand && ref.current.swing) {
|
||||
if (this.isFav) {
|
||||
ref.current.rubberBand();
|
||||
} else {
|
||||
ref.current.swing();
|
||||
}
|
||||
}
|
||||
props.onStarPress();
|
||||
};
|
||||
|
|
@ -71,31 +74,29 @@ 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(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
const {colors} = props.theme;
|
||||
return (
|
||||
<List.Item
|
||||
title={props.item.name.replace(REPLACE_REGEX, ' ')}
|
||||
onPress={props.onPress}
|
||||
left={(iconProps: ListIconPropsType): React.Node => (
|
||||
left={(iconProps) => (
|
||||
<List.Icon
|
||||
color={iconProps.color}
|
||||
style={iconProps.style}
|
||||
icon="chevron-right"
|
||||
/>
|
||||
)}
|
||||
right={(iconProps: ListIconPropsType): React.Node => (
|
||||
<Animatable.View
|
||||
ref={(ref: Animatable.View) => {
|
||||
this.starRef = ref;
|
||||
}}
|
||||
useNativeDriver>
|
||||
right={(iconProps) => (
|
||||
<Animatable.View ref={this.starRef} useNativeDriver>
|
||||
<TouchableRipple
|
||||
onPress={this.onStarPress}
|
||||
style={{
|
||||
|
|
@ -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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
59
src/components/Lists/Proximo/ProximoListItem.tsx
Normal file
59
src/components/Lists/Proximo/ProximoListItem.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
|
|
@ -32,22 +30,23 @@ import {
|
|||
import {StyleSheet, View} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import ProxiwashConstants from '../../../constants/ProxiwashConstants';
|
||||
import ProxiwashConstants, {
|
||||
MachineStates,
|
||||
} 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: CustomThemeType,
|
||||
item: ProxiwashMachineType;
|
||||
theme: ReactNativePaper.Theme;
|
||||
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);
|
||||
|
|
@ -72,9 +71,19 @@ const styles = StyleSheet.create({
|
|||
* Component used to display a proxiwash item, showing machine progression and state
|
||||
*/
|
||||
class ProxiwashListItem extends React.Component<PropsType> {
|
||||
stateColors: {[key: string]: string};
|
||||
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'),
|
||||
};
|
||||
|
||||
stateStrings: {[key: string]: string};
|
||||
stateColors: {[key: string]: string};
|
||||
|
||||
title: string;
|
||||
|
||||
|
|
@ -83,16 +92,14 @@ 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')
|
||||
|
|
@ -116,65 +123,38 @@ 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[ProxiwashConstants.machineStates.AVAILABLE] =
|
||||
colors.proxiwashReadyColor;
|
||||
this.stateColors[ProxiwashConstants.machineStates.RUNNING] =
|
||||
colors.proxiwashRunningColor;
|
||||
this.stateColors[ProxiwashConstants.machineStates.RUNNING_NOT_STARTED] =
|
||||
this.stateColors[MachineStates.AVAILABLE] = colors.proxiwashReadyColor;
|
||||
this.stateColors[MachineStates.RUNNING] = colors.proxiwashRunningColor;
|
||||
this.stateColors[MachineStates.RUNNING_NOT_STARTED] =
|
||||
colors.proxiwashRunningNotStartedColor;
|
||||
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;
|
||||
this.stateColors[MachineStates.FINISHED] = colors.proxiwashFinishedColor;
|
||||
this.stateColors[MachineStates.UNAVAILABLE] = colors.proxiwashBrokenColor;
|
||||
this.stateColors[MachineStates.ERROR] = colors.proxiwashErrorColor;
|
||||
this.stateColors[MachineStates.UNKNOWN] = colors.proxiwashUnknownColor;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
const {colors} = props.theme;
|
||||
const machineState = props.item.state;
|
||||
const isRunning = machineState === ProxiwashConstants.machineStates.RUNNING;
|
||||
const isReady = machineState === ProxiwashConstants.machineStates.AVAILABLE;
|
||||
const isRunning = machineState === MachineStates.RUNNING;
|
||||
const isReady = machineState === 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
|
||||
|
|
@ -224,19 +204,19 @@ class ProxiwashListItem extends React.Component<PropsType> {
|
|||
justifyContent: 'center',
|
||||
}}
|
||||
onPress={this.onListItemPress}
|
||||
left={(): React.Node => icon}
|
||||
right={(): React.Node => (
|
||||
left={() => icon}
|
||||
right={() => (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<View style={{justifyContent: 'center'}}>
|
||||
<Text
|
||||
style={
|
||||
machineState === ProxiwashConstants.machineStates.FINISHED
|
||||
machineState === MachineStates.FINISHED
|
||||
? {fontWeight: 'bold'}
|
||||
: {}
|
||||
}>
|
||||
{stateString}
|
||||
</Text>
|
||||
{machineState === ProxiwashConstants.machineStates.RUNNING ? (
|
||||
{machineState === MachineStates.RUNNING ? (
|
||||
<Caption>{props.item.remainingTime} min</Caption>
|
||||
) : null}
|
||||
</View>
|
||||
|
|
@ -17,19 +17,16 @@
|
|||
* 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: CustomThemeType,
|
||||
title: string,
|
||||
isDryer: boolean,
|
||||
nbAvailable: number,
|
||||
theme: ReactNativePaper.Theme;
|
||||
title: string;
|
||||
isDryer: boolean;
|
||||
nbAvailable: number;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -61,7 +58,7 @@ class ProxiwashListItem extends React.Component<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
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} from 'react-native';
|
||||
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
import {Image, TouchableWithoutFeedback, View, ViewStyle} from 'react-native';
|
||||
import {AnimatableProperties} from 'react-native-animatable';
|
||||
|
||||
export type AnimatableViewRefType = {current: null | Animatable.View};
|
||||
export type AnimatableViewRefType = {
|
||||
current: null | (typeof Animatable.View & View);
|
||||
};
|
||||
|
||||
type PropsType = {
|
||||
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),
|
||||
emotion?: MASCOT_STYLE;
|
||||
animated?: boolean;
|
||||
style?: ViewStyle;
|
||||
entryAnimation?: AnimatableProperties<ViewStyle>;
|
||||
loopAnimation?: AnimatableProperties<ViewStyle>;
|
||||
onPress?: null | ((viewRef: AnimatableViewRefType) => void);
|
||||
onLongPress?: null | ((viewRef: AnimatableViewRefType) => void);
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
currentEmotion: number,
|
||||
currentEmotion: MASCOT_STYLE;
|
||||
};
|
||||
|
||||
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');
|
||||
|
||||
export const EYE_STYLE = {
|
||||
NORMAL: 0,
|
||||
GIRLY: 2,
|
||||
CUTE: 3,
|
||||
WINK: 4,
|
||||
HEART: 5,
|
||||
ANGRY: 6,
|
||||
};
|
||||
enum EYE_STYLE {
|
||||
NORMAL,
|
||||
GIRLY,
|
||||
CUTE,
|
||||
WINK,
|
||||
HEART,
|
||||
ANGRY,
|
||||
}
|
||||
|
||||
const GLASSES_STYLE = {
|
||||
NORMAL: 0,
|
||||
COOl: 1,
|
||||
};
|
||||
enum GLASSES_STYLE {
|
||||
NORMAL,
|
||||
COOl,
|
||||
}
|
||||
|
||||
export const MASCOT_STYLE = {
|
||||
NORMAL: 0,
|
||||
HAPPY: 1,
|
||||
GIRLY: 2,
|
||||
WINK: 3,
|
||||
CUTE: 4,
|
||||
INTELLO: 5,
|
||||
LOVE: 6,
|
||||
COOL: 7,
|
||||
ANGRY: 8,
|
||||
RANDOM: 999,
|
||||
};
|
||||
export enum MASCOT_STYLE {
|
||||
NORMAL,
|
||||
HAPPY,
|
||||
GIRLY,
|
||||
WINK,
|
||||
CUTE,
|
||||
INTELLO,
|
||||
LOVE,
|
||||
COOL,
|
||||
ANGRY,
|
||||
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: number]: number | string};
|
||||
eyeList: {[key in EYE_STYLE]: number};
|
||||
|
||||
glassesList: {[key: number]: number | string};
|
||||
glassesList: {[key in GLASSES_STYLE]: number};
|
||||
|
||||
onPress: (viewRef: AnimatableViewRefType) => void;
|
||||
|
||||
|
|
@ -113,23 +113,25 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.viewRef = React.createRef();
|
||||
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;
|
||||
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.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)
|
||||
if (this.initialEmotion === MASCOT_STYLE.RANDOM) {
|
||||
this.initialEmotion = Math.floor(Math.random() * MASCOT_STYLE.ANGRY) + 1;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
currentEmotion: this.initialEmotion,
|
||||
|
|
@ -138,29 +140,33 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
if (props.onPress == null) {
|
||||
this.onPress = (viewRef: AnimatableViewRefType) => {
|
||||
const ref = viewRef.current;
|
||||
if (ref != null) {
|
||||
if (ref && ref.rubberBand) {
|
||||
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 != null) {
|
||||
if (ref && ref.tada) {
|
||||
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: number): React.Node {
|
||||
getGlasses(style: GLASSES_STYLE) {
|
||||
const glasses = this.glassesList[style];
|
||||
return (
|
||||
<Image
|
||||
|
|
@ -179,11 +185,7 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getEye(
|
||||
style: number,
|
||||
isRight: boolean,
|
||||
rotation: string = '0deg',
|
||||
): React.Node {
|
||||
getEye(style: EYE_STYLE, isRight: boolean, rotation: string = '0deg') {
|
||||
const eye = this.eyeList[style];
|
||||
return (
|
||||
<Image
|
||||
|
|
@ -201,7 +203,7 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getEyes(emotion: number): React.Node {
|
||||
getEyes(emotion: MASCOT_STYLE) {
|
||||
const final = [];
|
||||
final.push(
|
||||
<View
|
||||
|
|
@ -246,7 +248,7 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
return final;
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props, state} = this;
|
||||
const entryAnimation = props.animated ? props.entryAnimation : null;
|
||||
const loopAnimation = props.animated ? props.loopAnimation : null;
|
||||
|
|
@ -256,7 +258,6 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
aspectRatio: 1,
|
||||
...props.style,
|
||||
}}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...entryAnimation}>
|
||||
<TouchableWithoutFeedback
|
||||
onPress={() => {
|
||||
|
|
@ -266,9 +267,7 @@ class Mascot extends React.Component<PropsType, StateType> {
|
|||
this.onLongPress(this.viewRef);
|
||||
}}>
|
||||
<Animatable.View ref={this.viewRef}>
|
||||
<Animatable.View
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...loopAnimation}>
|
||||
<Animatable.View {...loopAnimation}>
|
||||
<Image
|
||||
source={MASCOT_IMAGE}
|
||||
style={{
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
|
|
@ -37,48 +35,42 @@ 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: CustomThemeType,
|
||||
icon: string,
|
||||
title: string,
|
||||
message: string,
|
||||
theme: ReactNativePaper.Theme;
|
||||
icon: string;
|
||||
title: string;
|
||||
message: string;
|
||||
buttons: {
|
||||
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,
|
||||
action?: {
|
||||
message: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
onPress?: () => void;
|
||||
};
|
||||
cancel?: {
|
||||
message: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
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;
|
||||
|
|
@ -112,7 +104,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount(): * {
|
||||
componentDidMount() {
|
||||
BackHandler.addEventListener(
|
||||
'hardwareBackPress',
|
||||
this.onBackButtonPressAndroid,
|
||||
|
|
@ -146,14 +138,20 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
if (state.dialogVisible) {
|
||||
const {cancel} = props.buttons;
|
||||
const {action} = props.buttons;
|
||||
if (cancel != null) this.onDismiss(cancel.onPress);
|
||||
else this.onDismiss(action.onPress);
|
||||
if (cancel) {
|
||||
this.onDismiss(cancel.onPress);
|
||||
} else if (action) {
|
||||
this.onDismiss(action.onPress);
|
||||
} else {
|
||||
this.onDismiss();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
getSpeechBubble(): React.Node {
|
||||
getSpeechBubble() {
|
||||
const {state, props} = this;
|
||||
return (
|
||||
<Animatable.View
|
||||
|
|
@ -179,7 +177,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
title={props.title}
|
||||
left={
|
||||
props.icon != null
|
||||
? (): React.Node => (
|
||||
? () => (
|
||||
<Avatar.Icon
|
||||
size={48}
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
|
|
@ -187,7 +185,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
icon={props.icon}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Card.Content
|
||||
|
|
@ -207,7 +205,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getMascot(): React.Node {
|
||||
getMascot() {
|
||||
const {props, state} = this;
|
||||
return (
|
||||
<Animatable.View
|
||||
|
|
@ -223,7 +221,7 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getButtons(): React.Node {
|
||||
getButtons() {
|
||||
const {props} = this;
|
||||
const {action} = props.buttons;
|
||||
const {cancel} = props.buttons;
|
||||
|
|
@ -270,12 +268,12 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
}
|
||||
|
||||
getBackground(): React.Node {
|
||||
getBackground() {
|
||||
const {props, state} = this;
|
||||
return (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={() => {
|
||||
this.onDismiss(props.buttons.cancel.onPress);
|
||||
this.onDismiss(props.buttons.cancel?.onPress);
|
||||
}}>
|
||||
<Animatable.View
|
||||
style={{
|
||||
|
|
@ -298,10 +296,12 @@ class MascotPopup extends React.Component<PropsType, StateType> {
|
|||
AsyncStorageManager.set(prefKey, false);
|
||||
this.setState({dialogVisible: false});
|
||||
}
|
||||
if (callback != null) callback();
|
||||
if (callback != null) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {shouldRenderDialog} = this.state;
|
||||
if (shouldRenderDialog) {
|
||||
return (
|
||||
|
|
@ -1,62 +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/>.
|
||||
*/
|
||||
|
||||
// @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,35 +17,32 @@
|
|||
* 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';
|
||||
|
||||
export type CellType = {color: string, isEmpty: boolean, key: string};
|
||||
import {View, ViewStyle} from 'react-native';
|
||||
|
||||
type PropsType = {
|
||||
cell: CellType,
|
||||
style?: ViewStyle;
|
||||
size: number;
|
||||
color: string;
|
||||
};
|
||||
|
||||
class CellComponent extends React.PureComponent<PropsType> {
|
||||
render(): React.Node {
|
||||
const {props} = this;
|
||||
const item = props.cell;
|
||||
return (
|
||||
export default function SpeechArrow(props: PropsType) {
|
||||
return (
|
||||
<View style={props.style}>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: item.isEmpty ? 'transparent' : item.color,
|
||||
borderColor: 'transparent',
|
||||
borderRadius: 4,
|
||||
borderWidth: 1,
|
||||
aspectRatio: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderLeftWidth: 0,
|
||||
borderRightWidth: props.size,
|
||||
borderBottomWidth: props.size,
|
||||
borderStyle: 'solid',
|
||||
backgroundColor: 'transparent',
|
||||
borderLeftColor: 'transparent',
|
||||
borderRightColor: 'transparent',
|
||||
borderBottomColor: props.color,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(CellComponent);
|
||||
|
|
@ -17,41 +17,36 @@
|
|||
* 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 type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import {ViewStyle} from 'react-native';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
images: Array<{url: string}>,
|
||||
style: ViewStyleProp,
|
||||
images: Array<{url: string}>;
|
||||
style: ViewStyle;
|
||||
};
|
||||
|
||||
class ImageGalleryButton extends React.Component<PropsType> {
|
||||
onPress = () => {
|
||||
const {navigation, images} = this.props;
|
||||
navigation.navigate('gallery', {images});
|
||||
function ImageGalleryButton(props: PropsType) {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const onPress = () => {
|
||||
navigation.navigate('gallery', {images: props.images});
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TouchableRipple onPress={onPress} style={props.style}>
|
||||
<Image
|
||||
resizeMode="contain"
|
||||
source={{uri: props.images[0].url}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
/>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
|
||||
export default ImageGalleryButton;
|
||||
|
|
@ -1,82 +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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
75
src/components/Overrides/CustomAgenda.tsx
Normal file
75
src/components/Overrides/CustomAgenda.tsx
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/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
|
@ -1,77 +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/>.
|
||||
*/
|
||||
|
||||
/* 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);
|
||||
68
src/components/Overrides/CustomHTML.tsx
Normal file
68
src/components/Overrides/CustomHTML.tsx
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/>.
|
||||
*/
|
||||
|
||||
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,39 +17,33 @@
|
|||
* 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, HeaderButtons} from 'react-navigation-header-buttons';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import {
|
||||
HeaderButton,
|
||||
HeaderButtonProps,
|
||||
HeaderButtons,
|
||||
HeaderButtonsProps,
|
||||
} from 'react-navigation-header-buttons';
|
||||
import {useTheme} from 'react-native-paper';
|
||||
|
||||
const MaterialHeaderButton = (props: {
|
||||
theme: CustomThemeType,
|
||||
color: string,
|
||||
}): React.Node => {
|
||||
const {color, theme} = props;
|
||||
const MaterialHeaderButton = (props: HeaderButtonProps) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
// $FlowFixMe
|
||||
<HeaderButton
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
IconComponent={MaterialCommunityIcons}
|
||||
iconSize={26}
|
||||
color={color != null ? color : theme.colors.text}
|
||||
color={props.color ? props.color : theme.colors.text}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MaterialHeaderButtons = (props: {...}): React.Node => {
|
||||
const MaterialHeaderButtons = (
|
||||
props: HeaderButtonsProps & {children?: React.ReactNode},
|
||||
) => {
|
||||
return (
|
||||
// $FlowFixMe
|
||||
<HeaderButtons
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
HeaderButtonComponent={withTheme(MaterialHeaderButton)}
|
||||
/>
|
||||
<HeaderButtons {...props} HeaderButtonComponent={MaterialHeaderButton} />
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -17,10 +17,14 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Platform, StatusBar, StyleSheet, View} from 'react-native';
|
||||
import {
|
||||
ListRenderItemInfo,
|
||||
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';
|
||||
|
|
@ -35,26 +39,27 @@ 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.Node,
|
||||
mascotStyle?: number,
|
||||
colors: [string, string],
|
||||
key: string;
|
||||
title: string;
|
||||
text: string;
|
||||
view: () => React.ReactNode;
|
||||
mascotStyle?: number;
|
||||
colors: [string, string];
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
mainContent: {
|
||||
flex: 1,
|
||||
paddingBottom: 100,
|
||||
},
|
||||
text: {
|
||||
|
|
@ -83,7 +88,7 @@ const styles = StyleSheet.create({
|
|||
*/
|
||||
export default class CustomIntroSlider extends React.Component<
|
||||
PropsType,
|
||||
StateType,
|
||||
StateType
|
||||
> {
|
||||
sliderRef: {current: null | AppIntroSlider};
|
||||
|
||||
|
|
@ -98,8 +103,9 @@ export default class CustomIntroSlider extends React.Component<
|
|||
/**
|
||||
* Generates intro slides
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.currentSlides = [];
|
||||
this.state = {
|
||||
currentSlide: 0,
|
||||
};
|
||||
|
|
@ -109,14 +115,14 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '0', // Mascot
|
||||
title: i18n.t('intro.slideMain.title'),
|
||||
text: i18n.t('intro.slideMain.text'),
|
||||
view: (): React.Node => <MascotIntroWelcome />,
|
||||
view: () => <MascotIntroWelcome />,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
title: i18n.t('intro.slidePlanex.title'),
|
||||
text: i18n.t('intro.slidePlanex.text'),
|
||||
view: (): React.Node => <IntroIcon icon="calendar-clock" />,
|
||||
view: () => <IntroIcon icon="calendar-clock" />,
|
||||
mascotStyle: MASCOT_STYLE.INTELLO,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
|
|
@ -124,7 +130,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '2',
|
||||
title: i18n.t('intro.slideEvents.title'),
|
||||
text: i18n.t('intro.slideEvents.text'),
|
||||
view: (): React.Node => <IntroIcon icon="calendar-star" />,
|
||||
view: () => <IntroIcon icon="calendar-star" />,
|
||||
mascotStyle: MASCOT_STYLE.HAPPY,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
|
|
@ -132,7 +138,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '3',
|
||||
title: i18n.t('intro.slideServices.title'),
|
||||
text: i18n.t('intro.slideServices.text'),
|
||||
view: (): React.Node => <IntroIcon icon="view-dashboard-variant" />,
|
||||
view: () => <IntroIcon icon="view-dashboard-variant" />,
|
||||
mascotStyle: MASCOT_STYLE.CUTE,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
|
|
@ -140,7 +146,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '4',
|
||||
title: i18n.t('intro.slideDone.title'),
|
||||
text: i18n.t('intro.slideDone.text'),
|
||||
view: (): React.Node => <MascotIntroEnd />,
|
||||
view: () => <MascotIntroEnd />,
|
||||
colors: ['#9c165b', '#3e042b'],
|
||||
},
|
||||
];
|
||||
|
|
@ -152,7 +158,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
key: '1',
|
||||
title: i18n.t('intro.aprilFoolsSlide.title'),
|
||||
text: i18n.t('intro.aprilFoolsSlide.text'),
|
||||
view: (): React.Node => <View />,
|
||||
view: () => <View />,
|
||||
mascotStyle: MASCOT_STYLE.NORMAL,
|
||||
colors: ['#e01928', '#be1522'],
|
||||
},
|
||||
|
|
@ -162,21 +168,21 @@ export default class CustomIntroSlider extends React.Component<
|
|||
/**
|
||||
* Render item to be used for the intro introSlides
|
||||
*
|
||||
* @param item The item to be displayed
|
||||
* @param dimensions Dimensions of the item
|
||||
* @param data
|
||||
*/
|
||||
getIntroRenderItem = ({
|
||||
item,
|
||||
dimensions,
|
||||
}: {
|
||||
item: IntroSlideType,
|
||||
dimensions: {width: number, height: number},
|
||||
}): React.Node => {
|
||||
getIntroRenderItem = (
|
||||
data:
|
||||
| (ListRenderItemInfo<IntroSlideType> & {
|
||||
dimensions: {width: number; height: number};
|
||||
})
|
||||
| ListRenderItemInfo<IntroSlideType>,
|
||||
) => {
|
||||
const item = data.item;
|
||||
const {state} = this;
|
||||
const index = parseInt(item.key, 10);
|
||||
return (
|
||||
<LinearGradient
|
||||
style={[styles.mainContent, dimensions]}
|
||||
style={[styles.mainContent]}
|
||||
colors={item.colors}
|
||||
start={{x: 0, y: 0.1}}
|
||||
end={{x: 0.1, y: 1}}>
|
||||
|
|
@ -254,7 +260,9 @@ 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) => {
|
||||
|
|
@ -266,8 +274,9 @@ 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 = () => {
|
||||
|
|
@ -278,7 +287,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
props.onDone();
|
||||
};
|
||||
|
||||
getRenderNextButton = (): React.Node => {
|
||||
getRenderNextButton = () => {
|
||||
return (
|
||||
<Animatable.View
|
||||
useNativeDriver
|
||||
|
|
@ -293,7 +302,7 @@ export default class CustomIntroSlider extends React.Component<
|
|||
);
|
||||
};
|
||||
|
||||
getRenderDoneButton = (): React.Node => {
|
||||
getRenderDoneButton = () => {
|
||||
return (
|
||||
<Animatable.View
|
||||
useNativeDriver
|
||||
|
|
@ -308,11 +317,14 @@ export default class CustomIntroSlider extends React.Component<
|
|||
);
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
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,14 +17,11 @@
|
|||
* 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 {useTheme} 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
|
||||
|
|
@ -33,11 +30,11 @@ import type {CustomThemeType} from '../../managers/ThemeManager';
|
|||
* @return {*}
|
||||
*/
|
||||
function CustomModal(props: {
|
||||
theme: CustomThemeType,
|
||||
onRef: (re: Modalize) => void,
|
||||
children?: React.Node,
|
||||
}): React.Node {
|
||||
const {theme, onRef, children} = props;
|
||||
onRef: (re: Modalize) => void;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const {onRef, children} = props;
|
||||
return (
|
||||
<Modalize
|
||||
ref={onRef}
|
||||
|
|
@ -55,6 +52,4 @@ function CustomModal(props: {
|
|||
);
|
||||
}
|
||||
|
||||
CustomModal.defaultProps = {children: null};
|
||||
|
||||
export default withTheme(CustomModal);
|
||||
export default CustomModal;
|
||||
|
|
@ -1,84 +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/>.
|
||||
*/
|
||||
|
||||
// @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);
|
||||
61
src/components/Overrides/CustomSlider.tsx
Normal file
61
src/components/Overrides/CustomSlider.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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,8 +21,11 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {ActivityIndicator, withTheme} from 'react-native-paper';
|
||||
import type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import {ActivityIndicator, useTheme} from 'react-native-paper';
|
||||
|
||||
type Props = {
|
||||
isAbsolute?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component used to display a header button
|
||||
|
|
@ -30,29 +33,21 @@ import type {CustomThemeType} from '../../managers/ThemeManager';
|
|||
* @param props Props to pass to the component
|
||||
* @return {*}
|
||||
*/
|
||||
function BasicLoadingScreen(props: {
|
||||
theme: CustomThemeType,
|
||||
isAbsolute: boolean,
|
||||
}): React.Node {
|
||||
const {theme, isAbsolute} = props;
|
||||
const {colors} = theme;
|
||||
let position;
|
||||
if (isAbsolute != null && isAbsolute) position = 'absolute';
|
||||
|
||||
export default function BasicLoadingScreen(props: Props) {
|
||||
const theme = useTheme();
|
||||
const {isAbsolute} = props;
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.background,
|
||||
position,
|
||||
backgroundColor: theme.colors.background,
|
||||
position: isAbsolute ? 'absolute' : 'relative',
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<ActivityIndicator animating size="large" color={colors.primary} />
|
||||
<ActivityIndicator animating size="large" color={theme.colors.primary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(BasicLoadingScreen);
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -27,17 +25,16 @@ 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,
|
||||
theme: CustomThemeType,
|
||||
route: {name: string},
|
||||
onRefresh?: () => void,
|
||||
errorCode?: number,
|
||||
icon?: string,
|
||||
message?: string,
|
||||
showRetryButton?: boolean,
|
||||
navigation?: StackNavigationProp<any>;
|
||||
theme: ReactNativePaper.Theme;
|
||||
route?: {name: string};
|
||||
onRefresh?: () => void;
|
||||
errorCode?: number;
|
||||
icon?: string;
|
||||
message?: string;
|
||||
showRetryButton?: boolean;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
|
@ -82,9 +79,11 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.icon = '';
|
||||
this.showLoginButton = false;
|
||||
this.message = '';
|
||||
}
|
||||
|
||||
getRetryButton(): React.Node {
|
||||
getRetryButton() {
|
||||
const {props} = this;
|
||||
return (
|
||||
<Button
|
||||
|
|
@ -97,7 +96,7 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
getLoginButton(): React.Node {
|
||||
getLoginButton() {
|
||||
return (
|
||||
<Button
|
||||
mode="contained"
|
||||
|
|
@ -111,10 +110,12 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
|
||||
goToLogin = () => {
|
||||
const {props} = this;
|
||||
props.navigation.navigate('login', {
|
||||
screen: 'login',
|
||||
params: {nextScreen: props.route.name},
|
||||
});
|
||||
if (props.navigation) {
|
||||
props.navigation.navigate('login', {
|
||||
screen: 'login',
|
||||
params: {nextScreen: props.route ? props.route.name : undefined},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
generateMessage() {
|
||||
|
|
@ -169,13 +170,17 @@ class ErrorView extends React.PureComponent<PropsType> {
|
|||
}
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
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,12 +17,15 @@
|
|||
* 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 {RefreshControl, View} from 'react-native';
|
||||
import {
|
||||
NativeSyntheticEvent,
|
||||
RefreshControl,
|
||||
SectionListData,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import {Collapsible} from 'react-navigation-collapsible';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
|
|
@ -32,42 +35,44 @@ 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<T> = Array<{
|
||||
title: string,
|
||||
data: Array<T>,
|
||||
keyExtractor?: (T) => string,
|
||||
export type SectionListDataType<ItemT> = Array<{
|
||||
title: string;
|
||||
icon?: string;
|
||||
data: Array<ItemT>;
|
||||
keyExtractor?: (data: ItemT) => string;
|
||||
}>;
|
||||
|
||||
type PropsType<T> = {
|
||||
navigation: StackNavigationProp,
|
||||
fetchUrl: string,
|
||||
autoRefreshTime: number,
|
||||
refreshOnFocus: boolean,
|
||||
renderItem: (data: {item: T}) => React.Node,
|
||||
type PropsType<ItemT, RawData> = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
fetchUrl: string;
|
||||
autoRefreshTime: number;
|
||||
refreshOnFocus: boolean;
|
||||
renderItem: (data: {item: ItemT}) => React.ReactNode;
|
||||
createDataset: (
|
||||
data: ApiGenericDataType | null,
|
||||
data: RawData | null,
|
||||
isLoading?: boolean,
|
||||
) => SectionListDataType<T>,
|
||||
onScroll: (event: SyntheticEvent<EventTarget>) => void,
|
||||
collapsibleStack: Collapsible,
|
||||
) => SectionListDataType<ItemT>;
|
||||
onScroll: (event: NativeSyntheticEvent<EventTarget>) => void;
|
||||
collapsibleStack: Collapsible;
|
||||
|
||||
showError?: boolean,
|
||||
itemHeight?: number | null,
|
||||
updateData?: number,
|
||||
renderListHeaderComponent?: (data: ApiGenericDataType | null) => React.Node,
|
||||
showError?: boolean;
|
||||
itemHeight?: number | null;
|
||||
updateData?: number;
|
||||
renderListHeaderComponent?: (
|
||||
data: RawData | null,
|
||||
) => React.ComponentType<any> | React.ReactElement | null;
|
||||
renderSectionHeader?: (
|
||||
data: {section: {title: string}},
|
||||
data: {section: SectionListData<ItemT>},
|
||||
isLoading?: boolean,
|
||||
) => React.Node,
|
||||
stickyHeader?: boolean,
|
||||
) => React.ReactElement | null;
|
||||
stickyHeader?: boolean;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
refreshing: boolean,
|
||||
fetchedData: ApiGenericDataType | null,
|
||||
snackbarVisible: boolean,
|
||||
type StateType<RawData> = {
|
||||
refreshing: boolean;
|
||||
fetchedData: RawData | null;
|
||||
snackbarVisible: boolean;
|
||||
};
|
||||
|
||||
const MIN_REFRESH_TIME = 5 * 1000;
|
||||
|
|
@ -78,22 +83,25 @@ 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<T> extends React.PureComponent<PropsType<T>, StateType> {
|
||||
class WebSectionList<ItemT, RawData> extends React.PureComponent<
|
||||
PropsType<ItemT, RawData>,
|
||||
StateType<RawData>
|
||||
> {
|
||||
static defaultProps = {
|
||||
showError: true,
|
||||
itemHeight: null,
|
||||
updateData: 0,
|
||||
renderListHeaderComponent: (): React.Node => null,
|
||||
renderSectionHeader: (): React.Node => null,
|
||||
renderListHeaderComponent: () => null,
|
||||
renderSectionHeader: () => null,
|
||||
stickyHeader: false,
|
||||
};
|
||||
|
||||
refreshInterval: IntervalID;
|
||||
refreshInterval: NodeJS.Timeout | undefined;
|
||||
|
||||
lastRefresh: Date | null;
|
||||
lastRefresh: Date | undefined;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: PropsType<ItemT, RawData>) {
|
||||
super(props);
|
||||
this.state = {
|
||||
refreshing: false,
|
||||
fetchedData: null,
|
||||
|
|
@ -109,7 +117,7 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
|||
const {navigation} = this.props;
|
||||
navigation.addListener('focus', this.onScreenFocus);
|
||||
navigation.addListener('blur', this.onScreenBlur);
|
||||
this.lastRefresh = null;
|
||||
this.lastRefresh = undefined;
|
||||
this.onRefresh();
|
||||
}
|
||||
|
||||
|
|
@ -121,15 +129,18 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
|||
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 = () => {
|
||||
clearInterval(this.refreshInterval);
|
||||
if (this.refreshInterval) {
|
||||
clearInterval(this.refreshInterval);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -138,7 +149,7 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
|||
*
|
||||
* @param fetchedData The newly fetched data
|
||||
*/
|
||||
onFetchSuccess = (fetchedData: ApiGenericDataType) => {
|
||||
onFetchSuccess = (fetchedData: RawData) => {
|
||||
this.setState({
|
||||
fetchedData,
|
||||
refreshing: false,
|
||||
|
|
@ -167,7 +178,9 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
|||
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);
|
||||
|
|
@ -189,19 +202,18 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
|||
};
|
||||
|
||||
getItemLayout = (
|
||||
data: T,
|
||||
height: number,
|
||||
data: Array<SectionListData<ItemT>> | null,
|
||||
index: number,
|
||||
): {length: number, offset: number, index: number} | null => {
|
||||
const {itemHeight} = this.props;
|
||||
if (itemHeight == null) return null;
|
||||
): {length: number; offset: number; index: number} => {
|
||||
return {
|
||||
length: itemHeight,
|
||||
offset: itemHeight * index,
|
||||
length: height,
|
||||
offset: height * index,
|
||||
index,
|
||||
};
|
||||
};
|
||||
|
||||
getRenderSectionHeader = (data: {section: {title: string}}): React.Node => {
|
||||
getRenderSectionHeader = (data: {section: SectionListData<ItemT>}) => {
|
||||
const {renderSectionHeader} = this.props;
|
||||
const {refreshing} = this.state;
|
||||
if (renderSectionHeader != null) {
|
||||
|
|
@ -214,7 +226,7 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
|||
return null;
|
||||
};
|
||||
|
||||
getRenderItem = (data: {item: T}): React.Node => {
|
||||
getRenderItem = (data: {item: ItemT}) => {
|
||||
const {renderItem} = this.props;
|
||||
return (
|
||||
<Animatable.View animation="fadeInUp" duration={500} useNativeDriver>
|
||||
|
|
@ -223,19 +235,23 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
|||
);
|
||||
};
|
||||
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
onScroll = (event: NativeSyntheticEvent<EventTarget>) => {
|
||||
const {onScroll} = this.props;
|
||||
if (onScroll != null) onScroll(event);
|
||||
if (onScroll != null) {
|
||||
onScroll(event);
|
||||
}
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props, state} = this;
|
||||
let dataset = [];
|
||||
const {itemHeight} = props;
|
||||
let dataset: SectionListDataType<ItemT> = [];
|
||||
if (
|
||||
state.fetchedData != null ||
|
||||
(state.fetchedData == null && !props.showError)
|
||||
)
|
||||
) {
|
||||
dataset = props.createDataset(state.fetchedData, state.refreshing);
|
||||
}
|
||||
|
||||
const {containerPaddingTop} = props.collapsibleStack;
|
||||
return (
|
||||
|
|
@ -270,7 +286,11 @@ class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
|
|||
/>
|
||||
)
|
||||
}
|
||||
getItemLayout={props.itemHeight != null ? this.getItemLayout : null}
|
||||
getItemLayout={
|
||||
itemHeight
|
||||
? (data, index) => this.getItemLayout(itemHeight, data, index)
|
||||
: undefined
|
||||
}
|
||||
onScroll={this.onScroll}
|
||||
hasTab
|
||||
/>
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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 {
|
||||
|
|
@ -27,12 +25,17 @@ import {
|
|||
OverflowMenu,
|
||||
} from 'react-navigation-header-buttons';
|
||||
import i18n from 'i18n-js';
|
||||
import {Animated, BackHandler, Linking} from 'react-native';
|
||||
import {
|
||||
Animated,
|
||||
BackHandler,
|
||||
Linking,
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
} 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';
|
||||
|
|
@ -40,15 +43,15 @@ import ErrorView from './ErrorView';
|
|||
import BasicLoadingScreen from './BasicLoadingScreen';
|
||||
|
||||
type PropsType = {
|
||||
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,
|
||||
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;
|
||||
};
|
||||
|
||||
const AnimatedWebView = Animated.createAnimatedComponent(WebView);
|
||||
|
|
@ -63,14 +66,17 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
customPaddingFunction: null,
|
||||
};
|
||||
|
||||
currentUrl: string;
|
||||
|
||||
webviewRef: {current: null | WebView};
|
||||
|
||||
canGoBack: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.webviewRef = React.createRef();
|
||||
this.canGoBack = false;
|
||||
this.currentUrl = props.url;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -115,7 +121,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getBasicButton = (): React.Node => {
|
||||
getBasicButton = () => {
|
||||
return (
|
||||
<MaterialHeaderButtons>
|
||||
<Item
|
||||
|
|
@ -138,7 +144,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getAdvancedButtons = (): React.Node => {
|
||||
getAdvancedButtons = () => {
|
||||
const {props} = this;
|
||||
return (
|
||||
<MaterialHeaderButtons>
|
||||
|
|
@ -179,7 +185,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getRenderLoading = (): React.Node => <BasicLoadingScreen isAbsolute />;
|
||||
getRenderLoading = () => <BasicLoadingScreen isAbsolute />;
|
||||
|
||||
/**
|
||||
* Gets the javascript needed to generate a padding on top of the page
|
||||
|
|
@ -201,25 +207,32 @@ 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 = () => {
|
||||
const {url} = this.props;
|
||||
Linking.openURL(url);
|
||||
Linking.openURL(this.currentUrl);
|
||||
};
|
||||
|
||||
onScroll = (event: SyntheticEvent<EventTarget>) => {
|
||||
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
const {onScroll} = this.props;
|
||||
if (onScroll) onScroll(event);
|
||||
if (onScroll) {
|
||||
onScroll(event);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -228,11 +241,12 @@ 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(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
const {containerPaddingTop, onScrollWithListener} = props.collapsibleStack;
|
||||
return (
|
||||
|
|
@ -243,13 +257,14 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
injectedJavaScript={props.customJS}
|
||||
javaScriptEnabled
|
||||
renderLoading={this.getRenderLoading}
|
||||
renderError={(): React.Node => (
|
||||
renderError={() => (
|
||||
<ErrorView
|
||||
errorCode={ERROR_TYPE.CONNECTION_ERROR}
|
||||
onRefresh={this.onRefreshClicked}
|
||||
/>
|
||||
)}
|
||||
onNavigationStateChange={(navState: {canGoBack: boolean}) => {
|
||||
onNavigationStateChange={(navState) => {
|
||||
this.currentUrl = navState.url;
|
||||
this.canGoBack = navState.canGoBack;
|
||||
}}
|
||||
onMessage={props.onMessage}
|
||||
|
|
@ -257,7 +272,7 @@ class WebViewScreen extends React.PureComponent<PropsType> {
|
|||
this.injectJavaScript(this.getJavascriptPadding(containerPaddingTop));
|
||||
}}
|
||||
// Animations
|
||||
onScroll={onScrollWithListener(this.onScroll)}
|
||||
onScroll={(event) => onScrollWithListener(this.onScroll)(event)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,49 +17,33 @@
|
|||
* 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 type {CustomThemeType} from '../../managers/ThemeManager';
|
||||
import {BottomTabBarProps} from '@react-navigation/bottom-tabs';
|
||||
import {NavigationState} from '@react-navigation/native';
|
||||
import {
|
||||
PartialState,
|
||||
Route,
|
||||
} from '@react-navigation/routers/lib/typescript/src/types';
|
||||
|
||||
type RouteType = {
|
||||
name: string,
|
||||
key: string,
|
||||
params: {collapsible: Collapsible},
|
||||
state: {
|
||||
index: number,
|
||||
routes: Array<RouteType>,
|
||||
},
|
||||
type RouteType = Route<string> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
};
|
||||
|
||||
type PropsType = {
|
||||
state: {
|
||||
index: number,
|
||||
routes: Array<RouteType>,
|
||||
},
|
||||
descriptors: {
|
||||
[key: string]: {
|
||||
options: {
|
||||
tabBarLabel: string,
|
||||
title: string,
|
||||
},
|
||||
},
|
||||
},
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomThemeType,
|
||||
};
|
||||
interface PropsType extends BottomTabBarProps {
|
||||
theme: ReactNativePaper.Theme;
|
||||
}
|
||||
|
||||
type StateType = {
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
translateY: any,
|
||||
translateY: any;
|
||||
};
|
||||
|
||||
type validRoutes = 'proxiwash' | 'services' | 'planning' | 'planex';
|
||||
|
||||
const TAB_ICONS = {
|
||||
proxiwash: 'tshirt-crew',
|
||||
services: 'account-circle',
|
||||
|
|
@ -70,11 +54,13 @@ const TAB_ICONS = {
|
|||
class CustomTabBar extends React.Component<PropsType, StateType> {
|
||||
static TAB_BAR_HEIGHT = 48;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.state = {
|
||||
translateY: new Animated.Value(0),
|
||||
};
|
||||
// @ts-ignore
|
||||
props.navigation.addListener('state', this.onRouteChange);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -86,13 +72,9 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
*/
|
||||
onItemPress(route: RouteType, currentIndex: number, destIndex: number) {
|
||||
const {navigation} = this.props;
|
||||
const event = navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
if (currentIndex !== destIndex && !event.defaultPrevented)
|
||||
if (currentIndex !== destIndex) {
|
||||
navigation.navigate(route.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -102,13 +84,9 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
*/
|
||||
onItemLongPress(route: RouteType) {
|
||||
const {navigation} = this.props;
|
||||
const event = navigation.emit({
|
||||
type: 'tabLongPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
});
|
||||
if (route.name === 'home' && !event.defaultPrevented)
|
||||
if (route.name === 'home') {
|
||||
navigation.navigate('game-start');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -126,11 +104,13 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
* @param focused
|
||||
* @returns {null}
|
||||
*/
|
||||
getTabBarIcon = (route: RouteType, focused: boolean): React.Node => {
|
||||
let icon = TAB_ICONS[route.name];
|
||||
getTabBarIcon = (route: RouteType, focused: boolean) => {
|
||||
let icon = TAB_ICONS[route.name as validRoutes];
|
||||
icon = focused ? icon : `${icon}-outline`;
|
||||
if (route.name !== 'home') return icon;
|
||||
return null;
|
||||
if (route.name !== 'home') {
|
||||
return icon;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -141,14 +121,18 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
* @param index The index of the current route
|
||||
* @returns {*}
|
||||
*/
|
||||
getRenderIcon = (route: RouteType, index: number): React.Node => {
|
||||
getRenderIcon = (route: RouteType, index: number) => {
|
||||
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);
|
||||
|
|
@ -168,7 +152,7 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
onLongPress={onLongPress}
|
||||
icon={this.getTabBarIcon(route, isFocused)}
|
||||
color={color}
|
||||
label={label}
|
||||
label={label as string}
|
||||
focused={isFocused}
|
||||
extraData={state.index > index}
|
||||
key={route.key}
|
||||
|
|
@ -186,7 +170,7 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
};
|
||||
|
||||
getIcons(): React.Node {
|
||||
getIcons() {
|
||||
const {props} = this;
|
||||
return props.state.routes.map(this.getRenderIcon);
|
||||
}
|
||||
|
|
@ -197,9 +181,12 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
if (isFocused) {
|
||||
const stackState = route.state;
|
||||
const stackRoute =
|
||||
stackState != null ? stackState.routes[stackState.index] : null;
|
||||
const params: {collapsible: Collapsible} | null =
|
||||
stackRoute != null ? stackRoute.params : null;
|
||||
stackState && stackState.index != null
|
||||
? stackState.routes[stackState.index]
|
||||
: null;
|
||||
const params: {collapsible: Collapsible} | null | undefined = stackRoute
|
||||
? (stackRoute.params as {collapsible: Collapsible})
|
||||
: null;
|
||||
const collapsible = params != null ? params.collapsible : null;
|
||||
if (collapsible != null) {
|
||||
this.setState({
|
||||
|
|
@ -209,14 +196,11 @@ class CustomTabBar extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
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,20 +17,18 @@
|
|||
* 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';
|
||||
import FOCUSED_ICON from '../../../assets/tab-icon.png';
|
||||
import UNFOCUSED_ICON from '../../../assets/tab-icon-outline.png';
|
||||
const FOCUSED_ICON = require('../../../assets/tab-icon.png');
|
||||
const UNFOCUSED_ICON = require('../../../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);
|
||||
|
|
@ -44,6 +42,7 @@ class TabHomeIcon extends React.Component<PropsType> {
|
|||
Animatable.initializeRegistryWithDefinitions({
|
||||
fabFocusIn: {
|
||||
'0': {
|
||||
// @ts-ignore
|
||||
scale: 1,
|
||||
translateY: 0,
|
||||
},
|
||||
|
|
@ -58,6 +57,7 @@ class TabHomeIcon extends React.Component<PropsType> {
|
|||
},
|
||||
fabFocusOut: {
|
||||
'0': {
|
||||
// @ts-ignore
|
||||
scale: 1.1,
|
||||
translateY: -6,
|
||||
},
|
||||
|
|
@ -74,13 +74,7 @@ class TabHomeIcon extends React.Component<PropsType> {
|
|||
return nextProps.focused !== focused;
|
||||
}
|
||||
|
||||
getIconRender = ({
|
||||
size,
|
||||
color,
|
||||
}: {
|
||||
size: number,
|
||||
color: string,
|
||||
}): React.Node => {
|
||||
getIconRender = ({size, color}: {size: number; color: string}) => {
|
||||
const {focused} = this.props;
|
||||
return (
|
||||
<Image
|
||||
|
|
@ -94,7 +88,7 @@ class TabHomeIcon extends React.Component<PropsType> {
|
|||
);
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
return (
|
||||
<View
|
||||
|
|
@ -17,25 +17,21 @@
|
|||
* 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: MaterialCommunityIconsGlyphs,
|
||||
onPress: () => void,
|
||||
onLongPress: () => void,
|
||||
theme: CustomThemeType,
|
||||
extraData: null | boolean | number | string,
|
||||
focused: boolean;
|
||||
color: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
onPress: () => void;
|
||||
onLongPress: () => void;
|
||||
theme: ReactNativePaper.Theme;
|
||||
extraData: null | boolean | number | string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -49,6 +45,7 @@ class TabIcon extends React.Component<PropsType> {
|
|||
Animatable.initializeRegistryWithDefinitions({
|
||||
focusIn: {
|
||||
'0': {
|
||||
// @ts-ignore
|
||||
scale: 1,
|
||||
translateY: 0,
|
||||
},
|
||||
|
|
@ -63,6 +60,7 @@ class TabIcon extends React.Component<PropsType> {
|
|||
},
|
||||
focusOut: {
|
||||
'0': {
|
||||
// @ts-ignore
|
||||
scale: 1.2,
|
||||
translateY: 6,
|
||||
},
|
||||
|
|
@ -88,7 +86,7 @@ class TabIcon extends React.Component<PropsType> {
|
|||
);
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {props} = this;
|
||||
return (
|
||||
<TouchableRipple
|
||||
|
|
@ -99,7 +97,7 @@ class TabIcon extends React.Component<PropsType> {
|
|||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
borderRadius: 10
|
||||
borderRadius: 10,
|
||||
}}>
|
||||
<View>
|
||||
<Animatable.View
|
||||
|
|
@ -19,14 +19,16 @@
|
|||
|
||||
// @flow
|
||||
|
||||
import ICON_AMICALE from '../../assets/amicale.png';
|
||||
import ICON_CAMPUS from '../../assets/android.icon.png';
|
||||
const ICON_AMICALE = require('../../assets/amicale.png');
|
||||
const ICON_CAMPUS = require('../../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,
|
||||
|
|
@ -17,25 +17,26 @@
|
|||
* 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 {
|
||||
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',
|
||||
},
|
||||
stateIcons: [
|
||||
'radiobox-blank',
|
||||
'progress-check',
|
||||
'alert-circle-outline',
|
||||
'check-circle',
|
||||
'alert-octagram-outline',
|
||||
'alert',
|
||||
'help-circle-outline',
|
||||
],
|
||||
washinsa: {
|
||||
id: 'washinsa',
|
||||
title: 'screens.proxiwash.washinsa.title',
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -49,16 +47,16 @@ export default class Update {
|
|||
this.updateSlides = [
|
||||
{
|
||||
key: '0',
|
||||
title: i18n.t(`intro.updateSlide0.title`),
|
||||
text: i18n.t(`intro.updateSlide0.text`),
|
||||
view: (): React.Node => <MascotIntroWelcome />,
|
||||
title: i18n.t('intro.updateSlide0.title'),
|
||||
text: i18n.t('intro.updateSlide0.text'),
|
||||
view: () => <MascotIntroWelcome />,
|
||||
colors: ['#be1522', '#57080e'],
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
title: i18n.t(`intro.updateSlide1.title`),
|
||||
text: i18n.t(`intro.updateSlide1.text`),
|
||||
view: (): React.Node => <IntroIcon icon="account-heart-outline" />,
|
||||
title: i18n.t('intro.updateSlide1.title'),
|
||||
text: i18n.t('intro.updateSlide1.text'),
|
||||
view: () => <IntroIcon icon="account-heart-outline" />,
|
||||
colors: ['#9c165b', '#3e042b'],
|
||||
},
|
||||
];
|
||||
|
|
@ -17,10 +17,7 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import type {ProxiwashMachineType} from '../screens/Proxiwash/ProxiwashScreen';
|
||||
import type {CustomThemeType} from './ThemeManager';
|
||||
import type {RuFoodCategoryType} from '../screens/Services/SelfMenuScreen';
|
||||
|
||||
/**
|
||||
|
|
@ -57,8 +54,9 @@ export default class AprilFoolsManager {
|
|||
* @returns {ThemeManager}
|
||||
*/
|
||||
static getInstance(): AprilFoolsManager {
|
||||
if (AprilFoolsManager.instance == null)
|
||||
if (AprilFoolsManager.instance == null) {
|
||||
AprilFoolsManager.instance = new AprilFoolsManager();
|
||||
}
|
||||
return AprilFoolsManager.instance;
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +128,9 @@ export default class AprilFoolsManager {
|
|||
* @param currentTheme
|
||||
* @returns {{colors: {textDisabled: string, agendaDayTextColor: string, surface: string, background: string, dividerBackground: string, accent: string, agendaBackgroundColor: string, tabIcon: string, card: string, primary: string}}}
|
||||
*/
|
||||
static getAprilFoolsTheme(currentTheme: CustomThemeType): CustomThemeType {
|
||||
static getAprilFoolsTheme(
|
||||
currentTheme: ReactNativePaper.Theme,
|
||||
): ReactNativePaper.Theme {
|
||||
return {
|
||||
...currentTheme,
|
||||
colors: {
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
||||
|
|
@ -31,7 +29,7 @@ import {SERVICES_KEY} from './ServicesManager';
|
|||
export default class AsyncStorageManager {
|
||||
static instance: AsyncStorageManager | null = null;
|
||||
|
||||
static PREFERENCES = {
|
||||
static PREFERENCES: {[key: string]: {key: string; default: string}} = {
|
||||
debugUnlocked: {
|
||||
key: 'debugUnlocked',
|
||||
default: '0',
|
||||
|
|
@ -132,10 +130,10 @@ export default class AsyncStorageManager {
|
|||
},
|
||||
};
|
||||
|
||||
#currentPreferences: {[key: string]: string};
|
||||
private currentPreferences: {[key: string]: string};
|
||||
|
||||
constructor() {
|
||||
this.#currentPreferences = {};
|
||||
this.currentPreferences = {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -143,8 +141,9 @@ export default class AsyncStorageManager {
|
|||
* @returns {AsyncStorageManager}
|
||||
*/
|
||||
static getInstance(): AsyncStorageManager {
|
||||
if (AsyncStorageManager.instance == null)
|
||||
if (AsyncStorageManager.instance == null) {
|
||||
AsyncStorageManager.instance = new AsyncStorageManager();
|
||||
}
|
||||
return AsyncStorageManager.instance;
|
||||
}
|
||||
|
||||
|
|
@ -156,8 +155,7 @@ export default class AsyncStorageManager {
|
|||
*/
|
||||
static set(
|
||||
key: string,
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
value: number | string | boolean | {...} | Array<any>,
|
||||
value: number | string | boolean | object | Array<any>,
|
||||
) {
|
||||
AsyncStorageManager.getInstance().setPreference(key, value);
|
||||
}
|
||||
|
|
@ -200,8 +198,7 @@ export default class AsyncStorageManager {
|
|||
* @param key
|
||||
* @returns {{...}}
|
||||
*/
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
static getObject(key: string): any {
|
||||
static getObject<T>(key: string): T {
|
||||
return JSON.parse(AsyncStorageManager.getString(key));
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +209,7 @@ export default class AsyncStorageManager {
|
|||
* @return {Promise<void>}
|
||||
*/
|
||||
async loadPreferences() {
|
||||
const prefKeys = [];
|
||||
const prefKeys: Array<string> = [];
|
||||
// Get all available keys
|
||||
Object.keys(AsyncStorageManager.PREFERENCES).forEach((key: string) => {
|
||||
prefKeys.push(key);
|
||||
|
|
@ -223,8 +220,10 @@ 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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -237,16 +236,18 @@ export default class AsyncStorageManager {
|
|||
*/
|
||||
setPreference(
|
||||
key: string,
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
value: number | string | boolean | {...} | Array<any>,
|
||||
value: number | string | boolean | object | 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -259,6 +260,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, ApiGenericDataType} from '../utils/WebData';
|
||||
import type {ApiDataLoginType} from '../utils/WebData';
|
||||
import {apiRequest, ERROR_TYPE} from '../utils/WebData';
|
||||
|
||||
/**
|
||||
|
|
@ -40,12 +40,10 @@ const AUTH_PATH = 'password';
|
|||
export default class ConnectionManager {
|
||||
static instance: ConnectionManager | null = null;
|
||||
|
||||
#email: string;
|
||||
|
||||
#token: string | null;
|
||||
private token: string | null;
|
||||
|
||||
constructor() {
|
||||
this.#token = null;
|
||||
this.token = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,8 +52,9 @@ export default class ConnectionManager {
|
|||
* @returns {ConnectionManager}
|
||||
*/
|
||||
static getInstance(): ConnectionManager {
|
||||
if (ConnectionManager.instance == null)
|
||||
if (ConnectionManager.instance == null) {
|
||||
ConnectionManager.instance = new ConnectionManager();
|
||||
}
|
||||
return ConnectionManager.instance;
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +64,7 @@ export default class ConnectionManager {
|
|||
* @returns {string | null}
|
||||
*/
|
||||
getToken(): string | null {
|
||||
return this.#token;
|
||||
return this.token;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,18 +76,17 @@ export default class ConnectionManager {
|
|||
return new Promise(
|
||||
(resolve: (token: string) => void, reject: () => void) => {
|
||||
const token = this.getToken();
|
||||
if (token != null) resolve(token);
|
||||
else {
|
||||
if (token != null) {
|
||||
resolve(token);
|
||||
} else {
|
||||
Keychain.getInternetCredentials(SERVER_NAME)
|
||||
.then((data: Keychain.UserCredentials | false) => {
|
||||
if (
|
||||
data != null &&
|
||||
data.password != null &&
|
||||
typeof data.password === 'string'
|
||||
) {
|
||||
this.#token = data.password;
|
||||
resolve(this.#token);
|
||||
} else reject();
|
||||
if (data && data.password != null) {
|
||||
this.token = data.password;
|
||||
resolve(this.token);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
})
|
||||
.catch((): void => reject());
|
||||
}
|
||||
|
|
@ -116,8 +114,7 @@ export default class ConnectionManager {
|
|||
return new Promise((resolve: () => void, reject: () => void) => {
|
||||
Keychain.setInternetCredentials(SERVER_NAME, 'token', token)
|
||||
.then(() => {
|
||||
this.#token = token;
|
||||
this.#email = email;
|
||||
this.token = token;
|
||||
resolve();
|
||||
})
|
||||
.catch((): void => reject());
|
||||
|
|
@ -133,7 +130,7 @@ export default class ConnectionManager {
|
|||
return new Promise((resolve: () => void, reject: () => void) => {
|
||||
Keychain.resetInternetCredentials(SERVER_NAME)
|
||||
.then(() => {
|
||||
this.#token = null;
|
||||
this.token = null;
|
||||
resolve();
|
||||
})
|
||||
.catch((): void => reject());
|
||||
|
|
@ -156,13 +153,15 @@ export default class ConnectionManager {
|
|||
email,
|
||||
password,
|
||||
};
|
||||
apiRequest(AUTH_PATH, 'POST', data)
|
||||
apiRequest<ApiDataLoginType>(AUTH_PATH, 'POST', data)
|
||||
.then((response: ApiDataLoginType) => {
|
||||
if (response.token != null) {
|
||||
this.saveLogin(email, response.token)
|
||||
.then((): void => resolve())
|
||||
.catch((): void => reject(ERROR_TYPE.TOKEN_SAVE));
|
||||
} else reject(ERROR_TYPE.SERVER_ERROR);
|
||||
} else {
|
||||
reject(ERROR_TYPE.SERVER_ERROR);
|
||||
}
|
||||
})
|
||||
.catch((error: number): void => reject(error));
|
||||
},
|
||||
|
|
@ -176,24 +175,23 @@ export default class ConnectionManager {
|
|||
* @param params
|
||||
* @returns Promise<ApiGenericDataType>
|
||||
*/
|
||||
async authenticatedRequest(
|
||||
async authenticatedRequest<T>(
|
||||
path: string,
|
||||
params: {...},
|
||||
): Promise<ApiGenericDataType> {
|
||||
params: {[key: string]: any},
|
||||
): Promise<T> {
|
||||
return new Promise(
|
||||
(
|
||||
resolve: (response: ApiGenericDataType) => void,
|
||||
reject: (error: number) => void,
|
||||
) => {
|
||||
(resolve: (response: T) => void, reject: (error: number) => void) => {
|
||||
if (this.getToken() !== null) {
|
||||
const data = {
|
||||
...params,
|
||||
token: this.getToken(),
|
||||
};
|
||||
apiRequest(path, 'POST', data)
|
||||
.then((response: ApiGenericDataType): void => resolve(response))
|
||||
apiRequest<T>(path, 'POST', data)
|
||||
.then((response: T): void => resolve(response))
|
||||
.catch((error: number): void => reject(error));
|
||||
} else reject(ERROR_TYPE.TOKEN_RETRIEVE);
|
||||
} else {
|
||||
reject(ERROR_TYPE.TOKEN_RETRIEVE);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -21,12 +21,12 @@
|
|||
|
||||
import type {ServiceItemType} from './ServicesManager';
|
||||
import ServicesManager from './ServicesManager';
|
||||
import {getSublistWithIds} from '../utils/Utils';
|
||||
import {getSublistWithIds} from '../utils/Services';
|
||||
import AsyncStorageManager from './AsyncStorageManager';
|
||||
|
||||
export default class DashboardManager extends ServicesManager {
|
||||
getCurrentDashboard(): Array<ServiceItemType | null> {
|
||||
const dashboardIdList = AsyncStorageManager.getObject(
|
||||
const dashboardIdList = AsyncStorageManager.getObject<Array<string>>(
|
||||
AsyncStorageManager.PREFERENCES.dashboardItems.key,
|
||||
);
|
||||
const allDatasets = [
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import i18n from 'i18n-js';
|
||||
|
||||
/**
|
||||
|
|
@ -28,9 +26,9 @@ import i18n from 'i18n-js';
|
|||
export default class DateManager {
|
||||
static instance: DateManager | null = null;
|
||||
|
||||
daysOfWeek = [];
|
||||
daysOfWeek: Array<string> = [];
|
||||
|
||||
monthsOfYear = [];
|
||||
monthsOfYear: Array<string> = [];
|
||||
|
||||
constructor() {
|
||||
this.daysOfWeek.push(i18n.t('date.daysOfWeek.sunday')); // 0 represents sunday
|
||||
|
|
@ -60,7 +58,9 @@ export default class DateManager {
|
|||
* @returns {DateManager}
|
||||
*/
|
||||
static getInstance(): DateManager {
|
||||
if (DateManager.instance == null) DateManager.instance = new DateManager();
|
||||
if (DateManager.instance == null) {
|
||||
DateManager.instance = new DateManager();
|
||||
}
|
||||
return DateManager.instance;
|
||||
}
|
||||
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @flow
|
||||
|
||||
import i18n from 'i18n-js';
|
||||
import {StackNavigationProp} from '@react-navigation/stack';
|
||||
import AvailableWebsites from '../constants/AvailableWebsites';
|
||||
|
|
@ -93,24 +91,24 @@ export const SERVICES_CATEGORIES_KEY = {
|
|||
};
|
||||
|
||||
export type ServiceItemType = {
|
||||
key: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
image: string,
|
||||
onPress: () => void,
|
||||
badgeFunction?: (dashboard: FullDashboardType) => number,
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
image: string | number;
|
||||
onPress: () => void;
|
||||
badgeFunction?: (dashboard: FullDashboardType) => number;
|
||||
};
|
||||
|
||||
export type ServiceCategoryType = {
|
||||
key: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
image: string | number,
|
||||
content: Array<ServiceItemType>,
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
image: string | number;
|
||||
content: Array<ServiceItemType>;
|
||||
};
|
||||
|
||||
export default class ServicesManager {
|
||||
navigation: StackNavigationProp;
|
||||
navigation: StackNavigationProp<any>;
|
||||
|
||||
amicaleDataset: Array<ServiceItemType>;
|
||||
|
||||
|
|
@ -122,7 +120,7 @@ export default class ServicesManager {
|
|||
|
||||
categoriesDataset: Array<ServiceCategoryType>;
|
||||
|
||||
constructor(nav: StackNavigationProp) {
|
||||
constructor(nav: StackNavigationProp<any>) {
|
||||
this.navigation = nav;
|
||||
this.amicaleDataset = [
|
||||
{
|
||||
|
|
@ -336,9 +334,11 @@ export default class ServicesManager {
|
|||
* @returns {null}
|
||||
*/
|
||||
onAmicaleServicePress(route: string) {
|
||||
if (ConnectionManager.getInstance().isLoggedIn())
|
||||
if (ConnectionManager.getInstance().isLoggedIn()) {
|
||||
this.navigation.navigate(route);
|
||||
else this.navigation.navigate('login', {nextScreen: route});
|
||||
} else {
|
||||
this.navigation.navigate('login', {nextScreen: route});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -348,8 +348,9 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getAmicaleServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null)
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.amicaleDataset);
|
||||
}
|
||||
return this.amicaleDataset;
|
||||
}
|
||||
|
||||
|
|
@ -360,8 +361,9 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getStudentServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null)
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.studentsDataset);
|
||||
}
|
||||
return this.studentsDataset;
|
||||
}
|
||||
|
||||
|
|
@ -372,8 +374,9 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getINSAServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null)
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.insaDataset);
|
||||
}
|
||||
return this.insaDataset;
|
||||
}
|
||||
|
||||
|
|
@ -384,8 +387,9 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getSpecialServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null)
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.specialDataset);
|
||||
}
|
||||
return this.specialDataset;
|
||||
}
|
||||
|
||||
|
|
@ -396,8 +400,9 @@ export default class ServicesManager {
|
|||
* @returns {Array<ServiceCategoryType>}
|
||||
*/
|
||||
getCategories(excludedItems?: Array<string>): Array<ServiceCategoryType> {
|
||||
if (excludedItems != null)
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.categoriesDataset);
|
||||
}
|
||||
return this.categoriesDataset;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,307 +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/>.
|
||||
*/
|
||||
|
||||
// @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();
|
||||
}
|
||||
}
|
||||
298
src/managers/ThemeManager.ts
Normal file
298
src/managers/ThemeManager.ts
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* 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,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -40,18 +38,63 @@ 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 from '../screens/Amicale/Equipment/EquipmentListScreen';
|
||||
import EquipmentScreen, {
|
||||
DeviceType,
|
||||
} 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
|
||||
|
|
@ -63,19 +106,17 @@ const defaultScreenOptions = {
|
|||
...TransitionPresets.SlideFromRightIOS,
|
||||
};
|
||||
|
||||
const MainStack = createStackNavigator();
|
||||
const MainStack = createStackNavigator<MainStackParamsList>();
|
||||
|
||||
function MainStackComponent(props: {
|
||||
createTabNavigator: () => React.Node,
|
||||
}): React.Node {
|
||||
function MainStackComponent(props: {createTabNavigator: () => JSX.Element}) {
|
||||
const {createTabNavigator} = props;
|
||||
return (
|
||||
<MainStack.Navigator
|
||||
initialRouteName="main"
|
||||
initialRouteName={MainRoutes.Main}
|
||||
headerMode="screen"
|
||||
screenOptions={defaultScreenOptions}>
|
||||
<MainStack.Screen
|
||||
name="main"
|
||||
name={MainRoutes.Main}
|
||||
component={createTabNavigator}
|
||||
options={{
|
||||
headerShown: false,
|
||||
|
|
@ -83,62 +124,62 @@ function MainStackComponent(props: {
|
|||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name="gallery"
|
||||
name={MainRoutes.Gallery}
|
||||
component={ImageGalleryScreen}
|
||||
options={{
|
||||
headerShown: false,
|
||||
...modalTransition,
|
||||
}}
|
||||
/>
|
||||
{createScreenCollapsibleStack(
|
||||
'settings',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Settings,
|
||||
MainStack,
|
||||
SettingsScreen,
|
||||
i18n.t('screens.settings.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'dashboard-edit',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.DashboardEdit,
|
||||
MainStack,
|
||||
DashboardEditScreen,
|
||||
i18n.t('screens.settings.dashboardEdit.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'about',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.About,
|
||||
MainStack,
|
||||
AboutScreen,
|
||||
i18n.t('screens.about.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'dependencies',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Dependencies,
|
||||
MainStack,
|
||||
AboutDependenciesScreen,
|
||||
i18n.t('screens.about.libs'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'debug',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Debug,
|
||||
MainStack,
|
||||
DebugScreen,
|
||||
i18n.t('screens.about.debug'),
|
||||
)}
|
||||
|
||||
{createScreenCollapsibleStack(
|
||||
'game-start',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.GameStart,
|
||||
MainStack,
|
||||
GameStartScreen,
|
||||
i18n.t('screens.game.title'),
|
||||
true,
|
||||
null,
|
||||
undefined,
|
||||
'transparent',
|
||||
)}
|
||||
<MainStack.Screen
|
||||
name="game-main"
|
||||
name={MainRoutes.GameMain}
|
||||
component={GameMainScreen}
|
||||
options={{
|
||||
title: i18n.t('screens.game.title'),
|
||||
}}
|
||||
/>
|
||||
{createScreenCollapsibleStack(
|
||||
'login',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Login,
|
||||
MainStack,
|
||||
LoginScreen,
|
||||
i18n.t('screens.login.title'),
|
||||
|
|
@ -148,26 +189,26 @@ function MainStackComponent(props: {
|
|||
)}
|
||||
{getWebsiteStack('website', MainStack, WebsiteScreen, '')}
|
||||
|
||||
{createScreenCollapsibleStack(
|
||||
'self-menu',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.SelfMenu,
|
||||
MainStack,
|
||||
SelfMenuScreen,
|
||||
i18n.t('screens.menu.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'proximo',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Proximo,
|
||||
MainStack,
|
||||
ProximoMainScreen,
|
||||
i18n.t('screens.proximo.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'proximo-list',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ProximoList,
|
||||
MainStack,
|
||||
ProximoListScreen,
|
||||
i18n.t('screens.proximo.articleList'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'proximo-about',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ProximoAbout,
|
||||
MainStack,
|
||||
ProximoAboutScreen,
|
||||
i18n.t('screens.proximo.title'),
|
||||
|
|
@ -175,60 +216,60 @@ function MainStackComponent(props: {
|
|||
{...modalTransition},
|
||||
)}
|
||||
|
||||
{createScreenCollapsibleStack(
|
||||
'profile',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Profile,
|
||||
MainStack,
|
||||
ProfileScreen,
|
||||
i18n.t('screens.profile.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'club-list',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ClubList,
|
||||
MainStack,
|
||||
ClubListScreen,
|
||||
i18n.t('screens.clubs.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'club-information',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ClubInformation,
|
||||
MainStack,
|
||||
ClubDisplayScreen,
|
||||
i18n.t('screens.clubs.details'),
|
||||
true,
|
||||
{...modalTransition},
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'club-about',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.ClubAbout,
|
||||
MainStack,
|
||||
ClubAboutScreen,
|
||||
i18n.t('screens.clubs.title'),
|
||||
true,
|
||||
{...modalTransition},
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'equipment-list',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.EquipmentList,
|
||||
MainStack,
|
||||
EquipmentScreen,
|
||||
i18n.t('screens.equipment.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'equipment-rent',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.EquipmentRent,
|
||||
MainStack,
|
||||
EquipmentLendScreen,
|
||||
i18n.t('screens.equipment.book'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'equipment-confirm',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.EquipmentConfirm,
|
||||
MainStack,
|
||||
EquipmentConfirmScreen,
|
||||
i18n.t('screens.equipment.confirm'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'vote',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Vote,
|
||||
MainStack,
|
||||
VoteScreen,
|
||||
i18n.t('screens.vote.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
'feedback',
|
||||
{CreateScreenCollapsibleStack(
|
||||
MainRoutes.Feedback,
|
||||
MainStack,
|
||||
BugReportScreen,
|
||||
i18n.t('screens.feedback.title'),
|
||||
|
|
@ -238,25 +279,14 @@ function MainStackComponent(props: {
|
|||
}
|
||||
|
||||
type PropsType = {
|
||||
defaultHomeRoute: string | null,
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
defaultHomeData: {[key: string]: string},
|
||||
defaultHomeRoute: string | null;
|
||||
defaultHomeData: {[key: string]: string};
|
||||
};
|
||||
|
||||
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} />;
|
||||
}
|
||||
export default function MainNavigator(props: PropsType) {
|
||||
return (
|
||||
<MainStackComponent
|
||||
createTabNavigator={() => <TabNavigator {...props} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -44,7 +42,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';
|
||||
|
|
@ -62,25 +60,25 @@ const defaultScreenOptions = {
|
|||
|
||||
const ServicesStack = createStackNavigator();
|
||||
|
||||
function ServicesStackComponent(): React.Node {
|
||||
function ServicesStackComponent() {
|
||||
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,
|
||||
|
|
@ -92,19 +90,19 @@ function ServicesStackComponent(): React.Node {
|
|||
|
||||
const ProxiwashStack = createStackNavigator();
|
||||
|
||||
function ProxiwashStackComponent(): React.Node {
|
||||
function ProxiwashStackComponent() {
|
||||
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,
|
||||
|
|
@ -116,7 +114,7 @@ function ProxiwashStackComponent(): React.Node {
|
|||
|
||||
const PlanningStack = createStackNavigator();
|
||||
|
||||
function PlanningStackComponent(): React.Node {
|
||||
function PlanningStackComponent() {
|
||||
return (
|
||||
<PlanningStack.Navigator
|
||||
initialRouteName="index"
|
||||
|
|
@ -127,7 +125,7 @@ function PlanningStackComponent(): React.Node {
|
|||
component={PlanningScreen}
|
||||
options={{title: i18n.t('screens.planning.title')}}
|
||||
/>
|
||||
{createScreenCollapsibleStack(
|
||||
{CreateScreenCollapsibleStack(
|
||||
'planning-information',
|
||||
PlanningStack,
|
||||
PlanningDisplayScreen,
|
||||
|
|
@ -142,10 +140,11 @@ const HomeStack = createStackNavigator();
|
|||
function HomeStackComponent(
|
||||
initialRoute: string | null,
|
||||
defaultData: {[key: string]: string},
|
||||
): React.Node {
|
||||
) {
|
||||
let params;
|
||||
if (initialRoute != null)
|
||||
if (initialRoute) {
|
||||
params = {data: defaultData, nextScreen: initialRoute, shouldOpen: true};
|
||||
}
|
||||
const {colors} = useTheme();
|
||||
return (
|
||||
<HomeStack.Navigator
|
||||
|
|
@ -161,7 +160,7 @@ function HomeStackComponent(
|
|||
headerStyle: {
|
||||
backgroundColor: colors.surface,
|
||||
},
|
||||
headerTitle: (): React.Node => (
|
||||
headerTitle: () => (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Mascot
|
||||
style={{
|
||||
|
|
@ -203,19 +202,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,
|
||||
|
|
@ -227,7 +226,7 @@ function HomeStackComponent(
|
|||
|
||||
const PlanexStack = createStackNavigator();
|
||||
|
||||
function PlanexStackComponent(): React.Node {
|
||||
function PlanexStackComponent() {
|
||||
return (
|
||||
<PlanexStack.Navigator
|
||||
initialRouteName="index"
|
||||
|
|
@ -239,7 +238,7 @@ function PlanexStackComponent(): React.Node {
|
|||
PlanexScreen,
|
||||
i18n.t('screens.planex.title'),
|
||||
)}
|
||||
{createScreenCollapsibleStack(
|
||||
{CreateScreenCollapsibleStack(
|
||||
'group-select',
|
||||
PlanexStack,
|
||||
GroupSelectionScreen,
|
||||
|
|
@ -252,35 +251,33 @@ function PlanexStackComponent(): React.Node {
|
|||
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);
|
||||
if (props.defaultHomeRoute != null) this.defaultRoute = 'home';
|
||||
else
|
||||
this.defaultRoute = 'home';
|
||||
if (!props.defaultHomeRoute) {
|
||||
this.defaultRoute = AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.defaultStartScreen.key,
|
||||
).toLowerCase();
|
||||
this.createHomeStackComponent = (): React.Node =>
|
||||
}
|
||||
this.createHomeStackComponent = () =>
|
||||
HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={this.defaultRoute}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
tabBar={(props: {...}): React.Node => <CustomTabBar {...props} />}>
|
||||
tabBar={(tabProps) => <CustomTabBar {...tabProps} />}>
|
||||
<Tab.Screen
|
||||
name="services"
|
||||
option
|
||||
component={ServicesStackComponent}
|
||||
options={{title: i18n.t('screens.services.title')}}
|
||||
/>
|
||||
|
|
@ -299,7 +296,6 @@ export default class TabNavigator extends React.Component<PropsType> {
|
|||
component={PlanningStackComponent}
|
||||
options={{title: i18n.t('screens.planning.title')}}
|
||||
/>
|
||||
|
||||
<Tab.Screen
|
||||
name="planex"
|
||||
component={PlanexStackComponent}
|
||||
|
|
@ -17,8 +17,6 @@
|
|||
* 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';
|
||||
|
|
@ -26,8 +24,8 @@ import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatLis
|
|||
import packageJson from '../../../package.json';
|
||||
|
||||
type ListItemType = {
|
||||
name: string,
|
||||
version: string,
|
||||
name: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -37,9 +35,9 @@ type ListItemType = {
|
|||
* @return {Array<ListItemType>}
|
||||
*/
|
||||
function generateListFromObject(object: {
|
||||
[key: string]: string,
|
||||
[key: string]: string;
|
||||
}): Array<ListItemType> {
|
||||
const list = [];
|
||||
const list: Array<ListItemType> = [];
|
||||
const keys = Object.keys(object);
|
||||
keys.forEach((key: string) => {
|
||||
list.push({name: key, version: object[key]});
|
||||
|
|
@ -52,17 +50,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<null> {
|
||||
export default class AboutDependenciesScreen extends React.Component<{}> {
|
||||
data: Array<ListItemType>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.data = generateListFromObject(packageJson.dependencies);
|
||||
}
|
||||
|
||||
keyExtractor = (item: ListItemType): string => item.name;
|
||||
|
||||
getRenderItem = ({item}: {item: ListItemType}): React.Node => (
|
||||
getRenderItem = ({item}: {item: ListItemType}) => (
|
||||
<List.Item
|
||||
title={item.name}
|
||||
description={item.version.replace('^', '').replace('~', '')}
|
||||
|
|
@ -71,15 +69,15 @@ export default class AboutDependenciesScreen extends React.Component<null> {
|
|||
);
|
||||
|
||||
getItemLayout = (
|
||||
data: ListItemType,
|
||||
data: Array<ListItemType> | null | undefined,
|
||||
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(): React.Node {
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<CollapsibleFlatList
|
||||
|
|
@ -17,37 +17,32 @@
|
|||
* 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, withTheme} from 'react-native-paper';
|
||||
import {Avatar, Card, List} 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 = {
|
||||
|
|
@ -64,14 +59,14 @@ const links = {
|
|||
};
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
navigation: StackNavigationProp<any>;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
dialogVisible: boolean,
|
||||
dialogTitle: string,
|
||||
dialogMessage: string,
|
||||
dialogButtons: Array<OptionsDialogButtonType>,
|
||||
dialogVisible: boolean;
|
||||
dialogTitle: string;
|
||||
dialogMessage: string;
|
||||
dialogButtons: Array<OptionsDialogButtonType>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -86,11 +81,10 @@ 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
|
||||
*/
|
||||
static majorContributors: {[key: string]: MemberItemType} = {
|
||||
majorContributors: {[key: string]: MemberItemType} = {
|
||||
arnaud: {
|
||||
name: 'Arnaud Vergnet',
|
||||
message: i18n.t('screens.about.user.arnaud'),
|
||||
|
|
@ -109,7 +103,8 @@ 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=' +
|
||||
|
|
@ -120,7 +115,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
/**
|
||||
* Object containing data relative to users who helped during development
|
||||
*/
|
||||
static helpfulUsers: {[key: string]: MemberItemType} = {
|
||||
helpfulUsers: {[key: string]: MemberItemType} = {
|
||||
beranger: {
|
||||
name: 'Béranger Quintana Y Arciosana',
|
||||
message: i18n.t('screens.about.user.beranger'),
|
||||
|
|
@ -204,18 +199,18 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
teamData: Array<ListItemType> = [
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(AboutScreen.majorContributors.arnaud);
|
||||
this.onContributorListItemPress(this.majorContributors.arnaud);
|
||||
},
|
||||
icon: AboutScreen.majorContributors.arnaud.icon,
|
||||
text: AboutScreen.majorContributors.arnaud.name,
|
||||
icon: this.majorContributors.arnaud.icon,
|
||||
text: this.majorContributors.arnaud.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(AboutScreen.majorContributors.yohan);
|
||||
this.onContributorListItemPress(this.majorContributors.yohan);
|
||||
},
|
||||
icon: AboutScreen.majorContributors.yohan.icon,
|
||||
text: AboutScreen.majorContributors.yohan.name,
|
||||
icon: this.majorContributors.yohan.icon,
|
||||
text: this.majorContributors.yohan.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
|
|
@ -235,42 +230,42 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
thanksData: Array<ListItemType> = [
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.beranger);
|
||||
this.onContributorListItemPress(this.helpfulUsers.beranger);
|
||||
},
|
||||
icon: AboutScreen.helpfulUsers.beranger.icon,
|
||||
text: AboutScreen.helpfulUsers.beranger.name,
|
||||
icon: this.helpfulUsers.beranger.icon,
|
||||
text: this.helpfulUsers.beranger.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.celine);
|
||||
this.onContributorListItemPress(this.helpfulUsers.celine);
|
||||
},
|
||||
icon: AboutScreen.helpfulUsers.celine.icon,
|
||||
text: AboutScreen.helpfulUsers.celine.name,
|
||||
icon: this.helpfulUsers.celine.icon,
|
||||
text: this.helpfulUsers.celine.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.damien);
|
||||
this.onContributorListItemPress(this.helpfulUsers.damien);
|
||||
},
|
||||
icon: AboutScreen.helpfulUsers.damien.icon,
|
||||
text: AboutScreen.helpfulUsers.damien.name,
|
||||
icon: this.helpfulUsers.damien.icon,
|
||||
text: this.helpfulUsers.damien.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.titouan);
|
||||
this.onContributorListItemPress(this.helpfulUsers.titouan);
|
||||
},
|
||||
icon: AboutScreen.helpfulUsers.titouan.icon,
|
||||
text: AboutScreen.helpfulUsers.titouan.name,
|
||||
icon: this.helpfulUsers.titouan.icon,
|
||||
text: this.helpfulUsers.titouan.name,
|
||||
showChevron: false,
|
||||
},
|
||||
{
|
||||
onPressCallback: () => {
|
||||
this.onContributorListItemPress(AboutScreen.helpfulUsers.theo);
|
||||
this.onContributorListItemPress(this.helpfulUsers.theo);
|
||||
},
|
||||
icon: AboutScreen.helpfulUsers.theo.icon,
|
||||
text: AboutScreen.helpfulUsers.theo.name,
|
||||
icon: this.helpfulUsers.theo.icon,
|
||||
text: this.helpfulUsers.theo.name,
|
||||
showChevron: false,
|
||||
},
|
||||
];
|
||||
|
|
@ -333,7 +328,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
* @param user The member to show information for
|
||||
*/
|
||||
onContributorListItemPress(user: MemberItemType) {
|
||||
const dialogBtn = [
|
||||
const dialogBtn: Array<OptionsDialogButtonType> = [
|
||||
{
|
||||
title: 'OK',
|
||||
onPress: this.onDialogDismiss,
|
||||
|
|
@ -379,15 +374,14 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getAppCard(): React.Node {
|
||||
getAppCard() {
|
||||
return (
|
||||
<Card style={{marginBottom: 10}}>
|
||||
<Card.Title
|
||||
title="Campus"
|
||||
subtitle={packageJson.version}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
left={(iconProps) => (
|
||||
<Image
|
||||
size={iconProps.size}
|
||||
source={APP_LOGO}
|
||||
style={{width: iconProps.size, height: iconProps.size}}
|
||||
/>
|
||||
|
|
@ -409,12 +403,12 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getTeamCard(): React.Node {
|
||||
getTeamCard() {
|
||||
return (
|
||||
<Card style={{marginBottom: 10}}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.about.team')}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon size={iconProps.size} icon="account-multiple" />
|
||||
)}
|
||||
/>
|
||||
|
|
@ -434,12 +428,12 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getThanksCard(): React.Node {
|
||||
getThanksCard() {
|
||||
return (
|
||||
<Card style={{marginBottom: 10}}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.about.thanks')}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon size={iconProps.size} icon="hand-heart" />
|
||||
)}
|
||||
/>
|
||||
|
|
@ -459,12 +453,12 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getTechnoCard(): React.Node {
|
||||
getTechnoCard() {
|
||||
return (
|
||||
<Card style={{marginBottom: 10}}>
|
||||
<Card.Title
|
||||
title={i18n.t('screens.about.technologies')}
|
||||
left={(iconProps: CardTitleIconPropsType): React.Node => (
|
||||
left={(iconProps) => (
|
||||
<Avatar.Icon size={iconProps.size} icon="wrench" />
|
||||
)}
|
||||
/>
|
||||
|
|
@ -485,7 +479,13 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
* @param props
|
||||
* @return {*}
|
||||
*/
|
||||
static getChevronIcon(props: ListIconPropsType): React.Node {
|
||||
static getChevronIcon(props: {
|
||||
color: string;
|
||||
style?: {
|
||||
marginRight: number;
|
||||
marginVertical?: number;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<List.Icon color={props.color} style={props.style} icon="chevron-right" />
|
||||
);
|
||||
|
|
@ -498,7 +498,16 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
* @param props
|
||||
* @return {*}
|
||||
*/
|
||||
static getItemIcon(item: ListItemType, props: ListIconPropsType): React.Node {
|
||||
static getItemIcon(
|
||||
item: ListItemType,
|
||||
props: {
|
||||
color: string;
|
||||
style?: {
|
||||
marginRight: number;
|
||||
marginVertical?: number;
|
||||
};
|
||||
},
|
||||
) {
|
||||
return (
|
||||
<List.Icon color={props.color} style={props.style} icon={item.icon} />
|
||||
);
|
||||
|
|
@ -509,9 +518,14 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getCardItem = ({item}: {item: ListItemType}): React.Node => {
|
||||
const getItemIcon = (props: ListIconPropsType): React.Node =>
|
||||
AboutScreen.getItemIcon(item, props);
|
||||
getCardItem = ({item}: {item: ListItemType}) => {
|
||||
const getItemIcon = (props: {
|
||||
color: string;
|
||||
style?: {
|
||||
marginRight: number;
|
||||
marginVertical?: number;
|
||||
};
|
||||
}) => AboutScreen.getItemIcon(item, props);
|
||||
if (item.showChevron) {
|
||||
return (
|
||||
<List.Item
|
||||
|
|
@ -537,7 +551,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
* @param item The item to show
|
||||
* @return {*}
|
||||
*/
|
||||
getMainCard = ({item}: {item: {id: string}}): React.Node => {
|
||||
getMainCard = ({item}: {item: {id: string}}) => {
|
||||
switch (item.id) {
|
||||
case 'app':
|
||||
return this.getAppCard();
|
||||
|
|
@ -564,7 +578,7 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
*/
|
||||
keyExtractor = (item: ListItemType): string => item.icon;
|
||||
|
||||
render(): React.Node {
|
||||
render() {
|
||||
const {state} = this;
|
||||
return (
|
||||
<View
|
||||
|
|
@ -588,4 +602,4 @@ class AboutScreen extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
}
|
||||
|
||||
export default withTheme(AboutScreen);
|
||||
export default AboutScreen;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue