Added a new mascot dialog to replace banners

This commit is contained in:
Arnaud Vergnet 2020-07-12 00:04:33 +02:00
parent 976684dfce
commit 761132732b
14 changed files with 489 additions and 190 deletions

BIN
assets/mascot/mascot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -1,61 +1,33 @@
// @flow
import * as React from 'react';
import {Avatar, Card, List, withTheme} from 'react-native-paper';
import {StyleSheet, View} from "react-native";
import {List, withTheme} from 'react-native-paper';
import {View} from "react-native";
import type {CustomTheme} from "../../managers/ThemeManager";
import i18n from 'i18n-js';
import {StackNavigationProp} from "@react-navigation/stack";
const ICON_AMICALE = require("../../../assets/amicale.png");
type Props = {
navigation: StackNavigationProp,
theme: CustomTheme,
isLoggedIn: boolean,
}
class ActionsDashBoardItem extends React.Component<Props> {
shouldComponentUpdate(nextProps: Props): boolean {
return (nextProps.theme.dark !== this.props.theme.dark)
|| (nextProps.isLoggedIn !== this.props.isLoggedIn);
return (nextProps.theme.dark !== this.props.theme.dark);
}
render() {
const isLoggedIn = this.props.isLoggedIn;
return (
<View>
<Card style={{
...styles.card,
borderColor: this.props.theme.colors.primary,
}}>
<List.Item
title={i18n.t("homeScreen.dashboard.amicaleTitle")}
description={isLoggedIn
? i18n.t("homeScreen.dashboard.amicaleConnected")
: i18n.t("homeScreen.dashboard.amicaleConnect")}
left={props => <Avatar.Image
{...props}
size={40}
source={ICON_AMICALE}
style={styles.avatar}/>}
right={props => <List.Icon {...props} icon={isLoggedIn
? "chevron-right"
: "login"}/>}
onPress={isLoggedIn
? () => this.props.navigation.navigate("profile")
: () => this.props.navigation.navigate("login", {nextScreen: "profile"})}
style={styles.list}
/>
</Card>
<List.Item
title={i18n.t("feedbackScreen.homeButtonTitle")}
description={i18n.t("feedbackScreen.homeButtonSubtitle")}
left={props => <List.Icon {...props} icon={"bug"}/>}
right={props => <List.Icon {...props} icon={"chevron-right"}/>}
onPress={() => this.props.navigation.navigate("feedback")}
style={{...styles.list, marginLeft: 10, marginRight: 10}}
style={{paddingTop: 0, paddingBottom: 0, marginLeft: 10, marginRight: 10}}
/>
</View>
@ -63,22 +35,4 @@ class ActionsDashBoardItem extends React.Component<Props> {
}
}
const styles = StyleSheet.create({
card: {
width: 'auto',
margin: 10,
borderWidth: 1,
},
avatar: {
backgroundColor: 'transparent',
marginTop: 'auto',
marginBottom: 'auto',
},
list: {
// height: 50,
paddingTop: 0,
paddingBottom: 0,
}
});
export default withTheme(ActionsDashBoardItem);

View file

@ -0,0 +1,148 @@
// @flow
import * as React from 'react';
import * as Animatable from "react-native-animatable";
import {Image, View} from "react-native-animatable";
type Props = {
size: number,
emotion: number,
animated: boolean,
}
const MASCOT_IMAGE = require("../../../assets/mascot/mascot.png");
const MASCOT_EYES_NORMAL = require("../../../assets/mascot/mascot_eyes_normal.png");
const MASCOT_EYES_GIRLY = require("../../../assets/mascot/mascot_eyes_girly.png");
const MASCOT_EYES_CUTE = require("../../../assets/mascot/mascot_eyes_cute.png");
const MASCOT_EYES_WINK = require("../../../assets/mascot/mascot_eyes_wink.png");
const MASCOT_GLASSES = require("../../../assets/mascot/mascot_glasses.png");
export const EYE_STYLE = {
NORMAL: 0,
GIRLY: 2,
CUTE: 3,
WINK: 4,
}
export const MASCOT_STYLE = {
NORMAL: 0,
HAPPY: 1,
GIRLY: 2,
WINK: 3,
CUTE: 4,
INTELLO: 5,
};
class Mascot extends React.Component<Props> {
static defaultProps = {
animated: false
}
eyeList: { [key: number]: number | string }
constructor(props: Props) {
super(props);
this.eyeList = {};
this.eyeList[EYE_STYLE.NORMAL] = MASCOT_EYES_NORMAL;
this.eyeList[EYE_STYLE.GIRLY] = MASCOT_EYES_GIRLY;
this.eyeList[EYE_STYLE.CUTE] = MASCOT_EYES_CUTE;
this.eyeList[EYE_STYLE.WINK] = MASCOT_EYES_WINK;
}
getGlasses() {
return <Image
key={"glasses"}
source={MASCOT_GLASSES}
style={{
position: "absolute",
top: "15%",
left: 0,
width: this.props.size,
height: this.props.size,
}}
/>
}
getEye(style: number, isRight: boolean) {
const eye = this.eyeList[style];
return <Image
key={isRight ? "right" : "left"}
source={eye != null ? eye : this.eyeList[EYE_STYLE.NORMAL]}
style={{
position: "absolute",
top: "15%",
left: isRight ? "-11%" : "11%",
width: this.props.size,
height: this.props.size,
}}
/>
}
getEyes(emotion: number) {
let final = [];
final.push(<View
key={"container"}
style={{
position: "absolute",
width: this.props.size,
height: this.props.size,
}}/>);
if (emotion === MASCOT_STYLE.CUTE) {
final.push(this.getEye(EYE_STYLE.CUTE, true));
final.push(this.getEye(EYE_STYLE.CUTE, false));
} else if (emotion === MASCOT_STYLE.GIRLY) {
final.push(this.getEye(EYE_STYLE.GIRLY, true));
final.push(this.getEye(EYE_STYLE.GIRLY, false));
} else if (emotion === MASCOT_STYLE.HAPPY) {
final.push(this.getEye(EYE_STYLE.WINK, true));
final.push(this.getEye(EYE_STYLE.WINK, false));
} else if (emotion === MASCOT_STYLE.WINK) {
final.push(this.getEye(EYE_STYLE.WINK, true));
final.push(this.getEye(EYE_STYLE.NORMAL, false));
} else {
final.push(this.getEye(EYE_STYLE.NORMAL, true));
final.push(this.getEye(EYE_STYLE.NORMAL, false));
}
if (emotion === MASCOT_STYLE.INTELLO) {
final.push(this.getGlasses())
}
final.push(<View key={"container2"}/>);
return final;
}
render() {
const size = this.props.size;
return (
<Animatable.View
style={{
width: size,
height: size,
}}
useNativeDriver={true}
animation={this.props.animated ? "rubberBand" : null}
duration={2000}
>
<View
useNativeDriver={true}
animation={this.props.animated ? "swing" : null}
duration={2000}
iterationCount={"infinite"}
>
<Image
source={MASCOT_IMAGE}
style={{
width: size,
height: size,
}}
/>
{this.getEyes(this.props.emotion)}
</View>
</Animatable.View>
);
}
}
export default Mascot;

View file

@ -0,0 +1,225 @@
// @flow
import * as React from 'react';
import {Avatar, Button, Card, Paragraph, Portal, withTheme} from 'react-native-paper';
import Mascot from "./Mascot";
import * as Animatable from "react-native-animatable";
import {Dimensions, ScrollView, TouchableWithoutFeedback, View} from "react-native";
import type {CustomTheme} from "../../managers/ThemeManager";
type Props = {
visible: boolean,
theme: CustomTheme,
icon: string,
title: string,
message: string,
buttons: {
action: {
message: string,
icon: string | null,
color: string | null,
onPress: () => void,
},
cancel: {
message: string,
icon: string | null,
color: string | null,
onPress: () => void,
}
},
emotion: number,
}
type State = {
shouldShowDialog: boolean;
}
class MascotPopup extends React.Component<Props, State> {
mascotSize: number;
windowWidth: number;
windowHeight: number;
state = {
shouldShowDialog: this.props.visible,
};
constructor(props: Props) {
super(props);
this.windowWidth = Dimensions.get('window').width;
this.windowHeight = Dimensions.get('window').height;
this.mascotSize = Dimensions.get('window').height / 6;
}
onAnimationEnd = () => {
this.setState({
shouldShowDialog: this.props.visible,
})
}
shouldComponentUpdate(nextProps: Props): boolean {
if (nextProps.visible)
this.state.shouldShowDialog = true;
else if (nextProps.visible !== this.props.visible)
setTimeout(this.onAnimationEnd, 300);
return true;
}
getSpeechBubble() {
return (
<Animatable.View
style={{
marginLeft: "10%",
marginRight: "10%",
}}
useNativeDriver={true}
animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"}
duration={this.props.visible ? 1000 : 300}
>
<View style={{
marginLeft: this.mascotSize / 3,
width: 0,
height: 0,
borderLeftWidth: 0,
borderRightWidth: 20,
borderBottomWidth: 20,
borderStyle: 'solid',
backgroundColor: 'transparent',
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: this.props.theme.colors.mascotMessageArrow,
}}/>
<Card style={{
borderColor: this.props.theme.colors.mascotMessageArrow,
borderWidth: 4,
borderRadius: 10,
}}>
<Card.Title
title={this.props.title}
left={this.props.icon != null ?
(props) => <Avatar.Icon
{...props}
style={{backgroundColor: "transparent"}}
color={this.props.theme.colors.primary}
icon={this.props.icon}
/>
: null}
/>
<Card.Content style={{
maxHeight: this.windowHeight / 3
}}>
<ScrollView>
<Paragraph style={{marginBottom: 10}}>
{this.props.message}
</Paragraph>
</ScrollView>
</Card.Content>
<Card.Actions>
{this.getButtons()}
</Card.Actions>
</Card>
</Animatable.View>
);
}
getMascot() {
return (
<Animatable.View
useNativeDriver={true}
animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"}
duration={this.props.visible ? 1500 : 200}
>
<Mascot
size={this.mascotSize}
animated={true}
emotion={this.props.emotion}
/>
</Animatable.View>
);
}
getButtons() {
const action = this.props.buttons.action;
const cancel = this.props.buttons.cancel;
return (
<View style={{
flexDirection: "row",
marginLeft: "auto",
marginRight: "auto",
marginTop: "auto",
marginBottom: "auto",
}}>
{cancel != null
? <Button
mode={"contained"}
icon={cancel.icon}
color={cancel.color}
onPress={cancel.onPress}
>
{cancel.message}
</Button>
: null}
{action != null
? <Button
style={{
marginLeft: 20,
}}
mode={"contained"}
icon={action.icon}
color={action.color}
onPress={action.onPress}
>
{action.message}
</Button>
: null}
</View>
);
}
getBackground() {
return (
<TouchableWithoutFeedback onPress={this.props.buttons.cancel.onPress}>
<Animatable.View
style={{
position: "absolute",
backgroundColor: "rgba(0,0,0,0.7)",
width: "100%",
height: "100%",
}}
useNativeDriver={true}
animation={this.props.visible ? "fadeIn" : "fadeOut"}
duration={this.props.visible ? 300 : 300}
/>
</TouchableWithoutFeedback>
);
}
render() {
if (this.state.shouldShowDialog) {
return (
<Portal>
{this.getBackground()}
<View style={{
marginTop: "auto",
marginBottom: "auto",
}}>
{this.getMascot()}
{this.getSpeechBubble()}
</View>
</Portal>
);
} else
return null;
}
}
export default withTheme(MascotPopup);

View file

@ -55,6 +55,9 @@ export type CustomTheme = {
tetrisZ: string,
tetrisJ: string,
tetrisL: string,
// Mascot Popup
mascotMessageArrow: string,
},
}
@ -83,7 +86,7 @@ export default class ThemeManager {
primary: '#be1522',
accent: '#be1522',
tabIcon: "#929292",
card: "rgb(255, 255, 255)",
card: "#fff",
dividerBackground: '#e2e2e2',
ripple: "rgba(0,0,0,0.2)",
textDisabled: '#c1c1c1',
@ -126,6 +129,9 @@ export default class ThemeManager {
tetrisZ: '#ff0009',
tetrisJ: '#2a67e3',
tetrisL: '#da742d',
// Mascot Popup
mascotMessageArrow: "#dedede",
},
};
}
@ -144,7 +150,7 @@ export default class ThemeManager {
accent: '#be1522',
tabBackground: "#181818",
tabIcon: "#6d6d6d",
card: "rgb(18, 18, 18)",
card: "rgb(18,18,18)",
dividerBackground: '#222222',
ripple: "rgba(255,255,255,0.2)",
textDisabled: '#5b5b5b',
@ -186,6 +192,9 @@ export default class ThemeManager {
tetrisZ: '#b50008',
tetrisJ: '#0f37b9',
tetrisL: '#b96226',
// Mascot Popup
mascotMessageArrow: "#323232",
},
};
}

