diff --git a/app/components/UI/WhatsNewModal/index.js b/app/components/UI/WhatsNewModal/index.js index a5daefb94ce..030dd1fa6bb 100644 --- a/app/components/UI/WhatsNewModal/index.js +++ b/app/components/UI/WhatsNewModal/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { StyleSheet, View, @@ -26,7 +26,11 @@ import compareVersions from 'compare-versions'; import PropTypes from 'prop-types'; import { findRouteNameFromNavigatorState } from '../../../util/general'; import StyledButton from '../StyledButton'; -import { useAppThemeFromContext, mockTheme } from '../../../util/theme'; +import { + useAppThemeFromContext, + mockTheme, + useAssetFromTheme, +} from '../../../util/theme'; const modalMargin = 24; const modalPadding = 24; const screenWidth = Device.getDeviceWidth(); @@ -51,7 +55,13 @@ const createStyles = (colors) => flex: 1, width: slideItemWidth, paddingHorizontal: modalPadding, + }, + slideItemContainerContent: { paddingBottom: 16, + flexGrow: 1, + }, + slide: { + flex: 1, }, progessContainer: { flexDirection: 'row', @@ -119,12 +129,13 @@ const createStyles = (colors) => }); const WhatsNewModal = (props) => { + const scrollViewRef = useRef(); const [featuresToShow, setFeaturesToShow] = useState(null); const [show, setShow] = useState(false); const routes = useNavigationState((state) => state.routes); - const slideIds = [0, 1]; - const [currentSlide, setCurrentSlide] = useState(slideIds[0]); + const [currentSlide, setCurrentSlide] = useState(0); const { colors } = useAppThemeFromContext() || mockTheme; + const imageKey = useAssetFromTheme('light', 'dark'); const styles = createStyles(colors); useEffect(() => { @@ -208,16 +219,23 @@ const WhatsNewModal = (props) => { return (element = ( <Text style={styles.slideDescription}>{elementInfo.description}</Text> )); - case 'image': + case 'image': { + let image; + if (elementInfo.images) { + image = elementInfo.images[imageKey]; + } else { + image = elementInfo.image; + } return ( <View style={styles.slideImageContainer}> <Image - source={elementInfo.image} + source={image} style={styles.slideImage} resizeMode={'stretch'} /> </View> ); + } case 'button': return ( <View style={styles.button}> @@ -236,9 +254,13 @@ const WhatsNewModal = (props) => { const renderSlide = (slideInfo, index) => { const key = `slide-info-${index}`; return ( - <ScrollView key={key} style={styles.slideItemContainer}> + <ScrollView + key={key} + style={styles.slideItemContainer} + contentContainerStyle={styles.slideItemContainerContent} + > <TouchableWithoutFeedback> - <View> + <View style={styles.slide}> {slideInfo.map((elementInfo, elIndex) => { const elKey = `${key}-${elIndex}`; return <View key={elKey}>{renderSlideElement(elementInfo)}</View>; @@ -276,13 +298,15 @@ const WhatsNewModal = (props) => { onPress={closeModal} hitSlop={{ top: 10, left: 10, bottom: 10, right: 10 }} > - <Icon name="times" size={16} /> + <Icon name="times" size={16} color={colors.icon.default} /> </TouchableOpacity> </View> </View> - {featuresToShow && ( + {whatsNew.slides.length > 0 && ( <View style={styles.slideContent}> <ScrollView + ref={scrollViewRef} + scrollEnabled={whatsNew.slides.length > 1} // This is not duplicate. Needed for Android. onScrollEndDrag={onScrollEnd} onMomentumScrollEnd={onScrollEnd} @@ -292,17 +316,30 @@ const WhatsNewModal = (props) => { > {whatsNew.slides.map(renderSlide)} </ScrollView> - <View style={styles.progessContainer}> - {slideIds.map((id) => ( - <View - key={id} - style={[ - styles.slideCircle, - currentSlide === id ? styles.slideSolidCircle : {}, - ]} - /> - ))} - </View> + {whatsNew.slides.length > 1 && ( + <View style={styles.progessContainer}> + {whatsNew.slides.map((_, index) => ( + <TouchableWithoutFeedback + key={`slide-circle-${index}`} + onPress={() => { + scrollViewRef?.current?.scrollTo({ + y: 0, + x: index * slideItemWidth, + }); + setCurrentSlide(index); + }} + hitSlop={{ top: 8, left: 8, bottom: 8, right: 8 }} + > + <View + style={[ + styles.slideCircle, + currentSlide === index && styles.slideSolidCircle, + ]} + /> + </TouchableWithoutFeedback> + ))} + </View> + )} </View> )} </View> diff --git a/app/components/UI/WhatsNewModal/types.ts b/app/components/UI/WhatsNewModal/types.ts index e125eb09b4e..cf41881ee6a 100644 --- a/app/components/UI/WhatsNewModal/types.ts +++ b/app/components/UI/WhatsNewModal/types.ts @@ -5,6 +5,14 @@ interface SlideImage { image: ImageSourcePropType; } +interface SlideImages { + type: 'image'; + images: { + light: ImageSourcePropType; + dark: ImageSourcePropType; + }; +} + interface SlideTitle { type: 'title'; title: string; @@ -26,16 +34,19 @@ interface SlideButton { type SlideContentType = | SlideImage + | SlideImages | SlideTitle | SlideDescription | SlideButton; type WhatsNewSlides = SlideContentType[][]; +type VersionString = `${number}.${number}.${number}`; + export interface WhatsNew { onlyUpdates: boolean; - maxLastAppVersion: string; - minAppVersion: string; + maxLastAppVersion: VersionString; + minAppVersion: VersionString; /** * Slides utilizes a templating system in the form of a 2D array, which is eventually rendered within app/components/UI/WhatsNewModal/index.js. * The root layer determines the number of slides. Ex. To display 3 slides, the root layer should contain 3 arrays. diff --git a/app/components/UI/WhatsNewModal/whatsNewList.ts b/app/components/UI/WhatsNewModal/whatsNewList.ts index fa6fb04f4d0..1006241e922 100644 --- a/app/components/UI/WhatsNewModal/whatsNewList.ts +++ b/app/components/UI/WhatsNewModal/whatsNewList.ts @@ -1,10 +1,38 @@ /* eslint-disable import/prefer-default-export */ +import Routes from '../../../constants/navigation/Routes'; +import { strings } from '../../../../locales/i18n'; import { WhatsNew } from './types'; export const whatsNew: WhatsNew = { - // All users that have <1.0.7 and are updating to >=1.0.7 should see - onlyUpdates: true, // Only users who updated the app will see this, not newly installs - maxLastAppVersion: '1.0.7', // Only users who had a previous version <1.0.7 version will see this - minAppVersion: '1.0.7', // Only users who updated to a version >= 1.0.7 will see this - slides: [[], [], []], + // All users that have <5.3.0 and are updating to >=5.3.0 should see + onlyUpdates: false, // Only users who updated the app will see this, not newly installs + maxLastAppVersion: '5.3.0', // Only users who had a previous version <5.3.0 version will see this + minAppVersion: '5.3.0', // Only users who updated to a version >=5.3.0 will see this + slides: [ + [ + { + type: 'image', + /* eslint-disable import/no-commonjs, @typescript-eslint/no-require-imports */ + images: { + light: require('../../../images/whats_new_onramp_agg_light.png'), + dark: require('../../../images/whats_new_onramp_agg_dark.png'), + }, + }, + { + type: 'title', + title: strings('whats_new.feature_on_ramp_title'), + }, + { + type: 'description', + description: strings('whats_new.feature_on_ramp_text'), + }, + { + type: 'button', + buttonType: 'blue', + buttonText: strings('whats_new.feature_on_ramp_button'), + onPress: ({ navigation }) => + navigation.navigate(Routes.FIAT_ON_RAMP_AGGREGATOR.ID), + }, + ], + ], }; diff --git a/app/images/whats_new_onramp_agg_dark.png b/app/images/whats_new_onramp_agg_dark.png new file mode 100644 index 00000000000..3eb1430ff18 Binary files /dev/null and b/app/images/whats_new_onramp_agg_dark.png differ diff --git a/app/images/whats_new_onramp_agg_dark@2x.png b/app/images/whats_new_onramp_agg_dark@2x.png new file mode 100644 index 00000000000..3aadc02aa44 Binary files /dev/null and b/app/images/whats_new_onramp_agg_dark@2x.png differ diff --git a/app/images/whats_new_onramp_agg_dark@3x.png b/app/images/whats_new_onramp_agg_dark@3x.png new file mode 100644 index 00000000000..ffd01030254 Binary files /dev/null and b/app/images/whats_new_onramp_agg_dark@3x.png differ diff --git a/app/images/whats_new_onramp_agg_light.png b/app/images/whats_new_onramp_agg_light.png new file mode 100644 index 00000000000..3f818eebe6a Binary files /dev/null and b/app/images/whats_new_onramp_agg_light.png differ diff --git a/app/images/whats_new_onramp_agg_light@2x.png b/app/images/whats_new_onramp_agg_light@2x.png new file mode 100644 index 00000000000..31ccae5f5a1 Binary files /dev/null and b/app/images/whats_new_onramp_agg_light@2x.png differ diff --git a/app/images/whats_new_onramp_agg_light@3x.png b/app/images/whats_new_onramp_agg_light@3x.png new file mode 100644 index 00000000000..a65453e0cdc Binary files /dev/null and b/app/images/whats_new_onramp_agg_light@3x.png differ diff --git a/locales/languages/en.json b/locales/languages/en.json index 50ce7ffa59b..e2d5527f1ee 100644 --- a/locales/languages/en.json +++ b/locales/languages/en.json @@ -1859,9 +1859,9 @@ }, "whats_new": { "title": "See what's new", - "feature_security_settings_title": "Improved security settings", - "feature_security_settings_text": "You can now change your MetaMask app password & review how to backup your wallet Secret Recovery Phrase from Settings > Security & Privacy.", - "feature_security_settings_button": "View in Settings" + "feature_on_ramp_title": "Buy Crypto improvements", + "feature_on_ramp_text": "Our updated Buy Crypto feature aggregates quotes from multiple providers to help you find the best deal for your region and payment method.", + "feature_on_ramp_button": "Try it out" }, "invalid_network": { "title": "The chain ID for custom network \n %{network} \n has to be re-entered.",