Allow mascot popup to be controlled directly with a pref key

This commit is contained in:
Arnaud Vergnet 2020-07-23 14:52:56 +02:00
parent 6254ce1814
commit 5349e210cb
7 changed files with 92 additions and 148 deletions

View file

@ -7,9 +7,9 @@ import * as Animatable from "react-native-animatable";
import {BackHandler, Dimensions, ScrollView, TouchableWithoutFeedback, View} from "react-native"; import {BackHandler, Dimensions, ScrollView, TouchableWithoutFeedback, View} from "react-native";
import type {CustomTheme} from "../../managers/ThemeManager"; import type {CustomTheme} from "../../managers/ThemeManager";
import SpeechArrow from "./SpeechArrow"; import SpeechArrow from "./SpeechArrow";
import AsyncStorageManager from "../../managers/AsyncStorageManager";
type Props = { type Props = {
visible: boolean,
theme: CustomTheme, theme: CustomTheme,
icon: string, icon: string,
title: string, title: string,
@ -19,34 +19,34 @@ type Props = {
message: string, message: string,
icon: string | null, icon: string | null,
color: string | null, color: string | null,
onPress: () => void, onPress?: () => void,
}, },
cancel: { cancel: {
message: string, message: string,
icon: string | null, icon: string | null,
color: string | null, color: string | null,
onPress: () => void, onPress?: () => void,
} }
}, },
emotion: number, emotion: number,
visible?: boolean,
prefKey?: string,
} }
type State = { type State = {
shouldShowDialog: boolean; shouldRenderDialog: boolean, // Used to stop rendering after hide animation
dialogVisible: boolean,
} }
/**
* Component used to display a popup with the mascot.
*/
class MascotPopup extends React.Component<Props, State> { class MascotPopup extends React.Component<Props, State> {
mascotSize: number; mascotSize: number;
windowWidth: number; windowWidth: number;
windowHeight: number; windowHeight: number;
state = {
shouldShowDialog: this.props.visible,
};
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@ -54,18 +54,40 @@ class MascotPopup extends React.Component<Props, State> {
this.windowHeight = Dimensions.get('window').height; this.windowHeight = Dimensions.get('window').height;
this.mascotSize = Dimensions.get('window').height / 6; this.mascotSize = Dimensions.get('window').height / 6;
if (this.props.visible != null) {
this.state = {
shouldRenderDialog: this.props.visible,
dialogVisible: this.props.visible,
};
} else if (this.props.prefKey != null) {
const visible = AsyncStorageManager.getBool(this.props.prefKey);
this.state = {
shouldRenderDialog: visible,
dialogVisible: visible,
};
} else {
this.state = {
shouldRenderDialog: false,
dialogVisible: false,
};
}
} }
onAnimationEnd = () => { onAnimationEnd = () => {
this.setState({ this.setState({
shouldShowDialog: this.props.visible, shouldRenderDialog: false,
}) })
} }
shouldComponentUpdate(nextProps: Props): boolean { shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
if (nextProps.visible) { if (nextProps.visible) {
this.state.shouldShowDialog = true; this.state.shouldRenderDialog = true;
} else if (nextProps.visible !== this.props.visible) { this.state.dialogVisible = true;
} else if (nextProps.visible !== this.props.visible
|| (!nextState.dialogVisible && nextState.dialogVisible !== this.state.dialogVisible)) {
this.state.dialogVisible = false;
setTimeout(this.onAnimationEnd, 300); setTimeout(this.onAnimationEnd, 300);
} }
return true; return true;
@ -79,13 +101,13 @@ class MascotPopup extends React.Component<Props, State> {
} }
onBackButtonPressAndroid = () => { onBackButtonPressAndroid = () => {
if (this.state.shouldShowDialog) { if (this.state.dialogVisible) {
const cancel = this.props.buttons.cancel; const cancel = this.props.buttons.cancel;
const action = this.props.buttons.action; const action = this.props.buttons.action;
if (cancel != null) if (cancel != null)
cancel.onPress(); this.onDismiss(cancel.onPress);
else else
action.onPress(); this.onDismiss(action.onPress);
return true; return true;
} else { } else {
return false; return false;
@ -100,8 +122,8 @@ class MascotPopup extends React.Component<Props, State> {
marginRight: "10%", marginRight: "10%",
}} }}
useNativeDriver={true} useNativeDriver={true}
animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"} animation={this.state.dialogVisible ? "bounceInLeft" : "bounceOutLeft"}
duration={this.props.visible ? 1000 : 300} duration={this.state.dialogVisible ? 1000 : 300}
> >
<SpeechArrow <SpeechArrow
style={{marginLeft: this.mascotSize / 3}} style={{marginLeft: this.mascotSize / 3}}
@ -149,8 +171,8 @@ class MascotPopup extends React.Component<Props, State> {
return ( return (
<Animatable.View <Animatable.View
useNativeDriver={true} useNativeDriver={true}
animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"} animation={this.state.dialogVisible ? "bounceInLeft" : "bounceOutLeft"}
duration={this.props.visible ? 1500 : 200} duration={this.state.dialogVisible ? 1500 : 200}
> >
<Mascot <Mascot
style={{width: this.mascotSize}} style={{width: this.mascotSize}}
@ -181,7 +203,7 @@ class MascotPopup extends React.Component<Props, State> {
mode={"contained"} mode={"contained"}
icon={action.icon} icon={action.icon}
color={action.color} color={action.color}
onPress={action.onPress} onPress={() => this.onDismiss(action.onPress)}
> >
{action.message} {action.message}
</Button> </Button>
@ -195,7 +217,7 @@ class MascotPopup extends React.Component<Props, State> {
mode={"contained"} mode={"contained"}
icon={cancel.icon} icon={cancel.icon}
color={cancel.color} color={cancel.color}
onPress={cancel.onPress} onPress={() => this.onDismiss(cancel.onPress)}
> >
{cancel.message} {cancel.message}
</Button> </Button>
@ -206,7 +228,7 @@ class MascotPopup extends React.Component<Props, State> {
getBackground() { getBackground() {
return ( return (
<TouchableWithoutFeedback onPress={this.props.buttons.cancel.onPress}> <TouchableWithoutFeedback onPress={() => this.onDismiss(this.props.buttons.cancel.onPress)}>
<Animatable.View <Animatable.View
style={{ style={{
position: "absolute", position: "absolute",
@ -215,16 +237,25 @@ class MascotPopup extends React.Component<Props, State> {
height: "100%", height: "100%",
}} }}
useNativeDriver={true} useNativeDriver={true}
animation={this.props.visible ? "fadeIn" : "fadeOut"} animation={this.state.dialogVisible ? "fadeIn" : "fadeOut"}
duration={this.props.visible ? 300 : 300} duration={this.state.dialogVisible ? 300 : 300}
/> />
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
); );
} }
onDismiss = (callback?: ()=> void) => {
if (this.props.prefKey != null) {
AsyncStorageManager.set(this.props.prefKey, false);
this.setState({dialogVisible: false});
}
if (callback != null)
callback();
}
render() { render() {
if (this.state.shouldShowDialog) { if (this.state.shouldRenderDialog) {
return ( return (
<Portal> <Portal>
{this.getBackground()} {this.getBackground()}
@ -242,8 +273,7 @@ class MascotPopup extends React.Component<Props, State> {
</View> </View>
</Portal> </Portal>
) );
;
} else } else
return null; return null;

View file

@ -33,11 +33,7 @@ type Props = {
theme: CustomTheme, theme: CustomTheme,
} }
type State = { class GameStartScreen extends React.Component<Props> {
mascotDialogVisible: boolean,
}
class GameStartScreen extends React.Component<Props, State> {
gridManager: GridManager; gridManager: GridManager;
scores: Array<number>; scores: Array<number>;
@ -45,10 +41,6 @@ class GameStartScreen extends React.Component<Props, State> {
gameStats: GameStats | null; gameStats: GameStats | null;
isHighScore: boolean; isHighScore: boolean;
state = {
mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.gameStartShowBanner.key),
}
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.gridManager = new GridManager(4, 4, props.theme); this.gridManager = new GridManager(4, 4, props.theme);
@ -75,11 +67,6 @@ class GameStartScreen extends React.Component<Props, State> {
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.gameScores.key, this.scores); AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.gameScores.key, this.scores);
} }
hideMascotDialog = () => {
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.gameStartShowBanner.key, false);
this.setState({mascotDialogVisible: false})
};
getPiecesBackground() { getPiecesBackground() {
let gridList = []; let gridList = [];
for (let i = 0; i < 18; i++) { for (let i = 0; i < 18; i++) {
@ -415,7 +402,7 @@ class GameStartScreen extends React.Component<Props, State> {
<CollapsibleScrollView> <CollapsibleScrollView>
{this.getMainContent()} {this.getMainContent()}
<MascotPopup <MascotPopup
visible={this.state.mascotDialogVisible} prefKey={AsyncStorageManager.PREFERENCES.gameStartShowBanner.key}
title={i18n.t("screens.game.mascotDialog.title")} title={i18n.t("screens.game.mascotDialog.title")}
message={i18n.t("screens.game.mascotDialog.message")} message={i18n.t("screens.game.mascotDialog.message")}
icon={"gamepad-variant"} icon={"gamepad-variant"}
@ -424,7 +411,6 @@ class GameStartScreen extends React.Component<Props, State> {
cancel: { cancel: {
message: i18n.t("screens.game.mascotDialog.button"), message: i18n.t("screens.game.mascotDialog.button"),
icon: "check", icon: "check",
onPress: this.hideMascotDialog,
} }
}} }}
emotion={MASCOT_STYLE.COOL} emotion={MASCOT_STYLE.COOL}

View file

@ -84,7 +84,6 @@ type Props = {
type State = { type State = {
dialogVisible: boolean, dialogVisible: boolean,
mascotDialogVisible: boolean,
} }
/** /**
@ -112,9 +111,6 @@ class HomeScreen extends React.Component<Props, State> {
}); });
this.state = { this.state = {
dialogVisible: false, dialogVisible: false,
mascotDialogVisible: AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.homeShowBanner.key)
&& !this.isLoggedIn,
} }
} }
@ -184,11 +180,6 @@ class HomeScreen extends React.Component<Props, State> {
</MaterialHeaderButtons>; </MaterialHeaderButtons>;
}; };
hideMascotDialog = () => {
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.homeShowBanner.key, false);
this.setState({mascotDialogVisible: false})
};
showDisconnectDialog = () => this.setState({dialogVisible: true}); showDisconnectDialog = () => this.setState({dialogVisible: true});
hideDisconnectDialog = () => this.setState({dialogVisible: false}); hideDisconnectDialog = () => this.setState({dialogVisible: false});
@ -525,10 +516,7 @@ class HomeScreen extends React.Component<Props, State> {
* Callback when pressing the login button on the banner. * Callback when pressing the login button on the banner.
* This hides the banner and takes the user to the login page. * This hides the banner and takes the user to the login page.
*/ */
onLogin = () => { onLogin = () => this.props.navigation.navigate("login", {nextScreen: "profile"});
this.hideMascotDialog();
this.props.navigation.navigate("login", {nextScreen: "profile"});
}
render() { render() {
return ( return (
@ -554,26 +542,26 @@ class HomeScreen extends React.Component<Props, State> {
renderListHeaderComponent={this.getListHeader} renderListHeaderComponent={this.getListHeader}
/> />
</View> </View>
<MascotPopup {!this.isLoggedIn
visible={this.state.mascotDialogVisible} ? <MascotPopup
title={i18n.t("screens.home.mascotDialog.title")} prefKey={AsyncStorageManager.PREFERENCES.homeShowBanner.key}
message={i18n.t("screens.home.mascotDialog.message")} title={i18n.t("screens.home.mascotDialog.title")}
icon={"human-greeting"} message={i18n.t("screens.home.mascotDialog.message")}
buttons={{ icon={"human-greeting"}
action: { buttons={{
message: i18n.t("screens.home.mascotDialog.login"), action: {
icon: "login", message: i18n.t("screens.home.mascotDialog.login"),
onPress: this.onLogin, icon: "login",
}, onPress: this.onLogin,
cancel: { },
message: i18n.t("screens.home.mascotDialog.later"), cancel: {
icon: "close", message: i18n.t("screens.home.mascotDialog.later"),
color: this.props.theme.colors.warning, icon: "close",
onPress: this.hideMascotDialog, color: this.props.theme.colors.warning,
} }
}} }}
emotion={MASCOT_STYLE.CUTE} emotion={MASCOT_STYLE.CUTE}
/> /> : null}
<AnimatedFAB <AnimatedFAB
{...this.props} {...this.props}
ref={this.fabRef} ref={this.fabRef}

View file

@ -26,7 +26,6 @@ type Props = {
} }
type State = { type State = {
mascotDialogVisible: boolean,
dialogVisible: boolean, dialogVisible: boolean,
dialogTitle: string, dialogTitle: string,
dialogMessage: string, dialogMessage: string,
@ -143,10 +142,6 @@ class PlanexScreen extends React.Component<Props, State> {
props.navigation.setOptions({title: currentGroup.name}) props.navigation.setOptions({title: currentGroup.name})
} }
this.state = { this.state = {
mascotDialogVisible:
AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.planexShowBanner.key)
&& AsyncStorageManager.getString(AsyncStorageManager.PREFERENCES.defaultStartScreen.key)
.toLowerCase() !== 'planex',
dialogVisible: false, dialogVisible: false,
dialogTitle: "", dialogTitle: "",
dialogMessage: "", dialogMessage: "",
@ -162,24 +157,11 @@ class PlanexScreen extends React.Component<Props, State> {
this.props.navigation.addListener('focus', this.onScreenFocus); this.props.navigation.addListener('focus', this.onScreenFocus);
} }
/**
* Callback used when closing the banner.
* This hides the banner and saves to preferences to prevent it from reopening
*/
onMascotDialogCancel = () => {
this.setState({mascotDialogVisible: false});
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.planexShowBanner.key, false);
};
/** /**
* Callback used when the user clicks on the navigate to settings button. * Callback used when the user clicks on the navigate to settings button.
* This will hide the banner and open the SettingsScreen * This will hide the banner and open the SettingsScreen
*/ */
onGoToSettings = () => { onGoToSettings = () => this.props.navigation.navigate('settings');
this.onMascotDialogCancel();
this.props.navigation.navigate('settings');
};
onScreenFocus = () => { onScreenFocus = () => {
this.handleNavigationParams(); this.handleNavigationParams();
@ -357,8 +339,10 @@ class PlanexScreen extends React.Component<Props, State> {
? this.getWebView() ? this.getWebView()
: <View style={{height: '100%'}}>{this.getWebView()}</View>} : <View style={{height: '100%'}}>{this.getWebView()}</View>}
</View> </View>
<MascotPopup {AsyncStorageManager.getString(AsyncStorageManager.PREFERENCES.defaultStartScreen.key)
visible={this.state.mascotDialogVisible} .toLowerCase() !== 'planex'
? <MascotPopup
prefKey={AsyncStorageManager.PREFERENCES.planexShowBanner.key}
title={i18n.t("screens.planex.mascotDialog.title")} title={i18n.t("screens.planex.mascotDialog.title")}
message={i18n.t("screens.planex.mascotDialog.message")} message={i18n.t("screens.planex.mascotDialog.message")}
icon={"emoticon-kiss"} icon={"emoticon-kiss"}
@ -372,11 +356,10 @@ class PlanexScreen extends React.Component<Props, State> {
message: i18n.t("screens.planex.mascotDialog.cancel"), message: i18n.t("screens.planex.mascotDialog.cancel"),
icon: "close", icon: "close",
color: this.props.theme.colors.warning, color: this.props.theme.colors.warning,
onPress: this.onMascotDialogCancel,
} }
}} }}
emotion={MASCOT_STYLE.INTELLO} emotion={MASCOT_STYLE.INTELLO}
/> /> : null }
<AlertDialog <AlertDialog
visible={this.state.dialogVisible} visible={this.state.dialogVisible}
onDismiss={this.hideDialog} onDismiss={this.hideDialog}

View file

@ -36,7 +36,6 @@ type State = {
refreshing: boolean, refreshing: boolean,
agendaItems: Object, agendaItems: Object,
calendarShowing: boolean, calendarShowing: boolean,
mascotDialogVisible: boolean,
}; };
const FETCH_URL = "https://www.amicale-insat.fr/api/event/list"; const FETCH_URL = "https://www.amicale-insat.fr/api/event/list";
@ -56,7 +55,6 @@ class PlanningScreen extends React.Component<Props, State> {
refreshing: false, refreshing: false,
agendaItems: {}, agendaItems: {},
calendarShowing: false, calendarShowing: false,
mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.eventsShowBanner.key)
}; };
currentDate = getDateOnlyString(getCurrentDateString()); currentDate = getDateOnlyString(getCurrentDateString());
@ -105,15 +103,6 @@ class PlanningScreen extends React.Component<Props, State> {
} }
}; };
/**
* Callback used when closing the banner.
* This hides the banner and saves to preferences to prevent it from reopening
*/
onHideMascotDialog = () => {
this.setState({mascotDialogVisible: false});
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.eventsShowBanner.key, false);
};
/** /**
* Function used to check if a row has changed * Function used to check if a row has changed
* *
@ -250,7 +239,7 @@ class PlanningScreen extends React.Component<Props, State> {
onRef={this.onAgendaRef} onRef={this.onAgendaRef}
/> />
<MascotPopup <MascotPopup
visible={this.state.mascotDialogVisible} prefKey={AsyncStorageManager.PREFERENCES.eventsShowBanner.key}
title={i18n.t("screens.planning.mascotDialog.title")} title={i18n.t("screens.planning.mascotDialog.title")}
message={i18n.t("screens.planning.mascotDialog.message")} message={i18n.t("screens.planning.mascotDialog.message")}
icon={"party-popper"} icon={"party-popper"}
@ -259,7 +248,6 @@ class PlanningScreen extends React.Component<Props, State> {
cancel: { cancel: {
message: i18n.t("screens.planning.mascotDialog.button"), message: i18n.t("screens.planning.mascotDialog.button"),
icon: "check", icon: "check",
onPress: this.onHideMascotDialog,
} }
}} }}
emotion={MASCOT_STYLE.HAPPY} emotion={MASCOT_STYLE.HAPPY}

View file

@ -46,7 +46,6 @@ type State = {
refreshing: boolean, refreshing: boolean,
modalCurrentDisplayItem: React.Node, modalCurrentDisplayItem: React.Node,
machinesWatched: Array<Machine>, machinesWatched: Array<Machine>,
mascotDialogVisible: boolean,
}; };
@ -67,7 +66,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
refreshing: false, refreshing: false,
modalCurrentDisplayItem: null, modalCurrentDisplayItem: null,
machinesWatched: AsyncStorageManager.getObject(AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key), machinesWatched: AsyncStorageManager.getObject(AsyncStorageManager.PREFERENCES.proxiwashWatchedMachines.key),
mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.proxiwashShowBanner.key),
}; };
/** /**
@ -84,15 +82,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
modalStateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t('screens.proxiwash.modal.unknown'); modalStateStrings[ProxiwashConstants.machineStates.UNKNOWN] = i18n.t('screens.proxiwash.modal.unknown');
} }
/**
* Callback used when closing the banner.
* This hides the banner and saves to preferences to prevent it from reopening
*/
onHideMascotDialog = () => {
this.setState({mascotDialogVisible: false});
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.proxiwashShowBanner.key, false);
};
/** /**
* Setup notification channel for android and add listeners to detect notifications fired * Setup notification channel for android and add listeners to detect notifications fired
*/ */
@ -409,7 +398,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
updateData={this.state.machinesWatched.length}/> updateData={this.state.machinesWatched.length}/>
</View> </View>
<MascotPopup <MascotPopup
visible={this.state.mascotDialogVisible} prefKey={AsyncStorageManager.PREFERENCES.proxiwashShowBanner.key}
title={i18n.t("screens.proxiwash.mascotDialog.title")} title={i18n.t("screens.proxiwash.mascotDialog.title")}
message={i18n.t("screens.proxiwash.mascotDialog.message")} message={i18n.t("screens.proxiwash.mascotDialog.message")}
icon={"information"} icon={"information"}
@ -418,7 +407,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
cancel: { cancel: {
message: i18n.t("screens.proxiwash.mascotDialog.ok"), message: i18n.t("screens.proxiwash.mascotDialog.ok"),
icon: "check", icon: "check",
onPress: this.onHideMascotDialog,
} }
}} }}
emotion={MASCOT_STYLE.NORMAL} emotion={MASCOT_STYLE.NORMAL}

