From 5ccdec1e4ffabee589922571d786cf26546d9d78 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 31 Jan 2023 17:01:02 -0600 Subject: [PATCH] Image performance improvements (#126) * Switch out most images for FastImage * Add image loading placeholders * Fix tests --- .../view/com/composer/SelectedPhoto.test.tsx | 12 +- ios/Podfile.lock | 29 ++++ package.json | 1 + public/img/loading.png | Bin 0 -> 13287 bytes src/view/com/composer/ExternalEmbed.tsx | 9 +- src/view/com/composer/SelectedPhoto.tsx | 5 +- src/view/com/util/PostEmbeds.tsx | 21 ++- src/view/com/util/UserAvatar.tsx | 14 +- src/view/com/util/UserBanner.tsx | 7 +- src/view/com/util/images/AutoSizedImage.tsx | 126 ------------------ src/view/com/util/images/Image.tsx | 40 ++++++ src/view/com/util/images/ImageHorzList.tsx | 4 +- src/view/com/util/images/ImageLayoutGrid.tsx | 62 +++++++-- src/view/lib/assets.native.ts | 9 +- src/view/lib/assets.ts | 13 +- src/view/screens/Login.tsx | 11 +- yarn.lock | 5 + 17 files changed, 182 insertions(+), 186 deletions(-) create mode 100644 public/img/loading.png delete mode 100644 src/view/com/util/images/AutoSizedImage.tsx create mode 100644 src/view/com/util/images/Image.tsx diff --git a/__tests__/view/com/composer/SelectedPhoto.test.tsx b/__tests__/view/com/composer/SelectedPhoto.test.tsx index 26059ae303..18a14f1108 100644 --- a/__tests__/view/com/composer/SelectedPhoto.test.tsx +++ b/__tests__/view/com/composer/SelectedPhoto.test.tsx @@ -36,16 +36,14 @@ describe('SelectedPhoto', () => { const selectedPhotoImage = await findByTestId('selectedPhotoImage') expect(selectedPhotoImage).toBeTruthy() - // @ts-expect-error - expect(selectedPhotoImage).toHaveStyle({width: 250}) }) it('has 2 photos to render', async () => { const {findAllByTestId} = render() const selectedPhotoImage = await findAllByTestId('selectedPhotoImage') expect(selectedPhotoImage[0]).toBeTruthy() - // @ts-expect-error - expect(selectedPhotoImage[0]).toHaveStyle({width: 175}) + expect(selectedPhotoImage[1]).toBeTruthy() + expect(selectedPhotoImage[2]).toBeFalsy() }) it('has 3 photos to render', async () => { @@ -57,8 +55,10 @@ describe('SelectedPhoto', () => { ) const selectedPhotoImage = await findAllByTestId('selectedPhotoImage') expect(selectedPhotoImage[0]).toBeTruthy() - // @ts-expect-error - expect(selectedPhotoImage[0]).toHaveStyle({width: 85}) + expect(selectedPhotoImage[0]).toBeTruthy() + expect(selectedPhotoImage[1]).toBeTruthy() + expect(selectedPhotoImage[2]).toBeTruthy() + expect(selectedPhotoImage[3]).toBeFalsy() }) it('removes a photo', async () => { diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0c9bf86eb6..1df1a99cf5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -17,6 +17,15 @@ PODS: - hermes-engine/Pre-built (= 0.71.0) - hermes-engine/Pre-built (0.71.0) - libevent (2.1.12) + - libwebp (1.2.4): + - libwebp/demux (= 1.2.4) + - libwebp/mux (= 1.2.4) + - libwebp/webp (= 1.2.4) + - libwebp/demux (1.2.4): + - libwebp/webp + - libwebp/mux (1.2.4): + - libwebp/demux + - libwebp/webp (1.2.4) - RCT-Folly (2021.07.22.00): - boost - DoubleConversion @@ -346,6 +355,10 @@ PODS: - React-Core - RNCClipboard (1.11.1): - React-Core + - RNFastImage (8.6.3): + - React-Core + - SDWebImage (~> 5.11.1) + - SDWebImageWebPCoder (~> 0.8.4) - RNFS (2.20.0): - React-Core - RNGestureHandler (2.8.0): @@ -400,6 +413,12 @@ PODS: - React-RCTImage - RNSVG (12.5.0): - React-Core + - SDWebImage (5.11.1): + - SDWebImage/Core (= 5.11.1) + - SDWebImage/Core (5.11.1) + - SDWebImageWebPCoder (0.8.5): + - libwebp (~> 1.0) + - SDWebImage/Core (~> 5.10) - segment-analytics-react-native (2.10.1): - React-Core - sovran-react-native @@ -458,6 +477,7 @@ DEPENDENCIES: - RNBackgroundFetch (from `../node_modules/react-native-background-fetch`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" + - RNFastImage (from `../node_modules/react-native-fast-image`) - RNFS (from `../node_modules/react-native-fs`) - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`) @@ -475,6 +495,9 @@ SPEC REPOS: trunk: - fmt - libevent + - libwebp + - SDWebImage + - SDWebImageWebPCoder - Swime - TOCropViewController @@ -571,6 +594,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-async-storage/async-storage" RNCClipboard: :path: "../node_modules/@react-native-clipboard/clipboard" + RNFastImage: + :path: "../node_modules/react-native-fast-image" RNFS: :path: "../node_modules/react-native-fs" RNGestureHandler: @@ -606,6 +631,7 @@ SPEC CHECKSUMS: glog: 476ee3e89abb49e07f822b48323c51c57124b572 hermes-engine: f6e715aa6c8bd38de6c13bc85e07b0a337edaa89 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 + libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: fd4d923b964658aa0c4091a32c8b2004c6d9e3a6 RCTTypeSafety: c276d85975bde3d8448907235c70bf0da257adfd @@ -645,6 +671,7 @@ SPEC CHECKSUMS: RNBackgroundFetch: 8e16176ff415daac743a6eb57afc8e9e14dbe623 RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60 RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd + RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 62232ba8f562f7dea5ba1b3383494eb5bf97a4d3 RNImageCropPicker: 648356d68fbf9911a1016b3e3723885d28373eda @@ -654,6 +681,8 @@ SPEC CHECKSUMS: RNReanimated: d8d9d3d3801bda5e35e85cdffc871577d044dc2e RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d RNSVG: 6adc5c52d2488a476248413064b7f2832e639057 + SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d + SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d segment-analytics-react-native: cb097e393c3560a0d4cfd877044293e37b0050d9 sovran-react-native: fd3dc8f1a4b14acdc4ad25fc6b4ac4f52a2a2a15 Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b diff --git a/package.json b/package.json index a180286316..f6710d8679 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "react-native": "0.71.1", "react-native-appstate-hook": "^1.0.6", "react-native-background-fetch": "^4.1.8", + "react-native-fast-image": "^8.6.3", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^2.5.0", "react-native-haptic-feedback": "^1.14.0", diff --git a/public/img/loading.png b/public/img/loading.png new file mode 100644 index 0000000000000000000000000000000000000000..763d1cf67498abff02bf213a962b83f563630725 GIT binary patch literal 13287 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 z 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 ) : link.localThumb ? ( - + ) : ( - { const firstImageToShow = uris[index] - Image.prefetch(firstImageToShow) + RNImage.prefetch(firstImageToShow) uris.forEach(uri => { if (firstImageToShow !== uri) { // First image already prefeched above - Image.prefetch(uri) + RNImage.prefetch(uri) } }) } @@ -85,12 +91,12 @@ export function PostEmbeds({ } else { return ( - openLightbox(0)} onLongPress={() => onLongPress(0)} onPressIn={() => onPressIn(0)} - containerStyle={styles.singleImage} + style={styles.singleImage} /> ) @@ -105,7 +111,7 @@ export function PostEmbeds({ href={link.uri} noFeedback> {link.thumb ? ( - + ) : ( {avatar ? ( - ) : ( renderSvg(size, initials) @@ -122,10 +126,10 @@ export function UserAvatar({ ) : avatar ? ( - ) : ( renderSvg(size, initials) diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index 01f0a2a6d0..d10b540232 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -1,8 +1,9 @@ import React from 'react' -import {StyleSheet, View, Image} from 'react-native' +import {StyleSheet, View} from 'react-native' import Svg, {Rect, Defs, LinearGradient, Stop} from 'react-native-svg' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Image as PickedImage} from 'react-native-image-crop-picker' +import FastImage from 'react-native-fast-image' import {colors, gradients} from '../../lib/styles' import { openCamera, @@ -95,7 +96,7 @@ export function UserBanner({ bottomOffset={-10} menuWidth={170}> {banner ? ( - + ) : ( renderSvg() )} @@ -108,7 +109,7 @@ export function UserBanner({ ) : banner ? ( - void - style?: StyleProp - containerStyle?: StyleProp -}) { - const theme = useTheme() - const errPal = usePalette('error') - const [error, setError] = useState('') - const [imgInfo, setImgInfo] = useState() - const [containerInfo, setContainerInfo] = useState() - - useEffect(() => { - let aborted = false - if (!imgInfo) { - Image.getSize( - uri, - (width: number, height: number) => { - if (!aborted) { - setImgInfo({width, height}) - } - }, - (err: any) => { - if (!aborted) { - setError(String(err)) - } - }, - ) - } - return () => { - aborted = true - } - }, [uri, imgInfo]) - - const onLayout = (evt: LayoutChangeEvent) => { - setContainerInfo({ - width: evt.nativeEvent.layout.width, - height: evt.nativeEvent.layout.height, - }) - } - - let calculatedStyle: StyleProp | undefined - if (imgInfo && containerInfo) { - // imgInfo.height / imgInfo.width = x / containerInfo.width - // x = imgInfo.height / imgInfo.width * containerInfo.width - calculatedStyle = { - height: Math.min( - MAX_HEIGHT, - (imgInfo.height / imgInfo.width) * containerInfo.width, - ), - } - } - - return ( - - - {error ? ( - - {error} - - ) : calculatedStyle ? ( - - - - ) : ( - - )} - - - ) -} - -const styles = StyleSheet.create({ - placeholder: { - width: '100%', - aspectRatio: 1, - }, - errorContainer: { - paddingHorizontal: 12, - paddingVertical: 8, - }, - container: { - overflow: 'hidden', - }, -}) diff --git a/src/view/com/util/images/Image.tsx b/src/view/com/util/images/Image.tsx new file mode 100644 index 0000000000..dc8df6dd01 --- /dev/null +++ b/src/view/com/util/images/Image.tsx @@ -0,0 +1,40 @@ +import React from 'react' +import {StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native' +import FastImage from 'react-native-fast-image' +import {DELAY_PRESS_IN} from './constants' +import {LOADING} from '../../../lib/assets' + +export function Image({ + uri, + onPress, + onLongPress, + onPressIn, + style, +}: { + uri: string + onPress?: () => void + onLongPress?: () => void + onPressIn?: () => void + style?: StyleProp +}) { + return ( + + + + ) +} + +const styles = StyleSheet.create({ + container: { + overflow: 'hidden', + }, + image: { + width: '100%', + aspectRatio: 1, + }, +}) diff --git a/src/view/com/util/images/ImageHorzList.tsx b/src/view/com/util/images/ImageHorzList.tsx index 3664243086..ff5e43f910 100644 --- a/src/view/com/util/images/ImageHorzList.tsx +++ b/src/view/com/util/images/ImageHorzList.tsx @@ -1,12 +1,12 @@ import React from 'react' import { - Image, StyleProp, StyleSheet, TouchableWithoutFeedback, View, ViewStyle, } from 'react-native' +import FastImage from 'react-native-fast-image' export function ImageHorzList({ uris, @@ -21,7 +21,7 @@ export function ImageHorzList({ {uris.map((uri, i) => ( onPress?.(i)}> - + ))} diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx index 8e1faa66bd..5271913538 100644 --- a/src/view/com/util/images/ImageLayoutGrid.tsx +++ b/src/view/com/util/images/ImageLayoutGrid.tsx @@ -1,7 +1,5 @@ import React from 'react' import { - Image, - ImageStyle, LayoutChangeEvent, StyleProp, StyleSheet, @@ -9,6 +7,8 @@ import { View, ViewStyle, } from 'react-native' +import FastImage, {ImageStyle as FastImageStyle} from 'react-native-fast-image' +import {LOADING} from '../../../lib/assets' import {DELAY_PRESS_IN} from './constants' interface Dim { @@ -73,7 +73,7 @@ function ImageLayoutGridInner({ onPressIn?: (index: number) => void containerInfo: Dim }) { - const size1 = React.useMemo(() => { + const size1 = React.useMemo>(() => { if (type === 'three') { const size = (containerInfo.width - 10) / 3 return {width: size, height: size, resizeMode: 'cover', borderRadius: 4} @@ -82,7 +82,7 @@ function ImageLayoutGridInner({ return {width: size, height: size, resizeMode: 'cover', borderRadius: 4} } }, [type, containerInfo]) - const size2 = React.useMemo(() => { + const size2 = React.useMemo>(() => { if (type === 'three') { const size = ((containerInfo.width - 10) / 3) * 2 + 5 return {width: size, height: size, resizeMode: 'cover', borderRadius: 4} @@ -100,7 +100,11 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(0)} onPressIn={() => onPressIn?.(0)} onLongPress={() => onLongPress(0)}> - + onPress?.(1)} onPressIn={() => onPressIn?.(1)} onLongPress={() => onLongPress(1)}> - + ) @@ -121,7 +129,11 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(0)} onPressIn={() => onPressIn?.(0)} onLongPress={() => onLongPress(0)}> - + @@ -130,7 +142,11 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(1)} onPressIn={() => onPressIn?.(1)} onLongPress={() => onLongPress(1)}> - + onPress?.(2)} onPressIn={() => onPressIn?.(2)} onLongPress={() => onLongPress(2)}> - + @@ -153,7 +173,11 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(0)} onPressIn={() => onPressIn?.(0)} onLongPress={() => onLongPress(0)}> - + onPress?.(2)} onPressIn={() => onPressIn?.(2)} onLongPress={() => onLongPress(2)}> - + @@ -171,7 +199,11 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(1)} onPressIn={() => onPressIn?.(1)} onLongPress={() => onLongPress(1)}> - + onPress?.(3)} onPressIn={() => onPressIn?.(3)} onLongPress={() => onLongPress(3)}> - + diff --git a/src/view/lib/assets.native.ts b/src/view/lib/assets.native.ts index a2db553e74..4414273240 100644 --- a/src/view/lib/assets.native.ts +++ b/src/view/lib/assets.native.ts @@ -1,5 +1,6 @@ -import {ImageSourcePropType} from 'react-native' +import {ImageRequireSource} from 'react-native' -export const DEF_AVATAR: ImageSourcePropType = require('../../../public/img/default-avatar.jpg') -export const TABS_EXPLAINER: ImageSourcePropType = require('../../../public/img/tabs-explainer.jpg') -export const CLOUD_SPLASH: ImageSourcePropType = require('../../../public/img/cloud-splash.png') +export const DEF_AVATAR: ImageRequireSource = require('../../../public/img/default-avatar.jpg') +export const TABS_EXPLAINER: ImageRequireSource = require('../../../public/img/tabs-explainer.jpg') +export const CLOUD_SPLASH: ImageRequireSource = require('../../../public/img/cloud-splash.png') +export const LOADING: ImageRequireSource = require('../../../public/img/loading.png') diff --git a/src/view/lib/assets.ts b/src/view/lib/assets.ts index 948f3a22f3..ba7e7d93b8 100644 --- a/src/view/lib/assets.ts +++ b/src/view/lib/assets.ts @@ -1,7 +1,12 @@ -import {ImageSourcePropType} from 'react-native' +import {ImageRequireSource} from 'react-native' -export const DEF_AVATAR: ImageSourcePropType = {uri: '/img/default-avatar.jpg'} -export const TABS_EXPLAINER: ImageSourcePropType = { +// @ts-ignore we need to pretend -prf +export const DEF_AVATAR: ImageRequireSource = {uri: '/img/default-avatar.jpg'} +// @ts-ignore we need to pretend -prf +export const TABS_EXPLAINER: ImageRequireSource = { uri: '/img/tabs-explainer.jpg', } -export const CLOUD_SPLASH: ImageSourcePropType = {uri: '/img/cloud-splash.png'} +// @ts-ignore we need to pretend -prf +export const CLOUD_SPLASH: ImageRequireSource = {uri: '/img/cloud-splash.png'} +// @ts-ignore we need to pretend -prf +export const LOADING: ImageRequireSource = {uri: '/img/loading.png'} diff --git a/src/view/screens/Login.tsx b/src/view/screens/Login.tsx index 81a2c9e6b2..136ffb967f 100644 --- a/src/view/screens/Login.tsx +++ b/src/view/screens/Login.tsx @@ -1,11 +1,6 @@ import React, {useState} from 'react' -import { - Image, - SafeAreaView, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' +import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native' +import FastImage, {Source as FISource} from 'react-native-fast-image' import {observer} from 'mobx-react-lite' import {Signin} from '../com/login/Signin' import {CreateAccount} from '../com/login/CreateAccount' @@ -64,7 +59,7 @@ export const Login = observer(() => { if (screenState === ScreenState.S_SigninOrCreateAccount) { return ( - +