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 0000000000..763d1cf674
Binary files /dev/null and b/public/img/loading.png differ
diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx
index 7eaec5f04a..b7c10e4c95 100644
--- a/src/view/com/composer/ExternalEmbed.tsx
+++ b/src/view/com/composer/ExternalEmbed.tsx
@@ -8,7 +8,7 @@ import {
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {BlurView} from '@react-native-community/blur'
import LinearGradient from 'react-native-linear-gradient'
-import {AutoSizedImage} from '../util/images/AutoSizedImage'
+import {Image} from '../util/images/Image'
import {Text} from '../util/text/Text'
import {s, gradients} from '../../lib/styles'
import {usePalette} from '../../lib/hooks/usePalette'
@@ -38,10 +38,7 @@ export const ExternalEmbed = ({
) : 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 (
-
+