Image performance improvements (#126)
* Switch out most images for FastImage * Add image loading placeholders * Fix tests
This commit is contained in:
@@ -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 = ({
|
||||
<ActivityIndicator size="large" style={styles.spinner} />
|
||||
</View>
|
||||
) : link.localThumb ? (
|
||||
<AutoSizedImage
|
||||
uri={link.localThumb.path}
|
||||
containerStyle={styles.image}
|
||||
/>
|
||||
<Image uri={link.localThumb.path} style={styles.image} />
|
||||
) : (
|
||||
<LinearGradient
|
||||
colors={[gradients.blueDark.start, gradients.blueDark.end]}
|
||||
@@ -98,7 +95,7 @@ const styles = StyleSheet.create({
|
||||
borderTopLeftRadius: 6,
|
||||
borderTopRightRadius: 6,
|
||||
width: '100%',
|
||||
height: 200,
|
||||
maxHeight: 200,
|
||||
},
|
||||
imageFallback: {
|
||||
height: 160,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {Image, StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import {colors} from '../../lib/styles'
|
||||
|
||||
export const SelectedPhoto = ({
|
||||
@@ -42,7 +43,7 @@ export const SelectedPhoto = ({
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Image
|
||||
<FastImage
|
||||
testID="selectedPhotoImage"
|
||||
style={[styles.image, imageStyle]}
|
||||
source={{uri: item}}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, StyleProp, View, ViewStyle, Image} from 'react-native'
|
||||
import {
|
||||
StyleSheet,
|
||||
StyleProp,
|
||||
View,
|
||||
ViewStyle,
|
||||
Image as RNImage,
|
||||
} from 'react-native'
|
||||
import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import {Link} from '../util/Link'
|
||||
import {Text} from './text/Text'
|
||||
import {AutoSizedImage} from './images/AutoSizedImage'
|
||||
import {Image} from './images/Image'
|
||||
import {ImageLayoutGrid} from './images/ImageLayoutGrid'
|
||||
import {ImagesLightbox} from '../../../state/models/shell-ui'
|
||||
import {useStores} from '../../../state'
|
||||
@@ -37,11 +43,11 @@ export function PostEmbeds({
|
||||
}
|
||||
const onPressIn = (index: number) => {
|
||||
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 (
|
||||
<View style={[styles.imagesContainer, style]}>
|
||||
<AutoSizedImage
|
||||
<Image
|
||||
uri={embed.images[0].thumb}
|
||||
onPress={() => openLightbox(0)}
|
||||
onLongPress={() => onLongPress(0)}
|
||||
onPressIn={() => onPressIn(0)}
|
||||
containerStyle={styles.singleImage}
|
||||
style={styles.singleImage}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
@@ -105,7 +111,7 @@ export function PostEmbeds({
|
||||
href={link.uri}
|
||||
noFeedback>
|
||||
{link.thumb ? (
|
||||
<AutoSizedImage uri={link.thumb} containerStyle={styles.extImage} />
|
||||
<Image uri={link.thumb} style={styles.extImage} />
|
||||
) : (
|
||||
<LinearGradient
|
||||
colors={[gradients.blueDark.start, gradients.blueDark.end]}
|
||||
@@ -145,6 +151,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
singleImage: {
|
||||
borderRadius: 8,
|
||||
maxHeight: 500,
|
||||
},
|
||||
extOuter: {
|
||||
borderWidth: 1,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react'
|
||||
import {Image, StyleSheet, View} from 'react-native'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import Svg, {Circle, Text, Defs, LinearGradient, Stop} from 'react-native-svg'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import {
|
||||
openCamera,
|
||||
openCropper,
|
||||
@@ -102,13 +103,16 @@ export function UserAvatar({
|
||||
bottomOffset={-10}
|
||||
menuWidth={170}>
|
||||
{avatar ? (
|
||||
<Image
|
||||
<FastImage
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: Math.floor(size / 2),
|
||||
}}
|
||||
source={{uri: avatar}}
|
||||
source={{
|
||||
uri: avatar,
|
||||
priority: FastImage.priority.high,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
renderSvg(size, initials)
|
||||
@@ -122,10 +126,10 @@ export function UserAvatar({
|
||||
</View>
|
||||
</DropdownButton>
|
||||
) : avatar ? (
|
||||
<Image
|
||||
<FastImage
|
||||
style={{width: size, height: size, borderRadius: Math.floor(size / 2)}}
|
||||
resizeMode="stretch"
|
||||
source={{uri: avatar}}
|
||||
source={{uri: avatar, priority: FastImage.priority.high}}
|
||||
/>
|
||||
) : (
|
||||
renderSvg(size, initials)
|
||||
|
||||
@@ -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 ? (
|
||||
<Image style={styles.bannerImage} source={{uri: banner}} />
|
||||
<FastImage style={styles.bannerImage} source={{uri: banner}} />
|
||||
) : (
|
||||
renderSvg()
|
||||
)}
|
||||
@@ -108,7 +109,7 @@ export function UserBanner({
|
||||
</View>
|
||||
</DropdownButton>
|
||||
) : banner ? (
|
||||
<Image
|
||||
<FastImage
|
||||
style={styles.bannerImage}
|
||||
resizeMode="cover"
|
||||
source={{uri: banner}}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {
|
||||
Image,
|
||||
ImageStyle,
|
||||
LayoutChangeEvent,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {Text} from '../text/Text'
|
||||
import {useTheme} from '../../../lib/ThemeContext'
|
||||
import {usePalette} from '../../../lib/hooks/usePalette'
|
||||
import {DELAY_PRESS_IN} from './constants'
|
||||
|
||||
const MAX_HEIGHT = 300
|
||||
|
||||
interface Dim {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export function AutoSizedImage({
|
||||
uri,
|
||||
onPress,
|
||||
onLongPress,
|
||||
onPressIn,
|
||||
style,
|
||||
containerStyle,
|
||||
}: {
|
||||
uri: string
|
||||
onPress?: () => void
|
||||
style?: StyleProp<ImageStyle>
|
||||
containerStyle?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const errPal = usePalette('error')
|
||||
const [error, setError] = useState<string | undefined>('')
|
||||
const [imgInfo, setImgInfo] = useState<Dim | undefined>()
|
||||
const [containerInfo, setContainerInfo] = useState<Dim | undefined>()
|
||||
|
||||
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<ViewStyle> | 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 (
|
||||
<View style={style}>
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
onPressIn={onPressIn}
|
||||
delayPressIn={DELAY_PRESS_IN}>
|
||||
{error ? (
|
||||
<View style={[styles.errorContainer, errPal.view, containerStyle]}>
|
||||
<Text style={errPal.text}>{error}</Text>
|
||||
</View>
|
||||
) : calculatedStyle ? (
|
||||
<View style={[styles.container, containerStyle]}>
|
||||
<Image style={calculatedStyle} source={{uri}} />
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
style,
|
||||
styles.placeholder,
|
||||
{backgroundColor: theme.palette.default.backgroundLight},
|
||||
]}
|
||||
onLayout={onLayout}
|
||||
/>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
placeholder: {
|
||||
width: '100%',
|
||||
aspectRatio: 1,
|
||||
},
|
||||
errorContainer: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
container: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
})
|
||||
@@ -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<ViewStyle>
|
||||
}) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
onPressIn={onPressIn}
|
||||
delayPressIn={DELAY_PRESS_IN}
|
||||
style={[styles.container, style]}>
|
||||
<FastImage style={styles.image} source={{uri}} defaultSource={LOADING} />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
aspectRatio: 1,
|
||||
},
|
||||
})
|
||||
@@ -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({
|
||||
<View style={[styles.flexRow, style]}>
|
||||
{uris.map((uri, i) => (
|
||||
<TouchableWithoutFeedback key={i} onPress={() => onPress?.(i)}>
|
||||
<Image source={{uri}} style={styles.image} />
|
||||
<FastImage source={{uri}} style={styles.image} />
|
||||
</TouchableWithoutFeedback>
|
||||
))}
|
||||
</View>
|
||||
|
||||
@@ -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<ImageStyle>(() => {
|
||||
const size1 = React.useMemo<StyleProp<FastImageStyle>>(() => {
|
||||
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<ImageStyle>(() => {
|
||||
const size2 = React.useMemo<StyleProp<FastImageStyle>>(() => {
|
||||
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)}>
|
||||
<Image source={{uri: uris[0]}} style={size1} />
|
||||
<FastImage
|
||||
source={{uri: uris[0]}}
|
||||
style={size1}
|
||||
defaultSource={LOADING}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.wSpace} />
|
||||
<TouchableOpacity
|
||||
@@ -108,7 +112,11 @@ function ImageLayoutGridInner({
|
||||
onPress={() => onPress?.(1)}
|
||||
onPressIn={() => onPressIn?.(1)}
|
||||
onLongPress={() => onLongPress(1)}>
|
||||
<Image source={{uri: uris[1]}} style={size1} />
|
||||
<FastImage
|
||||
source={{uri: uris[1]}}
|
||||
style={size1}
|
||||
defaultSource={LOADING}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
@@ -121,7 +129,11 @@ function ImageLayoutGridInner({
|
||||
onPress={() => onPress?.(0)}
|
||||
onPressIn={() => onPressIn?.(0)}
|
||||
onLongPress={() => onLongPress(0)}>
|
||||
<Image source={{uri: uris[0]}} style={size2} />
|
||||
<FastImage
|
||||
source={{uri: uris[0]}}
|
||||
style={size2}
|
||||
defaultSource={LOADING}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.wSpace} />
|
||||
<View>
|
||||
@@ -130,7 +142,11 @@ function ImageLayoutGridInner({
|
||||
onPress={() => onPress?.(1)}
|
||||
onPressIn={() => onPressIn?.(1)}
|
||||
onLongPress={() => onLongPress(1)}>
|
||||
<Image source={{uri: uris[1]}} style={size1} />
|
||||
<FastImage
|
||||
source={{uri: uris[1]}}
|
||||
style={size1}
|
||||
defaultSource={LOADING}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.hSpace} />
|
||||
<TouchableOpacity
|
||||
@@ -138,7 +154,11 @@ function ImageLayoutGridInner({
|
||||
onPress={() => onPress?.(2)}
|
||||
onPressIn={() => onPressIn?.(2)}
|
||||
onLongPress={() => onLongPress(2)}>
|
||||
<Image source={{uri: uris[2]}} style={size1} />
|
||||
<FastImage
|
||||
source={{uri: uris[2]}}
|
||||
style={size1}
|
||||
defaultSource={LOADING}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -153,7 +173,11 @@ function ImageLayoutGridInner({
|
||||
onPress={() => onPress?.(0)}
|
||||
onPressIn={() => onPressIn?.(0)}
|
||||
onLongPress={() => onLongPress(0)}>
|
||||
<Image source={{uri: uris[0]}} style={size1} />
|
||||
<FastImage
|
||||
source={{uri: uris[0]}}
|
||||
style={size1}
|
||||
defaultSource={LOADING}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.hSpace} />
|
||||
<TouchableOpacity
|
||||
@@ -161,7 +185,11 @@ function ImageLayoutGridInner({
|
||||
onPress={() => onPress?.(2)}
|
||||
onPressIn={() => onPressIn?.(2)}
|
||||
onLongPress={() => onLongPress(2)}>
|
||||
<Image source={{uri: uris[2]}} style={size1} />
|
||||
<FastImage
|
||||
source={{uri: uris[2]}}
|
||||
style={size1}
|
||||
defaultSource={LOADING}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.wSpace} />
|
||||
@@ -171,7 +199,11 @@ function ImageLayoutGridInner({
|
||||
onPress={() => onPress?.(1)}
|
||||
onPressIn={() => onPressIn?.(1)}
|
||||
onLongPress={() => onLongPress(1)}>
|
||||
<Image source={{uri: uris[1]}} style={size1} />
|
||||
<FastImage
|
||||
source={{uri: uris[1]}}
|
||||
style={size1}
|
||||
defaultSource={LOADING}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.hSpace} />
|
||||
<TouchableOpacity
|
||||
@@ -179,7 +211,11 @@ function ImageLayoutGridInner({
|
||||
onPress={() => onPress?.(3)}
|
||||
onPressIn={() => onPressIn?.(3)}
|
||||
onLongPress={() => onLongPress(3)}>
|
||||
<Image source={{uri: uris[3]}} style={size1} />
|
||||
<FastImage
|
||||
source={{uri: uris[3]}}
|
||||
style={size1}
|
||||
defaultSource={LOADING}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Image source={CLOUD_SPLASH} style={styles.bgImg} />
|
||||
<FastImage source={CLOUD_SPLASH as FISource} style={styles.bgImg} />
|
||||
<SafeAreaView testID="noSessionView" style={styles.container}>
|
||||
<ErrorBoundary>
|
||||
<SigninOrCreateAccount
|
||||
|
||||
Reference in New Issue
Block a user