View file

@ -20,11 +20,6 @@ type Props = {
theme: CustomTheme, theme: CustomTheme,
} }
type State = {
mascotDialogVisible: boolean,
}
export type listItem = { export type listItem = {
title: string, title: string,
description: string, description: string,
@ -33,14 +28,10 @@ export type listItem = {
} }
class ServicesScreen extends React.Component<Props, State> { class ServicesScreen extends React.Component<Props> {
finalDataset: Array<listItem> finalDataset: Array<listItem>
state = {
mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.servicesShowBanner.key),
}
constructor(props) { constructor(props) {
super(props); super(props);
const services = new ServicesManager(props.navigation); const services = new ServicesManager(props.navigation);
@ -53,16 +44,6 @@ class ServicesScreen extends React.Component<Props, State> {
}); });
} }
/**
* Callback used when closing the banner.
* This hides the banner and saves to preferences to prevent it from reopening
*/
onHideMascotDialog = () => {
this.setState({mascotDialogVisible: false});
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.servicesShowBanner.key, false);
};
getAboutButton = () => getAboutButton = () =>
<MaterialHeaderButtons> <MaterialHeaderButtons>
<Item title="information" iconName="information" onPress={this.onAboutPress}/> <Item title="information" iconName="information" onPress={this.onAboutPress}/>
@ -146,7 +127,7 @@ class ServicesScreen extends React.Component<Props, State> {
hasTab={true} hasTab={true}
/> />
<MascotPopup <MascotPopup
visible={this.state.mascotDialogVisible} prefKey={AsyncStorageManager.PREFERENCES.servicesShowBanner.key}
title={i18n.t("screens.services.mascotDialog.title")} title={i18n.t("screens.services.mascotDialog.title")}
message={i18n.t("screens.services.mascotDialog.message")} message={i18n.t("screens.services.mascotDialog.message")}
icon={"cloud-question"} icon={"cloud-question"}