Update App.tsx and related files to use TypeScript

This commit is contained in:
Arnaud Vergnet 2020-09-21 21:44:07 +02:00
parent 54486d1deb
commit 375fc8b971
4 changed files with 334 additions and 341 deletions

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @flow
import * as React from 'react';
import {LogBox, Platform, SafeAreaView, View} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
@ -28,7 +26,6 @@ import SplashScreen from 'react-native-splash-screen';
import {OverflowMenuProvider} from 'react-navigation-header-buttons';
import AsyncStorageManager from './src/managers/AsyncStorageManager';
import CustomIntroSlider from './src/components/Overrides/CustomIntroSlider';
import type {CustomThemeType} from './src/managers/ThemeManager';
import ThemeManager from './src/managers/ThemeManager';
import MainNavigator from './src/navigation/MainNavigator';
import AprilFoolsManager from './src/managers/AprilFoolsManager';
@ -38,6 +35,7 @@ import type {ParsedUrlDataType} from './src/utils/URLHandler';
import URLHandler from './src/utils/URLHandler';
import {setupStatusBar} from './src/utils/Utils';
import initLocales from './src/utils/Locales';
import {NavigationContainerRef} from '@react-navigation/core';
// Native optimizations https://reactnavigation.org/docs/react-native-screens
// Crashes app when navigating away from webview on android 9+
@ -50,15 +48,15 @@ LogBox.ignoreLogs([
]);
type StateType = {
isLoading: boolean,
showIntro: boolean,
showUpdate: boolean,
showAprilFools: boolean,
currentTheme: CustomThemeType | null,
isLoading: boolean;
showIntro: boolean;
showUpdate: boolean;
showAprilFools: boolean;
currentTheme: ReactNativePaper.Theme | undefined;
};
export default class App extends React.Component<null, StateType> {
navigatorRef: {current: null | NavigationContainer};
navigatorRef: {current: null | NavigationContainerRef};
defaultHomeRoute: string | null;
@ -67,13 +65,13 @@ export default class App extends React.Component<null, StateType> {
urlHandler: URLHandler;
constructor() {
super();
super(null);
this.state = {
isLoading: true,
showIntro: true,
showUpdate: true,
showAprilFools: false,
currentTheme: null,
currentTheme: undefined,
};
initLocales();
this.navigatorRef = React.createRef();
@ -155,8 +153,11 @@ export default class App extends React.Component<null, StateType> {
// Only show intro if this is the first time starting the app
ThemeManager.getInstance().setUpdateThemeCallback(this.onUpdateTheme);
// Status bar goes dark if set too fast on ios
if (Platform.OS === 'ios') setTimeout(setupStatusBar, 1000);
else setupStatusBar();
if (Platform.OS === 'ios') {
setTimeout(setupStatusBar, 1000);
} else {
setupStatusBar();
}
this.setState({
isLoading: false,
@ -192,7 +193,7 @@ export default class App extends React.Component<null, StateType> {
/**
* Renders the app based on loading state
*/
render(): React.Node {
render() {
const {state} = this;
if (state.isLoading) {
return null;

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @flow
import AsyncStorage from '@react-native-community/async-storage';
import {SERVICES_KEY} from './ServicesManager';
@ -31,7 +29,7 @@ import {SERVICES_KEY} from './ServicesManager';
export default class AsyncStorageManager {
static instance: AsyncStorageManager | null = null;
static PREFERENCES = {
static PREFERENCES: {[key: string]: {key: string; default: string}} = {
debugUnlocked: {
key: 'debugUnlocked',
default: '0',
@ -132,10 +130,10 @@ export default class AsyncStorageManager {
},
};
#currentPreferences: {[key: string]: string};
private currentPreferences: {[key: string]: string};
constructor() {
this.#currentPreferences = {};
this.currentPreferences = {};
}
/**
@ -143,8 +141,9 @@ export default class AsyncStorageManager {
* @returns {AsyncStorageManager}
*/
static getInstance(): AsyncStorageManager {
if (AsyncStorageManager.instance == null)
if (AsyncStorageManager.instance == null) {
AsyncStorageManager.instance = new AsyncStorageManager();
}
return AsyncStorageManager.instance;
}
@ -156,8 +155,7 @@ export default class AsyncStorageManager {
*/
static set(
key: string,
// eslint-disable-next-line flowtype/no-weak-types
value: number | string | boolean | {...} | Array<any>,
value: number | string | boolean | object | Array<any>,
) {
AsyncStorageManager.getInstance().setPreference(key, value);
}
@ -200,8 +198,7 @@ export default class AsyncStorageManager {
* @param key
* @returns {{...}}
*/
// eslint-disable-next-line flowtype/no-weak-types
static getObject(key: string): any {
static getObject(key: string): object | Array<any> {
return JSON.parse(AsyncStorageManager.getString(key));
}
@ -212,7 +209,7 @@ export default class AsyncStorageManager {
* @return {Promise<void>}
*/
async loadPreferences() {
const prefKeys = [];
const prefKeys: Array<string> = [];
// Get all available keys
Object.keys(AsyncStorageManager.PREFERENCES).forEach((key: string) => {
prefKeys.push(key);
@ -223,8 +220,10 @@ export default class AsyncStorageManager {
resultArray.forEach((item: [string, string | null]) => {
const key = item[0];
let val = item[1];
if (val === null) val = AsyncStorageManager.PREFERENCES[key].default;
this.#currentPreferences[key] = val;
if (val === null) {
val = AsyncStorageManager.PREFERENCES[key].default;
}
this.currentPreferences[key] = val;
});
}
@ -237,16 +236,18 @@ export default class AsyncStorageManager {
*/
setPreference(
key: string,
// eslint-disable-next-line flowtype/no-weak-types
value: number | string | boolean | {...} | Array<any>,
value: number | string | boolean | object | Array<any>,
) {
if (AsyncStorageManager.PREFERENCES[key] != null) {
let convertedValue;
if (typeof value === 'string') convertedValue = value;
else if (typeof value === 'boolean' || typeof value === 'number')
if (typeof value === 'string') {
convertedValue = value;
} else if (typeof value === 'boolean' || typeof value === 'number') {
convertedValue = value.toString();
else convertedValue = JSON.stringify(value);
this.#currentPreferences[key] = convertedValue;
} else {
convertedValue = JSON.stringify(value);
}
this.currentPreferences[key] = convertedValue;
AsyncStorage.setItem(key, convertedValue);
}
}
@ -259,6 +260,6 @@ export default class AsyncStorageManager {
* @returns {string|null}
*/
getPreference(key: string): string | null {
return this.#currentPreferences[key];
return this.currentPreferences[key];
}
}

View file

@ -1,307 +0,0 @@
/*
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
*
* This file is part of Campus INSAT.
*
* Campus INSAT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Campus INSAT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @flow
import {DarkTheme, DefaultTheme} from 'react-native-paper';
import {Appearance} from 'react-native-appearance';
import AsyncStorageManager from './AsyncStorageManager';
import AprilFoolsManager from './AprilFoolsManager';
const colorScheme = Appearance.getColorScheme();
export type CustomThemeType = {
...DefaultTheme,
colors: {
primary: string,
accent: string,
tabIcon: string,
card: string,
dividerBackground: string,
ripple: string,
textDisabled: string,
icon: string,
subtitle: string,
success: string,
warning: string,
danger: string,
// Calendar/Agenda
agendaBackgroundColor: string,
agendaDayTextColor: string,
// PROXIWASH
proxiwashFinishedColor: string,
proxiwashReadyColor: string,
proxiwashRunningColor: string,
proxiwashRunningNotStartedColor: string,
proxiwashRunningBgColor: string,
proxiwashBrokenColor: string,
proxiwashErrorColor: string,
proxiwashUnknownColor: string,
// Screens
planningColor: string,
proximoColor: string,
proxiwashColor: string,
menuColor: string,
tutorinsaColor: string,
// Tetris
tetrisBackground: string,
tetrisBorder: string,
tetrisScore: string,
tetrisI: string,
tetrisO: string,
tetrisT: string,
tetrisS: string,
tetrisZ: string,
tetrisJ: string,
tetrisL: string,
gameGold: string,
gameSilver: string,
gameBronze: string,
// Mascot Popup
mascotMessageArrow: string,
},
};
/**
* Singleton class used to manage themes
*/
export default class ThemeManager {
static instance: ThemeManager | null = null;
updateThemeCallback: null | (() => void);
constructor() {
this.updateThemeCallback = null;
}
/**
* Gets the light theme
*
* @return {CustomThemeType} Object containing theme variables
* */
static getWhiteTheme(): CustomThemeType {
return {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
primary: '#be1522',
accent: '#be1522',
tabIcon: '#929292',
card: '#fff',
dividerBackground: '#e2e2e2',
ripple: 'rgba(0,0,0,0.2)',
textDisabled: '#c1c1c1',
icon: '#5d5d5d',
subtitle: '#707070',
success: '#5cb85c',
warning: '#f0ad4e',
danger: '#d9534f',
cc: 'dst',
// Calendar/Agenda
agendaBackgroundColor: '#f3f3f4',
agendaDayTextColor: '#636363',
// PROXIWASH
proxiwashFinishedColor: '#a5dc9d',
proxiwashReadyColor: 'transparent',
proxiwashRunningColor: '#a0ceff',
proxiwashRunningNotStartedColor: '#c9e0ff',
proxiwashRunningBgColor: '#c7e3ff',
proxiwashBrokenColor: '#ffa8a2',
proxiwashErrorColor: '#ffa8a2',
proxiwashUnknownColor: '#b6b6b6',
// Screens
planningColor: '#d9b10a',
proximoColor: '#ec5904',
proxiwashColor: '#1fa5ee',
menuColor: '#e91314',
tutorinsaColor: '#f93943',
// Tetris
tetrisBackground: '#f0f0f0',
tetrisScore: '#e2bd33',
tetrisI: '#3cd9e6',
tetrisO: '#ffdd00',
tetrisT: '#a716e5',
tetrisS: '#09c528',
tetrisZ: '#ff0009',
tetrisJ: '#2a67e3',
tetrisL: '#da742d',
gameGold: '#ffd610',
gameSilver: '#7b7b7b',
gameBronze: '#a15218',
// Mascot Popup
mascotMessageArrow: '#dedede',
},
};
}
/**
* Gets the dark theme
*
* @return {CustomThemeType} Object containing theme variables
* */
static getDarkTheme(): CustomThemeType {
return {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: '#be1522',
accent: '#be1522',
tabBackground: '#181818',
tabIcon: '#6d6d6d',
card: 'rgb(18,18,18)',
dividerBackground: '#222222',
ripple: 'rgba(255,255,255,0.2)',
textDisabled: '#5b5b5b',
icon: '#b3b3b3',
subtitle: '#aaaaaa',
success: '#5cb85c',
warning: '#f0ad4e',
danger: '#d9534f',
// Calendar/Agenda
agendaBackgroundColor: '#171717',
agendaDayTextColor: '#6d6d6d',
// PROXIWASH
proxiwashFinishedColor: '#31682c',
proxiwashReadyColor: 'transparent',
proxiwashRunningColor: '#213c79',
proxiwashRunningNotStartedColor: '#1e263e',
proxiwashRunningBgColor: '#1a2033',
proxiwashBrokenColor: '#7e2e2f',
proxiwashErrorColor: '#7e2e2f',
proxiwashUnknownColor: '#535353',
// Screens
planningColor: '#d99e09',
proximoColor: '#ec5904',
proxiwashColor: '#1fa5ee',
menuColor: '#b81213',
tutorinsaColor: '#f93943',
// Tetris
tetrisBackground: '#181818',
tetrisScore: '#e2d707',
tetrisI: '#30b3be',
tetrisO: '#c1a700',
tetrisT: '#9114c7',
tetrisS: '#08a121',
tetrisZ: '#b50008',
tetrisJ: '#0f37b9',
tetrisL: '#b96226',
gameGold: '#ffd610',
gameSilver: '#7b7b7b',
gameBronze: '#a15218',
// Mascot Popup
mascotMessageArrow: '#323232',
},
};
}
/**
* Get this class instance or create one if none is found
*
* @returns {ThemeManager}
*/
static getInstance(): ThemeManager {
if (ThemeManager.instance == null)
ThemeManager.instance = new ThemeManager();
return ThemeManager.instance;
}
/**
* Gets night mode status.
* If Follow System Preferences is enabled, will first use system theme.
* If disabled or not available, will use value stored din preferences
*
* @returns {boolean} Night mode state
*/
static getNightMode(): boolean {
return (
(AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.nightMode.key,
) &&
(!AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
) ||
colorScheme === 'no-preference')) ||
(AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
) &&
colorScheme === 'dark')
);
}
/**
* Get the current theme based on night mode and events
*
* @returns {CustomThemeType} The current theme
*/
static getCurrentTheme(): CustomThemeType {
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled())
return AprilFoolsManager.getAprilFoolsTheme(ThemeManager.getWhiteTheme());
return ThemeManager.getBaseTheme();
}
/**
* Get the theme based on night mode
*
* @return {CustomThemeType} The theme
*/
static getBaseTheme(): CustomThemeType {
if (ThemeManager.getNightMode()) return ThemeManager.getDarkTheme();
return ThemeManager.getWhiteTheme();
}
/**
* Sets the function to be called when the theme is changed (allows for general reload of the app)
*
* @param callback Function to call after theme change
*/
setUpdateThemeCallback(callback: () => void) {
this.updateThemeCallback = callback;
}
/**
* Set night mode and save it to preferences
*
* @param isNightMode True to enable night mode, false to disable
*/
setNightMode(isNightMode: boolean) {
AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.nightMode.key,
isNightMode,
);
if (this.updateThemeCallback != null) this.updateThemeCallback();
}
}

View file

@ -0,0 +1,298 @@
/*
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
*
* This file is part of Campus INSAT.
*
* Campus INSAT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Campus INSAT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
import {DarkTheme, DefaultTheme} from 'react-native-paper';
import {Appearance} from 'react-native-appearance';
import AsyncStorageManager from './AsyncStorageManager';
import AprilFoolsManager from './AprilFoolsManager';
const colorScheme = Appearance.getColorScheme();
declare global {
namespace ReactNativePaper {
interface ThemeColors {
primary: string;
accent: string;
border: string;
tabIcon: string;
card: string;
dividerBackground: string;
ripple: string;
textDisabled: string;
icon: string;
subtitle: string;
success: string;
warning: string;
danger: string;
// Calendar/Agenda
agendaBackgroundColor: string;
agendaDayTextColor: string;
// PROXIWASH
proxiwashFinishedColor: string;
proxiwashReadyColor: string;
proxiwashRunningColor: string;
proxiwashRunningNotStartedColor: string;
proxiwashRunningBgColor: string;
proxiwashBrokenColor: string;
proxiwashErrorColor: string;
proxiwashUnknownColor: string;
// Screens
planningColor: string;
proximoColor: string;
proxiwashColor: string;
menuColor: string;
tutorinsaColor: string;
// Tetris
tetrisBackground: string;
tetrisScore: string;
tetrisI: string;
tetrisO: string;
tetrisT: string;
tetrisS: string;
tetrisZ: string;
tetrisJ: string;
tetrisL: string;
gameGold: string;
gameSilver: string;
gameBronze: string;
// Mascot Popup
mascotMessageArrow: string;
}
}
}
const CustomWhiteTheme: ReactNativePaper.Theme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
primary: '#be1522',
accent: '#be1522',
border: '#e2e2e2',
tabIcon: '#929292',
card: '#fff',
dividerBackground: '#e2e2e2',
ripple: 'rgba(0,0,0,0.2)',
textDisabled: '#c1c1c1',
icon: '#5d5d5d',
subtitle: '#707070',
success: '#5cb85c',
warning: '#f0ad4e',
danger: '#d9534f',
// Calendar/Agenda
agendaBackgroundColor: '#f3f3f4',
agendaDayTextColor: '#636363',
// PROXIWASH
proxiwashFinishedColor: '#a5dc9d',
proxiwashReadyColor: 'transparent',
proxiwashRunningColor: '#a0ceff',
proxiwashRunningNotStartedColor: '#c9e0ff',
proxiwashRunningBgColor: '#c7e3ff',
proxiwashBrokenColor: '#ffa8a2',
proxiwashErrorColor: '#ffa8a2',
proxiwashUnknownColor: '#b6b6b6',
// Screens
planningColor: '#d9b10a',
proximoColor: '#ec5904',
proxiwashColor: '#1fa5ee',
menuColor: '#e91314',
tutorinsaColor: '#f93943',
// Tetris
tetrisBackground: '#f0f0f0',
tetrisScore: '#e2bd33',
tetrisI: '#3cd9e6',
tetrisO: '#ffdd00',
tetrisT: '#a716e5',
tetrisS: '#09c528',
tetrisZ: '#ff0009',
tetrisJ: '#2a67e3',
tetrisL: '#da742d',
gameGold: '#ffd610',
gameSilver: '#7b7b7b',
gameBronze: '#a15218',
// Mascot Popup
mascotMessageArrow: '#dedede',
},
};
const CustomDarkTheme: ReactNativePaper.Theme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: '#be1522',
accent: '#be1522',
border: '#222222',
tabIcon: '#6d6d6d',
card: 'rgb(18,18,18)',
dividerBackground: '#222222',
ripple: 'rgba(255,255,255,0.2)',
textDisabled: '#5b5b5b',
icon: '#b3b3b3',
subtitle: '#aaaaaa',
success: '#5cb85c',
warning: '#f0ad4e',
danger: '#d9534f',
// Calendar/Agenda
agendaBackgroundColor: '#171717',
agendaDayTextColor: '#6d6d6d',
// PROXIWASH
proxiwashFinishedColor: '#31682c',
proxiwashReadyColor: 'transparent',
proxiwashRunningColor: '#213c79',
proxiwashRunningNotStartedColor: '#1e263e',
proxiwashRunningBgColor: '#1a2033',
proxiwashBrokenColor: '#7e2e2f',
proxiwashErrorColor: '#7e2e2f',
proxiwashUnknownColor: '#535353',
// Screens
planningColor: '#d99e09',
proximoColor: '#ec5904',
proxiwashColor: '#1fa5ee',
menuColor: '#b81213',
tutorinsaColor: '#f93943',
// Tetris
tetrisBackground: '#181818',
tetrisScore: '#e2d707',
tetrisI: '#30b3be',
tetrisO: '#c1a700',
tetrisT: '#9114c7',
tetrisS: '#08a121',
tetrisZ: '#b50008',
tetrisJ: '#0f37b9',
tetrisL: '#b96226',
gameGold: '#ffd610',
gameSilver: '#7b7b7b',
gameBronze: '#a15218',
// Mascot Popup
mascotMessageArrow: '#323232',
},
};
/**
* Singleton class used to manage themes
*/
export default class ThemeManager {
static instance: ThemeManager | null = null;
updateThemeCallback: null | (() => void);
constructor() {
this.updateThemeCallback = null;
}
/**
* Get this class instance or create one if none is found
*
* @returns {ThemeManager}
*/
static getInstance(): ThemeManager {
if (ThemeManager.instance == null) {
ThemeManager.instance = new ThemeManager();
}
return ThemeManager.instance;
}
/**
* Gets night mode status.
* If Follow System Preferences is enabled, will first use system theme.
* If disabled or not available, will use value stored din preferences
*
* @returns {boolean} Night mode state
*/
static getNightMode(): boolean {
return (
(AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.nightMode.key,
) &&
(!AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
) ||
colorScheme === 'no-preference')) ||
(AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.nightModeFollowSystem.key,
) &&
colorScheme === 'dark')
);
}
/**
* Get the current theme based on night mode and events
*
* @returns {ReactNativePaper.Theme} The current theme
*/
static getCurrentTheme(): ReactNativePaper.Theme {
if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) {
return AprilFoolsManager.getAprilFoolsTheme(CustomWhiteTheme);
}
return ThemeManager.getBaseTheme();
}
/**
* Get the theme based on night mode
*
* @return {ReactNativePaper.Theme} The theme
*/
static getBaseTheme(): ReactNativePaper.Theme {
if (ThemeManager.getNightMode()) {
return CustomDarkTheme;
}
return CustomWhiteTheme;
}
/**
* Sets the function to be called when the theme is changed (allows for general reload of the app)
*
* @param callback Function to call after theme change
*/
setUpdateThemeCallback(callback: () => void) {
this.updateThemeCallback = callback;
}
/**
* Set night mode and save it to preferences
*
* @param isNightMode True to enable night mode, false to disable
*/
setNightMode(isNightMode: boolean) {
AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.nightMode.key,
isNightMode,
);
if (this.updateThemeCallback != null) {
this.updateThemeCallback();
}
}
}