Compare commits
No commits in common. "b15b200846eff925163a94c6933210f07c63bc71" and "b5d4ad83c3678846d69fad605bfd9e9c1e2ba629" have entirely different histories.
b15b200846
...
b5d4ad83c3
54 changed files with 2853 additions and 2632 deletions
211
App.tsx
211
App.tsx
|
|
@ -18,36 +18,27 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { LogBox, Platform } from 'react-native';
|
import { LogBox, Platform, SafeAreaView, View } from 'react-native';
|
||||||
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
|
import { Provider as PaperProvider } from 'react-native-paper';
|
||||||
import { setSafeBounceHeight } from 'react-navigation-collapsible';
|
import { setSafeBounceHeight } from 'react-navigation-collapsible';
|
||||||
import SplashScreen from 'react-native-splash-screen';
|
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 ConnectionManager from './src/managers/ConnectionManager';
|
||||||
import type { ParsedUrlDataType } from './src/utils/URLHandler';
|
import type { ParsedUrlDataType } from './src/utils/URLHandler';
|
||||||
import URLHandler from './src/utils/URLHandler';
|
import URLHandler from './src/utils/URLHandler';
|
||||||
|
import { setupStatusBar } from './src/utils/Utils';
|
||||||
import initLocales from './src/utils/Locales';
|
import initLocales from './src/utils/Locales';
|
||||||
import { NavigationContainerRef } from '@react-navigation/core';
|
import { NavigationContainerRef } from '@react-navigation/core';
|
||||||
import {
|
import GENERAL_STYLES from './src/constants/Styles';
|
||||||
defaultMascotPreferences,
|
import CollapsibleProvider from './src/components/providers/CollapsibleProvider';
|
||||||
defaultPlanexPreferences,
|
import CacheProvider from './src/components/providers/CacheProvider';
|
||||||
defaultPreferences,
|
|
||||||
defaultProxiwashPreferences,
|
|
||||||
GeneralPreferenceKeys,
|
|
||||||
GeneralPreferencesType,
|
|
||||||
MascotPreferenceKeys,
|
|
||||||
MascotPreferencesType,
|
|
||||||
PlanexPreferenceKeys,
|
|
||||||
PlanexPreferencesType,
|
|
||||||
ProxiwashPreferenceKeys,
|
|
||||||
ProxiwashPreferencesType,
|
|
||||||
retrievePreferences,
|
|
||||||
} from './src/utils/asyncStorage';
|
|
||||||
import {
|
|
||||||
GeneralPreferencesProvider,
|
|
||||||
MascotPreferencesProvider,
|
|
||||||
PlanexPreferencesProvider,
|
|
||||||
ProxiwashPreferencesProvider,
|
|
||||||
} from './src/components/providers/PreferencesProvider';
|
|
||||||
import MainApp from './src/screens/MainApp';
|
|
||||||
|
|
||||||
// Native optimizations https://reactnavigation.org/docs/react-native-screens
|
// Native optimizations https://reactnavigation.org/docs/react-native-screens
|
||||||
// Crashes app when navigating away from webview on android 9+
|
// Crashes app when navigating away from webview on android 9+
|
||||||
|
|
@ -61,20 +52,18 @@ LogBox.ignoreLogs([
|
||||||
|
|
||||||
type StateType = {
|
type StateType = {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
initialPreferences: {
|
showIntro: boolean;
|
||||||
general: GeneralPreferencesType;
|
showUpdate: boolean;
|
||||||
planex: PlanexPreferencesType;
|
showAprilFools: boolean;
|
||||||
proxiwash: ProxiwashPreferencesType;
|
currentTheme: ReactNativePaper.Theme | undefined;
|
||||||
mascot: MascotPreferencesType;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class App extends React.Component<{}, StateType> {
|
export default class App extends React.Component<{}, StateType> {
|
||||||
navigatorRef: { current: null | NavigationContainerRef };
|
navigatorRef: { current: null | NavigationContainerRef };
|
||||||
|
|
||||||
defaultHomeRoute: string | undefined;
|
defaultHomeRoute: string | null;
|
||||||
|
|
||||||
defaultHomeData: { [key: string]: string } | undefined;
|
defaultHomeData: { [key: string]: string };
|
||||||
|
|
||||||
urlHandler: URLHandler;
|
urlHandler: URLHandler;
|
||||||
|
|
||||||
|
|
@ -82,17 +71,15 @@ export default class App extends React.Component<{}, StateType> {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
initialPreferences: {
|
showIntro: true,
|
||||||
general: defaultPreferences,
|
showUpdate: true,
|
||||||
planex: defaultPlanexPreferences,
|
showAprilFools: false,
|
||||||
proxiwash: defaultProxiwashPreferences,
|
currentTheme: undefined,
|
||||||
mascot: defaultMascotPreferences,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
initLocales();
|
initLocales();
|
||||||
this.navigatorRef = React.createRef();
|
this.navigatorRef = React.createRef();
|
||||||
this.defaultHomeRoute = undefined;
|
this.defaultHomeRoute = null;
|
||||||
this.defaultHomeData = undefined;
|
this.defaultHomeData = {};
|
||||||
this.urlHandler = new URLHandler(this.onInitialURLParsed, this.onDetectURL);
|
this.urlHandler = new URLHandler(this.onInitialURLParsed, this.onDetectURL);
|
||||||
this.urlHandler.listen();
|
this.urlHandler.listen();
|
||||||
setSafeBounceHeight(Platform.OS === 'ios' ? 100 : 20);
|
setSafeBounceHeight(Platform.OS === 'ios' ? 100 : 20);
|
||||||
|
|
@ -127,27 +114,67 @@ 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
|
* Async loading is done, finish processing startup data
|
||||||
*/
|
*/
|
||||||
onLoadFinished = (
|
onLoadFinished = () => {
|
||||||
values: Array<
|
// Only show intro if this is the first time starting the app
|
||||||
| GeneralPreferencesType
|
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
|
||||||
| PlanexPreferencesType
|
// Status bar goes dark if set too fast on ios
|
||||||
| ProxiwashPreferencesType
|
if (Platform.OS === 'ios') {
|
||||||
| MascotPreferencesType
|
setTimeout(setupStatusBar, 1000);
|
||||||
| void
|
} else {
|
||||||
>
|
setupStatusBar();
|
||||||
) => {
|
}
|
||||||
const [general, planex, proxiwash, mascot] = values;
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
initialPreferences: {
|
currentTheme: ThemeManager.getCurrentTheme(),
|
||||||
general: general as GeneralPreferencesType,
|
showIntro: AsyncStorageManager.getBool(
|
||||||
planex: planex as PlanexPreferencesType,
|
AsyncStorageManager.PREFERENCES.showIntro.key
|
||||||
proxiwash: proxiwash as ProxiwashPreferencesType,
|
),
|
||||||
mascot: mascot as MascotPreferencesType,
|
showUpdate:
|
||||||
},
|
AsyncStorageManager.getNumber(
|
||||||
|
AsyncStorageManager.PREFERENCES.updateNumber.key
|
||||||
|
) !== Update.number,
|
||||||
|
showAprilFools:
|
||||||
|
AprilFoolsManager.getInstance().isAprilFoolsEnabled() &&
|
||||||
|
AsyncStorageManager.getBool(
|
||||||
|
AsyncStorageManager.PREFERENCES.showAprilFoolsStart.key
|
||||||
|
),
|
||||||
});
|
});
|
||||||
SplashScreen.hide();
|
SplashScreen.hide();
|
||||||
};
|
};
|
||||||
|
|
@ -159,22 +186,7 @@ export default class App extends React.Component<{}, StateType> {
|
||||||
*/
|
*/
|
||||||
loadAssetsAsync() {
|
loadAssetsAsync() {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
retrievePreferences(
|
AsyncStorageManager.getInstance().loadPreferences(),
|
||||||
Object.values(GeneralPreferenceKeys),
|
|
||||||
defaultPreferences
|
|
||||||
),
|
|
||||||
retrievePreferences(
|
|
||||||
Object.values(PlanexPreferenceKeys),
|
|
||||||
defaultPlanexPreferences
|
|
||||||
),
|
|
||||||
retrievePreferences(
|
|
||||||
Object.values(ProxiwashPreferenceKeys),
|
|
||||||
defaultProxiwashPreferences
|
|
||||||
),
|
|
||||||
retrievePreferences(
|
|
||||||
Object.values(MascotPreferenceKeys),
|
|
||||||
defaultMascotPreferences
|
|
||||||
),
|
|
||||||
ConnectionManager.getInstance().recoverLogin(),
|
ConnectionManager.getInstance().recoverLogin(),
|
||||||
])
|
])
|
||||||
.then(this.onLoadFinished)
|
.then(this.onLoadFinished)
|
||||||
|
|
@ -189,28 +201,43 @@ export default class App extends React.Component<{}, StateType> {
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
return null;
|
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 (
|
return (
|
||||||
<GeneralPreferencesProvider
|
<PaperProvider theme={state.currentTheme}>
|
||||||
initialPreferences={this.state.initialPreferences.general}
|
<CollapsibleProvider>
|
||||||
>
|
<CacheProvider>
|
||||||
<PlanexPreferencesProvider
|
<OverflowMenuProvider>
|
||||||
initialPreferences={this.state.initialPreferences.planex}
|
<View
|
||||||
>
|
style={{
|
||||||
<ProxiwashPreferencesProvider
|
backgroundColor: ThemeManager.getCurrentTheme().colors
|
||||||
initialPreferences={this.state.initialPreferences.proxiwash}
|
.background,
|
||||||
>
|
...GENERAL_STYLES.flex,
|
||||||
<MascotPreferencesProvider
|
}}
|
||||||
initialPreferences={this.state.initialPreferences.mascot}
|
>
|
||||||
>
|
<SafeAreaView style={GENERAL_STYLES.flex}>
|
||||||
<MainApp
|
<NavigationContainer
|
||||||
ref={this.navigatorRef}
|
theme={state.currentTheme}
|
||||||
defaultHomeData={this.defaultHomeData}
|
ref={this.navigatorRef}
|
||||||
defaultHomeRoute={this.defaultHomeRoute}
|
>
|
||||||
/>
|
<MainNavigator
|
||||||
</MascotPreferencesProvider>
|
defaultHomeRoute={this.defaultHomeRoute}
|
||||||
</ProxiwashPreferencesProvider>
|
defaultHomeData={this.defaultHomeData}
|
||||||
</PlanexPreferencesProvider>
|
/>
|
||||||
</GeneralPreferencesProvider>
|
</NavigationContainer>
|
||||||
|
</SafeAreaView>
|
||||||
|
</View>
|
||||||
|
</OverflowMenuProvider>
|
||||||
|
</CacheProvider>
|
||||||
|
</CollapsibleProvider>
|
||||||
|
</PaperProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -463,7 +463,6 @@
|
||||||
"badToken": "You are not logged in. Please login and try again.",
|
"badToken": "You are not logged in. Please login and try again.",
|
||||||
"noConsent": "You did not give your consent for data processing to the Amicale.",
|
"noConsent": "You did not give your consent for data processing to the Amicale.",
|
||||||
"tokenSave": "Could not save session token. Please contact support.",
|
"tokenSave": "Could not save session token. Please contact support.",
|
||||||
"tokenRetrieve": "Could not retrieve session token. Please contact support.",
|
|
||||||
"badInput": "Invalid input. Please try again.",
|
"badInput": "Invalid input. Please try again.",
|
||||||
"forbidden": "You do not have access to this data.",
|
"forbidden": "You do not have access to this data.",
|
||||||
"connectionError": "Network error. Please check your internet connection.",
|
"connectionError": "Network error. Please check your internet connection.",
|
||||||
|
|
|
||||||
|
|
@ -463,7 +463,6 @@
|
||||||
"badToken": "Tu n'est pas connecté. Merci de te connecter puis réessayes.",
|
"badToken": "Tu n'est pas connecté. Merci de te connecter puis réessayes.",
|
||||||
"noConsent": "Tu n'as pas donné ton consentement pour l'utilisation de tes données personnelles.",
|
"noConsent": "Tu n'as pas donné ton consentement pour l'utilisation de tes données personnelles.",
|
||||||
"tokenSave": "Impossible de sauvegarder le token de session. Merci de contacter le support.",
|
"tokenSave": "Impossible de sauvegarder le token de session. Merci de contacter le support.",
|
||||||
"tokenRetrieve": "Impossible de récupérer le token de session. Merci de contacter le support.",
|
|
||||||
"badInput": "Entrée invalide. Merci de réessayer.",
|
"badInput": "Entrée invalide. Merci de réessayer.",
|
||||||
"forbidden": "Tu n'as pas accès à cette information.",
|
"forbidden": "Tu n'as pas accès à cette information.",
|
||||||
"connectionError": "Erreur de réseau. Merci de vérifier ta connexion Internet.",
|
"connectionError": "Erreur de réseau. Merci de vérifier ta connexion Internet.",
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ function CollapsibleComponent(props: Props) {
|
||||||
const { paddedProps, headerColors } = props;
|
const { paddedProps, headerColors } = props;
|
||||||
const Comp = props.component;
|
const Comp = props.component;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { setCollapsible } = useCollapsible();
|
const { setCollapsible } = useCollapsible();
|
||||||
|
|
||||||
const collapsible = useCollapsibleHeader({
|
const collapsible = useCollapsibleHeader({
|
||||||
|
|
@ -59,11 +58,6 @@ function CollapsibleComponent(props: Props) {
|
||||||
collapsedColor: headerColors ? headerColors : theme.colors.surface,
|
collapsedColor: headerColors ? headerColors : theme.colors.surface,
|
||||||
useNativeDriver: true,
|
useNativeDriver: true,
|
||||||
},
|
},
|
||||||
navigationOptions: {
|
|
||||||
headerStyle: {
|
|
||||||
backgroundColor: headerColors ? headerColors : theme.colors.surface,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import * as React from 'react';
|
||||||
import { Animated, Dimensions, ViewStyle } from 'react-native';
|
import { Animated, Dimensions, ViewStyle } from 'react-native';
|
||||||
import ImageListItem from './ImageListItem';
|
import ImageListItem from './ImageListItem';
|
||||||
import CardListItem from './CardListItem';
|
import CardListItem from './CardListItem';
|
||||||
import { ServiceItemType } from '../../../utils/Services';
|
import type { ServiceItemType } from '../../../managers/ServicesManager';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
dataset: Array<ServiceItemType>;
|
dataset: Array<ServiceItemType>;
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Caption, Card, Paragraph, TouchableRipple } from 'react-native-paper';
|
import { Caption, Card, Paragraph, TouchableRipple } from 'react-native-paper';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
import type { ServiceItemType } from '../../../managers/ServicesManager';
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
import GENERAL_STYLES from '../../../constants/Styles';
|
||||||
import { ServiceItemType } from '../../../utils/Services';
|
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
item: ServiceItemType;
|
item: ServiceItemType;
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Text, TouchableRipple } from 'react-native-paper';
|
import { Text, TouchableRipple } from 'react-native-paper';
|
||||||
import { Image, StyleSheet, View } from 'react-native';
|
import { Image, StyleSheet, View } from 'react-native';
|
||||||
|
import type { ServiceItemType } from '../../../managers/ServicesManager';
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
import GENERAL_STYLES from '../../../constants/Styles';
|
||||||
import { ServiceItemType } from '../../../utils/Services';
|
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
item: ServiceItemType;
|
item: ServiceItemType;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,10 @@ import { FlatList, Image, StyleSheet, View } from 'react-native';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import DashboardEditItem from './DashboardEditItem';
|
import DashboardEditItem from './DashboardEditItem';
|
||||||
import AnimatedAccordion from '../../Animations/AnimatedAccordion';
|
import AnimatedAccordion from '../../Animations/AnimatedAccordion';
|
||||||
import { ServiceCategoryType, ServiceItemType } from '../../../utils/Services';
|
import type {
|
||||||
|
ServiceCategoryType,
|
||||||
|
ServiceItemType,
|
||||||
|
} from '../../../managers/ServicesManager';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
item: ServiceCategoryType;
|
item: ServiceCategoryType;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Image, StyleSheet } from 'react-native';
|
import { Image, StyleSheet } from 'react-native';
|
||||||
import { List, useTheme } from 'react-native-paper';
|
import { List, useTheme } from 'react-native-paper';
|
||||||
import { ServiceItemType } from '../../../utils/Services';
|
import type { ServiceItemType } from '../../../managers/ServicesManager';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
item: ServiceItemType;
|
item: ServiceItemType;
|
||||||
|
|
|
||||||
|
|
@ -28,17 +28,17 @@ import {
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import Mascot from './Mascot';
|
import Mascot from './Mascot';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
import MascotSpeechBubble, {
|
import MascotSpeechBubble, {
|
||||||
MascotSpeechBubbleProps,
|
MascotSpeechBubbleProps,
|
||||||
} from './MascotSpeechBubble';
|
} from './MascotSpeechBubble';
|
||||||
import { useMountEffect } from '../../utils/customHooks';
|
import { useMountEffect } from '../../utils/customHooks';
|
||||||
import { useRoute } from '@react-navigation/core';
|
|
||||||
import { useShouldShowMascot } from '../../context/preferencesContext';
|
|
||||||
|
|
||||||
type PropsType = MascotSpeechBubbleProps & {
|
type PropsType = MascotSpeechBubbleProps & {
|
||||||
emotion: number;
|
emotion: number;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
|
prefKey?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
@ -61,14 +61,13 @@ const BUBBLE_HEIGHT = Dimensions.get('window').height / 3;
|
||||||
* Component used to display a popup with the mascot.
|
* Component used to display a popup with the mascot.
|
||||||
*/
|
*/
|
||||||
function MascotPopup(props: PropsType) {
|
function MascotPopup(props: PropsType) {
|
||||||
const route = useRoute();
|
|
||||||
const { shouldShow, setShouldShow } = useShouldShowMascot(route.name);
|
|
||||||
|
|
||||||
const isVisible = () => {
|
const isVisible = () => {
|
||||||
if (props.visible !== undefined) {
|
if (props.visible !== undefined) {
|
||||||
return props.visible;
|
return props.visible;
|
||||||
|
} else if (props.prefKey != null) {
|
||||||
|
return AsyncStorageManager.getBool(props.prefKey);
|
||||||
} else {
|
} else {
|
||||||
return shouldShow;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -165,8 +164,10 @@ function MascotPopup(props: PropsType) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDismiss = (callback?: () => void) => {
|
const onDismiss = (callback?: () => void) => {
|
||||||
setShouldShow(false);
|
if (props.prefKey != null) {
|
||||||
setDialogVisible(false);
|
AsyncStorageManager.set(props.prefKey, false);
|
||||||
|
setDialogVisible(false);
|
||||||
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import LinearGradient from 'react-native-linear-gradient';
|
||||||
import * as Animatable from 'react-native-animatable';
|
import * as Animatable from 'react-native-animatable';
|
||||||
import { Card } from 'react-native-paper';
|
import { Card } from 'react-native-paper';
|
||||||
import Update from '../../constants/Update';
|
import Update from '../../constants/Update';
|
||||||
|
import ThemeManager from '../../managers/ThemeManager';
|
||||||
import Mascot, { MASCOT_STYLE } from '../Mascot/Mascot';
|
import Mascot, { MASCOT_STYLE } from '../Mascot/Mascot';
|
||||||
import MascotIntroWelcome from '../Intro/MascotIntroWelcome';
|
import MascotIntroWelcome from '../Intro/MascotIntroWelcome';
|
||||||
import IntroIcon from '../Intro/IconIntro';
|
import IntroIcon from '../Intro/IconIntro';
|
||||||
|
|
@ -288,6 +289,9 @@ export default class CustomIntroSlider extends React.Component<
|
||||||
|
|
||||||
onDone = () => {
|
onDone = () => {
|
||||||
const { props } = this;
|
const { props } = this;
|
||||||
|
CustomIntroSlider.setStatusBarColor(
|
||||||
|
ThemeManager.getCurrentTheme().colors.surface
|
||||||
|
);
|
||||||
props.onDone();
|
props.onDone();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ import { StyleSheet, View } from 'react-native';
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
import Urls from '../../constants/Urls';
|
import Urls from '../../constants/Urls';
|
||||||
import DateManager from '../../managers/DateManager';
|
import DateManager from '../../managers/DateManager';
|
||||||
|
import ThemeManager from '../../managers/ThemeManager';
|
||||||
import { PlanexGroupType } from '../../screens/Planex/GroupSelectionScreen';
|
import { PlanexGroupType } from '../../screens/Planex/GroupSelectionScreen';
|
||||||
import ErrorView from './ErrorView';
|
import ErrorView from './ErrorView';
|
||||||
import WebViewScreen from './WebViewScreen';
|
import WebViewScreen from './WebViewScreen';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { useTheme } from 'react-native-paper';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
currentGroup?: PlanexGroupType;
|
currentGroup?: PlanexGroupType;
|
||||||
|
|
@ -67,14 +67,6 @@ calendar.option({
|
||||||
}
|
}
|
||||||
});`;
|
});`;
|
||||||
|
|
||||||
export const JS_LOADED_MESSAGE = '1';
|
|
||||||
|
|
||||||
const NOTIFY_JS_INJECTED = `
|
|
||||||
function notifyJsInjected() {
|
|
||||||
window.ReactNativeWebView.postMessage('${JS_LOADED_MESSAGE}');
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Mobile friendly CSS
|
// Mobile friendly CSS
|
||||||
const CUSTOM_CSS =
|
const CUSTOM_CSS =
|
||||||
'body>.container{padding-top:20px; padding-bottom: 50px}header,#entite,#groupe_visibility,#calendar .fc-left,#calendar .fc-right{display:none}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-time{font-size:.5rem}#calendar .fc-month-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-month-view .fc-content-skeleton .fc-time{font-size:.7rem}.fc-axis{font-size:.8rem;width:15px!important}.fc-day-header{font-size:.8rem}.fc-unthemed td.fc-today{background:#be1522; opacity:0.4}';
|
'body>.container{padding-top:20px; padding-bottom: 50px}header,#entite,#groupe_visibility,#calendar .fc-left,#calendar .fc-right{display:none}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-agendaWeek-view .fc-content-skeleton .fc-time{font-size:.5rem}#calendar .fc-month-view .fc-content-skeleton .fc-title{font-size:.6rem}#calendar .fc-month-view .fc-content-skeleton .fc-time{font-size:.7rem}.fc-axis{font-size:.8rem;width:15px!important}.fc-day-header{font-size:.8rem}.fc-unthemed td.fc-today{background:#be1522; opacity:0.4}';
|
||||||
|
|
@ -94,35 +86,30 @@ const INJECT_STYLE_DARK = `$('head').append('<style>${CUSTOM_CSS_DARK}</style>')
|
||||||
*
|
*
|
||||||
* @param groupID The current group selected
|
* @param groupID The current group selected
|
||||||
*/
|
*/
|
||||||
const generateInjectedJS = (
|
const generateInjectedJS = (group: PlanexGroupType | undefined) => {
|
||||||
group: PlanexGroupType | undefined,
|
|
||||||
darkMode: boolean
|
|
||||||
) => {
|
|
||||||
let customInjectedJS = `$(document).ready(function() {
|
let customInjectedJS = `$(document).ready(function() {
|
||||||
${OBSERVE_MUTATIONS_INJECTED}
|
${OBSERVE_MUTATIONS_INJECTED}
|
||||||
${INJECT_STYLE}
|
${INJECT_STYLE}
|
||||||
${FULL_CALENDAR_SETTINGS}
|
${FULL_CALENDAR_SETTINGS}`;
|
||||||
${NOTIFY_JS_INJECTED}`;
|
|
||||||
if (group) {
|
if (group) {
|
||||||
customInjectedJS += `displayAde(${group.id});`;
|
customInjectedJS += `displayAde(${group.id});`;
|
||||||
}
|
}
|
||||||
if (DateManager.isWeekend(new Date())) {
|
if (DateManager.isWeekend(new Date())) {
|
||||||
customInjectedJS += `calendar.next();`;
|
customInjectedJS += `calendar.next();`;
|
||||||
}
|
}
|
||||||
if (darkMode) {
|
if (ThemeManager.getNightMode()) {
|
||||||
customInjectedJS += INJECT_STYLE_DARK;
|
customInjectedJS += INJECT_STYLE_DARK;
|
||||||
}
|
}
|
||||||
customInjectedJS += `notifyJsInjected();});true;`; // Prevents crash on ios
|
customInjectedJS += 'removeAlpha();});true;'; // Prevents crash on ios
|
||||||
return customInjectedJS;
|
return customInjectedJS;
|
||||||
};
|
};
|
||||||
|
|
||||||
function PlanexWebview(props: Props) {
|
function PlanexWebview(props: Props) {
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
return (
|
||||||
<View style={GENERAL_STYLES.flex}>
|
<View style={GENERAL_STYLES.flex}>
|
||||||
<WebViewScreen
|
<WebViewScreen
|
||||||
url={Urls.planex.planning}
|
url={Urls.planex.planning}
|
||||||
initialJS={generateInjectedJS(props.currentGroup, theme.dark)}
|
initialJS={generateInjectedJS(props.currentGroup)}
|
||||||
injectJS={props.injectJS}
|
injectJS={props.injectJS}
|
||||||
onMessage={props.onMessage}
|
onMessage={props.onMessage}
|
||||||
showAdvancedControls={false}
|
showAdvancedControls={false}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export type RequestProps = {
|
||||||
|
|
||||||
type Props<T> = RequestScreenProps<T>;
|
type Props<T> = RequestScreenProps<T>;
|
||||||
|
|
||||||
const MIN_REFRESH_TIME = 3 * 1000;
|
const MIN_REFRESH_TIME = 5 * 1000;
|
||||||
|
|
||||||
export default function RequestScreen<T>(props: Props<T>) {
|
export default function RequestScreen<T>(props: Props<T>) {
|
||||||
const navigation = useNavigation<StackNavigationProp<any>>();
|
const navigation = useNavigation<StackNavigationProp<any>>();
|
||||||
|
|
@ -94,7 +94,8 @@ export default function RequestScreen<T>(props: Props<T>) {
|
||||||
clearInterval(refreshInterval.current);
|
clearInterval(refreshInterval.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [props.cache, props.refreshOnFocus, props.autoRefreshTime, refreshData])
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [props.cache, props.refreshOnFocus])
|
||||||
);
|
);
|
||||||
|
|
||||||
const isErrorCritical = (e: API_REQUEST_CODES | undefined) => {
|
const isErrorCritical = (e: API_REQUEST_CODES | undefined) => {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,12 @@ type Props<ItemT, RawData> = Omit<
|
||||||
> &
|
> &
|
||||||
Omit<
|
Omit<
|
||||||
RequestScreenProps<RawData>,
|
RequestScreenProps<RawData>,
|
||||||
'render' | 'showLoading' | 'showError' | 'onMajorError'
|
| 'render'
|
||||||
|
| 'showLoading'
|
||||||
|
| 'showError'
|
||||||
|
| 'refresh'
|
||||||
|
| 'onFinish'
|
||||||
|
| 'onMajorError'
|
||||||
> &
|
> &
|
||||||
Omit<
|
Omit<
|
||||||
SectionListProps<ItemT>,
|
SectionListProps<ItemT>,
|
||||||
|
|
@ -166,8 +171,6 @@ function WebSectionList<ItemT, RawData>(props: Props<ItemT, RawData>) {
|
||||||
refreshOnFocus={props.refreshOnFocus}
|
refreshOnFocus={props.refreshOnFocus}
|
||||||
cache={props.cache}
|
cache={props.cache}
|
||||||
onCacheUpdate={props.onCacheUpdate}
|
onCacheUpdate={props.onCacheUpdate}
|
||||||
refresh={props.refresh}
|
|
||||||
onFinish={props.onFinish}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,173 +1,67 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
defaultMascotPreferences,
|
|
||||||
defaultPlanexPreferences,
|
|
||||||
defaultPreferences,
|
defaultPreferences,
|
||||||
defaultProxiwashPreferences,
|
|
||||||
GeneralPreferenceKeys,
|
|
||||||
GeneralPreferencesType,
|
|
||||||
MascotPreferenceKeys,
|
|
||||||
MascotPreferencesType,
|
|
||||||
PlanexPreferenceKeys,
|
|
||||||
PlanexPreferencesType,
|
|
||||||
PreferenceKeys,
|
PreferenceKeys,
|
||||||
PreferencesType,
|
PreferencesType,
|
||||||
ProxiwashPreferenceKeys,
|
|
||||||
ProxiwashPreferencesType,
|
|
||||||
setPreference,
|
setPreference,
|
||||||
} from '../../utils/asyncStorage';
|
} from '../../utils/asyncStorage';
|
||||||
import {
|
import {
|
||||||
MascotPreferencesContext,
|
|
||||||
PlanexPreferencesContext,
|
|
||||||
PreferencesContext,
|
PreferencesContext,
|
||||||
PreferencesContextType,
|
PreferencesContextType,
|
||||||
ProxiwashPreferencesContext,
|
|
||||||
} from '../../context/preferencesContext';
|
} from '../../context/preferencesContext';
|
||||||
|
|
||||||
function updateState<T extends Partial<PreferencesType>, K extends string>(
|
type Props = {
|
||||||
key: K,
|
children: React.ReactChild;
|
||||||
value: number | string | boolean | object | Array<any>,
|
initialPreferences: PreferencesType;
|
||||||
prevState: PreferencesContextType<T, K>
|
};
|
||||||
) {
|
|
||||||
const prevPreferences = { ...prevState.preferences };
|
|
||||||
const newPrefs = setPreference(key as PreferenceKeys, value, prevPreferences);
|
|
||||||
const newSate = {
|
|
||||||
...prevState,
|
|
||||||
preferences: { ...newPrefs },
|
|
||||||
};
|
|
||||||
return newSate;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetState<
|
export default function PreferencesProvider(props: Props) {
|
||||||
T extends Partial<PreferencesType>,
|
|
||||||
K extends Partial<PreferenceKeys>
|
|
||||||
>(
|
|
||||||
keys: Array<PreferenceKeys>,
|
|
||||||
defaults: T,
|
|
||||||
prevState: PreferencesContextType<T, K>
|
|
||||||
) {
|
|
||||||
const prevPreferences = { ...prevState.preferences };
|
|
||||||
let newPreferences = { ...prevPreferences };
|
|
||||||
keys.forEach((key) => {
|
|
||||||
newPreferences = setPreference(key, defaults[key], prevPreferences);
|
|
||||||
});
|
|
||||||
const newSate = {
|
|
||||||
...prevState,
|
|
||||||
preferences: { ...newPreferences },
|
|
||||||
};
|
|
||||||
return newSate;
|
|
||||||
}
|
|
||||||
|
|
||||||
function usePreferencesProvider<
|
|
||||||
T extends Partial<PreferencesType>,
|
|
||||||
K extends Partial<PreferenceKeys>
|
|
||||||
>(initialPreferences: T, defaults: T, keys: Array<K>) {
|
|
||||||
const updatePreferences = (
|
const updatePreferences = (
|
||||||
key: K,
|
key: PreferenceKeys,
|
||||||
value: number | string | boolean | object | Array<any>
|
value: number | string | boolean | object | Array<any>
|
||||||
) => {
|
) => {
|
||||||
setPreferencesState((prevState) => updateState(key, value, prevState));
|
setPreferencesState((prevState) => {
|
||||||
|
const prevPreferences = { ...prevState.preferences };
|
||||||
|
const newPrefs = setPreference(key, value, prevPreferences);
|
||||||
|
const newSate = {
|
||||||
|
...prevState,
|
||||||
|
preferences: { ...newPrefs },
|
||||||
|
};
|
||||||
|
return newSate;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetPreferences = () => {
|
const resetPreferences = () => {
|
||||||
setPreferencesState((prevState) => resetState(keys, defaults, prevState));
|
setPreferencesState((prevState) => {
|
||||||
|
const prevPreferences = { ...prevState.preferences };
|
||||||
|
let newPreferences = { ...prevPreferences };
|
||||||
|
Object.values(PreferenceKeys).forEach((key) => {
|
||||||
|
newPreferences = setPreference(
|
||||||
|
key,
|
||||||
|
defaultPreferences[key],
|
||||||
|
prevPreferences
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const newSate = {
|
||||||
|
...prevState,
|
||||||
|
preferences: { ...newPreferences },
|
||||||
|
};
|
||||||
|
return newSate;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const [preferencesState, setPreferencesState] = useState<
|
const [
|
||||||
PreferencesContextType<T, K>
|
preferencesState,
|
||||||
>({
|
setPreferencesState,
|
||||||
preferences: { ...initialPreferences },
|
] = useState<PreferencesContextType>({
|
||||||
|
preferences: { ...props.initialPreferences },
|
||||||
updatePreferences: updatePreferences,
|
updatePreferences: updatePreferences,
|
||||||
resetPreferences: resetPreferences,
|
resetPreferences: resetPreferences,
|
||||||
});
|
});
|
||||||
return preferencesState;
|
|
||||||
}
|
|
||||||
|
|
||||||
type PreferencesProviderProps<
|
|
||||||
T extends Partial<PreferencesType>,
|
|
||||||
K extends Partial<PreferenceKeys>
|
|
||||||
> = {
|
|
||||||
children: React.ReactChild;
|
|
||||||
initialPreferences: T;
|
|
||||||
defaults: T;
|
|
||||||
keys: Array<K>;
|
|
||||||
Context: React.Context<PreferencesContextType<T, K>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function PreferencesProvider<
|
|
||||||
T extends Partial<PreferencesType>,
|
|
||||||
K extends Partial<PreferenceKeys>
|
|
||||||
>(props: PreferencesProviderProps<T, K>) {
|
|
||||||
const { Context } = props;
|
|
||||||
const preferencesState = usePreferencesProvider<T, K>(
|
|
||||||
props.initialPreferences,
|
|
||||||
props.defaults,
|
|
||||||
Object.values(props.keys)
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<Context.Provider value={preferencesState}>
|
<PreferencesContext.Provider value={preferencesState}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Context.Provider>
|
</PreferencesContext.Provider>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props<T> = {
|
|
||||||
children: React.ReactChild;
|
|
||||||
initialPreferences: T;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function GeneralPreferencesProvider(
|
|
||||||
props: Props<GeneralPreferencesType>
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<PreferencesProvider
|
|
||||||
Context={PreferencesContext}
|
|
||||||
initialPreferences={props.initialPreferences}
|
|
||||||
defaults={defaultPreferences}
|
|
||||||
keys={Object.values(GeneralPreferenceKeys)}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</PreferencesProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PlanexPreferencesProvider(props: Props<PlanexPreferencesType>) {
|
|
||||||
return (
|
|
||||||
<PreferencesProvider
|
|
||||||
Context={PlanexPreferencesContext}
|
|
||||||
initialPreferences={props.initialPreferences}
|
|
||||||
defaults={defaultPlanexPreferences}
|
|
||||||
keys={Object.values(PlanexPreferenceKeys)}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</PreferencesProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ProxiwashPreferencesProvider(
|
|
||||||
props: Props<ProxiwashPreferencesType>
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<PreferencesProvider
|
|
||||||
Context={ProxiwashPreferencesContext}
|
|
||||||
initialPreferences={props.initialPreferences}
|
|
||||||
defaults={defaultProxiwashPreferences}
|
|
||||||
keys={Object.values(ProxiwashPreferenceKeys)}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</PreferencesProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MascotPreferencesProvider(props: Props<MascotPreferencesType>) {
|
|
||||||
return (
|
|
||||||
<PreferencesProvider
|
|
||||||
Context={MascotPreferencesContext}
|
|
||||||
initialPreferences={props.initialPreferences}
|
|
||||||
defaults={defaultMascotPreferences}
|
|
||||||
keys={Object.values(MascotPreferenceKeys)}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</PreferencesProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
src/context/preferencesContext.ts
Normal file
25
src/context/preferencesContext.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
import { useNavigation } from '@react-navigation/core';
|
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import { Appearance } from 'react-native-appearance';
|
|
||||||
import {
|
|
||||||
defaultMascotPreferences,
|
|
||||||
defaultPlanexPreferences,
|
|
||||||
defaultPreferences,
|
|
||||||
defaultProxiwashPreferences,
|
|
||||||
getPreferenceBool,
|
|
||||||
getPreferenceObject,
|
|
||||||
MascotPreferenceKeys,
|
|
||||||
MascotPreferencesType,
|
|
||||||
PlanexPreferenceKeys,
|
|
||||||
PlanexPreferencesType,
|
|
||||||
GeneralPreferenceKeys,
|
|
||||||
GeneralPreferencesType,
|
|
||||||
ProxiwashPreferenceKeys,
|
|
||||||
ProxiwashPreferencesType,
|
|
||||||
isValidMascotPreferenceKey,
|
|
||||||
PreferencesType,
|
|
||||||
} from '../utils/asyncStorage';
|
|
||||||
import {
|
|
||||||
getAmicaleServices,
|
|
||||||
getINSAServices,
|
|
||||||
getSpecialServices,
|
|
||||||
getStudentServices,
|
|
||||||
} from '../utils/Services';
|
|
||||||
|
|
||||||
const colorScheme = Appearance.getColorScheme();
|
|
||||||
|
|
||||||
export type PreferencesContextType<
|
|
||||||
T extends Partial<PreferencesType>,
|
|
||||||
K extends string
|
|
||||||
> = {
|
|
||||||
preferences: T;
|
|
||||||
updatePreferences: (
|
|
||||||
key: K,
|
|
||||||
value: number | string | boolean | object | Array<any>
|
|
||||||
) => void;
|
|
||||||
resetPreferences: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// CONTEXTES
|
|
||||||
// Preferences are separated into several contextes to improve performances
|
|
||||||
|
|
||||||
export const PreferencesContext = React.createContext<
|
|
||||||
PreferencesContextType<GeneralPreferencesType, GeneralPreferenceKeys>
|
|
||||||
>({
|
|
||||||
preferences: defaultPreferences,
|
|
||||||
updatePreferences: () => undefined,
|
|
||||||
resetPreferences: () => undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const PlanexPreferencesContext = React.createContext<
|
|
||||||
PreferencesContextType<PlanexPreferencesType, PlanexPreferenceKeys>
|
|
||||||
>({
|
|
||||||
preferences: defaultPlanexPreferences,
|
|
||||||
updatePreferences: () => undefined,
|
|
||||||
resetPreferences: () => undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ProxiwashPreferencesContext = React.createContext<
|
|
||||||
PreferencesContextType<ProxiwashPreferencesType, ProxiwashPreferenceKeys>
|
|
||||||
>({
|
|
||||||
preferences: defaultProxiwashPreferences,
|
|
||||||
updatePreferences: () => undefined,
|
|
||||||
resetPreferences: () => undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const MascotPreferencesContext = React.createContext<
|
|
||||||
PreferencesContextType<MascotPreferencesType, MascotPreferenceKeys>
|
|
||||||
>({
|
|
||||||
preferences: defaultMascotPreferences,
|
|
||||||
updatePreferences: () => undefined,
|
|
||||||
resetPreferences: () => undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Context Hooks
|
|
||||||
|
|
||||||
export function usePreferences() {
|
|
||||||
return useContext(PreferencesContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function usePlanexPreferences() {
|
|
||||||
return useContext(PlanexPreferencesContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useProxiwashPreferences() {
|
|
||||||
return useContext(ProxiwashPreferencesContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useMascotPreferences() {
|
|
||||||
return useContext(MascotPreferencesContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom Hooks
|
|
||||||
|
|
||||||
export function useShouldShowMascot(route: string) {
|
|
||||||
const { preferences, updatePreferences } = useMascotPreferences();
|
|
||||||
const key = route + 'ShowMascot';
|
|
||||||
let shouldShow = false;
|
|
||||||
if (isValidMascotPreferenceKey(key)) {
|
|
||||||
shouldShow = getPreferenceBool(key, preferences) !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setShouldShow = (show: boolean) => {
|
|
||||||
if (isValidMascotPreferenceKey(key)) {
|
|
||||||
updatePreferences(key, show);
|
|
||||||
} else {
|
|
||||||
console.log('Invalid preference key: ' + key);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { shouldShow, setShouldShow };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDarkTheme() {
|
|
||||||
const { preferences } = usePreferences();
|
|
||||||
return (
|
|
||||||
(getPreferenceBool(GeneralPreferenceKeys.nightMode, preferences) !==
|
|
||||||
false &&
|
|
||||||
(getPreferenceBool(
|
|
||||||
GeneralPreferenceKeys.nightModeFollowSystem,
|
|
||||||
preferences
|
|
||||||
) === false ||
|
|
||||||
colorScheme === 'no-preference')) ||
|
|
||||||
(getPreferenceBool(
|
|
||||||
GeneralPreferenceKeys.nightModeFollowSystem,
|
|
||||||
preferences
|
|
||||||
) !== false &&
|
|
||||||
colorScheme === 'dark')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCurrentDashboard() {
|
|
||||||
const { preferences, updatePreferences } = usePreferences();
|
|
||||||
const navigation = useNavigation();
|
|
||||||
const dashboardIdList = getPreferenceObject(
|
|
||||||
GeneralPreferenceKeys.dashboardItems,
|
|
||||||
preferences
|
|
||||||
) as Array<string>;
|
|
||||||
|
|
||||||
const updateCurrentDashboard = (newList: Array<string>) => {
|
|
||||||
updatePreferences(GeneralPreferenceKeys.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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
269
src/managers/AsyncStorageManager.ts
Normal file
269
src/managers/AsyncStorageManager.ts
Normal file
|
|
@ -0,0 +1,269 @@
|
||||||
|
/*
|
||||||
|
* 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/managers/DashboardManager.ts
Normal file
38
src/managers/DashboardManager.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
371
src/managers/ServicesManager.ts
Normal file
371
src/managers/ServicesManager.ts
Normal file
|
|
@ -0,0 +1,371 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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 { 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 {
|
declare global {
|
||||||
namespace ReactNativePaper {
|
namespace ReactNativePaper {
|
||||||
|
|
@ -59,7 +83,7 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CustomWhiteTheme: ReactNativePaper.Theme = {
|
const CustomWhiteTheme: ReactNativePaper.Theme = {
|
||||||
...DefaultTheme,
|
...DefaultTheme,
|
||||||
colors: {
|
colors: {
|
||||||
...DefaultTheme.colors,
|
...DefaultTheme.colors,
|
||||||
|
|
@ -118,7 +142,7 @@ export const CustomWhiteTheme: ReactNativePaper.Theme = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomDarkTheme: ReactNativePaper.Theme = {
|
const CustomDarkTheme: ReactNativePaper.Theme = {
|
||||||
...DarkTheme,
|
...DarkTheme,
|
||||||
colors: {
|
colors: {
|
||||||
...DarkTheme.colors,
|
...DarkTheme.colors,
|
||||||
|
|
@ -176,3 +200,99 @@ export const CustomDarkTheme: ReactNativePaper.Theme = {
|
||||||
mascotMessageArrow: '#323232',
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,23 +46,16 @@ import EquipmentConfirmScreen from '../screens/Amicale/Equipment/EquipmentConfir
|
||||||
import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
|
import DashboardEditScreen from '../screens/Other/Settings/DashboardEditScreen';
|
||||||
import GameStartScreen from '../screens/Game/screens/GameStartScreen';
|
import GameStartScreen from '../screens/Game/screens/GameStartScreen';
|
||||||
import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
|
import ImageGalleryScreen from '../screens/Media/ImageGalleryScreen';
|
||||||
import { usePreferences } from '../context/preferencesContext';
|
|
||||||
import {
|
|
||||||
getPreferenceBool,
|
|
||||||
GeneralPreferenceKeys,
|
|
||||||
} from '../utils/asyncStorage';
|
|
||||||
import IntroScreen from '../screens/Intro/IntroScreen';
|
|
||||||
|
|
||||||
export enum MainRoutes {
|
export enum MainRoutes {
|
||||||
Main = 'main',
|
Main = 'main',
|
||||||
Intro = 'Intro',
|
|
||||||
Gallery = 'gallery',
|
Gallery = 'gallery',
|
||||||
Settings = 'settings',
|
Settings = 'settings',
|
||||||
DashboardEdit = 'dashboard-edit',
|
DashboardEdit = 'dashboard-edit',
|
||||||
About = 'about',
|
About = 'about',
|
||||||
Dependencies = 'dependencies',
|
Dependencies = 'dependencies',
|
||||||
Debug = 'debug',
|
Debug = 'debug',
|
||||||
GameStart = 'game',
|
GameStart = 'game-start',
|
||||||
GameMain = 'game-main',
|
GameMain = 'game-main',
|
||||||
Login = 'login',
|
Login = 'login',
|
||||||
SelfMenu = 'self-menu',
|
SelfMenu = 'self-menu',
|
||||||
|
|
@ -73,12 +66,11 @@ export enum MainRoutes {
|
||||||
ClubList = 'club-list',
|
ClubList = 'club-list',
|
||||||
ClubInformation = 'club-information',
|
ClubInformation = 'club-information',
|
||||||
ClubAbout = 'club-about',
|
ClubAbout = 'club-about',
|
||||||
EquipmentList = 'equipment',
|
EquipmentList = 'equipment-list',
|
||||||
EquipmentRent = 'equipment-rent',
|
EquipmentRent = 'equipment-rent',
|
||||||
EquipmentConfirm = 'equipment-confirm',
|
EquipmentConfirm = 'equipment-confirm',
|
||||||
Vote = 'vote',
|
Vote = 'vote',
|
||||||
Feedback = 'feedback',
|
Feedback = 'feedback',
|
||||||
Website = 'website',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultParams = { [key in MainRoutes]: object | undefined };
|
type DefaultParams = { [key in MainRoutes]: object | undefined };
|
||||||
|
|
@ -104,23 +96,15 @@ export type MainStackParamsList = FullParamsList &
|
||||||
|
|
||||||
const MainStack = createStackNavigator<MainStackParamsList>();
|
const MainStack = createStackNavigator<MainStackParamsList>();
|
||||||
|
|
||||||
function getIntroScreens() {
|
function MainStackComponent(props: {
|
||||||
|
createTabNavigator: () => React.ReactElement;
|
||||||
|
}) {
|
||||||
|
const { createTabNavigator } = props;
|
||||||
return (
|
return (
|
||||||
<>
|
<MainStack.Navigator
|
||||||
<MainStack.Screen
|
initialRouteName={MainRoutes.Main}
|
||||||
name={MainRoutes.Intro}
|
headerMode={'screen'}
|
||||||
component={IntroScreen}
|
>
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRegularScreens(createTabNavigator: () => React.ReactElement) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MainStack.Screen
|
<MainStack.Screen
|
||||||
name={MainRoutes.Main}
|
name={MainRoutes.Main}
|
||||||
component={createTabNavigator}
|
component={createTabNavigator}
|
||||||
|
|
@ -199,7 +183,7 @@ function getRegularScreens(createTabNavigator: () => React.ReactElement) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<MainStack.Screen
|
<MainStack.Screen
|
||||||
name={MainRoutes.Website}
|
name={'website'}
|
||||||
component={WebsiteScreen}
|
component={WebsiteScreen}
|
||||||
options={{
|
options={{
|
||||||
title: '',
|
title: '',
|
||||||
|
|
@ -296,48 +280,19 @@ function getRegularScreens(createTabNavigator: () => React.ReactElement) {
|
||||||
title: i18n.t('screens.feedback.title'),
|
title: i18n.t('screens.feedback.title'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MainStackComponent(props: {
|
|
||||||
showIntro: boolean;
|
|
||||||
createTabNavigator: () => React.ReactElement;
|
|
||||||
}) {
|
|
||||||
const { showIntro, createTabNavigator } = props;
|
|
||||||
return (
|
|
||||||
<MainStack.Navigator
|
|
||||||
initialRouteName={showIntro ? MainRoutes.Intro : MainRoutes.Main}
|
|
||||||
headerMode={'screen'}
|
|
||||||
>
|
|
||||||
{showIntro ? getIntroScreens() : getRegularScreens(createTabNavigator)}
|
|
||||||
</MainStack.Navigator>
|
</MainStack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
defaultHomeRoute?: string;
|
defaultHomeRoute: string | null;
|
||||||
defaultHomeData?: { [key: string]: string };
|
defaultHomeData: { [key: string]: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
function MainNavigator(props: PropsType) {
|
export default function MainNavigator(props: PropsType) {
|
||||||
const { preferences } = usePreferences();
|
|
||||||
const showIntro = getPreferenceBool(
|
|
||||||
GeneralPreferenceKeys.showIntro,
|
|
||||||
preferences
|
|
||||||
);
|
|
||||||
const createTabNavigator = () => <TabNavigator {...props} />;
|
|
||||||
return (
|
return (
|
||||||
<MainStackComponent
|
<MainStackComponent
|
||||||
showIntro={showIntro !== false}
|
createTabNavigator={() => <TabNavigator {...props} />}
|
||||||
createTabNavigator={createTabNavigator}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo(
|
|
||||||
MainNavigator,
|
|
||||||
(pp: PropsType, np: PropsType) =>
|
|
||||||
pp.defaultHomeRoute === np.defaultHomeRoute &&
|
|
||||||
pp.defaultHomeData === np.defaultHomeData
|
|
||||||
);
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import PlanningDisplayScreen from '../screens/Planning/PlanningDisplayScreen';
|
||||||
import ProxiwashScreen from '../screens/Proxiwash/ProxiwashScreen';
|
import ProxiwashScreen from '../screens/Proxiwash/ProxiwashScreen';
|
||||||
import ProxiwashAboutScreen from '../screens/Proxiwash/ProxiwashAboutScreen';
|
import ProxiwashAboutScreen from '../screens/Proxiwash/ProxiwashAboutScreen';
|
||||||
import PlanexScreen from '../screens/Planex/PlanexScreen';
|
import PlanexScreen from '../screens/Planex/PlanexScreen';
|
||||||
|
import AsyncStorageManager from '../managers/AsyncStorageManager';
|
||||||
import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
|
import ClubDisplayScreen from '../screens/Amicale/Clubs/ClubDisplayScreen';
|
||||||
import ScannerScreen from '../screens/Home/ScannerScreen';
|
import ScannerScreen from '../screens/Home/ScannerScreen';
|
||||||
import FeedItemScreen from '../screens/Home/FeedItemScreen';
|
import FeedItemScreen from '../screens/Home/FeedItemScreen';
|
||||||
|
|
@ -40,11 +41,6 @@ import WebsitesHomeScreen from '../screens/Services/ServicesScreen';
|
||||||
import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
|
import ServicesSectionScreen from '../screens/Services/ServicesSectionScreen';
|
||||||
import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
|
import AmicaleContactScreen from '../screens/Amicale/AmicaleContactScreen';
|
||||||
import Mascot, { MASCOT_STYLE } from '../components/Mascot/Mascot';
|
import Mascot, { MASCOT_STYLE } from '../components/Mascot/Mascot';
|
||||||
import { usePreferences } from '../context/preferencesContext';
|
|
||||||
import {
|
|
||||||
getPreferenceString,
|
|
||||||
GeneralPreferenceKeys,
|
|
||||||
} from '../utils/asyncStorage';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
header: {
|
header: {
|
||||||
|
|
@ -60,20 +56,6 @@ 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();
|
const ServicesStack = createStackNavigator();
|
||||||
|
|
||||||
function ServicesStackComponent() {
|
function ServicesStackComponent() {
|
||||||
|
|
@ -139,8 +121,8 @@ function PlanningStackComponent() {
|
||||||
const HomeStack = createStackNavigator();
|
const HomeStack = createStackNavigator();
|
||||||
|
|
||||||
function HomeStackComponent(
|
function HomeStackComponent(
|
||||||
initialRoute?: string,
|
initialRoute: string | null,
|
||||||
defaultData?: { [key: string]: string }
|
defaultData: { [key: string]: string }
|
||||||
) {
|
) {
|
||||||
let params;
|
let params;
|
||||||
if (initialRoute) {
|
if (initialRoute) {
|
||||||
|
|
@ -232,11 +214,11 @@ function PlanexStackComponent() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tab = createBottomTabNavigator<TabStackParamsList>();
|
const Tab = createBottomTabNavigator();
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
defaultHomeRoute?: string;
|
defaultHomeRoute: string | null;
|
||||||
defaultHomeData?: { [key: string]: string };
|
defaultHomeData: { [key: string]: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
const ICONS: {
|
const ICONS: {
|
||||||
|
|
@ -257,7 +239,7 @@ const ICONS: {
|
||||||
normal: '',
|
normal: '',
|
||||||
focused: '',
|
focused: '',
|
||||||
},
|
},
|
||||||
events: {
|
planning: {
|
||||||
normal: 'calendar-range-outline',
|
normal: 'calendar-range-outline',
|
||||||
focused: 'calendar-range',
|
focused: 'calendar-range',
|
||||||
},
|
},
|
||||||
|
|
@ -267,77 +249,65 @@ const ICONS: {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function TabNavigator(props: PropsType) {
|
export default class TabNavigator extends React.Component<PropsType> {
|
||||||
const { preferences } = usePreferences();
|
defaultRoute: string;
|
||||||
let defaultRoute = getPreferenceString(
|
createHomeStackComponent: () => any;
|
||||||
GeneralPreferenceKeys.defaultStartScreen,
|
|
||||||
preferences
|
constructor(props: PropsType) {
|
||||||
);
|
super(props);
|
||||||
if (!defaultRoute) {
|
this.defaultRoute = 'home';
|
||||||
defaultRoute = 'home';
|
if (!props.defaultHomeRoute) {
|
||||||
} else {
|
this.defaultRoute = AsyncStorageManager.getString(
|
||||||
defaultRoute = defaultRoute.toLowerCase();
|
AsyncStorageManager.PREFERENCES.defaultStartScreen.key
|
||||||
|
).toLowerCase();
|
||||||
|
}
|
||||||
|
this.createHomeStackComponent = () =>
|
||||||
|
HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const createHomeStackComponent = () =>
|
render() {
|
||||||
HomeStackComponent(props.defaultHomeRoute, props.defaultHomeData);
|
const LABELS: {
|
||||||
|
[key: string]: string;
|
||||||
const LABELS: {
|
} = {
|
||||||
[key: string]: string;
|
services: i18n.t('screens.services.title'),
|
||||||
} = {
|
proxiwash: i18n.t('screens.proxiwash.title'),
|
||||||
services: i18n.t('screens.services.title'),
|
home: i18n.t('screens.home.title'),
|
||||||
proxiwash: i18n.t('screens.proxiwash.title'),
|
planning: i18n.t('screens.planning.title'),
|
||||||
home: i18n.t('screens.home.title'),
|
planex: i18n.t('screens.planex.title'),
|
||||||
events: i18n.t('screens.planning.title'),
|
};
|
||||||
planex: i18n.t('screens.planex.title'),
|
return (
|
||||||
};
|
<Tab.Navigator
|
||||||
return (
|
initialRouteName={this.defaultRoute}
|
||||||
<Tab.Navigator
|
tabBar={(tabProps) => (
|
||||||
initialRouteName={defaultRoute}
|
<CustomTabBar {...tabProps} labels={LABELS} icons={ICONS} />
|
||||||
tabBar={(tabProps) => (
|
)}
|
||||||
<CustomTabBar {...tabProps} labels={LABELS} icons={ICONS} />
|
>
|
||||||
)}
|
<Tab.Screen
|
||||||
>
|
name={'services'}
|
||||||
<Tab.Screen
|
component={ServicesStackComponent}
|
||||||
name={'services'}
|
options={{ title: i18n.t('screens.services.title') }}
|
||||||
component={ServicesStackComponent}
|
/>
|
||||||
options={{ title: i18n.t('screens.services.title') }}
|
<Tab.Screen
|
||||||
/>
|
name={'proxiwash'}
|
||||||
<Tab.Screen
|
component={ProxiwashStackComponent}
|
||||||
name={'proxiwash'}
|
options={{ title: i18n.t('screens.proxiwash.title') }}
|
||||||
component={ProxiwashStackComponent}
|
/>
|
||||||
options={{ title: i18n.t('screens.proxiwash.title') }}
|
<Tab.Screen
|
||||||
/>
|
name={'home'}
|
||||||
<Tab.Screen
|
component={this.createHomeStackComponent}
|
||||||
name={'home'}
|
options={{ title: i18n.t('screens.home.title') }}
|
||||||
component={createHomeStackComponent}
|
/>
|
||||||
options={{ title: i18n.t('screens.home.title') }}
|
<Tab.Screen
|
||||||
/>
|
name={'planning'}
|
||||||
<Tab.Screen
|
component={PlanningStackComponent}
|
||||||
name={'events'}
|
options={{ title: i18n.t('screens.planning.title') }}
|
||||||
component={PlanningStackComponent}
|
/>
|
||||||
options={{ title: i18n.t('screens.planning.title') }}
|
<Tab.Screen
|
||||||
/>
|
name={'planex'}
|
||||||
<Tab.Screen
|
component={PlanexStackComponent}
|
||||||
name={'planex'}
|
options={{ title: i18n.t('screens.planex.title') }}
|
||||||
component={PlanexStackComponent}
|
/>
|
||||||
options={{ title: i18n.t('screens.planex.title') }}
|
</Tab.Navigator>
|
||||||
/>
|
);
|
||||||
</Tab.Navigator>
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default React.memo(
|
|
||||||
TabNavigator,
|
|
||||||
(pp: PropsType, np: PropsType) =>
|
|
||||||
pp.defaultHomeRoute === np.defaultHomeRoute &&
|
|
||||||
pp.defaultHomeData === np.defaultHomeData
|
|
||||||
);
|
|
||||||
|
|
||||||
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/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef, useState } from 'react';
|
import * as React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -25,17 +25,12 @@ import {
|
||||||
Subheading,
|
Subheading,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title,
|
||||||
useTheme,
|
withTheme,
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import { Modalize } from 'react-native-modalize';
|
import { Modalize } from 'react-native-modalize';
|
||||||
import CustomModal from '../../components/Overrides/CustomModal';
|
import CustomModal from '../../components/Overrides/CustomModal';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
||||||
import { usePreferences } from '../../context/preferencesContext';
|
|
||||||
import {
|
|
||||||
defaultPreferences,
|
|
||||||
GeneralPreferenceKeys,
|
|
||||||
isValidGeneralPreferenceKey,
|
|
||||||
} from '../../utils/asyncStorage';
|
|
||||||
|
|
||||||
type PreferenceItemType = {
|
type PreferenceItemType = {
|
||||||
key: string;
|
key: string;
|
||||||
|
|
@ -43,6 +38,15 @@ type PreferenceItemType = {
|
||||||
current: string;
|
current: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PropsType = {
|
||||||
|
theme: ReactNativePaper.Theme;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateType = {
|
||||||
|
modalCurrentDisplayItem: PreferenceItemType | null;
|
||||||
|
currentPreferences: Array<PreferenceItemType>;
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -58,35 +62,47 @@ const styles = StyleSheet.create({
|
||||||
* Class defining the Debug screen.
|
* Class defining the Debug screen.
|
||||||
* This screen allows the user to get and modify information on the app/device.
|
* This screen allows the user to get and modify information on the app/device.
|
||||||
*/
|
*/
|
||||||
function DebugScreen() {
|
class DebugScreen extends React.Component<PropsType, StateType> {
|
||||||
const theme = useTheme();
|
modalRef: { current: Modalize | null };
|
||||||
const { preferences, updatePreferences } = usePreferences();
|
|
||||||
const modalRef = useRef<Modalize>(null);
|
|
||||||
|
|
||||||
const [modalInputValue, setModalInputValue] = useState<string>('');
|
modalInputValue: string;
|
||||||
const [
|
|
||||||
modalCurrentDisplayItem,
|
|
||||||
setModalCurrentDisplayItem,
|
|
||||||
] = useState<PreferenceItemType | null>(null);
|
|
||||||
|
|
||||||
const currentPreferences: Array<PreferenceItemType> = [];
|
/**
|
||||||
Object.values(GeneralPreferenceKeys).forEach((key) => {
|
* Copies user preferences to state for easier manipulation
|
||||||
const newObject: PreferenceItemType = {
|
*
|
||||||
key: key,
|
* @param props
|
||||||
current: preferences[key],
|
*/
|
||||||
default: defaultPreferences[key],
|
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,
|
||||||
};
|
};
|
||||||
currentPreferences.push(newObject);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const getModalContent = () => {
|
/**
|
||||||
|
* Gets the edit modal content
|
||||||
|
*
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
getModalContent() {
|
||||||
|
const { props, state } = this;
|
||||||
let key = '';
|
let key = '';
|
||||||
let defaultValue = '';
|
let defaultValue = '';
|
||||||
let current = '';
|
let current = '';
|
||||||
if (modalCurrentDisplayItem) {
|
if (state.modalCurrentDisplayItem) {
|
||||||
key = modalCurrentDisplayItem.key;
|
key = state.modalCurrentDisplayItem.key;
|
||||||
defaultValue = modalCurrentDisplayItem.default;
|
defaultValue = state.modalCurrentDisplayItem.default;
|
||||||
current = modalCurrentDisplayItem.current;
|
defaultValue = state.modalCurrentDisplayItem.default;
|
||||||
|
current = state.modalCurrentDisplayItem.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -94,14 +110,19 @@ function DebugScreen() {
|
||||||
<Title>{key}</Title>
|
<Title>{key}</Title>
|
||||||
<Subheading>Default: {defaultValue}</Subheading>
|
<Subheading>Default: {defaultValue}</Subheading>
|
||||||
<Subheading>Current: {current}</Subheading>
|
<Subheading>Current: {current}</Subheading>
|
||||||
<TextInput label={'New Value'} onChangeText={setModalInputValue} />
|
<TextInput
|
||||||
|
label="New Value"
|
||||||
|
onChangeText={(text: string) => {
|
||||||
|
this.modalInputValue = text;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
dark
|
dark
|
||||||
color={theme.colors.success}
|
color={props.theme.colors.success}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
saveNewPrefs(key, modalInputValue);
|
this.saveNewPrefs(key, this.modalInputValue);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Save new value
|
Save new value
|
||||||
|
|
@ -109,9 +130,9 @@ function DebugScreen() {
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
dark
|
dark
|
||||||
color={theme.colors.danger}
|
color={props.theme.colors.danger}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
saveNewPrefs(key, defaultValue);
|
this.saveNewPrefs(key, defaultValue);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Reset to default
|
Reset to default
|
||||||
|
|
@ -119,46 +140,85 @@ function DebugScreen() {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const getRenderItem = ({ item }: { item: PreferenceItemType }) => {
|
getRenderItem = ({ item }: { item: PreferenceItemType }) => {
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
title={item.key}
|
title={item.key}
|
||||||
description="Click to edit"
|
description="Click to edit"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
showEditModal(item);
|
this.showEditModal(item);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showEditModal = (item: PreferenceItemType) => {
|
/**
|
||||||
setModalCurrentDisplayItem(item);
|
* Shows the edit modal
|
||||||
if (modalRef.current) {
|
*
|
||||||
modalRef.current.open();
|
* @param item
|
||||||
|
*/
|
||||||
|
showEditModal(item: PreferenceItemType) {
|
||||||
|
this.setState({
|
||||||
|
modalCurrentDisplayItem: item,
|
||||||
|
});
|
||||||
|
if (this.modalRef.current) {
|
||||||
|
this.modalRef.current.open();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const saveNewPrefs = (key: string, value: string) => {
|
/**
|
||||||
if (isValidGeneralPreferenceKey(key)) {
|
* Finds the index of the given key in the preferences array
|
||||||
updatePreferences(key, value);
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (modalRef.current) {
|
return index;
|
||||||
modalRef.current.close();
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
/**
|
||||||
<View>
|
* Saves the new value of the given preference
|
||||||
<CustomModal ref={modalRef}>{getModalContent()}</CustomModal>
|
*
|
||||||
<CollapsibleFlatList
|
* @param key The pref key
|
||||||
data={currentPreferences}
|
* @param value The pref value
|
||||||
extraData={currentPreferences}
|
*/
|
||||||
renderItem={getRenderItem}
|
saveNewPrefs(key: string, value: string) {
|
||||||
/>
|
this.setState((prevState: StateType): {
|
||||||
</View>
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { state } = this;
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<CustomModal ref={this.modalRef}>{this.getModalContent()}</CustomModal>
|
||||||
|
<CollapsibleFlatList
|
||||||
|
data={state.currentPreferences}
|
||||||
|
extraData={state.currentPreferences}
|
||||||
|
renderItem={this.getRenderItem}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DebugScreen;
|
export default withTheme(DebugScreen);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import i18n from 'i18n-js';
|
||||||
import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
|
import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
|
||||||
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
||||||
import { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
import { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
||||||
|
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
import GENERAL_STYLES from '../../../constants/Styles';
|
||||||
import ConnectionManager from '../../../managers/ConnectionManager';
|
import ConnectionManager from '../../../managers/ConnectionManager';
|
||||||
import { ApiRejectType } from '../../../utils/WebData';
|
import { ApiRejectType } from '../../../utils/WebData';
|
||||||
|
|
@ -35,7 +36,7 @@ type PropsType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateType = {
|
type StateType = {
|
||||||
mascotDialogVisible: boolean | undefined;
|
mascotDialogVisible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeviceType = {
|
export type DeviceType = {
|
||||||
|
|
@ -74,7 +75,9 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
||||||
super(props);
|
super(props);
|
||||||
this.userRents = null;
|
this.userRents = null;
|
||||||
this.state = {
|
this.state = {
|
||||||
mascotDialogVisible: undefined,
|
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||||
|
AsyncStorageManager.PREFERENCES.equipmentShowMascot.key
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,6 +145,10 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
||||||
};
|
};
|
||||||
|
|
||||||
hideMascotDialog = () => {
|
hideMascotDialog = () => {
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.equipmentShowMascot.key,
|
||||||
|
false
|
||||||
|
);
|
||||||
this.setState({ mascotDialogVisible: false });
|
this.setState({ mascotDialogVisible: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import { StackNavigationProp, StackScreenProps } from '@react-navigation/stack';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import ConnectionManager from '../../managers/ConnectionManager';
|
import ConnectionManager from '../../managers/ConnectionManager';
|
||||||
import ErrorDialog from '../../components/Dialogs/ErrorDialog';
|
import ErrorDialog from '../../components/Dialogs/ErrorDialog';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||||
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
|
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
|
||||||
|
|
@ -55,7 +56,7 @@ type StateType = {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
dialogVisible: boolean;
|
dialogVisible: boolean;
|
||||||
dialogError: ApiRejectType;
|
dialogError: ApiRejectType;
|
||||||
mascotDialogVisible: boolean | undefined;
|
mascotDialogVisible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ICON_AMICALE = require('../../../assets/amicale.png');
|
const ICON_AMICALE = require('../../../assets/amicale.png');
|
||||||
|
|
@ -117,7 +118,9 @@ class LoginScreen extends React.Component<Props, StateType> {
|
||||||
loading: false,
|
loading: false,
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
dialogError: { status: REQUEST_STATUS.SUCCESS },
|
dialogError: { status: REQUEST_STATUS.SUCCESS },
|
||||||
mascotDialogVisible: undefined,
|
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||||
|
AsyncStorageManager.PREFERENCES.loginShowMascot.key
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,6 +321,10 @@ class LoginScreen extends React.Component<Props, StateType> {
|
||||||
};
|
};
|
||||||
|
|
||||||
hideMascotDialog = () => {
|
hideMascotDialog = () => {
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.loginShowMascot.key,
|
||||||
|
false
|
||||||
|
);
|
||||||
this.setState({ mascotDialogVisible: false });
|
this.setState({ mascotDialogVisible: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -350,11 +357,10 @@ class LoginScreen extends React.Component<Props, StateType> {
|
||||||
handleSuccess = () => {
|
handleSuccess = () => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
// Do not show the home login banner again
|
// Do not show the home login banner again
|
||||||
// TODO
|
AsyncStorageManager.set(
|
||||||
// AsyncStorageManager.set(
|
AsyncStorageManager.PREFERENCES.homeShowMascot.key,
|
||||||
// AsyncStorageManager.PREFERENCES.homeShowMascot.key,
|
false
|
||||||
// false
|
);
|
||||||
// );
|
|
||||||
if (this.nextScreen == null) {
|
if (this.nextScreen == null) {
|
||||||
navigation.goBack();
|
navigation.goBack();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -36,16 +36,13 @@ import MaterialHeaderButtons, {
|
||||||
} from '../../components/Overrides/CustomHeaderButton';
|
} from '../../components/Overrides/CustomHeaderButton';
|
||||||
import CardList from '../../components/Lists/CardList/CardList';
|
import CardList from '../../components/Lists/CardList/CardList';
|
||||||
import Mascot, { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
import Mascot, { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||||
|
import ServicesManager, { SERVICES_KEY } from '../../managers/ServicesManager';
|
||||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
||||||
|
import type { ServiceItemType } from '../../managers/ServicesManager';
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
import Urls from '../../constants/Urls';
|
import Urls from '../../constants/Urls';
|
||||||
import RequestScreen from '../../components/Screens/RequestScreen';
|
import RequestScreen from '../../components/Screens/RequestScreen';
|
||||||
import ConnectionManager from '../../managers/ConnectionManager';
|
import ConnectionManager from '../../managers/ConnectionManager';
|
||||||
import {
|
|
||||||
getAmicaleServices,
|
|
||||||
ServiceItemType,
|
|
||||||
SERVICES_KEY,
|
|
||||||
} from '../../utils/Services';
|
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp<any>;
|
navigation: StackNavigationProp<any>;
|
||||||
|
|
@ -103,9 +100,8 @@ class ProfileScreen extends React.Component<PropsType, StateType> {
|
||||||
super(props);
|
super(props);
|
||||||
this.data = undefined;
|
this.data = undefined;
|
||||||
this.flatListData = [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }];
|
this.flatListData = [{ id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }];
|
||||||
this.amicaleDataset = getAmicaleServices(props.navigation.navigate, [
|
const services = new ServicesManager(props.navigation);
|
||||||
SERVICES_KEY.PROFILE,
|
this.amicaleDataset = services.getAmicaleServices([SERVICES_KEY.PROFILE]);
|
||||||
]);
|
|
||||||
this.state = {
|
this.state = {
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import VoteResults from '../../components/Amicale/Vote/VoteResults';
|
||||||
import VoteWait from '../../components/Amicale/Vote/VoteWait';
|
import VoteWait from '../../components/Amicale/Vote/VoteWait';
|
||||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
|
import VoteNotAvailable from '../../components/Amicale/Vote/VoteNotAvailable';
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
import ConnectionManager from '../../managers/ConnectionManager';
|
import ConnectionManager from '../../managers/ConnectionManager';
|
||||||
|
|
@ -117,7 +118,7 @@ type PropsType = {};
|
||||||
|
|
||||||
type StateType = {
|
type StateType = {
|
||||||
hasVoted: boolean;
|
hasVoted: boolean;
|
||||||
mascotDialogVisible: boolean | undefined;
|
mascotDialogVisible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
@ -153,7 +154,9 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
||||||
this.dates = undefined;
|
this.dates = undefined;
|
||||||
this.state = {
|
this.state = {
|
||||||
hasVoted: false,
|
hasVoted: false,
|
||||||
mascotDialogVisible: undefined,
|
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||||
|
AsyncStorageManager.PREFERENCES.voteShowMascot.key
|
||||||
|
),
|
||||||
};
|
};
|
||||||
this.hasVoted = false;
|
this.hasVoted = false;
|
||||||
this.today = new Date();
|
this.today = new Date();
|
||||||
|
|
@ -325,6 +328,10 @@ export default class VoteScreen extends React.Component<PropsType, StateType> {
|
||||||
};
|
};
|
||||||
|
|
||||||
hideMascotDialog = () => {
|
hideMascotDialog = () => {
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.voteShowMascot.key,
|
||||||
|
false
|
||||||
|
);
|
||||||
this.setState({ mascotDialogVisible: false });
|
this.setState({ mascotDialogVisible: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
import { GamePodium } from './GamePodium';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
scores: Array<number>;
|
|
||||||
isHighScore: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
topScoreContainer: {
|
|
||||||
marginBottom: 20,
|
|
||||||
marginTop: 20,
|
|
||||||
},
|
|
||||||
topScoreSubcontainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function FullGamePodium(props: Props) {
|
|
||||||
const { scores, isHighScore } = props;
|
|
||||||
const gold = scores.length > 0 ? scores[0] : '-';
|
|
||||||
const silver = scores.length > 1 ? scores[1] : '-';
|
|
||||||
const bronze = scores.length > 2 ? scores[2] : '-';
|
|
||||||
return (
|
|
||||||
<View style={styles.topScoreContainer}>
|
|
||||||
<GamePodium place={1} score={gold.toString()} isHighScore={isHighScore} />
|
|
||||||
<View style={styles.topScoreSubcontainer}>
|
|
||||||
<GamePodium
|
|
||||||
place={3}
|
|
||||||
score={bronze.toString()}
|
|
||||||
isHighScore={isHighScore}
|
|
||||||
/>
|
|
||||||
<GamePodium
|
|
||||||
place={2}
|
|
||||||
score={silver.toString()}
|
|
||||||
isHighScore={isHighScore}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
import * as Animatable from 'react-native-animatable';
|
|
||||||
import { useTheme } from 'react-native-paper';
|
|
||||||
import GridManager from '../logic/GridManager';
|
|
||||||
import Piece from '../logic/Piece';
|
|
||||||
import GridComponent from './GridComponent';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
pieceContainer: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
pieceBackground: {
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function GameBackground() {
|
|
||||||
const theme = useTheme();
|
|
||||||
const gridManager = new GridManager(4, 4, theme);
|
|
||||||
const gridList = [];
|
|
||||||
for (let i = 0; i < 18; i += 1) {
|
|
||||||
gridList.push(gridManager.getEmptyGrid(4, 4));
|
|
||||||
const piece = new Piece(theme);
|
|
||||||
piece.toGrid(gridList[i], true);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={styles.pieceContainer}>
|
|
||||||
{gridList.map((item, index) => {
|
|
||||||
const size = 10 + Math.floor(Math.random() * 30);
|
|
||||||
const top = Math.floor(Math.random() * 100);
|
|
||||||
const rot = Math.floor(Math.random() * 360);
|
|
||||||
const left = (index % 6) * 20;
|
|
||||||
const animDelay = size * 20;
|
|
||||||
const animDuration = 2 * (2000 - size * 30);
|
|
||||||
return (
|
|
||||||
<Animatable.View
|
|
||||||
useNativeDriver={true}
|
|
||||||
animation={'fadeInDownBig'}
|
|
||||||
delay={animDelay}
|
|
||||||
duration={animDuration}
|
|
||||||
key={`piece${index.toString()}`}
|
|
||||||
style={{
|
|
||||||
width: `${size}%`,
|
|
||||||
top: `${top}%`,
|
|
||||||
left: `${left}%`,
|
|
||||||
...styles.pieceBackground,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GridComponent
|
|
||||||
width={4}
|
|
||||||
height={4}
|
|
||||||
grid={item}
|
|
||||||
style={{
|
|
||||||
transform: [{ rotateZ: `${rot}deg` }],
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Animatable.View>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
import { IconButton, useTheme } from 'react-native-paper';
|
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
|
||||||
import GameLogic, { MovementCallbackType } from '../logic/GameLogic';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
logic: GameLogic;
|
|
||||||
onDirectionPressed: MovementCallbackType;
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
controlsContainer: {
|
|
||||||
height: 80,
|
|
||||||
flexDirection: 'row',
|
|
||||||
},
|
|
||||||
directionsContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
flex: 4,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function GameControls(props: Props) {
|
|
||||||
const { logic } = props;
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<View style={styles.controlsContainer}>
|
|
||||||
<IconButton
|
|
||||||
icon={'rotate-right-variant'}
|
|
||||||
size={40}
|
|
||||||
onPress={() => logic.rotatePressed(props.onDirectionPressed)}
|
|
||||||
style={GENERAL_STYLES.flex}
|
|
||||||
/>
|
|
||||||
<View style={styles.directionsContainer}>
|
|
||||||
<IconButton
|
|
||||||
icon={'chevron-left'}
|
|
||||||
size={40}
|
|
||||||
style={GENERAL_STYLES.flex}
|
|
||||||
onPress={() => logic.pressedOut()}
|
|
||||||
onPressIn={() => logic.leftPressedIn(props.onDirectionPressed)}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
icon={'chevron-right'}
|
|
||||||
size={40}
|
|
||||||
style={GENERAL_STYLES.flex}
|
|
||||||
onPress={() => logic.pressedOut()}
|
|
||||||
onPressIn={() => logic.rightPressed(props.onDirectionPressed)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<IconButton
|
|
||||||
icon={'arrow-down-bold'}
|
|
||||||
size={40}
|
|
||||||
onPressIn={() => logic.downPressedIn(props.onDirectionPressed)}
|
|
||||||
onPress={() => logic.pressedOut()}
|
|
||||||
style={GENERAL_STYLES.flex}
|
|
||||||
color={theme.colors.tetrisScore}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default React.memo(GameControls, () => true);
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
import { Text, useTheme } from 'react-native-paper';
|
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
||||||
import * as Animatable from 'react-native-animatable';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
place: 1 | 2 | 3;
|
|
||||||
score: string;
|
|
||||||
isHighScore: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
podiumContainer: {
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
podiumIconContainer: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: -20,
|
|
||||||
},
|
|
||||||
centertext: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function GamePodium(props: Props) {
|
|
||||||
const { place, score, isHighScore } = props;
|
|
||||||
const theme = useTheme();
|
|
||||||
let icon = 'podium-gold';
|
|
||||||
let color = theme.colors.gameGold;
|
|
||||||
let fontSize = 20;
|
|
||||||
let size = 70;
|
|
||||||
if (place === 2) {
|
|
||||||
icon = 'podium-silver';
|
|
||||||
color = theme.colors.gameSilver;
|
|
||||||
fontSize = 18;
|
|
||||||
size = 60;
|
|
||||||
} else if (place === 3) {
|
|
||||||
icon = 'podium-bronze';
|
|
||||||
color = theme.colors.gameBronze;
|
|
||||||
fontSize = 15;
|
|
||||||
size = 50;
|
|
||||||
}
|
|
||||||
const marginLeft = place === 2 ? 20 : 'auto';
|
|
||||||
const marginRight = place === 3 ? 20 : 'auto';
|
|
||||||
const fontWeight = place === 1 ? 'bold' : undefined;
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
marginLeft: marginLeft,
|
|
||||||
marginRight: marginRight,
|
|
||||||
...styles.podiumContainer,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isHighScore && place === 1 ? (
|
|
||||||
<Animatable.View
|
|
||||||
animation="swing"
|
|
||||||
iterationCount="infinite"
|
|
||||||
duration={2000}
|
|
||||||
delay={1000}
|
|
||||||
useNativeDriver
|
|
||||||
style={styles.podiumIconContainer}
|
|
||||||
>
|
|
||||||
<Animatable.View
|
|
||||||
animation="pulse"
|
|
||||||
iterationCount="infinite"
|
|
||||||
useNativeDriver
|
|
||||||
>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name="decagram"
|
|
||||||
color={theme.colors.gameGold}
|
|
||||||
size={150}
|
|
||||||
/>
|
|
||||||
</Animatable.View>
|
|
||||||
</Animatable.View>
|
|
||||||
) : null}
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={icon}
|
|
||||||
color={isHighScore && place === 1 ? '#fff' : color}
|
|
||||||
size={size}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontWeight: fontWeight,
|
|
||||||
fontSize,
|
|
||||||
...styles.centertext,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{score}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { View } from 'react-native';
|
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
||||||
import i18n from 'i18n-js';
|
|
||||||
import { Text, useTheme } from 'react-native-paper';
|
|
||||||
import { StyleSheet } from 'react-native';
|
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
score: number;
|
|
||||||
highScore?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
scoreMainContainer: {
|
|
||||||
marginTop: 10,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
scoreCurrentContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
|
||||||
scoreText: {
|
|
||||||
marginLeft: 5,
|
|
||||||
fontSize: 20,
|
|
||||||
},
|
|
||||||
scoreBestContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
marginTop: 5,
|
|
||||||
},
|
|
||||||
centerVerticalSmallMargin: {
|
|
||||||
...GENERAL_STYLES.centerVertical,
|
|
||||||
marginLeft: 5,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function GameScore(props: Props) {
|
|
||||||
const { score, highScore } = props;
|
|
||||||
const theme = useTheme();
|
|
||||||
const displayHighScore =
|
|
||||||
highScore == null || score > highScore ? score : highScore;
|
|
||||||
return (
|
|
||||||
<View style={styles.scoreMainContainer}>
|
|
||||||
<View style={styles.scoreCurrentContainer}>
|
|
||||||
<Text style={styles.scoreText}>
|
|
||||||
{i18n.t('screens.game.score', { score: score })}
|
|
||||||
</Text>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name="star"
|
|
||||||
color={theme.colors.tetrisScore}
|
|
||||||
size={20}
|
|
||||||
style={styles.centerVerticalSmallMargin}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.scoreBestContainer}>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
...styles.scoreText,
|
|
||||||
color: theme.colors.textDisabled,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n.t('screens.game.highScore', { score: displayHighScore })}
|
|
||||||
</Text>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name="star"
|
|
||||||
color={theme.colors.tetrisScore}
|
|
||||||
size={10}
|
|
||||||
style={styles.centerVerticalSmallMargin}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default React.memo(
|
|
||||||
GameScore,
|
|
||||||
(pp, np) => pp.highScore === np.highScore && pp.score === np.score
|
|
||||||
);
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
import { Caption, Text, useTheme } from 'react-native-paper';
|
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
|
||||||
import i18n from 'i18n-js';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
time: number;
|
|
||||||
level: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
centerSmallMargin: {
|
|
||||||
...GENERAL_STYLES.centerHorizontal,
|
|
||||||
marginBottom: 5,
|
|
||||||
},
|
|
||||||
centerBigMargin: {
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
marginBottom: 20,
|
|
||||||
},
|
|
||||||
statusContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
},
|
|
||||||
statusIcon: {
|
|
||||||
marginLeft: 5,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function getFormattedTime(seconds: number): string {
|
|
||||||
const date = new Date();
|
|
||||||
date.setHours(0);
|
|
||||||
date.setMinutes(0);
|
|
||||||
date.setSeconds(seconds);
|
|
||||||
let format;
|
|
||||||
if (date.getHours()) {
|
|
||||||
format = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
|
||||||
} else if (date.getMinutes()) {
|
|
||||||
format = `${date.getMinutes()}:${date.getSeconds()}`;
|
|
||||||
} else {
|
|
||||||
format = date.getSeconds().toString();
|
|
||||||
}
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
function GameStatus(props: Props) {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
...GENERAL_STYLES.flex,
|
|
||||||
...GENERAL_STYLES.centerVertical,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<View style={GENERAL_STYLES.centerHorizontal}>
|
|
||||||
<Caption style={styles.centerSmallMargin}>
|
|
||||||
{i18n.t('screens.game.time')}
|
|
||||||
</Caption>
|
|
||||||
<View style={styles.statusContainer}>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={'timer'}
|
|
||||||
color={theme.colors.subtitle}
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
...styles.statusIcon,
|
|
||||||
color: theme.colors.subtitle,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getFormattedTime(props.time)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={styles.centerBigMargin}>
|
|
||||||
<Caption style={styles.centerSmallMargin}>
|
|
||||||
{i18n.t('screens.game.level')}
|
|
||||||
</Caption>
|
|
||||||
<View style={styles.statusContainer}>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={'gamepad-square'}
|
|
||||||
color={theme.colors.text}
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<Text style={styles.statusIcon}>{props.level}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default React.memo(
|
|
||||||
GameStatus,
|
|
||||||
(pp, np) => pp.level === np.level && pp.time === np.time
|
|
||||||
);
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
import { Card, Divider, Headline, Text, useTheme } from 'react-native-paper';
|
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
||||||
import Mascot, { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
|
||||||
import SpeechArrow from '../../../components/Mascot/SpeechArrow';
|
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
|
||||||
import i18n from 'i18n-js';
|
|
||||||
|
|
||||||
type GameStatsType = {
|
|
||||||
score: number;
|
|
||||||
level: number;
|
|
||||||
time: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isHighScore: boolean;
|
|
||||||
stats: GameStatsType;
|
|
||||||
};
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
recapCard: {
|
|
||||||
borderWidth: 2,
|
|
||||||
marginLeft: 20,
|
|
||||||
marginRight: 20,
|
|
||||||
},
|
|
||||||
recapContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
|
||||||
recapScoreContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
marginTop: 10,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
recapScore: {
|
|
||||||
fontSize: 20,
|
|
||||||
},
|
|
||||||
recapScoreIcon: {
|
|
||||||
marginLeft: 5,
|
|
||||||
},
|
|
||||||
recapIcon: {
|
|
||||||
marginRight: 5,
|
|
||||||
marginLeft: 5,
|
|
||||||
},
|
|
||||||
centertext: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function PostGameContent(props: Props) {
|
|
||||||
const { isHighScore, stats } = props;
|
|
||||||
const theme = useTheme();
|
|
||||||
const width = isHighScore ? '50%' : '30%';
|
|
||||||
const margin = isHighScore ? 'auto' : undefined;
|
|
||||||
const marginLeft = isHighScore ? '60%' : '20%';
|
|
||||||
const color = isHighScore ? theme.colors.gameGold : theme.colors.primary;
|
|
||||||
return (
|
|
||||||
<View style={GENERAL_STYLES.flex}>
|
|
||||||
<Mascot
|
|
||||||
emotion={isHighScore ? MASCOT_STYLE.LOVE : MASCOT_STYLE.NORMAL}
|
|
||||||
animated={isHighScore}
|
|
||||||
style={{
|
|
||||||
width: width,
|
|
||||||
marginLeft: margin,
|
|
||||||
marginRight: margin,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SpeechArrow
|
|
||||||
style={{ marginLeft: marginLeft }}
|
|
||||||
size={20}
|
|
||||||
color={theme.colors.mascotMessageArrow}
|
|
||||||
/>
|
|
||||||
<Card
|
|
||||||
style={{
|
|
||||||
borderColor: theme.colors.mascotMessageArrow,
|
|
||||||
...styles.recapCard,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Card.Content>
|
|
||||||
<Headline
|
|
||||||
style={{
|
|
||||||
color: color,
|
|
||||||
...styles.centertext,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isHighScore
|
|
||||||
? i18n.t('screens.game.newHighScore')
|
|
||||||
: i18n.t('screens.game.gameOver')}
|
|
||||||
</Headline>
|
|
||||||
<Divider />
|
|
||||||
<View style={styles.recapScoreContainer}>
|
|
||||||
<Text style={styles.recapScore}>
|
|
||||||
{i18n.t('screens.game.score', { score: stats.score })}
|
|
||||||
</Text>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={'star'}
|
|
||||||
color={theme.colors.tetrisScore}
|
|
||||||
size={30}
|
|
||||||
style={styles.recapScoreIcon}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.recapContainer}>
|
|
||||||
<Text>{i18n.t('screens.game.level')}</Text>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
style={styles.recapIcon}
|
|
||||||
name={'gamepad-square'}
|
|
||||||
size={20}
|
|
||||||
color={theme.colors.textDisabled}
|
|
||||||
/>
|
|
||||||
<Text>{stats.level}</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.recapContainer}>
|
|
||||||
<Text>{i18n.t('screens.game.time')}</Text>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
style={styles.recapIcon}
|
|
||||||
name={'timer'}
|
|
||||||
size={20}
|
|
||||||
color={theme.colors.textDisabled}
|
|
||||||
/>
|
|
||||||
<Text>{stats.time}</Text>
|
|
||||||
</View>
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
Divider,
|
|
||||||
Headline,
|
|
||||||
Paragraph,
|
|
||||||
useTheme,
|
|
||||||
} from 'react-native-paper';
|
|
||||||
import Mascot, { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
|
||||||
import SpeechArrow from '../../../components/Mascot/SpeechArrow';
|
|
||||||
import i18n from 'i18n-js';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
welcomeMascot: {
|
|
||||||
width: '40%',
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
|
||||||
welcomeCard: {
|
|
||||||
borderWidth: 2,
|
|
||||||
marginLeft: 10,
|
|
||||||
marginRight: 10,
|
|
||||||
},
|
|
||||||
speechArrow: {
|
|
||||||
marginLeft: '60%',
|
|
||||||
},
|
|
||||||
welcomeText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
marginTop: 10,
|
|
||||||
},
|
|
||||||
centertext: {
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function WelcomeGameContent() {
|
|
||||||
const theme = useTheme();
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Mascot emotion={MASCOT_STYLE.COOL} style={styles.welcomeMascot} />
|
|
||||||
<SpeechArrow
|
|
||||||
style={styles.speechArrow}
|
|
||||||
size={20}
|
|
||||||
color={theme.colors.mascotMessageArrow}
|
|
||||||
/>
|
|
||||||
<Card
|
|
||||||
style={{
|
|
||||||
borderColor: theme.colors.mascotMessageArrow,
|
|
||||||
...styles.welcomeCard,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Card.Content>
|
|
||||||
<Headline
|
|
||||||
style={{
|
|
||||||
color: theme.colors.primary,
|
|
||||||
...styles.centertext,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n.t('screens.game.welcomeTitle')}
|
|
||||||
</Headline>
|
|
||||||
<Divider />
|
|
||||||
<Paragraph style={styles.welcomeText}>
|
|
||||||
{i18n.t('screens.game.welcomeMessage')}
|
|
||||||
</Paragraph>
|
|
||||||
</Card.Content>
|
|
||||||
</Card>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -17,9 +17,10 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
import * as React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import { useTheme } from 'react-native-paper';
|
import { Caption, IconButton, Text, withTheme } from 'react-native-paper';
|
||||||
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import GameLogic from '../logic/GameLogic';
|
import GameLogic from '../logic/GameLogic';
|
||||||
|
|
@ -32,16 +33,25 @@ import MaterialHeaderButtons, {
|
||||||
import type { OptionsDialogButtonType } from '../../../components/Dialogs/OptionsDialog';
|
import type { OptionsDialogButtonType } from '../../../components/Dialogs/OptionsDialog';
|
||||||
import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
|
import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
import GENERAL_STYLES from '../../../constants/Styles';
|
||||||
import { MainRoutes } from '../../../navigation/MainNavigator';
|
|
||||||
import GameStatus from '../components/GameStatus';
|
type PropsType = {
|
||||||
import GameControls from '../components/GameControls';
|
navigation: StackNavigationProp<any>;
|
||||||
import GameScore from '../components/GameScore';
|
route: { params: { highScore: number } };
|
||||||
import { usePreferences } from '../../../context/preferencesContext';
|
theme: ReactNativePaper.Theme;
|
||||||
import {
|
};
|
||||||
getPreferenceObject,
|
|
||||||
PreferenceKeys,
|
type StateType = {
|
||||||
} from '../../../utils/asyncStorage';
|
grid: GridType;
|
||||||
import { useFocusEffect, useNavigation } from '@react-navigation/core';
|
gameTime: number;
|
||||||
|
gameScore: number;
|
||||||
|
gameLevel: number;
|
||||||
|
|
||||||
|
dialogVisible: boolean;
|
||||||
|
dialogTitle: string;
|
||||||
|
dialogMessage: string;
|
||||||
|
dialogButtons: Array<OptionsDialogButtonType>;
|
||||||
|
onDialogDismiss: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
|
@ -51,6 +61,44 @@ const styles = StyleSheet.create({
|
||||||
gridContainer: {
|
gridContainer: {
|
||||||
flex: 4,
|
flex: 4,
|
||||||
},
|
},
|
||||||
|
centerSmallMargin: {
|
||||||
|
...GENERAL_STYLES.centerHorizontal,
|
||||||
|
marginBottom: 5,
|
||||||
|
},
|
||||||
|
centerVerticalSmallMargin: {
|
||||||
|
...GENERAL_STYLES.centerVertical,
|
||||||
|
marginLeft: 5,
|
||||||
|
},
|
||||||
|
centerBigMargin: {
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
statusContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
statusIcon: {
|
||||||
|
marginLeft: 5,
|
||||||
|
},
|
||||||
|
scoreMainContainer: {
|
||||||
|
marginTop: 10,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
scoreCurrentContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
},
|
||||||
|
scoreText: {
|
||||||
|
marginLeft: 5,
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
scoreBestContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginTop: 5,
|
||||||
|
},
|
||||||
controlsContainer: {
|
controlsContainer: {
|
||||||
height: 80,
|
height: 80,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
@ -66,133 +114,273 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function GameMainScreen() {
|
class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
const theme = useTheme();
|
static getFormattedTime(seconds: number): string {
|
||||||
const navigation = useNavigation<StackNavigationProp<any>>();
|
const date = new Date();
|
||||||
const logic = useRef(new GameLogic(20, 10, theme));
|
date.setHours(0);
|
||||||
|
date.setMinutes(0);
|
||||||
const [gameTime, setGameTime] = useState(0);
|
date.setSeconds(seconds);
|
||||||
|
let format;
|
||||||
const [gameState, setGameState] = useState({
|
if (date.getHours()) {
|
||||||
grid: logic.current.getCurrentGrid(),
|
format = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
||||||
gameScore: 0,
|
} else if (date.getMinutes()) {
|
||||||
gameLevel: 0,
|
format = `${date.getMinutes()}:${date.getSeconds()}`;
|
||||||
});
|
|
||||||
|
|
||||||
const [dialogContent, setDialogContent] = useState<{
|
|
||||||
dialogTitle: string;
|
|
||||||
dialogMessage: string;
|
|
||||||
dialogButtons: Array<OptionsDialogButtonType>;
|
|
||||||
onDialogDismiss: () => void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const { preferences, updatePreferences } = usePreferences();
|
|
||||||
|
|
||||||
function getScores() {
|
|
||||||
const pref = getPreferenceObject(PreferenceKeys.gameScores, preferences) as
|
|
||||||
| Array<number>
|
|
||||||
| undefined;
|
|
||||||
if (pref) {
|
|
||||||
return pref.sort((a, b) => b - a);
|
|
||||||
} else {
|
} else {
|
||||||
return [];
|
format = date.getSeconds().toString();
|
||||||
|
}
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
logic: GameLogic;
|
||||||
|
|
||||||
|
highScore: number | null;
|
||||||
|
|
||||||
|
constructor(props: PropsType) {
|
||||||
|
super(props);
|
||||||
|
this.highScore = null;
|
||||||
|
this.logic = new GameLogic(20, 10, props.theme);
|
||||||
|
this.state = {
|
||||||
|
grid: this.logic.getCurrentGrid(),
|
||||||
|
gameTime: 0,
|
||||||
|
gameScore: 0,
|
||||||
|
gameLevel: 0,
|
||||||
|
dialogVisible: false,
|
||||||
|
dialogTitle: '',
|
||||||
|
dialogMessage: '',
|
||||||
|
dialogButtons: [],
|
||||||
|
onDialogDismiss: () => {},
|
||||||
|
};
|
||||||
|
if (props.route.params != null) {
|
||||||
|
this.highScore = props.route.params.highScore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedScores = getScores();
|
componentDidMount() {
|
||||||
const highScore = savedScores.length > 0 ? savedScores[0] : undefined;
|
const { navigation } = this.props;
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerRight: getRightButton,
|
headerRight: this.getRightButton,
|
||||||
});
|
});
|
||||||
startGame();
|
this.startGame();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}
|
||||||
}, [navigation]);
|
|
||||||
|
|
||||||
useFocusEffect(
|
componentWillUnmount() {
|
||||||
useCallback(() => {
|
this.logic.endGame(true);
|
||||||
const l = logic.current;
|
}
|
||||||
return () => l.endGame(true);
|
|
||||||
}, [])
|
|
||||||
);
|
|
||||||
|
|
||||||
const getRightButton = () => (
|
getRightButton = () => {
|
||||||
<MaterialHeaderButtons>
|
return (
|
||||||
<Item title={'pause'} iconName={'pause'} onPress={togglePause} />
|
<MaterialHeaderButtons>
|
||||||
</MaterialHeaderButtons>
|
<Item title="pause" iconName="pause" onPress={this.togglePause} />
|
||||||
);
|
</MaterialHeaderButtons>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const onTick = (score: number, level: number, newGrid: GridType) => {
|
onTick = (score: number, level: number, newGrid: GridType) => {
|
||||||
setGameState({
|
this.setState({
|
||||||
gameScore: score,
|
gameScore: score,
|
||||||
gameLevel: level,
|
gameLevel: level,
|
||||||
grid: newGrid,
|
grid: newGrid,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDialogDismiss = () => setDialogContent(undefined);
|
onClock = (time: number) => {
|
||||||
|
this.setState({
|
||||||
|
gameTime: time,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onGameEnd = (time: number, score: number, isRestart: boolean) => {
|
onDialogDismiss = () => {
|
||||||
setGameState((prevState) => ({
|
this.setState({ dialogVisible: false });
|
||||||
...prevState,
|
};
|
||||||
|
|
||||||
|
onGameEnd = (time: number, score: number, isRestart: boolean) => {
|
||||||
|
const { props, state } = this;
|
||||||
|
this.setState({
|
||||||
|
gameTime: time,
|
||||||
gameScore: score,
|
gameScore: score,
|
||||||
}));
|
});
|
||||||
setGameTime(time);
|
|
||||||
const newScores = [...savedScores];
|
|
||||||
const isHighScore = newScores.length === 0 || score > newScores[0];
|
|
||||||
for (let i = 0; i < 3; i += 1) {
|
|
||||||
if (newScores.length > i && score > newScores[i]) {
|
|
||||||
newScores.splice(i, 0, score);
|
|
||||||
break;
|
|
||||||
} else if (newScores.length <= i) {
|
|
||||||
newScores.push(score);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (newScores.length > 3) {
|
|
||||||
newScores.splice(3, 1);
|
|
||||||
}
|
|
||||||
if (newScores.some((item, i) => item !== savedScores[i])) {
|
|
||||||
updatePreferences(PreferenceKeys.gameScores, newScores);
|
|
||||||
}
|
|
||||||
if (!isRestart) {
|
if (!isRestart) {
|
||||||
navigation.replace(MainRoutes.GameStart, {
|
props.navigation.replace('game-start', {
|
||||||
score: score,
|
score: state.gameScore,
|
||||||
level: gameState.gameLevel,
|
level: state.gameLevel,
|
||||||
time: time,
|
time: state.gameTime,
|
||||||
isHighScore: isHighScore,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDirectionPressed = (newGrid: GridType, score?: number) => {
|
getStatusIcons() {
|
||||||
setGameState((prevState) => ({
|
const { props, state } = this;
|
||||||
...prevState,
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
...GENERAL_STYLES.flex,
|
||||||
|
...GENERAL_STYLES.centerVertical,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={GENERAL_STYLES.centerHorizontal}>
|
||||||
|
<Caption style={styles.centerSmallMargin}>
|
||||||
|
{i18n.t('screens.game.time')}
|
||||||
|
</Caption>
|
||||||
|
<View style={styles.statusContainer}>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="timer"
|
||||||
|
color={props.theme.colors.subtitle}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
...styles.statusIcon,
|
||||||
|
color: props.theme.colors.subtitle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{GameMainScreen.getFormattedTime(state.gameTime)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.centerBigMargin}>
|
||||||
|
<Caption style={styles.centerSmallMargin}>
|
||||||
|
{i18n.t('screens.game.level')}
|
||||||
|
</Caption>
|
||||||
|
<View style={styles.statusContainer}>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="gamepad-square"
|
||||||
|
color={props.theme.colors.text}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
<Text style={styles.statusIcon}>{state.gameLevel}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getScoreIcon() {
|
||||||
|
const { props, state } = this;
|
||||||
|
const highScore =
|
||||||
|
this.highScore == null || state.gameScore > this.highScore
|
||||||
|
? state.gameScore
|
||||||
|
: this.highScore;
|
||||||
|
return (
|
||||||
|
<View style={styles.scoreMainContainer}>
|
||||||
|
<View style={styles.scoreCurrentContainer}>
|
||||||
|
<Text style={styles.scoreText}>
|
||||||
|
{i18n.t('screens.game.score', { score: state.gameScore })}
|
||||||
|
</Text>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="star"
|
||||||
|
color={props.theme.colors.tetrisScore}
|
||||||
|
size={20}
|
||||||
|
style={styles.centerVerticalSmallMargin}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.scoreBestContainer}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
...styles.scoreText,
|
||||||
|
color: props.theme.colors.textDisabled,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t('screens.game.highScore', { score: highScore })}
|
||||||
|
</Text>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="star"
|
||||||
|
color={props.theme.colors.tetrisScore}
|
||||||
|
size={10}
|
||||||
|
style={styles.centerVerticalSmallMargin}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getControlButtons() {
|
||||||
|
const { props } = this;
|
||||||
|
return (
|
||||||
|
<View style={styles.controlsContainer}>
|
||||||
|
<IconButton
|
||||||
|
icon="rotate-right-variant"
|
||||||
|
size={40}
|
||||||
|
onPress={() => {
|
||||||
|
this.logic.rotatePressed(this.updateGrid);
|
||||||
|
}}
|
||||||
|
style={GENERAL_STYLES.flex}
|
||||||
|
/>
|
||||||
|
<View style={styles.directionsContainer}>
|
||||||
|
<IconButton
|
||||||
|
icon="chevron-left"
|
||||||
|
size={40}
|
||||||
|
style={GENERAL_STYLES.flex}
|
||||||
|
onPress={() => {
|
||||||
|
this.logic.pressedOut();
|
||||||
|
}}
|
||||||
|
onPressIn={() => {
|
||||||
|
this.logic.leftPressedIn(this.updateGrid);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon="chevron-right"
|
||||||
|
size={40}
|
||||||
|
style={GENERAL_STYLES.flex}
|
||||||
|
onPress={() => {
|
||||||
|
this.logic.pressedOut();
|
||||||
|
}}
|
||||||
|
onPressIn={() => {
|
||||||
|
this.logic.rightPressed(this.updateGrid);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<IconButton
|
||||||
|
icon="arrow-down-bold"
|
||||||
|
size={40}
|
||||||
|
onPressIn={() => {
|
||||||
|
this.logic.downPressedIn(this.updateGridScore);
|
||||||
|
}}
|
||||||
|
onPress={() => {
|
||||||
|
this.logic.pressedOut();
|
||||||
|
}}
|
||||||
|
style={GENERAL_STYLES.flex}
|
||||||
|
color={props.theme.colors.tetrisScore}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGrid = (newGrid: GridType) => {
|
||||||
|
this.setState({
|
||||||
|
grid: newGrid,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateGridScore = (newGrid: GridType, score?: number) => {
|
||||||
|
this.setState((prevState: StateType): {
|
||||||
|
grid: GridType;
|
||||||
|
gameScore: number;
|
||||||
|
} => ({
|
||||||
grid: newGrid,
|
grid: newGrid,
|
||||||
gameScore: score != null ? score : prevState.gameScore,
|
gameScore: score != null ? score : prevState.gameScore,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const togglePause = () => {
|
togglePause = () => {
|
||||||
logic.current.togglePause();
|
this.logic.togglePause();
|
||||||
if (logic.current.isGamePaused()) {
|
if (this.logic.isGamePaused()) {
|
||||||
showPausePopup();
|
this.showPausePopup();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showPausePopup = () => {
|
showPausePopup = () => {
|
||||||
const onDismiss = () => {
|
const onDismiss = () => {
|
||||||
togglePause();
|
this.togglePause();
|
||||||
onDialogDismiss();
|
this.onDialogDismiss();
|
||||||
};
|
};
|
||||||
setDialogContent({
|
this.setState({
|
||||||
|
dialogVisible: true,
|
||||||
dialogTitle: i18n.t('screens.game.pause'),
|
dialogTitle: i18n.t('screens.game.pause'),
|
||||||
dialogMessage: i18n.t('screens.game.pauseMessage'),
|
dialogMessage: i18n.t('screens.game.pauseMessage'),
|
||||||
dialogButtons: [
|
dialogButtons: [
|
||||||
{
|
{
|
||||||
title: i18n.t('screens.game.restart.text'),
|
title: i18n.t('screens.game.restart.text'),
|
||||||
onPress: showRestartConfirm,
|
onPress: this.showRestartConfirm,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.t('screens.game.resume'),
|
title: i18n.t('screens.game.resume'),
|
||||||
|
|
@ -203,68 +391,71 @@ export default function GameMainScreen() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const showRestartConfirm = () => {
|
showRestartConfirm = () => {
|
||||||
setDialogContent({
|
this.setState({
|
||||||
|
dialogVisible: true,
|
||||||
dialogTitle: i18n.t('screens.game.restart.confirm'),
|
dialogTitle: i18n.t('screens.game.restart.confirm'),
|
||||||
dialogMessage: i18n.t('screens.game.restart.confirmMessage'),
|
dialogMessage: i18n.t('screens.game.restart.confirmMessage'),
|
||||||
dialogButtons: [
|
dialogButtons: [
|
||||||
{
|
{
|
||||||
title: i18n.t('screens.game.restart.confirmYes'),
|
title: i18n.t('screens.game.restart.confirmYes'),
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
onDialogDismiss();
|
this.onDialogDismiss();
|
||||||
startGame();
|
this.startGame();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.t('screens.game.restart.confirmNo'),
|
title: i18n.t('screens.game.restart.confirmNo'),
|
||||||
onPress: showPausePopup,
|
onPress: this.showPausePopup,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onDialogDismiss: showPausePopup,
|
onDialogDismiss: this.showPausePopup,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const startGame = () => {
|
startGame = () => {
|
||||||
logic.current.startGame(onTick, setGameTime, onGameEnd);
|
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<View style={GENERAL_STYLES.flex}>
|
const { props, state } = this;
|
||||||
<View style={styles.container}>
|
return (
|
||||||
<GameStatus time={gameTime} level={gameState.gameLevel} />
|
<View style={GENERAL_STYLES.flex}>
|
||||||
<View style={styles.gridContainer}>
|
<View style={styles.container}>
|
||||||
<GameScore score={gameState.gameScore} highScore={highScore} />
|
{this.getStatusIcons()}
|
||||||
<GridComponent
|
<View style={styles.gridContainer}>
|
||||||
width={logic.current.getWidth()}
|
{this.getScoreIcon()}
|
||||||
height={logic.current.getHeight()}
|
<GridComponent
|
||||||
grid={gameState.grid}
|
width={this.logic.getWidth()}
|
||||||
style={{
|
height={this.logic.getHeight()}
|
||||||
backgroundColor: theme.colors.tetrisBackground,
|
grid={state.grid}
|
||||||
...GENERAL_STYLES.flex,
|
style={{
|
||||||
...GENERAL_STYLES.centerHorizontal,
|
backgroundColor: props.theme.colors.tetrisBackground,
|
||||||
}}
|
...GENERAL_STYLES.flex,
|
||||||
/>
|
...GENERAL_STYLES.centerHorizontal,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={GENERAL_STYLES.flex}>
|
||||||
|
<Preview
|
||||||
|
items={this.logic.getNextPiecesPreviews()}
|
||||||
|
style={styles.preview}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={GENERAL_STYLES.flex}>
|
{this.getControlButtons()}
|
||||||
<Preview
|
|
||||||
items={logic.current.getNextPiecesPreviews()}
|
|
||||||
style={styles.preview}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<GameControls
|
|
||||||
logic={logic.current}
|
|
||||||
onDirectionPressed={onDirectionPressed}
|
|
||||||
/>
|
|
||||||
{dialogContent ? (
|
|
||||||
<OptionsDialog
|
<OptionsDialog
|
||||||
visible={dialogContent !== undefined}
|
visible={state.dialogVisible}
|
||||||
title={dialogContent.dialogTitle}
|
title={state.dialogTitle}
|
||||||
message={dialogContent.dialogMessage}
|
message={state.dialogMessage}
|
||||||
buttons={dialogContent.dialogButtons}
|
buttons={state.dialogButtons}
|
||||||
onDismiss={dialogContent.onDialogDismiss}
|
onDismiss={state.onDialogDismiss}
|
||||||
/>
|
/>
|
||||||
) : null}
|
</View>
|
||||||
</View>
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withTheme(GameMainScreen);
|
||||||
|
|
|
||||||
|
|
@ -18,121 +18,477 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Button, useTheme } from 'react-native-paper';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
|
Headline,
|
||||||
|
Paragraph,
|
||||||
|
Text,
|
||||||
|
withTheme,
|
||||||
|
} from 'react-native-paper';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
|
import * as Animatable from 'react-native-animatable';
|
||||||
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
import Mascot, { MASCOT_STYLE } from '../../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
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';
|
||||||
|
import Piece from '../logic/Piece';
|
||||||
|
import SpeechArrow from '../../../components/Mascot/SpeechArrow';
|
||||||
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
import GENERAL_STYLES from '../../../constants/Styles';
|
||||||
import GameBackground from '../components/GameBrackground';
|
|
||||||
import PostGameContent from '../components/PostGameContent';
|
|
||||||
import WelcomeGameContent from '../components/WelcomeGameContent';
|
|
||||||
import FullGamePodium from '../components/FullGamePodium';
|
|
||||||
import { useNavigation } from '@react-navigation/core';
|
|
||||||
import { usePreferences } from '../../../context/preferencesContext';
|
|
||||||
import {
|
|
||||||
getPreferenceObject,
|
|
||||||
PreferenceKeys,
|
|
||||||
} from '../../../utils/asyncStorage';
|
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
|
||||||
|
|
||||||
type GameStatsType = {
|
type GameStatsType = {
|
||||||
score: number;
|
score: number;
|
||||||
level: number;
|
level: number;
|
||||||
time: number;
|
time: number;
|
||||||
isHighScore: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
|
navigation: StackNavigationProp<any>;
|
||||||
route: {
|
route: {
|
||||||
params?: GameStatsType;
|
params: GameStatsType;
|
||||||
};
|
};
|
||||||
|
theme: ReactNativePaper.Theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
pieceContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
pieceBackground: {
|
||||||
|
position: 'absolute',
|
||||||
|
},
|
||||||
playButton: {
|
playButton: {
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
},
|
},
|
||||||
|
recapCard: {
|
||||||
|
borderWidth: 2,
|
||||||
|
marginLeft: 20,
|
||||||
|
marginRight: 20,
|
||||||
|
},
|
||||||
|
recapContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
},
|
||||||
|
recapScoreContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginTop: 10,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
recapScore: {
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
recapScoreIcon: {
|
||||||
|
marginLeft: 5,
|
||||||
|
},
|
||||||
|
recapIcon: {
|
||||||
|
marginRight: 5,
|
||||||
|
marginLeft: 5,
|
||||||
|
},
|
||||||
|
welcomeMascot: {
|
||||||
|
width: '40%',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
},
|
||||||
|
welcomeCard: {
|
||||||
|
borderWidth: 2,
|
||||||
|
marginLeft: 10,
|
||||||
|
marginRight: 10,
|
||||||
|
},
|
||||||
|
centertext: {
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
welcomeText: {
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
speechArrow: {
|
||||||
|
marginLeft: '60%',
|
||||||
|
},
|
||||||
|
podiumContainer: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
|
podiumIconContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: -20,
|
||||||
|
},
|
||||||
|
topScoreContainer: {
|
||||||
|
marginBottom: 20,
|
||||||
|
marginTop: 20,
|
||||||
|
},
|
||||||
|
topScoreSubcontainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function GameStartScreen(props: Props) {
|
class GameStartScreen extends React.Component<PropsType> {
|
||||||
const theme = useTheme();
|
gridManager: GridManager;
|
||||||
const navigation = useNavigation<StackNavigationProp<any>>();
|
|
||||||
|
|
||||||
const { preferences } = usePreferences();
|
scores: Array<number>;
|
||||||
|
|
||||||
function getScores() {
|
gameStats?: GameStatsType;
|
||||||
const pref = getPreferenceObject(PreferenceKeys.gameScores, preferences) as
|
|
||||||
| Array<number>
|
isHighScore: boolean;
|
||||||
| undefined;
|
|
||||||
if (pref) {
|
constructor(props: PropsType) {
|
||||||
return pref.sort((a, b) => b - a);
|
super(props);
|
||||||
} else {
|
this.isHighScore = false;
|
||||||
return [];
|
this.gridManager = new GridManager(4, 4, props.theme);
|
||||||
|
this.scores = AsyncStorageManager.getObject(
|
||||||
|
AsyncStorageManager.PREFERENCES.gameScores.key
|
||||||
|
);
|
||||||
|
this.scores.sort((a: number, b: number): number => b - a);
|
||||||
|
if (props.route.params != null) {
|
||||||
|
this.recoverGameScore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const scores = getScores();
|
getPiecesBackground() {
|
||||||
const lastGameStats = props.route.params;
|
const { theme } = this.props;
|
||||||
|
const gridList = [];
|
||||||
|
for (let i = 0; i < 18; i += 1) {
|
||||||
|
gridList.push(this.gridManager.getEmptyGrid(4, 4));
|
||||||
|
const piece = new Piece(theme);
|
||||||
|
piece.toGrid(gridList[i], true);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={styles.pieceContainer}>
|
||||||
|
{gridList.map((item: GridType, index: number) => {
|
||||||
|
const size = 10 + Math.floor(Math.random() * 30);
|
||||||
|
const top = Math.floor(Math.random() * 100);
|
||||||
|
const rot = Math.floor(Math.random() * 360);
|
||||||
|
const left = (index % 6) * 20;
|
||||||
|
const animDelay = size * 20;
|
||||||
|
const animDuration = 2 * (2000 - size * 30);
|
||||||
|
return (
|
||||||
|
<Animatable.View
|
||||||
|
useNativeDriver
|
||||||
|
animation="fadeInDownBig"
|
||||||
|
delay={animDelay}
|
||||||
|
duration={animDuration}
|
||||||
|
key={`piece${index.toString()}`}
|
||||||
|
style={{
|
||||||
|
width: `${size}%`,
|
||||||
|
top: `${top}%`,
|
||||||
|
left: `${left}%`,
|
||||||
|
...styles.pieceBackground,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GridComponent
|
||||||
|
width={4}
|
||||||
|
height={4}
|
||||||
|
grid={item}
|
||||||
|
style={{
|
||||||
|
transform: [{ rotateZ: `${rot}deg` }],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Animatable.View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const getMainContent = () => {
|
getPostGameContent(stats: GameStatsType) {
|
||||||
|
const { props } = this;
|
||||||
|
const width = this.isHighScore ? '50%' : '30%';
|
||||||
|
const margin = this.isHighScore ? 'auto' : undefined;
|
||||||
|
const marginLeft = this.isHighScore ? '60%' : '20%';
|
||||||
|
const color = this.isHighScore
|
||||||
|
? props.theme.colors.gameGold
|
||||||
|
: props.theme.colors.primary;
|
||||||
return (
|
return (
|
||||||
<View style={GENERAL_STYLES.flex}>
|
<View style={GENERAL_STYLES.flex}>
|
||||||
{lastGameStats ? (
|
<Mascot
|
||||||
<PostGameContent
|
emotion={this.isHighScore ? MASCOT_STYLE.LOVE : MASCOT_STYLE.NORMAL}
|
||||||
stats={lastGameStats}
|
animated={this.isHighScore}
|
||||||
isHighScore={lastGameStats.isHighScore}
|
style={{
|
||||||
/>
|
width: width,
|
||||||
) : (
|
marginLeft: margin,
|
||||||
<WelcomeGameContent />
|
marginRight: margin,
|
||||||
)}
|
}}
|
||||||
|
/>
|
||||||
|
<SpeechArrow
|
||||||
|
style={{ marginLeft: marginLeft }}
|
||||||
|
size={20}
|
||||||
|
color={props.theme.colors.mascotMessageArrow}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
borderColor: props.theme.colors.mascotMessageArrow,
|
||||||
|
...styles.recapCard,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card.Content>
|
||||||
|
<Headline
|
||||||
|
style={{
|
||||||
|
color: color,
|
||||||
|
...styles.centertext,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.isHighScore
|
||||||
|
? i18n.t('screens.game.newHighScore')
|
||||||
|
: i18n.t('screens.game.gameOver')}
|
||||||
|
</Headline>
|
||||||
|
<Divider />
|
||||||
|
<View style={styles.recapScoreContainer}>
|
||||||
|
<Text style={styles.recapScore}>
|
||||||
|
{i18n.t('screens.game.score', { score: stats.score })}
|
||||||
|
</Text>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="star"
|
||||||
|
color={props.theme.colors.tetrisScore}
|
||||||
|
size={30}
|
||||||
|
style={styles.recapScoreIcon}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.recapContainer}>
|
||||||
|
<Text>{i18n.t('screens.game.level')}</Text>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
style={styles.recapIcon}
|
||||||
|
name="gamepad-square"
|
||||||
|
size={20}
|
||||||
|
color={props.theme.colors.textDisabled}
|
||||||
|
/>
|
||||||
|
<Text>{stats.level}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.recapContainer}>
|
||||||
|
<Text>{i18n.t('screens.game.time')}</Text>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
style={styles.recapIcon}
|
||||||
|
name="timer"
|
||||||
|
size={20}
|
||||||
|
color={props.theme.colors.textDisabled}
|
||||||
|
/>
|
||||||
|
<Text>{stats.time}</Text>
|
||||||
|
</View>
|
||||||
|
</Card.Content>
|
||||||
|
</Card>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getWelcomeText() {
|
||||||
|
const { props } = this;
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Mascot emotion={MASCOT_STYLE.COOL} style={styles.welcomeMascot} />
|
||||||
|
<SpeechArrow
|
||||||
|
style={styles.speechArrow}
|
||||||
|
size={20}
|
||||||
|
color={props.theme.colors.mascotMessageArrow}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
borderColor: props.theme.colors.mascotMessageArrow,
|
||||||
|
...styles.welcomeCard,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card.Content>
|
||||||
|
<Headline
|
||||||
|
style={{
|
||||||
|
color: props.theme.colors.primary,
|
||||||
|
...styles.centertext,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n.t('screens.game.welcomeTitle')}
|
||||||
|
</Headline>
|
||||||
|
<Divider />
|
||||||
|
<Paragraph style={styles.welcomeText}>
|
||||||
|
{i18n.t('screens.game.welcomeMessage')}
|
||||||
|
</Paragraph>
|
||||||
|
</Card.Content>
|
||||||
|
</Card>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPodiumRender(place: 1 | 2 | 3, score: string) {
|
||||||
|
const { props } = this;
|
||||||
|
let icon = 'podium-gold';
|
||||||
|
let color = props.theme.colors.gameGold;
|
||||||
|
let fontSize = 20;
|
||||||
|
let size = 70;
|
||||||
|
if (place === 2) {
|
||||||
|
icon = 'podium-silver';
|
||||||
|
color = props.theme.colors.gameSilver;
|
||||||
|
fontSize = 18;
|
||||||
|
size = 60;
|
||||||
|
} else if (place === 3) {
|
||||||
|
icon = 'podium-bronze';
|
||||||
|
color = props.theme.colors.gameBronze;
|
||||||
|
fontSize = 15;
|
||||||
|
size = 50;
|
||||||
|
}
|
||||||
|
const marginLeft = place === 2 ? 20 : 'auto';
|
||||||
|
const marginRight = place === 3 ? 20 : 'auto';
|
||||||
|
const fontWeight = place === 1 ? 'bold' : undefined;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginLeft: marginLeft,
|
||||||
|
marginRight: marginRight,
|
||||||
|
...styles.podiumContainer,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.isHighScore && place === 1 ? (
|
||||||
|
<Animatable.View
|
||||||
|
animation="swing"
|
||||||
|
iterationCount="infinite"
|
||||||
|
duration={2000}
|
||||||
|
delay={1000}
|
||||||
|
useNativeDriver
|
||||||
|
style={styles.podiumIconContainer}
|
||||||
|
>
|
||||||
|
<Animatable.View
|
||||||
|
animation="pulse"
|
||||||
|
iterationCount="infinite"
|
||||||
|
useNativeDriver
|
||||||
|
>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="decagram"
|
||||||
|
color={props.theme.colors.gameGold}
|
||||||
|
size={150}
|
||||||
|
/>
|
||||||
|
</Animatable.View>
|
||||||
|
</Animatable.View>
|
||||||
|
) : null}
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name={icon}
|
||||||
|
color={this.isHighScore && place === 1 ? '#fff' : color}
|
||||||
|
size={size}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontWeight: fontWeight,
|
||||||
|
fontSize,
|
||||||
|
...styles.centertext,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{score}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTopScoresRender() {
|
||||||
|
const gold = this.scores.length > 0 ? this.scores[0] : '-';
|
||||||
|
const silver = this.scores.length > 1 ? this.scores[1] : '-';
|
||||||
|
const bronze = this.scores.length > 2 ? this.scores[2] : '-';
|
||||||
|
return (
|
||||||
|
<View style={styles.topScoreContainer}>
|
||||||
|
{this.getPodiumRender(1, gold.toString())}
|
||||||
|
<View style={styles.topScoreSubcontainer}>
|
||||||
|
{this.getPodiumRender(3, bronze.toString())}
|
||||||
|
{this.getPodiumRender(2, silver.toString())}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMainContent() {
|
||||||
|
const { props } = this;
|
||||||
|
return (
|
||||||
|
<View style={GENERAL_STYLES.flex}>
|
||||||
|
{this.gameStats != null
|
||||||
|
? this.getPostGameContent(this.gameStats)
|
||||||
|
: this.getWelcomeText()}
|
||||||
<Button
|
<Button
|
||||||
icon={'play'}
|
icon="play"
|
||||||
mode={'contained'}
|
mode="contained"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigation.replace('game-main');
|
props.navigation.replace('game-main', {
|
||||||
|
highScore: this.scores.length > 0 ? this.scores[0] : null,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
style={styles.playButton}
|
style={styles.playButton}
|
||||||
>
|
>
|
||||||
{i18n.t('screens.game.play')}
|
{i18n.t('screens.game.play')}
|
||||||
</Button>
|
</Button>
|
||||||
<FullGamePodium
|
{this.getTopScoresRender()}
|
||||||
scores={scores}
|
|
||||||
isHighScore={lastGameStats?.isHighScore === true}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
keyExtractor = (item: number): string => item.toString();
|
||||||
<View style={GENERAL_STYLES.flex}>
|
|
||||||
<GameBackground />
|
recoverGameScore() {
|
||||||
<LinearGradient
|
const { route } = this.props;
|
||||||
style={GENERAL_STYLES.flex}
|
this.gameStats = route.params;
|
||||||
colors={[`${theme.colors.background}00`, theme.colors.background]}
|
if (this.gameStats.score != null) {
|
||||||
start={{ x: 0, y: 0 }}
|
this.isHighScore =
|
||||||
end={{ x: 0, y: 1 }}
|
this.scores.length === 0 || this.gameStats.score > this.scores[0];
|
||||||
>
|
for (let i = 0; i < 3; i += 1) {
|
||||||
<CollapsibleScrollView headerColors={'transparent'}>
|
if (this.scores.length > i && this.gameStats.score > this.scores[i]) {
|
||||||
{getMainContent()}
|
this.scores.splice(i, 0, this.gameStats.score);
|
||||||
<MascotPopup
|
break;
|
||||||
title={i18n.t('screens.game.mascotDialog.title')}
|
} else if (this.scores.length <= i) {
|
||||||
message={i18n.t('screens.game.mascotDialog.message')}
|
this.scores.push(this.gameStats.score);
|
||||||
icon="gamepad-variant"
|
break;
|
||||||
buttons={{
|
}
|
||||||
cancel: {
|
}
|
||||||
message: i18n.t('screens.game.mascotDialog.button'),
|
if (this.scores.length > 3) {
|
||||||
icon: 'check',
|
this.scores.splice(3, 1);
|
||||||
},
|
}
|
||||||
}}
|
AsyncStorageManager.set(
|
||||||
emotion={MASCOT_STYLE.COOL}
|
AsyncStorageManager.PREFERENCES.gameScores.key,
|
||||||
/>
|
this.scores
|
||||||
</CollapsibleScrollView>
|
);
|
||||||
</LinearGradient>
|
}
|
||||||
</View>
|
}
|
||||||
);
|
|
||||||
|
render() {
|
||||||
|
const { props } = this;
|
||||||
|
return (
|
||||||
|
<View style={GENERAL_STYLES.flex}>
|
||||||
|
{this.getPiecesBackground()}
|
||||||
|
<LinearGradient
|
||||||
|
style={GENERAL_STYLES.flex}
|
||||||
|
colors={[
|
||||||
|
`${props.theme.colors.background}00`,
|
||||||
|
props.theme.colors.background,
|
||||||
|
]}
|
||||||
|
start={{ x: 0, y: 0 }}
|
||||||
|
end={{ x: 0, y: 1 }}
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
buttons={{
|
||||||
|
cancel: {
|
||||||
|
message: i18n.t('screens.game.mascotDialog.button'),
|
||||||
|
icon: 'check',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
emotion={MASCOT_STYLE.COOL}
|
||||||
|
/>
|
||||||
|
</CollapsibleScrollView>
|
||||||
|
</LinearGradient>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default withTheme(GameStartScreen);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useLayoutEffect, useRef, useState } from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
FlatList,
|
FlatList,
|
||||||
NativeScrollEvent,
|
NativeScrollEvent,
|
||||||
|
|
@ -26,13 +26,9 @@ import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { Headline, useTheme } from 'react-native-paper';
|
import { Headline, withTheme } from 'react-native-paper';
|
||||||
import {
|
import { CommonActions } from '@react-navigation/native';
|
||||||
CommonActions,
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
useFocusEffect,
|
|
||||||
useNavigation,
|
|
||||||
} from '@react-navigation/native';
|
|
||||||
import { StackScreenProps } from '@react-navigation/stack';
|
|
||||||
import * as Animatable from 'react-native-animatable';
|
import * as Animatable from 'react-native-animatable';
|
||||||
import { View } from 'react-native-animatable';
|
import { View } from 'react-native-animatable';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
|
|
@ -48,17 +44,16 @@ import MaterialHeaderButtons, {
|
||||||
import AnimatedFAB from '../../components/Animations/AnimatedFAB';
|
import AnimatedFAB from '../../components/Animations/AnimatedFAB';
|
||||||
import ConnectionManager from '../../managers/ConnectionManager';
|
import ConnectionManager from '../../managers/ConnectionManager';
|
||||||
import LogoutDialog from '../../components/Amicale/LogoutDialog';
|
import LogoutDialog from '../../components/Amicale/LogoutDialog';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||||
|
import DashboardManager from '../../managers/DashboardManager';
|
||||||
|
import type { ServiceItemType } from '../../managers/ServicesManager';
|
||||||
import { getDisplayEvent, getFutureEvents } from '../../utils/Home';
|
import { getDisplayEvent, getFutureEvents } from '../../utils/Home';
|
||||||
import type { PlanningEventType } from '../../utils/Planning';
|
import type { PlanningEventType } from '../../utils/Planning';
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
import Urls from '../../constants/Urls';
|
import Urls from '../../constants/Urls';
|
||||||
import { readData } from '../../utils/WebData';
|
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;
|
const FEED_ITEM_HEIGHT = 500;
|
||||||
|
|
||||||
|
|
@ -93,7 +88,15 @@ type RawDashboardType = {
|
||||||
dashboard: FullDashboardType;
|
dashboard: FullDashboardType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = StackScreenProps<TabStackParamsList, TabRoutes.Home>;
|
type PropsType = {
|
||||||
|
navigation: StackNavigationProp<any>;
|
||||||
|
route: { params: { nextScreen: string; data: object } };
|
||||||
|
theme: ReactNativePaper.Theme;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateType = {
|
||||||
|
dialogVisible: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
dashboardRow: {
|
dashboardRow: {
|
||||||
|
|
@ -124,91 +127,106 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortFeedTime = (a: FeedItemType, b: FeedItemType): number =>
|
/**
|
||||||
b.time - a.time;
|
* 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;
|
||||||
|
|
||||||
const generateNewsFeed = (rawFeed: RawNewsFeedType): Array<FeedItemType> => {
|
static generateNewsFeed(rawFeed: RawNewsFeedType): Array<FeedItemType> {
|
||||||
const finalFeed: Array<FeedItemType> = [];
|
const finalFeed: Array<FeedItemType> = [];
|
||||||
Object.keys(rawFeed).forEach((key: string) => {
|
Object.keys(rawFeed).forEach((key: string) => {
|
||||||
const category: Array<FeedItemType> | null = rawFeed[key];
|
const category: Array<FeedItemType> | null = rawFeed[key];
|
||||||
if (category != null && category.length > 0) {
|
if (category != null && category.length > 0) {
|
||||||
finalFeed.push(...category);
|
finalFeed.push(...category);
|
||||||
}
|
|
||||||
});
|
|
||||||
finalFeed.sort(sortFeedTime);
|
|
||||||
return finalFeed;
|
|
||||||
};
|
|
||||||
|
|
||||||
function HomeScreen(props: Props) {
|
|
||||||
const theme = useTheme();
|
|
||||||
const navigation = useNavigation();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
finalFeed.sort(HomeScreen.sortFeedTime);
|
||||||
}, [navigation, isLoggedIn]);
|
return finalFeed;
|
||||||
|
}
|
||||||
|
|
||||||
useFocusEffect(
|
isLoggedIn: boolean | null;
|
||||||
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 }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ConnectionManager.getInstance().isLoggedIn() !== isLoggedIn) {
|
fabRef: { current: null | AnimatedFAB };
|
||||||
setIsLoggedIn(ConnectionManager.getInstance().isLoggedIn());
|
|
||||||
}
|
currentNewFeed: Array<FeedItemType>;
|
||||||
// handle link open when home is not focused or created
|
|
||||||
handleNavigationParams();
|
currentDashboard: FullDashboardType | null;
|
||||||
return () => {};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
dashboardManager: DashboardManager;
|
||||||
}, [isLoggedIn])
|
|
||||||
);
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { props } = this;
|
||||||
|
props.navigation.addListener('focus', this.onScreenFocus);
|
||||||
|
// Handle link open when home is focused
|
||||||
|
props.navigation.addListener('state', this.handleNavigationParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the event dashboard render item.
|
* Gets the event dashboard render item.
|
||||||
|
|
@ -217,7 +235,7 @@ function HomeScreen(props: Props) {
|
||||||
* @param content
|
* @param content
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
const getDashboardEvent = (content: Array<PlanningEventType>) => {
|
getDashboardEvent(content: Array<PlanningEventType>) {
|
||||||
const futureEvents = getFutureEvents(content);
|
const futureEvents = getFutureEvents(content);
|
||||||
const displayEvent = getDisplayEvent(futureEvents);
|
const displayEvent = getDisplayEvent(futureEvents);
|
||||||
// const clickPreviewAction = () =>
|
// const clickPreviewAction = () =>
|
||||||
|
|
@ -228,15 +246,15 @@ function HomeScreen(props: Props) {
|
||||||
return (
|
return (
|
||||||
<DashboardItem
|
<DashboardItem
|
||||||
eventNumber={futureEvents.length}
|
eventNumber={futureEvents.length}
|
||||||
clickAction={onEventContainerClick}
|
clickAction={this.onEventContainerClick}
|
||||||
>
|
>
|
||||||
<PreviewEventDashboardItem
|
<PreviewEventDashboardItem
|
||||||
event={displayEvent}
|
event={displayEvent}
|
||||||
clickAction={onEventContainerClick}
|
clickAction={this.onEventContainerClick}
|
||||||
/>
|
/>
|
||||||
</DashboardItem>
|
</DashboardItem>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a dashboard item with a row of shortcut buttons.
|
* Gets a dashboard item with a row of shortcut buttons.
|
||||||
|
|
@ -244,16 +262,16 @@ function HomeScreen(props: Props) {
|
||||||
* @param content
|
* @param content
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
const getDashboardRow = (content: Array<ServiceItemType | null>) => {
|
getDashboardRow(content: Array<ServiceItemType | null>) {
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={content}
|
data={content}
|
||||||
renderItem={getDashboardRowRenderItem}
|
renderItem={this.getDashboardRowRenderItem}
|
||||||
horizontal
|
horizontal
|
||||||
contentContainerStyle={styles.dashboardRow}
|
contentContainerStyle={styles.dashboardRow}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a dashboard shortcut item
|
* Gets a dashboard shortcut item
|
||||||
|
|
@ -261,19 +279,15 @@ function HomeScreen(props: Props) {
|
||||||
* @param item
|
* @param item
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
const getDashboardRowRenderItem = ({
|
getDashboardRowRenderItem = ({ item }: { item: ServiceItemType | null }) => {
|
||||||
item,
|
|
||||||
}: {
|
|
||||||
item: ServiceItemType | null;
|
|
||||||
}) => {
|
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
return (
|
return (
|
||||||
<SmallDashboardItem
|
<SmallDashboardItem
|
||||||
image={item.image}
|
image={item.image}
|
||||||
onPress={item.onPress}
|
onPress={item.onPress}
|
||||||
badgeCount={
|
badgeCount={
|
||||||
homeDashboard != null && item.badgeFunction != null
|
this.currentDashboard != null && item.badgeFunction != null
|
||||||
? item.badgeFunction(homeDashboard)
|
? item.badgeFunction(this.currentDashboard)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -282,13 +296,29 @@ function HomeScreen(props: Props) {
|
||||||
return <SmallDashboardItem />;
|
return <SmallDashboardItem />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRenderItem = ({ item }: { item: FeedItemType }) => (
|
/**
|
||||||
<FeedItem item={item} height={FEED_ITEM_HEIGHT} />
|
* 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 getRenderSectionHeader = (data: {
|
/**
|
||||||
|
* 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: {
|
||||||
section: SectionListData<FeedItemType>;
|
section: SectionListData<FeedItemType>;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { props } = this;
|
||||||
const icon = data.section.icon;
|
const icon = data.section.icon;
|
||||||
if (data.section.data.length > 0) {
|
if (data.section.data.length > 0) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -300,7 +330,7 @@ function HomeScreen(props: Props) {
|
||||||
<Headline
|
<Headline
|
||||||
style={{
|
style={{
|
||||||
...styles.sectionHeaderEmpty,
|
...styles.sectionHeaderEmpty,
|
||||||
color: theme.colors.textDisabled,
|
color: props.theme.colors.textDisabled,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data.section.title}
|
{data.section.title}
|
||||||
|
|
@ -309,7 +339,7 @@ function HomeScreen(props: Props) {
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name={icon}
|
name={icon}
|
||||||
size={100}
|
size={100}
|
||||||
color={theme.colors.textDisabled}
|
color={props.theme.colors.textDisabled}
|
||||||
style={GENERAL_STYLES.center}
|
style={GENERAL_STYLES.center}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
@ -317,7 +347,7 @@ function HomeScreen(props: Props) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getListHeader = (fetchedData: RawDashboardType | undefined) => {
|
getListHeader = (fetchedData: RawDashboardType | undefined) => {
|
||||||
let dashboard = null;
|
let dashboard = null;
|
||||||
if (fetchedData != null) {
|
if (fetchedData != null) {
|
||||||
dashboard = fetchedData.dashboard;
|
dashboard = fetchedData.dashboard;
|
||||||
|
|
@ -325,17 +355,41 @@ function HomeScreen(props: Props) {
|
||||||
return (
|
return (
|
||||||
<Animatable.View animation="fadeInDown" duration={500} useNativeDriver>
|
<Animatable.View animation="fadeInDown" duration={500} useNativeDriver>
|
||||||
<ActionsDashBoardItem />
|
<ActionsDashBoardItem />
|
||||||
{getDashboardRow(currentDashboard)}
|
{this.getDashboardRow(this.dashboardManager.getCurrentDashboard())}
|
||||||
{getDashboardEvent(dashboard == null ? [] : dashboard.today_events)}
|
{this.getDashboardEvent(
|
||||||
|
dashboard == null ? [] : dashboard.today_events
|
||||||
|
)}
|
||||||
</Animatable.View>
|
</Animatable.View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showDisconnectDialog = () => setDialogVisible(true);
|
/**
|
||||||
|
* 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 hideDisconnectDialog = () => setDialogVisible(false);
|
showDisconnectDialog = (): void => this.setState({ dialogVisible: true });
|
||||||
|
|
||||||
const openScanner = () => navigation.navigate('scanner');
|
hideDisconnectDialog = (): void => this.setState({ dialogVisible: false });
|
||||||
|
|
||||||
|
openScanner = () => {
|
||||||
|
const { props } = this;
|
||||||
|
props.navigation.navigate('scanner');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the dataset to be used in the FlatList
|
* Creates the dataset to be used in the FlatList
|
||||||
|
|
@ -344,7 +398,7 @@ function HomeScreen(props: Props) {
|
||||||
* @param isLoading
|
* @param isLoading
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
const createDataset = (
|
createDataset = (
|
||||||
fetchedData: RawDashboardType | undefined,
|
fetchedData: RawDashboardType | undefined,
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
): Array<{
|
): Array<{
|
||||||
|
|
@ -353,20 +407,21 @@ function HomeScreen(props: Props) {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
id: string;
|
id: string;
|
||||||
}> => {
|
}> => {
|
||||||
let currentNewFeed: Array<FeedItemType> = [];
|
|
||||||
if (fetchedData) {
|
if (fetchedData) {
|
||||||
if (fetchedData.news_feed) {
|
if (fetchedData.news_feed) {
|
||||||
currentNewFeed = generateNewsFeed(fetchedData.news_feed);
|
this.currentNewFeed = HomeScreen.generateNewsFeed(
|
||||||
|
fetchedData.news_feed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (fetchedData.dashboard) {
|
if (fetchedData.dashboard) {
|
||||||
homeDashboard = fetchedData.dashboard;
|
this.currentDashboard = fetchedData.dashboard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentNewFeed.length > 0) {
|
if (this.currentNewFeed.length > 0) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: i18n.t('screens.home.feedTitle'),
|
title: i18n.t('screens.home.feedTitle'),
|
||||||
data: currentNewFeed,
|
data: this.currentNewFeed,
|
||||||
id: SECTIONS_ID[1],
|
id: SECTIONS_ID[1],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -383,11 +438,14 @@ function HomeScreen(props: Props) {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEventContainerClick = () => navigation.navigate(TabRoutes.Planning);
|
onEventContainerClick = () => {
|
||||||
|
const { props } = this;
|
||||||
|
props.navigation.navigate('planning');
|
||||||
|
};
|
||||||
|
|
||||||
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
if (fabRef.current) {
|
if (this.fabRef.current) {
|
||||||
fabRef.current.onScroll(event);
|
this.fabRef.current.onScroll(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -395,50 +453,63 @@ function HomeScreen(props: Props) {
|
||||||
* Callback when pressing the login button on the banner.
|
* Callback when pressing the login button on the banner.
|
||||||
* This hides the banner and takes the user to the login page.
|
* This hides the banner and takes the user to the login page.
|
||||||
*/
|
*/
|
||||||
const onLogin = () =>
|
onLogin = () => {
|
||||||
navigation.navigate(MainRoutes.Login, {
|
const { props } = this;
|
||||||
|
props.navigation.navigate('login', {
|
||||||
nextScreen: 'profile',
|
nextScreen: 'profile',
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<View style={GENERAL_STYLES.flex}>
|
const { props, state } = this;
|
||||||
<View style={styles.content}>
|
return (
|
||||||
<WebSectionList
|
<View style={GENERAL_STYLES.flex}>
|
||||||
request={() => readData<RawDashboardType>(Urls.app.dashboard)}
|
<View style={styles.content}>
|
||||||
createDataset={createDataset}
|
<WebSectionList
|
||||||
autoRefreshTime={REFRESH_TIME}
|
request={() => readData<RawDashboardType>(Urls.app.dashboard)}
|
||||||
refreshOnFocus={true}
|
createDataset={this.createDataset}
|
||||||
renderItem={getRenderItem}
|
autoRefreshTime={REFRESH_TIME}
|
||||||
itemHeight={FEED_ITEM_HEIGHT}
|
refreshOnFocus={true}
|
||||||
onScroll={onScroll}
|
renderItem={this.getRenderItem}
|
||||||
renderSectionHeader={getRenderSectionHeader}
|
itemHeight={FEED_ITEM_HEIGHT}
|
||||||
renderListHeaderComponent={getListHeader}
|
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}
|
||||||
/>
|
/>
|
||||||
</View>
|
</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 HomeScreen;
|
export default withTheme(HomeScreen);
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
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,
|
|
||||||
GeneralPreferenceKeys,
|
|
||||||
} from '../../utils/asyncStorage';
|
|
||||||
|
|
||||||
export default function IntroScreen() {
|
|
||||||
const { preferences, updatePreferences } = usePreferences();
|
|
||||||
|
|
||||||
const onDone = () => {
|
|
||||||
updatePreferences(GeneralPreferenceKeys.showIntro, false);
|
|
||||||
updatePreferences(GeneralPreferenceKeys.updateNumber, Update.number);
|
|
||||||
updatePreferences(GeneralPreferenceKeys.showAprilFoolsStart, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const showIntro =
|
|
||||||
getPreferenceBool(GeneralPreferenceKeys.showIntro, preferences) !== false;
|
|
||||||
|
|
||||||
const isUpdate =
|
|
||||||
getPreferenceNumber(GeneralPreferenceKeys.updateNumber, preferences) !==
|
|
||||||
Update.number && !showIntro;
|
|
||||||
|
|
||||||
const isAprilFools =
|
|
||||||
AprilFoolsManager.getInstance().isAprilFoolsEnabled() &&
|
|
||||||
getPreferenceBool(
|
|
||||||
GeneralPreferenceKeys.showAprilFoolsStart,
|
|
||||||
preferences
|
|
||||||
) !== false &&
|
|
||||||
!showIntro;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CustomIntroSlider
|
|
||||||
onDone={onDone}
|
|
||||||
isUpdate={isUpdate}
|
|
||||||
isAprilFools={isAprilFools}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
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;
|
|
||||||
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,21 +17,31 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef, useState } from 'react';
|
import * as React from 'react';
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import { Button, Card, Paragraph } from 'react-native-paper';
|
import { Button, Card, Paragraph } from 'react-native-paper';
|
||||||
import { FlatList, StyleSheet } from 'react-native';
|
import { FlatList, StyleSheet } from 'react-native';
|
||||||
import { View } from 'react-native-animatable';
|
import { View } from 'react-native-animatable';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import DashboardEditAccordion from '../../../components/Lists/DashboardEdit/DashboardEditAccordion';
|
import type {
|
||||||
import DashboardEditPreviewItem from '../../../components/Lists/DashboardEdit/DashboardEditPreviewItem';
|
|
||||||
import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
|
|
||||||
import {
|
|
||||||
getCategories,
|
|
||||||
ServiceCategoryType,
|
ServiceCategoryType,
|
||||||
ServiceItemType,
|
ServiceItemType,
|
||||||
} from '../../../utils/Services';
|
} from '../../../managers/ServicesManager';
|
||||||
import { useNavigation } from '@react-navigation/core';
|
import DashboardManager from '../../../managers/DashboardManager';
|
||||||
import { useCurrentDashboard } from '../../../context/preferencesContext';
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
dashboardContainer: {
|
dashboardContainer: {
|
||||||
|
|
@ -61,71 +71,85 @@ const styles = StyleSheet.create({
|
||||||
/**
|
/**
|
||||||
* Class defining the Settings screen. This screen shows controls to modify app preferences.
|
* Class defining the Settings screen. This screen shows controls to modify app preferences.
|
||||||
*/
|
*/
|
||||||
function DashboardEditScreen() {
|
class DashboardEditScreen extends React.Component<PropsType, StateType> {
|
||||||
const navigation = useNavigation();
|
content: Array<ServiceCategoryType>;
|
||||||
|
|
||||||
const {
|
initialDashboard: Array<ServiceItemType | null>;
|
||||||
currentDashboard,
|
|
||||||
currentDashboardIdList,
|
|
||||||
updateCurrentDashboard,
|
|
||||||
} = useCurrentDashboard();
|
|
||||||
const initialDashboard = useRef(currentDashboardIdList);
|
|
||||||
const [activeItem, setActiveItem] = useState(0);
|
|
||||||
|
|
||||||
const getDashboardRowRenderItem = ({
|
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 = ({
|
||||||
item,
|
item,
|
||||||
index,
|
index,
|
||||||
}: {
|
}: {
|
||||||
item: ServiceItemType | null;
|
item: ServiceItemType | null;
|
||||||
index: number;
|
index: number;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { activeItem } = this.state;
|
||||||
return (
|
return (
|
||||||
<DashboardEditPreviewItem
|
<DashboardEditPreviewItem
|
||||||
image={item?.image}
|
image={item?.image}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setActiveItem(index);
|
this.setState({ activeItem: index });
|
||||||
}}
|
}}
|
||||||
isActive={activeItem === index}
|
isActive={activeItem === index}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDashboard = (content: Array<ServiceItemType | null>) => {
|
getDashboard(content: Array<ServiceItemType | null>) {
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={content}
|
data={content}
|
||||||
extraData={activeItem}
|
extraData={this.state}
|
||||||
renderItem={getDashboardRowRenderItem}
|
renderItem={this.getDashboardRowRenderItem}
|
||||||
horizontal
|
horizontal
|
||||||
contentContainerStyle={styles.dashboard}
|
contentContainerStyle={styles.dashboard}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const getRenderItem = ({ item }: { item: ServiceCategoryType }) => {
|
getRenderItem = ({ item }: { item: ServiceCategoryType }) => {
|
||||||
|
const { currentDashboardIdList } = this.state;
|
||||||
return (
|
return (
|
||||||
<DashboardEditAccordion
|
<DashboardEditAccordion
|
||||||
item={item}
|
item={item}
|
||||||
onPress={updateDashboard}
|
onPress={this.updateDashboard}
|
||||||
activeDashboard={currentDashboardIdList}
|
activeDashboard={currentDashboardIdList}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getListHeader = () => {
|
getListHeader() {
|
||||||
|
const { currentDashboard } = this.state;
|
||||||
return (
|
return (
|
||||||
<Card style={styles.card}>
|
<Card style={styles.card}>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<Button
|
<Button
|
||||||
mode={'contained'}
|
mode="contained"
|
||||||
onPress={undoDashboard}
|
onPress={this.undoDashboard}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
>
|
>
|
||||||
{i18n.t('screens.settings.dashboardEdit.undo')}
|
{i18n.t('screens.settings.dashboardEdit.undo')}
|
||||||
</Button>
|
</Button>
|
||||||
<View style={styles.dashboardContainer}>
|
<View style={styles.dashboardContainer}>
|
||||||
{getDashboard(currentDashboard)}
|
{this.getDashboard(currentDashboard)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Paragraph style={styles.text}>
|
<Paragraph style={styles.text}>
|
||||||
|
|
@ -134,28 +158,43 @@ function DashboardEditScreen() {
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const updateDashboard = (service: ServiceItemType) => {
|
updateDashboard = (service: ServiceItemType) => {
|
||||||
updateCurrentDashboard(
|
const { currentDashboard, currentDashboardIdList, activeItem } = this.state;
|
||||||
currentDashboardIdList.map((id, index) =>
|
currentDashboard[activeItem] = service;
|
||||||
index === activeItem ? service.key : id
|
currentDashboardIdList[activeItem] = service.key;
|
||||||
)
|
this.setState({
|
||||||
|
currentDashboard,
|
||||||
|
currentDashboardIdList,
|
||||||
|
});
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.dashboardItems.key,
|
||||||
|
currentDashboardIdList
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const undoDashboard = () => {
|
undoDashboard = () => {
|
||||||
updateCurrentDashboard(initialDashboard.current);
|
this.setState({
|
||||||
|
currentDashboard: [...this.initialDashboard],
|
||||||
|
currentDashboardIdList: [...this.initialDashboardIdList],
|
||||||
|
});
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.dashboardItems.key,
|
||||||
|
this.initialDashboardIdList
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<CollapsibleFlatList
|
return (
|
||||||
data={getCategories(navigation.navigate)}
|
<CollapsibleFlatList
|
||||||
renderItem={getRenderItem}
|
data={this.content}
|
||||||
ListHeaderComponent={getListHeader()}
|
renderItem={this.getRenderItem}
|
||||||
style={{}}
|
ListHeaderComponent={this.getListHeader()}
|
||||||
/>
|
style={{}}
|
||||||
);
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DashboardEditScreen;
|
export default DashboardEditScreen;
|
||||||
|
|
|
||||||
|
|
@ -26,24 +26,28 @@ import {
|
||||||
List,
|
List,
|
||||||
Switch,
|
Switch,
|
||||||
ToggleButton,
|
ToggleButton,
|
||||||
useTheme,
|
withTheme,
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import { Appearance } from 'react-native-appearance';
|
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 CustomSlider from '../../../components/Overrides/CustomSlider';
|
||||||
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||||
import GENERAL_STYLES from '../../../constants/Styles';
|
import GENERAL_STYLES from '../../../constants/Styles';
|
||||||
import {
|
|
||||||
usePreferences,
|
type PropsType = {
|
||||||
useProxiwashPreferences,
|
navigation: StackNavigationProp<any>;
|
||||||
} from '../../../context/preferencesContext';
|
theme: ReactNativePaper.Theme;
|
||||||
import { useNavigation } from '@react-navigation/core';
|
};
|
||||||
import {
|
|
||||||
getPreferenceBool,
|
type StateType = {
|
||||||
getPreferenceNumber,
|
nightMode: boolean;
|
||||||
getPreferenceString,
|
nightModeFollowSystem: boolean;
|
||||||
GeneralPreferenceKeys,
|
startScreenPickerSelected: string;
|
||||||
ProxiwashPreferenceKeys,
|
selectedWash: string;
|
||||||
} from '../../../utils/asyncStorage';
|
isDebugUnlocked: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
slider: {
|
slider: {
|
||||||
|
|
@ -62,74 +66,98 @@ const styles = StyleSheet.create({
|
||||||
/**
|
/**
|
||||||
* Class defining the Settings screen. This screen shows controls to modify app preferences.
|
* Class defining the Settings screen. This screen shows controls to modify app preferences.
|
||||||
*/
|
*/
|
||||||
function SettingsScreen() {
|
class SettingsScreen extends React.Component<PropsType, StateType> {
|
||||||
const navigation = useNavigation();
|
savedNotificationReminder: number;
|
||||||
const theme = useTheme();
|
|
||||||
const generalPreferences = usePreferences();
|
|
||||||
const proxiwashPreferences = useProxiwashPreferences();
|
|
||||||
|
|
||||||
const nightMode = getPreferenceBool(
|
/**
|
||||||
GeneralPreferenceKeys.nightMode,
|
* Loads user preferences into state
|
||||||
generalPreferences.preferences
|
*/
|
||||||
) as boolean;
|
constructor(props: PropsType) {
|
||||||
const nightModeFollowSystem =
|
super(props);
|
||||||
(getPreferenceBool(
|
const notifReminder = AsyncStorageManager.getString(
|
||||||
GeneralPreferenceKeys.nightModeFollowSystem,
|
AsyncStorageManager.PREFERENCES.proxiwashNotifications.key
|
||||||
generalPreferences.preferences
|
);
|
||||||
) as boolean) && Appearance.getColorScheme() !== 'no-preference';
|
this.savedNotificationReminder = parseInt(notifReminder, 10);
|
||||||
const startScreenPickerSelected = getPreferenceString(
|
if (Number.isNaN(this.savedNotificationReminder)) {
|
||||||
GeneralPreferenceKeys.defaultStartScreen,
|
this.savedNotificationReminder = 0;
|
||||||
generalPreferences.preferences
|
}
|
||||||
) as string;
|
|
||||||
const selectedWash = getPreferenceString(
|
|
||||||
ProxiwashPreferenceKeys.selectedWash,
|
|
||||||
proxiwashPreferences.preferences
|
|
||||||
) as string;
|
|
||||||
const isDebugUnlocked = getPreferenceBool(
|
|
||||||
GeneralPreferenceKeys.debugUnlocked,
|
|
||||||
generalPreferences.preferences
|
|
||||||
) as boolean;
|
|
||||||
const notif = getPreferenceNumber(
|
|
||||||
ProxiwashPreferenceKeys.proxiwashNotifications,
|
|
||||||
proxiwashPreferences.preferences
|
|
||||||
);
|
|
||||||
const savedNotificationReminder = !notif || Number.isNaN(notif) ? 0 : notif;
|
|
||||||
|
|
||||||
const onProxiwashNotifPickerValueChange = (value: number) => {
|
this.state = {
|
||||||
proxiwashPreferences.updatePreferences(
|
nightMode: ThemeManager.getNightMode(),
|
||||||
ProxiwashPreferenceKeys.proxiwashNotifications,
|
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
|
value
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onStartScreenPickerValueChange = (value: string) => {
|
/**
|
||||||
|
* Saves the value for the proxiwash reminder notification time
|
||||||
|
*
|
||||||
|
* @param value The value to store
|
||||||
|
*/
|
||||||
|
onStartScreenPickerValueChange = (value: string) => {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
generalPreferences.updatePreferences(
|
this.setState({ startScreenPickerSelected: value });
|
||||||
GeneralPreferenceKeys.defaultStartScreen,
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.defaultStartScreen.key,
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProxiwashNotifPicker = () => {
|
/**
|
||||||
|
* Returns a picker allowing the user to select the proxiwash reminder notification time
|
||||||
|
*
|
||||||
|
* @returns {React.Node}
|
||||||
|
*/
|
||||||
|
getProxiwashNotifPicker() {
|
||||||
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
<CustomSlider
|
<CustomSlider
|
||||||
style={styles.slider}
|
style={styles.slider}
|
||||||
minimumValue={0}
|
minimumValue={0}
|
||||||
maximumValue={10}
|
maximumValue={10}
|
||||||
step={1}
|
step={1}
|
||||||
value={savedNotificationReminder}
|
value={this.savedNotificationReminder}
|
||||||
onValueChange={onProxiwashNotifPickerValueChange}
|
onValueChange={this.onProxiwashNotifPickerValueChange}
|
||||||
thumbTintColor={theme.colors.primary}
|
thumbTintColor={theme.colors.primary}
|
||||||
minimumTrackTintColor={theme.colors.primary}
|
minimumTrackTintColor={theme.colors.primary}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const getProxiwashChangePicker = () => {
|
/**
|
||||||
|
* Returns a radio picker allowing the user to select the proxiwash
|
||||||
|
*
|
||||||
|
* @returns {React.Node}
|
||||||
|
*/
|
||||||
|
getProxiwashChangePicker() {
|
||||||
|
const { selectedWash } = this.state;
|
||||||
return (
|
return (
|
||||||
<RadioButton.Group
|
<RadioButton.Group
|
||||||
onValueChange={onSelectWashValueChange}
|
onValueChange={this.onSelectWashValueChange}
|
||||||
value={selectedWash}
|
value={selectedWash}
|
||||||
>
|
>
|
||||||
<RadioButton.Item
|
<RadioButton.Item
|
||||||
|
|
@ -142,12 +170,18 @@ function SettingsScreen() {
|
||||||
/>
|
/>
|
||||||
</RadioButton.Group>
|
</RadioButton.Group>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const getStartScreenPicker = () => {
|
/**
|
||||||
|
* Returns a picker allowing the user to select the start screen
|
||||||
|
*
|
||||||
|
* @returns {React.Node}
|
||||||
|
*/
|
||||||
|
getStartScreenPicker() {
|
||||||
|
const { startScreenPickerSelected } = this.state;
|
||||||
return (
|
return (
|
||||||
<ToggleButton.Row
|
<ToggleButton.Row
|
||||||
onValueChange={onStartScreenPickerValueChange}
|
onValueChange={this.onStartScreenPickerValueChange}
|
||||||
value={startScreenPickerSelected}
|
value={startScreenPickerSelected}
|
||||||
style={GENERAL_STYLES.centerHorizontal}
|
style={GENERAL_STYLES.centerHorizontal}
|
||||||
>
|
>
|
||||||
|
|
@ -158,20 +192,30 @@ function SettingsScreen() {
|
||||||
<ToggleButton icon="clock" value="planex" />
|
<ToggleButton icon="clock" value="planex" />
|
||||||
</ToggleButton.Row>
|
</ToggleButton.Row>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles night mode and saves it to preferences
|
||||||
|
*/
|
||||||
|
onToggleNightMode = () => {
|
||||||
|
const { nightMode } = this.state;
|
||||||
|
ThemeManager.getInstance().setNightMode(!nightMode);
|
||||||
|
this.setState({ nightMode: !nightMode });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onToggleNightMode = () => {
|
onToggleNightModeFollowSystem = () => {
|
||||||
generalPreferences.updatePreferences(
|
const { nightModeFollowSystem } = this.state;
|
||||||
GeneralPreferenceKeys.nightMode,
|
const value = !nightModeFollowSystem;
|
||||||
!nightMode
|
this.setState({ nightModeFollowSystem: value });
|
||||||
);
|
AsyncStorageManager.set(
|
||||||
};
|
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
|
||||||
|
value
|
||||||
const onToggleNightModeFollowSystem = () => {
|
|
||||||
generalPreferences.updatePreferences(
|
|
||||||
GeneralPreferenceKeys.nightModeFollowSystem,
|
|
||||||
!nightModeFollowSystem
|
|
||||||
);
|
);
|
||||||
|
if (value) {
|
||||||
|
const nightMode = Appearance.getColorScheme() === 'dark';
|
||||||
|
ThemeManager.getInstance().setNightMode(nightMode);
|
||||||
|
this.setState({ nightMode });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -184,13 +228,13 @@ function SettingsScreen() {
|
||||||
* @param state The current state of the switch
|
* @param state The current state of the switch
|
||||||
* @returns {React.Node}
|
* @returns {React.Node}
|
||||||
*/
|
*/
|
||||||
const getToggleItem = (
|
static getToggleItem(
|
||||||
onPressCallback: () => void,
|
onPressCallback: () => void,
|
||||||
icon: string,
|
icon: string,
|
||||||
title: string,
|
title: string,
|
||||||
subtitle: string,
|
subtitle: string,
|
||||||
state: boolean
|
state: boolean
|
||||||
) => {
|
) {
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
title={title}
|
title={title}
|
||||||
|
|
@ -201,15 +245,16 @@ function SettingsScreen() {
|
||||||
right={() => <Switch value={state} onValueChange={onPressCallback} />}
|
right={() => <Switch value={state} onValueChange={onPressCallback} />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const getNavigateItem = (
|
getNavigateItem(
|
||||||
route: string,
|
route: string,
|
||||||
icon: string,
|
icon: string,
|
||||||
title: string,
|
title: string,
|
||||||
subtitle: string,
|
subtitle: string,
|
||||||
onLongPress?: () => void
|
onLongPress?: () => void
|
||||||
) => {
|
) {
|
||||||
|
const { navigation } = this.props;
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
title={title}
|
title={title}
|
||||||
|
|
@ -230,127 +275,144 @@ function SettingsScreen() {
|
||||||
onLongPress={onLongPress}
|
onLongPress={onLongPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const onSelectWashValueChange = (value: string) => {
|
/**
|
||||||
|
* Saves the value for the proxiwash selected wash
|
||||||
|
*
|
||||||
|
* @param value The value to store
|
||||||
|
*/
|
||||||
|
onSelectWashValueChange = (value: string) => {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
proxiwashPreferences.updatePreferences(
|
this.setState({ selectedWash: value });
|
||||||
ProxiwashPreferenceKeys.selectedWash,
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.selectedWash.key,
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const unlockDebugMode = () => {
|
/**
|
||||||
generalPreferences.updatePreferences(
|
* Unlocks debug mode and saves its state to user preferences
|
||||||
GeneralPreferenceKeys.debugUnlocked,
|
*/
|
||||||
|
unlockDebugMode = () => {
|
||||||
|
this.setState({ isDebugUnlocked: true });
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.debugUnlocked.key,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<CollapsibleScrollView>
|
const { nightModeFollowSystem, nightMode, isDebugUnlocked } = this.state;
|
||||||
<Card style={styles.card}>
|
return (
|
||||||
<Card.Title title={i18n.t('screens.settings.generalCard')} />
|
<CollapsibleScrollView>
|
||||||
<List.Section>
|
<Card style={styles.card}>
|
||||||
{Appearance.getColorScheme() !== 'no-preference'
|
<Card.Title title={i18n.t('screens.settings.generalCard')} />
|
||||||
? getToggleItem(
|
<List.Section>
|
||||||
onToggleNightModeFollowSystem,
|
{Appearance.getColorScheme() !== 'no-preference'
|
||||||
'theme-light-dark',
|
? SettingsScreen.getToggleItem(
|
||||||
i18n.t('screens.settings.nightModeAuto'),
|
this.onToggleNightModeFollowSystem,
|
||||||
i18n.t('screens.settings.nightModeAutoSub'),
|
'theme-light-dark',
|
||||||
nightModeFollowSystem
|
i18n.t('screens.settings.nightModeAuto'),
|
||||||
)
|
i18n.t('screens.settings.nightModeAutoSub'),
|
||||||
: null}
|
nightModeFollowSystem
|
||||||
{Appearance.getColorScheme() === 'no-preference' ||
|
)
|
||||||
!nightModeFollowSystem
|
: null}
|
||||||
? getToggleItem(
|
{Appearance.getColorScheme() === 'no-preference' ||
|
||||||
onToggleNightMode,
|
!nightModeFollowSystem
|
||||||
'theme-light-dark',
|
? SettingsScreen.getToggleItem(
|
||||||
i18n.t('screens.settings.nightMode'),
|
this.onToggleNightMode,
|
||||||
nightMode
|
'theme-light-dark',
|
||||||
? i18n.t('screens.settings.nightModeSubOn')
|
i18n.t('screens.settings.nightMode'),
|
||||||
: i18n.t('screens.settings.nightModeSubOff'),
|
nightMode
|
||||||
nightMode
|
? i18n.t('screens.settings.nightModeSubOn')
|
||||||
)
|
: i18n.t('screens.settings.nightModeSubOff'),
|
||||||
: null}
|
nightMode
|
||||||
<List.Item
|
)
|
||||||
title={i18n.t('screens.settings.startScreen')}
|
: null}
|
||||||
description={i18n.t('screens.settings.startScreenSub')}
|
<List.Item
|
||||||
left={(props) => (
|
title={i18n.t('screens.settings.startScreen')}
|
||||||
<List.Icon color={props.color} style={props.style} icon="power" />
|
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')
|
||||||
)}
|
)}
|
||||||
/>
|
</List.Section>
|
||||||
{getStartScreenPicker()}
|
</Card>
|
||||||
{getNavigateItem(
|
<Card style={styles.card}>
|
||||||
'dashboard-edit',
|
<Card.Title title="Proxiwash" />
|
||||||
'view-dashboard',
|
<List.Section>
|
||||||
i18n.t('screens.settings.dashboard'),
|
<List.Item
|
||||||
i18n.t('screens.settings.dashboardSub')
|
title={i18n.t('screens.settings.proxiwashNotifReminder')}
|
||||||
)}
|
description={i18n.t('screens.settings.proxiwashNotifReminderSub')}
|
||||||
</List.Section>
|
left={(props) => (
|
||||||
</Card>
|
<List.Icon
|
||||||
<Card style={styles.card}>
|
color={props.color}
|
||||||
<Card.Title title="Proxiwash" />
|
style={props.style}
|
||||||
<List.Section>
|
icon="washing-machine"
|
||||||
<List.Item
|
/>
|
||||||
title={i18n.t('screens.settings.proxiwashNotifReminder')}
|
)}
|
||||||
description={i18n.t('screens.settings.proxiwashNotifReminderSub')}
|
/>
|
||||||
left={(props) => (
|
<View style={styles.pickerContainer}>
|
||||||
<List.Icon
|
{this.getProxiwashNotifPicker()}
|
||||||
color={props.color}
|
</View>
|
||||||
style={props.style}
|
<List.Item
|
||||||
icon="washing-machine"
|
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
|
||||||
)}
|
)}
|
||||||
/>
|
{this.getNavigateItem(
|
||||||
<View style={styles.pickerContainer}>
|
'feedback',
|
||||||
{getProxiwashNotifPicker()}
|
'comment-quote',
|
||||||
</View>
|
i18n.t('screens.feedback.homeButtonTitle'),
|
||||||
<List.Item
|
i18n.t('screens.feedback.homeButtonSubtitle')
|
||||||
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>
|
||||||
<View style={styles.pickerContainer}>
|
</Card>
|
||||||
{getProxiwashChangePicker()}
|
</CollapsibleScrollView>
|
||||||
</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 SettingsScreen;
|
export default withTheme(SettingsScreen);
|
||||||
|
|
|
||||||
|
|
@ -17,22 +17,23 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useLayoutEffect, useState } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { Searchbar } from 'react-native-paper';
|
import { Searchbar } from 'react-native-paper';
|
||||||
import { stringMatchQuery } from '../../utils/Search';
|
import { stringMatchQuery } from '../../utils/Search';
|
||||||
import WebSectionList from '../../components/Screens/WebSectionList';
|
import WebSectionList from '../../components/Screens/WebSectionList';
|
||||||
import GroupListAccordion from '../../components/Lists/PlanexGroups/GroupListAccordion';
|
import GroupListAccordion from '../../components/Lists/PlanexGroups/GroupListAccordion';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import Urls from '../../constants/Urls';
|
import Urls from '../../constants/Urls';
|
||||||
import { readData } from '../../utils/WebData';
|
import { readData } from '../../utils/WebData';
|
||||||
import { useNavigation } from '@react-navigation/core';
|
import { useNavigation } from '@react-navigation/core';
|
||||||
import { useCachedPlanexGroups } from '../../context/cacheContext';
|
import { useCachedPlanexGroups } from '../../context/cacheContext';
|
||||||
import { usePlanexPreferences } from '../../context/preferencesContext';
|
|
||||||
import {
|
|
||||||
getPreferenceObject,
|
|
||||||
PlanexPreferenceKeys,
|
|
||||||
} from '../../utils/asyncStorage';
|
|
||||||
|
|
||||||
export type PlanexGroupType = {
|
export type PlanexGroupType = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -62,23 +63,13 @@ function sortName(
|
||||||
|
|
||||||
function GroupSelectionScreen() {
|
function GroupSelectionScreen() {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const { preferences, updatePreferences } = usePlanexPreferences();
|
|
||||||
const { groups, setGroups } = useCachedPlanexGroups();
|
const { groups, setGroups } = useCachedPlanexGroups();
|
||||||
const [currentSearchString, setCurrentSearchString] = useState('');
|
const [currentSearchString, setCurrentSearchString] = useState('');
|
||||||
|
const [favoriteGroups, setFavoriteGroups] = useState<Array<PlanexGroupType>>(
|
||||||
const getFavoriteGroups = (): Array<PlanexGroupType> => {
|
AsyncStorageManager.getObject(
|
||||||
const data = getPreferenceObject(
|
AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key
|
||||||
PlanexPreferenceKeys.planexFavoriteGroups,
|
)
|
||||||
preferences
|
);
|
||||||
);
|
|
||||||
if (data) {
|
|
||||||
return data as Array<PlanexGroupType>;
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const favoriteGroups = getFavoriteGroups();
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
|
|
@ -149,8 +140,10 @@ function GroupSelectionScreen() {
|
||||||
* @param item The article pressed
|
* @param item The article pressed
|
||||||
*/
|
*/
|
||||||
const onListItemPress = (item: PlanexGroupType) => {
|
const onListItemPress = (item: PlanexGroupType) => {
|
||||||
updatePreferences(PlanexPreferenceKeys.planexCurrentGroup, item);
|
navigation.navigate('planex', {
|
||||||
navigation.goBack();
|
screen: 'index',
|
||||||
|
params: { group: item },
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -160,16 +153,12 @@ function GroupSelectionScreen() {
|
||||||
*/
|
*/
|
||||||
const onListFavoritePress = useCallback(
|
const onListFavoritePress = useCallback(
|
||||||
(group: PlanexGroupType) => {
|
(group: PlanexGroupType) => {
|
||||||
const updateFavorites = (newValue: Array<PlanexGroupType>) => {
|
|
||||||
updatePreferences(PlanexPreferenceKeys.planexFavoriteGroups, newValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeGroupFromFavorites = (g: PlanexGroupType) => {
|
const removeGroupFromFavorites = (g: PlanexGroupType) => {
|
||||||
updateFavorites(favoriteGroups.filter((f) => f.id !== g.id));
|
setFavoriteGroups(favoriteGroups.filter((f) => f.id !== g.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const addGroupToFavorites = (g: PlanexGroupType) => {
|
const addGroupToFavorites = (g: PlanexGroupType) => {
|
||||||
updateFavorites([...favoriteGroups, g].sort(sortName));
|
setFavoriteGroups([...favoriteGroups, g].sort(sortName));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (favoriteGroups.some((f) => f.id === group.id)) {
|
if (favoriteGroups.some((f) => f.id === group.id)) {
|
||||||
|
|
@ -178,9 +167,16 @@ function GroupSelectionScreen() {
|
||||||
addGroupToFavorites(group);
|
addGroupToFavorites(group);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[favoriteGroups, updatePreferences]
|
[favoriteGroups]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.planexFavoriteGroups.key,
|
||||||
|
favoriteGroups
|
||||||
|
);
|
||||||
|
}, [favoriteGroups]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the dataset to be used in the FlatList.
|
* 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.
|
* This improves formatting of group names, sorts alphabetically the categories, and adds favorites at the top.
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,17 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { Title, useTheme } from 'react-native-paper';
|
import { Title, useTheme } from 'react-native-paper';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import {
|
||||||
|
CommonActions,
|
||||||
|
useFocusEffect,
|
||||||
|
useNavigation,
|
||||||
|
} from '@react-navigation/native';
|
||||||
import Autolink from 'react-native-autolink';
|
import Autolink from 'react-native-autolink';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import AlertDialog from '../../components/Dialogs/AlertDialog';
|
import AlertDialog from '../../components/Dialogs/AlertDialog';
|
||||||
import { dateToString, getTimeOnlyString } from '../../utils/Planning';
|
import { dateToString, getTimeOnlyString } from '../../utils/Planning';
|
||||||
import DateManager from '../../managers/DateManager';
|
import DateManager from '../../managers/DateManager';
|
||||||
|
|
@ -31,17 +36,8 @@ import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||||
import { getPrettierPlanexGroupName } from '../../utils/Utils';
|
import { getPrettierPlanexGroupName } from '../../utils/Utils';
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
import PlanexWebview, {
|
import PlanexWebview from '../../components/Screens/PlanexWebview';
|
||||||
JS_LOADED_MESSAGE,
|
|
||||||
} from '../../components/Screens/PlanexWebview';
|
|
||||||
import PlanexBottomBar from '../../components/Animations/PlanexBottomBar';
|
import PlanexBottomBar from '../../components/Animations/PlanexBottomBar';
|
||||||
import {
|
|
||||||
getPreferenceString,
|
|
||||||
GeneralPreferenceKeys,
|
|
||||||
PlanexPreferenceKeys,
|
|
||||||
} from '../../utils/asyncStorage';
|
|
||||||
import { usePlanexPreferences } from '../../context/preferencesContext';
|
|
||||||
import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
|
@ -54,10 +50,17 @@ const styles = StyleSheet.create({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function PlanexScreen() {
|
type Props = {
|
||||||
|
route: {
|
||||||
|
params: {
|
||||||
|
group?: PlanexGroupType;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function PlanexScreen(props: Props) {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { preferences } = usePlanexPreferences();
|
|
||||||
|
|
||||||
const [dialogContent, setDialogContent] = useState<
|
const [dialogContent, setDialogContent] = useState<
|
||||||
| undefined
|
| undefined
|
||||||
|
|
@ -68,15 +71,13 @@ function PlanexScreen() {
|
||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
const [injectJS, setInjectJS] = useState('');
|
const [injectJS, setInjectJS] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
const getCurrentGroup: () => PlanexGroupType | undefined = useCallback(() => {
|
const getCurrentGroup = (): PlanexGroupType | undefined => {
|
||||||
let currentGroupString = getPreferenceString(
|
let currentGroupString = AsyncStorageManager.getString(
|
||||||
PlanexPreferenceKeys.planexCurrentGroup,
|
AsyncStorageManager.PREFERENCES.planexCurrentGroup.key
|
||||||
preferences
|
|
||||||
);
|
);
|
||||||
let group: PlanexGroupType;
|
let group: PlanexGroupType;
|
||||||
if (currentGroupString) {
|
if (currentGroupString !== '') {
|
||||||
group = JSON.parse(currentGroupString);
|
group = JSON.parse(currentGroupString);
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
title: getPrettierPlanexGroupName(group.name),
|
title: getPrettierPlanexGroupName(group.name),
|
||||||
|
|
@ -84,10 +85,22 @@ function PlanexScreen() {
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [navigation, preferences]);
|
};
|
||||||
|
|
||||||
const currentGroup = getCurrentGroup();
|
const [currentGroup, setCurrentGroup] = useState<PlanexGroupType | undefined>(
|
||||||
|
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.
|
* Gets the Webview, with an error view on top if no group is selected.
|
||||||
*
|
*
|
||||||
|
|
@ -136,26 +149,22 @@ function PlanexScreen() {
|
||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
const onMessage = (event: { nativeEvent: { data: string } }) => {
|
const onMessage = (event: { nativeEvent: { data: string } }) => {
|
||||||
if (event.nativeEvent.data === JS_LOADED_MESSAGE) {
|
const data: {
|
||||||
setLoading(false);
|
start: string;
|
||||||
} else {
|
end: string;
|
||||||
const data: {
|
title: string;
|
||||||
start: string;
|
color: string;
|
||||||
end: string;
|
} = JSON.parse(event.nativeEvent.data);
|
||||||
title: string;
|
const startDate = dateToString(new Date(data.start), true);
|
||||||
color: string;
|
const endDate = dateToString(new Date(data.end), true);
|
||||||
} = JSON.parse(event.nativeEvent.data);
|
const startString = getTimeOnlyString(startDate);
|
||||||
const startDate = dateToString(new Date(data.start), true);
|
const endString = getTimeOnlyString(endDate);
|
||||||
const endDate = dateToString(new Date(data.end), true);
|
|
||||||
const startString = getTimeOnlyString(startDate);
|
|
||||||
const endString = getTimeOnlyString(endDate);
|
|
||||||
|
|
||||||
let msg = `${DateManager.getInstance().getTranslatedDate(startDate)}\n`;
|
let msg = `${DateManager.getInstance().getTranslatedDate(startDate)}\n`;
|
||||||
if (startString != null && endString != null) {
|
if (startString != null && endString != null) {
|
||||||
msg += `${startString} - ${endString}`;
|
msg += `${startString} - ${endString}`;
|
||||||
}
|
|
||||||
showDialog(data.title, msg, data.color);
|
|
||||||
}
|
}
|
||||||
|
showDialog(data.title, msg, data.color);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -185,20 +194,21 @@ function PlanexScreen() {
|
||||||
|
|
||||||
const hideDialog = () => setDialogContent(undefined);
|
const hideDialog = () => setDialogContent(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
/**
|
||||||
const group = getCurrentGroup();
|
* Sends the webpage a message with the new group to select and save it to preferences
|
||||||
if (group) {
|
*
|
||||||
sendMessage('setGroup', group.id.toString());
|
* @param group The group object selected
|
||||||
navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
|
*/
|
||||||
}
|
const selectNewGroup = (group: PlanexGroupType) => {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
sendMessage('setGroup', group.id.toString());
|
||||||
}, [getCurrentGroup, navigation]);
|
setCurrentGroup(group);
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.planexCurrentGroup.key,
|
||||||
|
group
|
||||||
|
);
|
||||||
|
|
||||||
const showMascot =
|
navigation.setOptions({ title: getPrettierPlanexGroupName(group.name) });
|
||||||
getPreferenceString(
|
};
|
||||||
GeneralPreferenceKeys.defaultStartScreen,
|
|
||||||
preferences
|
|
||||||
)?.toLowerCase() !== 'planex';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={GENERAL_STYLES.flex}>
|
<View style={GENERAL_STYLES.flex}>
|
||||||
|
|
@ -210,13 +220,11 @@ function PlanexScreen() {
|
||||||
<View style={GENERAL_STYLES.flex}>{getWebView()}</View>
|
<View style={GENERAL_STYLES.flex}>{getWebView()}</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{loading ? (
|
{AsyncStorageManager.getString(
|
||||||
<View style={styles.container}>
|
AsyncStorageManager.PREFERENCES.defaultStartScreen.key
|
||||||
<BasicLoadingScreen />
|
).toLowerCase() !== 'planex' ? (
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
{showMascot ? (
|
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
|
prefKey={AsyncStorageManager.PREFERENCES.planexShowMascot.key}
|
||||||
title={i18n.t('screens.planex.mascotDialog.title')}
|
title={i18n.t('screens.planex.mascotDialog.title')}
|
||||||
message={i18n.t('screens.planex.mascotDialog.message')}
|
message={i18n.t('screens.planex.mascotDialog.message')}
|
||||||
icon="emoticon-kiss"
|
icon="emoticon-kiss"
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import {
|
||||||
import CustomAgenda from '../../components/Overrides/CustomAgenda';
|
import CustomAgenda from '../../components/Overrides/CustomAgenda';
|
||||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
import Urls from '../../constants/Urls';
|
import Urls from '../../constants/Urls';
|
||||||
|
|
||||||
|
|
@ -290,6 +291,7 @@ class PlanningScreen extends React.Component<PropsType, StateType> {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
|
prefKey={AsyncStorageManager.PREFERENCES.eventsShowMascot.key}
|
||||||
title={i18n.t('screens.planning.mascotDialog.title')}
|
title={i18n.t('screens.planning.mascotDialog.title')}
|
||||||
message={i18n.t('screens.planning.mascotDialog.message')}
|
message={i18n.t('screens.planning.mascotDialog.message')}
|
||||||
icon="party-popper"
|
icon="party-popper"
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useLayoutEffect, useRef, useState } from 'react';
|
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
SectionListData,
|
SectionListData,
|
||||||
SectionListRenderItemInfo,
|
SectionListRenderItemInfo,
|
||||||
|
|
@ -28,6 +28,7 @@ import i18n from 'i18n-js';
|
||||||
import { Avatar, Button, Card, Text, useTheme } from 'react-native-paper';
|
import { Avatar, Button, Card, Text, useTheme } from 'react-native-paper';
|
||||||
import { Modalize } from 'react-native-modalize';
|
import { Modalize } from 'react-native-modalize';
|
||||||
import WebSectionList from '../../components/Screens/WebSectionList';
|
import WebSectionList from '../../components/Screens/WebSectionList';
|
||||||
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import ProxiwashListItem from '../../components/Lists/Proxiwash/ProxiwashListItem';
|
import ProxiwashListItem from '../../components/Lists/Proxiwash/ProxiwashListItem';
|
||||||
import ProxiwashConstants, {
|
import ProxiwashConstants, {
|
||||||
MachineStates,
|
MachineStates,
|
||||||
|
|
@ -49,17 +50,9 @@ import type { SectionListDataType } from '../../components/Screens/WebSectionLis
|
||||||
import type { LaundromatType } from './ProxiwashAboutScreen';
|
import type { LaundromatType } from './ProxiwashAboutScreen';
|
||||||
import GENERAL_STYLES from '../../constants/Styles';
|
import GENERAL_STYLES from '../../constants/Styles';
|
||||||
import { readData } from '../../utils/WebData';
|
import { readData } from '../../utils/WebData';
|
||||||
import { useNavigation } from '@react-navigation/core';
|
import { useFocusEffect, useNavigation } from '@react-navigation/core';
|
||||||
import { setupMachineNotification } from '../../utils/Notifications';
|
import { setupMachineNotification } from '../../utils/Notifications';
|
||||||
import ProximoListHeader from '../../components/Lists/Proximo/ProximoListHeader';
|
import ProximoListHeader from '../../components/Lists/Proximo/ProximoListHeader';
|
||||||
import {
|
|
||||||
getPreferenceNumber,
|
|
||||||
getPreferenceObject,
|
|
||||||
getPreferenceString,
|
|
||||||
ProxiwashPreferenceKeys,
|
|
||||||
} from '../../utils/asyncStorage';
|
|
||||||
import { useProxiwashPreferences } from '../../context/preferencesContext';
|
|
||||||
import { useSubsequentEffect } from '../../utils/customHooks';
|
|
||||||
|
|
||||||
const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds
|
const REFRESH_TIME = 1000 * 10; // Refresh every 10 seconds
|
||||||
const LIST_ITEM_HEIGHT = 64;
|
const LIST_ITEM_HEIGHT = 64;
|
||||||
|
|
@ -98,44 +91,23 @@ const styles = StyleSheet.create({
|
||||||
function ProxiwashScreen() {
|
function ProxiwashScreen() {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { preferences, updatePreferences } = useProxiwashPreferences();
|
|
||||||
const [
|
const [
|
||||||
modalCurrentDisplayItem,
|
modalCurrentDisplayItem,
|
||||||
setModalCurrentDisplayItem,
|
setModalCurrentDisplayItem,
|
||||||
] = useState<React.ReactElement | null>(null);
|
] = useState<React.ReactElement | null>(null);
|
||||||
const reminder = getPreferenceNumber(
|
const [machinesWatched, setMachinesWatched] = useState<
|
||||||
ProxiwashPreferenceKeys.proxiwashNotifications,
|
Array<ProxiwashMachineType>
|
||||||
preferences
|
>(
|
||||||
|
AsyncStorageManager.getObject(
|
||||||
|
AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const [refresh, setRefresh] = useState(false);
|
|
||||||
|
|
||||||
const getMachinesWatched = () => {
|
const [selectedWash, setSelectedWash] = useState(
|
||||||
const data = getPreferenceObject(
|
AsyncStorageManager.getString(
|
||||||
ProxiwashPreferenceKeys.proxiwashWatchedMachines,
|
AsyncStorageManager.PREFERENCES.selectedWash.key
|
||||||
preferences
|
) as 'tripodeB' | 'washinsa'
|
||||||
) as Array<ProxiwashMachineType>;
|
);
|
||||||
return data ? (data as Array<ProxiwashMachineType>) : [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSelectedWash = () => {
|
|
||||||
const data = getPreferenceString(
|
|
||||||
ProxiwashPreferenceKeys.selectedWash,
|
|
||||||
preferences
|
|
||||||
);
|
|
||||||
if (data !== 'washinsa' && data !== 'tripodeB') {
|
|
||||||
return 'washinsa';
|
|
||||||
} else {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const machinesWatched: Array<ProxiwashMachineType> = getMachinesWatched();
|
|
||||||
const selectedWash: 'washinsa' | 'tripodeB' = getSelectedWash();
|
|
||||||
|
|
||||||
useSubsequentEffect(() => {
|
|
||||||
// Refresh the list when the selected wash changes
|
|
||||||
setRefresh(true);
|
|
||||||
}, [selectedWash]);
|
|
||||||
|
|
||||||
const modalStateStrings: { [key in MachineStates]: string } = {
|
const modalStateStrings: { [key in MachineStates]: string } = {
|
||||||
[MachineStates.AVAILABLE]: i18n.t('screens.proxiwash.modal.ready'),
|
[MachineStates.AVAILABLE]: i18n.t('screens.proxiwash.modal.ready'),
|
||||||
|
|
@ -165,6 +137,17 @@ function ProxiwashScreen() {
|
||||||
});
|
});
|
||||||
}, [navigation]);
|
}, [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
|
* Callback used when the user clicks on enable notifications for a machine
|
||||||
*
|
*
|
||||||
|
|
@ -310,7 +293,6 @@ function ProxiwashScreen() {
|
||||||
setupMachineNotification(
|
setupMachineNotification(
|
||||||
machine.number,
|
machine.number,
|
||||||
true,
|
true,
|
||||||
reminder,
|
|
||||||
getMachineEndDate(machine)
|
getMachineEndDate(machine)
|
||||||
);
|
);
|
||||||
saveNotificationToState(machine);
|
saveNotificationToState(machine);
|
||||||
|
|
@ -359,11 +341,8 @@ function ProxiwashScreen() {
|
||||||
...data.dryers,
|
...data.dryers,
|
||||||
...data.washers,
|
...data.washers,
|
||||||
]);
|
]);
|
||||||
if (cleanedList.length !== machinesWatched.length) {
|
if (cleanedList !== machinesWatched) {
|
||||||
updatePreferences(
|
setMachinesWatched(machinesWatched);
|
||||||
ProxiwashPreferenceKeys.proxiwashWatchedMachines,
|
|
||||||
cleanedList
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -428,7 +407,11 @@ function ProxiwashScreen() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveNewWatchedList = (list: Array<ProxiwashMachineType>) => {
|
const saveNewWatchedList = (list: Array<ProxiwashMachineType>) => {
|
||||||
updatePreferences(ProxiwashPreferenceKeys.proxiwashWatchedMachines, list);
|
setMachinesWatched(list);
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key,
|
||||||
|
list
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderListHeaderComponent = (
|
const renderListHeaderComponent = (
|
||||||
|
|
@ -453,7 +436,6 @@ function ProxiwashScreen() {
|
||||||
default:
|
default:
|
||||||
data = ProxiwashConstants.washinsa;
|
data = ProxiwashConstants.washinsa;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={GENERAL_STYLES.flex}>
|
<View style={GENERAL_STYLES.flex}>
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
|
@ -466,11 +448,10 @@ function ProxiwashScreen() {
|
||||||
refreshOnFocus={true}
|
refreshOnFocus={true}
|
||||||
extraData={machinesWatched.length}
|
extraData={machinesWatched.length}
|
||||||
renderListHeaderComponent={renderListHeaderComponent}
|
renderListHeaderComponent={renderListHeaderComponent}
|
||||||
refresh={refresh}
|
|
||||||
onFinish={() => setRefresh(false)}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
|
prefKey={AsyncStorageManager.PREFERENCES.proxiwashShowMascot.key}
|
||||||
title={i18n.t('screens.proxiwash.mascotDialog.title')}
|
title={i18n.t('screens.proxiwash.mascotDialog.title')}
|
||||||
message={i18n.t('screens.proxiwash.mascotDialog.message')}
|
message={i18n.t('screens.proxiwash.mascotDialog.message')}
|
||||||
icon="information"
|
icon="information"
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,12 @@ import MaterialHeaderButtons, {
|
||||||
} from '../../components/Overrides/CustomHeaderButton';
|
} from '../../components/Overrides/CustomHeaderButton';
|
||||||
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
import { MASCOT_STYLE } from '../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../components/Mascot/MascotPopup';
|
||||||
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
import AsyncStorageManager from '../../managers/AsyncStorageManager';
|
||||||
import {
|
import ServicesManager, {
|
||||||
getCategories,
|
|
||||||
ServiceCategoryType,
|
|
||||||
SERVICES_CATEGORIES_KEY,
|
SERVICES_CATEGORIES_KEY,
|
||||||
} from '../../utils/Services';
|
} from '../../managers/ServicesManager';
|
||||||
|
import CollapsibleFlatList from '../../components/Collapsible/CollapsibleFlatList';
|
||||||
|
import type { ServiceCategoryType } from '../../managers/ServicesManager';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp<any>;
|
navigation: StackNavigationProp<any>;
|
||||||
|
|
@ -66,7 +66,8 @@ class ServicesScreen extends React.Component<PropsType> {
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.finalDataset = getCategories(props.navigation.navigate, [
|
const services = new ServicesManager(props.navigation);
|
||||||
|
this.finalDataset = services.getCategories([
|
||||||
SERVICES_CATEGORIES_KEY.SPECIAL,
|
SERVICES_CATEGORIES_KEY.SPECIAL,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +159,7 @@ class ServicesScreen extends React.Component<PropsType> {
|
||||||
hasTab
|
hasTab
|
||||||
/>
|
/>
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
|
prefKey={AsyncStorageManager.PREFERENCES.servicesShowMascot.key}
|
||||||
title={i18n.t('screens.services.mascotDialog.title')}
|
title={i18n.t('screens.services.mascotDialog.title')}
|
||||||
message={i18n.t('screens.services.mascotDialog.message')}
|
message={i18n.t('screens.services.mascotDialog.message')}
|
||||||
icon="cloud-question"
|
icon="cloud-question"
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import { Collapsible } from 'react-navigation-collapsible';
|
||||||
import { CommonActions } from '@react-navigation/native';
|
import { CommonActions } from '@react-navigation/native';
|
||||||
import { StackNavigationProp } from '@react-navigation/stack';
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
import CardList from '../../components/Lists/CardList/CardList';
|
import CardList from '../../components/Lists/CardList/CardList';
|
||||||
import { ServiceCategoryType } from '../../utils/Services';
|
import type { ServiceCategoryType } from '../../managers/ServicesManager';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp<any>;
|
navigation: StackNavigationProp<any>;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
|
import AsyncStorageManager from '../managers/AsyncStorageManager';
|
||||||
import PushNotificationIOS from '@react-native-community/push-notification-ios';
|
import PushNotificationIOS from '@react-native-community/push-notification-ios';
|
||||||
import PushNotification from 'react-native-push-notification';
|
import PushNotification from 'react-native-push-notification';
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
|
|
@ -78,8 +79,11 @@ PushNotification.configure({
|
||||||
* @param machineID The machine id to schedule notifications for. This is used as id and in the notification string.
|
* @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
|
* @param date The date to trigger the notification at
|
||||||
*/
|
*/
|
||||||
function createNotifications(machineID: string, date: Date, reminder?: number) {
|
function createNotifications(machineID: string, date: Date) {
|
||||||
if (reminder && !Number.isNaN(reminder) && reminder > 0) {
|
const reminder = AsyncStorageManager.getNumber(
|
||||||
|
AsyncStorageManager.PREFERENCES.proxiwashNotifications.key
|
||||||
|
);
|
||||||
|
if (!Number.isNaN(reminder) && reminder > 0) {
|
||||||
const id = reminderIdFactor * parseInt(machineID, 10);
|
const id = reminderIdFactor * parseInt(machineID, 10);
|
||||||
const reminderDate = new Date(date);
|
const reminderDate = new Date(date);
|
||||||
reminderDate.setMinutes(reminderDate.getMinutes() - reminder);
|
reminderDate.setMinutes(reminderDate.getMinutes() - reminder);
|
||||||
|
|
@ -118,11 +122,10 @@ function createNotifications(machineID: string, date: Date, reminder?: number) {
|
||||||
export function setupMachineNotification(
|
export function setupMachineNotification(
|
||||||
machineID: string,
|
machineID: string,
|
||||||
isEnabled: boolean,
|
isEnabled: boolean,
|
||||||
reminder?: number,
|
|
||||||
endDate?: Date | null
|
endDate?: Date | null
|
||||||
) {
|
) {
|
||||||
if (isEnabled && endDate) {
|
if (isEnabled && endDate) {
|
||||||
createNotifications(machineID, endDate, reminder);
|
createNotifications(machineID, endDate);
|
||||||
} else {
|
} else {
|
||||||
PushNotification.cancelLocalNotifications({ id: machineID });
|
PushNotification.cancelLocalNotifications({ id: machineID });
|
||||||
const reminderId = reminderIdFactor * parseInt(machineID, 10);
|
const reminderId = reminderIdFactor * parseInt(machineID, 10);
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,6 @@ export function getErrorMessage(
|
||||||
fullMessage.message = i18n.t('errors.tokenSave');
|
fullMessage.message = i18n.t('errors.tokenSave');
|
||||||
fullMessage.icon = 'alert-circle-outline';
|
fullMessage.icon = 'alert-circle-outline';
|
||||||
break;
|
break;
|
||||||
case REQUEST_STATUS.TOKEN_RETRIEVE:
|
|
||||||
fullMessage.message = i18n.t('errors.tokenRetrieve');
|
|
||||||
fullMessage.icon = 'alert-circle-outline';
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
fullMessage.message = i18n.t('errors.unknown');
|
fullMessage.message = i18n.t('errors.unknown');
|
||||||
fullMessage.icon = 'alert-circle-outline';
|
fullMessage.icon = 'alert-circle-outline';
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,6 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* 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
|
* Gets the given services list without items of the given ids
|
||||||
|
|
@ -30,296 +25,45 @@ import { TabRoutes } from '../navigation/TabNavigator';
|
||||||
* @returns {[]}
|
* @returns {[]}
|
||||||
*/
|
*/
|
||||||
export default function getStrippedServicesList<T extends { key: string }>(
|
export default function getStrippedServicesList<T extends { key: string }>(
|
||||||
sourceList: Array<T>,
|
idList: Array<string>,
|
||||||
idList?: Array<string>
|
sourceList: Array<T>
|
||||||
) {
|
) {
|
||||||
if (idList) {
|
const newArray: Array<T> = [];
|
||||||
return sourceList.filter((item) => !idList.includes(item.key));
|
sourceList.forEach((item: T) => {
|
||||||
} else {
|
if (!idList.includes(item.key)) {
|
||||||
return sourceList;
|
newArray.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 AMICALE_LOGO = require('../../assets/amicale.png');
|
const item = originalList[i];
|
||||||
|
if (idList.includes(item.key)) {
|
||||||
export const SERVICES_KEY = {
|
subList[idList.indexOf(item.key)] = item;
|
||||||
CLUBS: 'clubs',
|
itemsAdded += 1;
|
||||||
PROFILE: 'profile',
|
if (itemsAdded === idList.length) {
|
||||||
EQUIPMENT: 'equipment',
|
break;
|
||||||
AMICALE_WEBSITE: 'amicale_website',
|
}
|
||||||
VOTE: 'vote',
|
}
|
||||||
PROXIMO: 'proximo',
|
}
|
||||||
WIKETUD: 'wiketud',
|
return subList;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,23 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Platform, StatusBar } from 'react-native';
|
import { Platform, StatusBar } from 'react-native';
|
||||||
|
import ThemeManager from '../managers/ThemeManager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates status bar content color if on iOS only,
|
* Updates status bar content color if on iOS only,
|
||||||
* as the android status bar is always set to black.
|
* as the android status bar is always set to black.
|
||||||
*/
|
*/
|
||||||
export function setupStatusBar(theme?: ReactNativePaper.Theme) {
|
export function setupStatusBar() {
|
||||||
if (theme) {
|
if (ThemeManager.getNightMode()) {
|
||||||
if (theme.dark) {
|
StatusBar.setBarStyle('light-content', true);
|
||||||
StatusBar.setBarStyle('light-content', true);
|
} else {
|
||||||
} else {
|
StatusBar.setBarStyle('dark-content', true);
|
||||||
StatusBar.setBarStyle('dark-content', true);
|
}
|
||||||
}
|
if (Platform.OS === 'android') {
|
||||||
if (Platform.OS === 'android') {
|
StatusBar.setBackgroundColor(
|
||||||
StatusBar.setBackgroundColor(theme.colors.surface, true);
|
ThemeManager.getCurrentTheme().colors.surface,
|
||||||
}
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,14 @@
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { SERVICES_KEY } from './Services';
|
import { SERVICES_KEY } from '../managers/ServicesManager';
|
||||||
|
|
||||||
export enum GeneralPreferenceKeys {
|
export enum PreferenceKeys {
|
||||||
debugUnlocked = 'debugUnlocked',
|
debugUnlocked = 'debugUnlocked',
|
||||||
showIntro = 'showIntro',
|
showIntro = 'showIntro',
|
||||||
updateNumber = 'updateNumber',
|
updateNumber = 'updateNumber',
|
||||||
|
proxiwashNotifications = 'proxiwashNotifications',
|
||||||
nightModeFollowSystem = 'nightModeFollowSystem',
|
nightModeFollowSystem = 'nightModeFollowSystem',
|
||||||
nightMode = 'nightMode',
|
nightMode = 'nightMode',
|
||||||
defaultStartScreen = 'defaultStartScreen',
|
defaultStartScreen = 'defaultStartScreen',
|
||||||
showAprilFoolsStart = 'showAprilFoolsStart',
|
|
||||||
dashboardItems = 'dashboardItems',
|
|
||||||
gameScores = 'gameScores',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PlanexPreferenceKeys {
|
|
||||||
planexCurrentGroup = 'planexCurrentGroup',
|
|
||||||
planexFavoriteGroups = 'planexFavoriteGroups',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ProxiwashPreferenceKeys {
|
|
||||||
proxiwashNotifications = 'proxiwashNotifications',
|
|
||||||
proxiwashWatchedMachines = 'proxiwashWatchedMachines',
|
|
||||||
selectedWash = 'selectedWash',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum MascotPreferenceKeys {
|
|
||||||
servicesShowMascot = 'servicesShowMascot',
|
servicesShowMascot = 'servicesShowMascot',
|
||||||
proxiwashShowMascot = 'proxiwashShowMascot',
|
proxiwashShowMascot = 'proxiwashShowMascot',
|
||||||
homeShowMascot = 'homeShowMascot',
|
homeShowMascot = 'homeShowMascot',
|
||||||
|
|
@ -33,110 +17,68 @@ export enum MascotPreferenceKeys {
|
||||||
loginShowMascot = 'loginShowMascot',
|
loginShowMascot = 'loginShowMascot',
|
||||||
voteShowMascot = 'voteShowMascot',
|
voteShowMascot = 'voteShowMascot',
|
||||||
equipmentShowMascot = 'equipmentShowMascot',
|
equipmentShowMascot = 'equipmentShowMascot',
|
||||||
gameShowMascot = 'gameShowMascot',
|
gameStartMascot = 'gameStartMascot',
|
||||||
|
proxiwashWatchedMachines = 'proxiwashWatchedMachines',
|
||||||
|
showAprilFoolsStart = 'showAprilFoolsStart',
|
||||||
|
planexCurrentGroup = 'planexCurrentGroup',
|
||||||
|
planexFavoriteGroups = 'planexFavoriteGroups',
|
||||||
|
dashboardItems = 'dashboardItems',
|
||||||
|
gameScores = 'gameScores',
|
||||||
|
selectedWash = 'selectedWash',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PreferenceKeys = {
|
|
||||||
...GeneralPreferenceKeys,
|
|
||||||
...PlanexPreferenceKeys,
|
|
||||||
...ProxiwashPreferenceKeys,
|
|
||||||
...MascotPreferenceKeys,
|
|
||||||
};
|
|
||||||
export type PreferenceKeys =
|
|
||||||
| GeneralPreferenceKeys
|
|
||||||
| PlanexPreferenceKeys
|
|
||||||
| ProxiwashPreferenceKeys
|
|
||||||
| MascotPreferenceKeys;
|
|
||||||
|
|
||||||
export type PreferencesType = { [key in PreferenceKeys]: string };
|
export type PreferencesType = { [key in PreferenceKeys]: string };
|
||||||
export type GeneralPreferencesType = { [key in GeneralPreferenceKeys]: string };
|
|
||||||
export type PlanexPreferencesType = {
|
|
||||||
[key in PlanexPreferenceKeys]: string;
|
|
||||||
};
|
|
||||||
export type ProxiwashPreferencesType = {
|
|
||||||
[key in ProxiwashPreferenceKeys]: string;
|
|
||||||
};
|
|
||||||
export type MascotPreferencesType = { [key in MascotPreferenceKeys]: string };
|
|
||||||
|
|
||||||
export const defaultPlanexPreferences: {
|
export const defaultPreferences: { [key in PreferenceKeys]: string } = {
|
||||||
[key in PlanexPreferenceKeys]: string;
|
[PreferenceKeys.debugUnlocked]: '0',
|
||||||
} = {
|
[PreferenceKeys.showIntro]: '1',
|
||||||
[PlanexPreferenceKeys.planexCurrentGroup]: '',
|
[PreferenceKeys.updateNumber]: '0',
|
||||||
[PlanexPreferenceKeys.planexFavoriteGroups]: '[]',
|
[PreferenceKeys.proxiwashNotifications]: '5',
|
||||||
};
|
[PreferenceKeys.nightModeFollowSystem]: '1',
|
||||||
|
[PreferenceKeys.nightMode]: '1',
|
||||||
export const defaultProxiwashPreferences: {
|
[PreferenceKeys.defaultStartScreen]: 'home',
|
||||||
[key in ProxiwashPreferenceKeys]: string;
|
[PreferenceKeys.servicesShowMascot]: '1',
|
||||||
} = {
|
[PreferenceKeys.proxiwashShowMascot]: '1',
|
||||||
[ProxiwashPreferenceKeys.proxiwashNotifications]: '5',
|
[PreferenceKeys.homeShowMascot]: '1',
|
||||||
[ProxiwashPreferenceKeys.proxiwashWatchedMachines]: '[]',
|
[PreferenceKeys.eventsShowMascot]: '1',
|
||||||
[ProxiwashPreferenceKeys.selectedWash]: 'washinsa',
|
[PreferenceKeys.planexShowMascot]: '1',
|
||||||
};
|
[PreferenceKeys.loginShowMascot]: '1',
|
||||||
|
[PreferenceKeys.voteShowMascot]: '1',
|
||||||
export const defaultMascotPreferences: {
|
[PreferenceKeys.equipmentShowMascot]: '1',
|
||||||
[key in MascotPreferenceKeys]: string;
|
[PreferenceKeys.gameStartMascot]: '1',
|
||||||
} = {
|
[PreferenceKeys.proxiwashWatchedMachines]: '[]',
|
||||||
[MascotPreferenceKeys.servicesShowMascot]: '1',
|
[PreferenceKeys.showAprilFoolsStart]: '1',
|
||||||
[MascotPreferenceKeys.proxiwashShowMascot]: '1',
|
[PreferenceKeys.planexCurrentGroup]: '',
|
||||||
[MascotPreferenceKeys.homeShowMascot]: '1',
|
[PreferenceKeys.planexFavoriteGroups]: '[]',
|
||||||
[MascotPreferenceKeys.eventsShowMascot]: '1',
|
[PreferenceKeys.dashboardItems]: JSON.stringify([
|
||||||
[MascotPreferenceKeys.planexShowMascot]: '1',
|
|
||||||
[MascotPreferenceKeys.loginShowMascot]: '1',
|
|
||||||
[MascotPreferenceKeys.voteShowMascot]: '1',
|
|
||||||
[MascotPreferenceKeys.equipmentShowMascot]: '1',
|
|
||||||
[MascotPreferenceKeys.gameShowMascot]: '1',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultPreferences: { [key in GeneralPreferenceKeys]: string } = {
|
|
||||||
[GeneralPreferenceKeys.debugUnlocked]: '0',
|
|
||||||
[GeneralPreferenceKeys.showIntro]: '1',
|
|
||||||
[GeneralPreferenceKeys.updateNumber]: '0',
|
|
||||||
[GeneralPreferenceKeys.nightModeFollowSystem]: '1',
|
|
||||||
[GeneralPreferenceKeys.nightMode]: '1',
|
|
||||||
[GeneralPreferenceKeys.defaultStartScreen]: 'home',
|
|
||||||
[GeneralPreferenceKeys.showAprilFoolsStart]: '1',
|
|
||||||
|
|
||||||
[GeneralPreferenceKeys.dashboardItems]: JSON.stringify([
|
|
||||||
SERVICES_KEY.EMAIL,
|
SERVICES_KEY.EMAIL,
|
||||||
SERVICES_KEY.WASHERS,
|
SERVICES_KEY.WASHERS,
|
||||||
SERVICES_KEY.PROXIMO,
|
SERVICES_KEY.PROXIMO,
|
||||||
SERVICES_KEY.TUTOR_INSA,
|
SERVICES_KEY.TUTOR_INSA,
|
||||||
SERVICES_KEY.RU,
|
SERVICES_KEY.RU,
|
||||||
]),
|
]),
|
||||||
|
[PreferenceKeys.gameScores]: '[]',
|
||||||
[GeneralPreferenceKeys.gameScores]: '[]',
|
[PreferenceKeys.selectedWash]: 'washinsa',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isValidGeneralPreferenceKey(
|
|
||||||
key: string
|
|
||||||
): key is GeneralPreferenceKeys {
|
|
||||||
return key in Object.values(GeneralPreferenceKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isValidMascotPreferenceKey(
|
|
||||||
key: string
|
|
||||||
): key is MascotPreferenceKeys {
|
|
||||||
return key in Object.values(MascotPreferenceKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set preferences object current values from AsyncStorage.
|
* Set preferences object current values from AsyncStorage.
|
||||||
* This function should be called once on start.
|
* This function should be called once on start.
|
||||||
*
|
*
|
||||||
* @return {Promise<PreferencesType>}
|
* @return {Promise<PreferencesType>}
|
||||||
*/
|
*/
|
||||||
export function retrievePreferences<
|
export function retrievePreferences(
|
||||||
Keys extends PreferenceKeys,
|
keys: Array<PreferenceKeys>,
|
||||||
T extends Partial<PreferencesType>
|
defaults: PreferencesType
|
||||||
>(keys: Array<Keys>, defaults: T): Promise<T> {
|
): Promise<PreferencesType> {
|
||||||
return new Promise((resolve: (preferences: T) => void) => {
|
return new Promise((resolve: (preferences: PreferencesType) => void) => {
|
||||||
AsyncStorage.multiGet(keys)
|
AsyncStorage.multiGet(Object.values(keys))
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const preferences = { ...defaults };
|
const preferences = { ...defaults };
|
||||||
result.forEach((item) => {
|
result.forEach((item) => {
|
||||||
let [key, value] = item;
|
let [key, value] = item;
|
||||||
if (value !== null) {
|
if (value !== null) {
|
||||||
preferences[key as Keys] = value;
|
preferences[key as PreferenceKeys] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
resolve(preferences);
|
resolve(preferences);
|
||||||
|
|
@ -152,14 +94,11 @@ export function retrievePreferences<
|
||||||
* @param key
|
* @param key
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
export function setPreference<
|
export function setPreference(
|
||||||
Keys extends PreferenceKeys,
|
key: PreferenceKeys,
|
||||||
T extends Partial<PreferencesType>
|
value: number | string | boolean | object | Array<any>,
|
||||||
>(
|
prevPreferences: PreferencesType
|
||||||
key: Keys,
|
): PreferencesType {
|
||||||
value: number | string | boolean | object | Array<any> | undefined,
|
|
||||||
prevPreferences: T
|
|
||||||
): T {
|
|
||||||
let convertedValue: string;
|
let convertedValue: string;
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
convertedValue = value;
|
convertedValue = value;
|
||||||
|
|
@ -181,10 +120,10 @@ export function setPreference<
|
||||||
* @param key
|
* @param key
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function getPreferenceString<
|
export function getPreferenceString(
|
||||||
Keys extends PreferenceKeys,
|
key: PreferenceKeys,
|
||||||
T extends Partial<PreferencesType>
|
preferences: PreferencesType
|
||||||
>(key: Keys, preferences: T): string | undefined {
|
): string | undefined {
|
||||||
return preferences[key];
|
return preferences[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,10 +133,10 @@ export function getPreferenceString<
|
||||||
* @param key
|
* @param key
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function getPreferenceBool<
|
export function getPreferenceBool(
|
||||||
Keys extends PreferenceKeys,
|
key: PreferenceKeys,
|
||||||
T extends Partial<PreferencesType>
|
preferences: PreferencesType
|
||||||
>(key: Keys, preferences: T): boolean | undefined {
|
): boolean | undefined {
|
||||||
const value = preferences[key];
|
const value = preferences[key];
|
||||||
return value ? value === '1' || value === 'true' : undefined;
|
return value ? value === '1' || value === 'true' : undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -208,12 +147,12 @@ export function getPreferenceBool<
|
||||||
* @param key
|
* @param key
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function getPreferenceNumber<
|
export function getPreferenceNumber(
|
||||||
Keys extends PreferenceKeys,
|
key: PreferenceKeys,
|
||||||
T extends Partial<PreferencesType>
|
preferences: PreferencesType
|
||||||
>(key: Keys, preferences: T): number | undefined {
|
): number | undefined {
|
||||||
const value = preferences[key] as string | undefined;
|
const value = preferences[key];
|
||||||
return value ? parseFloat(value) : undefined;
|
return value !== undefined ? parseFloat(value) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -222,10 +161,10 @@ export function getPreferenceNumber<
|
||||||
* @param key
|
* @param key
|
||||||
* @returns {{...}}
|
* @returns {{...}}
|
||||||
*/
|
*/
|
||||||
export function getPreferenceObject<
|
export function getPreferenceObject(
|
||||||
Keys extends PreferenceKeys,
|
key: PreferenceKeys,
|
||||||
T extends Partial<PreferencesType>
|
preferences: PreferencesType
|
||||||
>(key: Keys, preferences: T): object | Array<any> | undefined {
|
): object | Array<any> | undefined {
|
||||||
const value = preferences[key] as string | undefined;
|
const value = preferences[key];
|
||||||
return value ? JSON.parse(value) : undefined;
|
return value ? JSON.parse(value) : undefined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue