From 375fc8b97134fe6271f4f8ac1463c2035b72ac9a Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Mon, 21 Sep 2020 21:44:07 +0200 Subject: [PATCH] Update App.tsx and related files to use TypeScript --- App.js => App.tsx | 29 +- ...orageManager.js => AsyncStorageManager.ts} | 41 +-- src/managers/ThemeManager.js | 307 ------------------ src/managers/ThemeManager.ts | 298 +++++++++++++++++ 4 files changed, 334 insertions(+), 341 deletions(-) rename App.js => App.tsx (93%) rename src/managers/{AsyncStorageManager.js => AsyncStorageManager.ts} (86%) delete mode 100644 src/managers/ThemeManager.js create mode 100644 src/managers/ThemeManager.ts diff --git a/App.js b/App.tsx similarity index 93% rename from App.js rename to App.tsx index 467779b..8c32968 100644 --- a/App.js +++ b/App.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @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 { - navigatorRef: {current: null | NavigationContainer}; + navigatorRef: {current: null | NavigationContainerRef}; defaultHomeRoute: string | null; @@ -67,13 +65,13 @@ export default class App extends React.Component { 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 { // 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 { /** * Renders the app based on loading state */ - render(): React.Node { + render() { const {state} = this; if (state.isLoading) { return null; diff --git a/src/managers/AsyncStorageManager.js b/src/managers/AsyncStorageManager.ts similarity index 86% rename from src/managers/AsyncStorageManager.js rename to src/managers/AsyncStorageManager.ts index 5b5b5ca..483d0c8 100644 --- a/src/managers/AsyncStorageManager.js +++ b/src/managers/AsyncStorageManager.ts @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @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, + value: number | string | boolean | object | Array, ) { 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 { return JSON.parse(AsyncStorageManager.getString(key)); } @@ -212,7 +209,7 @@ export default class AsyncStorageManager { * @return {Promise} */ async loadPreferences() { - const prefKeys = []; + const prefKeys: Array = []; // 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, + value: number | string | boolean | object | Array, ) { 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]; } } diff --git a/src/managers/ThemeManager.js b/src/managers/ThemeManager.js deleted file mode 100644 index 0b6872d..0000000 --- a/src/managers/ThemeManager.js +++ /dev/null @@ -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 . - */ - -// @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(); - } -} diff --git a/src/managers/ThemeManager.ts b/src/managers/ThemeManager.ts new file mode 100644 index 0000000..11c2b31 --- /dev/null +++ b/src/managers/ThemeManager.ts @@ -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 . + */ + +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(); + } + } +}