Skip to content

Commit

Permalink
feat: add notifications onboarding wizard (#9263)
Browse files Browse the repository at this point in the history
## **Description**

This PR implements a new step on the onboarding wizard to teach users
about notifications feature.

## **Related issues**

N/A

## **Manual testing steps**

Install a fresh app and accept the tour at beginning. 

## **Screenshots/Recordings**

https://github.com/MetaMask/metamask-mobile/assets/44679989/ff6273cb-613c-4b4d-afe8-99fb63d5445f


### **After**
![Screenshot 2024-04-16 at 17 37
21](https://github.com/MetaMask/metamask-mobile/assets/44679989/faf5526d-a0e5-4c92-8dd4-fa65c08573a9)

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
Jonathansoufer authored Apr 23, 2024
1 parent 1dc1d70 commit eb74bd0
Show file tree
Hide file tree
Showing 24 changed files with 660 additions and 277 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ const TabBar = ({ state, descriptors, navigation }: TabBarProps) => {
* Current onboarding wizard step
*/
const wizardStep = useSelector((reduxState: any) => reduxState.wizard.step);

/**
* Return current step of onboarding wizard if not step 5 nor 0
*/
const renderOnboardingWizard = useCallback(
() =>
[4, 5].includes(wizardStep) && (
[4, 5, 6].includes(wizardStep) && (
<OnboardingWizard navigation={navigation} coachmarkRef={tabBarRef} />
),
[navigation, wizardStep],
Expand Down
4 changes: 3 additions & 1 deletion app/components/UI/Navbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,15 @@ const styles = StyleSheet.create({
paddingVertical: Device.isAndroid() ? 14 : 8,
},
infoButton: {
paddingRight: Device.isAndroid() ? 0 : 18,

marginTop: 5,
},
disabled: {
opacity: 0.3,
},
leftButtonContainer: {
marginRight: Device.isAndroid() ? 22 : 12,
marginRight: 12,
flexDirection: 'row',
alignItems: 'flex-end',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ exports[`Coachmark should render correctly 1`] = `
}
>
1
/5
/6
</Text>
</View>
<TouchableOpacity
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/OnboardingWizard/Coachmark/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ export default class Coachmark extends PureComponent {
<View style={styles.progress}>
<View style={styles.progessContainer}>
{currentStep !== 0 && (
<Text style={styles.stepCounter}>{currentStep}/5</Text>
<Text style={styles.stepCounter}>{currentStep}/6</Text>
)}
</View>

Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/OnboardingWizard/Step1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const styles = StyleSheet.create({
position: 'absolute',
left: 0,
right: 0,
bottom: Device.isIphoneX() ? 80 : Device.isIos() ? 40 : 60,
bottom: Device.isIphoneX() ? 80 : Device.isIos() ? 40 : 64,
},
});

Expand Down
179 changes: 73 additions & 106 deletions app/components/UI/OnboardingWizard/Step2/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React, { PureComponent } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Platform, StyleSheet, Text, View } from 'react-native';
import Coachmark from '../Coachmark';
import setOnboardingWizardStep from '../../../../actions/wizard';
import { strings } from '../../../../../locales/i18n';
import Coachmark from '../Coachmark';

import onboardingStyles from './../styles';
import {
MetaMetricsEvents,
ONBOARDING_WIZARD_STEP_DESCRIPTION,
} from '../../../../core/Analytics';
import { mockTheme, ThemeContext } from '../../../../util/theme';
import { useTheme } from '../../../../util/theme';
import generateTestId from '../../../../../wdio/utils/generateTestId';
import { ONBOARDING_WIZARD_SECOND_STEP_CONTENT_ID } from '../../../../../wdio/screen-objects/testIDs/Components/OnboardingWizard.testIds';
import { withMetricsAwareness } from '../../../../components/hooks/useMetrics';
import { useMetrics } from '../../../hooks/useMetrics';

const styles = StyleSheet.create({
main: {
Expand All @@ -27,95 +28,59 @@ const styles = StyleSheet.create({
},
});

class Step2 extends PureComponent {
static propTypes = {
/**
* Dispatch set onboarding wizard step
*/
setOnboardingWizardStep: PropTypes.func,
/**
* Coachmark ref to get position
*/
coachmarkRef: PropTypes.object,
/**
* Callback called when closing step
*/
onClose: PropTypes.func,
/**
* Metrics injected by withMetricsAwareness HOC
*/
metrics: PropTypes.object,
};

state = {
coachmarkTop: 0,
};
const Step2 = ({ setOnboardingWizardStep, coachmarkRef, onClose }) => {
const { colors } = useTheme();
const { trackEvent } = useMetrics();

componentDidMount = () => {
this.getPosition(this.props.coachmarkRef.yourAccountRef);
};
const [coachmarkTop, setCoachmarkTop] = useState(0);

/**
* If component ref defined, calculate its position and position coachmark accordingly
*/
getPosition = (ref) => {
ref &&
ref.current &&
ref.current.measure((fx, fy, width, height, px, py) => {
this.setState({
coachmarkTop: py + height,
});
});
};
const handleLayout = useCallback(() => {
const yourAccRef = coachmarkRef.yourAccountRef?.current;
if (!yourAccRef) return;

/**
* Dispatches 'setOnboardingWizardStep' with next step
*/
onNext = () => {
const { setOnboardingWizardStep } = this.props;
setOnboardingWizardStep && setOnboardingWizardStep(3);
this.props.metrics.trackEvent(
MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED,
{
tutorial_step_count: 2,
tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2],
yourAccRef.measure(
(
accActionsFx,
accActionsFy,
accActionsWidth,
accActionsHeight,
accActionsPageX,
accActionsPageY,
) => {
const top = accActionsHeight + accActionsPageY;
setCoachmarkTop(top);
},
);
}, [coachmarkRef.yourAccountRef]);

useEffect(() => {
handleLayout();
}, [handleLayout]);

const onNext = () => {
setOnboardingWizardStep && setOnboardingWizardStep(3);
trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_COMPLETED, {
tutorial_step_count: 2,
tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2],
});
};

/**
* Dispatches 'setOnboardingWizardStep' with back step
*/
onBack = () => {
const { setOnboardingWizardStep } = this.props;
const onBack = () => {
setOnboardingWizardStep && setOnboardingWizardStep(1);
this.props.metrics.trackEvent(
MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED,
{
tutorial_step_count: 2,
tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2],
},
);
trackEvent(MetaMetricsEvents.ONBOARDING_TOUR_STEP_REVISITED, {
tutorial_step_count: 2,
tutorial_step_name: ONBOARDING_WIZARD_STEP_DESCRIPTION[2],
});
};

getOnboardingStyles = () => {
const colors = this.context.colors || mockTheme.colors;
return onboardingStyles(colors);
};
const getOnboardingStyles = () => onboardingStyles(colors);

/**
* Calls props 'onClose'
*/
onClose = () => {
const { onClose } = this.props;
const onCloseStep = () => {
onClose && onClose(false);
};

/**
* Returns content for this step
*/
content = () => {
const dynamicOnboardingStyles = this.getOnboardingStyles();
const content = () => {
const dynamicOnboardingStyles = getOnboardingStyles();

return (
<View style={dynamicOnboardingStyles.contentContainer}>
Expand All @@ -132,36 +97,38 @@ class Step2 extends PureComponent {
);
};

render() {
return (
<View style={styles.main}>
<View
style={[
styles.coachmarkContainer,
{
top: this.state.coachmarkTop,
},
]}
>
<Coachmark
title={strings('onboarding_wizard_new.step2.title')}
content={this.content()}
onNext={this.onNext}
onBack={this.onBack}
topIndicatorPosition={'topCenter'}
currentStep={1}
onClose={this.onClose}
/>
</View>
return (
<View style={styles.main}>
<View
style={[
styles.coachmarkContainer,
{
top: coachmarkTop,
},
]}
>
<Coachmark
title={strings('onboarding_wizard_new.step2.title')}
content={content()}
onNext={onNext}
onBack={onBack}
topIndicatorPosition={'topCenter'}
currentStep={1}
onClose={onCloseStep}
/>
</View>
);
}
}
</View>
);
};

Step2.propTypes = {
setOnboardingWizardStep: PropTypes.func,
coachmarkRef: PropTypes.object,
onClose: PropTypes.func,
};

const mapDispatchToProps = (dispatch) => ({
setOnboardingWizardStep: (step) => dispatch(setOnboardingWizardStep(step)),
});

Step2.contextType = ThemeContext;

export default connect(null, mapDispatchToProps)(withMetricsAwareness(Step2));
export default connect(null, mapDispatchToProps)(Step2);
13 changes: 4 additions & 9 deletions app/components/UI/OnboardingWizard/Step3/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Dimensions, Platform, StyleSheet, Text, View } from 'react-native';
import { Platform, StyleSheet, Text, View } from 'react-native';
import Coachmark from '../Coachmark';
import setOnboardingWizardStep from '../../../../actions/wizard';
import { strings } from '../../../../../locales/i18n';
Expand All @@ -27,6 +27,9 @@ const styles = StyleSheet.create({
},
coachmarkContainer: {
position: 'absolute',
left: 0,
right: 0,
marginHorizontal: 16,
},
});

Expand All @@ -35,8 +38,6 @@ const Step3 = ({ setOnboardingWizardStep, coachmarkRef, onClose }) => {
const { trackEvent } = useMetrics();

const [coachmarkTop, setCoachmarkTop] = useState(0);
const [coachmarkLeft, setCoachmarkLeft] = useState(0);
const [coachmarkRight, setCoachmarkRight] = useState(0);

const handleLayout = useCallback(() => {
const accActionsRef = coachmarkRef.accountActionsRef?.current;
Expand All @@ -52,11 +53,7 @@ const Step3 = ({ setOnboardingWizardStep, coachmarkRef, onClose }) => {
accActionsPageY,
) => {
const top = accActionsHeight + accActionsPageY;
const right =
Dimensions.get('window').width - (accActionsPageX + accActionsWidth);
setCoachmarkTop(top);
setCoachmarkLeft(accActionsPageX);
setCoachmarkRight(right);
},
);
}, [coachmarkRef.accountActionsRef]);
Expand Down Expand Up @@ -109,8 +106,6 @@ const Step3 = ({ setOnboardingWizardStep, coachmarkRef, onClose }) => {
styles.coachmarkContainer,
{
top: coachmarkTop,
left: coachmarkLeft,
right: coachmarkRight,
},
]}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ exports[`Step4 should render correctly 1`] = `
}
}
>
<Component />
<Component
coachmarkRef={Object {}}
/>
</ContextProvider>
`;
Loading

0 comments on commit eb74bd0

Please sign in to comment.