Use context to handle preferences
This is not tested, expect crashes
This commit is contained in:
parent
b5d4ad83c3
commit
00f9428972
39 changed files with 1238 additions and 1949 deletions
130
App.tsx
130
App.tsx
|
@ -18,27 +18,21 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { LogBox, Platform, SafeAreaView, View } from 'react-native';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { Provider as PaperProvider } from 'react-native-paper';
|
||||
import { LogBox, Platform } from 'react-native';
|
||||
import { setSafeBounceHeight } from 'react-navigation-collapsible';
|
||||
import SplashScreen from 'react-native-splash-screen';
|
||||
import { OverflowMenuProvider } from 'react-navigation-header-buttons';
|
||||
import AsyncStorageManager from './src/managers/AsyncStorageManager';
|
||||
import CustomIntroSlider from './src/components/Overrides/CustomIntroSlider';
|
||||
import ThemeManager from './src/managers/ThemeManager';
|
||||
import MainNavigator from './src/navigation/MainNavigator';
|
||||
import AprilFoolsManager from './src/managers/AprilFoolsManager';
|
||||
import Update from './src/constants/Update';
|
||||
import ConnectionManager from './src/managers/ConnectionManager';
|
||||
import type { ParsedUrlDataType } from './src/utils/URLHandler';
|
||||
import URLHandler from './src/utils/URLHandler';
|
||||
import { setupStatusBar } from './src/utils/Utils';
|
||||
import initLocales from './src/utils/Locales';
|
||||
import { NavigationContainerRef } from '@react-navigation/core';
|
||||
import GENERAL_STYLES from './src/constants/Styles';
|
||||
import CollapsibleProvider from './src/components/providers/CollapsibleProvider';
|
||||
import CacheProvider from './src/components/providers/CacheProvider';
|
||||
import {
|
||||
defaultPreferences,
|
||||
PreferenceKeys,
|
||||
retrievePreferences,
|
||||
} from './src/utils/asyncStorage';
|
||||
import PreferencesProvider from './src/components/providers/PreferencesProvider';
|
||||
import MainApp from './src/screens/MainApp';
|
||||
|
||||
// Native optimizations https://reactnavigation.org/docs/react-native-screens
|
||||
// Crashes app when navigating away from webview on android 9+
|
||||
|
@ -52,10 +46,6 @@ LogBox.ignoreLogs([
|
|||
|
||||
type StateType = {
|
||||
isLoading: boolean;
|
||||
showIntro: boolean;
|
||||
showUpdate: boolean;
|
||||
showAprilFools: boolean;
|
||||
currentTheme: ReactNativePaper.Theme | undefined;
|
||||
};
|
||||
|
||||
export default class App extends React.Component<{}, StateType> {
|
||||
|
@ -71,10 +61,6 @@ export default class App extends React.Component<{}, StateType> {
|
|||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
showIntro: true,
|
||||
showUpdate: true,
|
||||
showAprilFools: false,
|
||||
currentTheme: undefined,
|
||||
};
|
||||
initLocales();
|
||||
this.navigatorRef = React.createRef();
|
||||
|
@ -114,67 +100,12 @@ export default class App extends React.Component<{}, StateType> {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the current theme
|
||||
*/
|
||||
onUpdateTheme = () => {
|
||||
this.setState({
|
||||
currentTheme: ThemeManager.getCurrentTheme(),
|
||||
});
|
||||
setupStatusBar();
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback when user ends the intro. Save in preferences to avoid showing back the introSlides
|
||||
*/
|
||||
onIntroDone = () => {
|
||||
this.setState({
|
||||
showIntro: false,
|
||||
showUpdate: false,
|
||||
showAprilFools: false,
|
||||
});
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.showIntro.key,
|
||||
false
|
||||
);
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.updateNumber.key,
|
||||
Update.number
|
||||
);
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.showAprilFoolsStart.key,
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Async loading is done, finish processing startup data
|
||||
*/
|
||||
onLoadFinished = () => {
|
||||
// Only show intro if this is the first time starting the app
|
||||
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
|
||||
// Status bar goes dark if set too fast on ios
|
||||
if (Platform.OS === 'ios') {
|
||||
setTimeout(setupStatusBar, 1000);
|
||||
} else {
|
||||
setupStatusBar();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
currentTheme: ThemeManager.getCurrentTheme(),
|
||||
showIntro: AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.showIntro.key
|
||||
),
|
||||
showUpdate:
|
||||
AsyncStorageManager.getNumber(
|
||||
AsyncStorageManager.PREFERENCES.updateNumber.key
|
||||
) !== Update.number,
|
||||
showAprilFools:
|
||||
AprilFoolsManager.getInstance().isAprilFoolsEnabled() &&
|
||||
AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.showAprilFoolsStart.key
|
||||
),
|
||||
});
|
||||
SplashScreen.hide();
|
||||
};
|
||||
|
@ -186,7 +117,7 @@ export default class App extends React.Component<{}, StateType> {
|
|||
*/
|
||||
loadAssetsAsync() {
|
||||
Promise.all([
|
||||
AsyncStorageManager.getInstance().loadPreferences(),
|
||||
retrievePreferences(Object.values(PreferenceKeys), defaultPreferences),
|
||||
ConnectionManager.getInstance().recoverLogin(),
|
||||
])
|
||||
.then(this.onLoadFinished)
|
||||
|
@ -201,43 +132,14 @@ export default class App extends React.Component<{}, StateType> {
|
|||
if (state.isLoading) {
|
||||
return null;
|
||||
}
|
||||
if (state.showIntro || state.showUpdate || state.showAprilFools) {
|
||||
return (
|
||||
<CustomIntroSlider
|
||||
onDone={this.onIntroDone}
|
||||
isUpdate={state.showUpdate && !state.showIntro}
|
||||
isAprilFools={state.showAprilFools && !state.showIntro}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PaperProvider theme={state.currentTheme}>
|
||||
<CollapsibleProvider>
|
||||
<CacheProvider>
|
||||
<OverflowMenuProvider>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: ThemeManager.getCurrentTheme().colors
|
||||
.background,
|
||||
...GENERAL_STYLES.flex,
|
||||
}}
|
||||
>
|
||||
<SafeAreaView style={GENERAL_STYLES.flex}>
|
||||
<NavigationContainer
|
||||
theme={state.currentTheme}
|
||||
ref={this.navigatorRef}
|
||||
>
|
||||
<MainNavigator
|
||||
defaultHomeRoute={this.defaultHomeRoute}
|
||||
defaultHomeData={this.defaultHomeData}
|
||||
/>
|
||||
</NavigationContainer>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</OverflowMenuProvider>
|
||||
</CacheProvider>
|
||||
</CollapsibleProvider>
|
||||
</PaperProvider>
|
||||
<PreferencesProvider initialPreferences={defaultPreferences}>
|
||||
<MainApp
|
||||
ref={this.navigatorRef}
|
||||
defaultHomeData={this.defaultHomeData}
|
||||
defaultHomeRoute={this.defaultHomeRoute}
|
||||
/>
|
||||
</PreferencesProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import * as React from 'react';
|
|||
import { Animated, Dimensions, ViewStyle } from 'react-native';
|
||||
import ImageListItem from './ImageListItem';
|
||||
import CardListItem from './CardListItem';
|
||||
import type { ServiceItemType } from '../../../managers/ServicesManager';
|
||||
import { ServiceItemType } from '../../../utils/Services';
|
||||
|
||||
type PropsType = {
|
||||
dataset: Array<ServiceItemType>;
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
import * as React from 'react';
|
||||
import { Caption, Card, Paragraph, TouchableRipple } from 'react-native-paper';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import type { ServiceItemType } from '../../../managers/ServicesManager';
|
||||
import GENERAL_STYLES from '../../../constants/Styles';
|
||||
import { ServiceItemType } from '../../../utils/Services';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceItemType;
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
import * as React from 'react';
|
||||
import { Text, TouchableRipple } from 'react-native-paper';
|
||||
import { Image, StyleSheet, View } from 'react-native';
|
||||
import type { ServiceItemType } from '../../../managers/ServicesManager';
|
||||
import GENERAL_STYLES from '../../../constants/Styles';
|
||||
import { ServiceItemType } from '../../../utils/Services';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceItemType;
|
||||
|
|
|
@ -23,10 +23,7 @@ import { FlatList, Image, StyleSheet, View } from 'react-native';
|
|||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import DashboardEditItem from './DashboardEditItem';
|
||||
import AnimatedAccordion from '../../Animations/AnimatedAccordion';
|
||||
import type {
|
||||
ServiceCategoryType,
|
||||
ServiceItemType,
|
||||
} from '../../../managers/ServicesManager';
|
||||
import { ServiceCategoryType, ServiceItemType } from '../../../utils/Services';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceCategoryType;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import * as React from 'react';
|
||||
import { Image, StyleSheet } from 'react-native';
|
||||
import { List, useTheme } from 'react-native-paper';
|
||||
import type { ServiceItemType } from '../../../managers/ServicesManager';
|
||||
import { ServiceItemType } from '../../../utils/Services';
|
||||
|
||||
type PropsType = {
|
||||
item: ServiceItemType;
|
||||
|
|
|
@ -28,17 +28,17 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import Mascot from './Mascot';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import GENERAL_STYLES from '../../constants/Styles';
|
||||
import MascotSpeechBubble, {
|
||||
MascotSpeechBubbleProps,
|
||||
} from './MascotSpeechBubble';
|
||||
import { useMountEffect } from '../../utils/customHooks';
|
||||
import { useRoute } from '@react-navigation/core';
|
||||
import { useShouldShowMascot } from '../../context/preferencesContext';
|
||||
|
||||
type PropsType = MascotSpeechBubbleProps & {
|
||||
emotion: number;
|
||||
visible?: boolean;
|
||||
prefKey?: string;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -61,13 +61,14 @@ const BUBBLE_HEIGHT = Dimensions.get('window').height / 3;
|
|||
* Component used to display a popup with the mascot.
|
||||
*/
|
||||
function MascotPopup(props: PropsType) {
|
||||
const route = useRoute();
|
||||
const { shouldShow, setShouldShow } = useShouldShowMascot(route.name);
|
||||
|
||||
const isVisible = () => {
|
||||
if (props.visible !== undefined) {
|
||||
return props.visible;
|
||||
} else if (props.prefKey != null) {
|
||||
return AsyncStorageManager.getBool(props.prefKey);
|
||||
} else {
|
||||
return false;
|
||||
return shouldShow;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -164,10 +165,8 @@ function MascotPopup(props: PropsType) {
|
|||
};
|
||||
|
||||
const onDismiss = (callback?: () => void) => {
|
||||
if (props.prefKey != null) {
|
||||
AsyncStorageManager.set(props.prefKey, false);
|
||||
setDialogVisible(false);
|
||||
}
|
||||
setShouldShow(false);
|
||||
setDialogVisible(false);
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import LinearGradient from 'react-native-linear-gradient';
|
|||
import * as Animatable from 'react-native-animatable';
|
||||
import { Card } from 'react-native-paper';
|
||||
import Update from '../../constants/Update';
|
||||
import ThemeManager from '../../managers/ThemeManager';
|
||||
import Mascot, { MASCOT_STYLE } from '../Mascot/Mascot';
|
||||
import MascotIntroWelcome from '../Intro/MascotIntroWelcome';
|
||||
import IntroIcon from '../Intro/IconIntro';
|
||||
|
@ -289,9 +288,6 @@ export default class CustomIntroSlider extends React.Component<
|
|||
|
||||
onDone = () => {
|
||||
const { props } = this;
|
||||
CustomIntroSlider.setStatusBarColor(
|
||||
ThemeManager.getCurrentTheme().colors.surface
|
||||
);
|
||||
props.onDone();
|
||||
};
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ import { StyleSheet, View } from 'react-native';
|
|||
import GENERAL_STYLES from '../../constants/Styles';
|
||||
import Urls from '../../constants/Urls';
|
||||
import DateManager from '../../managers/DateManager';
|
||||
import ThemeManager from '../../managers/ThemeManager';
|
||||
import { PlanexGroupType } from '../../screens/Planex/GroupSelectionScreen';
|
||||
import ErrorView from './ErrorView';
|
||||
import WebViewScreen from './WebViewScreen';
|
||||
import i18n from 'i18n-js';
|
||||
import { useTheme } from 'react-native-paper';
|
||||
|
||||
type Props = {
|
||||
currentGroup?: PlanexGroupType;
|
||||
|
@ -86,7 +86,10 @@ const INJECT_STYLE_DARK = `$('head').append('<style>${CUSTOM_CSS_DARK}</style>')
|
|||
*
|
||||
* @param groupID The current group selected
|
||||
*/
|
||||
const generateInjectedJS = (group: PlanexGroupType | undefined) => {
|
||||
const generateInjectedJS = (
|
||||
group: PlanexGroupType | undefined,
|
||||
darkMode: boolean
|
||||
) => {
|
||||
let customInjectedJS = `$(document).ready(function() {
|
||||
${OBSERVE_MUTATIONS_INJECTED}
|
||||
${INJECT_STYLE}
|
||||
|
@ -97,7 +100,7 @@ const generateInjectedJS = (group: PlanexGroupType | undefined) => {
|
|||
if (DateManager.isWeekend(new Date())) {
|
||||
customInjectedJS += `calendar.next();`;
|
||||
}
|
||||
if (ThemeManager.getNightMode()) {
|
||||
if (darkMode) {
|
||||
customInjectedJS += INJECT_STYLE_DARK;
|
||||
}
|
||||
customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
|
||||
|
@ -105,11 +108,12 @@ const generateInjectedJS = (group: PlanexGroupType | undefined) => {
|
|||
};
|
||||
|
||||
function PlanexWebview(props: Props) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<View style={GENERAL_STYLES.flex}>
|
||||
<WebViewScreen
|
||||
url={Urls.planex.planning}
|
||||
initialJS={generateInjectedJS(props.currentGroup)}
|
||||
initialJS={generateInjectedJS(props.currentGroup, theme.dark)}
|
||||
injectJS={props.injectJS}
|
||||
onMessage={props.onMessage}
|
||||
showAdvancedControls={false}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import React, { useContext } from 'react';
|
||||
import {
|
||||
defaultPreferences,
|
||||
PreferenceKeys,
|
||||
PreferencesType,
|
||||
} from '../utils/asyncStorage';
|
||||
|
||||
export type PreferencesContextType = {
|
||||
preferences: PreferencesType;
|
||||
updatePreferences: (
|
||||
key: PreferenceKeys,
|
||||
value: number | string | boolean | object | Array<any>
|
||||
) => void;
|
||||
resetPreferences: () => void;
|
||||
};
|
||||
|
||||
export const PreferencesContext = React.createContext<PreferencesContextType>({
|
||||
preferences: defaultPreferences,
|
||||
updatePreferences: () => undefined,
|
||||
resetPreferences: () => undefined,
|
||||
});
|
||||
|
||||
export function usePreferences() {
|
||||
return useContext(PreferencesContext);
|
||||
}
|
97
src/context/preferencesContext.tsx
Normal file
97
src/context/preferencesContext.tsx
Normal file
|
@ -0,0 +1,97 @@
|
|||
import { useNavigation } from '@react-navigation/core';
|
||||
import React, { useContext } from 'react';
|
||||
import { Appearance } from 'react-native-appearance';
|
||||
import {
|
||||
defaultPreferences,
|
||||
getPreferenceBool,
|
||||
getPreferenceObject,
|
||||
isValidPreferenceKey,
|
||||
PreferenceKeys,
|
||||
PreferencesType,
|
||||
} from '../utils/asyncStorage';
|
||||
import {
|
||||
getAmicaleServices,
|
||||
getINSAServices,
|
||||
getSpecialServices,
|
||||
getStudentServices,
|
||||
} from '../utils/Services';
|
||||
|
||||
const colorScheme = Appearance.getColorScheme();
|
||||
|
||||
export type PreferencesContextType = {
|
||||
preferences: PreferencesType;
|
||||
updatePreferences: (
|
||||
key: PreferenceKeys,
|
||||
value: number | string | boolean | object | Array<any>
|
||||
) => void;
|
||||
resetPreferences: () => void;
|
||||
};
|
||||
|
||||
export const PreferencesContext = React.createContext<PreferencesContextType>({
|
||||
preferences: defaultPreferences,
|
||||
updatePreferences: () => undefined,
|
||||
resetPreferences: () => undefined,
|
||||
});
|
||||
|
||||
export function usePreferences() {
|
||||
return useContext(PreferencesContext);
|
||||
}
|
||||
|
||||
export function useShouldShowMascot(route: string) {
|
||||
const { preferences, updatePreferences } = usePreferences();
|
||||
const key = route + 'ShowMascot';
|
||||
let shouldShow = false;
|
||||
if (isValidPreferenceKey(key)) {
|
||||
shouldShow = getPreferenceBool(key, preferences) !== false;
|
||||
}
|
||||
|
||||
const setShouldShow = (show: boolean) => {
|
||||
if (isValidPreferenceKey(key)) {
|
||||
updatePreferences(key, show);
|
||||
} else {
|
||||
console.log('Invalid preference key: ' + key);
|
||||
}
|
||||
};
|
||||
|
||||
return { shouldShow, setShouldShow };
|
||||
}
|
||||
|
||||
export function useDarkTheme() {
|
||||
const { preferences } = usePreferences();
|
||||
return (
|
||||
(getPreferenceBool(PreferenceKeys.nightMode, preferences) !== false &&
|
||||
(getPreferenceBool(PreferenceKeys.nightModeFollowSystem, preferences) ===
|
||||
false ||
|
||||
colorScheme === 'no-preference')) ||
|
||||
(getPreferenceBool(PreferenceKeys.nightModeFollowSystem, preferences) !==
|
||||
false &&
|
||||
colorScheme === 'dark')
|
||||
);
|
||||
}
|
||||
|
||||
export function useCurrentDashboard() {
|
||||
const { preferences, updatePreferences } = usePreferences();
|
||||
const navigation = useNavigation();
|
||||
const dashboardIdList = getPreferenceObject(
|
||||
PreferenceKeys.dashboardItems,
|
||||
preferences
|
||||
) as Array<string>;
|
||||
|
||||
const updateCurrentDashboard = (newList: Array<string>) => {
|
||||
updatePreferences(PreferenceKeys.dashboardItems, newList);
|
||||
};
|
||||
|
||||
const allDatasets = [
|
||||
...getAmicaleServices(navigation.navigate),
|
||||
...getStudentServices(navigation.navigate),
|
||||
...getINSAServices(navigation.navigate),
|
||||
...getSpecialServices(navigation.navigate),
|
||||
];
|
||||
return {
|
||||
currentDashboard: allDatasets.filter((item) =>
|
||||
dashboardIdList.includes(item.key)
|
||||
),
|
||||
currentDashboardIdList: dashboardIdList,
|
||||
updateCurrentDashboard: updateCurrentDashboard,
|
||||
};
|
||||
}
|
|
@ -1,269 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { SERVICES_KEY } from './ServicesManager';
|
||||
|
||||
/**
|
||||
* Singleton used to manage preferences.
|
||||
* Preferences are fetched at the start of the app and saved in an instance object.
|
||||
* This allows for a synchronous access to saved data.
|
||||
*/
|
||||
|
||||
export default class AsyncStorageManager {
|
||||
static instance: AsyncStorageManager | null = null;
|
||||
|
||||
static PREFERENCES: { [key: string]: { key: string; default: string } } = {
|
||||
debugUnlocked: {
|
||||
key: 'debugUnlocked',
|
||||
default: '0',
|
||||
},
|
||||
showIntro: {
|
||||
key: 'showIntro',
|
||||
default: '1',
|
||||
},
|
||||
updateNumber: {
|
||||
key: 'updateNumber',
|
||||
default: '0',
|
||||
},
|
||||
proxiwashNotifications: {
|
||||
key: 'proxiwashNotifications',
|
||||
default: '5',
|
||||
},
|
||||
nightModeFollowSystem: {
|
||||
key: 'nightModeFollowSystem',
|
||||
default: '1',
|
||||
},
|
||||
nightMode: {
|
||||
key: 'nightMode',
|
||||
default: '1',
|
||||
},
|
||||
defaultStartScreen: {
|
||||
key: 'defaultStartScreen',
|
||||
default: 'home',
|
||||
},
|
||||
servicesShowMascot: {
|
||||
key: 'servicesShowMascot',
|
||||
default: '1',
|
||||
},
|
||||
proxiwashShowMascot: {
|
||||
key: 'proxiwashShowMascot',
|
||||
default: '1',
|
||||
},
|
||||
homeShowMascot: {
|
||||
key: 'homeShowMascot',
|
||||
default: '1',
|
||||
},
|
||||
eventsShowMascot: {
|
||||
key: 'eventsShowMascot',
|
||||
default: '1',
|
||||
},
|
||||
planexShowMascot: {
|
||||
key: 'planexShowMascot',
|
||||
default: '1',
|
||||
},
|
||||
loginShowMascot: {
|
||||
key: 'loginShowMascot',
|
||||
default: '1',
|
||||
},
|
||||
voteShowMascot: {
|
||||
key: 'voteShowMascot',
|
||||
default: '1',
|
||||
},
|
||||
equipmentShowMascot: {
|
||||
key: 'equipmentShowMascot',
|
||||
default: '1',
|
||||
},
|
||||
gameStartMascot: {
|
||||
key: 'gameStartMascot',
|
||||
default: '1',
|
||||
},
|
||||
proxiwashWatchedMachines: {
|
||||
key: 'proxiwashWatchedMachines',
|
||||
default: '[]',
|
||||
},
|
||||
showAprilFoolsStart: {
|
||||
key: 'showAprilFoolsStart',
|
||||
default: '1',
|
||||
},
|
||||
planexCurrentGroup: {
|
||||
key: 'planexCurrentGroup',
|
||||
default: '',
|
||||
},
|
||||
planexFavoriteGroups: {
|
||||
key: 'planexFavoriteGroups',
|
||||
default: '[]',
|
||||
},
|
||||
dashboardItems: {
|
||||
key: 'dashboardItems',
|
||||
default: JSON.stringify([
|
||||
SERVICES_KEY.EMAIL,
|
||||
SERVICES_KEY.WASHERS,
|
||||
SERVICES_KEY.PROXIMO,
|
||||
SERVICES_KEY.TUTOR_INSA,
|
||||
SERVICES_KEY.RU,
|
||||
]),
|
||||
},
|
||||
gameScores: {
|
||||
key: 'gameScores',
|
||||
default: '[]',
|
||||
},
|
||||
selectedWash: {
|
||||
key: 'selectedWash',
|
||||
default: 'washinsa',
|
||||
},
|
||||
};
|
||||
|
||||
private currentPreferences: { [key: string]: string };
|
||||
|
||||
constructor() {
|
||||
this.currentPreferences = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this class instance or create one if none is found
|
||||
* @returns {AsyncStorageManager}
|
||||
*/
|
||||
static getInstance(): AsyncStorageManager {
|
||||
if (AsyncStorageManager.instance == null) {
|
||||
AsyncStorageManager.instance = new AsyncStorageManager();
|
||||
}
|
||||
return AsyncStorageManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the value associated to the given key to preferences.
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
static set(
|
||||
key: string,
|
||||
value: number | string | boolean | object | Array<any>
|
||||
) {
|
||||
AsyncStorageManager.getInstance().setPreference(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string value of the given preference
|
||||
*
|
||||
* @param key
|
||||
* @returns {string}
|
||||
*/
|
||||
static getString(key: string): string {
|
||||
const value = AsyncStorageManager.getInstance().getPreference(key);
|
||||
return value != null ? value : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the boolean value of the given preference
|
||||
*
|
||||
* @param key
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static getBool(key: string): boolean {
|
||||
const value = AsyncStorageManager.getString(key);
|
||||
return value === '1' || value === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number value of the given preference
|
||||
*
|
||||
* @param key
|
||||
* @returns {number}
|
||||
*/
|
||||
static getNumber(key: string): number {
|
||||
return parseFloat(AsyncStorageManager.getString(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object value of the given preference
|
||||
*
|
||||
* @param key
|
||||
* @returns {{...}}
|
||||
*/
|
||||
static getObject<T>(key: string): T {
|
||||
return JSON.parse(AsyncStorageManager.getString(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set preferences object current values from AsyncStorage.
|
||||
* This function should be called at the app's start.
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async loadPreferences() {
|
||||
return new Promise((resolve: (val: void) => void) => {
|
||||
const prefKeys: Array<string> = [];
|
||||
// Get all available keys
|
||||
Object.keys(AsyncStorageManager.PREFERENCES).forEach((key: string) => {
|
||||
prefKeys.push(key);
|
||||
});
|
||||
// Get corresponding values
|
||||
AsyncStorage.multiGet(prefKeys).then((resultArray) => {
|
||||
// Save those values for later use
|
||||
resultArray.forEach((item: [string, string | null]) => {
|
||||
const key = item[0];
|
||||
let val = item[1];
|
||||
if (val === null) {
|
||||
val = AsyncStorageManager.PREFERENCES[key].default;
|
||||
}
|
||||
this.currentPreferences[key] = val;
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the value associated to the given key to preferences.
|
||||
* This updates the preferences object and saves it to AsyncStorage.
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
setPreference(
|
||||
key: string,
|
||||
value: number | string | boolean | object | Array<any>
|
||||
) {
|
||||
if (AsyncStorageManager.PREFERENCES[key] != null) {
|
||||
let convertedValue;
|
||||
if (typeof value === 'string') {
|
||||
convertedValue = value;
|
||||
} else if (typeof value === 'boolean' || typeof value === 'number') {
|
||||
convertedValue = value.toString();
|
||||
} else {
|
||||
convertedValue = JSON.stringify(value);
|
||||
}
|
||||
this.currentPreferences[key] = convertedValue;
|
||||
AsyncStorage.setItem(key, convertedValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value at the given key.
|
||||
* If the key is not available, returns null
|
||||
*
|
||||
* @param key
|
||||
* @returns {string|null}
|
||||
*/
|
||||
getPreference(key: string): string | null {
|
||||
return this.currentPreferences[key];
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { ServiceItemType } from './ServicesManager';
|
||||
import ServicesManager from './ServicesManager';
|
||||
import { getSublistWithIds } from '../utils/Services';
|
||||
import AsyncStorageManager from './AsyncStorageManager';
|
||||
|
||||
export default class DashboardManager extends ServicesManager {
|
||||
getCurrentDashboard(): Array<ServiceItemType | null> {
|
||||
const dashboardIdList = AsyncStorageManager.getObject<Array<string>>(
|
||||
AsyncStorageManager.PREFERENCES.dashboardItems.key
|
||||
);
|
||||
const allDatasets = [
|
||||
...this.amicaleDataset,
|
||||
...this.studentsDataset,
|
||||
...this.insaDataset,
|
||||
...this.specialDataset,
|
||||
];
|
||||
return getSublistWithIds(dashboardIdList, allDatasets);
|
||||
}
|
||||
}
|
|
@ -1,371 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import i18n from 'i18n-js';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import ConnectionManager from './ConnectionManager';
|
||||
import type { FullDashboardType } from '../screens/Home/HomeScreen';
|
||||
import getStrippedServicesList from '../utils/Services';
|
||||
import Urls from '../constants/Urls';
|
||||
|
||||
const AMICALE_LOGO = require('../../assets/amicale.png');
|
||||
|
||||
export const SERVICES_KEY = {
|
||||
CLUBS: 'clubs',
|
||||
PROFILE: 'profile',
|
||||
EQUIPMENT: 'equipment',
|
||||
AMICALE_WEBSITE: 'amicale_website',
|
||||
VOTE: 'vote',
|
||||
PROXIMO: 'proximo',
|
||||
WIKETUD: 'wiketud',
|
||||
ELUS_ETUDIANTS: 'elus_etudiants',
|
||||
TUTOR_INSA: 'tutor_insa',
|
||||
RU: 'ru',
|
||||
AVAILABLE_ROOMS: 'available_rooms',
|
||||
BIB: 'bib',
|
||||
EMAIL: 'email',
|
||||
ENT: 'ent',
|
||||
INSA_ACCOUNT: 'insa_account',
|
||||
WASHERS: 'washers',
|
||||
DRYERS: 'dryers',
|
||||
};
|
||||
|
||||
export const SERVICES_CATEGORIES_KEY = {
|
||||
AMICALE: 'amicale',
|
||||
STUDENTS: 'students',
|
||||
INSA: 'insa',
|
||||
SPECIAL: 'special',
|
||||
};
|
||||
|
||||
export type ServiceItemType = {
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
image: string | number;
|
||||
onPress: () => void;
|
||||
badgeFunction?: (dashboard: FullDashboardType) => number;
|
||||
};
|
||||
|
||||
export type ServiceCategoryType = {
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
image: string | number;
|
||||
content: Array<ServiceItemType>;
|
||||
};
|
||||
|
||||
export default class ServicesManager {
|
||||
navigation: StackNavigationProp<any>;
|
||||
|
||||
amicaleDataset: Array<ServiceItemType>;
|
||||
|
||||
studentsDataset: Array<ServiceItemType>;
|
||||
|
||||
insaDataset: Array<ServiceItemType>;
|
||||
|
||||
specialDataset: Array<ServiceItemType>;
|
||||
|
||||
categoriesDataset: Array<ServiceCategoryType>;
|
||||
|
||||
constructor(nav: StackNavigationProp<any>) {
|
||||
this.navigation = nav;
|
||||
this.amicaleDataset = [
|
||||
{
|
||||
key: SERVICES_KEY.CLUBS,
|
||||
title: i18n.t('screens.clubs.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.clubs'),
|
||||
image: Urls.images.clubs,
|
||||
onPress: (): void => this.onAmicaleServicePress('club-list'),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.PROFILE,
|
||||
title: i18n.t('screens.profile.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.profile'),
|
||||
image: Urls.images.profile,
|
||||
onPress: (): void => this.onAmicaleServicePress('profile'),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.EQUIPMENT,
|
||||
title: i18n.t('screens.equipment.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.equipment'),
|
||||
image: Urls.images.equipment,
|
||||
onPress: (): void => this.onAmicaleServicePress('equipment-list'),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.AMICALE_WEBSITE,
|
||||
title: i18n.t('screens.websites.amicale'),
|
||||
subtitle: i18n.t('screens.services.descriptions.amicaleWebsite'),
|
||||
image: Urls.images.amicale,
|
||||
onPress: (): void =>
|
||||
nav.navigate('website', {
|
||||
host: Urls.websites.amicale,
|
||||
title: i18n.t('screens.websites.amicale'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.VOTE,
|
||||
title: i18n.t('screens.vote.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.vote'),
|
||||
image: Urls.images.vote,
|
||||
onPress: (): void => this.onAmicaleServicePress('vote'),
|
||||
},
|
||||
];
|
||||
this.studentsDataset = [
|
||||
{
|
||||
key: SERVICES_KEY.PROXIMO,
|
||||
title: i18n.t('screens.proximo.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.proximo'),
|
||||
image: Urls.images.proximo,
|
||||
onPress: (): void => nav.navigate('proximo'),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.proximo_articles,
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.WIKETUD,
|
||||
title: 'Wiketud',
|
||||
subtitle: i18n.t('screens.services.descriptions.wiketud'),
|
||||
image: Urls.images.wiketud,
|
||||
onPress: (): void =>
|
||||
nav.navigate('website', {
|
||||
host: Urls.websites.wiketud,
|
||||
title: 'Wiketud',
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.ELUS_ETUDIANTS,
|
||||
title: 'Élus Étudiants',
|
||||
subtitle: i18n.t('screens.services.descriptions.elusEtudiants'),
|
||||
image: Urls.images.elusEtudiants,
|
||||
onPress: (): void =>
|
||||
nav.navigate('website', {
|
||||
host: Urls.websites.elusEtudiants,
|
||||
title: 'Élus Étudiants',
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.TUTOR_INSA,
|
||||
title: "Tutor'INSA",
|
||||
subtitle: i18n.t('screens.services.descriptions.tutorInsa'),
|
||||
image: Urls.images.tutorInsa,
|
||||
onPress: (): void =>
|
||||
nav.navigate('website', {
|
||||
host: Urls.websites.tutorInsa,
|
||||
title: "Tutor'INSA",
|
||||
}),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.available_tutorials,
|
||||
},
|
||||
];
|
||||
this.insaDataset = [
|
||||
{
|
||||
key: SERVICES_KEY.RU,
|
||||
title: i18n.t('screens.menu.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.self'),
|
||||
image: Urls.images.menu,
|
||||
onPress: (): void => nav.navigate('self-menu'),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.today_menu.length,
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.AVAILABLE_ROOMS,
|
||||
title: i18n.t('screens.websites.rooms'),
|
||||
subtitle: i18n.t('screens.services.descriptions.availableRooms'),
|
||||
image: Urls.images.availableRooms,
|
||||
onPress: (): void =>
|
||||
nav.navigate('website', {
|
||||
host: Urls.websites.availableRooms,
|
||||
title: i18n.t('screens.websites.rooms'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.BIB,
|
||||
title: i18n.t('screens.websites.bib'),
|
||||
subtitle: i18n.t('screens.services.descriptions.bib'),
|
||||
image: Urls.images.bib,
|
||||
onPress: (): void =>
|
||||
nav.navigate('website', {
|
||||
host: Urls.websites.bib,
|
||||
title: i18n.t('screens.websites.bib'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.EMAIL,
|
||||
title: i18n.t('screens.websites.mails'),
|
||||
subtitle: i18n.t('screens.services.descriptions.mails'),
|
||||
image: Urls.images.bluemind,
|
||||
onPress: (): void =>
|
||||
nav.navigate('website', {
|
||||
host: Urls.websites.bluemind,
|
||||
title: i18n.t('screens.websites.mails'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.ENT,
|
||||
title: i18n.t('screens.websites.ent'),
|
||||
subtitle: i18n.t('screens.services.descriptions.ent'),
|
||||
image: Urls.images.ent,
|
||||
onPress: (): void =>
|
||||
nav.navigate('website', {
|
||||
host: Urls.websites.ent,
|
||||
title: i18n.t('screens.websites.ent'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.INSA_ACCOUNT,
|
||||
title: i18n.t('screens.insaAccount.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.insaAccount'),
|
||||
image: Urls.images.insaAccount,
|
||||
onPress: (): void =>
|
||||
nav.navigate('website', {
|
||||
host: Urls.websites.insaAccount,
|
||||
title: i18n.t('screens.insaAccount.title'),
|
||||
}),
|
||||
},
|
||||
];
|
||||
this.specialDataset = [
|
||||
{
|
||||
key: SERVICES_KEY.WASHERS,
|
||||
title: i18n.t('screens.proxiwash.washers'),
|
||||
subtitle: i18n.t('screens.services.descriptions.washers'),
|
||||
image: Urls.images.washer,
|
||||
onPress: (): void => nav.navigate('proxiwash'),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.available_washers,
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.DRYERS,
|
||||
title: i18n.t('screens.proxiwash.dryers'),
|
||||
subtitle: i18n.t('screens.services.descriptions.washers'),
|
||||
image: Urls.images.dryer,
|
||||
onPress: (): void => nav.navigate('proxiwash'),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.available_dryers,
|
||||
},
|
||||
];
|
||||
this.categoriesDataset = [
|
||||
{
|
||||
key: SERVICES_CATEGORIES_KEY.AMICALE,
|
||||
title: i18n.t('screens.services.categories.amicale'),
|
||||
subtitle: i18n.t('screens.services.more'),
|
||||
image: AMICALE_LOGO,
|
||||
content: this.amicaleDataset,
|
||||
},
|
||||
{
|
||||
key: SERVICES_CATEGORIES_KEY.STUDENTS,
|
||||
title: i18n.t('screens.services.categories.students'),
|
||||
subtitle: i18n.t('screens.services.more'),
|
||||
image: 'account-group',
|
||||
content: this.studentsDataset,
|
||||
},
|
||||
{
|
||||
key: SERVICES_CATEGORIES_KEY.INSA,
|
||||
title: i18n.t('screens.services.categories.insa'),
|
||||
subtitle: i18n.t('screens.services.more'),
|
||||
image: 'school',
|
||||
content: this.insaDataset,
|
||||
},
|
||||
{
|
||||
key: SERVICES_CATEGORIES_KEY.SPECIAL,
|
||||
title: i18n.t('screens.services.categories.special'),
|
||||
subtitle: i18n.t('screens.services.categories.special'),
|
||||
image: 'star',
|
||||
content: this.specialDataset,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the user to the login screen if he is not logged in
|
||||
*
|
||||
* @param route
|
||||
* @returns {null}
|
||||
*/
|
||||
onAmicaleServicePress(route: string) {
|
||||
if (ConnectionManager.getInstance().isLoggedIn()) {
|
||||
this.navigation.navigate(route);
|
||||
} else {
|
||||
this.navigation.navigate('login', { nextScreen: route });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of amicale's services
|
||||
*
|
||||
* @param excludedItems Ids of items to exclude from the returned list
|
||||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getAmicaleServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.amicaleDataset);
|
||||
}
|
||||
return this.amicaleDataset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of students' services
|
||||
*
|
||||
* @param excludedItems Ids of items to exclude from the returned list
|
||||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getStudentServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.studentsDataset);
|
||||
}
|
||||
return this.studentsDataset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of INSA's services
|
||||
*
|
||||
* @param excludedItems Ids of items to exclude from the returned list
|
||||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getINSAServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.insaDataset);
|
||||
}
|
||||
return this.insaDataset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of special services
|
||||
*
|
||||
* @param excludedItems Ids of items to exclude from the returned list
|
||||
* @returns {Array<ServiceItemType>}
|
||||
*/
|
||||
getSpecialServices(excludedItems?: Array<string>): Array<ServiceItemType> {
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.specialDataset);
|
||||
}
|
||||
return this.specialDataset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all services sorted by category
|
||||
*
|
||||
* @param excludedItems Ids of categories to exclude from the returned list
|
||||
* @returns {Array<ServiceCategoryType>}
|
||||
*/
|
||||
getCategories(excludedItems?: Array<string>): Array<ServiceCategoryType> {
|
||||
if (excludedItems != null) {
|
||||
return getStrippedServicesList(excludedItems, this.categoriesDataset);
|
||||
}
|
||||
return this.categoriesDataset;
|
||||
}
|
||||
}
|
|
@ -46,16 +46,20 @@ import EquipmentConfirmScreen from '../screens/Amicale/Equipment/EquipmentConfir
|
|||
import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
|
||||
import GameStartScreen from '../screens/Game/screens/GameStartScreen';
|
||||
import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
|
||||
import { usePreferences } from '../context/preferencesContext';
|
||||
import { getPreferenceBool, PreferenceKeys } from '../utils/asyncStorage';
|
||||
import IntroScreen from '../screens/Intro/IntroScreen';
|
||||
|
||||
export enum MainRoutes {
|
||||
Main = 'main',
|
||||
Intro = 'Intro',
|
||||
Gallery = 'gallery',
|
||||
Settings = 'settings',
|
||||
DashboardEdit = 'dashboard-edit',
|
||||
About = 'about',
|
||||
Dependencies = 'dependencies',
|
||||
Debug = 'debug',
|
||||
GameStart = 'game-start',
|
||||
GameStart = 'game',
|
||||
GameMain = 'game-main',
|
||||
Login = 'login',
|
||||
SelfMenu = 'self-menu',
|
||||
|
@ -66,11 +70,12 @@ export enum MainRoutes {
|
|||
ClubList = 'club-list',
|
||||
ClubInformation = 'club-information',
|
||||
ClubAbout = 'club-about',
|
||||
EquipmentList = 'equipment-list',
|
||||
EquipmentList = 'equipment',
|
||||
EquipmentRent = 'equipment-rent',
|
||||
EquipmentConfirm = 'equipment-confirm',
|
||||
Vote = 'vote',
|
||||
Feedback = 'feedback',
|
||||
Website = 'website',
|
||||
}
|
||||
|
||||
type DefaultParams = { [key in MainRoutes]: object | undefined };
|
||||
|
@ -96,13 +101,31 @@ export type MainStackParamsList = FullParamsList &
|
|||
|
||||
const MainStack = createStackNavigator<MainStackParamsList>();
|
||||
|
||||
function getIntroScreens() {
|
||||
return (
|
||||
<>
|
||||
<MainStack.Screen
|
||||
name={MainRoutes.Intro}
|
||||
component={IntroScreen}
|
||||
options={{
|
||||
headerShown: false,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function MainStackComponent(props: {
|
||||
showIntro: boolean;
|
||||
createTabNavigator: () => React.ReactElement;
|
||||
}) {
|
||||
const { createTabNavigator } = props;
|
||||
const { showIntro, createTabNavigator } = props;
|
||||
if (showIntro) {
|
||||
return getIntroScreens();
|
||||
}
|
||||
return (
|
||||
<MainStack.Navigator
|
||||
initialRouteName={MainRoutes.Main}
|
||||
initialRouteName={showIntro ? MainRoutes.Intro : MainRoutes.Main}
|
||||
headerMode={'screen'}
|
||||
>
|
||||
<MainStack.Screen
|
||||
|
@ -183,7 +206,7 @@ function MainStackComponent(props: {
|
|||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name={'website'}
|
||||
name={MainRoutes.Website}
|
||||
component={WebsiteScreen}
|
||||
options={{
|
||||
title: '',
|
||||
|
@ -290,8 +313,11 @@ type PropsType = {
|
|||
};
|
||||
|
||||
export default function MainNavigator(props: PropsType) {
|
||||
const { preferences } = usePreferences();
|
||||
const showIntro = getPreferenceBool(PreferenceKeys.showIntro, preferences);
|
||||
return (
|
||||
<MainStackComponent
|
||||
showIntro={showIntro !== false}
|
||||
createTabNavigator={() => <TabNavigator {...props} />}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -31,7 +31,6 @@ import PlanningDisplayScreen from '../screens/Planning/PlanningDisplayScreen';
|
|||
import ProxiwashScreen from '../screens/Proxiwash/ProxiwashScreen';
|
||||
import ProxiwashAboutScreen from '../screens/Proxiwash/ProxiwashAboutScreen';
|
||||
import PlanexScreen from '../screens/Planex/PlanexScreen';
|
||||
import AsyncStorageManager from '../managers/AsyncStorageManager';
|
||||
import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
|
||||
import ScannerScreen from '../screens/Home/ScannerScreen';
|
||||
import FeedItemScreen from '../screens/Home/FeedItemScreen';
|
||||
|
@ -41,6 +40,8 @@ import WebsitesHomeScreen from '../screens/Services/ServicesScreen';
|
|||
import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
|
||||
import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
|
||||
import Mascot, { MASCOT_STYLE } from '../components/Mascot/Mascot';
|
||||
import { usePreferences } from '../context/preferencesContext';
|
||||
import { getPreferenceString, PreferenceKeys } from '../utils/asyncStorage';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
|
@ -56,6 +57,20 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
type DefaultParams = { [key in TabRoutes]: object | undefined };
|
||||
|
||||
export type FullParamsList = DefaultParams & {
|
||||
[TabRoutes.Home]: {
|
||||
nextScreen: string;
|
||||
data: Record<string, object | undefined>;
|
||||
};
|
||||
};
|
||||
|
||||
// Don't know why but TS is complaining without this
|
||||
// See: https://stackoverflow.com/questions/63652687/interface-does-not-satisfy-the-constraint-recordstring-object-undefined
|
||||
export type TabStackParamsList = FullParamsList &
|
||||
Record<string, object | undefined>;
|
||||
|
||||
const ServicesStack = createStackNavigator();
|
||||
|
||||
function ServicesStackComponent() {
|
||||
|
@ -214,7 +229,7 @@ function PlanexStackComponent() {
|
|||
);
|
||||
}
|
||||
|
||||
const Tab = createBottomTabNavigator();
|
||||
const Tab = createBottomTabNavigator<TabStackParamsList>();
|
||||
|
||||
type PropsType = {
|
||||
defaultHomeRoute: string | null;
|
||||
|
@ -249,65 +264,70 @@ const ICONS: {
|
|||
},
|
||||
};
|
||||
|
||||
export default class TabNavigator extends React.Component<PropsType> {
|
||||
defaultRoute: string;
|
||||
createHomeStackComponent: () => any;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.defaultRoute = 'home';
|
||||
if (!props.defaultHomeRoute) {
|
||||
this.defaultRoute = AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.defaultStartScreen.key
|
||||
).toLowerCase();
|
||||
}
|
||||
this.createHomeStackComponent = () =>
|
||||
HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
|
||||
export default function TabNavigator(props: PropsType) {
|
||||
const { preferences } = usePreferences();
|
||||
let defaultRoute = getPreferenceString(
|
||||
PreferenceKeys.defaultStartScreen,
|
||||
preferences
|
||||
);
|
||||
if (!defaultRoute) {
|
||||
defaultRoute = 'home';
|
||||
} else {
|
||||
defaultRoute = defaultRoute.toLowerCase();
|
||||
}
|
||||
|
||||
render() {
|
||||
const LABELS: {
|
||||
[key: string]: string;
|
||||
} = {
|
||||
services: i18n.t('screens.services.title'),
|
||||
proxiwash: i18n.t('screens.proxiwash.title'),
|
||||
home: i18n.t('screens.home.title'),
|
||||
planning: i18n.t('screens.planning.title'),
|
||||
planex: i18n.t('screens.planex.title'),
|
||||
};
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={this.defaultRoute}
|
||||
tabBar={(tabProps) => (
|
||||
<CustomTabBar {...tabProps} labels={LABELS} icons={ICONS} />
|
||||
)}
|
||||
>
|
||||
<Tab.Screen
|
||||
name={'services'}
|
||||
component={ServicesStackComponent}
|
||||
options={{ title: i18n.t('screens.services.title') }}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={'proxiwash'}
|
||||
component={ProxiwashStackComponent}
|
||||
options={{ title: i18n.t('screens.proxiwash.title') }}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={'home'}
|
||||
component={this.createHomeStackComponent}
|
||||
options={{ title: i18n.t('screens.home.title') }}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={'planning'}
|
||||
component={PlanningStackComponent}
|
||||
options={{ title: i18n.t('screens.planning.title') }}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={'planex'}
|
||||
component={PlanexStackComponent}
|
||||
options={{ title: i18n.t('screens.planex.title') }}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
);
|
||||
}
|
||||
const createHomeStackComponent = () =>
|
||||
HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
|
||||
|
||||
const LABELS: {
|
||||
[key: string]: string;
|
||||
} = {
|
||||
services: i18n.t('screens.services.title'),
|
||||
proxiwash: i18n.t('screens.proxiwash.title'),
|
||||
home: i18n.t('screens.home.title'),
|
||||
planning: i18n.t('screens.planning.title'),
|
||||
planex: i18n.t('screens.planex.title'),
|
||||
};
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={defaultRoute}
|
||||
tabBar={(tabProps) => (
|
||||
<CustomTabBar {...tabProps} labels={LABELS} icons={ICONS} />
|
||||
)}
|
||||
>
|
||||
<Tab.Screen
|
||||
name={'services'}
|
||||
component={ServicesStackComponent}
|
||||
options={{ title: i18n.t('screens.services.title') }}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={'proxiwash'}
|
||||
component={ProxiwashStackComponent}
|
||||
options={{ title: i18n.t('screens.proxiwash.title') }}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={'home'}
|
||||
component={createHomeStackComponent}
|
||||
options={{ title: i18n.t('screens.home.title') }}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={'events'}
|
||||
component={PlanningStackComponent}
|
||||
options={{ title: i18n.t('screens.planning.title') }}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name={'planex'}
|
||||
component={PlanexStackComponent}
|
||||
options={{ title: i18n.t('screens.planex.title') }}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
export enum TabRoutes {
|
||||
Services = 'services',
|
||||
Proxiwash = 'proxiwash',
|
||||
Home = 'home',
|
||||
Planning = 'events',
|
||||
Planex = 'planex',
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import {
|
||||
Button,
|
||||
|
@ -25,12 +25,17 @@ import {
|
|||
Subheading,
|
||||
TextInput,
|
||||
Title,
|
||||
withTheme,
|
||||
useTheme,
|
||||
} from 'react-native-paper';
|
||||
import { Modalize } from 'react-native-modalize';
|
||||
import CustomModal from '../../components/Overrides/CustomModal';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
||||
import { usePreferences } from '../../context/preferencesContext';
|
||||
import {
|
||||
defaultPreferences,
|
||||
isValidPreferenceKey,
|
||||
PreferenceKeys,
|
||||
} from '../../utils/asyncStorage';
|
||||
|
||||
type PreferenceItemType = {
|
||||
key: string;
|
||||
|
@ -38,15 +43,6 @@ type PreferenceItemType = {
|
|||
current: string;
|
||||
};
|
||||
|
||||
type PropsType = {
|
||||
theme: ReactNativePaper.Theme;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
modalCurrentDisplayItem: PreferenceItemType | null;
|
||||
currentPreferences: Array<PreferenceItemType>;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
|
@ -62,47 +58,35 @@ const styles = StyleSheet.create({
|
|||
* Class defining the Debug screen.
|
||||
* This screen allows the user to get and modify information on the app/device.
|
||||
*/
|
||||
class DebugScreen extends React.Component<PropsType, StateType> {
|
||||
modalRef: { current: Modalize | null };
|
||||
function DebugScreen() {
|
||||
const theme = useTheme();
|
||||
const { preferences, updatePreferences } = usePreferences();
|
||||
const modalRef = useRef<Modalize>(null);
|
||||
|
||||
modalInputValue: string;
|
||||
const [modalInputValue, setModalInputValue] = useState<string>('');
|
||||
const [
|
||||
modalCurrentDisplayItem,
|
||||
setModalCurrentDisplayItem,
|
||||
] = useState<PreferenceItemType | null>(null);
|
||||
|
||||
/**
|
||||
* Copies user preferences to state for easier manipulation
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.modalRef = React.createRef<Modalize>();
|
||||
this.modalInputValue = '';
|
||||
const currentPreferences: Array<PreferenceItemType> = [];
|
||||
Object.values(AsyncStorageManager.PREFERENCES).forEach((object: any) => {
|
||||
const newObject: PreferenceItemType = { ...object };
|
||||
newObject.current = AsyncStorageManager.getString(newObject.key);
|
||||
currentPreferences.push(newObject);
|
||||
});
|
||||
this.state = {
|
||||
modalCurrentDisplayItem: null,
|
||||
currentPreferences,
|
||||
const currentPreferences: Array<PreferenceItemType> = [];
|
||||
Object.values(PreferenceKeys).forEach((key) => {
|
||||
const newObject: PreferenceItemType = {
|
||||
key: key,
|
||||
current: preferences[key],
|
||||
default: defaultPreferences[key],
|
||||
};
|
||||
}
|
||||
currentPreferences.push(newObject);
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets the edit modal content
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getModalContent() {
|
||||
const { props, state } = this;
|
||||
const getModalContent = () => {
|
||||
let key = '';
|
||||
let defaultValue = '';
|
||||
let current = '';
|
||||
if (state.modalCurrentDisplayItem) {
|
||||
key = state.modalCurrentDisplayItem.key;
|
||||
defaultValue = state.modalCurrentDisplayItem.default;
|
||||
defaultValue = state.modalCurrentDisplayItem.default;
|
||||
current = state.modalCurrentDisplayItem.current;
|
||||
if (modalCurrentDisplayItem) {
|
||||
key = modalCurrentDisplayItem.key;
|
||||
defaultValue = modalCurrentDisplayItem.default;
|
||||
current = modalCurrentDisplayItem.current;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -110,19 +94,14 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
<Title>{key}</Title>
|
||||
<Subheading>Default: {defaultValue}</Subheading>
|
||||
<Subheading>Current: {current}</Subheading>
|
||||
<TextInput
|
||||
label="New Value"
|
||||
onChangeText={(text: string) => {
|
||||
this.modalInputValue = text;
|
||||
}}
|
||||
/>
|
||||
<TextInput label={'New Value'} onChangeText={setModalInputValue} />
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
mode="contained"
|
||||
dark
|
||||
color={props.theme.colors.success}
|
||||
color={theme.colors.success}
|
||||
onPress={() => {
|
||||
this.saveNewPrefs(key, this.modalInputValue);
|
||||
saveNewPrefs(key, modalInputValue);
|
||||
}}
|
||||
>
|
||||
Save new value
|
||||
|
@ -130,9 +109,9 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
<Button
|
||||
mode="contained"
|
||||
dark
|
||||
color={props.theme.colors.danger}
|
||||
color={theme.colors.danger}
|
||||
onPress={() => {
|
||||
this.saveNewPrefs(key, defaultValue);
|
||||
saveNewPrefs(key, defaultValue);
|
||||
}}
|
||||
>
|
||||
Reset to default
|
||||
|
@ -140,85 +119,46 @@ class DebugScreen extends React.Component<PropsType, StateType> {
|
|||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
getRenderItem = ({ item }: { item: PreferenceItemType }) => {
|
||||
const getRenderItem = ({ item }: { item: PreferenceItemType }) => {
|
||||
return (
|
||||
<List.Item
|
||||
title={item.key}
|
||||
description="Click to edit"
|
||||
onPress={() => {
|
||||
this.showEditModal(item);
|
||||
showEditModal(item);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the edit modal
|
||||
*
|
||||
* @param item
|
||||
*/
|
||||
showEditModal(item: PreferenceItemType) {
|
||||
this.setState({
|
||||
modalCurrentDisplayItem: item,
|
||||
});
|
||||
if (this.modalRef.current) {
|
||||
this.modalRef.current.open();
|
||||
const showEditModal = (item: PreferenceItemType) => {
|
||||
setModalCurrentDisplayItem(item);
|
||||
if (modalRef.current) {
|
||||
modalRef.current.open();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the index of the given key in the preferences array
|
||||
*
|
||||
* @param key THe key to find the index of
|
||||
* @returns {number}
|
||||
*/
|
||||
findIndexOfKey(key: string): number {
|
||||
const { currentPreferences } = this.state;
|
||||
let index = -1;
|
||||
for (let i = 0; i < currentPreferences.length; i += 1) {
|
||||
if (currentPreferences[i].key === key) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
const saveNewPrefs = (key: string, value: string) => {
|
||||
if (isValidPreferenceKey(key)) {
|
||||
updatePreferences(key, value);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the new value of the given preference
|
||||
*
|
||||
* @param key The pref key
|
||||
* @param value The pref value
|
||||
*/
|
||||
saveNewPrefs(key: string, value: string) {
|
||||
this.setState((prevState: StateType): {
|
||||
currentPreferences: Array<PreferenceItemType>;
|
||||
} => {
|
||||
const currentPreferences = [...prevState.currentPreferences];
|
||||
currentPreferences[this.findIndexOfKey(key)].current = value;
|
||||
return { currentPreferences };
|
||||
});
|
||||
AsyncStorageManager.set(key, value);
|
||||
if (this.modalRef.current) {
|
||||
this.modalRef.current.close();
|
||||
if (modalRef.current) {
|
||||
modalRef.current.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { state } = this;
|
||||
return (
|
||||
<View>
|
||||
<CustomModal ref={this.modalRef}>{this.getModalContent()}</CustomModal>
|
||||
<CollapsibleFlatList
|
||||
data={state.currentPreferences}
|
||||
extraData={state.currentPreferences}
|
||||
renderItem={this.getRenderItem}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<CustomModal ref={modalRef}>{getModalContent()}</CustomModal>
|
||||
<CollapsibleFlatList
|
||||
data={currentPreferences}
|
||||
extraData={currentPreferences}
|
||||
renderItem={getRenderItem}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(DebugScreen);
|
||||
export default DebugScreen;
|
||||
|
|
|
@ -25,7 +25,6 @@ import i18n from 'i18n-js';
|
|||
import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
|
||||
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
||||
import { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
||||
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
||||
import GENERAL_STYLES from '../../../constants/Styles';
|
||||
import ConnectionManager from '../../../managers/ConnectionManager';
|
||||
import { ApiRejectType } from '../../../utils/WebData';
|
||||
|
@ -36,7 +35,7 @@ type PropsType = {
|
|||
};
|
||||
|
||||
type StateType = {
|
||||
mascotDialogVisible: boolean;
|
||||
mascotDialogVisible: boolean | undefined;
|
||||
};
|
||||
|
||||
export type DeviceType = {
|
||||
|
@ -75,9 +74,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
|||
super(props);
|
||||
this.userRents = null;
|
||||
this.state = {
|
||||
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.equipmentShowMascot.key
|
||||
),
|
||||
mascotDialogVisible: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -145,10 +142,6 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
|||
};
|
||||
|
||||
hideMascotDialog = () => {
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.equipmentShowMascot.key,
|
||||
false
|
||||
);
|
||||
this.setState({ mascotDialogVisible: false });
|
||||
};
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack';
|
|||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import ConnectionManager from '../../managers/ConnectionManager';
|
||||
import ErrorDialog from '../../components/Dialogs/ErrorDialog';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
|
||||
|
@ -56,7 +55,7 @@ type StateType = {
|
|||
loading: boolean;
|
||||
dialogVisible: boolean;
|
||||
dialogError: ApiRejectType;
|
||||
mascotDialogVisible: boolean;
|
||||
mascotDialogVisible: boolean | undefined;
|
||||
};
|
||||
|
||||
const ICON_AMICALE = require('../../../assets/amicale.png');
|
||||
|
@ -118,9 +117,7 @@ class LoginScreen extends React.Component<Props, StateType> {
|
|||
loading: false,
|
||||
dialogVisible: false,
|
||||
dialogError: { status: REQUEST_STATUS.SUCCESS },
|
||||
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.loginShowMascot.key
|
||||
),
|
||||
mascotDialogVisible: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -321,10 +318,6 @@ class LoginScreen extends React.Component<Props, StateType> {
|
|||
};
|
||||
|
||||
hideMascotDialog = () => {
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.loginShowMascot.key,
|
||||
false
|
||||
);
|
||||
this.setState({ mascotDialogVisible: false });
|
||||
};
|
||||
|
||||
|
@ -357,10 +350,11 @@ class LoginScreen extends React.Component<Props, StateType> {
|
|||
handleSuccess = () => {
|
||||
const { navigation } = this.props;
|
||||
// Do not show the home login banner again
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.homeShowMascot.key,
|
||||
false
|
||||
);
|
||||
// TODO
|
||||
// AsyncStorageManager.set(
|
||||
// AsyncStorageManager.PREFERENCES.homeShowMascot.key,
|
||||
// false
|
||||
// );
|
||||
if (this.nextScreen == null) {
|
||||
navigation.goBack();
|
||||
} else {
|
||||
|
|
|
@ -36,13 +36,16 @@ import MaterialHeaderButtons, {
|
|||
} from '../../components/Overrides/CustomHeaderButton';
|
||||
import CardList from '../../components/Lists/CardList/CardList';
|
||||
import Mascot, { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||
import ServicesManager, { SERVICES_KEY } from '../../managers/ServicesManager';
|
||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
||||
import type { ServiceItemType } from '../../managers/ServicesManager';
|
||||
import GENERAL_STYLES from '../../constants/Styles';
|
||||
import Urls from '../../constants/Urls';
|
||||
import RequestScreen from '../../components/Screens/RequestScreen';
|
||||
import ConnectionManager from '../../managers/ConnectionManager';
|
||||
import {
|
||||
getAmicaleServices,
|
||||
ServiceItemType,
|
||||
SERVICES_KEY,
|
||||
} from '../../utils/Services';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
|
@ -100,8 +103,9 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
|
|||
super(props);
|
||||
this.data = undefined;
|
||||
this.flatListData = [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }];
|
||||
const services = new ServicesManager(props.navigation);
|
||||
this.amicaleDataset = services.getAmicaleServices([SERVICES_KEY.PROFILE]);
|
||||
this.amicaleDataset = getAmicaleServices(props.navigation.navigate, [
|
||||
SERVICES_KEY.PROFILE,
|
||||
]);
|
||||
this.state = {
|
||||
dialogVisible: false,
|
||||
};
|
||||
|
|
|
@ -28,7 +28,6 @@ import VoteResults from '../../components/Amicale/Vote/VoteResults';
|
|||
import VoteWait from '../../components/Amicale/Vote/VoteWait';
|
||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
|
||||
import GENERAL_STYLES from '../../constants/Styles';
|
||||
import ConnectionManager from '../../managers/ConnectionManager';
|
||||
|
@ -118,7 +117,7 @@ type PropsType = {};
|
|||
|
||||
type StateType = {
|
||||
hasVoted: boolean;
|
||||
mascotDialogVisible: boolean;
|
||||
mascotDialogVisible: boolean | undefined;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -154,9 +153,7 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
this.dates = undefined;
|
||||
this.state = {
|
||||
hasVoted: false,
|
||||
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.voteShowMascot.key
|
||||
),
|
||||
mascotDialogVisible: undefined,
|
||||
};
|
||||
this.hasVoted = false;
|
||||
this.today = new Date();
|
||||
|
@ -328,10 +325,6 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
|||
};
|
||||
|
||||
hideMascotDialog = () => {
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.voteShowMascot.key,
|
||||
false
|
||||
);
|
||||
this.setState({ mascotDialogVisible: false });
|
||||
};
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import MaterialHeaderButtons, {
|
|||
import type { OptionsDialogButtonType } from '../../../components/Dialogs/OptionsDialog';
|
||||
import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
|
||||
import GENERAL_STYLES from '../../../constants/Styles';
|
||||
import { MainRoutes } from '../../../navigation/MainNavigator';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
|
@ -200,7 +201,7 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
|||
gameScore: score,
|
||||
});
|
||||
if (!isRestart) {
|
||||
props.navigation.replace('game-start', {
|
||||
props.navigation.replace(MainRoutes.GameStart, {
|
||||
score: state.gameScore,
|
||||
level: state.gameLevel,
|
||||
time: state.gameTime,
|
||||
|
|
|
@ -35,7 +35,6 @@ import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityI
|
|||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import Mascot, { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
||||
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
||||
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
||||
import type { GridType } from '../components/GridComponent';
|
||||
import GridComponent from '../components/GridComponent';
|
||||
import GridManager from '../logic/GridManager';
|
||||
|
@ -152,9 +151,11 @@ class GameStartScreen extends React.Component<PropsType> {
|
|||
super(props);
|
||||
this.isHighScore = false;
|
||||
this.gridManager = new GridManager(4, 4, props.theme);
|
||||
this.scores = AsyncStorageManager.getObject(
|
||||
AsyncStorageManager.PREFERENCES.gameScores.key
|
||||
);
|
||||
// TODO
|
||||
// this.scores = AsyncStorageManager.getObject(
|
||||
// AsyncStorageManager.PREFERENCES.gameScores.key
|
||||
// );
|
||||
this.scores = [];
|
||||
this.scores.sort((a: number, b: number): number => b - a);
|
||||
if (props.route.params != null) {
|
||||
this.recoverGameScore();
|
||||
|
@ -448,10 +449,11 @@ class GameStartScreen extends React.Component<PropsType> {
|
|||
if (this.scores.length > 3) {
|
||||
this.scores.splice(3, 1);
|
||||
}
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.gameScores.key,
|
||||
this.scores
|
||||
);
|
||||
// TODO
|
||||
// AsyncStorageManager.set(
|
||||
// AsyncStorageManager.PREFERENCES.gameScores.key,
|
||||
// this.scores
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,7 +474,6 @@ class GameStartScreen extends React.Component<PropsType> {
|
|||
<CollapsibleScrollView headerColors={'transparent'}>
|
||||
{this.getMainContent()}
|
||||
<MascotPopup
|
||||
prefKey={AsyncStorageManager.PREFERENCES.gameStartMascot.key}
|
||||
title={i18n.t('screens.game.mascotDialog.title')}
|
||||
message={i18n.t('screens.game.mascotDialog.message')}
|
||||
icon="gamepad-variant"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import React, { useLayoutEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
FlatList,
|
||||
NativeScrollEvent,
|
||||
|
@ -26,9 +26,13 @@ import {
|
|||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import { Headline, withTheme } from 'react-native-paper';
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { Headline, useTheme } from 'react-native-paper';
|
||||
import {
|
||||
CommonActions,
|
||||
useFocusEffect,
|
||||
useNavigation,
|
||||
} from '@react-navigation/native';
|
||||
import { StackScreenProps } from '@react-navigation/stack';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import { View } from 'react-native-animatable';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
@ -44,16 +48,17 @@ import MaterialHeaderButtons, {
|
|||
import AnimatedFAB from '../../components/Animations/AnimatedFAB';
|
||||
import ConnectionManager from '../../managers/ConnectionManager';
|
||||
import LogoutDialog from '../../components/Amicale/LogoutDialog';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||
import DashboardManager from '../../managers/DashboardManager';
|
||||
import type { ServiceItemType } from '../../managers/ServicesManager';
|
||||
import { getDisplayEvent, getFutureEvents } from '../../utils/Home';
|
||||
import type { PlanningEventType } from '../../utils/Planning';
|
||||
import GENERAL_STYLES from '../../constants/Styles';
|
||||
import Urls from '../../constants/Urls';
|
||||
import { readData } from '../../utils/WebData';
|
||||
import { TabRoutes, TabStackParamsList } from '../../navigation/TabNavigator';
|
||||
import { ServiceItemType } from '../../utils/Services';
|
||||
import { useCurrentDashboard } from '../../context/preferencesContext';
|
||||
import { MainRoutes } from '../../navigation/MainNavigator';
|
||||
|
||||
const FEED_ITEM_HEIGHT = 500;
|
||||
|
||||
|
@ -88,15 +93,7 @@ type RawDashboardType = {
|
|||
dashboard: FullDashboardType;
|
||||
};
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
route: { params: { nextScreen: string; data: object } };
|
||||
theme: ReactNativePaper.Theme;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
dialogVisible: boolean;
|
||||
};
|
||||
type Props = StackScreenProps<TabStackParamsList, TabRoutes.Home>;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
dashboardRow: {
|
||||
|
@ -127,106 +124,94 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
const sortFeedTime = (a: FeedItemType, b: FeedItemType): number =>
|
||||
b.time - a.time;
|
||||
|
||||
const generateNewsFeed = (rawFeed: RawNewsFeedType): Array<FeedItemType> => {
|
||||
const finalFeed: Array<FeedItemType> = [];
|
||||
Object.keys(rawFeed).forEach((key: string) => {
|
||||
const category: Array<FeedItemType> | null = rawFeed[key];
|
||||
if (category != null && category.length > 0) {
|
||||
finalFeed.push(...category);
|
||||
}
|
||||
});
|
||||
finalFeed.sort(sortFeedTime);
|
||||
return finalFeed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Class defining the app's home screen
|
||||
*/
|
||||
class HomeScreen extends React.Component<PropsType, StateType> {
|
||||
static sortFeedTime = (a: FeedItemType, b: FeedItemType): number =>
|
||||
b.time - a.time;
|
||||
function HomeScreen(props: Props) {
|
||||
const theme = useTheme();
|
||||
const navigation = useNavigation();
|
||||
|
||||
static generateNewsFeed(rawFeed: RawNewsFeedType): Array<FeedItemType> {
|
||||
const finalFeed: Array<FeedItemType> = [];
|
||||
Object.keys(rawFeed).forEach((key: string) => {
|
||||
const category: Array<FeedItemType> | null = rawFeed[key];
|
||||
if (category != null && category.length > 0) {
|
||||
finalFeed.push(...category);
|
||||
const [dialogVisible, setDialogVisible] = useState(false);
|
||||
const fabRef = useRef<AnimatedFAB>(null);
|
||||
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(
|
||||
ConnectionManager.getInstance().isLoggedIn()
|
||||
);
|
||||
const { currentDashboard } = useCurrentDashboard();
|
||||
|
||||
let homeDashboard: FullDashboardType | null = null;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const getHeaderButton = () => {
|
||||
let onPressLog = () =>
|
||||
navigation.navigate('login', { nextScreen: 'profile' });
|
||||
let logIcon = 'login';
|
||||
let logColor = theme.colors.primary;
|
||||
if (isLoggedIn) {
|
||||
onPressLog = () => showDisconnectDialog();
|
||||
logIcon = 'logout';
|
||||
logColor = theme.colors.text;
|
||||
}
|
||||
});
|
||||
finalFeed.sort(HomeScreen.sortFeedTime);
|
||||
return finalFeed;
|
||||
}
|
||||
|
||||
isLoggedIn: boolean | null;
|
||||
|
||||
fabRef: { current: null | AnimatedFAB };
|
||||
|
||||
currentNewFeed: Array<FeedItemType>;
|
||||
|
||||
currentDashboard: FullDashboardType | null;
|
||||
|
||||
dashboardManager: DashboardManager;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.fabRef = React.createRef();
|
||||
this.dashboardManager = new DashboardManager(props.navigation);
|
||||
this.currentNewFeed = [];
|
||||
this.currentDashboard = null;
|
||||
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
|
||||
props.navigation.setOptions({
|
||||
headerRight: this.getHeaderButton,
|
||||
});
|
||||
this.state = {
|
||||
dialogVisible: false,
|
||||
return (
|
||||
<MaterialHeaderButtons>
|
||||
<Item
|
||||
title={'log'}
|
||||
iconName={logIcon}
|
||||
color={logColor}
|
||||
onPress={onPressLog}
|
||||
/>
|
||||
<Item
|
||||
title={i18n.t('screens.settings.title')}
|
||||
iconName={'cog'}
|
||||
onPress={() => navigation.navigate(MainRoutes.Settings)}
|
||||
/>
|
||||
</MaterialHeaderButtons>
|
||||
);
|
||||
};
|
||||
}
|
||||
navigation.setOptions({
|
||||
headerRight: getHeaderButton,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [navigation, isLoggedIn]);
|
||||
|
||||
componentDidMount() {
|
||||
const { props } = this;
|
||||
props.navigation.addListener('focus', this.onScreenFocus);
|
||||
// Handle link open when home is focused
|
||||
props.navigation.addListener('state', this.handleNavigationParams);
|
||||
}
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
const handleNavigationParams = () => {
|
||||
const { route } = props;
|
||||
if (route.params != null) {
|
||||
if (route.params.nextScreen != null) {
|
||||
navigation.navigate(route.params.nextScreen, route.params.data);
|
||||
// reset params to prevent infinite loop
|
||||
navigation.dispatch(CommonActions.setParams({ nextScreen: null }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates login state and navigation parameters on screen focus
|
||||
*/
|
||||
onScreenFocus = () => {
|
||||
const { props } = this;
|
||||
if (ConnectionManager.getInstance().isLoggedIn() !== this.isLoggedIn) {
|
||||
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
|
||||
props.navigation.setOptions({
|
||||
headerRight: this.getHeaderButton,
|
||||
});
|
||||
}
|
||||
// handle link open when home is not focused or created
|
||||
this.handleNavigationParams();
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets header buttons based on login state
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getHeaderButton = () => {
|
||||
const { props } = this;
|
||||
let onPressLog = (): void =>
|
||||
props.navigation.navigate('login', { nextScreen: 'profile' });
|
||||
let logIcon = 'login';
|
||||
let logColor = props.theme.colors.primary;
|
||||
if (this.isLoggedIn) {
|
||||
onPressLog = (): void => this.showDisconnectDialog();
|
||||
logIcon = 'logout';
|
||||
logColor = props.theme.colors.text;
|
||||
}
|
||||
|
||||
const onPressSettings = (): void => props.navigation.navigate('settings');
|
||||
return (
|
||||
<MaterialHeaderButtons>
|
||||
<Item
|
||||
title="log"
|
||||
iconName={logIcon}
|
||||
color={logColor}
|
||||
onPress={onPressLog}
|
||||
/>
|
||||
<Item
|
||||
title={i18n.t('screens.settings.title')}
|
||||
iconName="cog"
|
||||
onPress={onPressSettings}
|
||||
/>
|
||||
</MaterialHeaderButtons>
|
||||
);
|
||||
};
|
||||
if (ConnectionManager.getInstance().isLoggedIn() !== isLoggedIn) {
|
||||
setIsLoggedIn(ConnectionManager.getInstance().isLoggedIn());
|
||||
}
|
||||
// handle link open when home is not focused or created
|
||||
handleNavigationParams();
|
||||
return () => {};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isLoggedIn])
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the event dashboard render item.
|
||||
|
@ -235,7 +220,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
* @param content
|
||||
* @return {*}
|
||||
*/
|
||||
getDashboardEvent(content: Array<PlanningEventType>) {
|
||||
const getDashboardEvent = (content: Array<PlanningEventType>) => {
|
||||
const futureEvents = getFutureEvents(content);
|
||||
const displayEvent = getDisplayEvent(futureEvents);
|
||||
// const clickPreviewAction = () =>
|
||||
|
@ -246,15 +231,15 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
return (
|
||||
<DashboardItem
|
||||
eventNumber={futureEvents.length}
|
||||
clickAction={this.onEventContainerClick}
|
||||
clickAction={onEventContainerClick}
|
||||
>
|
||||
<PreviewEventDashboardItem
|
||||
event={displayEvent}
|
||||
clickAction={this.onEventContainerClick}
|
||||
clickAction={onEventContainerClick}
|
||||
/>
|
||||
</DashboardItem>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a dashboard item with a row of shortcut buttons.
|
||||
|
@ -262,16 +247,16 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
* @param content
|
||||
* @return {*}
|
||||
*/
|
||||
getDashboardRow(content: Array<ServiceItemType | null>) {
|
||||
const getDashboardRow = (content: Array<ServiceItemType | null>) => {
|
||||
return (
|
||||
<FlatList
|
||||
data={content}
|
||||
renderItem={this.getDashboardRowRenderItem}
|
||||
renderItem={getDashboardRowRenderItem}
|
||||
horizontal
|
||||
contentContainerStyle={styles.dashboardRow}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a dashboard shortcut item
|
||||
|
@ -279,15 +264,19 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
* @param item
|
||||
* @returns {*}
|
||||
*/
|
||||
getDashboardRowRenderItem = ({ item }: { item: ServiceItemType | null }) => {
|
||||
const getDashboardRowRenderItem = ({
|
||||
item,
|
||||
}: {
|
||||
item: ServiceItemType | null;
|
||||
}) => {
|
||||
if (item != null) {
|
||||
return (
|
||||
<SmallDashboardItem
|
||||
image={item.image}
|
||||
onPress={item.onPress}
|
||||
badgeCount={
|
||||
this.currentDashboard != null && item.badgeFunction != null
|
||||
? item.badgeFunction(this.currentDashboard)
|
||||
homeDashboard != null && item.badgeFunction != null
|
||||
? item.badgeFunction(homeDashboard)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
@ -296,29 +285,13 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
return <SmallDashboardItem />;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a render item for the given feed object
|
||||
*
|
||||
* @param item The feed item to display
|
||||
* @return {*}
|
||||
*/
|
||||
getFeedItem(item: FeedItemType) {
|
||||
return <FeedItem item={item} height={FEED_ITEM_HEIGHT} />;
|
||||
}
|
||||
const getRenderItem = ({ item }: { item: FeedItemType }) => (
|
||||
<FeedItem item={item} height={FEED_ITEM_HEIGHT} />
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets a FlatList render item
|
||||
*
|
||||
* @param item The item to display
|
||||
* @param section The current section
|
||||
* @return {*}
|
||||
*/
|
||||
getRenderItem = ({ item }: { item: FeedItemType }) => this.getFeedItem(item);
|
||||
|
||||
getRenderSectionHeader = (data: {
|
||||
const getRenderSectionHeader = (data: {
|
||||
section: SectionListData<FeedItemType>;
|
||||
}) => {
|
||||
const { props } = this;
|
||||
const icon = data.section.icon;
|
||||
if (data.section.data.length > 0) {
|
||||
return (
|
||||
|
@ -330,7 +303,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
<Headline
|
||||
style={{
|
||||
...styles.sectionHeaderEmpty,
|
||||
color: props.theme.colors.textDisabled,
|
||||
color: theme.colors.textDisabled,
|
||||
}}
|
||||
>
|
||||
{data.section.title}
|
||||
|
@ -339,7 +312,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
<MaterialCommunityIcons
|
||||
name={icon}
|
||||
size={100}
|
||||
color={props.theme.colors.textDisabled}
|
||||
color={theme.colors.textDisabled}
|
||||
style={GENERAL_STYLES.center}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -347,7 +320,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
);
|
||||
};
|
||||
|
||||
getListHeader = (fetchedData: RawDashboardType | undefined) => {
|
||||
const getListHeader = (fetchedData: RawDashboardType | undefined) => {
|
||||
let dashboard = null;
|
||||
if (fetchedData != null) {
|
||||
dashboard = fetchedData.dashboard;
|
||||
|
@ -355,41 +328,17 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
return (
|
||||
<Animatable.View animation="fadeInDown" duration={500} useNativeDriver>
|
||||
<ActionsDashBoardItem />
|
||||
{this.getDashboardRow(this.dashboardManager.getCurrentDashboard())}
|
||||
{this.getDashboardEvent(
|
||||
dashboard == null ? [] : dashboard.today_events
|
||||
)}
|
||||
{getDashboardRow(currentDashboard)}
|
||||
{getDashboardEvent(dashboard == null ? [] : dashboard.today_events)}
|
||||
</Animatable.View>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates to the a new screen if navigation parameters specify one
|
||||
*/
|
||||
handleNavigationParams = () => {
|
||||
const { props } = this;
|
||||
if (props.route.params != null) {
|
||||
if (props.route.params.nextScreen != null) {
|
||||
props.navigation.navigate(
|
||||
props.route.params.nextScreen,
|
||||
props.route.params.data
|
||||
);
|
||||
// reset params to prevent infinite loop
|
||||
props.navigation.dispatch(
|
||||
CommonActions.setParams({ nextScreen: null })
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
const showDisconnectDialog = () => setDialogVisible(true);
|
||||
|
||||
showDisconnectDialog = (): void => this.setState({ dialogVisible: true });
|
||||
const hideDisconnectDialog = () => setDialogVisible(false);
|
||||
|
||||
hideDisconnectDialog = (): void => this.setState({ dialogVisible: false });
|
||||
|
||||
openScanner = () => {
|
||||
const { props } = this;
|
||||
props.navigation.navigate('scanner');
|
||||
};
|
||||
const openScanner = () => navigation.navigate('scanner');
|
||||
|
||||
/**
|
||||
* Creates the dataset to be used in the FlatList
|
||||
|
@ -398,7 +347,7 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
* @param isLoading
|
||||
* @return {*}
|
||||
*/
|
||||
createDataset = (
|
||||
const createDataset = (
|
||||
fetchedData: RawDashboardType | undefined,
|
||||
isLoading: boolean
|
||||
): Array<{
|
||||
|
@ -407,21 +356,20 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
icon?: string;
|
||||
id: string;
|
||||
}> => {
|
||||
let currentNewFeed: Array<FeedItemType> = [];
|
||||
if (fetchedData) {
|
||||
if (fetchedData.news_feed) {
|
||||
this.currentNewFeed = HomeScreen.generateNewsFeed(
|
||||
fetchedData.news_feed
|
||||
);
|
||||
currentNewFeed = generateNewsFeed(fetchedData.news_feed);
|
||||
}
|
||||
if (fetchedData.dashboard) {
|
||||
this.currentDashboard = fetchedData.dashboard;
|
||||
homeDashboard = fetchedData.dashboard;
|
||||
}
|
||||
}
|
||||
if (this.currentNewFeed.length > 0) {
|
||||
if (currentNewFeed.length > 0) {
|
||||
return [
|
||||
{
|
||||
title: i18n.t('screens.home.feedTitle'),
|
||||
data: this.currentNewFeed,
|
||||
data: currentNewFeed,
|
||||
id: SECTIONS_ID[1],
|
||||
},
|
||||
];
|
||||
|
@ -438,14 +386,11 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
];
|
||||
};
|
||||
|
||||
onEventContainerClick = () => {
|
||||
const { props } = this;
|
||||
props.navigation.navigate('planning');
|
||||
};
|
||||
const onEventContainerClick = () => navigation.navigate(TabRoutes.Planning);
|
||||
|
||||
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (this.fabRef.current) {
|
||||
this.fabRef.current.onScroll(event);
|
||||
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (fabRef.current) {
|
||||
fabRef.current.onScroll(event);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -453,63 +398,50 @@ class HomeScreen extends React.Component<PropsType, StateType> {
|
|||
* Callback when pressing the login button on the banner.
|
||||
* This hides the banner and takes the user to the login page.
|
||||
*/
|
||||
onLogin = () => {
|
||||
const { props } = this;
|
||||
props.navigation.navigate('login', {
|
||||
const onLogin = () =>
|
||||
navigation.navigate(MainRoutes.Login, {
|
||||
nextScreen: 'profile',
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
return (
|
||||
<View style={GENERAL_STYLES.flex}>
|
||||
<View style={styles.content}>
|
||||
<WebSectionList
|
||||
request={() => readData<RawDashboardType>(Urls.app.dashboard)}
|
||||
createDataset={this.createDataset}
|
||||
autoRefreshTime={REFRESH_TIME}
|
||||
refreshOnFocus={true}
|
||||
renderItem={this.getRenderItem}
|
||||
itemHeight={FEED_ITEM_HEIGHT}
|
||||
onScroll={this.onScroll}
|
||||
renderSectionHeader={this.getRenderSectionHeader}
|
||||
renderListHeaderComponent={this.getListHeader}
|
||||
/>
|
||||
</View>
|
||||
{!this.isLoggedIn ? (
|
||||
<MascotPopup
|
||||
prefKey={AsyncStorageManager.PREFERENCES.homeShowMascot.key}
|
||||
title={i18n.t('screens.home.mascotDialog.title')}
|
||||
message={i18n.t('screens.home.mascotDialog.message')}
|
||||
icon="human-greeting"
|
||||
buttons={{
|
||||
action: {
|
||||
message: i18n.t('screens.home.mascotDialog.login'),
|
||||
icon: 'login',
|
||||
onPress: this.onLogin,
|
||||
},
|
||||
cancel: {
|
||||
message: i18n.t('screens.home.mascotDialog.later'),
|
||||
icon: 'close',
|
||||
color: props.theme.colors.warning,
|
||||
},
|
||||
}}
|
||||
emotion={MASCOT_STYLE.CUTE}
|
||||
/>
|
||||
) : null}
|
||||
<AnimatedFAB
|
||||
ref={this.fabRef}
|
||||
icon="qrcode-scan"
|
||||
onPress={this.openScanner}
|
||||
/>
|
||||
<LogoutDialog
|
||||
visible={state.dialogVisible}
|
||||
onDismiss={this.hideDisconnectDialog}
|
||||
return (
|
||||
<View style={GENERAL_STYLES.flex}>
|
||||
<View style={styles.content}>
|
||||
<WebSectionList
|
||||
request={() => readData<RawDashboardType>(Urls.app.dashboard)}
|
||||
createDataset={createDataset}
|
||||
autoRefreshTime={REFRESH_TIME}
|
||||
refreshOnFocus={true}
|
||||
renderItem={getRenderItem}
|
||||
itemHeight={FEED_ITEM_HEIGHT}
|
||||
onScroll={onScroll}
|
||||
renderSectionHeader={getRenderSectionHeader}
|
||||
renderListHeaderComponent={getListHeader}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
{!isLoggedIn ? (
|
||||
<MascotPopup
|
||||
title={i18n.t('screens.home.mascotDialog.title')}
|
||||
message={i18n.t('screens.home.mascotDialog.message')}
|
||||
icon="human-greeting"
|
||||
buttons={{
|
||||
action: {
|
||||
message: i18n.t('screens.home.mascotDialog.login'),
|
||||
icon: 'login',
|
||||
onPress: onLogin,
|
||||
},
|
||||
cancel: {
|
||||
message: i18n.t('screens.home.mascotDialog.later'),
|
||||
icon: 'close',
|
||||
color: theme.colors.warning,
|
||||
},
|
||||
}}
|
||||
emotion={MASCOT_STYLE.CUTE}
|
||||
/>
|
||||
) : null}
|
||||
<AnimatedFAB ref={fabRef} icon="qrcode-scan" onPress={openScanner} />
|
||||
<LogoutDialog visible={dialogVisible} onDismiss={hideDisconnectDialog} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(HomeScreen);
|
||||
export default HomeScreen;
|
||||
|
|
41
src/screens/Intro/IntroScreen.tsx
Normal file
41
src/screens/Intro/IntroScreen.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import React from 'react';
|
||||
import CustomIntroSlider from '../../components/Overrides/CustomIntroSlider';
|
||||
import Update from '../../constants/Update';
|
||||
import { usePreferences } from '../../context/preferencesContext';
|
||||
import AprilFoolsManager from '../../managers/AprilFoolsManager';
|
||||
import {
|
||||
getPreferenceBool,
|
||||
getPreferenceNumber,
|
||||
PreferenceKeys,
|
||||
} from '../../utils/asyncStorage';
|
||||
|
||||
export default function IntroScreen() {
|
||||
const { preferences, updatePreferences } = usePreferences();
|
||||
|
||||
const onDone = () => {
|
||||
updatePreferences(PreferenceKeys.showIntro, false);
|
||||
updatePreferences(PreferenceKeys.updateNumber, Update.number);
|
||||
updatePreferences(PreferenceKeys.showAprilFoolsStart, false);
|
||||
};
|
||||
|
||||
const showIntro =
|
||||
getPreferenceBool(PreferenceKeys.showIntro, preferences) !== false;
|
||||
|
||||
const isUpdate =
|
||||
getPreferenceNumber(PreferenceKeys.updateNumber, preferences) !==
|
||||
Update.number && !showIntro;
|
||||
|
||||
const isAprilFools =
|
||||
AprilFoolsManager.getInstance().isAprilFoolsEnabled() &&
|
||||
getPreferenceBool(PreferenceKeys.showAprilFoolsStart, preferences) !==
|
||||
false &&
|
||||
!showIntro;
|
||||
|
||||
return (
|
||||
<CustomIntroSlider
|
||||
onDone={onDone}
|
||||
isUpdate={isUpdate}
|
||||
isAprilFools={isAprilFools}
|
||||
/>
|
||||
);
|
||||
}
|
61
src/screens/MainApp.tsx
Normal file
61
src/screens/MainApp.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import React, { Ref, useEffect } from 'react';
|
||||
import {
|
||||
NavigationContainer,
|
||||
NavigationContainerRef,
|
||||
} from '@react-navigation/native';
|
||||
import { Provider as PaperProvider } from 'react-native-paper';
|
||||
import GENERAL_STYLES from '../constants/Styles';
|
||||
import CollapsibleProvider from '../components/providers/CollapsibleProvider';
|
||||
import CacheProvider from '../components/providers/CacheProvider';
|
||||
import { OverflowMenuProvider } from 'react-navigation-header-buttons';
|
||||
import MainNavigator from '../navigation/MainNavigator';
|
||||
import { Platform, SafeAreaView, View } from 'react-native';
|
||||
import { useDarkTheme } from '../context/preferencesContext';
|
||||
import { CustomDarkTheme, CustomWhiteTheme } from '../utils/Themes';
|
||||
import { setupStatusBar } from '../utils/Utils';
|
||||
|
||||
type Props = {
|
||||
defaultHomeRoute: string | null;
|
||||
defaultHomeData: { [key: string]: string };
|
||||
};
|
||||
|
||||
function MainApp(props: Props, ref?: Ref<NavigationContainerRef>) {
|
||||
const darkTheme = useDarkTheme();
|
||||
const theme = darkTheme ? CustomDarkTheme : CustomWhiteTheme;
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'ios') {
|
||||
setTimeout(setupStatusBar, 1000);
|
||||
} else {
|
||||
setupStatusBar(theme);
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<PaperProvider theme={theme}>
|
||||
<CollapsibleProvider>
|
||||
<CacheProvider>
|
||||
<OverflowMenuProvider>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.colors.background,
|
||||
...GENERAL_STYLES.flex,
|
||||
}}
|
||||
>
|
||||
<SafeAreaView style={GENERAL_STYLES.flex}>
|
||||
<NavigationContainer theme={theme} ref={ref}>
|
||||
<MainNavigator
|
||||
defaultHomeRoute={props.defaultHomeRoute}
|
||||
defaultHomeData={props.defaultHomeData}
|
||||
/>
|
||||
</NavigationContainer>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</OverflowMenuProvider>
|
||||
</CacheProvider>
|
||||
</CollapsibleProvider>
|
||||
</PaperProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.forwardRef(MainApp);
|
|
@ -17,31 +17,21 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Button, Card, Paragraph } from 'react-native-paper';
|
||||
import { FlatList, StyleSheet } from 'react-native';
|
||||
import { View } from 'react-native-animatable';
|
||||
import i18n from 'i18n-js';
|
||||
import type {
|
||||
ServiceCategoryType,
|
||||
ServiceItemType,
|
||||
} from '../../../managers/ServicesManager';
|
||||
import DashboardManager from '../../../managers/DashboardManager';
|
||||
import DashboardEditAccordion from '../../../components/Lists/DashboardEdit/DashboardEditAccordion';
|
||||
import DashboardEditPreviewItem from '../../../components/Lists/DashboardEdit/DashboardEditPreviewItem';
|
||||
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
||||
import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
currentDashboard: Array<ServiceItemType | null>;
|
||||
currentDashboardIdList: Array<string>;
|
||||
activeItem: number;
|
||||
};
|
||||
import {
|
||||
getCategories,
|
||||
ServiceCategoryType,
|
||||
ServiceItemType,
|
||||
} from '../../../utils/Services';
|
||||
import { useNavigation } from '@react-navigation/core';
|
||||
import { useCurrentDashboard } from '../../../context/preferencesContext';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
dashboardContainer: {
|
||||
|
@ -71,85 +61,71 @@ const styles = StyleSheet.create({
|
|||
/**
|
||||
* Class defining the Settings screen. This screen shows controls to modify app preferences.
|
||||
*/
|
||||
class DashboardEditScreen extends React.Component<PropsType, StateType> {
|
||||
content: Array<ServiceCategoryType>;
|
||||
function DashboardEditScreen() {
|
||||
const navigation = useNavigation();
|
||||
|
||||
initialDashboard: Array<ServiceItemType | null>;
|
||||
const {
|
||||
currentDashboard,
|
||||
currentDashboardIdList,
|
||||
updateCurrentDashboard,
|
||||
} = useCurrentDashboard();
|
||||
const initialDashboard = useRef(currentDashboardIdList);
|
||||
const [activeItem, setActiveItem] = useState(0);
|
||||
|
||||
initialDashboardIdList: Array<string>;
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
const dashboardManager = new DashboardManager(props.navigation);
|
||||
this.initialDashboardIdList = AsyncStorageManager.getObject(
|
||||
AsyncStorageManager.PREFERENCES.dashboardItems.key
|
||||
);
|
||||
this.initialDashboard = dashboardManager.getCurrentDashboard();
|
||||
this.state = {
|
||||
currentDashboard: [...this.initialDashboard],
|
||||
currentDashboardIdList: [...this.initialDashboardIdList],
|
||||
activeItem: 0,
|
||||
};
|
||||
this.content = dashboardManager.getCategories();
|
||||
}
|
||||
|
||||
getDashboardRowRenderItem = ({
|
||||
const getDashboardRowRenderItem = ({
|
||||
item,
|
||||
index,
|
||||
}: {
|
||||
item: ServiceItemType | null;
|
||||
index: number;
|
||||
}) => {
|
||||
const { activeItem } = this.state;
|
||||
return (
|
||||
<DashboardEditPreviewItem
|
||||
image={item?.image}
|
||||
onPress={() => {
|
||||
this.setState({ activeItem: index });
|
||||
setActiveItem(index);
|
||||
}}
|
||||
isActive={activeItem === index}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
getDashboard(content: Array<ServiceItemType | null>) {
|
||||
const getDashboard = (content: Array<ServiceItemType | null>) => {
|
||||
return (
|
||||
<FlatList
|
||||
data={content}
|
||||
extraData={this.state}
|
||||
renderItem={this.getDashboardRowRenderItem}
|
||||
extraData={activeItem}
|
||||
renderItem={getDashboardRowRenderItem}
|
||||
horizontal
|
||||
contentContainerStyle={styles.dashboard}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
getRenderItem = ({ item }: { item: ServiceCategoryType }) => {
|
||||
const { currentDashboardIdList } = this.state;
|
||||
const getRenderItem = ({ item }: { item: ServiceCategoryType }) => {
|
||||
return (
|
||||
<DashboardEditAccordion
|
||||
item={item}
|
||||
onPress={this.updateDashboard}
|
||||
onPress={updateDashboard}
|
||||
activeDashboard={currentDashboardIdList}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
getListHeader() {
|
||||
const { currentDashboard } = this.state;
|
||||
const getListHeader = () => {
|
||||
return (
|
||||
<Card style={styles.card}>
|
||||
<Card.Content>
|
||||
<View style={styles.buttonContainer}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={this.undoDashboard}
|
||||
mode={'contained'}
|
||||
onPress={undoDashboard}
|
||||
style={styles.button}
|
||||
>
|
||||
{i18n.t('screens.settings.dashboardEdit.undo')}
|
||||
</Button>
|
||||
<View style={styles.dashboardContainer}>
|
||||
{this.getDashboard(currentDashboard)}
|
||||
{getDashboard(currentDashboard)}
|
||||
</View>
|
||||
</View>
|
||||
<Paragraph style={styles.text}>
|
||||
|
@ -158,43 +134,28 @@ class DashboardEditScreen extends React.Component<PropsType, StateType> {
|
|||
</Card.Content>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
updateDashboard = (service: ServiceItemType) => {
|
||||
const { currentDashboard, currentDashboardIdList, activeItem } = this.state;
|
||||
currentDashboard[activeItem] = service;
|
||||
currentDashboardIdList[activeItem] = service.key;
|
||||
this.setState({
|
||||
currentDashboard,
|
||||
currentDashboardIdList,
|
||||
});
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.dashboardItems.key,
|
||||
currentDashboardIdList
|
||||
const updateDashboard = (service: ServiceItemType) => {
|
||||
updateCurrentDashboard(
|
||||
currentDashboardIdList.map((id, index) =>
|
||||
index === activeItem ? service.key : id
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
undoDashboard = () => {
|
||||
this.setState({
|
||||
currentDashboard: [...this.initialDashboard],
|
||||
currentDashboardIdList: [...this.initialDashboardIdList],
|
||||
});
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.dashboardItems.key,
|
||||
this.initialDashboardIdList
|
||||
);
|
||||
const undoDashboard = () => {
|
||||
updateCurrentDashboard(initialDashboard.current);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<CollapsibleFlatList
|
||||
data={this.content}
|
||||
renderItem={this.getRenderItem}
|
||||
ListHeaderComponent={this.getListHeader()}
|
||||
style={{}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CollapsibleFlatList
|
||||
data={getCategories(navigation.navigate)}
|
||||
renderItem={getRenderItem}
|
||||
ListHeaderComponent={getListHeader()}
|
||||
style={{}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardEditScreen;
|
||||
|
|
|
@ -26,28 +26,20 @@ import {
|
|||
List,
|
||||
Switch,
|
||||
ToggleButton,
|
||||
withTheme,
|
||||
useTheme,
|
||||
} from 'react-native-paper';
|
||||
import { Appearance } from 'react-native-appearance';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import ThemeManager from '../../../managers/ThemeManager';
|
||||
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
||||
import CustomSlider from '../../../components/Overrides/CustomSlider';
|
||||
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||
import GENERAL_STYLES from '../../../constants/Styles';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
theme: ReactNativePaper.Theme;
|
||||
};
|
||||
|
||||
type StateType = {
|
||||
nightMode: boolean;
|
||||
nightModeFollowSystem: boolean;
|
||||
startScreenPickerSelected: string;
|
||||
selectedWash: string;
|
||||
isDebugUnlocked: boolean;
|
||||
};
|
||||
import { usePreferences } from '../../../context/preferencesContext';
|
||||
import { useNavigation } from '@react-navigation/core';
|
||||
import {
|
||||
getPreferenceBool,
|
||||
getPreferenceNumber,
|
||||
getPreferenceString,
|
||||
PreferenceKeys,
|
||||
} from '../../../utils/asyncStorage';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
slider: {
|
||||
|
@ -66,98 +58,67 @@ const styles = StyleSheet.create({
|
|||
/**
|
||||
* Class defining the Settings screen. This screen shows controls to modify app preferences.
|
||||
*/
|
||||
class SettingsScreen extends React.Component<PropsType, StateType> {
|
||||
savedNotificationReminder: number;
|
||||
function SettingsScreen() {
|
||||
const navigation = useNavigation();
|
||||
const theme = useTheme();
|
||||
const { preferences, updatePreferences } = usePreferences();
|
||||
|
||||
/**
|
||||
* Loads user preferences into state
|
||||
*/
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
const notifReminder = AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.proxiwashNotifications.key
|
||||
);
|
||||
this.savedNotificationReminder = parseInt(notifReminder, 10);
|
||||
if (Number.isNaN(this.savedNotificationReminder)) {
|
||||
this.savedNotificationReminder = 0;
|
||||
}
|
||||
const nightMode = getPreferenceBool(
|
||||
PreferenceKeys.nightMode,
|
||||
preferences
|
||||
) as boolean;
|
||||
const nightModeFollowSystem =
|
||||
(getPreferenceBool(
|
||||
PreferenceKeys.nightModeFollowSystem,
|
||||
preferences
|
||||
) as boolean) && Appearance.getColorScheme() !== 'no-preference';
|
||||
const startScreenPickerSelected = getPreferenceString(
|
||||
PreferenceKeys.defaultStartScreen,
|
||||
preferences
|
||||
) as string;
|
||||
const selectedWash = getPreferenceString(
|
||||
PreferenceKeys.selectedWash,
|
||||
preferences
|
||||
) as string;
|
||||
const isDebugUnlocked = getPreferenceBool(
|
||||
PreferenceKeys.debugUnlocked,
|
||||
preferences
|
||||
) as boolean;
|
||||
const notif = getPreferenceNumber(
|
||||
PreferenceKeys.proxiwashNotifications,
|
||||
preferences
|
||||
);
|
||||
const savedNotificationReminder = !notif || Number.isNaN(notif) ? 0 : notif;
|
||||
|
||||
this.state = {
|
||||
nightMode: ThemeManager.getNightMode(),
|
||||
nightModeFollowSystem:
|
||||
AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key
|
||||
) && Appearance.getColorScheme() !== 'no-preference',
|
||||
startScreenPickerSelected: AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.defaultStartScreen.key
|
||||
),
|
||||
selectedWash: AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.selectedWash.key
|
||||
),
|
||||
isDebugUnlocked: AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.debugUnlocked.key
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the value for the proxiwash reminder notification time
|
||||
*
|
||||
* @param value The value to store
|
||||
*/
|
||||
onProxiwashNotifPickerValueChange = (value: number) => {
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.proxiwashNotifications.key,
|
||||
value
|
||||
);
|
||||
const onProxiwashNotifPickerValueChange = (value: number) => {
|
||||
updatePreferences(PreferenceKeys.proxiwashNotifications, value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves the value for the proxiwash reminder notification time
|
||||
*
|
||||
* @param value The value to store
|
||||
*/
|
||||
onStartScreenPickerValueChange = (value: string) => {
|
||||
const onStartScreenPickerValueChange = (value: string) => {
|
||||
if (value != null) {
|
||||
this.setState({ startScreenPickerSelected: value });
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.defaultStartScreen.key,
|
||||
value
|
||||
);
|
||||
updatePreferences(PreferenceKeys.defaultStartScreen, value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a picker allowing the user to select the proxiwash reminder notification time
|
||||
*
|
||||
* @returns {React.Node}
|
||||
*/
|
||||
getProxiwashNotifPicker() {
|
||||
const { theme } = this.props;
|
||||
const getProxiwashNotifPicker = () => {
|
||||
return (
|
||||
<CustomSlider
|
||||
style={styles.slider}
|
||||
minimumValue={0}
|
||||
maximumValue={10}
|
||||
step={1}
|
||||
value={this.savedNotificationReminder}
|
||||
onValueChange={this.onProxiwashNotifPickerValueChange}
|
||||
value={savedNotificationReminder}
|
||||
onValueChange={onProxiwashNotifPickerValueChange}
|
||||
thumbTintColor={theme.colors.primary}
|
||||
minimumTrackTintColor={theme.colors.primary}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a radio picker allowing the user to select the proxiwash
|
||||
*
|
||||
* @returns {React.Node}
|
||||
*/
|
||||
getProxiwashChangePicker() {
|
||||
const { selectedWash } = this.state;
|
||||
const getProxiwashChangePicker = () => {
|
||||
return (
|
||||
<RadioButton.Group
|
||||
onValueChange={this.onSelectWashValueChange}
|
||||
onValueChange={onSelectWashValueChange}
|
||||
value={selectedWash}
|
||||
>
|
||||
<RadioButton.Item
|
||||
|
@ -170,18 +131,12 @@ class SettingsScreen extends React.Component<PropsType, StateType> {
|
|||
/>
|
||||
</RadioButton.Group>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a picker allowing the user to select the start screen
|
||||
*
|
||||
* @returns {React.Node}
|
||||
*/
|
||||
getStartScreenPicker() {
|
||||
const { startScreenPickerSelected } = this.state;
|
||||
const getStartScreenPicker = () => {
|
||||
return (
|
||||
<ToggleButton.Row
|
||||
onValueChange={this.onStartScreenPickerValueChange}
|
||||
onValueChange={onStartScreenPickerValueChange}
|
||||
value={startScreenPickerSelected}
|
||||
style={GENERAL_STYLES.centerHorizontal}
|
||||
>
|
||||
|
@ -192,30 +147,17 @@ class SettingsScreen extends React.Component<PropsType, StateType> {
|
|||
<ToggleButton icon="clock" value="planex" />
|
||||
</ToggleButton.Row>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles night mode and saves it to preferences
|
||||
*/
|
||||
onToggleNightMode = () => {
|
||||
const { nightMode } = this.state;
|
||||
ThemeManager.getInstance().setNightMode(!nightMode);
|
||||
this.setState({ nightMode: !nightMode });
|
||||
};
|
||||
|
||||
onToggleNightModeFollowSystem = () => {
|
||||
const { nightModeFollowSystem } = this.state;
|
||||
const value = !nightModeFollowSystem;
|
||||
this.setState({ nightModeFollowSystem: value });
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
|
||||
value
|
||||
const onToggleNightMode = () => {
|
||||
updatePreferences(PreferenceKeys.nightMode, !nightMode);
|
||||
};
|
||||
|
||||
const onToggleNightModeFollowSystem = () => {
|
||||
updatePreferences(
|
||||
PreferenceKeys.nightModeFollowSystem,
|
||||
!nightModeFollowSystem
|
||||
);
|
||||
if (value) {
|
||||
const nightMode = Appearance.getColorScheme() === 'dark';
|
||||
ThemeManager.getInstance().setNightMode(nightMode);
|
||||
this.setState({ nightMode });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -228,13 +170,13 @@ class SettingsScreen extends React.Component<PropsType, StateType> {
|
|||
* @param state The current state of the switch
|
||||
* @returns {React.Node}
|
||||
*/
|
||||
static getToggleItem(
|
||||
const getToggleItem = (
|
||||
onPressCallback: () => void,
|
||||
icon: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
state: boolean
|
||||
) {
|
||||
) => {
|
||||
return (
|
||||
<List.Item
|
||||
title={title}
|
||||
|
@ -245,16 +187,15 @@ class SettingsScreen extends React.Component<PropsType, StateType> {
|
|||
right={() => <Switch value={state} onValueChange={onPressCallback} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
getNavigateItem(
|
||||
const getNavigateItem = (
|
||||
route: string,
|
||||
icon: string,
|
||||
title: string,
|
||||
subtitle: string,
|
||||
onLongPress?: () => void
|
||||
) {
|
||||
const { navigation } = this.props;
|
||||
) => {
|
||||
return (
|
||||
<List.Item
|
||||
title={title}
|
||||
|
@ -275,144 +216,121 @@ class SettingsScreen extends React.Component<PropsType, StateType> {
|
|||
onLongPress={onLongPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves the value for the proxiwash selected wash
|
||||
*
|
||||
* @param value The value to store
|
||||
*/
|
||||
onSelectWashValueChange = (value: string) => {
|
||||
const onSelectWashValueChange = (value: string) => {
|
||||
if (value != null) {
|
||||
this.setState({ selectedWash: value });
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.selectedWash.key,
|
||||
value
|
||||
);
|
||||
updatePreferences(PreferenceKeys.selectedWash, value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unlocks debug mode and saves its state to user preferences
|
||||
*/
|
||||
unlockDebugMode = () => {
|
||||
this.setState({ isDebugUnlocked: true });
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.debugUnlocked.key,
|
||||
true
|
||||
);
|
||||
const unlockDebugMode = () => {
|
||||
updatePreferences(PreferenceKeys.debugUnlocked, true);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { nightModeFollowSystem, nightMode, isDebugUnlocked } = this.state;
|
||||
return (
|
||||
<CollapsibleScrollView>
|
||||
<Card style={styles.card}>
|
||||
<Card.Title title={i18n.t('screens.settings.generalCard')} />
|
||||
<List.Section>
|
||||
{Appearance.getColorScheme() !== 'no-preference'
|
||||
? SettingsScreen.getToggleItem(
|
||||
this.onToggleNightModeFollowSystem,
|
||||
'theme-light-dark',
|
||||
i18n.t('screens.settings.nightModeAuto'),
|
||||
i18n.t('screens.settings.nightModeAutoSub'),
|
||||
nightModeFollowSystem
|
||||
)
|
||||
: null}
|
||||
{Appearance.getColorScheme() === 'no-preference' ||
|
||||
!nightModeFollowSystem
|
||||
? SettingsScreen.getToggleItem(
|
||||
this.onToggleNightMode,
|
||||
'theme-light-dark',
|
||||
i18n.t('screens.settings.nightMode'),
|
||||
nightMode
|
||||
? i18n.t('screens.settings.nightModeSubOn')
|
||||
: i18n.t('screens.settings.nightModeSubOff'),
|
||||
nightMode
|
||||
)
|
||||
: null}
|
||||
<List.Item
|
||||
title={i18n.t('screens.settings.startScreen')}
|
||||
description={i18n.t('screens.settings.startScreenSub')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
color={props.color}
|
||||
style={props.style}
|
||||
icon="power"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{this.getStartScreenPicker()}
|
||||
{this.getNavigateItem(
|
||||
'dashboard-edit',
|
||||
'view-dashboard',
|
||||
i18n.t('screens.settings.dashboard'),
|
||||
i18n.t('screens.settings.dashboardSub')
|
||||
return (
|
||||
<CollapsibleScrollView>
|
||||
<Card style={styles.card}>
|
||||
<Card.Title title={i18n.t('screens.settings.generalCard')} />
|
||||
<List.Section>
|
||||
{Appearance.getColorScheme() !== 'no-preference'
|
||||
? getToggleItem(
|
||||
onToggleNightModeFollowSystem,
|
||||
'theme-light-dark',
|
||||
i18n.t('screens.settings.nightModeAuto'),
|
||||
i18n.t('screens.settings.nightModeAutoSub'),
|
||||
nightModeFollowSystem
|
||||
)
|
||||
: null}
|
||||
{Appearance.getColorScheme() === 'no-preference' ||
|
||||
!nightModeFollowSystem
|
||||
? getToggleItem(
|
||||
onToggleNightMode,
|
||||
'theme-light-dark',
|
||||
i18n.t('screens.settings.nightMode'),
|
||||
nightMode
|
||||
? i18n.t('screens.settings.nightModeSubOn')
|
||||
: i18n.t('screens.settings.nightModeSubOff'),
|
||||
nightMode
|
||||
)
|
||||
: null}
|
||||
<List.Item
|
||||
title={i18n.t('screens.settings.startScreen')}
|
||||
description={i18n.t('screens.settings.startScreenSub')}
|
||||
left={(props) => (
|
||||
<List.Icon color={props.color} style={props.style} icon="power" />
|
||||
)}
|
||||
</List.Section>
|
||||
</Card>
|
||||
<Card style={styles.card}>
|
||||
<Card.Title title="Proxiwash" />
|
||||
<List.Section>
|
||||
<List.Item
|
||||
title={i18n.t('screens.settings.proxiwashNotifReminder')}
|
||||
description={i18n.t('screens.settings.proxiwashNotifReminderSub')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
color={props.color}
|
||||
style={props.style}
|
||||
icon="washing-machine"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<View style={styles.pickerContainer}>
|
||||
{this.getProxiwashNotifPicker()}
|
||||
</View>
|
||||
<List.Item
|
||||
title={i18n.t('screens.settings.proxiwashChangeWash')}
|
||||
description={i18n.t('screens.settings.proxiwashChangeWashSub')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
color={props.color}
|
||||
style={props.style}
|
||||
icon="washing-machine"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<View style={styles.pickerContainer}>
|
||||
{this.getProxiwashChangePicker()}
|
||||
</View>
|
||||
</List.Section>
|
||||
</Card>
|
||||
<Card style={styles.card}>
|
||||
<Card.Title title={i18n.t('screens.settings.information')} />
|
||||
<List.Section>
|
||||
{isDebugUnlocked
|
||||
? this.getNavigateItem(
|
||||
'debug',
|
||||
'bug-check',
|
||||
i18n.t('screens.debug.title'),
|
||||
''
|
||||
)
|
||||
: null}
|
||||
{this.getNavigateItem(
|
||||
'about',
|
||||
'information',
|
||||
i18n.t('screens.about.title'),
|
||||
i18n.t('screens.about.buttonDesc'),
|
||||
this.unlockDebugMode
|
||||
/>
|
||||
{getStartScreenPicker()}
|
||||
{getNavigateItem(
|
||||
'dashboard-edit',
|
||||
'view-dashboard',
|
||||
i18n.t('screens.settings.dashboard'),
|
||||
i18n.t('screens.settings.dashboardSub')
|
||||
)}
|
||||
</List.Section>
|
||||
</Card>
|
||||
<Card style={styles.card}>
|
||||
<Card.Title title="Proxiwash" />
|
||||
<List.Section>
|
||||
<List.Item
|
||||
title={i18n.t('screens.settings.proxiwashNotifReminder')}
|
||||
description={i18n.t('screens.settings.proxiwashNotifReminderSub')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
color={props.color}
|
||||
style={props.style}
|
||||
icon="washing-machine"
|
||||
/>
|
||||
)}
|
||||
{this.getNavigateItem(
|
||||
'feedback',
|
||||
'comment-quote',
|
||||
i18n.t('screens.feedback.homeButtonTitle'),
|
||||
i18n.t('screens.feedback.homeButtonSubtitle')
|
||||
/>
|
||||
<View style={styles.pickerContainer}>
|
||||
{getProxiwashNotifPicker()}
|
||||
</View>
|
||||
<List.Item
|
||||
title={i18n.t('screens.settings.proxiwashChangeWash')}
|
||||
description={i18n.t('screens.settings.proxiwashChangeWashSub')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
color={props.color}
|
||||
style={props.style}
|
||||
icon="washing-machine"
|
||||
/>
|
||||
)}
|
||||
</List.Section>
|
||||
</Card>
|
||||
</CollapsibleScrollView>
|
||||
);
|
||||
}
|
||||
/>
|
||||
<View style={styles.pickerContainer}>
|
||||
{getProxiwashChangePicker()}
|
||||
</View>
|
||||
</List.Section>
|
||||
</Card>
|
||||
<Card style={styles.card}>
|
||||
<Card.Title title={i18n.t('screens.settings.information')} />
|
||||
<List.Section>
|
||||
{isDebugUnlocked
|
||||
? getNavigateItem(
|
||||
'debug',
|
||||
'bug-check',
|
||||
i18n.t('screens.debug.title'),
|
||||
''
|
||||
)
|
||||
: null}
|
||||
{getNavigateItem(
|
||||
'about',
|
||||
'information',
|
||||
i18n.t('screens.about.title'),
|
||||
i18n.t('screens.about.buttonDesc'),
|
||||
unlockDebugMode
|
||||
)}
|
||||
{getNavigateItem(
|
||||
'feedback',
|
||||
'comment-quote',
|
||||
i18n.t('screens.feedback.homeButtonTitle'),
|
||||
i18n.t('screens.feedback.homeButtonSubtitle')
|
||||
)}
|
||||
</List.Section>
|
||||
</Card>
|
||||
</CollapsibleScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTheme(SettingsScreen);
|
||||
export default SettingsScreen;
|
||||
|
|
|
@ -17,23 +17,19 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import i18n from 'i18n-js';
|
||||
import { Searchbar } from 'react-native-paper';
|
||||
import { stringMatchQuery } from '../../utils/Search';
|
||||
import WebSectionList from '../../components/Screens/WebSectionList';
|
||||
import GroupListAccordion from '../../components/Lists/PlanexGroups/GroupListAccordion';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import Urls from '../../constants/Urls';
|
||||
import { readData } from '../../utils/WebData';
|
||||
import { useNavigation } from '@react-navigation/core';
|
||||
import { useCachedPlanexGroups } from '../../context/cacheContext';
|
||||
import { usePreferences } from '../../context/preferencesContext';
|
||||
import { getPreferenceObject, PreferenceKeys } from '../../utils/asyncStorage';
|
||||
|
||||
export type PlanexGroupType = {
|
||||
name: string;
|
||||
|
@ -63,13 +59,23 @@ function sortName(
|
|||
|
||||
function GroupSelectionScreen() {
|
||||
const navigation = useNavigation();
|
||||
const { preferences, updatePreferences } = usePreferences();
|
||||
const { groups, setGroups } = useCachedPlanexGroups();
|
||||
const [currentSearchString, setCurrentSearchString] = useState('');
|
||||
const [favoriteGroups, setFavoriteGroups] = useState<Array<PlanexGroupType>>(
|
||||
AsyncStorageManager.getObject(
|
||||
AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key
|
||||
)
|
||||
);
|
||||
|
||||
const getFavoriteGroups = (): Array<PlanexGroupType> => {
|
||||
const data = getPreferenceObject(
|
||||
PreferenceKeys.planexFavoriteGroups,
|
||||
preferences
|
||||
);
|
||||
if (data) {
|
||||
return data as Array<PlanexGroupType>;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const favoriteGroups = getFavoriteGroups();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
|
@ -140,10 +146,8 @@ function GroupSelectionScreen() {
|
|||
* @param item The article pressed
|
||||
*/
|
||||
const onListItemPress = (item: PlanexGroupType) => {
|
||||
navigation.navigate('planex', {
|
||||
screen: 'index',
|
||||
params: { group: item },
|
||||
});
|
||||
updatePreferences(PreferenceKeys.planexCurrentGroup, item);
|
||||
navigation.goBack();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -153,12 +157,16 @@ function GroupSelectionScreen() {
|
|||
*/
|
||||
const onListFavoritePress = useCallback(
|
||||
(group: PlanexGroupType) => {
|
||||
const updateFavorites = (newValue: Array<PlanexGroupType>) => {
|
||||
updatePreferences(PreferenceKeys.planexFavoriteGroups, newValue);
|
||||
};
|
||||
|
||||
const removeGroupFromFavorites = (g: PlanexGroupType) => {
|
||||
setFavoriteGroups(favoriteGroups.filter((f) => f.id !== g.id));
|
||||
updateFavorites(favoriteGroups.filter((f) => f.id !== g.id));
|
||||
};
|
||||
|
||||
const addGroupToFavorites = (g: PlanexGroupType) => {
|
||||
setFavoriteGroups([...favoriteGroups, g].sort(sortName));
|
||||
updateFavorites([...favoriteGroups, g].sort(sortName));
|
||||
};
|
||||
|
||||
if (favoriteGroups.some((f) => f.id === group.id)) {
|
||||
|
@ -167,16 +175,9 @@ function GroupSelectionScreen() {
|
|||
addGroupToFavorites(group);
|
||||
}
|
||||
},
|
||||
[favoriteGroups]
|
||||
[favoriteGroups, updatePreferences]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
|
||||
favoriteGroups
|
||||
);
|
||||
}, [favoriteGroups]);
|
||||
|
||||
/**
|
||||
* Generates the dataset to be used in the FlatList.
|
||||
* This improves formatting of group names, sorts alphabetically the categories, and adds favorites at the top.
|
||||
|
|
|
@ -17,17 +17,12 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Title, useTheme } from 'react-native-paper';
|
||||
import i18n from 'i18n-js';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import {
|
||||
CommonActions,
|
||||
useFocusEffect,
|
||||
useNavigation,
|
||||
} from '@react-navigation/native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import Autolink from 'react-native-autolink';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import AlertDialog from '../../components/Dialogs/AlertDialog';
|
||||
import { dateToString, getTimeOnlyString } from '../../utils/Planning';
|
||||
import DateManager from '../../managers/DateManager';
|
||||
|
@ -38,6 +33,8 @@ import { getPrettierPlanexGroupName } from '../../utils/Utils';
|
|||
import GENERAL_STYLES from '../../constants/Styles';
|
||||
import PlanexWebview from '../../components/Screens/PlanexWebview';
|
||||
import PlanexBottomBar from '../../components/Animations/PlanexBottomBar';
|
||||
import { usePreferences } from '../../context/preferencesContext';
|
||||
import { getPreferenceString, PreferenceKeys } from '../../utils/asyncStorage';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -50,17 +47,10 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
route: {
|
||||
params: {
|
||||
group?: PlanexGroupType;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function PlanexScreen(props: Props) {
|
||||
function PlanexScreen() {
|
||||
const navigation = useNavigation();
|
||||
const theme = useTheme();
|
||||
const { preferences } = usePreferences();
|
||||
|
||||
const [dialogContent, setDialogContent] = useState<
|
||||
| undefined
|
||||
|
@ -72,12 +62,13 @@ function PlanexScreen(props: Props) {
|
|||
>();
|
||||
const [injectJS, setInjectJS] = useState('');
|
||||
|
||||
const getCurrentGroup = (): PlanexGroupType | undefined => {
|
||||
let currentGroupString = AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.planexCurrentGroup.key
|
||||
const getCurrentGroup: () => PlanexGroupType | undefined = useCallback(() => {
|
||||
let currentGroupString = getPreferenceString(
|
||||
PreferenceKeys.planexCurrentGroup,
|
||||
preferences
|
||||
);
|
||||
let group: PlanexGroupType;
|
||||
if (currentGroupString !== '') {
|
||||
if (currentGroupString) {
|
||||
group = JSON.parse(currentGroupString);
|
||||
navigation.setOptions({
|
||||
title: getPrettierPlanexGroupName(group.name),
|
||||
|
@ -85,22 +76,10 @@ function PlanexScreen(props: Props) {
|
|||
return group;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}, [navigation, preferences]);
|
||||
|
||||
const [currentGroup, setCurrentGroup] = useState<PlanexGroupType | undefined>(
|
||||
getCurrentGroup()
|
||||
);
|
||||
const currentGroup = getCurrentGroup();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (props.route.params?.group) {
|
||||
// reset params to prevent infinite loop
|
||||
selectNewGroup(props.route.params.group);
|
||||
navigation.dispatch(CommonActions.setParams({ group: undefined }));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.route.params])
|
||||
);
|
||||
/**
|
||||
* Gets the Webview, with an error view on top if no group is selected.
|
||||
*
|
||||
|
@ -194,21 +173,20 @@ function PlanexScreen(props: Props) {
|
|||
|
||||
const hideDialog = () => setDialogContent(undefined);
|
||||
|
||||
/**
|
||||
* Sends the webpage a message with the new group to select and save it to preferences
|
||||
*
|
||||
* @param group The group object selected
|
||||
*/
|
||||
const selectNewGroup = (group: PlanexGroupType) => {
|
||||
sendMessage('setGroup', group.id.toString());
|
||||
setCurrentGroup(group);
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.planexCurrentGroup.key,
|
||||
group
|
||||
);
|
||||
useEffect(() => {
|
||||
const group = getCurrentGroup();
|
||||
if (group) {
|
||||
sendMessage('setGroup', group.id.toString());
|
||||
navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [getCurrentGroup, navigation]);
|
||||
|
||||
navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
|
||||
};
|
||||
const showMascot =
|
||||
getPreferenceString(
|
||||
PreferenceKeys.defaultStartScreen,
|
||||
preferences
|
||||
)?.toLowerCase() !== 'planex';
|
||||
|
||||
return (
|
||||
<View style={GENERAL_STYLES.flex}>
|
||||
|
@ -220,11 +198,8 @@ function PlanexScreen(props: Props) {
|
|||
<View style={GENERAL_STYLES.flex}>{getWebView()}</View>
|
||||
)}
|
||||
</View>
|
||||
{AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.defaultStartScreen.key
|
||||
).toLowerCase() !== 'planex' ? (
|
||||
{showMascot ? (
|
||||
<MascotPopup
|
||||
prefKey={AsyncStorageManager.PREFERENCES.planexShowMascot.key}
|
||||
title={i18n.t('screens.planex.mascotDialog.title')}
|
||||
message={i18n.t('screens.planex.mascotDialog.message')}
|
||||
icon="emoticon-kiss"
|
||||
|
|
|
@ -34,7 +34,6 @@ import {
|
|||
import CustomAgenda from '../../components/Overrides/CustomAgenda';
|
||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import GENERAL_STYLES from '../../constants/Styles';
|
||||
import Urls from '../../constants/Urls';
|
||||
|
||||
|
@ -291,7 +290,6 @@ class PlanningScreen extends React.Component<PropsType, StateType> {
|
|||
}
|
||||
/>
|
||||
<MascotPopup
|
||||
prefKey={AsyncStorageManager.PREFERENCES.eventsShowMascot.key}
|
||||
title={i18n.t('screens.planning.mascotDialog.title')}
|
||||
message={i18n.t('screens.planning.mascotDialog.message')}
|
||||
icon="party-popper"
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import React, { useLayoutEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
SectionListData,
|
||||
SectionListRenderItemInfo,
|
||||
|
@ -28,7 +28,6 @@ import i18n from 'i18n-js';
|
|||
import { Avatar, Button, Card, Text, useTheme } from 'react-native-paper';
|
||||
import { Modalize } from 'react-native-modalize';
|
||||
import WebSectionList from '../../components/Screens/WebSectionList';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import ProxiwashListItem from '../../components/Lists/Proxiwash/ProxiwashListItem';
|
||||
import ProxiwashConstants, {
|
||||
MachineStates,
|
||||
|
@ -50,9 +49,16 @@ import type { SectionListDataType } from '../../components/Screens/WebSectionLis
|
|||
import type { LaundromatType } from './ProxiwashAboutScreen';
|
||||
import GENERAL_STYLES from '../../constants/Styles';
|
||||
import { readData } from '../../utils/WebData';
|
||||
import { useFocusEffect, useNavigation } from '@react-navigation/core';
|
||||
import { useNavigation } from '@react-navigation/core';
|
||||
import { setupMachineNotification } from '../../utils/Notifications';
|
||||
import ProximoListHeader from '../../components/Lists/Proximo/ProximoListHeader';
|
||||
import { usePreferences } from '../../context/preferencesContext';
|
||||
import {
|
||||
getPreferenceNumber,
|
||||
getPreferenceObject,
|
||||
getPreferenceString,
|
||||
PreferenceKeys,
|
||||
} from '../../utils/asyncStorage';
|
||||
|
||||
const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds
|
||||
const LIST_ITEM_HEIGHT = 64;
|
||||
|
@ -91,23 +97,35 @@ const styles = StyleSheet.create({
|
|||
function ProxiwashScreen() {
|
||||
const navigation = useNavigation();
|
||||
const theme = useTheme();
|
||||
const { preferences, updatePreferences } = usePreferences();
|
||||
const [
|
||||
modalCurrentDisplayItem,
|
||||
setModalCurrentDisplayItem,
|
||||
] = useState<React.ReactElement | null>(null);
|
||||
const [machinesWatched, setMachinesWatched] = useState<
|
||||
Array<ProxiwashMachineType>
|
||||
>(
|
||||
AsyncStorageManager.getObject(
|
||||
AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key
|
||||
)
|
||||
const reminder = getPreferenceNumber(
|
||||
PreferenceKeys.proxiwashNotifications,
|
||||
preferences
|
||||
);
|
||||
|
||||
const [selectedWash, setSelectedWash] = useState(
|
||||
AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.selectedWash.key
|
||||
) as 'tripodeB' | 'washinsa'
|
||||
);
|
||||
const getMachinesWatched = () => {
|
||||
const data = getPreferenceObject(
|
||||
PreferenceKeys.proxiwashWatchedMachines,
|
||||
preferences
|
||||
) as Array<ProxiwashMachineType>;
|
||||
return data ? (data as Array<ProxiwashMachineType>) : [];
|
||||
};
|
||||
|
||||
const getSelectedWash = () => {
|
||||
const data = getPreferenceString(PreferenceKeys.selectedWash, preferences);
|
||||
if (data !== 'washinsa' && data !== 'tripodeB') {
|
||||
return 'washinsa';
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
const machinesWatched: Array<ProxiwashMachineType> = getMachinesWatched();
|
||||
const selectedWash: 'washinsa' | 'tripodeB' = getSelectedWash();
|
||||
|
||||
const modalStateStrings: { [key in MachineStates]: string } = {
|
||||
[MachineStates.AVAILABLE]: i18n.t('screens.proxiwash.modal.ready'),
|
||||
|
@ -137,17 +155,6 @@ function ProxiwashScreen() {
|
|||
});
|
||||
}, [navigation]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const selected = AsyncStorageManager.getString(
|
||||
AsyncStorageManager.PREFERENCES.selectedWash.key
|
||||
) as 'tripodeB' | 'washinsa';
|
||||
if (selected !== selectedWash) {
|
||||
setSelectedWash(selected);
|
||||
}
|
||||
}, [selectedWash])
|
||||
);
|
||||
|
||||
/**
|
||||
* Callback used when the user clicks on enable notifications for a machine
|
||||
*
|
||||
|
@ -293,6 +300,7 @@ function ProxiwashScreen() {
|
|||
setupMachineNotification(
|
||||
machine.number,
|
||||
true,
|
||||
reminder,
|
||||
getMachineEndDate(machine)
|
||||
);
|
||||
saveNotificationToState(machine);
|
||||
|
@ -342,7 +350,7 @@ function ProxiwashScreen() {
|
|||
...data.washers,
|
||||
]);
|
||||
if (cleanedList !== machinesWatched) {
|
||||
setMachinesWatched(machinesWatched);
|
||||
updatePreferences(PreferenceKeys.proxiwashWatchedMachines, cleanedList);
|
||||
}
|
||||
return [
|
||||
{
|
||||
|
@ -407,11 +415,7 @@ function ProxiwashScreen() {
|
|||
};
|
||||
|
||||
const saveNewWatchedList = (list: Array<ProxiwashMachineType>) => {
|
||||
setMachinesWatched(list);
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key,
|
||||
list
|
||||
);
|
||||
updatePreferences(PreferenceKeys.proxiwashWatchedMachines, list);
|
||||
};
|
||||
|
||||
const renderListHeaderComponent = (
|
||||
|
@ -451,7 +455,6 @@ function ProxiwashScreen() {
|
|||
/>
|
||||
</View>
|
||||
<MascotPopup
|
||||
prefKey={AsyncStorageManager.PREFERENCES.proxiwashShowMascot.key}
|
||||
title={i18n.t('screens.proxiwash.mascotDialog.title')}
|
||||
message={i18n.t('screens.proxiwash.mascotDialog.message')}
|
||||
icon="information"
|
||||
|
|
|
@ -35,12 +35,12 @@ import MaterialHeaderButtons, {
|
|||
} from '../../components/Overrides/CustomHeaderButton';
|
||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||
import ServicesManager, {
|
||||
SERVICES_CATEGORIES_KEY,
|
||||
} from '../../managers/ServicesManager';
|
||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
||||
import type { ServiceCategoryType } from '../../managers/ServicesManager';
|
||||
import {
|
||||
getCategories,
|
||||
ServiceCategoryType,
|
||||
SERVICES_CATEGORIES_KEY,
|
||||
} from '../../utils/Services';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
|
@ -66,8 +66,7 @@ class ServicesScreen extends React.Component<PropsType> {
|
|||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
const services = new ServicesManager(props.navigation);
|
||||
this.finalDataset = services.getCategories([
|
||||
this.finalDataset = getCategories(props.navigation.navigate, [
|
||||
SERVICES_CATEGORIES_KEY.SPECIAL,
|
||||
]);
|
||||
}
|
||||
|
@ -159,7 +158,6 @@ class ServicesScreen extends React.Component<PropsType> {
|
|||
hasTab
|
||||
/>
|
||||
<MascotPopup
|
||||
prefKey={AsyncStorageManager.PREFERENCES.servicesShowMascot.key}
|
||||
title={i18n.t('screens.services.mascotDialog.title')}
|
||||
message={i18n.t('screens.services.mascotDialog.message')}
|
||||
icon="cloud-question"
|
||||
|
|
|
@ -22,7 +22,7 @@ import { Collapsible } from 'react-navigation-collapsible';
|
|||
import { CommonActions } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import CardList from '../../components/Lists/CardList/CardList';
|
||||
import type { ServiceCategoryType } from '../../managers/ServicesManager';
|
||||
import { ServiceCategoryType } from '../../utils/Services';
|
||||
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp<any>;
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
|
||||
import i18n from 'i18n-js';
|
||||
import AsyncStorageManager from '../managers/AsyncStorageManager';
|
||||
import PushNotificationIOS from '@react-native-community/push-notification-ios';
|
||||
import PushNotification from 'react-native-push-notification';
|
||||
import { Platform } from 'react-native';
|
||||
|
@ -79,11 +78,8 @@ PushNotification.configure({
|
|||
* @param machineID The machine id to schedule notifications for. This is used as id and in the notification string.
|
||||
* @param date The date to trigger the notification at
|
||||
*/
|
||||
function createNotifications(machineID: string, date: Date) {
|
||||
const reminder = AsyncStorageManager.getNumber(
|
||||
AsyncStorageManager.PREFERENCES.proxiwashNotifications.key
|
||||
);
|
||||
if (!Number.isNaN(reminder) && reminder > 0) {
|
||||
function createNotifications(machineID: string, date: Date, reminder?: number) {
|
||||
if (reminder && !Number.isNaN(reminder) && reminder > 0) {
|
||||
const id = reminderIdFactor * parseInt(machineID, 10);
|
||||
const reminderDate = new Date(date);
|
||||
reminderDate.setMinutes(reminderDate.getMinutes() - reminder);
|
||||
|
@ -122,10 +118,11 @@ function createNotifications(machineID: string, date: Date) {
|
|||
export function setupMachineNotification(
|
||||
machineID: string,
|
||||
isEnabled: boolean,
|
||||
reminder?: number,
|
||||
endDate?: Date | null
|
||||
) {
|
||||
if (isEnabled && endDate) {
|
||||
createNotifications(machineID, endDate);
|
||||
createNotifications(machineID, endDate, reminder);
|
||||
} else {
|
||||
PushNotification.cancelLocalNotifications({ id: machineID });
|
||||
const reminderId = reminderIdFactor * parseInt(machineID, 10);
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import i18n from 'i18n-js';
|
||||
import type { FullDashboardType } from '../screens/Home/HomeScreen';
|
||||
import Urls from '../constants/Urls';
|
||||
import { MainRoutes } from '../navigation/MainNavigator';
|
||||
import { TabRoutes } from '../navigation/TabNavigator';
|
||||
|
||||
/**
|
||||
* Gets the given services list without items of the given ids
|
||||
|
@ -25,45 +30,296 @@
|
|||
* @returns {[]}
|
||||
*/
|
||||
export default function getStrippedServicesList<T extends { key: string }>(
|
||||
idList: Array<string>,
|
||||
sourceList: Array<T>
|
||||
sourceList: Array<T>,
|
||||
idList?: Array<string>
|
||||
) {
|
||||
const newArray: Array<T> = [];
|
||||
sourceList.forEach((item: T) => {
|
||||
if (!idList.includes(item.key)) {
|
||||
newArray.push(item);
|
||||
}
|
||||
});
|
||||
return newArray;
|
||||
if (idList) {
|
||||
return sourceList.filter((item) => !idList.includes(item.key));
|
||||
} else {
|
||||
return sourceList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a sublist of the given list with items of the given ids only
|
||||
*
|
||||
* The given list must have a field id or key
|
||||
*
|
||||
* @param idList The ids of items to find
|
||||
* @param originalList The original list
|
||||
* @returns {[]}
|
||||
*/
|
||||
export function getSublistWithIds<T extends { key: string }>(
|
||||
idList: Array<string>,
|
||||
originalList: Array<T>
|
||||
) {
|
||||
const subList: Array<T | null> = [];
|
||||
for (let i = 0; i < idList.length; i += 1) {
|
||||
subList.push(null);
|
||||
}
|
||||
let itemsAdded = 0;
|
||||
for (let i = 0; i < originalList.length; i += 1) {
|
||||
const item = originalList[i];
|
||||
if (idList.includes(item.key)) {
|
||||
subList[idList.indexOf(item.key)] = item;
|
||||
itemsAdded += 1;
|
||||
if (itemsAdded === idList.length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return subList;
|
||||
const AMICALE_LOGO = require('../../assets/amicale.png');
|
||||
|
||||
export const SERVICES_KEY = {
|
||||
CLUBS: 'clubs',
|
||||
PROFILE: 'profile',
|
||||
EQUIPMENT: 'equipment',
|
||||
AMICALE_WEBSITE: 'amicale_website',
|
||||
VOTE: 'vote',
|
||||
PROXIMO: 'proximo',
|
||||
WIKETUD: 'wiketud',
|
||||
ELUS_ETUDIANTS: 'elus_etudiants',
|
||||
TUTOR_INSA: 'tutor_insa',
|
||||
RU: 'ru',
|
||||
AVAILABLE_ROOMS: 'available_rooms',
|
||||
BIB: 'bib',
|
||||
EMAIL: 'email',
|
||||
ENT: 'ent',
|
||||
INSA_ACCOUNT: 'insa_account',
|
||||
WASHERS: 'washers',
|
||||
DRYERS: 'dryers',
|
||||
};
|
||||
|
||||
export const SERVICES_CATEGORIES_KEY = {
|
||||
AMICALE: 'amicale',
|
||||
STUDENTS: 'students',
|
||||
INSA: 'insa',
|
||||
SPECIAL: 'special',
|
||||
};
|
||||
|
||||
export type ServiceItemType = {
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
image: string | number;
|
||||
onPress: () => void;
|
||||
badgeFunction?: (dashboard: FullDashboardType) => number;
|
||||
};
|
||||
|
||||
export type ServiceCategoryType = {
|
||||
key: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
image: string | number;
|
||||
content: Array<ServiceItemType>;
|
||||
};
|
||||
|
||||
export function getAmicaleServices(
|
||||
onPress: (route: string, params?: { [key: string]: any }) => void,
|
||||
excludedItems?: Array<string>
|
||||
): Array<ServiceItemType> {
|
||||
const amicaleDataset = [
|
||||
{
|
||||
key: SERVICES_KEY.CLUBS,
|
||||
title: i18n.t('screens.clubs.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.clubs'),
|
||||
image: Urls.images.clubs,
|
||||
onPress: () => onPress(MainRoutes.ClubList),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.PROFILE,
|
||||
title: i18n.t('screens.profile.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.profile'),
|
||||
image: Urls.images.profile,
|
||||
onPress: () => onPress(MainRoutes.Profile),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.EQUIPMENT,
|
||||
title: i18n.t('screens.equipment.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.equipment'),
|
||||
image: Urls.images.equipment,
|
||||
onPress: () => onPress(MainRoutes.EquipmentList),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.AMICALE_WEBSITE,
|
||||
title: i18n.t('screens.websites.amicale'),
|
||||
subtitle: i18n.t('screens.services.descriptions.amicaleWebsite'),
|
||||
image: Urls.images.amicale,
|
||||
onPress: () =>
|
||||
onPress(MainRoutes.Website, {
|
||||
host: Urls.websites.amicale,
|
||||
title: i18n.t('screens.websites.amicale'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.VOTE,
|
||||
title: i18n.t('screens.vote.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.vote'),
|
||||
image: Urls.images.vote,
|
||||
onPress: () => onPress(MainRoutes.Vote),
|
||||
},
|
||||
];
|
||||
return getStrippedServicesList(amicaleDataset, excludedItems);
|
||||
}
|
||||
|
||||
export function getStudentServices(
|
||||
onPress: (route: string, params?: { [key: string]: any }) => void,
|
||||
excludedItems?: Array<string>
|
||||
): Array<ServiceItemType> {
|
||||
const studentsDataset = [
|
||||
{
|
||||
key: SERVICES_KEY.PROXIMO,
|
||||
title: i18n.t('screens.proximo.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.proximo'),
|
||||
image: Urls.images.proximo,
|
||||
onPress: () => onPress(MainRoutes.Proximo),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.proximo_articles,
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.WIKETUD,
|
||||
title: 'Wiketud',
|
||||
subtitle: i18n.t('screens.services.descriptions.wiketud'),
|
||||
image: Urls.images.wiketud,
|
||||
onPress: () =>
|
||||
onPress(MainRoutes.Website, {
|
||||
host: Urls.websites.wiketud,
|
||||
title: 'Wiketud',
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.ELUS_ETUDIANTS,
|
||||
title: 'Élus Étudiants',
|
||||
subtitle: i18n.t('screens.services.descriptions.elusEtudiants'),
|
||||
image: Urls.images.elusEtudiants,
|
||||
onPress: () =>
|
||||
onPress(MainRoutes.Website, {
|
||||
host: Urls.websites.elusEtudiants,
|
||||
title: 'Élus Étudiants',
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.TUTOR_INSA,
|
||||
title: "Tutor'INSA",
|
||||
subtitle: i18n.t('screens.services.descriptions.tutorInsa'),
|
||||
image: Urls.images.tutorInsa,
|
||||
onPress: () =>
|
||||
onPress(MainRoutes.Website, {
|
||||
host: Urls.websites.tutorInsa,
|
||||
title: "Tutor'INSA",
|
||||
}),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.available_tutorials,
|
||||
},
|
||||
];
|
||||
return getStrippedServicesList(studentsDataset, excludedItems);
|
||||
}
|
||||
|
||||
export function getINSAServices(
|
||||
onPress: (route: string, params?: { [key: string]: any }) => void,
|
||||
excludedItems?: Array<string>
|
||||
): Array<ServiceItemType> {
|
||||
const insaDataset = [
|
||||
{
|
||||
key: SERVICES_KEY.RU,
|
||||
title: i18n.t('screens.menu.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.self'),
|
||||
image: Urls.images.menu,
|
||||
onPress: () => onPress(MainRoutes.SelfMenu),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.today_menu.length,
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.AVAILABLE_ROOMS,
|
||||
title: i18n.t('screens.websites.rooms'),
|
||||
subtitle: i18n.t('screens.services.descriptions.availableRooms'),
|
||||
image: Urls.images.availableRooms,
|
||||
onPress: () =>
|
||||
onPress(MainRoutes.Website, {
|
||||
host: Urls.websites.availableRooms,
|
||||
title: i18n.t('screens.websites.rooms'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.BIB,
|
||||
title: i18n.t('screens.websites.bib'),
|
||||
subtitle: i18n.t('screens.services.descriptions.bib'),
|
||||
image: Urls.images.bib,
|
||||
onPress: () =>
|
||||
onPress(MainRoutes.Website, {
|
||||
host: Urls.websites.bib,
|
||||
title: i18n.t('screens.websites.bib'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.EMAIL,
|
||||
title: i18n.t('screens.websites.mails'),
|
||||
subtitle: i18n.t('screens.services.descriptions.mails'),
|
||||
image: Urls.images.bluemind,
|
||||
onPress: () =>
|
||||
onPress(MainRoutes.Website, {
|
||||
host: Urls.websites.bluemind,
|
||||
title: i18n.t('screens.websites.mails'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.ENT,
|
||||
title: i18n.t('screens.websites.ent'),
|
||||
subtitle: i18n.t('screens.services.descriptions.ent'),
|
||||
image: Urls.images.ent,
|
||||
onPress: () =>
|
||||
onPress(MainRoutes.Website, {
|
||||
host: Urls.websites.ent,
|
||||
title: i18n.t('screens.websites.ent'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.INSA_ACCOUNT,
|
||||
title: i18n.t('screens.insaAccount.title'),
|
||||
subtitle: i18n.t('screens.services.descriptions.insaAccount'),
|
||||
image: Urls.images.insaAccount,
|
||||
onPress: () =>
|
||||
onPress(MainRoutes.Website, {
|
||||
host: Urls.websites.insaAccount,
|
||||
title: i18n.t('screens.insaAccount.title'),
|
||||
}),
|
||||
},
|
||||
];
|
||||
return getStrippedServicesList(insaDataset, excludedItems);
|
||||
}
|
||||
|
||||
export function getSpecialServices(
|
||||
onPress: (route: string, params?: { [key: string]: any }) => void,
|
||||
excludedItems?: Array<string>
|
||||
): Array<ServiceItemType> {
|
||||
const specialDataset = [
|
||||
{
|
||||
key: SERVICES_KEY.WASHERS,
|
||||
title: i18n.t('screens.proxiwash.washers'),
|
||||
subtitle: i18n.t('screens.services.descriptions.washers'),
|
||||
image: Urls.images.washer,
|
||||
onPress: () => onPress(TabRoutes.Proxiwash),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.available_washers,
|
||||
},
|
||||
{
|
||||
key: SERVICES_KEY.DRYERS,
|
||||
title: i18n.t('screens.proxiwash.dryers'),
|
||||
subtitle: i18n.t('screens.services.descriptions.washers'),
|
||||
image: Urls.images.dryer,
|
||||
onPress: () => onPress(TabRoutes.Proxiwash),
|
||||
badgeFunction: (dashboard: FullDashboardType): number =>
|
||||
dashboard.available_dryers,
|
||||
},
|
||||
];
|
||||
return getStrippedServicesList(specialDataset, excludedItems);
|
||||
}
|
||||
|
||||
export function getCategories(
|
||||
onPress: (route: string, params?: { [key: string]: any }) => void,
|
||||
excludedItems?: Array<string>
|
||||
): Array<ServiceCategoryType> {
|
||||
const categoriesDataset = [
|
||||
{
|
||||
key: SERVICES_CATEGORIES_KEY.AMICALE,
|
||||
title: i18n.t('screens.services.categories.amicale'),
|
||||
subtitle: i18n.t('screens.services.more'),
|
||||
image: AMICALE_LOGO,
|
||||
content: getAmicaleServices(onPress),
|
||||
},
|
||||
{
|
||||
key: SERVICES_CATEGORIES_KEY.STUDENTS,
|
||||
title: i18n.t('screens.services.categories.students'),
|
||||
subtitle: i18n.t('screens.services.more'),
|
||||
image: 'account-group',
|
||||
content: getStudentServices(onPress),
|
||||
},
|
||||
{
|
||||
key: SERVICES_CATEGORIES_KEY.INSA,
|
||||
title: i18n.t('screens.services.categories.insa'),
|
||||
subtitle: i18n.t('screens.services.more'),
|
||||
image: 'school',
|
||||
content: getINSAServices(onPress),
|
||||
},
|
||||
{
|
||||
key: SERVICES_CATEGORIES_KEY.SPECIAL,
|
||||
title: i18n.t('screens.services.categories.special'),
|
||||
subtitle: i18n.t('screens.services.categories.special'),
|
||||
image: 'star',
|
||||
content: getSpecialServices(onPress),
|
||||
},
|
||||
];
|
||||
return getStrippedServicesList(categoriesDataset, excludedItems);
|
||||
}
|
||||
|
|
|
@ -1,28 +1,4 @@
|
|||
/*
|
||||
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
||||
*
|
||||
* This file is part of Campus INSAT.
|
||||
*
|
||||
* Campus INSAT is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Campus INSAT is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { DarkTheme, DefaultTheme } from 'react-native-paper';
|
||||
import { Appearance } from 'react-native-appearance';
|
||||
import AsyncStorageManager from './AsyncStorageManager';
|
||||
import AprilFoolsManager from './AprilFoolsManager';
|
||||
|
||||
const colorScheme = Appearance.getColorScheme();
|
||||
|
||||
declare global {
|
||||
namespace ReactNativePaper {
|
||||
|
@ -83,7 +59,7 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
const CustomWhiteTheme: ReactNativePaper.Theme = {
|
||||
export const CustomWhiteTheme: ReactNativePaper.Theme = {
|
||||
...DefaultTheme,
|
||||
colors: {
|
||||
...DefaultTheme.colors,
|
||||
|
@ -142,7 +118,7 @@ const CustomWhiteTheme: ReactNativePaper.Theme = {
|
|||
},
|
||||
};
|
||||
|
||||
const CustomDarkTheme: ReactNativePaper.Theme = {
|
||||
export const CustomDarkTheme: ReactNativePaper.Theme = {
|
||||
...DarkTheme,
|
||||
colors: {
|
||||
...DarkTheme.colors,
|
||||
|
@ -200,99 +176,3 @@ const CustomDarkTheme: ReactNativePaper.Theme = {
|
|||
mascotMessageArrow: '#323232',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton class used to manage themes
|
||||
*/
|
||||
export default class ThemeManager {
|
||||
static instance: ThemeManager | null = null;
|
||||
|
||||
updateThemeCallback: null | (() => void);
|
||||
|
||||
constructor() {
|
||||
this.updateThemeCallback = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this class instance or create one if none is found
|
||||
*
|
||||
* @returns {ThemeManager}
|
||||
*/
|
||||
static getInstance(): ThemeManager {
|
||||
if (ThemeManager.instance == null) {
|
||||
ThemeManager.instance = new ThemeManager();
|
||||
}
|
||||
return ThemeManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets night mode status.
|
||||
* If Follow System Preferences is enabled, will first use system theme.
|
||||
* If disabled or not available, will use value stored din preferences
|
||||
*
|
||||
* @returns {boolean} Night mode state
|
||||
*/
|
||||
static getNightMode(): boolean {
|
||||
return (
|
||||
(AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightMode.key
|
||||
) &&
|
||||
(!AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key
|
||||
) ||
|
||||
colorScheme === 'no-preference')) ||
|
||||
(AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key
|
||||
) &&
|
||||
colorScheme === 'dark')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current theme based on night mode and events
|
||||
*
|
||||
* @returns {ReactNativePaper.Theme} The current theme
|
||||
*/
|
||||
static getCurrentTheme(): ReactNativePaper.Theme {
|
||||
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
|
||||
return AprilFoolsManager.getAprilFoolsTheme(CustomWhiteTheme);
|
||||
}
|
||||
return ThemeManager.getBaseTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the theme based on night mode
|
||||
*
|
||||
* @return {ReactNativePaper.Theme} The theme
|
||||
*/
|
||||
static getBaseTheme(): ReactNativePaper.Theme {
|
||||
if (ThemeManager.getNightMode()) {
|
||||
return CustomDarkTheme;
|
||||
}
|
||||
return CustomWhiteTheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the function to be called when the theme is changed (allows for general reload of the app)
|
||||
*
|
||||
* @param callback Function to call after theme change
|
||||
*/
|
||||
setUpdateThemeCallback(callback: () => void) {
|
||||
this.updateThemeCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set night mode and save it to preferences
|
||||
*
|
||||
* @param isNightMode True to enable night mode, false to disable
|
||||
*/
|
||||
setNightMode(isNightMode: boolean) {
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.nightMode.key,
|
||||
isNightMode
|
||||
);
|
||||
if (this.updateThemeCallback != null) {
|
||||
this.updateThemeCallback();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,23 +18,21 @@
|
|||
*/
|
||||
|
||||
import { Platform, StatusBar } from 'react-native';
|
||||
import ThemeManager from '../managers/ThemeManager';
|
||||
|
||||
/**
|
||||
* Updates status bar content color if on iOS only,
|
||||
* as the android status bar is always set to black.
|
||||
*/
|
||||
export function setupStatusBar() {
|
||||
if (ThemeManager.getNightMode()) {
|
||||
StatusBar.setBarStyle('light-content', true);
|
||||
} else {
|
||||
StatusBar.setBarStyle('dark-content', true);
|
||||
}
|
||||
if (Platform.OS === 'android') {
|
||||
StatusBar.setBackgroundColor(
|
||||
ThemeManager.getCurrentTheme().colors.surface,
|
||||
true
|
||||
);
|
||||
export function setupStatusBar(theme?: ReactNativePaper.Theme) {
|
||||
if (theme) {
|
||||
if (theme.dark) {
|
||||
StatusBar.setBarStyle('light-content', true);
|
||||
} else {
|
||||
StatusBar.setBarStyle('dark-content', true);
|
||||
}
|
||||
if (Platform.OS === 'android') {
|
||||
StatusBar.setBackgroundColor(theme.colors.surface, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { SERVICES_KEY } from '../managers/ServicesManager';
|
||||
import { SERVICES_KEY } from './Services';
|
||||
|
||||
export enum PreferenceKeys {
|
||||
debugUnlocked = 'debugUnlocked',
|
||||
|
@ -9,6 +9,7 @@ export enum PreferenceKeys {
|
|||
nightModeFollowSystem = 'nightModeFollowSystem',
|
||||
nightMode = 'nightMode',
|
||||
defaultStartScreen = 'defaultStartScreen',
|
||||
|
||||
servicesShowMascot = 'servicesShowMascot',
|
||||
proxiwashShowMascot = 'proxiwashShowMascot',
|
||||
homeShowMascot = 'homeShowMascot',
|
||||
|
@ -17,7 +18,8 @@ export enum PreferenceKeys {
|
|||
loginShowMascot = 'loginShowMascot',
|
||||
voteShowMascot = 'voteShowMascot',
|
||||
equipmentShowMascot = 'equipmentShowMascot',
|
||||
gameStartMascot = 'gameStartMascot',
|
||||
gameShowMascot = 'gameShowMascot',
|
||||
|
||||
proxiwashWatchedMachines = 'proxiwashWatchedMachines',
|
||||
showAprilFoolsStart = 'showAprilFoolsStart',
|
||||
planexCurrentGroup = 'planexCurrentGroup',
|
||||
|
@ -45,7 +47,7 @@ export const defaultPreferences: { [key in PreferenceKeys]: string } = {
|
|||
[PreferenceKeys.loginShowMascot]: '1',
|
||||
[PreferenceKeys.voteShowMascot]: '1',
|
||||
[PreferenceKeys.equipmentShowMascot]: '1',
|
||||
[PreferenceKeys.gameStartMascot]: '1',
|
||||
[PreferenceKeys.gameShowMascot]: '1',
|
||||
[PreferenceKeys.proxiwashWatchedMachines]: '[]',
|
||||
[PreferenceKeys.showAprilFoolsStart]: '1',
|
||||
[PreferenceKeys.planexCurrentGroup]: '',
|
||||
|
@ -114,6 +116,10 @@ export function setPreference(
|
|||
return prevPreferences;
|
||||
}
|
||||
|
||||
export function isValidPreferenceKey(key: string): key is PreferenceKeys {
|
||||
return key in Object.values(PreferenceKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the boolean value of the given preference
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue