diff --git a/App.js b/App.js index 32b16f9..4c9703e 100644 --- a/App.js +++ b/App.js @@ -6,12 +6,13 @@ import LocaleManager from './src/managers/LocaleManager'; import AsyncStorageManager from "./src/managers/AsyncStorageManager"; import CustomIntroSlider from "./src/components/Overrides/CustomIntroSlider"; import {SplashScreen} from 'expo'; +import type {CustomTheme} from "./src/managers/ThemeManager"; import ThemeManager from './src/managers/ThemeManager'; import {NavigationContainer} from '@react-navigation/native'; import {createStackNavigator} from '@react-navigation/stack'; import DrawerNavigator from './src/navigation/DrawerNavigator'; import {initExpoToken} from "./src/utils/Notifications"; -import {Provider as PaperProvider, Theme} from 'react-native-paper'; +import {Provider as PaperProvider} from 'react-native-paper'; import AprilFoolsManager from "./src/managers/AprilFoolsManager"; import Update from "./src/constants/Update"; import ConnectionManager from "./src/managers/ConnectionManager"; @@ -30,7 +31,7 @@ type State = { showIntro: boolean, showUpdate: boolean, showAprilFools: boolean, - currentTheme: Theme | null, + currentTheme: CustomTheme | null, }; const Stack = createStackNavigator(); diff --git a/src/components/Amicale/Vote/VoteResults.js b/src/components/Amicale/Vote/VoteResults.js index 90fa68c..fbe57b5 100644 --- a/src/components/Amicale/Vote/VoteResults.js +++ b/src/components/Amicale/Vote/VoteResults.js @@ -1,16 +1,17 @@ // @flow import * as React from 'react'; -import {Avatar, Card, List, ProgressBar, Subheading, Theme, withTheme} from "react-native-paper"; +import {Avatar, Card, List, ProgressBar, Subheading, withTheme} from "react-native-paper"; import {FlatList, StyleSheet} from "react-native"; import i18n from 'i18n-js'; import type {team} from "../../../screens/Amicale/VoteScreen"; +import type {CustomTheme} from "../../../managers/ThemeManager"; type Props = { teams: Array, dateEnd: string, - theme: Theme, + theme: CustomTheme, } class VoteResults extends React.Component { @@ -38,9 +39,9 @@ class VoteResults extends React.Component { } } - getWinnerIds(teams: Array){ + getWinnerIds(teams: Array) { let max = teams[0].votes; - this.winnerIds= []; + this.winnerIds = []; for (let i = 0; i < teams.length; i++) { if (teams[i].votes === max) this.winnerIds.push(teams[i].id); @@ -51,7 +52,7 @@ class VoteResults extends React.Component { voteKeyExtractor = (item: team) => item.id.toString(); - resultRenderItem = ({item}: {item: team}) => { + resultRenderItem = ({item}: { item: team }) => { const isWinner = this.winnerIds.indexOf(item.id) !== -1; const isDraw = this.winnerIds.length > 1; const colors = this.props.theme.colors; @@ -90,7 +91,7 @@ class VoteResults extends React.Component { />} /> - {i18n.t('voteScreen.results.totalVotes') + ' ' +this.totalVotes} + {i18n.t('voteScreen.results.totalVotes') + ' ' + this.totalVotes} {/*$FlowFixMe*/} { diff --git a/src/components/Animations/AnimatedBottomBar.js b/src/components/Animations/AnimatedBottomBar.js index 72627d3..6a42487 100644 --- a/src/components/Animations/AnimatedBottomBar.js +++ b/src/components/Animations/AnimatedBottomBar.js @@ -2,17 +2,18 @@ import * as React from 'react'; import {StyleSheet, View} from "react-native"; -import {FAB, IconButton, Surface, Theme, withTheme} from "react-native-paper"; +import {FAB, IconButton, Surface, withTheme} from "react-native-paper"; import AutoHideHandler from "../../utils/AutoHideHandler"; import * as Animatable from 'react-native-animatable'; import CustomTabBar from "../Tabbar/CustomTabBar"; import {StackNavigationProp} from "@react-navigation/stack"; +import type {CustomTheme} from "../../managers/ThemeManager"; const AnimatedFAB = Animatable.createAnimatableComponent(FAB); type Props = { navigation: StackNavigationProp, - theme: Theme, + theme: CustomTheme, onPress: (action: string, data: any) => void, seekAttention: boolean, } diff --git a/src/components/Home/ActionsDashboardItem.js b/src/components/Home/ActionsDashboardItem.js index 8966488..6c7f466 100644 --- a/src/components/Home/ActionsDashboardItem.js +++ b/src/components/Home/ActionsDashboardItem.js @@ -4,10 +4,12 @@ import * as React from 'react'; import {Button, Card, withTheme} from 'react-native-paper'; import {Platform, StyleSheet} from "react-native"; import i18n from 'i18n-js'; +import {DrawerNavigationProp} from "@react-navigation/drawer"; +import type {CustomTheme} from "../../managers/ThemeManager"; type Props = { - navigation: Object, - theme: Object, + navigation: DrawerNavigationProp, + theme: CustomTheme, } class ActionsDashBoardItem extends React.Component { diff --git a/src/components/Home/EventDashboardItem.js b/src/components/Home/EventDashboardItem.js index e2b5bfd..e902e29 100644 --- a/src/components/Home/EventDashboardItem.js +++ b/src/components/Home/EventDashboardItem.js @@ -4,11 +4,13 @@ import * as React from 'react'; import {Avatar, Card, Text, withTheme} from 'react-native-paper'; import {StyleSheet} from "react-native"; import i18n from "i18n-js"; +import type {CustomTheme} from "../../managers/ThemeManager"; type Props = { eventNumber: number; - clickAction: Function, - theme: Object, + clickAction: () => void, + theme: CustomTheme, + children?: React.Node } /** diff --git a/src/components/Home/FeedItem.js b/src/components/Home/FeedItem.js index bd5f0a7..b69bf8c 100644 --- a/src/components/Home/FeedItem.js +++ b/src/components/Home/FeedItem.js @@ -1,15 +1,21 @@ +// @flow + import * as React from 'react'; import {Avatar, Button, Card, Text} from 'react-native-paper'; import {View} from "react-native"; import Autolink from "react-native-autolink"; import i18n from "i18n-js"; import ImageModal from 'react-native-image-modal'; +import {StackNavigationProp} from "@react-navigation/stack"; +import type {CustomTheme} from "../../managers/ThemeManager"; +import type {feedItem} from "../../screens/Home/HomeScreen"; const ICON_AMICALE = require('../../../assets/amicale.png'); type Props = { - navigation: Object, - theme: Object, + navigation: StackNavigationProp, + theme: CustomTheme, + item: feedItem, title: string, subtitle: string, height: number, @@ -32,17 +38,19 @@ class FeedItem extends React.Component { */ getAvatar() { return ( - + ); } onPress = () => { - this.props.navigation.navigate('feed-information', + this.props.navigation.navigate( + 'feed-information', { data: this.props.item, date: this.props.subtitle - }) + }); }; render() { diff --git a/src/components/Home/PreviewEventDashboardItem.js b/src/components/Home/PreviewEventDashboardItem.js index 9bc1e58..62c5227 100644 --- a/src/components/Home/PreviewEventDashboardItem.js +++ b/src/components/Home/PreviewEventDashboardItem.js @@ -6,10 +6,13 @@ import i18n from "i18n-js"; import {Avatar, Button, Card} from 'react-native-paper'; import {getFormattedEventTime, isDescriptionEmpty} from "../../utils/Planning"; import CustomHTML from "../Overrides/CustomHTML"; +import type {CustomTheme} from "../../managers/ThemeManager"; +import type {event} from "../../screens/Home/HomeScreen"; type Props = { - event: Object, - clickAction: Function, + event?: event, + clickAction: () => void, + theme?: CustomTheme, } /** @@ -19,14 +22,15 @@ class PreviewEventDashboardItem extends React.Component { render() { const props = this.props; - const isEmpty = props.event === undefined + const isEmpty = props.event == null ? true - : isDescriptionEmpty(props.event['description']); + : isDescriptionEmpty(props.event.description); - if (props.event !== undefined && props.event !== null) { - const hasImage = props.event['logo'] !== '' && props.event['logo'] !== null; + if (props.event != null) { + const event = props.event; + const hasImage = event.logo !== '' && event.logo != null; const getImage = () => ; return ( @@ -37,17 +41,17 @@ class PreviewEventDashboardItem extends React.Component { > {hasImage ? : } {!isEmpty ? - + : null} diff --git a/src/components/Home/SmallDashboardItem.js b/src/components/Home/SmallDashboardItem.js index 9da4928..d3c8bab 100644 --- a/src/components/Home/SmallDashboardItem.js +++ b/src/components/Home/SmallDashboardItem.js @@ -3,15 +3,16 @@ import * as React from 'react'; import {Badge, IconButton, withTheme} from 'react-native-paper'; import {View} from "react-native"; +import type {CustomTheme} from "../../managers/ThemeManager"; type Props = { color: string, icon: string, - clickAction: Function, + clickAction: () => void, isAvailable: boolean, badgeNumber: number, - theme: Object, + theme: CustomTheme, }; /** diff --git a/src/managers/ThemeManager.js b/src/managers/ThemeManager.js index af891c9..9c087c4 100644 --- a/src/managers/ThemeManager.js +++ b/src/managers/ThemeManager.js @@ -1,12 +1,61 @@ // @flow import AsyncStorageManager from "./AsyncStorageManager"; -import {DarkTheme, DefaultTheme} from 'react-native-paper'; +import {DarkTheme, DefaultTheme, Theme} from 'react-native-paper'; import AprilFoolsManager from "./AprilFoolsManager"; import {Appearance} from 'react-native-appearance'; const colorScheme = Appearance.getColorScheme(); +export type CustomTheme = { + ...Theme, + 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, + proxiwashRunningBgColor: string, + proxiwashBrokenColor: string, + proxiwashErrorColor: 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, + }, +} + /** * Singleton class used to manage themes */ @@ -22,9 +71,9 @@ export default class ThemeManager { /** * Gets the light theme * - * @return {Object} Object containing theme variables + * @return {CustomTheme} Object containing theme variables * */ - static getWhiteTheme(): Object { + static getWhiteTheme(): CustomTheme { return { ...DefaultTheme, colors: { @@ -41,6 +90,7 @@ export default class ThemeManager { success: "#5cb85c", warning: "#f0ad4e", danger: "#d9534f", + cc: 'dst', // Calendar/Agenda agendaBackgroundColor: '#f3f3f4', @@ -79,9 +129,9 @@ export default class ThemeManager { /** * Gets the dark theme * - * @return {Object} Object containing theme variables + * @return {CustomTheme} Object containing theme variables * */ - static getDarkTheme(): Object { + static getDarkTheme(): CustomTheme { return { ...DarkTheme, colors: { @@ -162,9 +212,9 @@ export default class ThemeManager { /** * Get the current theme based on night mode and events * - * @returns {Object} The current theme + * @returns {CustomTheme} The current theme */ - static getCurrentTheme(): Object { + static getCurrentTheme(): CustomTheme { if (AprilFoolsManager.getInstance().isAprilFoolsEnabled()) return AprilFoolsManager.getAprilFoolsTheme(ThemeManager.getWhiteTheme()); else @@ -174,9 +224,9 @@ export default class ThemeManager { /** * Get the theme based on night mode * - * @return {Object} The theme + * @return {CustomTheme} The theme */ - static getBaseTheme() { + static getBaseTheme(): CustomTheme { if (ThemeManager.getNightMode()) return ThemeManager.getDarkTheme(); else @@ -188,7 +238,7 @@ export default class ThemeManager { * * @param callback Function to call after theme change */ - setUpdateThemeCallback(callback: ?Function) { + setUpdateThemeCallback(callback: () => void) { this.updateThemeCallback = callback; } @@ -200,7 +250,7 @@ export default class ThemeManager { setNightMode(isNightMode: boolean) { let nightModeKey = AsyncStorageManager.getInstance().preferences.nightMode.key; AsyncStorageManager.getInstance().savePref(nightModeKey, isNightMode ? '1' : '0'); - if (this.updateThemeCallback !== null) + if (this.updateThemeCallback != null) this.updateThemeCallback(); } diff --git a/src/screens/Home/HomeScreen.js b/src/screens/Home/HomeScreen.js index ba9324d..b32f825 100644 --- a/src/screens/Home/HomeScreen.js +++ b/src/screens/Home/HomeScreen.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react'; -import {Animated, FlatList} from 'react-native'; +import {FlatList} from 'react-native'; import i18n from "i18n-js"; import DashboardItem from "../../components/Home/EventDashboardItem"; import WebSectionList from "../../components/Screens/WebSectionList"; @@ -14,9 +14,10 @@ import ActionsDashBoardItem from "../../components/Home/ActionsDashboardItem"; import ConnectionManager from "../../managers/ConnectionManager"; import {CommonActions} from '@react-navigation/native'; import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton"; -import {AnimatedValue} from "react-native-reanimated"; import AnimatedFAB from "../../components/Animations/AnimatedFAB"; import AnimatedFocusView from "../../components/Animations/AnimatedFocusView"; +import {StackNavigationProp} from "@react-navigation/stack"; +import type {CustomTheme} from "../../managers/ThemeManager"; // import DATA from "../dashboard_data.json"; @@ -31,30 +32,80 @@ const SECTIONS_ID = [ const REFRESH_TIME = 1000 * 20; // Refresh every 20 seconds -type Props = { - navigation: Object, - route: Object, - theme: Object, +type rawDashboard = { + news_feed: { + data: Array, + }, + dashboard: fullDashboard, } -type State = { - fabPosition: AnimatedValue +export type feedItem = { + full_picture: string, + message: string, + permalink_url: string, + created_time: number, + id: string, +}; + +type fullDashboard = { + today_menu: Array<{ [key: string]: any }>, + proximo_articles: number, + available_machines: { + dryers: number, + washers: number, + }, + today_events: Array<{ [key: string]: any }>, + available_tutorials: number, +} + +type dashboardItem = { + id: string, + content: Array<{ [key: string]: any }> +}; + +type dashboardSmallItem = { + id: string, + data: number, + icon: string, + color: string, + onPress: () => void, + isAvailable: boolean +}; + +export type event = { + id: number, + title: string, + logo: string | null, + date_begin: string, + date_end: string, + description: string, + club: string, + category_id: number, + url: string, +} + +type listSection = { + title: string, + data: Array | Array, + id: string +}; + +type Props = { + navigation: StackNavigationProp, + route: { params: any, ... }, + theme: CustomTheme, } /** * Class defining the app's home screen */ -class HomeScreen extends React.Component { +class HomeScreen extends React.Component { colors: Object; isLoggedIn: boolean | null; - fabRef: Object; - - state = { - fabPosition: new Animated.Value(0), - }; + fabRef: { current: null | AnimatedFAB }; constructor(props) { super(props); @@ -69,8 +120,8 @@ class HomeScreen extends React.Component { * @param dateString {string} The Unix Timestamp representation of a date * @return {string} The formatted output date */ - static getFormattedDate(dateString: string) { - let date = new Date(Number.parseInt(dateString) * 1000); + static getFormattedDate(dateString: number) { + let date = new Date(dateString * 1000); return date.toLocaleString(); } @@ -92,8 +143,8 @@ class HomeScreen extends React.Component { }; handleNavigationParams = () => { - if (this.props.route.params !== undefined) { - if (this.props.route.params.nextScreen !== undefined && this.props.route.params.nextScreen !== null) { + if (this.props.route.params != null) { + if (this.props.route.params.nextScreen != null) { this.props.navigation.navigate(this.props.route.params.nextScreen, this.props.route.params.data); // reset params to prevent infinite loop this.props.navigation.dispatch(CommonActions.setParams({nextScreen: null})); @@ -138,14 +189,14 @@ class HomeScreen extends React.Component { * @param fetchedData * @return {*} */ - createDataset = (fetchedData: Object) => { + createDataset = (fetchedData: rawDashboard) => { // fetchedData = DATA; let newsData = []; let dashboardData = []; - if (fetchedData['news_feed'] !== undefined) - newsData = fetchedData['news_feed']['data']; - if (fetchedData['dashboard'] !== undefined) - dashboardData = this.generateDashboardDataset(fetchedData['dashboard']); + if (fetchedData.news_feed != null) + newsData = fetchedData.news_feed.data; + if (fetchedData.dashboard != null) + dashboardData = this.generateDashboardDataset(fetchedData.dashboard); return [ { title: '', @@ -164,79 +215,61 @@ class HomeScreen extends React.Component { * Generates the dataset associated to the dashboard to be displayed in the FlatList as a section * * @param dashboardData - * @return {*} + * @return {Array} */ - generateDashboardDataset(dashboardData: Object) { - let dataset = [ - + generateDashboardDataset(dashboardData: fullDashboard): Array { + return [ { id: 'top', - content: [] - }, - { - id: 'actions', - content: undefined - }, - { - id: 'event', - content: undefined - }, - ]; - for (let [key, value: number | Object | Array] of Object.entries(dashboardData)) { - switch (key) { - case 'available_machines': - dataset[0]['content'][0] = { + content: [ + { id: 'washers', - data: value.washers, + data: dashboardData.available_machines.washers, icon: 'washing-machine', color: this.colors.proxiwashColor, onPress: this.onProxiwashClick, - isAvailable: value.washers > 0 - }; - dataset[0]['content'][1] = { - ...dataset[0]['content'][0], + isAvailable: dashboardData.available_machines.washers > 0 + }, + { id: 'dryers', - data: value.dryers, + data: dashboardData.available_machines.dryers, icon: 'tumble-dryer', - isAvailable: value.dryers > 0 - }; - break; - case 'available_tutorials': - dataset[0]['content'][2] = { - id: key, - data: value, + color: this.colors.proxiwashColor, + onPress: this.onProxiwashClick, + isAvailable: dashboardData.available_machines.dryers > 0 + }, + { + id: 'available_tutorials', + data: dashboardData.available_tutorials, icon: 'school', color: this.colors.tutorinsaColor, onPress: this.onTutorInsaClick, - isAvailable: parseInt(value) > 0 - }; - break; - case 'proximo_articles': - dataset[0]['content'][3] = { - id: key, - data: value, + isAvailable: dashboardData.available_tutorials > 0 + }, + { + id: 'proximo_articles', + data: dashboardData.proximo_articles, icon: 'shopping', color: this.colors.proximoColor, onPress: this.onProximoClick, - isAvailable: parseInt(value) > 0 - }; - break; - case 'today_menu': - dataset[0]['content'][4] = { - id: key, - data: 0, - icon: 'silverware-fork-knife', + isAvailable: dashboardData.proximo_articles > 0 + }, + { + id: 'silverware-fork-knife', + data: dashboardData.today_menu, + icon: 'shopping', color: this.colors.menuColor, onPress: this.onMenuClick, - isAvailable: value.length > 0 - }; - break; - case 'today_events': - dataset[2]['content'] = value; - break; - } - } - return dataset + isAvailable: dashboardData.today_menu.length > 0 + }, + ] + }, + {id: 'actions', content: []}, + { + id: 'event', + content: dashboardData.today_events + }, + ]; } /** @@ -245,11 +278,11 @@ class HomeScreen extends React.Component { * @param item The item to display * @return {*} */ - getDashboardItem(item: Object) { - let content = item['content']; - if (item['id'] === 'event') + getDashboardItem(item: dashboardItem) { + let content = item.content; + if (item.id === 'event') return this.getDashboardEvent(content); - else if (item['id'] === 'top') + else if (item.id === 'top') return this.getDashboardRow(content); else return this.getDashboardActions(); @@ -278,14 +311,14 @@ class HomeScreen extends React.Component { /** * Gets the duration (in milliseconds) of an event * - * @param event {Object} + * @param event {event} * @return {number} The number of milliseconds */ - getEventDuration(event: Object): number { - let start = stringToDate(event['date_begin']); - let end = stringToDate(event['date_end']); + getEventDuration(event: event): number { + let start = stringToDate(event.date_begin); + let end = stringToDate(event.date_end); let duration = 0; - if (start !== undefined && start !== null && end !== undefined && end !== null) + if (start != null && end != null) duration = end - start; return duration; } @@ -297,11 +330,11 @@ class HomeScreen extends React.Component { * @param limit * @return {Array} */ - getEventsAfterLimit(events: Object, limit: Date): Array { + getEventsAfterLimit(events: Array, limit: Date): Array { let validEvents = []; for (let event of events) { - let startDate = stringToDate(event['date_begin']); - if (startDate !== undefined && startDate !== null && startDate >= limit) { + let startDate = stringToDate(event.date_begin); + if (startDate != null && startDate >= limit) { validEvents.push(event); } } @@ -314,7 +347,7 @@ class HomeScreen extends React.Component { * * @param events */ - getLongestEvent(events: Array): Object { + getLongestEvent(events: Array): event { let longestEvent = events[0]; let longestTime = 0; for (let event of events) { @@ -332,16 +365,16 @@ class HomeScreen extends React.Component { * * @param events */ - getFutureEvents(events: Array): Array { + getFutureEvents(events: Array): Array { let validEvents = []; let now = new Date(); for (let event of events) { - let startDate = stringToDate(event['date_begin']); - let endDate = stringToDate(event['date_end']); - if (startDate !== undefined && startDate !== null) { + let startDate = stringToDate(event.date_begin); + let endDate = stringToDate(event.date_end); + if (startDate != null) { if (startDate > now) validEvents.push(event); - else if (endDate !== undefined && endDate !== null) { + else if (endDate != null) { if (endDate > now || endDate < startDate) // Display event if it ends the following day validEvents.push(event); } @@ -356,8 +389,8 @@ class HomeScreen extends React.Component { * @param events * @return {Object} */ - getDisplayEvent(events: Array): Object { - let displayEvent = undefined; + getDisplayEvent(events: Array): event | null { + let displayEvent = null; if (events.length > 1) { let eventsAfterLimit = this.getEventsAfterLimit(events, this.getTodayEventTimeLimit()); if (eventsAfterLimit.length > 0) { @@ -383,7 +416,7 @@ class HomeScreen extends React.Component { * @param content * @return {*} */ - getDashboardEvent(content: Array) { + getDashboardEvent(content: Array) { let futureEvents = this.getFutureEvents(content); let displayEvent = this.getDisplayEvent(futureEvents); const clickPreviewAction = () => @@ -394,14 +427,14 @@ class HomeScreen extends React.Component { clickAction={this.onEventContainerClick} > ); } - dashboardRowRenderItem = ({item}: Object) => { + dashboardRowRenderItem = ({item}: { item: dashboardSmallItem }) => { return ( { * @param content * @return {*} */ - getDashboardRow(content: Array) { - return ; + getDashboardRow(content: Array) { + return ( + //$FlowFixMe + ); } /** @@ -437,7 +472,7 @@ class HomeScreen extends React.Component { * @param item The feed item to display * @return {*} */ - getFeedItem(item: Object) { + getFeedItem(item: feedItem) { return ( { * @param section The current section * @return {*} */ - getRenderItem = ({item, section}: Object) => { - return (section['id'] === SECTIONS_ID[0] - ? this.getDashboardItem(item) - : this.getFeedItem(item)); + getRenderItem = ({item, section}: { + item: { [key: string]: any }, + section: listSection + }) => { + if (section.id === SECTIONS_ID[0]) { + const data: dashboardItem = item; + return this.getDashboardItem(data); + } else { + const data: feedItem = item; + return this.getFeedItem(data); + } }; openScanner = () => this.props.navigation.navigate("scanner"); - onScroll = (event: Object) => { - this.fabRef.current.onScroll(event); + onScroll = (event: SyntheticEvent) => { + if (this.fabRef.current != null) + this.fabRef.current.onScroll(event); }; render() { - const nav = this.props.navigation; return (