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