Browse Source

Use context to handle preferences

This is not tested, expect crashes
Arnaud Vergnet 6 months ago
parent
commit
00f9428972
39 changed files with 1243 additions and 1954 deletions
  1. 16
    114
      App.tsx
  2. 1
    1
      src/components/Lists/CardList/CardList.tsx
  3. 1
    1
      src/components/Lists/CardList/CardListItem.tsx
  4. 1
    1
      src/components/Lists/CardList/ImageListItem.tsx
  5. 1
    4
      src/components/Lists/DashboardEdit/DashboardEditAccordion.tsx
  6. 1
    1
      src/components/Lists/DashboardEdit/DashboardEditItem.tsx
  7. 8
    9
      src/components/Mascot/MascotPopup.tsx
  8. 0
    4
      src/components/Overrides/CustomIntroSlider.tsx
  9. 8
    4
      src/components/Screens/PlanexWebview.tsx
  10. 0
    25
      src/context/preferencesContext.ts
  11. 97
    0
      src/context/preferencesContext.tsx
  12. 0
    269
      src/managers/AsyncStorageManager.ts
  13. 0
    38
      src/managers/DashboardManager.ts
  14. 0
    371
      src/managers/ServicesManager.ts
  15. 31
    5
      src/navigation/MainNavigator.tsx
  16. 81
    61
      src/navigation/TabNavigator.tsx
  17. 62
    122
      src/screens/About/DebugScreen.tsx
  18. 2
    9
      src/screens/Amicale/Equipment/EquipmentListScreen.tsx
  19. 7
    13
      src/screens/Amicale/LoginScreen.tsx
  20. 8
    4
      src/screens/Amicale/ProfileScreen.tsx
  21. 2
    9
      src/screens/Amicale/VoteScreen.tsx
  22. 2
    1
      src/screens/Game/screens/GameMainScreen.tsx
  23. 10
    9
      src/screens/Game/screens/GameStartScreen.tsx
  24. 170
    238
      src/screens/Home/HomeScreen.tsx
  25. 41
    0
      src/screens/Intro/IntroScreen.tsx
  26. 61
    0
      src/screens/MainApp.tsx
  27. 47
    86
      src/screens/Other/Settings/DashboardEditScreen.tsx
  28. 170
    252
      src/screens/Other/Settings/SettingsScreen.tsx
  29. 27
    26
      src/screens/Planex/GroupSelectionScreen.tsx
  30. 27
    52
      src/screens/Planex/PlanexScreen.tsx
  31. 0
    2
      src/screens/Planning/PlanningScreen.tsx
  32. 35
    32
      src/screens/Proxiwash/ProxiwashScreen.tsx
  33. 6
    8
      src/screens/Services/ServicesScreen.tsx
  34. 1
    1
      src/screens/Services/ServicesSectionScreen.tsx
  35. 4
    7
      src/utils/Notifications.ts
  36. 294
    38
      src/utils/Services.ts
  37. 2
    122
      src/utils/Themes.ts
  38. 10
    12
      src/utils/Utils.ts
  39. 9
    3
      src/utils/asyncStorage.ts

+ 16
- 114
App.tsx View File

@@ -18,27 +18,21 @@
18 18
  */
19 19
 
20 20
 import React from 'react';
21
-import { LogBox, Platform, SafeAreaView, View } from 'react-native';
22
-import { NavigationContainer } from '@react-navigation/native';
23
-import { Provider as PaperProvider } from 'react-native-paper';
21
+import { LogBox, Platform } from 'react-native';
24 22
 import { setSafeBounceHeight } from 'react-navigation-collapsible';
25 23
 import SplashScreen from 'react-native-splash-screen';
26
-import { OverflowMenuProvider } from 'react-navigation-header-buttons';
27
-import AsyncStorageManager from './src/managers/AsyncStorageManager';
28
-import CustomIntroSlider from './src/components/Overrides/CustomIntroSlider';
29
-import ThemeManager from './src/managers/ThemeManager';
30
-import MainNavigator from './src/navigation/MainNavigator';
31
-import AprilFoolsManager from './src/managers/AprilFoolsManager';
32
-import Update from './src/constants/Update';
33 24
 import ConnectionManager from './src/managers/ConnectionManager';
34 25
 import type { ParsedUrlDataType } from './src/utils/URLHandler';
35 26
 import URLHandler from './src/utils/URLHandler';
36
-import { setupStatusBar } from './src/utils/Utils';
37 27
 import initLocales from './src/utils/Locales';
38 28
 import { NavigationContainerRef } from '@react-navigation/core';
39
-import GENERAL_STYLES from './src/constants/Styles';
40
-import CollapsibleProvider from './src/components/providers/CollapsibleProvider';
41
-import CacheProvider from './src/components/providers/CacheProvider';
29
+import {
30
+  defaultPreferences,
31
+  PreferenceKeys,
32
+  retrievePreferences,
33
+} from './src/utils/asyncStorage';
34
+import PreferencesProvider from './src/components/providers/PreferencesProvider';
35
+import MainApp from './src/screens/MainApp';
42 36
 
43 37
 // Native optimizations https://reactnavigation.org/docs/react-native-screens
44 38
 // Crashes app when navigating away from webview on android 9+
