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

View file

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

View file

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

View file

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

View file

@ -36,7 +36,6 @@ type State = {
refreshing: boolean,
agendaItems: Object,
calendarShowing: boolean,
mascotDialogVisible: boolean,
};
const FETCH_URL = "https://www.amicale-insat.fr/api/event/list";
@ -56,7 +55,6 @@ class PlanningScreen extends React.Component<Props, State> {
refreshing: false,
agendaItems: {},
calendarShowing: false,
mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.eventsShowBanner.key)
};
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
*
@ -250,7 +239,7 @@ class PlanningScreen extends React.Component<Props, State> {
onRef={this.onAgendaRef}
/>
<MascotPopup
visible={this.state.mascotDialogVisible}
prefKey={AsyncStorageManager.PREFERENCES.eventsShowBanner.key}
title={i18n.t("screens.planning.mascotDialog.title")}
message={i18n.t("screens.planning.mascotDialog.message")}
icon={"party-popper"}
@ -259,7 +248,6 @@ class PlanningScreen extends React.Component<Props, State> {
cancel: {
message: i18n.t("screens.planning.mascotDialog.button"),
icon: "check",
onPress: this.onHideMascotDialog,
}
}}
emotion={MASCOT_STYLE.HAPPY}

View file

@ -46,7 +46,6 @@ type State = {
refreshing: boolean,
modalCurrentDisplayItem: React.Node,
machinesWatched: Array<Machine>,
mascotDialogVisible: boolean,
};
@ -67,7 +66,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
refreshing: false,
modalCurrentDisplayItem: null,
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');
}
/**
* 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
*/
@ -409,7 +398,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
updateData={this.state.machinesWatched.length}/>
</View>
<MascotPopup
visible={this.state.mascotDialogVisible}
prefKey={AsyncStorageManager.PREFERENCES.proxiwashShowBanner.key}
title={i18n.t("screens.proxiwash.mascotDialog.title")}
message={i18n.t("screens.proxiwash.mascotDialog.message")}
icon={"information"}
@ -418,7 +407,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
cancel: {
message: i18n.t("screens.proxiwash.mascotDialog.ok"),
icon: "check",
onPress: this.onHideMascotDialog,
}
}}
emotion={MASCOT_STYLE.NORMAL}

View file

@ -20,11 +20,6 @@ type Props = {
theme: CustomTheme,
}
type State = {
mascotDialogVisible: boolean,
}
export type listItem = {
title: 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>
state = {
mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.servicesShowBanner.key),
}
constructor(props) {
super(props);
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 = () =>
<MaterialHeaderButtons>
<Item title="information" iconName="information" onPress={this.onAboutPress}/>
@ -146,7 +127,7 @@ class ServicesScreen extends React.Component<Props, State> {
hasTab={true}
/>
<MascotPopup
visible={this.state.mascotDialogVisible}
prefKey={AsyncStorageManager.PREFERENCES.servicesShowBanner.key}
title={i18n.t("screens.services.mascotDialog.title")}
message={i18n.t("screens.services.mascotDialog.message")}
icon={"cloud-question"}