From 097cfe3f0dc0754d2373a4b7cdb658cc19e41490 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Sun, 17 Mar 2024 16:10:43 +0100 Subject: [PATCH 01/24] =?UTF-8?q?refactor:=20:recycle:=20am=C3=A9lioration?= =?UTF-8?q?=20de=20la=20modularit=C3=A9=20de=20SelectService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- views/NewAuthStack/SelectService.tsx | 46 +++++++++------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/views/NewAuthStack/SelectService.tsx b/views/NewAuthStack/SelectService.tsx index fc09172f..df826134 100644 --- a/views/NewAuthStack/SelectService.tsx +++ b/views/NewAuthStack/SelectService.tsx @@ -19,9 +19,8 @@ const SelectService = ({ navigation }) => { const UIColors = GetUIColors(); const insets = useSafeAreaInsets(); - const [edAlertVisible, setEdAlertVisible] = useState(false); - const [skolengoAlertVisible, setSkolengoAlertVisible] = useState(false); const [serviceAlertVisible, setServiceAlertVisible] = useState(false); + const [serviceNotSuportedAlertVisible, setServiceNotSuportedAlertVisible] = useState(false); const [apiResponse, setApiResponse] = useState(false); @@ -33,9 +32,9 @@ const SelectService = ({ navigation }) => { function callFetchPapiAPI(path: string) { return fetchPapiAPI(path) - .then(data => { - return data; - }) + .then(data => { + return data; + }) } useLayoutEffect(() => { @@ -48,7 +47,7 @@ const SelectService = ({ navigation }) => { }); }, [UIColors]); - const [selectedService, setSelectedService] = useState(null); + const [selectedService, setSelectedService] = useState(null); const [serviceOptions, setServiceOptions] = useState([ { name: 'PRONOTE', @@ -63,8 +62,8 @@ const SelectService = ({ navigation }) => { company: 'Kosmos', description: 'Comptes régionnaux', icon: require('../../assets/logo_modern_skolengo.png'), - view: 'LoginSkolengoSelectSchool', - soon: true, + view: 'LoginSkolengoEtab', + soon: false, }, { name: 'EcoleDirecte', @@ -81,15 +80,11 @@ const SelectService = ({ navigation }) => { const continueToLogin = () => { if (selectedService !== null) { - if (selectedService === 1) { - setSkolengoAlertVisible(true); + const service = serviceOptions[selectedService]; + if(service.soon) { + setServiceNotSuportedAlertVisible(service.name); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); - } - else if (selectedService === 2) { - setEdAlertVisible(true); - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); - } - else { + } else { setServiceAlertVisible(true); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); } @@ -200,22 +195,11 @@ const SelectService = ({ navigation }) => { } - title="EcoleDirecte" - subtitle="EcoleDirecte n’est pas encore disponible sur Papillon. Veuillez réessayer plus tard." - cancelAction={() => setEdAlertVisible(false)} - /> - - } - title="Skolengo" - subtitle="Skolengo n’est pas encore disponible sur Papillon. Veuillez réessayer plus tard." - cancelAction={() => setSkolengoAlertVisible(false)} + title={serviceNotSuportedAlertVisible} + subtitle={`${serviceNotSuportedAlertVisible} n’est pas encore disponible sur Papillon. Veuillez réessayer plus tard.`} + cancelAction={() => setServiceNotSuportedAlertVisible(false)} /> From 4df3f293aeee59c2543ca16dd3cf183d1e628128 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Mon, 18 Mar 2024 00:51:45 +0100 Subject: [PATCH 02/24] feat: :wrench: ajout de skolengo (skoapp-prod) --- .npmrc | 2 ++ .yarnrc | 1 + android/app/src/main/AndroidManifest.xml | 2 ++ app.json | 19 +++----------- ios/Papillon/Info.plist | 1 + package-lock.json | 33 ++++++++++++------------ package.json | 4 +-- 7 files changed, 27 insertions(+), 35 deletions(-) create mode 100644 .npmrc create mode 100644 .yarnrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..aea106aa --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +omit=optional +no-optional=true \ No newline at end of file diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 00000000..56da139e --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +--install.ignore-optional true \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 58712062..9444a706 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -38,8 +38,10 @@ + + diff --git a/app.json b/app.json index 5e179784..dcd8ad3d 100644 --- a/app.json +++ b/app.json @@ -14,21 +14,6 @@ "notification": { "icon": "./assets/icon-small-notif.png" }, - "intentFilters": [ - { - "action": "VIEW", - "autoVerify": true, - "data": [ - { - "scheme": "skoapp-prod" - } - ], - "category": [ - "BROWSABLE", - "DEFAULT" - ] - } - ], "splash": { "image": "./assets/launch/splash-light.png", "resizeMode": "cover", @@ -38,7 +23,9 @@ "supportsTablet": true, "appStoreUrl": "https://apps.apple.com/us/app/papillon-lappli-scolaire/id6477761165", "bundleIdentifier": "xyz.getpapillon.ios", - "associatedDomains": ["applinks:getpapillon.xyz"], + "associatedDomains": [ + "applinks:getpapillon.xyz" + ], "splash": { "backgroundColor": "#32AB8E", "image": "./assets/launch/splash-light.png", diff --git a/ios/Papillon/Info.plist b/ios/Papillon/Info.plist index 5c6f8289..e43aa12f 100644 --- a/ios/Papillon/Info.plist +++ b/ios/Papillon/Info.plist @@ -466,6 +466,7 @@ CFBundleURLSchemes papillon + skoapp-prod xyz.getpapillon.ios diff --git a/package-lock.json b/package-lock.json index 4c8888d9..b2101c7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,6 +103,7 @@ "react-native-webview": "^13.8.1", "react-native-wheel-scrollview-picker": "^2.0.4", "reanimated-color-picker": "^2.3.4", + "scolengo-api": "^3.0.2", "sync-storage": "^0.4.2", "typescript": "^5.1.3" }, @@ -115,8 +116,7 @@ "@typescript-eslint/eslint-plugin": "^6.19.0", "@typescript-eslint/parser": "^6.19.0", "eslint": "^8.56.0", - "eslint-plugin-react": "^7.33.2", - "scolengo-api": "^2.2.0" + "eslint-plugin-react": "^7.33.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -7970,7 +7970,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", - "dev": true, "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -7981,7 +7980,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -14943,7 +14941,7 @@ "version": "4.15.4", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", - "dev": true, + "optional": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -17090,7 +17088,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "dev": true, + "optional": true, "engines": { "node": ">= 6" } @@ -17213,7 +17211,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", - "dev": true, + "optional": true, "engines": { "node": "^10.13.0 || >=12.0.0" } @@ -17279,7 +17277,7 @@ "version": "5.6.1", "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz", "integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==", - "dev": true, + "optional": true, "dependencies": { "jose": "^4.15.1", "lru-cache": "^6.0.0", @@ -17294,7 +17292,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -17306,7 +17304,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "optional": true }, "node_modules/optionator": { "version": "0.9.3", @@ -18591,8 +18589,7 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/pump": { "version": "3.0.0", @@ -19968,13 +19965,15 @@ } }, "node_modules/scolengo-api": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/scolengo-api/-/scolengo-api-2.2.0.tgz", - "integrity": "sha512-4jre4XuauncAFlFI44Gp4UkuDt8FcYhke6rlJlFptu+F/H5VIG1mU6ncI9vJWBD+RGIXehUATcgDL+/+DZTxZg==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/scolengo-api/-/scolengo-api-3.0.2.tgz", + "integrity": "sha512-gSxz7OnL9liT6eZQlqhRgra3RgzEjMzRw5KnfJ2E8BxFZpq8whbMyEOPW0bo1adoVLjtUA9iciZrZ1zfV0KQ6w==", "dependencies": { "axios": "^1.3.5", - "jsonapi-fractal": "^2.3.1", + "base-64": "^1.0.0", + "jsonapi-fractal": "^2.3.1" + }, + "optionalDependencies": { "openid-client": "^5.4.0" } }, diff --git a/package.json b/package.json index c65a6dba..bb0264a2 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "react-native-webview": "^13.8.1", "react-native-wheel-scrollview-picker": "^2.0.4", "reanimated-color-picker": "^2.3.4", + "scolengo-api": "^3.0.2", "sync-storage": "^0.4.2", "typescript": "^5.1.3" }, @@ -118,7 +119,6 @@ "@typescript-eslint/eslint-plugin": "^6.19.0", "@typescript-eslint/parser": "^6.19.0", "eslint": "^8.56.0", - "eslint-plugin-react": "^7.33.2", - "scolengo-api": "^2.2.0" + "eslint-plugin-react": "^7.33.2" } } From 5970d12cdc6eb608c3aff3ff7006aeac85d6a3fb Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Mon, 18 Mar 2024 00:52:59 +0100 Subject: [PATCH 03/24] feat: :sparkles: maj du login Skolengo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Info : Skolengo est activé uniquement en version dev pour l'instant à des fins de test (__DEV__ || false) --- stacks/AuthStack.tsx | 13 +- views/NewAuthStack/SelectService.tsx | 6 +- .../Skolengo/LocateSkolengoEtab.tsx | 242 +++++++++++++ .../Skolengo/LoginSkolengoSelectSchool.js | 328 ------------------ 4 files changed, 251 insertions(+), 338 deletions(-) create mode 100644 views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx delete mode 100644 views/NewAuthStack/Skolengo/LoginSkolengoSelectSchool.js diff --git a/stacks/AuthStack.tsx b/stacks/AuthStack.tsx index 170f1b8b..ae442fa6 100644 --- a/stacks/AuthStack.tsx +++ b/stacks/AuthStack.tsx @@ -5,8 +5,7 @@ const Stack = createNativeStackNavigator(); import { headerTitleStyles } from './AppStack'; import GetUIColors from '../utils/GetUIColors'; - -import { LoginSkolengoSelectSchool } from '../views/NewAuthStack/Skolengo/LoginSkolengoSelectSchool'; +import { Platform } from 'react-native'; const AuthStack = ({ navigation }) => { const UIColors = GetUIColors(); @@ -92,14 +91,14 @@ const AuthStack = ({ navigation }) => { } }, { - name: 'LoginSkolengoSelectSchool', - component: LoginSkolengoSelectSchool, + name: 'LocateSkolengoEtab', + component: require('../views/NewAuthStack/Skolengo/LocateSkolengoEtab').default, options: { - title: 'Se connecter via Skolengo', - presentation: 'modal', + headerTitle: 'Connexion via Skolengo', + headerBackTitleVisible: false, } }, - ] + ] as const; return ( { company: 'Kosmos', description: 'Comptes régionnaux', icon: require('../../assets/logo_modern_skolengo.png'), - view: 'LoginSkolengoEtab', - soon: false, + view: 'LocateSkolengoEtab', + soon: __DEV__ || false, }, { name: 'EcoleDirecte', diff --git a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx new file mode 100644 index 00000000..0c7d38d3 --- /dev/null +++ b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx @@ -0,0 +1,242 @@ +import React, { useState } from 'react'; + +import { + StatusBar, + StyleSheet, + TouchableOpacity, + TextInput, + ScrollView, + ActivityIndicator, + Platform, +} from 'react-native'; +import { Locate, MapPin, School, Search, X } from 'lucide-react-native'; + +import GetUIColors from '../../../utils/GetUIColors'; +import useDebounce from '../../../hooks/useDebounce'; + +import NativeList from '../../../components/NativeList'; +import NativeItem from '../../../components/NativeItem'; +import NativeText from '../../../components/NativeText'; +import PapillonLoading from '../../../components/PapillonLoading'; + +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import * as Location from 'expo-location'; +import { School as SkolengoSchool } from 'scolengo-api/types/models/School'; +import { loginSkolengoWorkflow } from '../../../fetch/SkolengoData/SkolengoDatas'; +import { useAppContext } from '../../../utils/AppContext'; +import { Skolengo } from 'scolengo-api'; + +export const LocateSkolengoEtab = ({ + navigation, +}: { + navigation: any; // TODO +}) => { + const UIColors = GetUIColors(); + const insets = useSafeAreaInsets(); + + const [instances, setInstances] = useState(null); + const [searchMethod, setSearchMethod] = useState<'text' | 'geo'>('text'); + const [isLoading, setIsLoading] = React.useState(false); + const [currentSearch, setCurrentSearch] = React.useState(''); + const debouncedCurrentSearch = useDebounce(currentSearch, 175); + + const textInputRef = React.createRef(); + + // Focus on input when screen transition is done + // and and when it's the first time its opened. + React.useEffect(() => { + const unsubscribe = navigation.addListener('transitionEnd', () => { + textInputRef.current?.focus(); + }); + + return unsubscribe; + }, [navigation]); + + // When the user stops typing (detected by debounce) + // we send a request to a geo-api. + React.useEffect(() => { + (async () => { + if (debouncedCurrentSearch.length <= 2) { + setIsLoading(false); + setInstances(null); + return; + } + + try { + // to make the request to geo-api. + const data = await Skolengo.searchSchool({ + text: debouncedCurrentSearch, + }); + + setInstances(data); + } catch { + // any error to reset states. + setInstances([]); + } finally { + setIsLoading(false); + } + })(); + }, [debouncedCurrentSearch]); + + const LocateMe = async () => { + setIsLoading(true); + + try { + const { status } = await Location.requestForegroundPermissionsAsync(); + + if (status !== 'granted') { + setIsLoading(false); + return; + } + + const location = await Location.getCurrentPositionAsync({}); + + if (location.coords.latitude && location.coords.longitude) { + const data = await Skolengo.searchSchool({ + lat: location.coords.latitude, + lon: location.coords.longitude, + }); + setInstances(data); + setSearchMethod('geo'); + } + } finally { + setIsLoading(false); + } + }; + + const appctx = useAppContext(); + + async function selectEtab(item: SkolengoSchool) { + loginSkolengoWorkflow(appctx, navigation, item); + } + + return ( + + + + + } + trailing={ + isLoading ? ( + + ) : (currentSearch.length > 0 || searchMethod==='geo') ? ( + { + setCurrentSearch(''); + setInstances(null); + setSearchMethod('text'); + }} + > + + + ) : null + } + > + { + setCurrentSearch(text); + setIsLoading(true); + if(searchMethod === 'geo') setSearchMethod('text'); + }} + /> + + + + {!isLoading && currentSearch.length < 2 ? ( + + } + onPress={() => { + LocateMe(); + }} + > + Me localiser + + Trouve automatiquement les établissements proches de vous + + + + ) : null} + + {!isLoading && currentSearch.length < 2 && searchMethod === 'text' ? ( + + } + title="Rechercher une ville" + subtitle="Veuillez indiquer le nom d'une ville ou d'un etablissement afin de proceder à la recherche" + /> + ) : null} + + {isLoading && ( + + )} + + {!isLoading && instances?.length === 0 && ( + + } + title="Aucun établissement trouvé" + subtitle={ + 'Aucun établissement n\'a été trouvé correspondant à votre recherche ou proche de chez vous.' + } + /> + )} + + {!isLoading && instances && instances.length > 0 && ( + + {instances.map((instance) => { + return ( + } + onPress={() => selectEtab(instance)} + > + {instance.name} + + {`${instance.zipCode} ${instance.city}${instance.distance ? `\n(à ${Math.round(instance.distance*10)/10} km)` : ''}`} + + + ); + })} + + )} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + input: { + fontSize: 16, + fontFamily: 'Papillon-Medium', + }, +}); + +export default LocateSkolengoEtab; \ No newline at end of file diff --git a/views/NewAuthStack/Skolengo/LoginSkolengoSelectSchool.js b/views/NewAuthStack/Skolengo/LoginSkolengoSelectSchool.js deleted file mode 100644 index c62933dc..00000000 --- a/views/NewAuthStack/Skolengo/LoginSkolengoSelectSchool.js +++ /dev/null @@ -1,328 +0,0 @@ -import * as React from 'react'; -import { - ScrollView, - View, - StatusBar, - Platform, - StyleSheet, -} from 'react-native'; - -import { - useTheme, - ActivityIndicator, - List, - Text, - Searchbar, -} from 'react-native-paper'; -import { useState } from 'react'; -import * as Location from 'expo-location'; -import { School, Map, Backpack, Locate } from 'lucide-react-native'; -import PapillonIcon from '../../../components/PapillonIcon'; -import ListItem from '../../../components/ListItem'; -import PapillonButton from '../../../components/PapillonButton'; -import { SkolengoStatic } from '../../../fetch/SkolengoData/SkolengoLoginFlow'; -import { useAppContext } from '../../../utils/AppContext'; -import { loginSkolengoWorkflow } from '../../../fetch/SkolengoData/SkolengoDatas'; - -import AlertBottomSheet from '../../../interface/AlertBottomSheet'; - -function LoginSkolengoSelectSchool({ navigation }) { - const theme = useTheme(); - - const [searchQuery, setSearchQuery] = useState(''); - - /** @type {[import('scolengo-api/types/models/School').School[], import('react').Dispatch>]} */ - const [EtabList, setEtabList] = useState([]); - const [loading, setLoading] = useState(false); - const [localisationPermissionAlert, setLocalisationPermissionAlert] = useState(false); - - const appctx = useAppContext(); - - function setResults(schools) { - setEtabList(schools ?? []); - setLoading(false); - } - - function searchSchool(text) { - setLoading(true); - setEtabList([]); - setSearchQuery(text); - SkolengoStatic.getSchools({ - text, - }) - .then(setResults); - } - - async function searchSchoolByCoords() { - const { status } = await Location.requestForegroundPermissionsAsync(); - - if (status !== 'granted') { - setLocalisationPermissionAlert(true); - return; - } - - setLoading(true); - setEtabList([]); - - const location = await Location.getCurrentPositionAsync({}); - - SkolengoStatic.getSchools({ - lat: location.coords.latitude, - lon: location.coords.longitude, - }).then(setResults); - } - - React.useLayoutEffect(() => { - navigation.setOptions({ - headerSearchBarOptions: { - placeholder: 'Entrez le nom d\'un lycée', - cancelButtonText: 'Annuler', - hideWhenScrolling: false, - hideNavigationBar: false, - onChangeText: (event) => searchSchool(event.nativeEvent.text), - }, - }); - }, [navigation]); - - async function selectEtab(item) { - loginSkolengoWorkflow(appctx, navigation, item); - } - - return ( - - - setLocalisationPermissionAlert(false)} - color='#222647' - icon={} - /> - - {Platform.OS === 'ios' ? ( - - ) : ( - - )} - - {Platform.OS === 'android' ? ( - searchSchool(evt)} - style={{ marginHorizontal: 12, marginTop: 12 }} - /> - ) : null} - - navigation.goBack()} - style={{ marginTop: 14, marginHorizontal: 14 }} - /> - - } - color="#222647" - onPress={() => searchSchoolByCoords()} - style={{ marginTop: 12 }} - /> - - {EtabList.length > 0 && !loading ? ( - - Établissements disponibles - - {EtabList.map((item, index) => ( - } - color="#222647" - onPress={() => selectEtab(item)} - style={styles.etabItem} - /> - ))} - - ) : null} - - {loading ? ( - - - - Recherche des établissements - - - Cela peut prendre quelques secondes. - - - ) : null} - - {EtabList.length === 0 && searchQuery.trim() !== '' && !loading ? ( - - } - color="#222647" - style={{ marginBottom: 14 }} - fill - small - /> - - - Aucun résultat - - - Rééssayez avec une autre recherche ou utilisez une autre méthode de - connexion. - - - ) : null} - - {EtabList.length === 0 && searchQuery.trim() === '' && !loading ? ( - - } - color="#222647" - style={{ marginBottom: 14 }} - fill - /> - - - Démarrez une recherche - - - Utilisez la barre de recherche pour rechercher une ville ou un code - postal. - - - ) : null} - - ); -} - -const styles = StyleSheet.create({ - etabItem: { - marginBottom: 5, - }, - etabItemList: {}, - - qrModal: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, - - qrModalTitle: { - fontSize: 24, - fontWeight: 'bold', - marginBottom: 12, - marginTop: 24, - color: '#000', - }, - - qrModalText: { - fontSize: 16, - opacity: 0.6, - marginBottom: 24, - textAlign: 'center', - marginHorizontal: 30, - color: '#000', - }, - - qrBtn: { - width: '95%', - }, - - qrModalScannerContainer: { - width: '95%', - flex: 1, - marginBottom: 12, - - borderRadius: 8, - borderCurve: 'continuous', - overflow: 'hidden', - }, - - qrModalScanner: { - flex: 1, - }, - - detectedEtab: { - position: 'absolute', - bottom: 0, - left: 0, - backgroundColor: '#fff', - borderRadius: 8, - borderCurve: 'continuous', - overflow: 'hidden', - padding: 12, - paddingHorizontal: 16, - flexDirection: 'row', - alignItems: 'center', - margin: 12, - gap: 16, - }, - - detectedEtabData: { - flex: 1, - }, - - detectedEtabText: { - fontSize: 17, - fontWeight: 'bold', - color: '#000', - }, - detectedEtabDescription: { - fontSize: 15, - opacity: 0.6, - color: '#000', - }, -}); - -export { LoginSkolengoSelectSchool }; From b72ea05c0cfdee9ac96e580b2469841bd3ac604b Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Mon, 18 Mar 2024 01:06:21 +0100 Subject: [PATCH 04/24] feat: :sparkles: ajout de SkolengoCommonCache Version typesafe de SkolengoCache + ajout de skoapp-prod dans app.json --- app.json | 2 +- .../NewSkolengoCommons/SkolengoCommonCache.ts | 163 ++++++++++++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 fetch/NewSkolengoCommons/SkolengoCommonCache.ts diff --git a/app.json b/app.json index dcd8ad3d..66439f91 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Papillon", "slug": "papillonvex", - "scheme": "papillon", + "scheme": ["papillon", "skoapp-prod"], "version": "6.6.2", "orientation": "portrait", "icon": "./assets/icon.png", diff --git a/fetch/NewSkolengoCommons/SkolengoCommonCache.ts b/fetch/NewSkolengoCommons/SkolengoCommonCache.ts new file mode 100644 index 00000000..1f78977d --- /dev/null +++ b/fetch/NewSkolengoCommons/SkolengoCommonCache.ts @@ -0,0 +1,163 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; + +class CommonCacheUtils { + static ASYNCSTORAGE_PREFIX = 'SkolengoCache_'; + + static _DATE_ENCODING_CHAR = '@'; + + static validateDateString = (str: string) => + typeof str === 'string' && + new Date(str?.toString()).toString() !== 'Invalid Date' && + str === new Date(str?.toString()).toISOString(); + + static parser = (obj: any) => + JSON.stringify( + obj, + (_key, value) => + this.validateDateString(value) + ? `${this._DATE_ENCODING_CHAR}${value}${this._DATE_ENCODING_CHAR}` + : value, + 2 + ); + + static deparser = (str: string) => + JSON.parse(str, (_key, value) => + typeof value === 'string' && + value.startsWith(this._DATE_ENCODING_CHAR) && + value.endsWith(this._DATE_ENCODING_CHAR) + ? new Date(value.replaceAll(this._DATE_ENCODING_CHAR, '')) + : value + ); +} + +type CommonCacheItem = + | { + expired: true; + maxDate: number; + data: T; + } + | { + expired: false; + maxDate: number; + data: T; + }; + +type CommonCacheSetItem = Omit, 'expired'>; + +export class SkolengoCommonCache { + static SECOND = 1000; + + static MINUTE = 60 * this.SECOND; + + static HOUR = 60 * this.MINUTE; + + static DAY = 24 * this.HOUR; + + static TIMEZONE_OFFSET = new Date().getTimezoneOffset() * this.MINUTE; + + static msToTomorrow() { + const now = Date.now(); + const timePassedToday = (now % this.DAY) + this.TIMEZONE_OFFSET; + return this.DAY - timePassedToday; + } + + static setItem(key: CacheKeys, value: T, timeout: number) { + const storedDatas = { + maxDate: new Date().getTime() + timeout, + data: value, + }; + return AsyncStorage.setItem( + `${CommonCacheUtils.ASYNCSTORAGE_PREFIX}${key}`, + CommonCacheUtils.parser(storedDatas) + ); + } + + static async setCollectionItem( + key: CacheKeys, + id: string, + value: T, + timeout: number + ) { + if (id?.toString().trim().length === 0) return; + const actualCache = await this.getItem< + Record> + >(key, {}); + actualCache.data![id] = { + data: value, + maxDate: new Date().getTime() + timeout, + }; + return this.setItem(key, actualCache.data, this.DAY * 30); + } + + static async getItem( + key: CacheKeys, + defaultResponse?: T + ): Promise> { + const cachedDatas = await AsyncStorage.getItem( + `${CommonCacheUtils.ASYNCSTORAGE_PREFIX}${key}` + ); + if (cachedDatas === null) + return { + expired: true, + maxDate: 0, + data: defaultResponse || (null as T), + }; + const value = CommonCacheUtils.deparser(cachedDatas); + return { + expired: value.maxDate < new Date().getTime(), + maxDate: value.maxDate, + data: value.data, + }; + } + + static async getCollectionItem( + key: CacheKeys, + id: string, + defaultResponse?: T + ) { + const cachedDatas = await this.getItem< + Record> + >(key, {}); + if (!cachedDatas?.data || !cachedDatas?.data[id]) + return { + expired: true, + maxDate: 0, + data: defaultResponse || (null as T), + }; + const value = cachedDatas.data[id]; + return { + expired: value.maxDate < new Date().getTime() || !value.data, + maxDate: value.maxDate, + data: value.data, + }; + } + + static removeItem(key: CacheKeys) { + return AsyncStorage.removeItem( + `${CommonCacheUtils.ASYNCSTORAGE_PREFIX}${key}` + ); + } + + static clearItems = () => + Promise.all( + Object.values(this.cacheKeys).map((key) => this.removeItem(key)) + ); + + static cacheKeys = { + userdatas: 'userdatas', + absences: 'absences', + schoolInfos: 'schoolInfos', + schoolInfoCollection: 'schoolInfoCollection', + evalSettings: 'evalSettings', + evalDatas: 'evalDatas', + periods: 'periods', + grades: 'grades', + homeworkList: 'homeworkList', + timetable: 'timetable', + recap: 'recap', + } as const; +} + +type _CacheKeys = typeof SkolengoCommonCache.cacheKeys; + +export type CacheKeys = _CacheKeys[keyof _CacheKeys]; From 542d7661020d36d25e5ba34b442388df321ddac6 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Mon, 18 Mar 2024 01:27:10 +0100 Subject: [PATCH 05/24] style: :art: renommage appctx > appContext Renommage pour avoir un seul nom au sein du projet --- fetch/SkolengoData/SkolengoDatas.js | 10 +++++----- views/Devoirs/CreateHomeworkScreen.js | 2 +- views/Grades/GradeView.js | 2 +- views/Grades/GradesSimulatorAdd.js | 4 ++-- views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/fetch/SkolengoData/SkolengoDatas.js b/fetch/SkolengoData/SkolengoDatas.js index 642c8d3d..56945661 100644 --- a/fetch/SkolengoData/SkolengoDatas.js +++ b/fetch/SkolengoData/SkolengoDatas.js @@ -1163,13 +1163,13 @@ const errHandler = (err) => { }; /** - * @param {typeof import("../../../utils/AppContext").DefaultValuesAppContext} appctx + * @param {typeof import("../../../utils/AppContext").DefaultValuesAppContext} appContext * @param {any} navigation * @param {import("scolengo-api/types/models/School").School} school * @param {SkolengoDatas} [skolengoInstance=undefined] */ export const loginSkolengoWorkflow = async ( - appctx, + appContext, navigation, school, skolengoInstance = undefined @@ -1218,11 +1218,11 @@ export const loginSkolengoWorkflow = async ( icon: 'auto', floating: true, }); - if (appctx) { + if (appContext) { const a = new SkolengoDatas(token, school); await a.saveToken(disco); - appctx.dataprovider.init('Skolengo').then(() => { - appctx.setLoggedIn(true); + appContextdataProvider.init('Skolengo').then(() => { + appContext.setLoggedIn(true); navigation.popToTop(); }); return true; diff --git a/views/Devoirs/CreateHomeworkScreen.js b/views/Devoirs/CreateHomeworkScreen.js index 03f478b1..4769682a 100644 --- a/views/Devoirs/CreateHomeworkScreen.js +++ b/views/Devoirs/CreateHomeworkScreen.js @@ -39,7 +39,7 @@ const CreateHomeworkScreen = ({ route, navigation }) => { const [date, setDate] = useState(new Date()); const [loading, setLoading] = useState(false); - const appctx = useAppContext(); + const appContext = useAppContext(); const [selectedSubject, setSelectedSubject] = useState(0); const [nativeSubjects, setNativeSubjects] = useState([]); diff --git a/views/Grades/GradeView.js b/views/Grades/GradeView.js index e2bd21a6..b6e3cea7 100644 --- a/views/Grades/GradeView.js +++ b/views/Grades/GradeView.js @@ -47,7 +47,7 @@ import { Buffer } from 'buffer'; import {calculateAverage, calculateSubjectAverage} from '../../utils/grades/averages'; function GradeView({ route, navigation }) { - const appctx = useAppContext(); + const appContext = useAppContext(); const theme = useTheme(); const { grade, allGrades } = route.params; const UIColors = GetUIColors(); diff --git a/views/Grades/GradesSimulatorAdd.js b/views/Grades/GradesSimulatorAdd.js index f5615c06..5949a9ac 100644 --- a/views/Grades/GradesSimulatorAdd.js +++ b/views/Grades/GradesSimulatorAdd.js @@ -26,7 +26,7 @@ const GradesSimulatorAdd = ({ navigation }) => { const theme = useTheme(); const insets = useSafeAreaInsets(); - const appctx = useAppContext(); + const appContext = useAppContext(); const [description, setDescription] = useState(''); const [out_of, setOutOf] = useState('20'); @@ -44,7 +44,7 @@ const GradesSimulatorAdd = ({ navigation }) => { const [fieldsAlert, setFieldsAlert] = useState(false); useEffect(() => { - appctx.dataprovider.getGrades('', false).then((grades) => { + appContextdataProvider.getGrades('', false).then((grades) => { let subjects = []; grades.grades.forEach((grade) => { // if subject name is not in the list diff --git a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx index 0c7d38d3..52065694 100644 --- a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx +++ b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx @@ -105,10 +105,10 @@ export const LocateSkolengoEtab = ({ } }; - const appctx = useAppContext(); + const appContext = useAppContext(); async function selectEtab(item: SkolengoSchool) { - loginSkolengoWorkflow(appctx, navigation, item); + loginSkolengoWorkflow(appContext, navigation, item); } return ( From 4aa5e0836c55d95d9775ff462abc77d4076c5829 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Mon, 18 Mar 2024 01:31:07 +0100 Subject: [PATCH 06/24] fix: :bug: activation conditionnelle de Skolengo --- views/NewAuthStack/SelectService.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/NewAuthStack/SelectService.tsx b/views/NewAuthStack/SelectService.tsx index 43bea9fe..5ef6e970 100644 --- a/views/NewAuthStack/SelectService.tsx +++ b/views/NewAuthStack/SelectService.tsx @@ -63,7 +63,7 @@ const SelectService = ({ navigation }) => { description: 'Comptes régionnaux', icon: require('../../assets/logo_modern_skolengo.png'), view: 'LocateSkolengoEtab', - soon: __DEV__ || false, + soon: !(__DEV__), }, { name: 'EcoleDirecte', From 115376d345d9842bc59a1e21e1038b79749c447a Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Mon, 18 Mar 2024 01:33:03 +0100 Subject: [PATCH 07/24] fix: :poop: rajout d'un point (appContext . dataProvider) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Car je suis fatigué donc je fais pas gaffe ! --- fetch/SkolengoData/SkolengoDatas.js | 2 +- views/Grades/GradesSimulatorAdd.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fetch/SkolengoData/SkolengoDatas.js b/fetch/SkolengoData/SkolengoDatas.js index 56945661..2472bf6c 100644 --- a/fetch/SkolengoData/SkolengoDatas.js +++ b/fetch/SkolengoData/SkolengoDatas.js @@ -1221,7 +1221,7 @@ export const loginSkolengoWorkflow = async ( if (appContext) { const a = new SkolengoDatas(token, school); await a.saveToken(disco); - appContextdataProvider.init('Skolengo').then(() => { + appContext.dataProvider.init('Skolengo').then(() => { appContext.setLoggedIn(true); navigation.popToTop(); }); diff --git a/views/Grades/GradesSimulatorAdd.js b/views/Grades/GradesSimulatorAdd.js index 5949a9ac..3b443278 100644 --- a/views/Grades/GradesSimulatorAdd.js +++ b/views/Grades/GradesSimulatorAdd.js @@ -44,7 +44,7 @@ const GradesSimulatorAdd = ({ navigation }) => { const [fieldsAlert, setFieldsAlert] = useState(false); useEffect(() => { - appContextdataProvider.getGrades('', false).then((grades) => { + appContext.dataProvider.getGrades('', false).then((grades) => { let subjects = []; grades.grades.forEach((grade) => { // if subject name is not in the list From f584d4044c6b0e7361dd83ee08de7e49ebc7710b Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Mon, 18 Mar 2024 01:39:56 +0100 Subject: [PATCH 08/24] feat: :construction: blocage de la connexion par Skolengo A continuer avec scolengo-api --- fetch/SkolengoData/SkolengoDatas.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fetch/SkolengoData/SkolengoDatas.js b/fetch/SkolengoData/SkolengoDatas.js index 2472bf6c..401e633d 100644 --- a/fetch/SkolengoData/SkolengoDatas.js +++ b/fetch/SkolengoData/SkolengoDatas.js @@ -1208,6 +1208,12 @@ export const loginSkolengoWorkflow = async ( disco ).catch(errHandler); if (!token) return errHandler(); + Alert.alert( + 'Skolengo : intégration en cours', + 'Veuillez patienter, le processus de connexion à Skolengo à fonctionné.\nMais l\'intégration de Skolengo (NG) n\'est pas encode terminé.\n\nRevenez plus tard.', + ); + // TODO : Créer l'intégration via scolengo-api + return; await Promise.all([ AsyncStorage.setItem('service', 'Skolengo'), AsyncStorage.setItem('token', 'skolengo'), From 02f3e291b96ca13d92e2982c5c6dec860e89b714 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Mon, 18 Mar 2024 22:33:34 +0100 Subject: [PATCH 09/24] feat: :construction: ajout d'une classe SkolengoDataProvider (en construction) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ne sert actuellement à rien et sert de fichier de base pour créer l'intégration future de skolengo --- .../SkolengoCommonCache.ts | 0 fetch/NewSkolengo/SkolengoDataProvider.ts | 9 +++++++++ 2 files changed, 9 insertions(+) rename fetch/{NewSkolengoCommons => NewSkolengo}/SkolengoCommonCache.ts (100%) create mode 100644 fetch/NewSkolengo/SkolengoDataProvider.ts diff --git a/fetch/NewSkolengoCommons/SkolengoCommonCache.ts b/fetch/NewSkolengo/SkolengoCommonCache.ts similarity index 100% rename from fetch/NewSkolengoCommons/SkolengoCommonCache.ts rename to fetch/NewSkolengo/SkolengoCommonCache.ts diff --git a/fetch/NewSkolengo/SkolengoDataProvider.ts b/fetch/NewSkolengo/SkolengoDataProvider.ts new file mode 100644 index 00000000..a90db949 --- /dev/null +++ b/fetch/NewSkolengo/SkolengoDataProvider.ts @@ -0,0 +1,9 @@ +let SkolengoNotImplementedWarn = false; +export class SkolengoDataProvider { + constructor() { + if (!SkolengoNotImplementedWarn) { + console.warn('SkolengoDataProvider is not implemented'); + SkolengoNotImplementedWarn = true; + } + } +} From 7eb44aa2e99c43cf26e3fba9670fed923604222b Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Tue, 19 Mar 2024 00:46:01 +0100 Subject: [PATCH 10/24] feat: :construction: ajout du Workflow d'authentification Skolengo (WIP) --- fetch/NewSkolengo/SkolengoAuthWorkflow.ts | 98 +++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 fetch/NewSkolengo/SkolengoAuthWorkflow.ts diff --git a/fetch/NewSkolengo/SkolengoAuthWorkflow.ts b/fetch/NewSkolengo/SkolengoAuthWorkflow.ts new file mode 100644 index 00000000..c47a3772 --- /dev/null +++ b/fetch/NewSkolengo/SkolengoAuthWorkflow.ts @@ -0,0 +1,98 @@ +import { School } from 'scolengo-api/types/models/School'; +import { SkolengoDataProvider } from './SkolengoDataProvider'; +import { AppContextType } from '../../utils/AppContext'; +import { + AuthRequest, + exchangeCodeAsync, + resolveDiscoveryAsync, +} from 'expo-auth-session'; +import { coolDownAsync, warmUpAsync } from 'expo-web-browser'; +import { Alert } from 'react-native'; + +const skolengoErrorHandler = (err?: Error | any) => { + if (err instanceof Error) console.error(err); + Alert.alert( + 'Erreur', + 'Une erreur est survenue lors de la connexion à Skolengo. Veuillez réessayer.' + ); + return null; +}; + +export const loginSkolengoWorkflow = async ( + appContext: AppContextType, + navigation: any, + school: School, + skolengoInstance: SkolengoDataProvider | null = null +) => { + const disco = await Promise.all([ + fetch(school.emsOIDCWellKnownUrl) + .then((res) => res.json()) + .then((res) => res.issuer), + warmUpAsync(), + ]) + .then(([issuer]) => resolveDiscoveryAsync(issuer)) + .catch(skolengoErrorHandler); + if (!disco) return; + const OID_CLIENT_ID = atob( + 'U2tvQXBwLlByb2QuMGQzNDkyMTctOWE0ZS00MWVjLTlhZjktZGY5ZTY5ZTA5NDk0' + ); + const OID_CLIENT_SECRET = atob( + 'N2NiNGQ5YTgtMjU4MC00MDQxLTlhZTgtZDU4MDM4NjkxODNm' + ); + const REDIRECT_URI = 'skoapp-prod://sign-in-callback'; + // TODO : En attente d'une possible intégration dans scolengo-api (cf https://github.com/maelgangloff/scolengo-api/pull/41) + const authRes = new AuthRequest({ + clientId: OID_CLIENT_ID, + clientSecret: OID_CLIENT_SECRET, + redirectUri: REDIRECT_URI, + extraParams: { + scope: 'openid', + response_type: 'code', + }, + usePKCE: false, + }); + const res = await authRes.promptAsync(disco).catch(skolengoErrorHandler)!; + coolDownAsync(); + if (!res || res?.type === 'dismiss') return; + /* if (!res?.params?.code) { + return skolengoErrorHandler(res.error); + } + const token = await exchangeCodeAsync( + { + clientId: SkolengoStatic.OID_CLIENT_ID, + clientSecret: SkolengoStatic.OID_CLIENT_SECRET, + code: res.params.code, + redirectUri: 'skoapp-prod://sign-in-callback', + }, + disco + ).catch(skolengoErrorHandler); + if (!token) return skolengoErrorHandler(); + Alert.alert( + 'Skolengo : intégration en cours', + 'Veuillez patienter, le processus de connexion à Skolengo à fonctionné.\nMais l\'intégration de Skolengo (NG) n\'est pas encode terminé.\n\nRevenez plus tard.' + ); + // TODO : Créer l'intégration via scolengo-api + return; */ + /* await Promise.all([ + AsyncStorage.setItem('service', 'Skolengo'), + AsyncStorage.setItem('token', 'skolengo'), + ]); + showMessage({ + message: 'Connecté avec succès', + type: 'success', + icon: 'auto', + floating: true, + }); + if (appContext) { + const a = new SkolengoDatas(token, school); + await a.saveToken(disco); + appContext.dataProvider.init('Skolengo').then(() => { + appContext.setLoggedIn(true); + navigation.popToTop(); + }); + return true; + } + skolengoInstance.school = school; + skolengoInstance.rtInstance = token; + return skolengoInstance; */ +}; From 77c090b87b3b34d4d827cc98721e6306d5b32ad5 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Tue, 19 Mar 2024 00:46:49 +0100 Subject: [PATCH 11/24] style: :art: lint - LocateSkolengoEtab --- views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx index 52065694..fdfa65d8 100644 --- a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx +++ b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx @@ -130,7 +130,7 @@ export const LocateSkolengoEtab = ({ trailing={ isLoading ? ( - ) : (currentSearch.length > 0 || searchMethod==='geo') ? ( + ) : currentSearch.length > 0 || searchMethod === 'geo' ? ( { setCurrentSearch(''); @@ -152,7 +152,7 @@ export const LocateSkolengoEtab = ({ onChangeText={(text) => { setCurrentSearch(text); setIsLoading(true); - if(searchMethod === 'geo') setSearchMethod('text'); + if (searchMethod === 'geo') setSearchMethod('text'); }} /> @@ -218,13 +218,17 @@ export const LocateSkolengoEtab = ({ > {instance.name} - {`${instance.zipCode} ${instance.city}${instance.distance ? `\n(à ${Math.round(instance.distance*10)/10} km)` : ''}`} + {`${instance.zipCode} ${instance.city}${ + instance.distance + ? `\n(à ${Math.round(instance.distance * 10) / 10} km)` + : '' + }`} ); })} - )} + )} ); }; @@ -239,4 +243,4 @@ const styles = StyleSheet.create({ }, }); -export default LocateSkolengoEtab; \ No newline at end of file +export default LocateSkolengoEtab; From 05b4ff1e5dd8af1416889c954590b6059e582045 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Tue, 19 Mar 2024 00:58:05 +0100 Subject: [PATCH 12/24] =?UTF-8?q?feat:=20:art:=20restructuration=20Setting?= =?UTF-8?q?sScreen=20pour=20une=20meilleure=20lisibilit=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + suppression de l'ancienne implémentation Skolengo dans les Settings --- views/Settings/SettingsScreen.tsx | 274 +++++++++++++----------------- 1 file changed, 117 insertions(+), 157 deletions(-) diff --git a/views/Settings/SettingsScreen.tsx b/views/Settings/SettingsScreen.tsx index 94ef8699..d289ce67 100644 --- a/views/Settings/SettingsScreen.tsx +++ b/views/Settings/SettingsScreen.tsx @@ -4,18 +4,18 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import * as Haptics from 'expo-haptics'; import { unsetBackgroundFetch } from '../../fetch/BackgroundFetch'; -import { LogOut, RefreshCw, RotateCw, Server, Trash2 } from 'lucide-react-native'; +import { + LogOut, + RefreshCw, + RotateCw, + Server, + Trash2, +} from 'lucide-react-native'; import { showMessage } from 'react-native-flash-message'; import { revokeAsync } from 'expo-auth-session'; import GetUIColors from '../../utils/GetUIColors'; import { useAppContext } from '../../utils/AppContext'; -import { SkolengoCache } from '../../fetch/SkolengoData/SkolengoCache'; - -import { - SkolengoDatas, - loginSkolengoWorkflow, -} from '../../fetch/SkolengoData/SkolengoDatas'; import NativeList from '../../components/NativeList'; import NativeItem from '../../components/NativeItem'; @@ -23,168 +23,120 @@ import NativeText from '../../components/NativeText'; import AlertBottomSheet from '../../interface/AlertBottomSheet'; import { IndexDataInstance } from '../../fetch'; +import { SkolengoCommonCache } from '../../fetch/NewSkolengo/SkolengoCommonCache'; function SettingsScreen({ navigation }) { const UIColors = GetUIColors(); const appContext = useAppContext(); - const [pronoteTokenActionAlert, setPronoteTokenActionAlert] = React.useState(false); - const [skolengoCacheClearAlert, setSkolengoCacheClearAlert] = React.useState(false); - const [skolengoReconnectAlert, setSkolengoReconnectAlert] = React.useState(false); + const [pronoteTokenActionAlert, setPronoteTokenActionAlert] = + React.useState(false); + const [skolengoCacheClearAlert, setSkolengoCacheClearAlert] = + React.useState(false); + const [skolengoReconnectAlert, setSkolengoReconnectAlert] = + React.useState(false); const [deleteAccountAlert, setDeleteAccountAlert] = React.useState(false); async function pronoteRegenerateToken() { // Force another initialisation. - await appContext.dataProvider.init('pronote'); + await appContext.dataProvider?.init('pronote'); setPronoteTokenActionAlert(true); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } - function skolengoCacheClear() { - if (appContext.dataProvider.service === 'skolengo') { + if (appContext.dataProvider?.service === 'skolengo') { setSkolengoCacheClearAlert(true); } } function skolengoReconnect() { - if (appContext.dataProvider.service === 'skolengo') { + if (appContext.dataProvider?.service === 'skolengo') { setSkolengoReconnectAlert(true); } } - return ( - - {appContext.dataProvider.service === 'pronote' && ( - - } - chevron - onPress={() => pronoteRegenerateToken()} - > - - Régénérer le token - - - Régénérer le token de votre compte - - - - )} + const PronoteSettings = appContext.dataProvider?.service === 'pronote' && ( + + } + chevron + onPress={() => pronoteRegenerateToken()} + > + Régénérer le token + Régénérer le token de votre compte + + + ); + const SkolengoSettings = appContext.dataProvider?.service === 'skolengo' && ( + + } + chevron + onPress={() => skolengoCacheClear()} + > + Vider le cache + + Supprimer le cache des données de Skolengo + + } - cancelAction={() => setPronoteTokenActionAlert(false)} + visible={skolengoCacheClearAlert} + title="Vider le cache" + subtitle="Êtes-vous sûr de vouloir vider le cache ?" + icon={} + primaryButton="Vider le cache" + primaryAction={async () => { + SkolengoCommonCache.clearItems().then(() => { + showMessage({ + message: 'Cache vidé avec succès', + type: 'success', + icon: 'auto', + floating: true, + }); + console.log('Cache vidé avec succès'); + }); + }} + cancelButton="Annuler" + cancelAction={() => setSkolengoCacheClearAlert(false)} /> - - {appContext.dataProvider.service === 'skolengo' && ( - - } - chevron - onPress={() => skolengoCacheClear()} - > - - Vider le cache - - - Supprimer le cache des données de Skolengo - - - } - primaryButton='Vider le cache' - primaryAction={async () => { - SkolengoCache.clearItems().then(() => - showMessage({ - message: 'Cache vidé avec succès', - type: 'success', - icon: 'auto', - floating: true, - }) - - ); - console.log('Cache vidé avec succès'); - }} - cancelButton='Annuler' - cancelAction={() => setSkolengoCacheClearAlert(false)} - /> - } - chevron - onPress={() => skolengoReconnect()} - > - - Reconnecter son compte Skolengo - - - & régénérer le token - - - } - primaryButton='Reconnecter' - primaryAction={async () => { - if (!appContext?.dataProvider?.skolengoInstance) return; - if (!appContext?.dataProvider?.skolengoInstance.rtInstance) - await appContext?.dataProvider?.init('skolengo'); - const validRetry = await loginSkolengoWorkflow( - appContext, - null, - appContext.dataProvider.skolengoInstance.school, - appContext.dataProvider.skolengoInstance - ); - if (validRetry === true) { - SkolengoCache.clearItems(); - const discovery = AsyncStorage.getItem( - SkolengoDatas.DISCOVERY_PATH - )?.then((_disco) => _disco && JSON.parse(_disco)); - revokeAsync( - { - ...appContext.dataProvider.skolengoInstance?.rtInstance, - token: - appContext.dataProvider.skolengoInstance?.rtInstance - .accessToken, - }, - discovery - ).then(() => { - showMessage({ - message: 'Compte déconnecté avec succès', - type: 'success', - icon: 'auto', - floating: true, - }); - navigation.navigate('login'); - }); - } - setSkolengoReconnectAlert(false); - }} - cancelButton='Annuler' - cancelAction={() => setSkolengoReconnectAlert(false)} - /> - - - )} - - } + chevron + onPress={() => skolengoReconnect()} > + Reconnecter son compte Skolengo + & régénérer le token + + } + primaryButton="Reconnecter" + primaryAction={async () => { + showMessage({ + message: 'Fonctionnalité en cours de développement', + type: 'info', + icon: 'info', + floating: true, + }); + // TODO : Réintégrer cette fonctionnalité après la nouvelle implémentation de Skolengo + }} + cancelButton="Annuler" + cancelAction={() => setSkolengoReconnectAlert(false)} + /> + + ); + + return ( + + + {PronoteSettings} + {SkolengoSettings} + + } chevron @@ -193,39 +145,47 @@ function SettingsScreen({ navigation }) { Déconnexion - - Se déconnecter de votre compte - + Se déconnecter de votre compte + } + cancelAction={() => setPronoteTokenActionAlert(false)} + /> + } - color='#D81313' - cancelButton='Annuler' + icon={} + color="#D81313" + cancelButton="Annuler" cancelAction={() => setDeleteAccountAlert(false)} - primaryButton='Déconnexion' + primaryButton="Déconnexion" primaryAction={async () => { try { - if (appContext.dataProvider.service === 'skolengo') - await appContext.dataProvider.skolengoInstance?.skolengoDisconnect(); - } catch { /* no-op */ } - + if (appContext.dataProvider?.service === 'skolengo') { + // TODO : Réintégrer cette fonctionnalité après la nouvelle implémentation de Skolengo + } + } catch { + /* no-op */ + } + // Remove every data from storage. await AsyncStorage.clear(); - AsyncStorage.setItem("preventNotifInit", "true") //to prevent notif to re-init after logout (app stack still displayed for a few second before re-rendering) + AsyncStorage.setItem('preventNotifInit', 'true'); //to prevent notif to re-init after logout (app stack still displayed for a few second before re-rendering) // Create a new provider since we're resetting everything. try { appContext.setDataProvider(new IndexDataInstance()); - } - catch (e) { + } catch (e) { console.error('Error while creating new data provider', e); } appContext.setLoggedIn(false); - unsetBackgroundFetch() + unsetBackgroundFetch(); showMessage({ message: 'Déconnecté avec succès', type: 'success', From 1fca3129fefa194b675783d8ec68b67595d2717d Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Tue, 19 Mar 2024 01:01:44 +0100 Subject: [PATCH 13/24] =?UTF-8?q?feat:=20:coffin:=20suppression=20de=20l'a?= =?UTF-8?q?ncienne=20int=C3=A9gration=20de=20Skolengo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fetch/SkolengoData/SkolengoBase.js | 63 - fetch/SkolengoData/SkolengoCache.js | 124 -- fetch/SkolengoData/SkolengoCustomTypes.d.ts | 48 - fetch/SkolengoData/SkolengoDatas.js | 1239 ----------------- fetch/SkolengoData/SkolengoLoginFlow.js | 56 - fetch/index.ts | 11 +- utils/AppContext.tsx | 2 +- .../Skolengo/LocateSkolengoEtab.tsx | 2 +- 8 files changed, 7 insertions(+), 1538 deletions(-) delete mode 100644 fetch/SkolengoData/SkolengoBase.js delete mode 100644 fetch/SkolengoData/SkolengoCache.js delete mode 100644 fetch/SkolengoData/SkolengoCustomTypes.d.ts delete mode 100644 fetch/SkolengoData/SkolengoDatas.js delete mode 100644 fetch/SkolengoData/SkolengoLoginFlow.js diff --git a/fetch/SkolengoData/SkolengoBase.js b/fetch/SkolengoData/SkolengoBase.js deleted file mode 100644 index e37076d6..00000000 --- a/fetch/SkolengoData/SkolengoBase.js +++ /dev/null @@ -1,63 +0,0 @@ -export class SkolengoBase { - static BASE_URL = 'https://api.skolengo.com/api/v1/bff-sko-app'; - - static OID_CLIENT_ID_HIDDEN = [ - 83, 107, 111, 65, 112, 112, 46, 80, 114, 111, 100, 46, 48, 100, 51, 52, 57, - 50, 49, 55, 45, 57, 97, 52, 101, 45, 52, 49, 101, 99, 45, 57, 97, 102, 57, - 45, 100, 102, 57, 101, 54, 57, 101, 48, 57, 52, 57, 52, - ]; - - static OID_CLIENT_SECRET_HIDDEN = [ - 55, 99, 98, 52, 100, 57, 97, 56, 45, 50, 53, 56, 48, 45, 52, 48, 52, 49, 45, - 57, 97, 101, 56, 45, 100, 53, 56, 48, 51, 56, 54, 57, 49, 56, 51, 102, - ]; - - static OID_CLIENT_ID = this.OID_CLIENT_ID_HIDDEN.map((e) => - String.fromCharCode(e) - ).join(''); - - static OID_CLIENT_SECRET = this.OID_CLIENT_SECRET_HIDDEN.map((e) => - String.fromCharCode(e) - ).join(''); - - static searchParamsSerialiser = (arr, prekey = '', notroot = false) => { - const result = Object.entries(arr).reduce( - (acc, [key, value]) => - value === null || value === undefined || value === '' || value === false - ? acc - : typeof value === 'object' - ? { - ...acc, - ...this.searchParamsSerialiser( - value, - notroot ? `${prekey}[${key}]` : `${key}`, - true - ), - } - : { ...acc, [notroot ? `${prekey}[${key}]` : key]: value }, - {} - ); - if (notroot) return result; - return this.objToUrlParams(result); - }; - - static objToUrlParams = (obj) => { - const params = new URLSearchParams(); - Object.entries(obj).forEach(([key, value]) => { - params.append(key, value); - }); - if (params.size === 0) return ''; - return `?${params.toString()}`; - }; - - static dateParser = (date) => - Number.isNaN(new Date(date).getTime()) - ? undefined - : new Date(date).toISOString().split('T')[0]; - - static dateVerifier = (...dates) => - dates.every((date) => { - const d = new Date(date); - return d instanceof Date && !Number.isNaN(d.getTime()); - }); -} diff --git a/fetch/SkolengoData/SkolengoCache.js b/fetch/SkolengoData/SkolengoCache.js deleted file mode 100644 index 112be2ef..00000000 --- a/fetch/SkolengoData/SkolengoCache.js +++ /dev/null @@ -1,124 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; - -class SkolengoPrivateCache { - static ASYNCSTORAGE_PREFIX = 'SkolengoCache_'; - - static _DATE_ENCODING_CHAR = '@'; - - static validateDateString = (str) => - typeof str === 'string' && - new Date(str?.toString()).toString() !== 'Invalid Date' && - str === new Date(str?.toString()).toISOString(); - - static parser = (obj) => - JSON.stringify( - obj, - (_key, value) => - this.validateDateString(value) - ? `${this.DATE_ENCODING_CHAR}${value}${this.DATE_ENCODING_CHAR}` - : value, - 2 - ); - - static deparser = (str) => - JSON.parse(str, (_key, value) => - typeof value === 'string' && - value.startsWith(this.DATE_ENCODING_CHAR) && - value.endsWith(this.DATE_ENCODING_CHAR) - ? new Date(value.replaceAll(this.DATE_ENCODING_CHAR, '')) - : value - ); -} - -export class SkolengoCache { - static SECOND = 1000; - - static MINUTE = 60 * this.SECOND; - - static HOUR = 60 * this.MINUTE; - - static DAY = 24 * this.HOUR; - - static msToTomorrow() { - const now = Date.now(); - const timePassedToday = now % this.DAY; - return this.DAY - timePassedToday; - } - - static setItem(key, value, timeout) { - const storedDatas = { - maxDate: new Date().getTime() + timeout, - data: value, - }; - return AsyncStorage.setItem( - `${SkolengoPrivateCache.ASYNCSTORAGE_PREFIX}${key}`, - SkolengoPrivateCache.parser(storedDatas) - ); - } - - static async setCollectionItem(key, id = '', value, timeout) { - const actualCache = await this.getItem(key, {}); - if (id?.toString().trim().length === 0) return; - actualCache.data[id] = { value, maxDate: new Date().getTime() + timeout }; - return this.setItem(key, actualCache.data, this.DAY * 30); - } - - static async getItem(key, defaultResponse = null) { - const cachedDatas = await AsyncStorage.getItem( - `${SkolengoPrivateCache.ASYNCSTORAGE_PREFIX}${key}` - ); - if (cachedDatas === null) - return { - expired: true, - maxDate: 0, - data: defaultResponse, - }; - const value = SkolengoPrivateCache.deparser(cachedDatas); - return { - expired: value.maxDate < new Date().getTime(), - maxDate: value.maxDate, - data: value.data, - }; - } - - static async getCollectionItem(key, id, defaultResponse = null) { - const cachedDatas = await this.getItem(key, {}); - if (!cachedDatas?.data || !cachedDatas?.data[id]) - return { - expired: true, - maxDate: 0, - data: defaultResponse, - }; - const value = cachedDatas.data[id]; - return { - expired: value.maxDate < new Date().getTime() || !value.data, - maxDate: value.maxDate, - data: value.data, - }; - } - - static removeItem(key) { - return AsyncStorage.removeItem( - `${SkolengoPrivateCache.ASYNCSTORAGE_PREFIX}${key}` - ); - } - - static clearItems = () => - Promise.all( - Object.values(this.cacheKeys).map((key) => this.removeItem(key)) - ); - - static cacheKeys = { - userdatas: 'userdatas', - absences: 'absences', - schoolInfos: 'schoolInfos', - schoolInfoCollection: 'schoolInfoCollection', - evalSettings: 'evalSettings', - evalDatas: 'evalDatas', - periods: 'periods', - grades: 'grades', - homeworkList: 'homeworkList', - timetable: 'timetable', - recap: 'recap', - }; -} diff --git a/fetch/SkolengoData/SkolengoCustomTypes.d.ts b/fetch/SkolengoData/SkolengoCustomTypes.d.ts deleted file mode 100644 index f8b4ffb7..00000000 --- a/fetch/SkolengoData/SkolengoCustomTypes.d.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { EvaluationDetail } from 'scolengo-api/types/models/Results'; - -export type Grades = { - grades: { - id: string; - subject: { - id: string; - name: string; - groups: boolean; - }; - date: string | null; - description: any; - is_bonus: boolean; - is_optional: boolean; - is_out_of_20: boolean; - grade: { - value: number | null; - out_of: number | null; - coefficient: number; - average: number | null; - max: number | null; - min: number | null; - significant: number; - }; - }[]; - averages: { - subject: { - id: string; - name: string; - groups: boolean; - }; - average: number | null; - class_average: number | null; - max: EvaluationDetail; - min: EvaluationDetail; - out_of: number; - significant: number; - color: string | null; - }[]; - overall_average: number; - class_overall_average: number; -}; - -export type Period = { - id: string; - name: string; - actual: boolean; -}; diff --git a/fetch/SkolengoData/SkolengoDatas.js b/fetch/SkolengoData/SkolengoDatas.js deleted file mode 100644 index 401e633d..00000000 --- a/fetch/SkolengoData/SkolengoDatas.js +++ /dev/null @@ -1,1239 +0,0 @@ -import { - TokenResponse, - AuthRequest, - exchangeCodeAsync, - resolveDiscoveryAsync, - revokeAsync, -} from 'expo-auth-session'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { deserialize } from 'jsonapi-fractal'; -import jwtDecode from 'jwt-decode'; -import { Alert } from 'react-native'; -import { btoa } from 'js-base64'; -import { coolDownAsync, warmUpAsync } from 'expo-web-browser'; -import { showMessage } from 'react-native-flash-message'; -import { SkolengoBase } from './SkolengoBase'; -import { SkolengoCache } from './SkolengoCache'; -import { SkolengoStatic } from './SkolengoLoginFlow'; - -const to20 = (grade, out_of) => (grade * 20) / out_of; - -export class SkolengoDatas extends SkolengoBase { - /** - * @param {Date} date - */ - static parseDate = (date) => - `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; - - static TOKEN_PATH = 'sko-token'; - - static SCHOOL_PATH = 'sko-school'; - - static CURRENT_USER_PATH = 'sko-current-user'; - - static DISCOVERY_PATH = 'sko-discovery'; - - static BASE_URL = 'https://api.skolengo.com/api/v1/bff-sko-app'; - - /** - * @type {Promise} - */ - school; - - /** - * @type {TokenResponse} - */ - rtInstance; - - /** - * @type {{ - * sub: string - * oauthClientId: string - * clientName: string - * roles: any[] - * iss: string - * client_id: string - * grant_type: string - * permissions: any[] - * scope: string - * serverIpAddress: string - * longTermAuthenticationRequestTokenUsed: boolean - * state: string - * exp: number - * iat: number - * jti: string - * email: string - * clientIpAddress: string - * isFromNewLogin: boolean - * authenticationDate: string - * successfulAuthenticationHandlers: string - * profile: string - * userAgent: string - * given_name: string - * nonce: string - * credentialType: string - * aud: string - * authenticationMethod: string - * geoLocation: string - * scopes: string - * family_name: string - * }} - */ - currentUser; - - /** - * @type {boolean} - * - * @memberof SkolengoDatas - */ - retry; - - saveToken = (discovery = false) => - Promise.all([ - AsyncStorage.setItem( - SkolengoDatas.TOKEN_PATH, - JSON.stringify(this.rtInstance) - ), - AsyncStorage.setItem( - SkolengoDatas.SCHOOL_PATH, - JSON.stringify(this.school) - ), - AsyncStorage.setItem( - SkolengoDatas.CURRENT_USER_PATH, - JSON.stringify(this.currentUser) - ), - discovery && - AsyncStorage.setItem( - SkolengoDatas.DISCOVERY_PATH, - JSON.stringify(discovery) - ), - ]) - .then(() => ('token-saved')) - .then(() => this); - - static initSkolengoDatas = async () => { - const [rtInstance, school, currentUser] = await Promise.all([ - AsyncStorage.getItem(SkolengoDatas.TOKEN_PATH) - .then((_token) => JSON.parse(_token)) - .then((token) => new TokenResponse(token)), - AsyncStorage.getItem(SkolengoDatas.SCHOOL_PATH).then((_school) => - JSON.parse(_school) - ), - AsyncStorage.getItem(SkolengoDatas.CURRENT_USER_PATH).then( - (_currentUser) => JSON.parse(_currentUser) - ), - ]); - return new SkolengoDatas(rtInstance, school, currentUser); - }; - - /** - * @param {TokenResponse} rtInstance - * @param {import('scolengo-api/types/models/School').School} school - */ - constructor(rtInstance, school) { - super(); - this.rtInstance = rtInstance; - this.school = school; - this.currentUserGenerate(); - this.retry = false; - } - - /** - * @param {'get' | 'post' | 'put' | 'delete'} method 'get' | 'post' | 'put' | 'delete' - * @param {string} url URL of the api route without baseurl - * @param {Record>} params Query params - * @param {Record|null} body Body of the request - * @param {"json"|"stream"} [responseType='json'] Response type (optional) - * @returns {Promise} - */ - request = async ( - method, - url, - params = {}, - body = null, - responseType = 'json', - retry = false, - dser = true - ) => { - let errStatus; - try { - const fetchOpt = { - method, - credentials: 'include', - body: body ? JSON.stringify(body) : null, - headers: { - Authorization: `Bearer ${this.rtInstance.accessToken}`, - 'X-Skolengo-Date-Format': 'utc', - 'X-Skolengo-School-Id': this.school.id, - 'X-Skolengo-Ems-Code': this.school.emsCode, - }, - }; - const fetchUrl = `${ - SkolengoDatas.BASE_URL - }${url}${SkolengoDatas.searchParamsSerialiser(params)}`; - const res = await fetch(fetchUrl, fetchOpt); - if (res.status === 401 && retry) { - Alert.alert( - 'Erreur de connexion', - 'Veuillez vous reconnecter à votre compte Skolengo', - [ - { - text: 'OK', - onPress: async () => { - const inst = await loginSkolengoWorkflow( - null, - null, - this.school, - this - ); - this.rtInstance = inst.rtInstance; - this.saveToken(); - }, - }, - ], - { cancelable: false } - ); - } - if (!res.ok || res.status >= 400) { - ('error', res.status, res.statusText); - ('error.body', await res.text()); - errStatus = res.status; - throw new Error(`Error ${res.status} : ${res.statusText}`); - } - if (responseType === 'stream') return res.body; - const txt = await res.text(); - const json = JSON.parse(txt); - if (dser) return deserialize(json); - return json; - } catch (e) { - if (!retry && errStatus === 401) { - await this.retrying(e); - return this.request(method, url, params, body, responseType, true); - } - ('retry failed', e.message, e); - } - }; - - async retrying(e) { - if (this.retry === false) { - // Après des tests, le refreshAsync de Expo-Auth-Session ne semble pas fonctionné avec les CAS Skolengo. - this.retry = true; - ('retry', e); - (e?.message); - const disco = await AsyncStorage.getItem( - SkolengoDatas.DISCOVERY_PATH - ).then((_discovery) => JSON.parse(_discovery)); - const url = `${disco.tokenEndpoint}?grant_type=refresh_token&refresh_token=${this.rtInstance.refreshToken}`; - const auth = `Basic ${btoa( - `${SkolengoBase.OID_CLIENT_ID}:${SkolengoBase.OID_CLIENT_SECRET}` - )}`; - const newToken = await fetch(url, { - method: 'POST', - mode: 'no-cors', - credentials: 'include', - headers: { - Accept: 'application/json', - Authorization: auth, - }, - }) - .catch((_e) => { - ('refresh error', _e); - (_e.response); - }) - .then(async (res) => res.json()); - const newRtInstance = new TokenResponse({ - accessToken: newToken.access_token || this.rtInstance.accessToken, - idToken: newToken.id_token || this.rtInstance.idToken, - refreshToken: newToken.refresh_token || this.rtInstance.refreshToken, - expiresIn: newToken.expires_in || this.rtInstance.expiresIn, - scope: newToken.scope || this.rtInstance.scope, - tokenType: newToken.token_type || this.rtInstance.tokenType, - }); - this.rtInstance = newRtInstance; - this.currentUserGenerate(); - await this.saveToken(); - this.retry = false; - } else { - return new Promise((resolve) => { - // interval checking - const interval = setInterval(() => { - if (this.retry === false) { - clearInterval(interval); - resolve(); - } - }, 1000); - }); - } - } - - currentUserGenerate = () => { - // Convertion du JWT en JSON (en e/ppant les caractères spéciaux) - this.currentUser = jwtDecode(this.rtInstance.accessToken); - }; - - getUser = async (force = false) => { - if (!force) { - const cachedRecap = await SkolengoCache.getItem( - SkolengoCache.cacheKeys.userdatas - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - const datas = await this.request( - 'get', - `/users-info/${this.currentUser.sub}`, - { - include: 'school,students,students.school', - } - ).then(this.usrTransform); - SkolengoCache.setItem( - SkolengoCache.cacheKeys.userdatas, - datas, - SkolengoCache.DAY - ); - return datas; - }; - - /** - * @returns {Promise} - */ - getAbsenceFiles = async (force = false, limit = 20, offset = 0) => { - if (!force) { - const cachedRecap = await SkolengoCache.getItem( - SkolengoCache.cacheKeys.absences - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - - const datas = await this.request('get', '/absence-files', { - filter: { - 'student.id': this.currentUser.sub, - 'currentState.absenceType': 'ABSENCE,LATENESS', - }, - include: - 'currentState,currentState.absenceReason,currentState.absenceRecurrence', - fields: { - absenceFile: 'currentState', - absenceFileState: - 'creationDateTime,absenceStartDateTime,absenceEndDateTime,absenceType,absenceFileStatus,absenceReason,absenceRecurrence', - absenceReason: 'code,longLabel', - absenceRecurrence: '', - }, - page: { - limit, - offset, - }, - }).then(async (e) => - e.length < limit - ? e - : this.getAbsenceFiles(force, limit, offset + limit).then((f) => - e.concat(f) - ) - ); - SkolengoCache.setItem( - SkolengoCache.cacheKeys.absences, - datas, - SkolengoCache.HOUR * 4 - ); - return datas; - }; - - /** - * @returns {Promise} - */ - getAbsenceFile = async (absenceFileId) => - this.request('get', `/absence-files/${absenceFileId}`, { - include: 'currentState', - filter: { - 'student.id': this.currentUser.sub, - 'currentState.absenceType': 'ABSENCE,LATENESS,DEPARTURE,EXEMPTION', - }, - }); - - getViesco = async (force = false) => - this.getAbsenceFiles(force).then(this.viescoTransform); - - /** - * @param {import('scolengo-api/types/models/SchoolLife').AbsenceFile[]} absences - */ - viescoTransform = (absences) => ({ - absences: absences - .filter( - (e) => - e.currentState.absenceType === 'ABSENCE' || - e.currentState.absenceType === 'EXEMPTION' - ) - .map((absence) => ({ - hours: Math.round( - (new Date(absence.currentState.absenceEndDateTime).getTime() - - new Date(absence.currentState.absenceStartDateTime).getTime()) / - (1000 * 60 * 60) - ), - from: absence.currentState.absenceStartDateTime, - to: absence.currentState.absenceEndDateTime, - reasons: [ - absence.currentState?.comment?.trim().length > 0 - ? absence.currentState.comment - : absence.currentState.absenceReason?.longLabel, - ], - justified: absence.currentState.absenceFileStatus === 'LOCKED', - })), - delays: absences - .filter( - (e) => - e.currentState.absenceType !== 'ABSENCE' && - e.currentState.absenceType !== 'EXEMPTION' - ) - .map((delay) => ({ - justified: delay.currentState.absenceFileStatus === 'LOCKED', - reasons: [ - delay.currentState?.comment?.trim().length > 0 - ? delay.currentState.comment - : delay.currentState.absenceReason?.longLabel, - ], - date: delay.currentState.absenceStartDateTime, - duration: Math.round( - (new Date(delay.currentState.absenceEndDateTime).getTime() - - new Date(delay.currentState.absenceStartDateTime).getTime()) / - (1000 * 60) - ), - })), - }); - - /** - * @returns {Promise} - */ - getSchoolInfos = async (force = false) => { - if (!force) { - const cachedRecap = await SkolengoCache.getItem( - SkolengoCache.cacheKeys.schoolInfos - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - const datas = await this.request('get', '/schools-info', { - include: - 'illustration,school,author,author.person,author.technicalUser,attachments', - }); - SkolengoCache.setItem( - SkolengoCache.cacheKeys.schoolInfos, - datas, - SkolengoCache.HOUR * 4 - ); - return datas; - }; - - /** - * @returns {Promise} - */ - getSchoolInfo = async (schoolInfoId, force = false) => { - if (!force) { - const cachedRecap = await SkolengoCache.getCollectionItem( - SkolengoCache.cacheKeys.schoolInfoCollection, - schoolInfoId, - {} - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - const datas = await this.request('get', `/schools-info/${schoolInfoId}`, { - include: - 'illustration,school,author,author.person,author.technicalUser,attachments', - }); - SkolengoCache.setCollectionItem( - SkolengoCache.cacheKeys.schoolInfoCollection, - schoolInfoId, - datas, - SkolengoCache.HOUR * 4 - ); - return datas; - }; - - getNews = async (force = false) => - this.getSchoolInfos(force).then((infos) => infos.map(this.newsTransform)); - - getUniqueNews = async (id, force = false) => - this.getSchoolInfo(id, force).then(this.newsTransform); - - /** - * @param {import('scolengo-api/types/models/School').SchoolInfo} info - */ - newsTransform = (info) => ({ - id: info.id, - title: ucFirst(info.title), - content: info.shortContent, - date: info.publicationDateTime, - author: info?.school?.name, - html_content: [{ texte: { V: info.content } }], - attachments: info.attachments.map((attach) => ({ - url: attach.url, - name: attach.name, - })), - }); - - /** - * @param {string} startDate (format : 2000-12-01) - * @param {string} endDate (format : 2000-12-31) - * @param {number} [limit=50] - * @param {number} [offset=0] - * @memberof SkolengoDatas - * @returns {Promise} - */ - getAgendas = ( - startDate, - endDate, - limit = 50, - offset = 0, - include = 'lessons,lessons.subject,lessons.teachers,homeworkAssignments,homeworkAssignments.subject' - ) => - SkolengoBase.dateVerifier(startDate, endDate || startDate) - ? this.request('get', '/agendas', { - include, - filter: { - 'student.id': this.currentUser.sub, - date: { - GE: SkolengoBase.dateParser(startDate), - LE: SkolengoBase.dateParser(endDate || startDate), - }, - }, - page: { - limit, - offset, - }, - }).then(async (e) => - e.length < limit - ? e - : this.getAgendas(startDate, endDate, limit, offset + limit).then( - (f) => e.concat(f) - ) - ) - : Promise.resolve([ - { - id: 'none', - date: SkolengoBase.dateParser(Date.now()), - lessons: [], - homeworkAssignments: [], - }, - ]); - - /** - * @returns {Promise} - */ - getEvaluationSettings = async (force = false, limit = 20, offset = 0) => { - if (!force) { - const cachedRecap = await SkolengoCache.getItem( - SkolengoCache.cacheKeys.evalSettings - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - const settings = await this.request('get', '/evaluations-settings', { - filter: { - 'student.id': this.currentUser.sub, - }, - page: { - limit, - offset, - }, - include: 'periods,skillsSetting,skillsSetting.skillAcquisitionColors', - }); - SkolengoCache.setItem( - SkolengoCache.cacheKeys.evalSettings, - settings, - SkolengoCache.DAY * 3 - ); - return settings; - }; - - /** - * @returns {Promise} - */ - getEvaluation = async (evaluationId, force = false) => { - if (!force) { - const cachedRecap = await SkolengoCache.getCollectionItem( - SkolengoCache.cacheKeys.evalDatas, - evaluationId, - {} - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - const datas = await this.request('get', `/evaluations/${evaluationId}`, { - include: - 'evaluationService,evaluationService.subject,evaluationService.teachers,subSubject,subSkills,evaluationResult,evaluationResult.subSkillsEvaluationResults,evaluationResult.subSkillsEvaluationResults.subSkill', - filter: { - 'student.id': this.currentUser.sub, - }, - fields: { - evaluationService: 'subject,teachers', - subject: 'label,color', - subSubject: 'label', - evaluation: 'title,topic,dateTime,coefficient,min,max,average,scale', - evaluationResult: - 'subSkillsEvaluationResults,nonEvaluationReason,mark,comment', - subSkill: 'shortLabel', - subSkillEvaluationResult: 'level,subSkill', - teacher: 'firstName,lastName,title', - }, - }); - /* SkolengoCache.getItem('evals', {}) - .then((actualCache) => ({ - ...actualCache.data, - [evaluationId]: datas, - })) - .then((newSavedCache) => - SkolengoCache.setItem('evals', newSavedCache, SkolengoCache.HOUR * 4) - ); */ - SkolengoCache.setCollectionItem( - SkolengoCache.cacheKeys.evalDatas, - evaluationId, - datas, - SkolengoCache.HOUR * 4 - ); - return datas; - }; - - /** - * @returns {Promise} - */ - getPeriods = async (force = false) => { - if (!force) { - const cachedRecap = await SkolengoCache.getItem( - SkolengoCache.cacheKeys.periods - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - const datas = await this.getEvaluationSettings(force).then((e) => - e.at(0)?.periods?.map((period) => this.periodTransform(period)) - ); - if (datas !== undefined) - SkolengoCache.setItem( - SkolengoCache.cacheKeys.periods, - datas, - SkolengoCache.DAY * 14 - ); - return datas; - }; - - /** - * - * @param {number|string} periodId - * @param {boolean} [force=false] - * @param {number} [limit=100] - * @param {number} [offset=0] - * @returns {Promise} - */ - getGrades = async (periodId, force = false, limit = 100, offset = 0) => { - if (!force && periodId) { - const cachedRecap = await SkolengoCache.getItem( - SkolengoCache.cacheKeys.grades - ); - if ( - !cachedRecap.expired && - Array.isArray(cachedRecap.data?.grades) && - Number(cachedRecap.data?.periodId) === Number(periodId) - ) - return cachedRecap.data; - } - if (!periodId) - periodId = await this.getPeriods(false).then( - (periods) => periods.find((period) => period.actual)?.id - ); - ('pId', periodId); - if (typeof periodId !== 'string' && typeof periodId !== 'number') - return this.gradesTransform([]); - - const datas = await this.getNotes(periodId, limit, offset) - .then((evals) => this.noteFullFill(evals, force)) - .then(this.gradesTransform); - SkolengoCache.setItem( - SkolengoCache.cacheKeys.grades, - { ...datas, periodId }, - SkolengoCache.HOUR * 4 - ); - return datas; - }; - - /** - * @param {import('scolengo-api/types/models/Results').Evaluation[]} evals - */ - noteFullFill = async (evals, force = false) => - Promise.all( - evals.map(async (evalSubj) => ({ - ...evalSubj, - evaluations: await Promise.all( - evalSubj.evaluations.map((evalData) => - this.getEvaluation(evalData.id, force) - ) - ), - })) - ); - - /** - * @returns {Promise} - */ - getNotes = async (periodId, limit = 100, offset = 0) => - this.request('get', '/evaluation-services', { - filter: { - 'student.id': this.currentUser.sub, - 'period.id': periodId, - }, - page: { - limit, - offset, - }, - include: - 'subject,evaluations,evaluations.evaluationResult,evaluations.evaluationResult.subSkillsEvaluationResults,evaluations.evaluationResult.subSkillsEvaluationResults.subSkill,evaluations.subSkills,teachers', - fields: { - evaluationService: 'coefficient,average,studentAverage,scale', - subject: 'label,color', - evaluation: - 'dateTime,coefficient,average,scale,evaluationResult,subSkills', - evaluationResult: 'mark,nonEvaluationReason,subSkillsEvaluationResults', - subSkillEvaluationResult: 'level,subSkill', - teacher: 'firstName,lastName,title', - subSkill: 'shortLabel', - }, - }) - .then(async (e) => - e.length < limit - ? e - : this.getNotes(periodId, limit, offset + limit).then((f) => [ - ...e, - ...f, - ]) - ) - .catch((e) => { - ('perioderr', periodId, e); - }); - - /** - * @returns {Promise} - */ - getHomeworkAssignments = async ( - startDate, - endDate, - limit = 20, - offset = 0 - ) => - SkolengoBase.dateVerifier(startDate, endDate) - ? this.request('get', '/homework-assignments', { - include: 'subject,teacher,teacher.person', - filter: { - 'student.id': this.currentUser.sub, - dueDate: { - GE: SkolengoBase.dateParser(startDate), - LE: SkolengoBase.dateParser(endDate), - }, - }, - page: { - limit, - offset, - }, - }).then(async (e) => - e.length < limit - ? e - : this.getHomeworkAssignments( - startDate, - endDate, - limit, - offset + limit - ).then((f) => e.concat(f)) - ) - : []; - - /** - * @returns {Promise} - */ - // route non utilisé actuellement - getHomeworkAssignment = async (homeworkId /* , force = false */) => - this.request('get', `/homework-assignments/${homeworkId}`, { - include: - 'subject,teacher,teacher.person,pedagogicContent,individualCorrectedWork,individualCorrectedWork.attachments,individualCorrectedWork.audio,commonCorrectedWork,commonCorrectedWork.attachments,commonCorrectedWork.audio,commonCorrectedWork.pedagogicContent,attachments,audio', - filter: { - 'student.id': this.currentUser.sub, - }, - }); - - getHomeworks = async (day, force, day2) => { - const dayFormatted = SkolengoBase.dateParser(day); - const day2Formatted = SkolengoBase.dateParser(day2 || day); - const canBeCached = !day2 || dayFormatted === day2Formatted; - if (!force && canBeCached) { - const cachedRecap = await SkolengoCache.getCollectionItem( - SkolengoCache.cacheKeys.homeworkList, - dayFormatted, - {} - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - ('fetch hw', dayFormatted); - const datas = await this.getAgendas( - dayFormatted, - day2Formatted, - 50, - 0, - 'homeworkAssignments,homeworkAssignments.subject' - ) - .then( - (e) => - e?.map((f) => - f.homeworkAssignments.map((g) => this.homeworkTransform(g)) - ) || [] - ) - .then((e) => (canBeCached ? e.flat() : e)); - - if (canBeCached) - SkolengoCache.setCollectionItem( - SkolengoCache.cacheKeys.homeworkList, - dayFormatted, - datas, - SkolengoCache.HOUR * 4 - ); - return datas; - }; - - getLongHomeworks = async (day, day2) => { - if (!day2) day2 = day; - return this.getAgendas( - SkolengoBase.dateParser(day), - SkolengoBase.dateParser(day2), - 50, - 0, - 'homeworkAssignments,homeworkAssignments.subject' - ).then((e) => - e?.map((f) => ({ - date: f.date, - homeworks: f.homeworkAssignments.map((g) => this.homeworkTransform(g)), - })) - ); - }; - - /** - * @returns {Promise} - */ - patchHomeworkAssignment = async (homeworkId, done) => { - const homework = await this.request( - 'patch', - `/homework-assignments/${homeworkId}`, - { - include: 'subject,teacher,teacher.person', - filter: { - 'student.id': this.currentUser.sub, - }, - }, - { - data: { - type: 'homework', - id: homeworkId, - attributes: { - done, - }, - }, - } - ); - const date = new Date(homework.dueDateTime || homework.dueDate); - const validDate = Number.isNaN(date.getTime()) ? null : date.toISOString(); - if (validDate) this.getHomeworks(date, true); - return { ...homework, status: 'ok' }; - }; - - /** - * @returns {Promise} - */ - getLesson = async (lessonId) => - this.request('get', `/lessons/${lessonId}`, { - include: - 'teachers,contents,contents.attachments,subject,toDoForTheLesson,toDoForTheLesson.subject,toDoAfterTheLesson,toDoAfterTheLesson.subject', - filter: { - 'student.id': this.currentUser.sub, - }, - }); - - getTimetable = async (day, force = false) => { - if (!force) { - const cachedRecap = await SkolengoCache.getCollectionItem( - SkolengoCache.cacheKeys.timetable, - SkolengoBase.dateParser(day), - {} - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - const agendas = await this.getAgendas( - SkolengoBase.dateParser(day), - SkolengoBase.dateParser(day) - ); - const datas = this.timetableTransform(agendas[0]); - SkolengoCache.setCollectionItem( - SkolengoCache.cacheKeys.timetable, - SkolengoBase.dateParser(day), - datas, - SkolengoCache.HOUR * 4 - ); - return datas; - }; - - static gradesDefault = { - grades: [], - averages: [], - overall_average: 0, - class_overall_average: 0, - }; - - getRecap = async (startDate, force) => { - if (!force) { - const cachedRecap = await SkolengoCache.getItem( - SkolengoCache.cacheKeys.recap - ); - if (!cachedRecap.expired) return cachedRecap.data; - } - try { - const [timetable, homeworkAgendas, notes] = await Promise.all([ - this.getTimetable(SkolengoBase.dateParser(startDate), force), - this.getAgendas( - SkolengoBase.dateParser(startDate), - SkolengoBase.dateParser( - new Date(startDate).getTime() + 15 * 24 * 60 * 60 * 1000 - ), - 50, - 0, - 'homeworkAssignments,homeworkAssignments.subject' - ), - this.getEvaluationSettings(force).then((periods) => - this.getGrades(periods[0].periods[0].id, force) - ), - ]); - const hws = homeworkAgendas - .filter((e) => e.homeworkAssignments.length > 0) - .map((homeworkAgenda) => ({ - date: new Date(homeworkAgenda.date), - hws: homeworkAgenda.homeworkAssignments.map((homework) => - this.homeworkTransform(homework) - ), - })); - const res = [ - timetable, - hws.at(0) || { - date: new Date(startDate), - hws: [], - }, - notes, - hws.at(1) || { - date: new Date(startDate), - hws: [], - }, - hws.at(2) || { - date: new Date(startDate), - hws: [], - }, - hws, - ]; - SkolengoCache.setItem( - SkolengoCache.cacheKeys.recap, - res, - SkolengoCache.msToTomorrow() - ); - return res; - } catch (e) { - ('recap err', e); - return [[], [], SkolengoDatas.gradesDefault]; - } - }; - - /** - * @param {import("scolengo-api/types/models/Common/User").User} user - */ - usrTransform = (user) => ({ - id: user.id, - name: `${user.lastName.toUpperCase()} ${user.firstName}`, - class: null, - establishment: - user.school && user.school.id === this.school.id ? this.school.name : '', - phone: user.mobilePhone, - email: user.importedMail || user.internalMail || user.externalMail, - address: - user.school && user.school.id === this.school.id - ? [ - this.school.addressLine1, - this.school.addressLine2, - this.school.addressLine3, - this.school.zipCode, - this.school.city, - this.school.country, - ].filter((e) => e) - : [], - ine: null, - profile_picture: null, - delegue: [], - periods: [], - }); - - /** - * @param {import('scolengo-api/types/models/Calendar').Agenda[]} agendas - */ - timetableTransform = ( - agenda = { - id: 'none', - date: SkolengoBase.dateParser(Date.now()), - lessons: [], - } - ) => - agenda?.lessons?.map((lesson) => ({ - id: lesson.id, - num: lesson.id, - subject: { - id: lesson.subject.id, - name: lesson.subject.label, - groups: false, - }, - teachers: lesson?.teachers?.map( - (e) => `${e.lastName} ${e.firstName.slice(0, 1)}.` - ), - rooms: [lesson.location].filter((e) => e), - group_names: [], - memo: null, - virtual: [], - start: lesson.startDateTime, - end: lesson.endDateTime, - background_color: lesson.subject.color, - status: null, - is_cancelled: lesson.canceled, - is_outing: false, - is_detention: false, - is_exempted: false, - is_test: false, - })); - - /** - * @param {import('scolengo-api/types/models/Results').Evaluation[]} evals - */ - gradesTransform = (evals = []) => { - const grades = evals - .map((evalSubj) => - evalSubj.evaluations.map((evalData) => ({ - id: evalData.id, - subject: { - id: evalSubj.subject.id, - name: evalSubj.subject.label, - groups: false, - }, - date: evalData.dateTime, - description: ucFirst( - evalData.topic?.trim() || evalData.title?.trim() || '' - ), - is_bonus: false, - is_optional: false, - is_out_of_20: evalData.scale === 20, - grade: { - value: evalData.evaluationResult.mark, - out_of: evalData.scale, - coefficient: evalData.coefficient, - average: evalData.average, - max: evalData.max, - min: evalData.min, - significant: - evalData.evaluationResult.nonEvaluationReason || - typeof evalData.evaluationResult.mark !== 'number' - ? 3 - : 0, - }, - })) - ) - .flat(); - const averages = - evals - ?.filter((e) => e.evaluations?.length > 0) - .map((evalSubj) => ({ - subject: { - id: evalSubj.subject.id, - name: evalSubj.subject.label, - groups: false, - }, - average: evalSubj.studentAverage, - class_average: evalSubj.average, - max: evalSubj.evaluations.reduce( - (acc, e) => - acc > to20(e.max, e.scale) ? acc : to20(e.max, e.scale), - 0 - ), - min: evalSubj.evaluations.reduce( - (acc, e) => - acc < to20(e.min, e.scale) ? acc : to20(e.min, e.scale), - 20 - ), - out_of: 20, - significant: 0, - color: evalSubj.subject.color, - })) || []; - - ( - 'av', - averages.map((e) => e) - ); - - const countedAverages = averages - .map((e) => e.average) - .filter((e) => typeof e === 'number'); - const overall_average = - countedAverages.reduce((a, b) => a + b, 0) / countedAverages.length; - (overall_average); - const countedOverallAverages = averages - .map((e) => e.class_average) - .filter((e) => e); - const class_overall_average = - countedOverallAverages.reduce((a, b) => a + b, 0) / - countedOverallAverages.length; - return { - grades, - averages, - overall_average, - class_overall_average, - }; - }; - - /** - * @param {import('scolengo-api/types/models/Calendar').HomeworkAssignment} homework - */ - homeworkTransform = (homework) => { - const dat = new Date(homework.dueDateTime || homework.dueDate); - if (Number.isNaN(dat.getTime())) - ({ - homework, - }); - return { - id: homework.id, - local_id: homework.id, - done: homework.done, - date: Number.isNaN(dat.getTime()) ? null : dat.toISOString(), - background_color: homework.subject.color, - subject: { - id: homework.subject.id, - name: homework.subject.label, - }, - description: homework.html - .replace(/<[^>]+>/g, '') - .split('\n') - .map((e) => e.trim()) - .filter((e) => e.length > 0) - .join('\n'), - files: (homework.attachments || []).map((attach) => ({ - id: attach.id, - url: attach.url, - type: 0, - name: attach.name, - })), - }; - }; - - /** - * @param {import('scolengo-api/types/models/Results/EvaluationSettings').Period} period - */ - periodTransform = (period) => ({ - id: period.id, - name: period.label, - actual: - new Date() > new Date(period.startDate) && - new Date() < new Date(period.endDate), - }); - - skolengoDisconnect = async () => { - const discovery = await AsyncStorage.getItem(SkolengoDatas.DISCOVERY_PATH)?.then(JSON.parse); - return Promise.all([ - revokeAsync( - { ...this.rtInstance, token: this.rtInstance.accessToken }, - { - ...discovery, - revocationEndpoint: - discovery.revocationEndpoint || discovery.revocation_endpoint - } - ).then(() => ('token revoked')), - AsyncStorage.removeItem(SkolengoDatas.TOKEN_PATH), - AsyncStorage.removeItem(SkolengoDatas.SCHOOL_PATH), - AsyncStorage.removeItem(SkolengoDatas.CURRENT_USER_PATH), - AsyncStorage.removeItem(SkolengoDatas.DISCOVERY_PATH), - SkolengoCache.clearItems(), - ]); - }; -} - -export const ucFirst = (str) => - (str?.charAt(0)?.toUpperCase() ?? '') + (str?.slice(1) ?? ''); - -const errHandler = (err) => { - if (err instanceof Error) console.error(err); - Alert.alert( - 'Erreur', - 'Une erreur est survenue lors de la connexion à Skolengo. Veuillez réessayer.' - ); -}; - -/** - * @param {typeof import("../../../utils/AppContext").DefaultValuesAppContext} appContext - * @param {any} navigation - * @param {import("scolengo-api/types/models/School").School} school - * @param {SkolengoDatas} [skolengoInstance=undefined] - */ -export const loginSkolengoWorkflow = async ( - appContext, - navigation, - school, - skolengoInstance = undefined -) => { - const disco = await Promise.all([ - fetch(school.emsOIDCWellKnownUrl) - .then((res) => res.json()) - .then((res) => res.issuer), - warmUpAsync(), - ]) - .then(([issuer]) => resolveDiscoveryAsync(issuer)) - .catch(errHandler); - const authRes = new AuthRequest({ - clientId: SkolengoStatic.OID_CLIENT_ID, - clientSecret: SkolengoStatic.OID_CLIENT_SECRET, - redirectUri: 'skoapp-prod://sign-in-callback', - extraParams: { - scope: 'openid', - response_type: 'code', - }, - usePKCE: false, - }); - const res = await authRes.promptAsync(disco).catch(errHandler); - coolDownAsync(); - if (res?.type === 'dismiss') return; - if (!res.params?.code) { - return errHandler(res.error); - } - const token = await exchangeCodeAsync( - { - clientId: SkolengoStatic.OID_CLIENT_ID, - clientSecret: SkolengoStatic.OID_CLIENT_SECRET, - code: res.params.code, - redirectUri: 'skoapp-prod://sign-in-callback', - }, - disco - ).catch(errHandler); - if (!token) return errHandler(); - Alert.alert( - 'Skolengo : intégration en cours', - 'Veuillez patienter, le processus de connexion à Skolengo à fonctionné.\nMais l\'intégration de Skolengo (NG) n\'est pas encode terminé.\n\nRevenez plus tard.', - ); - // TODO : Créer l'intégration via scolengo-api - return; - await Promise.all([ - AsyncStorage.setItem('service', 'Skolengo'), - AsyncStorage.setItem('token', 'skolengo'), - ]); - showMessage({ - message: 'Connecté avec succès', - type: 'success', - icon: 'auto', - floating: true, - }); - if (appContext) { - const a = new SkolengoDatas(token, school); - await a.saveToken(disco); - appContext.dataProvider.init('Skolengo').then(() => { - appContext.setLoggedIn(true); - navigation.popToTop(); - }); - return true; - } - skolengoInstance.school = school; - skolengoInstance.rtInstance = token; - return skolengoInstance; -}; diff --git a/fetch/SkolengoData/SkolengoLoginFlow.js b/fetch/SkolengoData/SkolengoLoginFlow.js deleted file mode 100644 index c3729881..00000000 --- a/fetch/SkolengoData/SkolengoLoginFlow.js +++ /dev/null @@ -1,56 +0,0 @@ -import { deserialize } from 'jsonapi-fractal'; - -import { SkolengoBase } from './SkolengoBase'; - -export class SkolengoStatic extends SkolengoBase { - static getSchools = ( - { text, lat, lon }, - { limit, offset } = { - limit: 100, - offset: 0, - } - ) => - fetch( - `${this.BASE_URL}/schools${this.searchParamsSerialiser({ - page: { - limit, - offset, - }, - filter: { - text: text || undefined, - lat: lat || undefined, - lon: lon || undefined, - }, - })}` - ) - .then((res) => res.json()) - .then((res) => deserialize(res)); - - static getSSchools = ( - { text, lat, lon }, - { limit, offset } = { - limit: 10, - offset: 0, - } - ) => { - ('search'); - return fetch({ - url: `${SkolengoStatic.BASE_URL}/schools`, - method: 'GET', - params: { - page: { - limit, - offset, - }, - filter: { - text: text ?? undefined, - lat: lat ?? undefined, - lon: lon ?? undefined, - }, - }, - }) - .then((res) => res.json()) - .then((res) => deserialize(res)) - .catch((err) => (err)); - }; -} diff --git a/fetch/index.ts b/fetch/index.ts index 5baa6e70..c4064a68 100644 --- a/fetch/index.ts +++ b/fetch/index.ts @@ -20,10 +20,9 @@ import { newsHandler as pronoteNewsHandler, newsStateHandler as pronoteNewsState import { homeworkPatchHandler as pronoteHomeworkPatchHandler, homeworkHandler as pronoteHomeworkHandler } from './PronoteData/homework'; import { discussionsHandler as pronoteDiscussionsHandler, discussionsRecipientsHandler as pronoteDiscussionsRecipientsHandler } from './PronoteData/discussions'; -// Skolengo related imports. -import type { SkolengoDatas } from './SkolengoData/SkolengoDatas'; import { PapillonVieScolaire } from './types/vie_scolaire'; import { PapillonEvaluation } from './types/evaluations'; +import { SkolengoDataProvider } from './NewSkolengo/SkolengoDataProvider'; export type ServiceName = 'pronote' | 'skolengo' @@ -33,7 +32,7 @@ export class IndexDataInstance { public isNetworkFailing = false; public service?: ServiceName; - public skolengoInstance?: SkolengoDatas; + public skolengoInstance?: SkolengoDataProvider; public pronoteInstance?: Pronote; /** @@ -77,8 +76,7 @@ export class IndexDataInstance { this.initializing = true; if (this.service === 'skolengo') { - const skolengo = await import('./SkolengoData/SkolengoDatas.js'); - this.skolengoInstance = await skolengo.SkolengoDatas.initSkolengoDatas(); + this.skolengoInstance = new SkolengoDataProvider(); // TODO: Let's say for now that it never fails... this.isNetworkFailing = false; @@ -227,7 +225,8 @@ export class IndexDataInstance { sunday.setDate(sundayIndex); if (this.service === 'skolengo') { - return this.skolengoInstance!.getTimetable(day, force); + // TODO + //return this.skolengoInstance!.getTimetable(day, force); } else if (this.service === 'pronote') { const timetable = await pronoteTimetableHandler([monday, sunday], this.pronoteInstance, force); diff --git a/utils/AppContext.tsx b/utils/AppContext.tsx index 26e0d5b1..81dc88e5 100644 --- a/utils/AppContext.tsx +++ b/utils/AppContext.tsx @@ -9,7 +9,7 @@ import { useMaterial3Theme } from '@pchmn/expo-material3-theme'; import type { IndexDataInstance } from '../fetch'; -interface AppContextType { +export interface AppContextType { loggedIn: boolean setLoggedIn: (loggedIn: boolean) => void dataProvider: IndexDataInstance | null diff --git a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx index fdfa65d8..785475cf 100644 --- a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx +++ b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx @@ -23,9 +23,9 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as Location from 'expo-location'; import { School as SkolengoSchool } from 'scolengo-api/types/models/School'; -import { loginSkolengoWorkflow } from '../../../fetch/SkolengoData/SkolengoDatas'; import { useAppContext } from '../../../utils/AppContext'; import { Skolengo } from 'scolengo-api'; +import { loginSkolengoWorkflow } from '../../../fetch/NewSkolengo/SkolengoAuthWorkflow'; export const LocateSkolengoEtab = ({ navigation, From e26fe865fb79389215a48d5fce629f8277e261ea Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Wed, 20 Mar 2024 22:06:46 +0100 Subject: [PATCH 14/24] feat: :sparkles: finalisation authentification Skolengo --- fetch/NewSkolengo/SkolengoAuthWorkflow.ts | 22 ++++++++-------------- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/fetch/NewSkolengo/SkolengoAuthWorkflow.ts b/fetch/NewSkolengo/SkolengoAuthWorkflow.ts index c47a3772..721abfe4 100644 --- a/fetch/NewSkolengo/SkolengoAuthWorkflow.ts +++ b/fetch/NewSkolengo/SkolengoAuthWorkflow.ts @@ -8,6 +8,7 @@ import { } from 'expo-auth-session'; import { coolDownAsync, warmUpAsync } from 'expo-web-browser'; import { Alert } from 'react-native'; +import { OID_CLIENT_ID, OID_CLIENT_SECRET, REDIRECT_URI } from 'scolengo-api'; const skolengoErrorHandler = (err?: Error | any) => { if (err instanceof Error) console.error(err); @@ -33,14 +34,6 @@ export const loginSkolengoWorkflow = async ( .then(([issuer]) => resolveDiscoveryAsync(issuer)) .catch(skolengoErrorHandler); if (!disco) return; - const OID_CLIENT_ID = atob( - 'U2tvQXBwLlByb2QuMGQzNDkyMTctOWE0ZS00MWVjLTlhZjktZGY5ZTY5ZTA5NDk0' - ); - const OID_CLIENT_SECRET = atob( - 'N2NiNGQ5YTgtMjU4MC00MDQxLTlhZTgtZDU4MDM4NjkxODNm' - ); - const REDIRECT_URI = 'skoapp-prod://sign-in-callback'; - // TODO : En attente d'une possible intégration dans scolengo-api (cf https://github.com/maelgangloff/scolengo-api/pull/41) const authRes = new AuthRequest({ clientId: OID_CLIENT_ID, clientSecret: OID_CLIENT_SECRET, @@ -53,25 +46,26 @@ export const loginSkolengoWorkflow = async ( }); const res = await authRes.promptAsync(disco).catch(skolengoErrorHandler)!; coolDownAsync(); - if (!res || res?.type === 'dismiss') return; - /* if (!res?.params?.code) { + if (!res || res?.type !== 'success') return; + if (!res.params.code) { return skolengoErrorHandler(res.error); } const token = await exchangeCodeAsync( { - clientId: SkolengoStatic.OID_CLIENT_ID, - clientSecret: SkolengoStatic.OID_CLIENT_SECRET, + clientId: OID_CLIENT_ID, + clientSecret: OID_CLIENT_SECRET, code: res.params.code, - redirectUri: 'skoapp-prod://sign-in-callback', + redirectUri: REDIRECT_URI, }, disco ).catch(skolengoErrorHandler); if (!token) return skolengoErrorHandler(); + console.log({token}); Alert.alert( 'Skolengo : intégration en cours', 'Veuillez patienter, le processus de connexion à Skolengo à fonctionné.\nMais l\'intégration de Skolengo (NG) n\'est pas encode terminé.\n\nRevenez plus tard.' ); - // TODO : Créer l'intégration via scolengo-api + /* // TODO : Créer l'intégration via scolengo-api return; */ /* await Promise.all([ AsyncStorage.setItem('service', 'Skolengo'), diff --git a/package-lock.json b/package-lock.json index 34e16355..80f92f15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,7 +103,7 @@ "react-native-webview": "^13.8.1", "react-native-wheel-scrollview-picker": "^2.0.4", "reanimated-color-picker": "^2.3.4", - "scolengo-api": "^3.0.2", + "scolengo-api": "^3.0.3", "sync-storage": "^0.4.2", "typescript": "^5.1.3" }, @@ -19965,9 +19965,9 @@ } }, "node_modules/scolengo-api": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/scolengo-api/-/scolengo-api-3.0.2.tgz", - "integrity": "sha512-gSxz7OnL9liT6eZQlqhRgra3RgzEjMzRw5KnfJ2E8BxFZpq8whbMyEOPW0bo1adoVLjtUA9iciZrZ1zfV0KQ6w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/scolengo-api/-/scolengo-api-3.0.3.tgz", + "integrity": "sha512-vV4OR03hAWmWaDTQvScfRVocQVxSwlGoft6vLLbFvsCki5y6dH1So5sQmf58BZx73KIOijRC7rP0jVMAWhsMag==", "dependencies": { "axios": "^1.3.5", "base-64": "^1.0.0", diff --git a/package.json b/package.json index fb1710a7..662e1476 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "react-native-webview": "^13.8.1", "react-native-wheel-scrollview-picker": "^2.0.4", "reanimated-color-picker": "^2.3.4", - "scolengo-api": "^3.0.2", + "scolengo-api": "^3.0.3", "sync-storage": "^0.4.2", "typescript": "^5.1.3" }, From ea4e250d89aeec5b99e5c9ec8fab0d6149165e94 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Wed, 20 Mar 2024 22:14:21 +0100 Subject: [PATCH 15/24] feat: :coffin: suppression GradeView --- App.old.tsx | 1 - views/Grades/GradeView.js | 562 -------------------------------------- 2 files changed, 563 deletions(-) delete mode 100644 views/Grades/GradeView.js diff --git a/App.old.tsx b/App.old.tsx index e376b9b0..1055fb0f 100644 --- a/App.old.tsx +++ b/App.old.tsx @@ -54,7 +54,6 @@ import HeaderSelectScreen from './views/Settings/HeaderSelectScreen'; import PaymentScreen from './views/Settings/PaymentScreen'; import GradesScreen from './views/GradesScreenNew'; -import GradeView from './views/Grades/GradeView'; import GradesSettings from './views/Grades/GradesSettings'; import NewsScreen from './views/NewsScreen'; diff --git a/views/Grades/GradeView.js b/views/Grades/GradeView.js deleted file mode 100644 index b6e3cea7..00000000 --- a/views/Grades/GradeView.js +++ /dev/null @@ -1,562 +0,0 @@ -import * as React from 'react'; -import { useEffect, useState } from 'react'; -import { - View, - ScrollView, - StyleSheet, - StatusBar, - TouchableOpacity, - Alert, - Platform, - Share as ShareUI, - Modal, - ActivityIndicator, -} from 'react-native'; -import { useTheme, Text } from 'react-native-paper'; - -import Config from 'react-native-config'; - -import { - Diff, - GraduationCap, - Percent, - Share, - SquareAsterisk, - TrendingDown, - TrendingUp, - UserMinus, - UserPlus, - Users2, - ChevronLeft, -} from 'lucide-react-native'; - -import { useLayoutEffect } from 'react'; -import { PressableScale } from 'react-native-pressable-scale'; -import { getSavedCourseColor } from '../../utils/ColorCoursName'; - -import formatCoursName from '../../utils/FormatCoursName'; -import GetUIColors from '../../utils/GetUIColors'; -import { useAppContext } from '../../utils/AppContext'; - -import NativeList from '../../components/NativeList'; -import NativeItem from '../../components/NativeItem'; -import NativeText from '../../components/NativeText'; - -import { Buffer } from 'buffer'; - -import {calculateAverage, calculateSubjectAverage} from '../../utils/grades/averages'; - -function GradeView({ route, navigation }) { - const appContext = useAppContext(); - const theme = useTheme(); - const { grade, allGrades } = route.params; - const UIColors = GetUIColors(); - - const [modalLoading, setModalLoading] = useState(false); - const [modalLoadingText, setModalLoadingText] = useState(''); - - const [isShared , setIsShared] = useState(false); - - let mainColor = '#888888'; - if (grade.background_color) { - mainColor = getSavedCourseColor(grade.subject.name, grade.background_color); - } - - let { description } = grade; - if (description === '') { - description = 'Aucune description'; - } - - // change header title component - useLayoutEffect(() => { - navigation.setOptions({ - headerTitle: description, - headerStyle: { - backgroundColor: mainColor, - }, - headerShadowVisible: false, - headerLeft: () => ( - Platform.OS === 'ios' ? ( - navigation.goBack()} style={styles.iosBack}> - - - ) : null - ), - }); - }, [navigation, grade]); - - const [average, setAverage] = useState(0); - const [averageWithoutGrade, setAverageWithoutGrade] = useState(0); - const [avgInfluence, setAvgInfluence] = useState(0); - const [avgPercentInfluence, setAvgPercentInfluence] = useState(0); - const [classAvg, setClassAvg] = useState(0); - const [classAvgWithoutGrade, setClassAvgWithoutGrade] = useState(0); - const [classAvgInfluence, setClassAvgInfluence] = useState(0); - const [valueTop, setValueTop] = useState(0); - const [valueBottom, setValueBottom] = useState(0); - - async function calculateInfluence(forgr, grlwg) { - const naverage = await calculateSubjectAverage(forgr, 'value'); - const naverageWithoutGrade = await calculateSubjectAverage(grlwg, 'value'); - const navgInfluence = naverage - naverageWithoutGrade; - const navgPercentInfluence = (navgInfluence / naverage) * 100 || 0; - const nclassAvg = await calculateSubjectAverage(forgr, 'average'); - const nclassAvgWithoutGrade = await calculateSubjectAverage(grlwg, 'average'); - const nclassAvgInfluence = nclassAvg - nclassAvgWithoutGrade; - - setAverage(naverage); - setAverageWithoutGrade(naverageWithoutGrade); - setAvgInfluence(navgInfluence); - setAvgPercentInfluence(navgPercentInfluence); - setClassAvg(nclassAvg); - setClassAvgWithoutGrade(nclassAvgWithoutGrade); - setClassAvgInfluence(nclassAvgInfluence); - } - - useEffect(() => { - const formatDate = (dateString) => new Date(dateString).getTime(); - - const date = formatDate(grade.date); - const formattedGrades = allGrades.filter(grade => formatDate(grade.date) <= date); - - const formattedValue = parseFloat(grade.grade.value.value).toFixed(2); - setValueTop(formattedValue.split('.')[0]); - setValueBottom(formattedValue.split('.')[1]); - - const gradesListWithoutGrade = formattedGrades.filter(g => g.id !== grade.id); - - calculateInfluence(formattedGrades, gradesListWithoutGrade); - }, []); - - useEffect(() => { - if(grade.share && grade.share.status) { - setIsShared(true); - } - }, [grade, isShared]); - - return ( - <> - - - - - - {modalLoadingText} - - - - - - - {formatCoursName(grade.subject.name)} - - - {new Date(grade.date).toLocaleDateString('fr-FR', { - year: 'numeric', - month: 'short', - day: 'numeric', - })} - - - - {grade.grade.value.significant === false && ( - <> - {valueTop} - - .{valueBottom} - - - )} - - {grade.grade.value.significant === true && (<> - {grade.grade.value.type[0] == '1' ? ( - - Abs. - - ) : ( - - N.not - - )} - )} - - - /{grade.grade.out_of.value} - - - - - {isShared && grade.share.name ? ( - - - } - trailing={ - - {grade.share.name} - - } - > - - Partagée par - - - - ) : null} - - - - } - trailing={ - - x {parseFloat(grade.grade.coefficient).toFixed(2)} - - } - > - - Coefficient - - - - - } - trailing= { - - - {parseFloat( - (grade.grade.value.value / grade.grade.out_of.value) * 20 - ).toFixed(2)} - - /20 - - } - > - - Remis sur /20 - - - - - - - } - trailing={ - - - {parseFloat(grade.grade.average.value).toFixed(2)} - - - /{grade.grade.out_of.value} - - - } - > - - Moy. de la classe - - - - } - trailing={ - - - {parseFloat(grade.grade.min.value).toFixed(2)} - - - /{grade.grade.out_of.value} - - - } - > - - Note minimale - - - - } - trailing={ - - - {parseFloat(grade.grade.max.value).toFixed(2)} - - - /{grade.grade.out_of.value} - - - } - > - - Note maximale - - - - - {allGrades.length > 1 ? ( - - - } - trailing={ - avgInfluence > 0 ? ( - - + {parseFloat(avgInfluence).toFixed(2)} pts - - ) : ( - - - {parseFloat(avgInfluence).toFixed(2) * -1} pts - - ) - } - > - - Moyenne générale - - - - } - trailing={ - classAvgInfluence > 0 ? ( - - + {parseFloat(classAvgInfluence).toFixed(2)} pts - - ) : ( - - - {parseFloat(classAvgInfluence).toFixed(2) * -1} pts - - ) - } - > - - Moyenne de classe - - - - } - trailing={ - avgPercentInfluence > 0 ? ( - - + {parseFloat(avgPercentInfluence).toFixed(2)} % - - ) : ( - - - {parseFloat(avgPercentInfluence).toFixed(2) * -1} % - - ) - } - > - - Pourcentage d'influence - - - sur la moyenne générale - - - - ) : null} - - - - } - trailing={ - (grade.grade.value.value - grade.grade.average.value).toFixed(2) > 0 ? ( - - + {(grade.grade.value.value - grade.grade.average.value).toFixed(2)} pts - - ) : ( - - - {(grade.grade.value.value - grade.grade.average.value).toFixed(2) * -1} pts - - ) - } - > - - Diff. avec la moyenne - - - - - - - - ); -} - -const styles = StyleSheet.create({ - optionsList: { - gap: 9, - marginTop: 16, - marginHorizontal: 14, - }, - ListTitle: { - paddingLeft: 14, - fontSize: 15, - fontFamily: 'Papillon-Medium', - opacity: 0.5, - }, - - gradeHeader: { - width: '100%', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 14, - paddingTop: 14, - paddingBottom: 14, - }, - gradeHeaderTitle: { - flexDirection: 'column', - alignItems: 'flex-start', - justifyContent: 'center', - gap: 2, - flex: 1, - marginRight: 10, - }, - gradeHeaderSubject: { - fontSize: 17, - fontFamily: 'Papillon-Semibold', - color: '#fff', - flex: 1, - }, - gradeHeaderDate: { - fontSize: 15, - fontFamily: 'Papillon-Medium', - color: '#fff', - opacity: 0.6, - }, - - gradeHeaderGrade: { - flexDirection: 'row', - alignItems: 'flex-end', - justifyContent: 'flex-end', - gap: 1, - minWidth: 100, - }, - gradeHeaderGradeValueTop: { - fontSize: 36, - fontFamily: 'Papillon-Medium', - color: '#fff', - letterSpacing: 0.5, - }, - gradeHeaderGradeValueBottom: { - fontSize: 24, - fontFamily: 'Papillon-Medium', - color: '#fff', - marginBottom: 2, - letterSpacing: 0.5, - }, - gradeHeaderGradeScale: { - fontSize: 20, - fontFamily: 'Papillon-Medium', - color: '#fff', - opacity: 0.6, - marginBottom: 2, - letterSpacing: 0.5, - }, - - gradeDetail: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 14, - paddingHorizontal: 18, - gap: 18, - borderRadius: 12, - }, - averageIcon: { - opacity: 0.7, - marginBottom: 1, - }, - gradeDetailTitle: { - fontSize: 17, - fontFamily: 'Papillon-Medium', - opacity: 0.6, - flex: 1, - }, - gradeDetailRight: { - alignItems: 'flex-end', - justifyContent: 'center', - flexDirection: 'row', - gap: 0, - marginBottom: 1, - }, - gradeDetailValue: { - fontSize: 18, - fontFamily: 'Papillon-Semibold', - letterSpacing: 0.15, - }, - gradeDetailValueSub: { - fontSize: 15, - fontFamily: 'Papillon-Medium', - opacity: 0.6, - letterSpacing: 0.15, - }, - - iosBack: { - width: 32, - height: 32, - - borderRadius: 32, - - justifyContent: 'center', - alignItems: 'center', - - backgroundColor: '#ffffff33', - }, - - iosBackIcon: { - width: 0, - height: 0, - marginTop: -1, - marginLeft: -1, - }, - - -}); - -export default GradeView; From c8dd9341927eeb4388c64e6259674c3552c66f83 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Wed, 20 Mar 2024 23:04:56 +0100 Subject: [PATCH 16/24] fix: :rewind: revert d'un deeplink de test --- android/app/src/main/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9444a706..b00bb7d0 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -41,7 +41,6 @@ - From 402742e29e7c4d91df891ab4a05b1bed333c4dc2 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Wed, 20 Mar 2024 23:11:41 +0100 Subject: [PATCH 17/24] feat: :truck: renommage dossier fetch/NewSkolengo > fetch/Skolengo --- fetch/{NewSkolengo => Skolengo}/SkolengoAuthWorkflow.ts | 0 fetch/{NewSkolengo => Skolengo}/SkolengoCommonCache.ts | 0 fetch/{NewSkolengo => Skolengo}/SkolengoDataProvider.ts | 0 fetch/index.ts | 2 +- views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx | 2 +- views/Settings/SettingsScreen.tsx | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename fetch/{NewSkolengo => Skolengo}/SkolengoAuthWorkflow.ts (100%) rename fetch/{NewSkolengo => Skolengo}/SkolengoCommonCache.ts (100%) rename fetch/{NewSkolengo => Skolengo}/SkolengoDataProvider.ts (100%) diff --git a/fetch/NewSkolengo/SkolengoAuthWorkflow.ts b/fetch/Skolengo/SkolengoAuthWorkflow.ts similarity index 100% rename from fetch/NewSkolengo/SkolengoAuthWorkflow.ts rename to fetch/Skolengo/SkolengoAuthWorkflow.ts diff --git a/fetch/NewSkolengo/SkolengoCommonCache.ts b/fetch/Skolengo/SkolengoCommonCache.ts similarity index 100% rename from fetch/NewSkolengo/SkolengoCommonCache.ts rename to fetch/Skolengo/SkolengoCommonCache.ts diff --git a/fetch/NewSkolengo/SkolengoDataProvider.ts b/fetch/Skolengo/SkolengoDataProvider.ts similarity index 100% rename from fetch/NewSkolengo/SkolengoDataProvider.ts rename to fetch/Skolengo/SkolengoDataProvider.ts diff --git a/fetch/index.ts b/fetch/index.ts index c4064a68..7c7ec56a 100644 --- a/fetch/index.ts +++ b/fetch/index.ts @@ -22,7 +22,7 @@ import { discussionsHandler as pronoteDiscussionsHandler, discussionsRecipientsH import { PapillonVieScolaire } from './types/vie_scolaire'; import { PapillonEvaluation } from './types/evaluations'; -import { SkolengoDataProvider } from './NewSkolengo/SkolengoDataProvider'; +import { SkolengoDataProvider } from './Skolengo/SkolengoDataProvider export type ServiceName = 'pronote' | 'skolengo' diff --git a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx index 785475cf..a84d2910 100644 --- a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx +++ b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx @@ -25,7 +25,7 @@ import * as Location from 'expo-location'; import { School as SkolengoSchool } from 'scolengo-api/types/models/School'; import { useAppContext } from '../../../utils/AppContext'; import { Skolengo } from 'scolengo-api'; -import { loginSkolengoWorkflow } from '../../../fetch/NewSkolengo/SkolengoAuthWorkflow'; +import { loginSkolengoWorkflow } from '../../../fetch/Skolengo/SkolengoAuthWorkflow export const LocateSkolengoEtab = ({ navigation, diff --git a/views/Settings/SettingsScreen.tsx b/views/Settings/SettingsScreen.tsx index d289ce67..a3cc1283 100644 --- a/views/Settings/SettingsScreen.tsx +++ b/views/Settings/SettingsScreen.tsx @@ -23,7 +23,7 @@ import NativeText from '../../components/NativeText'; import AlertBottomSheet from '../../interface/AlertBottomSheet'; import { IndexDataInstance } from '../../fetch'; -import { SkolengoCommonCache } from '../../fetch/NewSkolengo/SkolengoCommonCache'; +import { SkolengoCommonCache } from '../../fetch/Skolengo/SkolengoCommonCache function SettingsScreen({ navigation }) { const UIColors = GetUIColors(); From 2d5fa0727245d45432bb239c158c617213cc1c57 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Thu, 21 Mar 2024 10:46:38 +0100 Subject: [PATCH 18/24] =?UTF-8?q?fix:=20:ambulance:=20fix=20des=20caract?= =?UTF-8?q?=C3=A8res=20manquants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fetch/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fetch/index.ts b/fetch/index.ts index 7c7ec56a..5c8e4ee4 100644 --- a/fetch/index.ts +++ b/fetch/index.ts @@ -22,7 +22,7 @@ import { discussionsHandler as pronoteDiscussionsHandler, discussionsRecipientsH import { PapillonVieScolaire } from './types/vie_scolaire'; import { PapillonEvaluation } from './types/evaluations'; -import { SkolengoDataProvider } from './Skolengo/SkolengoDataProvider +import { SkolengoDataProvider } from './Skolengo/SkolengoDataProvider'; export type ServiceName = 'pronote' | 'skolengo' @@ -307,7 +307,7 @@ export class IndexDataInstance { } } - async createDiscussion(subject, content, participants) { + async createDiscussion(subject: string, content: string, participants: any[]) { await this.waitInit(); // if (this.service === 'pronote') // return require('./PronoteData/PronoteConversations.js').createDiscussion( From 68d3c2d8004cdc35ce8b8a707184e2ab43c413d2 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Thu, 21 Mar 2024 13:06:33 +0100 Subject: [PATCH 19/24] test: :mute: - log du token Skolengo --- fetch/Skolengo/SkolengoAuthWorkflow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fetch/Skolengo/SkolengoAuthWorkflow.ts b/fetch/Skolengo/SkolengoAuthWorkflow.ts index 721abfe4..295e152a 100644 --- a/fetch/Skolengo/SkolengoAuthWorkflow.ts +++ b/fetch/Skolengo/SkolengoAuthWorkflow.ts @@ -60,7 +60,7 @@ export const loginSkolengoWorkflow = async ( disco ).catch(skolengoErrorHandler); if (!token) return skolengoErrorHandler(); - console.log({token}); + //console.log({token}); Alert.alert( 'Skolengo : intégration en cours', 'Veuillez patienter, le processus de connexion à Skolengo à fonctionné.\nMais l\'intégration de Skolengo (NG) n\'est pas encode terminé.\n\nRevenez plus tard.' From f68bd255e922bd8f435f6bd1c54ab62c3c84cc33 Mon Sep 17 00:00:00 2001 From: Tom <33373907+tom-theret@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:28:05 +0100 Subject: [PATCH 20/24] fix: syntax LocateSkolengoEtab.tsx --- views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx index a84d2910..8b218cec 100644 --- a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx +++ b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx @@ -25,7 +25,7 @@ import * as Location from 'expo-location'; import { School as SkolengoSchool } from 'scolengo-api/types/models/School'; import { useAppContext } from '../../../utils/AppContext'; import { Skolengo } from 'scolengo-api'; -import { loginSkolengoWorkflow } from '../../../fetch/Skolengo/SkolengoAuthWorkflow +import { loginSkolengoWorkflow } from '../../../fetch/Skolengo/SkolengoAuthWorkflow'; export const LocateSkolengoEtab = ({ navigation, From 754765beb5eab237af847dd168ae0f014cad6e08 Mon Sep 17 00:00:00 2001 From: Tom <33373907+tom-theret@users.noreply.github.com> Date: Thu, 21 Mar 2024 23:28:56 +0100 Subject: [PATCH 21/24] inset LocateSkolengoEtab.tsx --- views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx index 8b218cec..400055d3 100644 --- a/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx +++ b/views/NewAuthStack/Skolengo/LocateSkolengoEtab.tsx @@ -123,6 +123,7 @@ export const LocateSkolengoEtab = ({ /> {!isLoading && currentSearch.length < 2 ? ( - + } onPress={() => { @@ -205,6 +206,7 @@ export const LocateSkolengoEtab = ({ {!isLoading && instances && instances.length > 0 && ( Date: Thu, 21 Mar 2024 23:29:43 +0100 Subject: [PATCH 22/24] fix: syntax SettingsScreen.tsx --- views/Settings/SettingsScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/Settings/SettingsScreen.tsx b/views/Settings/SettingsScreen.tsx index a3cc1283..a6519407 100644 --- a/views/Settings/SettingsScreen.tsx +++ b/views/Settings/SettingsScreen.tsx @@ -23,7 +23,7 @@ import NativeText from '../../components/NativeText'; import AlertBottomSheet from '../../interface/AlertBottomSheet'; import { IndexDataInstance } from '../../fetch'; -import { SkolengoCommonCache } from '../../fetch/Skolengo/SkolengoCommonCache +import { SkolengoCommonCache } from '../../fetch/Skolengo/SkolengoCommonCache'; function SettingsScreen({ navigation }) { const UIColors = GetUIColors(); From 162ff96fcf8676dcbfc69943658be68cf3d79f06 Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Sat, 23 Mar 2024 21:35:31 +0100 Subject: [PATCH 23/24] fix: :fire: suppr. GraveView dans AppStack --- stacks/AppStack.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/stacks/AppStack.tsx b/stacks/AppStack.tsx index fdd0913c..d82a9d18 100644 --- a/stacks/AppStack.tsx +++ b/stacks/AppStack.tsx @@ -96,14 +96,6 @@ const AppStack = ({ navigation }) => { presentation: 'modal', } }, - { - name: 'Grade', - component: require('../views/Grades/GradeView').default, - options: { - presentation: 'modal', - headerTintColor: '#fff', - } - }, { name: 'GradesSettings', component: require('../views/Grades/GradesSettings').default, From 48ff4a9c52c8f686f6d56d5794a2aa65da6cb5fd Mon Sep 17 00:00:00 2001 From: NonozgYtb Date: Sat, 23 Mar 2024 22:25:11 +0100 Subject: [PATCH 24/24] feat: :art: simplification du SelectService.tsx + typage --- views/NewAuthStack/SelectService.tsx | 57 ++++++++++++---------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/views/NewAuthStack/SelectService.tsx b/views/NewAuthStack/SelectService.tsx index 5ef6e970..b81faeec 100644 --- a/views/NewAuthStack/SelectService.tsx +++ b/views/NewAuthStack/SelectService.tsx @@ -1,5 +1,5 @@ import React, { useLayoutEffect, useState, useEffect } from 'react'; -import { View, Image, StatusBar, StyleSheet, TouchableOpacity, Platform } from 'react-native'; +import { View, Image, Platform, StatusBar, StyleSheet, TouchableOpacity } from 'react-native'; import GetUIColors from '../../utils/GetUIColors'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as Haptics from 'expo-haptics'; @@ -20,9 +20,8 @@ const SelectService = ({ navigation }) => { const insets = useSafeAreaInsets(); const [serviceAlertVisible, setServiceAlertVisible] = useState(false); - const [serviceNotSuportedAlertVisible, setServiceNotSuportedAlertVisible] = useState(false); - const [apiResponse, setApiResponse] = useState(false); + const [apiResponse, setApiResponse] = useState>({}); useEffect(() => { callFetchPapiAPI('messages') @@ -34,7 +33,7 @@ const SelectService = ({ navigation }) => { return fetchPapiAPI(path) .then(data => { return data; - }) + }); } useLayoutEffect(() => { @@ -47,7 +46,7 @@ const SelectService = ({ navigation }) => { }); }, [UIColors]); - const [selectedService, setSelectedService] = useState(null); + const [selectedService, setSelectedService] = useState(null); const [serviceOptions, setServiceOptions] = useState([ { name: 'PRONOTE', @@ -72,20 +71,19 @@ const SelectService = ({ navigation }) => { icon: require('../../assets/logo_modern_ed.png'), soon: true, } - ]); + ] as const); - const selectOption = (index) => { + const selectOption = (index:number) => { setSelectedService(index); }; const continueToLogin = () => { if (selectedService !== null) { - const service = serviceOptions[selectedService]; - if(service.soon) { - setServiceNotSuportedAlertVisible(service.name); + + setServiceAlertVisible(true); + if(serviceOptions[selectedService]?.soon) { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); } else { - setServiceAlertVisible(true); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); } } @@ -105,15 +103,20 @@ const SelectService = ({ navigation }) => { Sélectionnez le service de vie scolaire que vous utilisez dans votre établissement. - } - title={apiResponse[serviceOptions[selectedService]?.company]?.title} - subtitle={apiResponse[serviceOptions[selectedService]?.company]?.content} - cancelAction={() => setServiceAlertVisible(false)} - primaryButton='Compris !' - primaryAction={() => {navigation.navigate(serviceOptions[selectedService]?.view); setServiceAlertVisible(false);}} - /> + + {selectedService !== null && serviceOptions[selectedService] && ( + : } + title={apiResponse[serviceOptions[selectedService].company]?.title} + subtitle={apiResponse[serviceOptions[selectedService].company]?.content} + cancelAction={() => setServiceAlertVisible(false)} + primaryButton={!serviceOptions[selectedService]?.soon && 'Compris !' || undefined} + primaryAction={!serviceOptions[selectedService]?.soon && (() => {navigation.navigate(serviceOptions[selectedService]?.view); setServiceAlertVisible(false);}) ||undefined} + /> + )} {Platform.OS !== 'ios' && ( @@ -191,17 +194,7 @@ const SelectService = ({ navigation }) => { Continuer - - - } - title={serviceNotSuportedAlertVisible} - subtitle={`${serviceNotSuportedAlertVisible} n’est pas encore disponible sur Papillon. Veuillez réessayer plus tard.`} - cancelAction={() => setServiceNotSuportedAlertVisible(false)} - /> - + ); }; @@ -243,4 +236,4 @@ const styles = StyleSheet.create({ }, }); -export default SelectService; +export default SelectService; \ No newline at end of file