View file

@ -5,7 +5,7 @@ import {FlatList} from 'react-native';
import i18n from "i18n-js";
import DashboardItem from "../../components/Home/EventDashboardItem";
import WebSectionList from "../../components/Screens/WebSectionList";
import {Avatar, Banner, withTheme} from 'react-native-paper';
import {withTheme} from 'react-native-paper';
import FeedItem from "../../components/Home/FeedItem";
import SquareDashboardItem from "../../components/Home/SmallDashboardItem";
import PreviewEventDashboardItem from "../../components/Home/PreviewEventDashboardItem";
@ -19,10 +19,10 @@ import type {CustomTheme} from "../../managers/ThemeManager";
import {View} from "react-native-animatable";
import ConnectionManager from "../../managers/ConnectionManager";
import LogoutDialog from "../../components/Amicale/LogoutDialog";
import {withCollapsible} from "../../utils/withCollapsible";
import {Collapsible} from "react-navigation-collapsible";
import AsyncStorageManager from "../../managers/AsyncStorageManager";
import AvailableWebsites from "../../constants/AvailableWebsites";
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
import MascotPopup from "../../components/Mascot/MascotPopup";
// import DATA from "../dashboard_data.json";
@ -97,12 +97,11 @@ type Props = {
navigation: StackNavigationProp,
route: { params: any, ... },
theme: CustomTheme,
collapsibleStack: Collapsible,
}
type State = {
dialogVisible: boolean,
bannerVisible: boolean,
mascotDialogVisible: boolean,
}
/**
@ -115,16 +114,19 @@ class HomeScreen extends React.Component<Props, State> {
fabRef: { current: null | AnimatedFAB };
currentNewFeed: Array<feedItem>;
state = {
dialogVisible: false,
bannerVisible: false,
}
constructor(props) {
super(props);
this.fabRef = React.createRef();
this.currentNewFeed = [];
this.isLoggedIn = null;
this.isLoggedIn = ConnectionManager.getInstance().isLoggedIn();
this.props.navigation.setOptions({
headerRight: this.getHeaderButton,
});
this.state = {
dialogVisible: false,
mascotDialogVisible: AsyncStorageManager.getInstance().preferences.homeShowBanner.current === "1"
&& !this.isLoggedIn,
}
}
/**
@ -142,13 +144,6 @@ class HomeScreen extends React.Component<Props, State> {
this.props.navigation.addListener('focus', this.onScreenFocus);
// Handle link open when home is focused
this.props.navigation.addListener('state', this.handleNavigationParams);
setTimeout(this.onBannerTimeout, 2000);
}
onBannerTimeout = () => {
this.setState({
bannerVisible: AsyncStorageManager.getInstance().preferences.homeShowBanner.current === "1"
})
}
/**
@ -161,9 +156,6 @@ class HomeScreen extends React.Component<Props, State> {
headerRight: this.getHeaderButton,
});
}
if (this.isLoggedIn) {
this.setState({bannerVisible: false})
}
// handle link open when home is not focused or created
this.handleNavigationParams();
};
@ -203,6 +195,14 @@ class HomeScreen extends React.Component<Props, State> {
</MaterialHeaderButtons>;
};
hideMascotDialog = () => {
AsyncStorageManager.getInstance().savePref(
AsyncStorageManager.getInstance().preferences.homeShowBanner.key,
'0'
);
this.setState({mascotDialogVisible: false})
};
showDisconnectDialog = () => this.setState({dialogVisible: true});
hideDisconnectDialog = () => this.setState({dialogVisible: false});
@ -569,29 +569,16 @@ class HomeScreen extends React.Component<Props, State> {
this.fabRef.current.onScroll(event);
};
/**
* Callback used when closing the banner.
* This hides the banner and saves to preferences to prevent it from reopening.
*/
onHideBanner = () => {
this.setState({bannerVisible: false});
AsyncStorageManager.getInstance().savePref(
AsyncStorageManager.getInstance().preferences.homeShowBanner.key,
'0'
);
};
/**
* Callback when pressing the login button on the banner.
* This hides the banner and takes the user to the login page.
*/
onLoginBanner = () => {
this.onHideBanner();
onLogin = () => {
this.hideMascotDialog();
this.props.navigation.navigate("login", {nextScreen: "profile"});
}
render() {
const {containerPaddingTop} = this.props.collapsibleStack;
return (
<View
style={{flex: 1}}
@ -613,6 +600,26 @@ class HomeScreen extends React.Component<Props, State> {
showError={false}
/>
</View>
<MascotPopup
visible={this.state.mascotDialogVisible}
title={i18n.t("homeScreen.loginBanner.title")}
message={i18n.t("homeScreen.loginBanner.message")}
icon={"check"}
buttons={{
action: {
message: i18n.t("homeScreen.loginBanner.login"),
icon: "login",
onPress: this.onLogin,
},
cancel: {
message: i18n.t("homeScreen.loginBanner.later"),
icon: "close",
color: this.props.theme.colors.warning,
onPress: this.hideMascotDialog,
}
}}
emotion={MASCOT_STYLE.CUTE}
/>
<AnimatedFAB
{...this.props}
ref={this.fabRef}
@ -624,32 +631,9 @@ class HomeScreen extends React.Component<Props, State> {
visible={this.state.dialogVisible}
onDismiss={this.hideDisconnectDialog}
/>
<Banner
style={{
marginTop: containerPaddingTop,
backgroundColor: this.props.theme.colors.surface
}}
visible={this.state.bannerVisible}
actions={[
{
label: i18n.t('homeScreen.loginBanner.login'),
onPress: this.onLoginBanner,
},
{
label: i18n.t('homeScreen.loginBanner.later'),
onPress: this.onHideBanner,
},
]}
icon={() => <Avatar.Icon
icon={'login'}
size={50}
/>}
>
{i18n.t('homeScreen.loginBanner.message')}
</Banner>
</View>
);
}
}
export default withCollapsible(withTheme(HomeScreen));
export default withTheme(HomeScreen);

View file

@ -4,30 +4,29 @@ import * as React from 'react';
import type {CustomTheme} from "../../managers/ThemeManager";
import ThemeManager from "../../managers/ThemeManager";
import WebViewScreen from "../../components/Screens/WebViewScreen";
import {Avatar, Banner, withTheme} from "react-native-paper";
import {withTheme} from "react-native-paper";
import i18n from "i18n-js";
import {View} from "react-native";
import AsyncStorageManager from "../../managers/AsyncStorageManager";
import AlertDialog from "../../components/Dialogs/AlertDialog";
import {withCollapsible} from "../../utils/withCollapsible";
import {dateToString, getTimeOnlyString} from "../../utils/Planning";
import DateManager from "../../managers/DateManager";
import AnimatedBottomBar from "../../components/Animations/AnimatedBottomBar";
import {CommonActions} from "@react-navigation/native";
import ErrorView from "../../components/Screens/ErrorView";
import {StackNavigationProp} from "@react-navigation/stack";
import {Collapsible} from "react-navigation-collapsible";
import type {group} from "./GroupSelectionScreen";
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
import MascotPopup from "../../components/Mascot/MascotPopup";
type Props = {
navigation: StackNavigationProp,
route: { params: { group: group } },
theme: CustomTheme,
collapsibleStack: Collapsible,
}
type State = {
bannerVisible: boolean,
mascotDialogVisible: boolean,
dialogVisible: boolean,
dialogTitle: string,
dialogMessage: string,
@ -144,7 +143,9 @@ class PlanexScreen extends React.Component<Props, State> {
props.navigation.setOptions({title: currentGroup.name})
}
this.state = {
bannerVisible: false,
mascotDialogVisible:
AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' &&
AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex',
dialogVisible: false,
dialogTitle: "",
dialogMessage: "",
@ -158,23 +159,14 @@ class PlanexScreen extends React.Component<Props, State> {
*/
componentDidMount() {
this.props.navigation.addListener('focus', this.onScreenFocus);
setTimeout(this.onBannerTimeout, 2000);
}
onBannerTimeout = () => {
this.setState({
bannerVisible:
AsyncStorageManager.getInstance().preferences.planexShowBanner.current === '1' &&
AsyncStorageManager.getInstance().preferences.defaultStartScreen.current !== 'Planex'
})
}
/**
* Callback used when closing the banner.
* This hides the banner and saves to preferences to prevent it from reopening
*/
onHideBanner = () => {
this.setState({bannerVisible: false});
onMascotDialogCancel = () => {
this.setState({mascotDialogVisible: false});
AsyncStorageManager.getInstance().savePref(
AsyncStorageManager.getInstance().preferences.planexShowBanner.key,
'0'
@ -187,7 +179,7 @@ class PlanexScreen extends React.Component<Props, State> {
* This will hide the banner and open the SettingsScreen
*/
onGoToSettings = () => {
this.onHideBanner();
this.onMascotDialogCancel();
this.props.navigation.navigate('settings');
};
@ -356,7 +348,6 @@ class PlanexScreen extends React.Component<Props, State> {
}
render() {
const {containerPaddingTop} = this.props.collapsibleStack;
return (
<View
style={{flex: 1}}
@ -371,30 +362,26 @@ class PlanexScreen extends React.Component<Props, State> {
? this.getWebView()
: <View style={{height: '100%'}}>{this.getWebView()}</View>}
</View>
<Banner
style={{
marginTop: containerPaddingTop,
backgroundColor: this.props.theme.colors.surface
}}
visible={this.state.bannerVisible}
actions={[
{
label: i18n.t('planexScreen.enableStartOK'),
<MascotPopup
visible={this.state.mascotDialogVisible}
title={i18n.t("planexScreen.enableStartScreenTitle")}
message={i18n.t("planexScreen.enableStartScreenMessage")}
icon={"power"}
buttons={{
action: {
message: i18n.t("planexScreen.enableStartOK"),
icon: "settings",
onPress: this.onGoToSettings,
},
{
label: i18n.t('planexScreen.enableStartCancel'),
onPress: this.onHideBanner,
},
]}
icon={() => <Avatar.Icon
icon={'power'}
size={40}
/>}
>
{i18n.t('planexScreen.enableStartScreen')}
</Banner>
cancel: {
message: i18n.t("planexScreen.enableStartCancel"),
icon: "close",
color: this.props.theme.colors.warning,
onPress: this.onMascotDialogCancel,
}
}}
emotion={MASCOT_STYLE.INTELLO}
/>
<AlertDialog
visible={this.state.dialogVisible}
onDismiss={this.hideDialog}
@ -411,4 +398,4 @@ class PlanexScreen extends React.Component<Props, State> {
}
}
export default withCollapsible(withTheme(PlanexScreen));
export default withTheme(PlanexScreen);

View file

@ -6,19 +6,19 @@ import i18n from "i18n-js";
import WebSectionList from "../../components/Screens/WebSectionList";
import * as Notifications from "../../utils/Notifications";
import AsyncStorageManager from "../../managers/AsyncStorageManager";
import {Avatar, Banner, Button, Card, Text, withTheme} from 'react-native-paper';
import {Avatar, Button, Card, Text, withTheme} from 'react-native-paper';
import ProxiwashListItem from "../../components/Lists/Proxiwash/ProxiwashListItem";
import ProxiwashConstants from "../../constants/ProxiwashConstants";
import CustomModal from "../../components/Overrides/CustomModal";
import AprilFoolsManager from "../../managers/AprilFoolsManager";
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
import ProxiwashSectionHeader from "../../components/Lists/Proxiwash/ProxiwashSectionHeader";
import {withCollapsible} from "../../utils/withCollapsible";
import type {CustomTheme} from "../../managers/ThemeManager";
import {Collapsible} from "react-navigation-collapsible";
import {StackNavigationProp} from "@react-navigation/stack";
import {getCleanedMachineWatched, getMachineEndDate, isMachineWatched} from "../../utils/Proxiwash";
import {Modalize} from "react-native-modalize";
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
import MascotPopup from "../../components/Mascot/MascotPopup";
const DATA_URL = "https://etud.insa-toulouse.fr/~amicale_app/v2/washinsa/washinsa_data.json";
@ -40,14 +40,13 @@ export type Machine = {
type Props = {
navigation: StackNavigationProp,
theme: CustomTheme,
collapsibleStack: Collapsible,
}
type State = {
refreshing: boolean,
modalCurrentDisplayItem: React.Node,
machinesWatched: Array<Machine>,
bannerVisible: boolean,
mascotDialogVisible: boolean,
};
@ -68,7 +67,7 @@ class ProxiwashScreen extends React.Component<Props, State> {
refreshing: false,
modalCurrentDisplayItem: null,
machinesWatched: JSON.parse(AsyncStorageManager.getInstance().preferences.proxiwashWatchedMachines.current),
bannerVisible: false,
mascotDialogVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === "1",
};
/**
@ -89,8 +88,8 @@ class ProxiwashScreen extends React.Component<Props, State> {
* Callback used when closing the banner.
* This hides the banner and saves to preferences to prevent it from reopening
*/
onHideBanner = () => {
this.setState({bannerVisible: false});
onHideMascotDialog = () => {
this.setState({mascotDialogVisible: false});
AsyncStorageManager.getInstance().savePref(
AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.key,
'0'
@ -107,11 +106,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
<Item title="information" iconName="information" onPress={this.onAboutPress}/>
</MaterialHeaderButtons>,
});
setTimeout(this.onBannerTimeout, 2000);
}
onBannerTimeout = () => {
this.setState({bannerVisible: AsyncStorageManager.getInstance().preferences.proxiwashShowBanner.current === "1"})
}
/**
@ -401,7 +395,6 @@ class ProxiwashScreen extends React.Component<Props, State> {
render() {
const nav = this.props.navigation;
const {containerPaddingTop} = this.props.collapsibleStack;
return (
<View
style={{flex: 1}}
@ -421,25 +414,21 @@ class ProxiwashScreen extends React.Component<Props, State> {
refreshOnFocus={true}
updateData={this.state.machinesWatched.length}/>
</View>
<Banner
style={{
marginTop: containerPaddingTop,
backgroundColor: this.props.theme.colors.surface
<MascotPopup
visible={this.state.mascotDialogVisible}
title={i18n.t("proxiwashScreen.bannerTitle")}
message={i18n.t("proxiwashScreen.enableNotificationsTip")}
icon={"bell"}
buttons={{
action: null,
cancel: {
message: i18n.t("proxiwashScreen.bannerButton"),
icon: "check",
onPress: this.onHideMascotDialog,
}
}}
visible={this.state.bannerVisible}
actions={[
{
label: i18n.t('proxiwashScreen.bannerButton'),
onPress: this.onHideBanner,
},
]}
icon={() => <Avatar.Icon
icon={'bell'}
size={40}
/>}
>
{i18n.t('proxiwashScreen.enableNotificationsTip')}
</Banner>
emotion={MASCOT_STYLE.NORMAL}
/>
<CustomModal onRef={this.onModalRef}>
{this.state.modalCurrentDisplayItem}
</CustomModal>
@ -448,4 +437,4 @@ class ProxiwashScreen extends React.Component<Props, State> {
}
}
export default withCollapsible(withTheme(ProxiwashScreen));
export default withTheme(ProxiwashScreen);

View file

@ -116,7 +116,8 @@
"loginBanner": {
"login": "Login",
"later": "Later",
"message": "Login to your Amicale account to get access to more services!"
"title": "Welcome, you!",
"message": "Login to your Amicale account to get access to more services!\n\nYou will still be able to login later."
}
},
"aboutScreen": {
@ -179,9 +180,10 @@
"dryerTips": "The advised dryer length is 35 minutes for 14 kg of laundry. You can choose a shorter length if the dryer is not fully charged.",
"procedure": "Procedure",
"tips": "Tips",
"enableNotificationsTip": "Click on a running machine to enable notifications",
"enableNotificationsTip": "Click on a running machine to enable notifications!\n\nYou will never forget your laundry again.",
"numAvailable": "available",
"numAvailablePlural": "available",
"bannerTitle": "Notifications!",
"bannerButton": "Got it!",
"modal": {
"enableNotifications": "Notify me",
@ -215,7 +217,8 @@
}
},
"planexScreen": {
"enableStartScreen": "Come here often? Set it as default screen!",
"enableStartScreenTitle": "Come here often?",
"enableStartScreenMessage": "Set it as default screen!\n\nCampus will start on Planex so you never miss a class. Click on the button bellow to navigate to the settings page.",
"enableStartOK": "Yes please!",
"enableStartCancel": "Later",
"noGroupSelected": "No group selected. Please select your group using the big beautiful red button bellow.",