@@ -52,10 +46,6 @@ LogBox.ignoreLogs([
52 46
 
53 47
 type StateType = {
54 48
   isLoading: boolean;
55
-  showIntro: boolean;
56
-  showUpdate: boolean;
57
-  showAprilFools: boolean;
58
-  currentTheme: ReactNativePaper.Theme | undefined;
59 49
 };
60 50
 
61 51
 export default class App extends React.Component<{}, StateType> {
@@ -71,10 +61,6 @@ export default class App extends React.Component<{}, StateType> {
71 61
     super(props);
72 62
     this.state = {
73 63
       isLoading: true,
74
-      showIntro: true,
75
-      showUpdate: true,
76
-      showAprilFools: false,
77
-      currentTheme: undefined,
78 64
     };
79 65
     initLocales();
80 66
     this.navigatorRef = React.createRef();
@@ -115,66 +101,11 @@ export default class App extends React.Component<{}, StateType> {
115 101
   };
116 102
 
117 103
   /**
118
-   * Updates the current theme
119
-   */
120
-  onUpdateTheme = () => {
121
-    this.setState({
122
-      currentTheme: ThemeManager.getCurrentTheme(),
123
-    });
124
-    setupStatusBar();
125
-  };
126
-
127
-  /**
128
-   * Callback when user ends the intro. Save in preferences to avoid showing back the introSlides
129
-   */
130
-  onIntroDone = () => {
131
-    this.setState({
132
-      showIntro: false,
133
-      showUpdate: false,
134
-      showAprilFools: false,
135
-    });
136
-    AsyncStorageManager.set(
137
-      AsyncStorageManager.PREFERENCES.showIntro.key,
138
-      false
139
-    );
140
-    AsyncStorageManager.set(
141
-      AsyncStorageManager.PREFERENCES.updateNumber.key,
142
-      Update.number
143
-    );
144
-    AsyncStorageManager.set(
145
-      AsyncStorageManager.PREFERENCES.showAprilFoolsStart.key,
146
-      false
147
-    );
148
-  };
149
-
150
-  /**
151 104
    * Async loading is done, finish processing startup data
152 105
    */
153 106
   onLoadFinished = () => {
154
-    // Only show intro if this is the first time starting the app
155
-    ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
156
-    // Status bar goes dark if set too fast on ios
157
-    if (Platform.OS === 'ios') {
158
-      setTimeout(setupStatusBar, 1000);
159
-    } else {
160
-      setupStatusBar();
161
-    }
162
-
163 107
     this.setState({
164 108
       isLoading: false,
165
-      currentTheme: ThemeManager.getCurrentTheme(),
166
-      showIntro: AsyncStorageManager.getBool(
167
-        AsyncStorageManager.PREFERENCES.showIntro.key
168
-      ),
169
-      showUpdate:
170
-        AsyncStorageManager.getNumber(
171
-          AsyncStorageManager.PREFERENCES.updateNumber.key
172
-        ) !== Update.number,
173
-      showAprilFools:
174
-        AprilFoolsManager.getInstance().isAprilFoolsEnabled() &&
175
-        AsyncStorageManager.getBool(
176
-          AsyncStorageManager.PREFERENCES.showAprilFoolsStart.key
177
-        ),
178 109
     });
179 110
     SplashScreen.hide();
180 111
   };
@@ -186,7 +117,7 @@ export default class App extends React.Component<{}, StateType> {
186 117
    */
187 118
   loadAssetsAsync() {
188 119
     Promise.all([
189
-      AsyncStorageManager.getInstance().loadPreferences(),
120
+      retrievePreferences(Object.values(PreferenceKeys), defaultPreferences),
190 121
       ConnectionManager.getInstance().recoverLogin(),
191 122
     ])
192 123
       .then(this.onLoadFinished)
@@ -201,43 +132,14 @@ export default class App extends React.Component<{}, StateType> {
201 132
     if (state.isLoading) {
202 133
       return null;
203 134
     }
204
-    if (state.showIntro || state.showUpdate || state.showAprilFools) {
205
-      return (
206
-        <CustomIntroSlider
207
-          onDone={this.onIntroDone}
208
-          isUpdate={state.showUpdate && !state.showIntro}
209
-          isAprilFools={state.showAprilFools && !state.showIntro}
210
-        />
211
-      );
212
-    }
213 135
     return (
214
-      <PaperProvider theme={state.currentTheme}>
215
-        <CollapsibleProvider>
216
-          <CacheProvider>
217
-            <OverflowMenuProvider>
218
-              <View
219
-                style={{
220
-                  backgroundColor: ThemeManager.getCurrentTheme().colors
221
-                    .background,
222
-                  ...GENERAL_STYLES.flex,
223
-                }}
224
-              >
225
-                <SafeAreaView style={GENERAL_STYLES.flex}>
226
-                  <NavigationContainer
227
-                    theme={state.currentTheme}
228
-                    ref={this.navigatorRef}
229
-                  >
230
-                    <MainNavigator
231
-                      defaultHomeRoute={this.defaultHomeRoute}
232
-                      defaultHomeData={this.defaultHomeData}
233
-                    />
234
-                  </NavigationContainer>
235
-                </SafeAreaView>
236
-              </View>
237
-            </OverflowMenuProvider>
238
-          </CacheProvider>
239
-        </CollapsibleProvider>
240
-      </PaperProvider>
136
+      <PreferencesProvider initialPreferences={defaultPreferences}>
137
+        <MainApp
138
+          ref={this.navigatorRef}
139
+          defaultHomeData={this.defaultHomeData}
140
+          defaultHomeRoute={this.defaultHomeRoute}
141
+        />
142
+      </PreferencesProvider>
241 143
     );
242 144
   }
243 145
 }

+ 1
- 1
src/components/Lists/CardList/CardList.tsx View File

@@ -21,7 +21,7 @@ import * as React from 'react';
21 21
 import { Animated, Dimensions, ViewStyle } from 'react-native';
22 22
 import ImageListItem from './ImageListItem';
23 23
 import CardListItem from './CardListItem';
24
-import type { ServiceItemType } from '../../../managers/ServicesManager';
24
+import { ServiceItemType } from '../../../utils/Services';
25 25
 
26 26
 type PropsType = {
27 27
   dataset: Array<ServiceItemType>;

+ 1
- 1
src/components/Lists/CardList/CardListItem.tsx View File

@@ -20,8 +20,8 @@
20 20
 import * as React from 'react';
21 21
 import { Caption, Card, Paragraph, TouchableRipple } from 'react-native-paper';
22 22
 import { StyleSheet, View } from 'react-native';
23
-import type { ServiceItemType } from '../../../managers/ServicesManager';
24 23
 import GENERAL_STYLES from '../../../constants/Styles';
24
+import { ServiceItemType } from '../../../utils/Services';
25 25
 
26 26
 type PropsType = {
27 27
   item: ServiceItemType;

+ 1
- 1
src/components/Lists/CardList/ImageListItem.tsx View File

@@ -20,8 +20,8 @@
20 20
 import * as React from 'react';
21 21
 import { Text, TouchableRipple } from 'react-native-paper';
22 22
 import { Image, StyleSheet, View } from 'react-native';
23
-import type { ServiceItemType } from '../../../managers/ServicesManager';
24 23
 import GENERAL_STYLES from '../../../constants/Styles';
24
+import { ServiceItemType } from '../../../utils/Services';
25 25
 
26 26
 type PropsType = {
27 27
   item: ServiceItemType;

+ 1
- 4
src/components/Lists/DashboardEdit/DashboardEditAccordion.tsx View File

@@ -23,10 +23,7 @@ import { FlatList, Image, StyleSheet, View } from 'react-native';
23 23
 import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
24 24
 import DashboardEditItem from './DashboardEditItem';
25 25
 import AnimatedAccordion from '../../Animations/AnimatedAccordion';
26
-import type {
27
-  ServiceCategoryType,
28
-  ServiceItemType,
29
-} from '../../../managers/ServicesManager';
26
+import { ServiceCategoryType, ServiceItemType } from '../../../utils/Services';
30 27
 
31 28
 type PropsType = {
32 29
   item: ServiceCategoryType;

+ 1
- 1
src/components/Lists/DashboardEdit/DashboardEditItem.tsx View File

@@ -20,7 +20,7 @@
20 20
 import * as React from 'react';
21 21
 import { Image, StyleSheet } from 'react-native';
22 22
 import { List, useTheme } from 'react-native-paper';
23
-import type { ServiceItemType } from '../../../managers/ServicesManager';
23
+import { ServiceItemType } from '../../../utils/Services';
24 24
 
25 25
 type PropsType = {
26 26
   item: ServiceItemType;

+ 8
- 9
src/components/Mascot/MascotPopup.tsx View File

@@ -28,17 +28,17 @@ import {
28 28
   View,
29 29
 } from 'react-native';
30 30
 import Mascot from './Mascot';
31
-import AsyncStorageManager from '../../managers/AsyncStorageManager';
32 31
 import GENERAL_STYLES from '../../constants/Styles';
33 32
 import MascotSpeechBubble, {
34 33
   MascotSpeechBubbleProps,
35 34
 } from './MascotSpeechBubble';
36 35
 import { useMountEffect } from '../../utils/customHooks';
36
+import { useRoute } from '@react-navigation/core';
37
+import { useShouldShowMascot } from '../../context/preferencesContext';
37 38
 
38 39
 type PropsType = MascotSpeechBubbleProps & {
39 40
   emotion: number;
40 41
   visible?: boolean;
41
-  prefKey?: string;
42 42
 };
43 43
 
44 44
 const styles = StyleSheet.create({
@@ -61,13 +61,14 @@ const BUBBLE_HEIGHT = Dimensions.get('window').height / 3;
61 61
  * Component used to display a popup with the mascot.
62 62
  */
63 63
 function MascotPopup(props: PropsType) {
64
+  const route = useRoute();
65
+  const { shouldShow, setShouldShow } = useShouldShowMascot(route.name);
66
+
64 67
   const isVisible = () => {
65 68
     if (props.visible !== undefined) {
66 69
       return props.visible;
67
-    } else if (props.prefKey != null) {
68
-      return AsyncStorageManager.getBool(props.prefKey);
69 70
     } else {
70
-      return false;
71
+      return shouldShow;
71 72
     }
72 73
   };
73 74
 
@@ -164,10 +165,8 @@ function MascotPopup(props: PropsType) {
164 165
   };
165 166
 
166 167
   const onDismiss = (callback?: () => void) => {
167
-    if (props.prefKey != null) {
168
-      AsyncStorageManager.set(props.prefKey, false);
169
-      setDialogVisible(false);
170
-    }
168
+    setShouldShow(false);
169
+    setDialogVisible(false);
171 170
     if (callback) {
172 171
       callback();
173 172
     }

+ 0
- 4
src/components/Overrides/CustomIntroSlider.tsx View File

@@ -32,7 +32,6 @@ import LinearGradient from 'react-native-linear-gradient';
32 32
 import * as Animatable from 'react-native-animatable';
33 33
 import { Card } from 'react-native-paper';
34 34
 import Update from '../../constants/Update';
35
-import ThemeManager from '../../managers/ThemeManager';
36 35
 import Mascot, { MASCOT_STYLE } from '../Mascot/Mascot';
37 36
 import MascotIntroWelcome from '../Intro/MascotIntroWelcome';
38 37
 import IntroIcon from '../Intro/IconIntro';
@@ -289,9 +288,6 @@ export default class CustomIntroSlider extends React.Component<
289 288
 
290 289
   onDone = () => {
291 290
     const { props } = this;
292
-    CustomIntroSlider.setStatusBarColor(
293
-      ThemeManager.getCurrentTheme().colors.surface
294
-    );
295 291
     props.onDone();
296 292
   };
297 293
 

+ 8
- 4
src/components/Screens/PlanexWebview.tsx View File

@@ -3,11 +3,11 @@ import { StyleSheet, View } from 'react-native';
3 3
 import GENERAL_STYLES from '../../constants/Styles';
4 4
 import Urls from '../../constants/Urls';
5 5
 import DateManager from '../../managers/DateManager';
6
-import ThemeManager from '../../managers/ThemeManager';
7 6
 import { PlanexGroupType } from '../../screens/Planex/GroupSelectionScreen';
8 7
 import ErrorView from './ErrorView';
9 8
 import WebViewScreen from './WebViewScreen';
10 9
 import i18n from 'i18n-js';
10
+import { useTheme } from 'react-native-paper';
11 11
 
12 12
 type Props = {
13 13
   currentGroup?: PlanexGroupType;
@@ -86,7 +86,10 @@ const INJECT_STYLE_DARK = `$('head').append('<style>${CUSTOM_CSS_DARK}</style>')
86 86
  *
87 87
  * @param groupID The current group selected
88 88
  */
89
-const generateInjectedJS = (group: PlanexGroupType | undefined) => {
89
+const generateInjectedJS = (
90
+  group: PlanexGroupType | undefined,
91
+  darkMode: boolean
92
+) => {
90 93
   let customInjectedJS = `$(document).ready(function() {
91 94
       ${OBSERVE_MUTATIONS_INJECTED}
92 95
       ${INJECT_STYLE}
@@ -97,7 +100,7 @@ const generateInjectedJS = (group: PlanexGroupType | undefined) => {
97 100
   if (DateManager.isWeekend(new Date())) {
98 101
     customInjectedJS += `calendar.next();`;
99 102
   }
100
-  if (ThemeManager.getNightMode()) {
103
+  if (darkMode) {
101 104
     customInjectedJS += INJECT_STYLE_DARK;
102 105
   }
103 106
   customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
@@ -105,11 +108,12 @@ const generateInjectedJS = (group: PlanexGroupType | undefined) => {
105 108
 };
106 109
 
107 110
 function PlanexWebview(props: Props) {
111
+  const theme = useTheme();
108 112
   return (
109 113
     <View style={GENERAL_STYLES.flex}>
110 114
       <WebViewScreen
111 115
         url={Urls.planex.planning}
112
-        initialJS={generateInjectedJS(props.currentGroup)}
116
+        initialJS={generateInjectedJS(props.currentGroup, theme.dark)}
113 117
         injectJS={props.injectJS}
114 118
         onMessage={props.onMessage}
115 119
         showAdvancedControls={false}

+ 0
- 25
src/context/preferencesContext.ts View File

@@ -1,25 +0,0 @@
1
-import React, { useContext } from 'react';
2
-import {
3
-  defaultPreferences,
4
-  PreferenceKeys,
5
-  PreferencesType,
6
-} from '../utils/asyncStorage';
7
-
8
-export type PreferencesContextType = {
9
-  preferences: PreferencesType;
10
-  updatePreferences: (
11
-    key: PreferenceKeys,
12
-    value: number | string | boolean | object | Array<any>
13
-  ) => void;
14
-  resetPreferences: () => void;
15
-};
16
-
17
-export const PreferencesContext = React.createContext<PreferencesContextType>({
18
-  preferences: defaultPreferences,
19
-  updatePreferences: () => undefined,
20
-  resetPreferences: () => undefined,
21
-});
22
-
23
-export function usePreferences() {
24
-  return useContext(PreferencesContext);
25
-}

+ 97
- 0
src/context/preferencesContext.tsx View File

@@ -0,0 +1,97 @@
1
+import { useNavigation } from '@react-navigation/core';
2
+import React, { useContext } from 'react';
3
+import { Appearance } from 'react-native-appearance';
4
+import {
5
+  defaultPreferences,
6
+  getPreferenceBool,
7
+  getPreferenceObject,
8
+  isValidPreferenceKey,
9
+  PreferenceKeys,
10
+  PreferencesType,
11
+} from '../utils/asyncStorage';
12
+import {
13
+  getAmicaleServices,
14
+  getINSAServices,
15
+  getSpecialServices,
16
+  getStudentServices,
17
+} from '../utils/Services';
18
+
19
+const colorScheme = Appearance.getColorScheme();
20
+
21
+export type PreferencesContextType = {
22
+  preferences: PreferencesType;
23
+  updatePreferences: (
24
+    key: PreferenceKeys,
25
+    value: number | string | boolean | object | Array<any>
26
+  ) => void;
27
+  resetPreferences: () => void;
28
+};
29
+
30
+export const PreferencesContext = React.createContext<PreferencesContextType>({
31
+  preferences: defaultPreferences,
32
+  updatePreferences: () => undefined,
33
+  resetPreferences: () => undefined,
34
+});
35
+
36
+export function usePreferences() {
37
+  return useContext(PreferencesContext);
38
+}
39
+
40
+export function useShouldShowMascot(route: string) {
41
+  const { preferences, updatePreferences } = usePreferences();
42
+  const key = route + 'ShowMascot';
43
+  let shouldShow = false;
44
+  if (isValidPreferenceKey(key)) {
45
+    shouldShow = getPreferenceBool(key, preferences) !== false;
46
+  }
47
+
48
+  const setShouldShow = (show: boolean) => {
49
+    if (isValidPreferenceKey(key)) {
50
+      updatePreferences(key, show);
51
+    } else {
52
+      console.log('Invalid preference key: ' + key);
53
+    }
54
+  };
55
+
56
+  return { shouldShow, setShouldShow };
57
+}
58
+
59
+export function useDarkTheme() {
60
+  const { preferences } = usePreferences();
61
+  return (
62
+    (getPreferenceBool(PreferenceKeys.nightMode, preferences) !== false &&
63
+      (getPreferenceBool(PreferenceKeys.nightModeFollowSystem, preferences) ===
64
+        false ||
65
+        colorScheme === 'no-preference')) ||
66
+    (getPreferenceBool(PreferenceKeys.nightModeFollowSystem, preferences) !==
67
+      false &&
68
+      colorScheme === 'dark')
69
+  );
70
+}
71
+
72
+export function useCurrentDashboard() {
73
+  const { preferences, updatePreferences } = usePreferences();
74
+  const navigation = useNavigation();
75
+  const dashboardIdList = getPreferenceObject(
76
+    PreferenceKeys.dashboardItems,
77
+    preferences
78
+  ) as Array<string>;
79
+
80
+  const updateCurrentDashboard = (newList: Array<string>) => {
81
+    updatePreferences(PreferenceKeys.dashboardItems, newList);
82
+  };
83
+
84
+  const allDatasets = [
85
+    ...getAmicaleServices(navigation.navigate),
86
+    ...getStudentServices(navigation.navigate),
87
+    ...getINSAServices(navigation.navigate),
88
+    ...getSpecialServices(navigation.navigate),
89
+  ];
90
+  return {
91
+    currentDashboard: allDatasets.filter((item) =>
92
+      dashboardIdList.includes(item.key)
93
+    ),
94
+    currentDashboardIdList: dashboardIdList,
95
+    updateCurrentDashboard: updateCurrentDashboard,
96
+  };
97
+}

+ 0
- 269
src/managers/AsyncStorageManager.ts View File

@@ -1,269 +0,0 @@
1
-/*
2
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
3
- *
4
- * This file is part of Campus INSAT.
5
- *
6
- * Campus INSAT is free software: you can redistribute it and/or modify
7
- *  it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Campus INSAT is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
- */
19
-
20
-import AsyncStorage from '@react-native-async-storage/async-storage';
21
-import { SERVICES_KEY } from './ServicesManager';
22
-
23
-/**
24
- * Singleton used to manage preferences.
25
- * Preferences are fetched at the start of the app and saved in an instance object.
26
- * This allows for a synchronous access to saved data.
27
- */
28
-
29
-export default class AsyncStorageManager {
30
-  static instance: AsyncStorageManager | null = null;
31
-
32
-  static PREFERENCES: { [key: string]: { key: string; default: string } } = {
33
-    debugUnlocked: {
34
-      key: 'debugUnlocked',
35
-      default: '0',
36
-    },
37
-    showIntro: {
38
-      key: 'showIntro',
39
-      default: '1',
40
-    },
41
-    updateNumber: {
42
-      key: 'updateNumber',
43
-      default: '0',
44
-    },
45
-    proxiwashNotifications: {
46
-      key: 'proxiwashNotifications',
47
-      default: '5',
48
-    },
49
-    nightModeFollowSystem: {
50
-      key: 'nightModeFollowSystem',
51
-      default: '1',
52
-    },
53
-    nightMode: {
54
-      key: 'nightMode',
55
-      default: '1',
56
-    },
57
-    defaultStartScreen: {
58
-      key: 'defaultStartScreen',
59
-      default: 'home',
60
-    },
61
-    servicesShowMascot: {
62
-      key: 'servicesShowMascot',
63
-      default: '1',
64
-    },
65
-    proxiwashShowMascot: {
66
-      key: 'proxiwashShowMascot',
67
-      default: '1',
68
-    },
69
-    homeShowMascot: {
70
-      key: 'homeShowMascot',
71
-      default: '1',
72
-    },
73
-    eventsShowMascot: {
74
-      key: 'eventsShowMascot',
75
-      default: '1',
76
-    },
77
-    planexShowMascot: {
78
-      key: 'planexShowMascot',
79
-      default: '1',
80
-    },
81
-    loginShowMascot: {
82
-      key: 'loginShowMascot',
83
-      default: '1',
84
-    },
85
-    voteShowMascot: {
86
-      key: 'voteShowMascot',
87
-      default: '1',
88
-    },
89
-    equipmentShowMascot: {
90
-      key: 'equipmentShowMascot',
91
-      default: '1',
92
-    },
93
-    gameStartMascot: {
94
-      key: 'gameStartMascot',
95
-      default: '1',
96
-    },
97
-    proxiwashWatchedMachines: {
98
-      key: 'proxiwashWatchedMachines',
99
-      default: '[]',
100
-    },
101
-    showAprilFoolsStart: {
102
-      key: 'showAprilFoolsStart',
103
-      default: '1',
104
-    },
105
-    planexCurrentGroup: {
106
-      key: 'planexCurrentGroup',
107
-      default: '',
108
-    },
109
-    planexFavoriteGroups: {
110
-      key: 'planexFavoriteGroups',
111
-      default: '[]',
112
-    },
113
-    dashboardItems: {
114
-      key: 'dashboardItems',
115
-      default: JSON.stringify([
116
-        SERVICES_KEY.EMAIL,
117
-        SERVICES_KEY.WASHERS,
118
-        SERVICES_KEY.PROXIMO,
119
-        SERVICES_KEY.TUTOR_INSA,
120
-        SERVICES_KEY.RU,
121
-      ]),
122
-    },
123
-    gameScores: {
124
-      key: 'gameScores',
125
-      default: '[]',
126
-    },
127
-    selectedWash: {
128
-      key: 'selectedWash',
129
-      default: 'washinsa',
130
-    },
131
-  };
132
-
133
-  private currentPreferences: { [key: string]: string };
134
-
135
-  constructor() {
136
-    this.currentPreferences = {};
137
-  }
138
-
139
-  /**
140
-   * Get this class instance or create one if none is found
141
-   * @returns {AsyncStorageManager}
142
-   */
143
-  static getInstance(): AsyncStorageManager {
144
-    if (AsyncStorageManager.instance == null) {
145
-      AsyncStorageManager.instance = new AsyncStorageManager();
146
-    }
147
-    return AsyncStorageManager.instance;
148
-  }
149
-
150
-  /**
151
-   * Saves the value associated to the given key to preferences.
152
-   *
153
-   * @param key
154
-   * @param value
155
-   */
156
-  static set(
157
-    key: string,
158
-    value: number | string | boolean | object | Array<any>
159
-  ) {
160
-    AsyncStorageManager.getInstance().setPreference(key, value);
161
-  }
162
-
163
-  /**
164
-   * Gets the string value of the given preference
165
-   *
166
-   * @param key
167
-   * @returns {string}
168
-   */
169
-  static getString(key: string): string {
170
-    const value = AsyncStorageManager.getInstance().getPreference(key);
171
-    return value != null ? value : '';
172
-  }
173
-
174
-  /**
175
-   * Gets the boolean value of the given preference
176
-   *
177
-   * @param key
178
-   * @returns {boolean}
179
-   */
180
-  static getBool(key: string): boolean {
181
-    const value = AsyncStorageManager.getString(key);
182
-    return value === '1' || value === 'true';
183
-  }
184
-
185
-  /**
186
-   * Gets the number value of the given preference
187
-   *
188
-   * @param key
189
-   * @returns {number}
190
-   */
191
-  static getNumber(key: string): number {
192
-    return parseFloat(AsyncStorageManager.getString(key));
193
-  }
194
-
195
-  /**
196
-   * Gets the object value of the given preference
197
-   *
198
-   * @param key
199
-   * @returns {{...}}
200
-   */
201
-  static getObject<T>(key: string): T {
202
-    return JSON.parse(AsyncStorageManager.getString(key));
203
-  }
204
-
205
-  /**
206
-   * Set preferences object current values from AsyncStorage.
207
-   * This function should be called at the app's start.
208
-   *
209
-   * @return {Promise<void>}
210
-   */
211
-  async loadPreferences() {
212
-    return new Promise((resolve: (val: void) => void) => {
213
-      const prefKeys: Array<string> = [];
214
-      // Get all available keys
215
-      Object.keys(AsyncStorageManager.PREFERENCES).forEach((key: string) => {
216
-        prefKeys.push(key);
217
-      });
218
-      // Get corresponding values
219
-      AsyncStorage.multiGet(prefKeys).then((resultArray) => {
220
-        // Save those values for later use
221
-        resultArray.forEach((item: [string, string | null]) => {
222
-          const key = item[0];
223
-          let val = item[1];
224
-          if (val === null) {
225
-            val = AsyncStorageManager.PREFERENCES[key].default;
226
-          }
227
-          this.currentPreferences[key] = val;
228
-        });
229
-        resolve();
230
-      });
231
-    });
232
-  }
233
-
234
-  /**
235
-   * Saves the value associated to the given key to preferences.
236
-   * This updates the preferences object and saves it to AsyncStorage.
237
-   *
238
-   * @param key
239
-   * @param value
240
-   */
241
-  setPreference(
242
-    key: string,
243
-    value: number | string | boolean | object | Array<any>
244
-  ) {
245
-    if (AsyncStorageManager.PREFERENCES[key] != null) {
246
-      let convertedValue;
247
-      if (typeof value === 'string') {
248
-        convertedValue = value;
249
-      } else if (typeof value === 'boolean' || typeof value === 'number') {
250
-        convertedValue = value.toString();
251
-      } else {
252
-        convertedValue = JSON.stringify(value);
253
-      }
254
-      this.currentPreferences[key] = convertedValue;
255
-      AsyncStorage.setItem(key, convertedValue);
256
-    }
257
-  }
258
-
259
-  /**
260
-   * Gets the value at the given key.
261
-   * If the key is not available, returns null
262
-   *
263
-   * @param key
264
-   * @returns {string|null}
265
-   */
266
-  getPreference(key: string): string | null {
267
-    return this.currentPreferences[key];
268
-  }
269
-}

+ 0
- 38
src/managers/DashboardManager.ts View File

@@ -1,38 +0,0 @@
1
-/*
2
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
3
- *
4
- * This file is part of Campus INSAT.
5
- *
6
- * Campus INSAT is free software: you can redistribute it and/or modify
7
- *  it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Campus INSAT is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
- */
19
-
20
-import type { ServiceItemType } from './ServicesManager';
21
-import ServicesManager from './ServicesManager';
22
-import { getSublistWithIds } from '../utils/Services';
23
-import AsyncStorageManager from './AsyncStorageManager';
24
-
25
-export default class DashboardManager extends ServicesManager {
26
-  getCurrentDashboard(): Array<ServiceItemType | null> {
27
-    const dashboardIdList = AsyncStorageManager.getObject<Array<string>>(
28
-      AsyncStorageManager.PREFERENCES.dashboardItems.key
29
-    );
30
-    const allDatasets = [
31
-      ...this.amicaleDataset,
32
-      ...this.studentsDataset,
33
-      ...this.insaDataset,
34
-      ...this.specialDataset,
35
-    ];
36
-    return getSublistWithIds(dashboardIdList, allDatasets);
37
-  }
38
-}

+ 0
- 371
src/managers/ServicesManager.ts View File

@@ -1,371 +0,0 @@
1
-/*
2
- * Copyright (c) 2019 - 2020 Arnaud Vergnet.
3
- *
4
- * This file is part of Campus INSAT.
5
- *
6
- * Campus INSAT is free software: you can redistribute it and/or modify
7
- *  it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Campus INSAT is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18
- */
19
-
20
-import i18n from 'i18n-js';
21
-import { StackNavigationProp } from '@react-navigation/stack';
22
-import ConnectionManager from './ConnectionManager';
23
-import type { FullDashboardType } from '../screens/Home/HomeScreen';
24
-import getStrippedServicesList from '../utils/Services';
25
-import Urls from '../constants/Urls';
26
-
27
-const AMICALE_LOGO = require('../../assets/amicale.png');
28
-
29
-export const SERVICES_KEY = {
30
-  CLUBS: 'clubs',
31
-  PROFILE: 'profile',
32
-  EQUIPMENT: 'equipment',
33
-  AMICALE_WEBSITE: 'amicale_website',
34
-  VOTE: 'vote',
35
-  PROXIMO: 'proximo',
36
-  WIKETUD: 'wiketud',
37
-  ELUS_ETUDIANTS: 'elus_etudiants',
38
-  TUTOR_INSA: 'tutor_insa',
39
-  RU: 'ru',
40
-  AVAILABLE_ROOMS: 'available_rooms',
41
-  BIB: 'bib',
42
-  EMAIL: 'email',
43
-  ENT: 'ent',
44
-  INSA_ACCOUNT: 'insa_account',
45
-  WASHERS: 'washers',
46
-  DRYERS: 'dryers',
47
-};
48
-
49
-export const SERVICES_CATEGORIES_KEY = {
50
-  AMICALE: 'amicale',
51
-  STUDENTS: 'students',
52
-  INSA: 'insa',
53
-  SPECIAL: 'special',
54
-};
55
-
56
-export type ServiceItemType = {
57
-  key: string;
58
-  title: string;
59
-  subtitle: string;
60
-  image: string | number;
61
-  onPress: () => void;
62
-  badgeFunction?: (dashboard: FullDashboardType) => number;
63
-};
64
-
65
-export type ServiceCategoryType = {
66
-  key: string;
67
-  title: string;
68
-  subtitle: string;
69
-  image: string | number;
70
-  content: Array<ServiceItemType>;
71
-};
72
-
73
-export default class ServicesManager {
74
-  navigation: StackNavigationProp<any>;
75
-
76
-  amicaleDataset: Array<ServiceItemType>;
77
-
78
-  studentsDataset: Array<ServiceItemType>;
79
-
80
-  insaDataset: Array<ServiceItemType>;
81
-
82
-  specialDataset: Array<ServiceItemType>;
83
-
84
-  categoriesDataset: Array<ServiceCategoryType>;
85
-
86
-  constructor(nav: StackNavigationProp<any>) {
87
-    this.navigation = nav;
88
-    this.amicaleDataset = [
89
-      {
90
-        key: SERVICES_KEY.CLUBS,
91
-        title: i18n.t('screens.clubs.title'),
92
-        subtitle: i18n.t('screens.services.descriptions.clubs'),
93
-        image: Urls.images.clubs,
94
-        onPress: (): void => this.onAmicaleServicePress('club-list'),
95
-      },
96
-      {
97
-        key: SERVICES_KEY.PROFILE,
98
-        title: i18n.t('screens.profile.title'),
99
-        subtitle: i18n.t('screens.services.descriptions.profile'),
100
-        image: Urls.images.profile,
101
-        onPress: (): void => this.onAmicaleServicePress('profile'),
102
-      },
103
-      {
104
-        key: SERVICES_KEY.EQUIPMENT,
105
-        title: i18n.t('screens.equipment.title'),
106
-        subtitle: i18n.t('screens.services.descriptions.equipment'),
107
-        image: Urls.images.equipment,
108
-        onPress: (): void => this.onAmicaleServicePress('equipment-list'),
109
-      },
110
-      {
111
-        key: SERVICES_KEY.AMICALE_WEBSITE,
112
-        title: i18n.t('screens.websites.amicale'),
113
-        subtitle: i18n.t('screens.services.descriptions.amicaleWebsite'),
114
-        image: Urls.images.amicale,
115
-        onPress: (): void =>
116
-          nav.navigate('website', {
117
-            host: Urls.websites.amicale,
118
-            title: i18n.t('screens.websites.amicale'),
119
-          }),
120
-      },
121
-      {
122
-        key: SERVICES_KEY.VOTE,
123
-        title: i18n.t('screens.vote.title'),
124
-        subtitle: i18n.t('screens.services.descriptions.vote'),
125
-        image: Urls.images.vote,
126
-        onPress: (): void => this.onAmicaleServicePress('vote'),
127
-      },
128
-    ];
129
-    this.studentsDataset = [
130
-      {
131
-        key: SERVICES_KEY.PROXIMO,
132
-        title: i18n.t('screens.proximo.title'),
133
-        subtitle: i18n.t('screens.services.descriptions.proximo'),
134
-        image: Urls.images.proximo,
135
-        onPress: (): void => nav.navigate('proximo'),
136
-        badgeFunction: (dashboard: FullDashboardType): number =>
137
-          dashboard.proximo_articles,
138
-      },
139
-      {
140
-        key: SERVICES_KEY.WIKETUD,
141
-        title: 'Wiketud',
142
-        subtitle: i18n.t('screens.services.descriptions.wiketud'),
143
-        image: Urls.images.wiketud,
144
-        onPress: (): void =>
145
-          nav.navigate('website', {
146
-            host: Urls.websites.wiketud,
147
-            title: 'Wiketud',
148
-          }),
149
-      },
150
-      {
151
-        key: SERVICES_KEY.ELUS_ETUDIANTS,
152
-        title: 'Élus Étudiants',
153
-        subtitle: i18n.t('screens.services.descriptions.elusEtudiants'),
154
-        image: Urls.images.elusEtudiants,
155
-        onPress: (): void =>
156
-          nav.navigate('website', {
157
-            host: Urls.websites.elusEtudiants,
158
-            title: 'Élus Étudiants',
159
-          }),
160
-      },
161
-      {
162
-        key: SERVICES_KEY.TUTOR_INSA,
163
-        title: "Tutor'INSA",
164
-        subtitle: i18n.t('screens.services.descriptions.tutorInsa'),
165
-        image: Urls.images.tutorInsa,
166
-        onPress: (): void =>
167
-          nav.navigate('website', {
168
-            host: Urls.websites.tutorInsa,
169
-            title: "Tutor'INSA",
170
-          }),
171
-        badgeFunction: (dashboard: FullDashboardType): number =>
172
-          dashboard.available_tutorials,
173
-      },
174
-    ];
175
-    this.insaDataset = [
176
-      {
177
-        key: SERVICES_KEY.RU,
178
-        title: i18n.t('screens.menu.title'),
179
-        subtitle: i18n.t('screens.services.descriptions.self'),
180
-        image: Urls.images.menu,
181
-        onPress: (): void => nav.navigate('self-menu'),
182
-        badgeFunction: (dashboard: FullDashboardType): number =>
183
-          dashboard.today_menu.length,
184
-      },
185
-      {
186
-        key: SERVICES_KEY.AVAILABLE_ROOMS,
187
-        title: i18n.t('screens.websites.rooms'),
188
-        subtitle: i18n.t('screens.services.descriptions.availableRooms'),
189
-        image: Urls.images.availableRooms,
190
-        onPress: (): void =>
191
-          nav.navigate('website', {
192
-            host: Urls.websites.availableRooms,
193
-            title: i18n.t('screens.websites.rooms'),
194
-          }),
195
-      },
196
-      {
197
-        key: SERVICES_KEY.BIB,
198
-        title: i18n.t('screens.websites.bib'),
199
-        subtitle: i18n.t('screens.services.descriptions.bib'),
200
-        image: Urls.images.bib,
201
-        onPress: (): void =>
202
-          nav.navigate('website', {
203
-            host: Urls.websites.bib,
204
-            title: i18n.t('screens.websites.bib'),
205
-          }),
206
-      },
207
-      {
208
-        key: SERVICES_KEY.EMAIL,
209
-        title: i18n.t('screens.websites.mails'),
210
-        subtitle: i18n.t('screens.services.descriptions.mails'),
211
-        image: Urls.images.bluemind,
212
-        onPress: (): void =>
213
-          nav.navigate('website', {
214
-            host: Urls.websites.bluemind,
215
-            title: i18n.t('screens.websites.mails'),
216
-          }),
217
-      },
218
-      {
219
-        key: SERVICES_KEY.ENT,
220
-        title: i18n.t('screens.websites.ent'),
221
-        subtitle: i18n.t('screens.services.descriptions.ent'),
222
-        image: Urls.images.ent,
223
-        onPress: (): void =>
224
-          nav.navigate('website', {
225
-            host: Urls.websites.ent,
226
-            title: i18n.t('screens.websites.ent'),
227
-          }),
228
-      },
229
-      {
230
-        key: SERVICES_KEY.INSA_ACCOUNT,
231
-        title: i18n.t('screens.insaAccount.title'),
232
-        subtitle: i18n.t('screens.services.descriptions.insaAccount'),
233
-        image: Urls.images.insaAccount,
234
-        onPress: (): void =>
235
-          nav.navigate('website', {
236
-            host: Urls.websites.insaAccount,
237
-            title: i18n.t('screens.insaAccount.title'),
238
-          }),
239
-      },
240
-    ];
241
-    this.specialDataset = [
242
-      {
243
-        key: SERVICES_KEY.WASHERS,
244
-        title: i18n.t('screens.proxiwash.washers'),
245
-        subtitle: i18n.t('screens.services.descriptions.washers'),
246
-        image: Urls.images.washer,
247
-        onPress: (): void => nav.navigate('proxiwash'),
248
-        badgeFunction: (dashboard: FullDashboardType): number =>
249
-          dashboard.available_washers,
250
-      },
251
-      {
252
-        key: SERVICES_KEY.DRYERS,
253
-        title: i18n.t('screens.proxiwash.dryers'),
254
-        subtitle: i18n.t('screens.services.descriptions.washers'),
255
-        image: Urls.images.dryer,
256
-        onPress: (): void => nav.navigate('proxiwash'),
257
-        badgeFunction: (dashboard: FullDashboardType): number =>
258
-          dashboard.available_dryers,
259
-      },
260
-    ];
261
-    this.categoriesDataset = [
262
-      {
263
-        key: SERVICES_CATEGORIES_KEY.AMICALE,
264
-        title: i18n.t('screens.services.categories.amicale'),
265
-        subtitle: i18n.t('screens.services.more'),
266
-        image: AMICALE_LOGO,
267
-        content: this.amicaleDataset,
268
-      },
269
-      {
270
-        key: SERVICES_CATEGORIES_KEY.STUDENTS,
271
-        title: i18n.t('screens.services.categories.students'),
272
-        subtitle: i18n.t('screens.services.more'),
273
-        image: 'account-group',
274
-        content: this.studentsDataset,
275
-      },
276
-      {
277
-        key: SERVICES_CATEGORIES_KEY.INSA,
278
-        title: i18n.t('screens.services.categories.insa'),
279
-        subtitle: i18n.t('screens.services.more'),
280
-        image: 'school',
281
-        content: this.insaDataset,
282
-      },
283
-      {
284
-        key: SERVICES_CATEGORIES_KEY.SPECIAL,
285
-        title: i18n.t('screens.services.categories.special'),
286
-        subtitle: i18n.t('screens.services.categories.special'),
287
-        image: 'star',
288
-        content: this.specialDataset,
289
-      },
290
-    ];
291
-  }
292
-
293
-  /**
294
-   * Redirects the user to the login screen if he is not logged in
295
-   *
296
-   * @param route
297
-   * @returns {null}
298
-   */
299
-  onAmicaleServicePress(route: string) {
300
-    if (ConnectionManager.getInstance().isLoggedIn()) {
301
-      this.navigation.navigate(route);
302
-    } else {
303
-      this.navigation.navigate('login', { nextScreen: route });
304
-    }
305
-  }
306
-
307
-  /**
308
-   * Gets the list of amicale's services
309
-   *
310
-   * @param excludedItems Ids of items to exclude from the returned list
311
-   * @returns {Array<ServiceItemType>}
312
-   */
313
-  getAmicaleServices(excludedItems?: Array<string>): Array<ServiceItemType> {
314
-    if (excludedItems != null) {
315
-      return getStrippedServicesList(excludedItems, this.amicaleDataset);
316
-    }
317
-    return this.amicaleDataset;
318
-  }
319
-
320
-  /**
321
-   * Gets the list of students' services
322
-   *
323
-   * @param excludedItems Ids of items to exclude from the returned list
324
-   * @returns {Array<ServiceItemType>}
325
-   */
326
-  getStudentServices(excludedItems?: Array<string>): Array<ServiceItemType> {
327
-    if (excludedItems != null) {
328
-      return getStrippedServicesList(excludedItems, this.studentsDataset);
329
-    }
330
-    return this.studentsDataset;
331
-  }
332
-
333
-  /**
334
-   * Gets the list of INSA's services
335
-   *
336
-   * @param excludedItems Ids of items to exclude from the returned list
337
-   * @returns {Array<ServiceItemType>}
338
-   */
339
-  getINSAServices(excludedItems?: Array<string>): Array<ServiceItemType> {
340
-    if (excludedItems != null) {
341
-      return getStrippedServicesList(excludedItems, this.insaDataset);
342
-    }
343
-    return this.insaDataset;
344
-  }
345
-
346
-  /**
347
-   * Gets the list of special services
348
-   *
349
-   * @param excludedItems Ids of items to exclude from the returned list
350
-   * @returns {Array<ServiceItemType>}
351
-   */
352
-  getSpecialServices(excludedItems?: Array<string>): Array<ServiceItemType> {
353
-    if (excludedItems != null) {
354
-      return getStrippedServicesList(excludedItems, this.specialDataset);
355
-    }
356
-    return this.specialDataset;
357
-  }
358
-
359
-  /**
360
-   * Gets all services sorted by category
361
-   *
362
-   * @param excludedItems Ids of categories to exclude from the returned list
363
-   * @returns {Array<ServiceCategoryType>}
364
-   */
365
-  getCategories(excludedItems?: Array<string>): Array<ServiceCategoryType> {
366
-    if (excludedItems != null) {
367
-      return getStrippedServicesList(excludedItems, this.categoriesDataset);
368
-    }
369
-    return this.categoriesDataset;
370
-  }
371
-}

+ 31
- 5
src/navigation/MainNavigator.tsx View File

@@ -46,16 +46,20 @@ import EquipmentConfirmScreen from '../screens/Amicale/Equipment/EquipmentConfir
46 46
 import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
47 47
 import GameStartScreen from '../screens/Game/screens/GameStartScreen';
48 48
 import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
49
+import { usePreferences } from '../context/preferencesContext';
50
+import { getPreferenceBool, PreferenceKeys } from '../utils/asyncStorage';
51
+import IntroScreen from '../screens/Intro/IntroScreen';
49 52
 
50 53
 export enum MainRoutes {
51 54
   Main = 'main',
55
+  Intro = 'Intro',
52 56
   Gallery = 'gallery',
53 57
   Settings = 'settings',
54 58
   DashboardEdit = 'dashboard-edit',
55 59
   About = 'about',
56 60
   Dependencies = 'dependencies',
57 61
   Debug = 'debug',
58
-  GameStart = 'game-start',
62
+  GameStart = 'game',
59 63
   GameMain = 'game-main',
60 64
   Login = 'login',
61 65
   SelfMenu = 'self-menu',
@@ -66,11 +70,12 @@ export enum MainRoutes {
66 70
   ClubList = 'club-list',
67 71
   ClubInformation = 'club-information',
68 72
   ClubAbout = 'club-about',
69
-  EquipmentList = 'equipment-list',
73
+  EquipmentList = 'equipment',
70 74
   EquipmentRent = 'equipment-rent',
71 75
   EquipmentConfirm = 'equipment-confirm',
72 76
   Vote = 'vote',
73 77
   Feedback = 'feedback',
78
+  Website = 'website',
74 79
 }
75 80
 
76 81
 type DefaultParams = { [key in MainRoutes]: object | undefined };
@@ -96,13 +101,31 @@ export type MainStackParamsList = FullParamsList &
96 101
 
97 102
 const MainStack = createStackNavigator<MainStackParamsList>();
98 103
 
104
+function getIntroScreens() {
105
+  return (
106
+    <>
107
+      <MainStack.Screen
108
+        name={MainRoutes.Intro}
109
+        component={IntroScreen}
110
+        options={{
111
+          headerShown: false,
112
+        }}
113
+      />
114
+    </>
115
+  );
116
+}
117
+
99 118
 function MainStackComponent(props: {
119
+  showIntro: boolean;
100 120
   createTabNavigator: () => React.ReactElement;
101 121
 }) {
102
-  const { createTabNavigator } = props;
122
+  const { showIntro, createTabNavigator } = props;
123
+  if (showIntro) {
124
+    return getIntroScreens();
125
+  }
103 126
   return (
104 127
     <MainStack.Navigator
105
-      initialRouteName={MainRoutes.Main}
128
+      initialRouteName={showIntro ? MainRoutes.Intro : MainRoutes.Main}
106 129
       headerMode={'screen'}
107 130
     >
108 131
       <MainStack.Screen
@@ -183,7 +206,7 @@ function MainStackComponent(props: {
183 206
         }}
184 207
       />
185 208
       <MainStack.Screen
186
-        name={'website'}
209
+        name={MainRoutes.Website}
187 210
         component={WebsiteScreen}
188 211
         options={{
189 212
           title: '',
@@ -290,8 +313,11 @@ type PropsType = {
290 313
 };
291 314
 
292 315
 export default function MainNavigator(props: PropsType) {
316
+  const { preferences } = usePreferences();
317
+  const showIntro = getPreferenceBool(PreferenceKeys.showIntro, preferences);
293 318
   return (
294 319
     <MainStackComponent
320
+      showIntro={showIntro !== false}
295 321
       createTabNavigator={() => <TabNavigator {...props} />}
296 322
     />
297 323
   );

+ 81
- 61
src/navigation/TabNavigator.tsx View File

@@ -31,7 +31,6 @@ import PlanningDisplayScreen from '../screens/Planning/PlanningDisplayScreen';
31 31
 import ProxiwashScreen from '../screens/Proxiwash/ProxiwashScreen';
32 32
 import ProxiwashAboutScreen from '../screens/Proxiwash/ProxiwashAboutScreen';
33 33
 import PlanexScreen from '../screens/Planex/PlanexScreen';
34
-import AsyncStorageManager from '../managers/AsyncStorageManager';
35 34
 import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
36 35
 import ScannerScreen from '../screens/Home/ScannerScreen';
37 36
 import FeedItemScreen from '../screens/Home/FeedItemScreen';
@@ -41,6 +40,8 @@ import WebsitesHomeScreen from '../screens/Services/ServicesScreen';
41 40
 import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
42 41
 import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
43 42
 import Mascot, { MASCOT_STYLE } from '../components/Mascot/Mascot';
43
+import { usePreferences } from '../context/preferencesContext';
44
+import { getPreferenceString, PreferenceKeys } from '../utils/asyncStorage';
44 45
 
45 46
 const styles = StyleSheet.create({
46 47
   header: {
@@ -56,6 +57,20 @@ const styles = StyleSheet.create({
56 57
   },
57 58
 });
58 59
 
60
+type DefaultParams = { [key in TabRoutes]: object | undefined };
61
+
62
+export type FullParamsList = DefaultParams & {
63
+  [TabRoutes.Home]: {
64
+    nextScreen: string;
65
+    data: Record<string, object | undefined>;
66
+  };
67
+};
68
+
69
+// Don't know why but TS is complaining without this
70
+// See: https://stackoverflow.com/questions/63652687/interface-does-not-satisfy-the-constraint-recordstring-object-undefined
71
+export type TabStackParamsList = FullParamsList &
72
+  Record<string, object | undefined>;
73
+
59 74
 const ServicesStack = createStackNavigator();
60 75
 
61 76
 function ServicesStackComponent() {
@@ -214,7 +229,7 @@ function PlanexStackComponent() {
214 229
   );
215 230
 }
216 231
 
217
-const Tab = createBottomTabNavigator();
232
+const Tab = createBottomTabNavigator<TabStackParamsList>();
218 233
 
219 234
 type PropsType = {
220 235
   defaultHomeRoute: string | null;
@@ -249,65 +264,70 @@ const ICONS: {
249 264
   },
250 265
 };
251 266
 
252
-export default class TabNavigator extends React.Component<PropsType> {
253
-  defaultRoute: string;
254
-  createHomeStackComponent: () => any;
255
-
256
-  constructor(props: PropsType) {
257
-    super(props);
258
-    this.defaultRoute = 'home';
259
-    if (!props.defaultHomeRoute) {
260
-      this.defaultRoute = AsyncStorageManager.getString(
261
-        AsyncStorageManager.PREFERENCES.defaultStartScreen.key
262
-      ).toLowerCase();
263
-    }
264
-    this.createHomeStackComponent = () =>
265
-      HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
267
+export default function TabNavigator(props: PropsType) {
268
+  const { preferences } = usePreferences();
269
+  let defaultRoute = getPreferenceString(
270
+    PreferenceKeys.defaultStartScreen,
271
+    preferences
272
+  );
273
+  if (!defaultRoute) {
274
+    defaultRoute = 'home';
275
+  } else {
276
+    defaultRoute = defaultRoute.toLowerCase();
266 277
   }
267 278
 
268
-  render() {
269
-    const LABELS: {
270
-      [key: string]: string;
271
-    } = {
272
-      services: i18n.t('screens.services.title'),
273
-      proxiwash: i18n.t('screens.proxiwash.title'),
274
-      home: i18n.t('screens.home.title'),
275
-      planning: i18n.t('screens.planning.title'),
276
-      planex: i18n.t('screens.planex.title'),
277
-    };
278
-    return (
279
-      <Tab.Navigator
280
-        initialRouteName={this.defaultRoute}
281
-        tabBar={(tabProps) => (
282
-          <CustomTabBar {...tabProps} labels={LABELS} icons={ICONS} />
283
-        )}
284
-      >
285
-        <Tab.Screen
286
-          name={'services'}
287
-          component={ServicesStackComponent}
288
-          options={{ title: i18n.t('screens.services.title') }}
289
-        />
290
-        <Tab.Screen
291
-          name={'proxiwash'}
292
-          component={ProxiwashStackComponent}
293
-          options={{ title: i18n.t('screens.proxiwash.title') }}
294
-        />
295
-        <Tab.Screen
296
-          name={'home'}
297
-          component={this.createHomeStackComponent}
298
-          options={{ title: i18n.t('screens.home.title') }}
299
-        />
300
-        <Tab.Screen
301
-          name={'planning'}
302
-          component={PlanningStackComponent}
303
-          options={{ title: i18n.t('screens.planning.title') }}
304
-        />
305
-        <Tab.Screen
306
-          name={'planex'}
307
-          component={PlanexStackComponent}
308
-          options={{ title: i18n.t('screens.planex.title') }}
309
-        />
310
-      </Tab.Navigator>
311
-    );
312
-  }
279
+  const createHomeStackComponent = () =>
280
+    HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
281
+
282
+  const LABELS: {
283
+    [key: string]: string;
284
+  } = {
285
+    services: i18n.t('screens.services.title'),
286
+    proxiwash: i18n.t('screens.proxiwash.title'),
287
+    home: i18n.t('screens.home.title'),
288
+    planning: i18n.t('screens.planning.title'),
289
+    planex: i18n.t('screens.planex.title'),
290
+  };
291
+  return (
292
+    <Tab.Navigator
293
+      initialRouteName={defaultRoute}
294
+      tabBar={(tabProps) => (
295
+        <CustomTabBar {...tabProps} labels={LABELS} icons={ICONS} />
296
+      )}
297
+    >
298
+      <Tab.Screen
299
+        name={'services'}
300
+        component={ServicesStackComponent}
301
+        options={{ title: i18n.t('screens.services.title') }}
302
+      />
303
+      <Tab.Screen
304
+        name={'proxiwash'}
305
+        component={ProxiwashStackComponent}
306
+        options={{ title: i18n.t('screens.proxiwash.title') }}
307
+      />
308
+      <Tab.Screen
309
+        name={'home'}
310
+        component={createHomeStackComponent}
311
+        options={{ title: i18n.t('screens.home.title') }}
312
+      />
313
+      <Tab.Screen
314
+        name={'events'}
315
+        component={PlanningStackComponent}
316
+        options={{ title: i18n.t('screens.planning.title') }}
317
+      />
318
+      <Tab.Screen
319
+        name={'planex'}
320
+        component={PlanexStackComponent}
321
+        options={{ title: i18n.t('screens.planex.title') }}
322
+      />
323
+    </Tab.Navigator>
324
+  );
325
+}
326
+
327
+export enum TabRoutes {
328
+  Services = 'services',
329
+  Proxiwash = 'proxiwash',
330
+  Home = 'home',
331
+  Planning = 'events',
332
+  Planex = 'planex',
313 333
 }

+ 62
- 122
src/screens/About/DebugScreen.tsx View File

@@ -17,7 +17,7 @@
17 17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18 18
  */
19 19
 
20
-import * as React from 'react';
20
+import React, { useRef, useState } from 'react';
21 21
 import { StyleSheet, View } from 'react-native';
22 22
 import {
23 23
   Button,
@@ -25,12 +25,17 @@ import {
25 25
   Subheading,
26 26
   TextInput,
27 27
   Title,
28
-  withTheme,
28
+  useTheme,
29 29
 } from 'react-native-paper';
30 30
 import { Modalize } from 'react-native-modalize';
31 31
 import CustomModal from '../../components/Overrides/CustomModal';
32
-import AsyncStorageManager from '../../managers/AsyncStorageManager';
33 32
 import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
33
+import { usePreferences } from '../../context/preferencesContext';
34
+import {
35
+  defaultPreferences,
36
+  isValidPreferenceKey,
37
+  PreferenceKeys,
38
+} from '../../utils/asyncStorage';
34 39
 
35 40
 type PreferenceItemType = {
36 41
   key: string;
@@ -38,15 +43,6 @@ type PreferenceItemType = {
38 43
   current: string;
39 44
 };
40 45
 
41
-type PropsType = {
42
-  theme: ReactNativePaper.Theme;
43
-};
44
-
45
-type StateType = {
46
-  modalCurrentDisplayItem: PreferenceItemType | null;
47
-  currentPreferences: Array<PreferenceItemType>;
48
-};
49
-
50 46
 const styles = StyleSheet.create({
51 47
   container: {
52 48
     flex: 1,
@@ -62,47 +58,35 @@ const styles = StyleSheet.create({
62 58
  * Class defining the Debug screen.
63 59
  * This screen allows the user to get and modify information on the app/device.
64 60
  */
65
-class DebugScreen extends React.Component<PropsType, StateType> {
66
-  modalRef: { current: Modalize | null };
67
-
68
-  modalInputValue: string;
69
-
70
-  /**
71
-   * Copies user preferences to state for easier manipulation
72
-   *
73
-   * @param props
74
-   */
75
-  constructor(props: PropsType) {
76
-    super(props);
77
-    this.modalRef = React.createRef<Modalize>();
78
-    this.modalInputValue = '';
79
-    const currentPreferences: Array<PreferenceItemType> = [];
80
-    Object.values(AsyncStorageManager.PREFERENCES).forEach((object: any) => {
81
-      const newObject: PreferenceItemType = { ...object };
82
-      newObject.current = AsyncStorageManager.getString(newObject.key);
83
-      currentPreferences.push(newObject);
84
-    });
85
-    this.state = {
86
-      modalCurrentDisplayItem: null,
87
-      currentPreferences,
61
+function DebugScreen() {
62
+  const theme = useTheme();
63
+  const { preferences, updatePreferences } = usePreferences();
64
+  const modalRef = useRef<Modalize>(null);
65
+
66
+  const [modalInputValue, setModalInputValue] = useState<string>('');
67
+  const [
68
+    modalCurrentDisplayItem,
69
+    setModalCurrentDisplayItem,
70
+  ] = useState<PreferenceItemType | null>(null);
71
+
72
+  const currentPreferences: Array<PreferenceItemType> = [];
73
+  Object.values(PreferenceKeys).forEach((key) => {
74
+    const newObject: PreferenceItemType = {
75
+      key: key,
76
+      current: preferences[key],
77
+      default: defaultPreferences[key],
88 78
     };
89
-  }
79
+    currentPreferences.push(newObject);
80
+  });
90 81
 
91
-  /**
92
-   * Gets the edit modal content
93
-   *
94
-   * @return {*}
95
-   */
96
-  getModalContent() {
97
-    const { props, state } = this;
82
+  const getModalContent = () => {
98 83
     let key = '';
99 84
     let defaultValue = '';
100 85
     let current = '';
101
-    if (state.modalCurrentDisplayItem) {
102
-      key = state.modalCurrentDisplayItem.key;
103
-      defaultValue = state.modalCurrentDisplayItem.default;
104
-      defaultValue = state.modalCurrentDisplayItem.default;
105
-      current = state.modalCurrentDisplayItem.current;
86
+    if (modalCurrentDisplayItem) {
87
+      key = modalCurrentDisplayItem.key;
88
+      defaultValue = modalCurrentDisplayItem.default;
89
+      current = modalCurrentDisplayItem.current;
106 90
     }
107 91
 
108 92
     return (
@@ -110,19 +94,14 @@ class DebugScreen extends React.Component<PropsType, StateType> {
110 94
         <Title>{key}</Title>
111 95
         <Subheading>Default: {defaultValue}</Subheading>
112 96
         <Subheading>Current: {current}</Subheading>
113
-        <TextInput
114
-          label="New Value"
115
-          onChangeText={(text: string) => {
116
-            this.modalInputValue = text;
117
-          }}
118
-        />
97
+        <TextInput label={'New Value'} onChangeText={setModalInputValue} />
119 98
         <View style={styles.buttonContainer}>
120 99
           <Button
121 100
             mode="contained"
122 101
             dark
123
-            color={props.theme.colors.success}
102
+            color={theme.colors.success}
124 103
             onPress={() => {
125
-              this.saveNewPrefs(key, this.modalInputValue);
104
+              saveNewPrefs(key, modalInputValue);
126 105
             }}
127 106
           >
128 107
             Save new value
@@ -130,9 +109,9 @@ class DebugScreen extends React.Component<PropsType, StateType> {
130 109
           <Button
131 110
             mode="contained"
132 111
             dark
133
-            color={props.theme.colors.danger}
112
+            color={theme.colors.danger}
134 113
             onPress={() => {
135
-              this.saveNewPrefs(key, defaultValue);
114
+              saveNewPrefs(key, defaultValue);
136 115
             }}
137 116
           >
138 117
             Reset to default
@@ -140,85 +119,46 @@ class DebugScreen extends React.Component<PropsType, StateType> {
140 119
         </View>
141 120
       </View>
142 121
     );
143
-  }
122
+  };
144 123
 
145
-  getRenderItem = ({ item }: { item: PreferenceItemType }) => {
124
+  const getRenderItem = ({ item }: { item: PreferenceItemType }) => {
146 125
     return (
147 126
       <List.Item
148 127
         title={item.key}
149 128
         description="Click to edit"
150 129
         onPress={() => {
151
-          this.showEditModal(item);
130
+          showEditModal(item);
152 131
         }}
153 132
       />
154 133
     );
155 134
   };
156 135
 
157
-  /**
158
-   * Shows the edit modal
159
-   *
160
-   * @param item
161
-   */
162
-  showEditModal(item: PreferenceItemType) {
163
-    this.setState({
164
-      modalCurrentDisplayItem: item,
165
-    });
166
-    if (this.modalRef.current) {
167
-      this.modalRef.current.open();
136
+  const showEditModal = (item: PreferenceItemType) => {
137
+    setModalCurrentDisplayItem(item);
138
+    if (modalRef.current) {
139
+      modalRef.current.open();
168 140
     }
169
-  }
141
+  };
170 142
 
171
-  /**
172
-   * Finds the index of the given key in the preferences array
173
-   *
174
-   * @param key THe key to find the index of
175
-   * @returns {number}
176
-   */
177
-  findIndexOfKey(key: string): number {
178
-    const { currentPreferences } = this.state;
179
-    let index = -1;
180
-    for (let i = 0; i < currentPreferences.length; i += 1) {
181
-      if (currentPreferences[i].key === key) {
182
-        index = i;
183
-        break;
184
-      }
143
+  const saveNewPrefs = (key: string, value: string) => {
144
+    if (isValidPreferenceKey(key)) {
145
+      updatePreferences(key, value);
185 146
     }
186
-    return index;
187
-  }
188
-
189
-  /**
190
-   * Saves the new value of the given preference
191
-   *
192
-   * @param key The pref key
193
-   * @param value The pref value
194
-   */
195
-  saveNewPrefs(key: string, value: string) {
196
-    this.setState((prevState: StateType): {
197
-      currentPreferences: Array<PreferenceItemType>;
198
-    } => {
199
-      const currentPreferences = [...prevState.currentPreferences];
200
-      currentPreferences[this.findIndexOfKey(key)].current = value;
201
-      return { currentPreferences };
202
-    });
203
-    AsyncStorageManager.set(key, value);
204
-    if (this.modalRef.current) {
205
-      this.modalRef.current.close();
147
+    if (modalRef.current) {
148
+      modalRef.current.close();
206 149
     }
207
-  }
150
+  };
208 151
 
209
-  render() {
210
-    const { state } = this;
211
-    return (
212
-      <View>
213
-        <CustomModal ref={this.modalRef}>{this.getModalContent()}</CustomModal>
214
-        <CollapsibleFlatList
215
-          data={state.currentPreferences}
216
-          extraData={state.currentPreferences}
217
-          renderItem={this.getRenderItem}
218
-        />
219
-      </View>
220
-    );
221
-  }
152
+  return (
153
+    <View>
154
+      <CustomModal ref={modalRef}>{getModalContent()}</CustomModal>
155
+      <CollapsibleFlatList
156
+        data={currentPreferences}
157
+        extraData={currentPreferences}
158
+        renderItem={getRenderItem}
159
+      />
160
+    </View>
161
+  );
222 162
 }
223 163
 
224
-export default withTheme(DebugScreen);
164
+export default DebugScreen;

+ 2
- 9
src/screens/Amicale/Equipment/EquipmentListScreen.tsx View File

@@ -25,7 +25,6 @@ import i18n from 'i18n-js';
25 25
 import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
26 26
 import MascotPopup from '../../../components/Mascot/MascotPopup';
27 27
 import { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
28
-import AsyncStorageManager from '../../../managers/AsyncStorageManager';
29 28
 import GENERAL_STYLES from '../../../constants/Styles';
30 29
 import ConnectionManager from '../../../managers/ConnectionManager';
31 30
 import { ApiRejectType } from '../../../utils/WebData';
@@ -36,7 +35,7 @@ type PropsType = {
36 35
 };
37 36
 
38 37
 type StateType = {
39
-  mascotDialogVisible: boolean;
38
+  mascotDialogVisible: boolean | undefined;
40 39
 };
41 40
 
42 41
 export type DeviceType = {
@@ -75,9 +74,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
75 74
     super(props);
76 75
     this.userRents = null;
77 76
     this.state = {
78
-      mascotDialogVisible: AsyncStorageManager.getBool(
79
-        AsyncStorageManager.PREFERENCES.equipmentShowMascot.key
80
-      ),
77
+      mascotDialogVisible: undefined,
81 78
     };
82 79
   }
83 80
 
@@ -145,10 +142,6 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
145 142
   };
146 143
 
147 144
   hideMascotDialog = () => {
148
-    AsyncStorageManager.set(
149
-      AsyncStorageManager.PREFERENCES.equipmentShowMascot.key,
150
-      false
151
-    );
152 145
     this.setState({ mascotDialogVisible: false });
153 146
   };
154 147
 

+ 7
- 13
src/screens/Amicale/LoginScreen.tsx View File

@@ -31,7 +31,6 @@ import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack';
31 31
 import LinearGradient from 'react-native-linear-gradient';
32 32
 import ConnectionManager from '../../managers/ConnectionManager';
33 33
 import ErrorDialog from '../../components/Dialogs/ErrorDialog';
34
-import AsyncStorageManager from '../../managers/AsyncStorageManager';
35 34
 import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
36 35
 import MascotPopup from '../../components/Mascot/MascotPopup';
37 36
 import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
@@ -56,7 +55,7 @@ type StateType = {
56 55
   loading: boolean;
57 56
   dialogVisible: boolean;
58 57
   dialogError: ApiRejectType;
59
-  mascotDialogVisible: boolean;
58
+  mascotDialogVisible: boolean | undefined;
60 59
 };
61 60
 
62 61
 const ICON_AMICALE = require('../../../assets/amicale.png');
@@ -118,9 +117,7 @@ class LoginScreen extends React.Component<Props, StateType> {
118 117
       loading: false,
119 118
       dialogVisible: false,
120 119
       dialogError: { status: REQUEST_STATUS.SUCCESS },
121
-      mascotDialogVisible: AsyncStorageManager.getBool(
122
-        AsyncStorageManager.PREFERENCES.loginShowMascot.key
123
-      ),
120
+      mascotDialogVisible: undefined,
124 121
     };
125 122
   }
126 123
 
@@ -321,10 +318,6 @@ class LoginScreen extends React.Component<Props, StateType> {
321 318
   };
322 319
 
323 320
   hideMascotDialog = () => {
324
-    AsyncStorageManager.set(
325
-      AsyncStorageManager.PREFERENCES.loginShowMascot.key,
326
-      false
327
-    );
328 321
     this.setState({ mascotDialogVisible: false });
329 322
   };
330 323
 
@@ -357,10 +350,11 @@ class LoginScreen extends React.Component<Props, StateType> {
357 350
   handleSuccess = () => {
358 351
     const { navigation } = this.props;
359 352
     // Do not show the home login banner again
360
-    AsyncStorageManager.set(
361
-      AsyncStorageManager.PREFERENCES.homeShowMascot.key,
362
-      false
363
-    );
353
+    // TODO
354
+    // AsyncStorageManager.set(
355
+    //   AsyncStorageManager.PREFERENCES.homeShowMascot.key,
356
+    //   false
357
+    // );
364 358
     if (this.nextScreen == null) {
365 359
       navigation.goBack();
366 360
     } else {

+ 8
- 4
src/screens/Amicale/ProfileScreen.tsx View File

@@ -36,13 +36,16 @@ import MaterialHeaderButtons, {
36 36
 } from '../../components/Overrides/CustomHeaderButton';
37 37
 import CardList from '../../components/Lists/CardList/CardList';
38 38
 import Mascot, { MASCOT_STYLE } from '../../components/Mascot/Mascot';
39
-import ServicesManager, { SERVICES_KEY } from '../../managers/ServicesManager';
40 39
 import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
41
-import type { ServiceItemType } from '../../managers/ServicesManager';
42 40
 import GENERAL_STYLES from '../../constants/Styles';
43 41
 import Urls from '../../constants/Urls';
44 42
 import RequestScreen from '../../components/Screens/RequestScreen';
45 43
 import ConnectionManager from '../../managers/ConnectionManager';
44
+import {
45
+  getAmicaleServices,
46
+  ServiceItemType,
47
+  SERVICES_KEY,
48
+} from '../../utils/Services';
46 49
 
47 50
 type PropsType = {
48 51
   navigation: StackNavigationProp<any>;
@@ -100,8 +103,9 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
100 103
     super(props);
101 104
     this.data = undefined;
102 105
     this.flatListData = [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }];
103
-    const services = new ServicesManager(props.navigation);
104
-    this.amicaleDataset = services.getAmicaleServices([SERVICES_KEY.PROFILE]);
106
+    this.amicaleDataset = getAmicaleServices(props.navigation.navigate, [
107
+      SERVICES_KEY.PROFILE,
108
+    ]);
105 109
     this.state = {
106 110
       dialogVisible: false,
107 111
     };

+ 2
- 9
src/screens/Amicale/VoteScreen.tsx View File

@@ -28,7 +28,6 @@ import VoteResults from '../../components/Amicale/Vote/VoteResults';
28 28
 import VoteWait from '../../components/Amicale/Vote/VoteWait';
29 29
 import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
30 30
 import MascotPopup from '../../components/Mascot/MascotPopup';
31
-import AsyncStorageManager from '../../managers/AsyncStorageManager';
32 31
 import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
33 32
 import GENERAL_STYLES from '../../constants/Styles';
34 33
 import ConnectionManager from '../../managers/ConnectionManager';
@@ -118,7 +117,7 @@ type PropsType = {};
118 117
 
119 118
 type StateType = {
120 119
   hasVoted: boolean;
121
-  mascotDialogVisible: boolean;
120
+  mascotDialogVisible: boolean | undefined;
122 121
 };
123 122
 
124 123
 const styles = StyleSheet.create({
@@ -154,9 +153,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
154 153
     this.dates = undefined;
155 154
     this.state = {
156 155
       hasVoted: false,
157
-      mascotDialogVisible: AsyncStorageManager.getBool(
158
-        AsyncStorageManager.PREFERENCES.voteShowMascot.key
159
-      ),
156
+      mascotDialogVisible: undefined,
160 157
     };
161 158
     this.hasVoted = false;
162 159
     this.today = new Date();
@@ -328,10 +325,6 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
328 325
   };
329 326
 
330 327
   hideMascotDialog = () => {
331
-    AsyncStorageManager.set(
332
-      AsyncStorageManager.PREFERENCES.voteShowMascot.key,
333
-      false
334
-    );
335 328
     this.setState({ mascotDialogVisible: false });
336 329
   };
337 330
 

+ 2
- 1
src/screens/Game/screens/GameMainScreen.tsx View File

@@ -33,6 +33,7 @@ import MaterialHeaderButtons, {
33 33
 import type { OptionsDialogButtonType } from '../../../components/Dialogs/OptionsDialog';
34 34
 import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
35 35
 import GENERAL_STYLES from '../../../constants/Styles';
36
+import { MainRoutes } from '../../../navigation/MainNavigator';
36 37
 
37 38
 type PropsType = {
38 39
   navigation: StackNavigationProp<any>;
@@ -200,7 +201,7 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
200 201
       gameScore: score,
201 202
     });
202 203
     if (!isRestart) {
203
-      props.navigation.replace('game-start', {
204
+      props.navigation.replace(MainRoutes.GameStart, {
204 205
         score: state.gameScore,
205 206
         level: state.gameLevel,
206 207
         time: state.gameTime,

+ 10
- 9
src/screens/Game/screens/GameStartScreen.tsx View File

@@ -35,7 +35,6 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
35 35
 import LinearGradient from 'react-native-linear-gradient';
36 36
 import Mascot, { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
37 37
 import MascotPopup from '../../../components/Mascot/MascotPopup';
38
-import AsyncStorageManager from '../../../managers/AsyncStorageManager';
39 38
 import type { GridType } from '../components/GridComponent';
40 39
 import GridComponent from '../components/GridComponent';
41 40
 import GridManager from '../logic/GridManager';
@@ -152,9 +151,11 @@ class GameStartScreen extends React.Component<PropsType> {
152 151
     super(props);
153 152
     this.isHighScore = false;
154 153
     this.gridManager = new GridManager(4, 4, props.theme);
155
-    this.scores = AsyncStorageManager.getObject(
156
-      AsyncStorageManager.PREFERENCES.gameScores.key
157
-    );
154
+    // TODO
155
+    // this.scores = AsyncStorageManager.getObject(
156
+    //   AsyncStorageManager.PREFERENCES.gameScores.key
157
+    // );
158
+    this.scores = [];
158 159
     this.scores.sort((a: number, b: number): number => b - a);
159 160
     if (props.route.params != null) {
160 161
       this.recoverGameScore();
@@ -448,10 +449,11 @@ class GameStartScreen extends React.Component<PropsType> {
448 449
       if (this.scores.length > 3) {
449 450
         this.scores.splice(3, 1);
450 451
       }
451
-      AsyncStorageManager.set(
452
-        AsyncStorageManager.PREFERENCES.gameScores.key,
453
-        this.scores
454
-      );
452
+      // TODO
453
+      // AsyncStorageManager.set(
454
+      //   AsyncStorageManager.PREFERENCES.gameScores.key,
455
+      //   this.scores
456
+      // );
455 457
     }
456 458
   }
457 459
 
@@ -472,7 +474,6 @@ class GameStartScreen extends React.Component<PropsType> {
472 474
           <CollapsibleScrollView headerColors={'transparent'}>
473 475
             {this.getMainContent()}
474 476
             <MascotPopup
475
-              prefKey={AsyncStorageManager.PREFERENCES.gameStartMascot.key}
476 477
               title={i18n.t('screens.game.mascotDialog.title')}
477 478
               message={i18n.t('screens.game.mascotDialog.message')}
478 479
               icon="gamepad-variant"

+ 170
- 238
src/screens/Home/HomeScreen.tsx View File

@@ -17,7 +17,7 @@
17 17
  * along with Campus INSAT.  If not, see <https://www.gnu.org/licenses/>.
18 18
  */
19 19
 
20
-import * as React from 'react';
20
+import React, { useLayoutEffect, useRef, useState } from 'react';
21 21
 import {
22 22
   FlatList,
23 23
   NativeScrollEvent,
@@ -26,9 +26,13 @@ import {
26 26
   StyleSheet,
27 27
 } from 'react-native';
28 28
 import i18n from 'i18n-js';
29
-import { Headline, withTheme } from 'react-native-paper';
30
-import { CommonActions } from '@react-navigation/native';
31
-import { StackNavigationProp } from '@react-navigation/stack';
29
+import { Headline, useTheme } from 'react-native-paper';
30
+import {
31
+  CommonActions,
32
+  useFocusEffect,
33
+  useNavigation,
34
+} from '@react-navigation/native';
35
+import { StackScreenProps } from '@react-navigation/stack';
32 36
 import * as Animatable from 'react-native-animatable';
33 37
 import { View } from 'react-native-animatable';
34 38
 import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
@@ -44,16 +48,17 @@ import MaterialHeaderButtons, {
44 48
 import AnimatedFAB from '../../components/Animations/AnimatedFAB';
45 49
 import ConnectionManager from '../../managers/ConnectionManager';
46 50
 import LogoutDialog from '../../components/Amicale/LogoutDialog';
47
-import AsyncStorageManager from '../../managers/AsyncStorageManager';
48 51
 import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
49 52
 import MascotPopup from '../../components/Mascot/MascotPopup';
50
-import DashboardManager from '../../managers/DashboardManager';
51
-import type { ServiceItemType } from '../../managers/ServicesManager';
52 53
 import { getDisplayEvent, getFutureEvents } from '../../utils/Home';
53 54
 import type { PlanningEventType } from '../../utils/Planning';
54 55
 import GENERAL_STYLES from '../../constants/Styles';
55 56
 import Urls from '../../constants/Urls';
56 57
 import { readData } from '../../utils/WebData';
58
+import { TabRoutes, TabStackParamsList } from '../../navigation/TabNavigator';
59
+import { ServiceItemType } from '../../utils/Services';
60
+import { useCurrentDashboard } from '../../context/preferencesContext';
61
+import { MainRoutes } from '../../navigation/MainNavigator';
57 62
 
58 63
 const FEED_ITEM_HEIGHT = 500;
59 64
 
@@ -88,15 +93,7 @@ type RawDashboardType = {
88 93
   dashboard: FullDashboardType;
89 94
 };
90 95
 
91
-type PropsType = {
92
-  navigation: StackNavigationProp<any>;
93
-  route: { params: { nextScreen: string; data: object } };
94
-  theme: ReactNativePaper.Theme;
95
-};
96
-
97
-type StateType = {
98
-  dialogVisible: boolean;
99
-};
96
+type Props = StackScreenProps<TabStackParamsList, TabRoutes.Home>;
100 97
 
101 98
 const styles = StyleSheet.create({
102 99
   dashboardRow: {
@@ -127,106 +124,94 @@ const styles = StyleSheet.create({
127 124
   },
128 125
 });
129 126
 
127
+const sortFeedTime = (a: FeedItemType, b: FeedItemType): number =>
128
+  b.time - a.time;
129
+
130
+const generateNewsFeed = (rawFeed: RawNewsFeedType): Array<FeedItemType> => {
131
+  const finalFeed: Array<FeedItemType> = [];
132
+  Object.keys(rawFeed).forEach((key: string) => {
133
+    const category: Array<FeedItemType> | null = rawFeed[key];
134
+    if (category != null && category.length > 0) {
135
+      finalFeed.push(...category);
136
+    }
137
+  });
138
+  finalFeed.sort(sortFeedTime);
139
+  return finalFeed;
140
+};
141
+
130 142
 /**
131 143
  * Class defining the app's home screen
132 144
  */
133
-class HomeScreen extends React.Component<PropsType, StateType> {
134
-  static sortFeedTime = (a: FeedItemType, b: FeedItemType): number =>
135
-    b.time - a.time;
136
-
137
-  static generateNewsFeed(rawFeed: RawNewsFeedType): Array<FeedItemType> {
138
-    const finalFeed: Array<FeedItemType> = [];
139
-    Object.keys(rawFeed).forEach((key: string) => {
140
-      const category: Array<FeedItemType> | null = rawFeed[key];
141
-      if (category != null && category.length > 0) {
142
-        finalFeed.push(...category);
145
+function HomeScreen(props: Props) {
146
+  const theme = useTheme();
147
+  const navigation = useNavigation();
148
+
149
+  const [dialogVisible, setDialogVisible] = useState(false);
150
+  const fabRef = useRef<AnimatedFAB>(null);
151
+
152
+  const [isLoggedIn, setIsLoggedIn] = useState(
153
+    ConnectionManager.getInstance().isLoggedIn()
154
+  );
155
+  const { currentDashboard } = useCurrentDashboard();
156
+
157
+  let homeDashboard: FullDashboardType | null = null;
158
+
159
+  useLayoutEffect(() => {
160
+    const getHeaderButton = () => {
161
+      let onPressLog = () =>
162
+        navigation.navigate('login', { nextScreen: 'profile' });
163
+      let logIcon = 'login';
164
+      let logColor = theme.colors.primary;
165
+      if (isLoggedIn) {
166
+        onPressLog = () => showDisconnectDialog();
167
+        logIcon = 'logout';
168
+        logColor = theme.colors.text;
143 169
       }
144
-    });
145
-    finalFeed.sort(HomeScreen.sortFeedTime);
146
-    return finalFeed;
147
-  }
148 170
 
149
-  isLoggedIn: boolean | null;
150
-
151
-  fabRef: { current: null | AnimatedFAB };
152
-
153
-  currentNewFeed: Array<FeedItemType>;
154
-
155
-  currentDashboard: FullDashboardType | null;
156
-
157
-  dashboardManager: DashboardManager;
158
-
159
-  constructor(props: PropsType) {
160
-    super(props);
161
-    this.fabRef = React.createRef();
162
-    this.dashboardManager = new DashboardManager(props.navigation);
163
-    this.currentNewFeed = [];
164
-    this.currentDashboard = null;
165
-    this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
166
-    props.navigation.setOptions({
167
-      headerRight: this.getHeaderButton,
168
-    });
169
-    this.state = {
170
-      dialogVisible: false,
171
+      return (
172
+        <MaterialHeaderButtons>
173
+          <Item
174
+            title={'log'}
175
+            iconName={logIcon}
176
+            color={logColor}
177
+            onPress={onPressLog}
178
+          />
179
+          <Item
180
+            title={i18n.t('screens.settings.title')}
181
+            iconName={'cog'}
182
+            onPress={() => navigation.navigate(MainRoutes.Settings)}
183
+          />
184
+        </MaterialHeaderButtons>
185
+      );
171 186
     };
172
-  }
173
-
174
-  componentDidMount() {
175
-    const { props } = this;
176
-    props.navigation.addListener('focus', this.onScreenFocus);
177
-    // Handle link open when home is focused
178
-    props.navigation.addListener('state', this.handleNavigationParams);
179
-  }
180
-
181
-  /**
182
-   * Updates login state and navigation parameters on screen focus
183
-   */
184
-  onScreenFocus = () => {
185
-    const { props } = this;
186
-    if (ConnectionManager.getInstance().isLoggedIn() !== this.isLoggedIn) {
187
-      this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
188
-      props.navigation.setOptions({
189
-        headerRight: this.getHeaderButton,
190
-      });
191
-    }
192
-    // handle link open when home is not focused or created
193
-    this.handleNavigationParams();
194
-  };
195
-
196
-  /**
197
-   * Gets header buttons based on login state
198
-   *
199
-   * @returns {*}
200
-   */
201
-  getHeaderButton = () => {
202
-    const { props } = this;
203
-    let onPressLog = (): void =>
204
-      props.navigation.navigate('login', { nextScreen: 'profile' });
205
-    let logIcon = 'login';
206
-    let logColor = props.theme.colors.primary;
207
-    if (this.isLoggedIn) {
208
-      onPressLog = (): void => this.showDisconnectDialog();
209
-      logIcon = 'logout';
210
-      logColor = props.theme.colors.text;
211
-    }
187
+    navigation.setOptions({
188
+      headerRight: getHeaderButton,
189
+    });
190
+    // eslint-disable-next-line react-hooks/exhaustive-deps
191
+  }, [navigation, isLoggedIn]);
192
+
193
+  useFocusEffect(
194
+    React.useCallback(() => {
195
+      const handleNavigationParams = () => {
196
+        const { route } = props;
197
+        if (route.params != null) {
198
+          if (route.params.nextScreen != null) {
199
+            navigation.navigate(route.params.nextScreen, route.params.data);
200
+            // reset params to prevent infinite loop
201
+            navigation.dispatch(CommonActions.setParams({ nextScreen: null }));
202
+          }
203
+        }
204
+      };
212 205
 
213
-    const onPressSettings = (): void => props.navigation.navigate('settings');
214
-    return (
215
-      <MaterialHeaderButtons>
216
-        <Item
217
-          title="log"
218
-          iconName={logIcon}
219
-          color={logColor}
220
-          onPress={onPressLog}
221
-        />
222
-        <Item
223
-          title={i18n.t('screens.settings.title')}
224
-          iconName="cog"
225
-          onPress={onPressSettings}
226
-        />
227
-      </MaterialHeaderButtons>
228
-    );
229
-  };
206
+      if (ConnectionManager.getInstance().isLoggedIn() !== isLoggedIn) {
207
+        setIsLoggedIn(ConnectionManager.getInstance().isLoggedIn());
208
+      }
209
+      // handle link open when home is not focused or created
210
+      handleNavigationParams();
211
+      return () => {};
212
+      // eslint-disable-next-line react-hooks/exhaustive-deps
213
+    }, [isLoggedIn])
214
+  );
230 215
 
231 216
   /**
232 217
    * Gets the event dashboard render item.
@@ -235,7 +220,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
235 220
    * @param content
236 221
    * @return {*}
237 222
    */
238
-  getDashboardEvent(content: Array<PlanningEventType>) {
223
+  const getDashboardEvent = (content: Array<PlanningEventType>) => {
239 224
     const futureEvents = getFutureEvents(content);
240 225
     const displayEvent = getDisplayEvent(futureEvents);
241 226
     // const clickPreviewAction = () =>
@@ -246,15 +231,15 @@ class HomeScreen extends React.Component<PropsType, StateType> {
246 231
     return (
247 232
       <DashboardItem
248 233
         eventNumber={futureEvents.length}
249
-        clickAction={this.onEventContainerClick}
234
+        clickAction={onEventContainerClick}
250 235
       >
251 236
         <PreviewEventDashboardItem
252 237
           event={displayEvent}
253
-          clickAction={this.onEventContainerClick}
238
+          clickAction={onEventContainerClick}
254 239
         />
255 240
       </DashboardItem>
256 241
     );
257
-  }
242
+  };
258 243
 
259 244
   /**
260 245
    * Gets a dashboard item with a row of shortcut buttons.
@@ -262,16 +247,16 @@ class HomeScreen extends React.Component<PropsType, StateType> {
262 247
    * @param content
263 248
    * @return {*}
264 249
    */
265
-  getDashboardRow(content: Array<ServiceItemType | null>) {
250
+  const getDashboardRow = (content: Array<ServiceItemType | null>) => {
266 251
     return (
267 252
       <FlatList
268 253
         data={content}
269
-        renderItem={this.getDashboardRowRenderItem}
254
+        renderItem={getDashboardRowRenderItem}
270 255
         horizontal
271 256
         contentContainerStyle={styles.dashboardRow}
272 257
       />
273 258
     );
274
-  }
259
+  };
275 260
 
276 261
   /**
277 262
    * Gets a dashboard shortcut item
@@ -279,15 +264,19 @@ class HomeScreen extends React.Component<PropsType, StateType> {
279 264
    * @param item
280 265
    * @returns {*}
281 266
    */
282
-  getDashboardRowRenderItem = ({ item }: { item: ServiceItemType | null }) => {
267
+  const getDashboardRowRenderItem = ({
268
+    item,
269
+  }: {
270
+    item: ServiceItemType | null;
271
+  }) => {
283 272
     if (item != null) {
284 273
       return (
285 274
         <SmallDashboardItem
286 275
           image={item.image}
287 276
           onPress={item.onPress}
288 277
           badgeCount={
289
-            this.currentDashboard != null && item.badgeFunction != null
290
-              ? item.badgeFunction(this.currentDashboard)
278
+            homeDashboard != null && item.badgeFunction != null
279
+              ? item.badgeFunction(homeDashboard)
291 280
               : undefined
292 281
           }
293 282
         />
@@ -296,29 +285,13 @@ class HomeScreen extends React.Component<PropsType, StateType> {
296 285
     return <SmallDashboardItem />;
297 286
   };
298 287
 
299
-  /**
300
-   * Gets a render item for the given feed object
301
-   *
302
-   * @param item The feed item to display
303
-   * @return {*}
304
-   */
305
-  getFeedItem(item: FeedItemType) {
306
-    return <FeedItem item={item} height={FEED_ITEM_HEIGHT} />;
307
-  }
308
-
309
-  /**
310
-   * Gets a FlatList render item
311
-   *
312
-   * @param item The item to display
313
-   * @param section The current section
314
-   * @return {*}
315
-   */
316
-  getRenderItem = ({ item }: { item: FeedItemType }) => this.getFeedItem(item);
288
+  const getRenderItem = ({ item }: { item: FeedItemType }) => (
289
+    <FeedItem item={item} height={FEED_ITEM_HEIGHT} />
290
+  );
317 291
 
318
-  getRenderSectionHeader = (data: {
292
+  const getRenderSectionHeader = (data: {
319 293
     section: SectionListData<FeedItemType>;
320 294
   }) => {
321
-    const { props } = this;
322 295
     const icon = data.section.icon;
323 296
     if (data.section.data.length > 0) {
324 297
       return (
@@ -330,7 +303,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
330 303
         <Headline
331 304
           style={{
332 305
             ...styles.sectionHeaderEmpty,
333
-            color: props.theme.colors.textDisabled,
306
+            color: theme.colors.textDisabled,
334 307
           }}
335 308
         >
336 309
           {data.section.title}
@@ -339,7 +312,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
339 312
           <MaterialCommunityIcons
340 313
             name={icon}
341 314
             size={100}
342
-            color={props.theme.colors.textDisabled}
315
+            color={theme.colors.textDisabled}
343 316
             style={GENERAL_STYLES.center}
344 317
           />
345 318
         ) : null}
@@ -347,7 +320,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
347 320
     );
348 321
   };
349 322
 
350
-  getListHeader = (fetchedData: RawDashboardType | undefined) => {
323
+  const getListHeader = (fetchedData: RawDashboardType | undefined) => {
351 324
     let dashboard = null;
352 325
     if (fetchedData != null) {
353 326
       dashboard = fetchedData.dashboard;
@@ -355,41 +328,17 @@ class HomeScreen extends React.Component<PropsType, StateType> {
355 328
     return (
356 329
       <Animatable.View animation="fadeInDown" duration={500} useNativeDriver>
357 330
         <ActionsDashBoardItem />
358
-        {this.getDashboardRow(this.dashboardManager.getCurrentDashboard())}
359
-        {this.getDashboardEvent(
360
-          dashboard == null ? [] : dashboard.today_events
361
-        )}
331
+        {getDashboardRow(currentDashboard)}
332
+        {getDashboardEvent(dashboard == null ? [] : dashboard.today_events)}
362 333
       </Animatable.View>
363 334
     );
364 335
   };
365 336
 
366
-  /**
367
-   * Navigates to the a new screen if navigation parameters specify one
368
-   */
369
-  handleNavigationParams = () => {
370
-    const { props } = this;
371
-    if (props.route.params != null) {
372
-      if (props.route.params.nextScreen != null) {
373
-        props.navigation.navigate(
374
-          props.route.params.nextScreen,
375
-          props.route.params.data
376
-        );
377
-        // reset params to prevent infinite loop
378
-        props.navigation.dispatch(
379
-          CommonActions.setParams({ nextScreen: null })
380
-        );
381
-      }
382
-    }
383
-  };
337
+  const showDisconnectDialog = () => setDialogVisible(true);
384 338
 
385
-  showDisconnectDialog = (): void => this.setState({ dialogVisible: true });
339
+  const hideDisconnectDialog = () => setDialogVisible(false);
386 340
 
387
-  hideDisconnectDialog = (): void => this.setState({ dialogVisible: false });
388
-
389
-  openScanner = () => {
390
-    const { props } = this;
391
-    props.navigation.navigate('scanner');
392
-  };
341
+  const openScanner = () => navigation.navigate('scanner');
393 342
 
394 343
   /**
395 344
    * Creates the dataset to be used in the FlatList
@@ -398,7 +347,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
398 347
    * @param isLoading
399 348
    * @return {*}
400 349
    */
401
-  createDataset = (
350
+  const createDataset = (
402 351
     fetchedData: RawDashboardType | undefined,
403 352
     isLoading: boolean
404 353
   ): Array<{
@@ -407,21 +356,20 @@ class HomeScreen extends React.Component<PropsType, StateType> {
407 356
     icon?: string;
408 357
     id: string;
409 358
   }> => {
359
+    let currentNewFeed: Array<FeedItemType> = [];
410 360
     if (fetchedData) {
411 361
       if (fetchedData.news_feed) {
412
-        this.currentNewFeed = HomeScreen.generateNewsFeed(
413
-          fetchedData.news_feed
414
-        );
362
+        currentNewFeed = generateNewsFeed(fetchedData.news_feed);
415 363
       }
416 364
       if (fetchedData.dashboard) {
417
-        this.currentDashboard = fetchedData.dashboard;
365
+        homeDashboard = fetchedData.dashboard;
418 366
       }
419 367
     }
420
-    if (this.currentNewFeed.length > 0) {
368
+    if (currentNewFeed.length > 0) {
421 369
       return [
422 370
         {
423 371
           title: i18n.t('screens.home.feedTitle'),
424
-          data: this.currentNewFeed,
372
+          data: currentNewFeed,
425 373
           id: SECTIONS_ID[1],
426 374
         },
427 375
       ];
@@ -438,14 +386,11 @@ class HomeScreen extends React.Component<PropsType, StateType> {
438 386
     ];
439 387
   };
440 388
 
441
-  onEventContainerClick = () => {
442
-    const { props } = this;
443
-    props.navigation.navigate('planning');
444
-  };
389
+  const onEventContainerClick = () => navigation.navigate(TabRoutes.Planning);
445 390
 
446
-  onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
447
-    if (this.fabRef.current) {
448
-      this.fabRef.current.onScroll(event);
391
+  const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
392
+    if (fabRef.current) {
393
+      fabRef.current.onScroll(event);
449 394
     }
450 395
   };
451 396
 
@@ -453,63 +398,50 @@ class HomeScreen extends React.Component<PropsType, StateType> {
453 398
    * Callback when pressing the login button on the banner.
454 399
    * This hides the banner and takes the user to the login page.
455 400
    */
456
-  onLogin = () => {
457
-    const { props } = this;
458
-    props.navigation.navigate('login', {
401
+  const onLogin = () =>
402
+    navigation.navigate(MainRoutes.Login, {
459 403
       nextScreen: 'profile',
460 404
     });
461
-  };
462 405
 
463
-  render() {
464
-    const { props, state } = this;
465
-    return (
466
-      <View style={GENERAL_STYLES.flex}>
467
-        <View style={styles.content}>
468
-          <WebSectionList
469
-            request={() => readData<RawDashboardType>(Urls.app.dashboard)}
470
-            createDataset={this.createDataset}
471
-            autoRefreshTime={REFRESH_TIME}
472
-            refreshOnFocus={true}
473
-            renderItem={this.getRenderItem}
474
-            itemHeight={FEED_ITEM_HEIGHT}
475
-            onScroll={this.onScroll}
476
-            renderSectionHeader={this.getRenderSectionHeader}
477
-            renderListHeaderComponent={this.getListHeader}
478
-          />
479
-        </View>
480
-        {!this.isLoggedIn ? (
481
-          <MascotPopup
482
-            prefKey={AsyncStorageManager.PREFERENCES.homeShowMascot.key}
483
-            title={i18n.t('screens.home.mascotDialog.title')}
484
-            message={i18n.t('screens.home.mascotDialog.message')}
485
-            icon="human-greeting"
486
-            buttons={{
487
-              action: {
488
-                message: i18n.t('screens.home.mascotDialog.login'),
489
-                icon: 'login',
490
-                onPress: this.onLogin,
491
-              },
492
-              cancel: {
493
-                message: i18n.t('screens.home.mascotDialog.later'),
494
-                icon: 'close',
495
-                color: props.theme.colors.warning,
496
-              },
497
-            }}
498
-            emotion={MASCOT_STYLE.CUTE}
499
-          />
500
-        ) : null}
501
-        <AnimatedFAB
502
-          ref={this.fabRef}
503
-          icon="qrcode-scan"
504
-          onPress={this.openScanner}
505
-        />
506
-        <LogoutDialog
507
-          visible={state.dialogVisible}
508
-          onDismiss={this.hideDisconnectDialog}
406
+  return (
407
+    <View style={GENERAL_STYLES.flex}>
408
+      <View style={styles.content}>
409
+        <WebSectionList