diff --git a/.imgbotconfig b/.imgbotconfig index da6942061dd1..81d9a16614f9 100644 --- a/.imgbotconfig +++ b/.imgbotconfig @@ -1,7 +1,3 @@ { - "ignoredFiles": [ - "assets/images/themeDependent/empty-state_background-fade-dark.png", // Caused an issue with color gradients, https://github.com/Expensify/App/issues/30499 - "assets/images/themeDependent/empty-state_background-fade-light.png" - ], "aggressiveCompression": "false" } diff --git a/assets/images/themeDependent/empty-state_background-fade-dark.png b/assets/images/themeDependent/empty-state_background-fade-dark.png deleted file mode 100644 index 59951ef707fb..000000000000 Binary files a/assets/images/themeDependent/empty-state_background-fade-dark.png and /dev/null differ diff --git a/assets/images/themeDependent/empty-state_background-fade-dark.svg b/assets/images/themeDependent/empty-state_background-fade-dark.svg new file mode 100644 index 000000000000..1e4a7eb6b396 --- /dev/null +++ b/assets/images/themeDependent/empty-state_background-fade-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/themeDependent/empty-state_background-fade-light.png b/assets/images/themeDependent/empty-state_background-fade-light.png deleted file mode 100644 index 200996057b47..000000000000 Binary files a/assets/images/themeDependent/empty-state_background-fade-light.png and /dev/null differ diff --git a/assets/images/themeDependent/empty-state_background-fade-light.svg b/assets/images/themeDependent/empty-state_background-fade-light.svg new file mode 100644 index 000000000000..31cd9533ff71 --- /dev/null +++ b/assets/images/themeDependent/empty-state_background-fade-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 1bf42a54d2a5..8aee808c219a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,7 @@ import SafeArea from './components/SafeArea'; import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider'; import {SearchRouterContextProvider} from './components/Search/SearchRouter/SearchRouterContext'; import SidePanelContextProvider from './components/SidePanel/SidePanelContextProvider'; +import SVGDefinitionsProvider from './components/SVGDefinitionsProvider'; import ThemeIllustrationsProvider from './components/ThemeIllustrationsProvider'; import ThemeProvider from './components/ThemeProvider'; import ThemeStylesProvider from './components/ThemeStylesProvider'; @@ -91,6 +92,7 @@ function App() { ThemeProvider, ThemeStylesProvider, ThemeIllustrationsProvider, + SVGDefinitionsProvider, HTMLEngineProvider, PortalProvider, SafeArea, diff --git a/src/components/SVGDefinitionsProvider/LinearGradientEmptyStateBackground.tsx b/src/components/SVGDefinitionsProvider/LinearGradientEmptyStateBackground.tsx new file mode 100644 index 000000000000..f8d119af6d39 --- /dev/null +++ b/src/components/SVGDefinitionsProvider/LinearGradientEmptyStateBackground.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +type LinearGradientEmptyStateBackgroundProps = { + isDarkTheme?: boolean; +}; + +/** + * Global definitions for @assets/images/themeDependent/empty-state_background-fade + */ +function LinearGradientEmptyStateBackground({isDarkTheme}: LinearGradientEmptyStateBackgroundProps) { + const stopColor1 = isDarkTheme ? '#1a3d32' : '#e6e1da'; + const stopColor2 = isDarkTheme ? '#061b09' : '#fcfbf9'; + return ( + + + + + ); +} + +export default LinearGradientEmptyStateBackground; diff --git a/src/components/SVGDefinitionsProvider/index.native.tsx b/src/components/SVGDefinitionsProvider/index.native.tsx new file mode 100644 index 000000000000..1ac4aa191cfb --- /dev/null +++ b/src/components/SVGDefinitionsProvider/index.native.tsx @@ -0,0 +1,7 @@ +import type ChildrenProps from '@src/types/utils/ChildrenProps'; + +function SVGDefinitionsProvider({children}: ChildrenProps) { + return children; +} + +export default SVGDefinitionsProvider; diff --git a/src/components/SVGDefinitionsProvider/index.tsx b/src/components/SVGDefinitionsProvider/index.tsx new file mode 100644 index 000000000000..fce526b28ecd --- /dev/null +++ b/src/components/SVGDefinitionsProvider/index.tsx @@ -0,0 +1,28 @@ +import type {ReactElement} from 'react'; +import React from 'react'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import LinearGradientEmptyStateBackground from './LinearGradientEmptyStateBackground'; + +/** + * Provides global SVG definitions and helps avoid duplicated ids. + * Duplicated ids in the cause rendering issues (like missing gradients). + */ +function SVGDefinitionsProvider({children}: ChildrenProps): ReactElement | null { + return ( + <> + + + + + + + {children} + + ); +} + +SVGDefinitionsProvider.displayName = 'SVGDefinitionsProvider'; +export default SVGDefinitionsProvider; diff --git a/src/pages/home/report/AnimatedEmptyStateBackground.tsx b/src/pages/home/report/AnimatedEmptyStateBackground.tsx index 2c0dca03e54e..bba88933f164 100644 --- a/src/pages/home/report/AnimatedEmptyStateBackground.tsx +++ b/src/pages/home/report/AnimatedEmptyStateBackground.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {View} from 'react-native'; -import Animated, {clamp, SensorType, useAnimatedSensor, useAnimatedStyle, useReducedMotion, useSharedValue, withSpring} from 'react-native-reanimated'; +import Animated, {clamp, FadeIn, SensorType, useAnimatedSensor, useAnimatedStyle, useReducedMotion, useSharedValue, withSpring} from 'react-native-reanimated'; +import ImageSVG from '@components/ImageSVG'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeIllustrations from '@hooks/useThemeIllustrations'; @@ -21,8 +22,10 @@ function AnimatedEmptyStateBackground() { const {windowWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const illustrations = useThemeIllustrations(); + const illustrationWidth = CONST.EMPTY_STATE_BACKGROUND.ASPECT_RATIO * CONST.EMPTY_STATE_BACKGROUND.WIDE_SCREEN.IMAGE_HEIGHT; // or whatever your SVG's natural width is + const maxBackgroundWidth = variables.sideBarWidth + illustrationWidth; // If window width is greater than the max background width, repeat the background image - const maxBackgroundWidth = variables.sideBarWidth + CONST.EMPTY_STATE_BACKGROUND.ASPECT_RATIO * CONST.EMPTY_STATE_BACKGROUND.WIDE_SCREEN.IMAGE_HEIGHT; + const numberOfRepeats = windowWidth > maxBackgroundWidth ? Math.ceil(windowWidth / illustrationWidth) : 1; // Get data from phone rotation sensor and prep other variables for animation const animatedSensor = useAnimatedSensor(SensorType.GYROSCOPE); @@ -50,11 +53,21 @@ function AnimatedEmptyStateBackground() { return ( - maxBackgroundWidth ? 'repeat' : 'cover'} - /> + entering={FadeIn} + > + {Array.from({length: numberOfRepeats}).map((_, index) => ( + 1 ? illustrationWidth : undefined} + style={{position: 'absolute', left: index * illustrationWidth}} + preserveAspectRatio="xMidYMid slice" + /> + ))} + ); } diff --git a/src/styles/theme/illustrations/themes/dark.ts b/src/styles/theme/illustrations/themes/dark.ts index 300440960862..c3aa477846e9 100644 --- a/src/styles/theme/illustrations/themes/dark.ts +++ b/src/styles/theme/illustrations/themes/dark.ts @@ -2,7 +2,7 @@ import GenericCompanyCard from '@assets/images/companyCards/generic-dark.svg'; import GenericCSVCompanyCardLarge from '@assets/images/companyCards/large/generic-csv-dark-large.svg'; import GenericCompanyCardLarge from '@assets/images/companyCards/large/generic-dark-large.svg'; import ExpensifyApprovedLogo from '@assets/images/subscription-details__approvedlogo.svg'; -import EmptyStateBackgroundImage from '@assets/images/themeDependent/empty-state_background-fade-dark.png'; +import EmptyStateBackgroundImage from '@assets/images/themeDependent/empty-state_background-fade-dark.svg'; import ExampleCheckEN from '@assets/images/themeDependent/example-check-image-dark-en.png'; import ExampleCheckES from '@assets/images/themeDependent/example-check-image-dark-es.png'; import WorkspaceProfile from '@assets/images/workspace-profile.png'; diff --git a/src/styles/theme/illustrations/themes/light.ts b/src/styles/theme/illustrations/themes/light.ts index a4fa86d2f816..2cabb18b18b1 100644 --- a/src/styles/theme/illustrations/themes/light.ts +++ b/src/styles/theme/illustrations/themes/light.ts @@ -2,7 +2,7 @@ import GenericCompanyCard from '@assets/images/companyCards/generic-light.svg'; import GenericCSVCompanyCardLarge from '@assets/images/companyCards/large/generic-csv-light-large.svg'; import GenericCompanyCardLarge from '@assets/images/companyCards/large/generic-light-large.svg'; import ExpensifyApprovedLogo from '@assets/images/subscription-details__approvedlogo--light.svg'; -import EmptyStateBackgroundImage from '@assets/images/themeDependent/empty-state_background-fade-light.png'; +import EmptyStateBackgroundImage from '@assets/images/themeDependent/empty-state_background-fade-light.svg'; import ExampleCheckEN from '@assets/images/themeDependent/example-check-image-light-en.png'; import ExampleCheckES from '@assets/images/themeDependent/example-check-image-light-es.png'; import WorkspaceProfile from '@assets/images/workspace-profile-light.png'; diff --git a/src/styles/theme/illustrations/types.ts b/src/styles/theme/illustrations/types.ts index cd897fa4d1cc..72cbc2b55090 100644 --- a/src/styles/theme/illustrations/types.ts +++ b/src/styles/theme/illustrations/types.ts @@ -4,7 +4,7 @@ import type {SvgProps} from 'react-native-svg'; import type IconAsset from '@src/types/utils/IconAsset'; type IllustrationsType = { - EmptyStateBackgroundImage: ImageSourcePropType; + EmptyStateBackgroundImage: FC; ExampleCheckES: ImageSourcePropType; ExampleCheckEN: ImageSourcePropType; WorkspaceProfile: ImageSourcePropType; diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 3bb7c50fa39d..4b3cfaebbde2 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -861,7 +861,7 @@ function getHorizontalStackedOverlayAvatarStyle(oneAvatarSize: AvatarSize, oneAv /** * Gets the correct size for the empty state background image based on screen dimensions */ -function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean): ImageStyle { +function getReportWelcomeBackgroundImageStyle(isSmallScreenWidth: boolean): ViewStyle { if (isSmallScreenWidth) { return { position: 'absolute',