Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions packages/mobile/src/components/image/CoverPhoto.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import type { ID, Nullable } from '@audius/common'
import { SquareSizes, WidthSizes, cacheUsersSelectors } from '@audius/common'
import { BlurView } from '@react-native-community/blur'
import { Animated, StyleSheet } from 'react-native'
import { useSelector } from 'react-redux'

import imageCoverPhotoBlank from 'app/assets/images/imageCoverPhotoBlank.jpg'
import type { ContentNodeImageSource } from 'app/hooks/useContentNodeImage'
import { useContentNodeImage } from 'app/hooks/useContentNodeImage'

import type { FastImageProps } from './FastImage'
import { FastImage } from './FastImage'
import { useProfilePicture } from './UserImage'

const { getUser } = cacheUsersSelectors

const AnimatedBlurView = Animated.createAnimatedComponent(BlurView)

const interpolateBlurViewOpacity = (scrollY: Animated.Value) =>
scrollY.interpolate({
inputRange: [-100, 0],
outputRange: [1, 0],
extrapolateLeft: 'extend',
extrapolateRight: 'clamp'
})

const interpolateImageScale = (animatedValue: Animated.Value) =>
animatedValue.interpolate({
inputRange: [-200, 0],
outputRange: [4, 1],
extrapolateLeft: 'extend',
extrapolateRight: 'clamp'
})

const interpolateImageTranslate = (animatedValue: Animated.Value) =>
animatedValue.interpolate({
inputRange: [-200, 0],
outputRange: [-40, 0],
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
})

export const useCoverPhoto = (
userId: Nullable<ID> = null,
size: WidthSizes = WidthSizes.SIZE_640
): ContentNodeImageSource & { shouldBlur?: boolean } => {
const cid = useSelector((state) => {
const user = getUser(state, { id: userId })
if (!user) return null
const { cover_photo_sizes, cover_photo } = user
return cover_photo_sizes || cover_photo
})

const cidMap = useSelector(
(state) => getUser(state, { id: userId })?.cover_photo_cids
)

const updatedSource = useSelector((state) => {
const user = getUser(state, { id: userId })
if (!user) return null
const { updatedProfilePicture } = user
if (!updatedProfilePicture) return null
return {
source: { uri: updatedProfilePicture.url },
handleError: () => {},
isFallbackImage: false
}
})

const coverPhotoSource = useContentNodeImage({
cid,
size,
fallbackImageSource: imageCoverPhotoBlank,
cidMap
})

const profilePictureSource = useProfilePicture(
userId,
size === WidthSizes.SIZE_640
? SquareSizes.SIZE_480_BY_480
: SquareSizes.SIZE_1000_BY_1000
)

if (updatedSource) return updatedSource

return coverPhotoSource.isFallbackImage &&
!profilePictureSource.isFallbackImage
? { ...profilePictureSource, shouldBlur: true }
: { ...coverPhotoSource, shouldBlur: false }
}

type CoverPhotoProps = {
userId: ID
animatedValue?: Animated.Value
} & Partial<FastImageProps>

export const CoverPhoto = (props: CoverPhotoProps) => {
const { userId, animatedValue, ...imageProps } = props

const { source, handleError, shouldBlur } = useCoverPhoto(userId)

return (
<Animated.View
style={
animatedValue && {
transform: [
{ scale: interpolateImageScale(animatedValue) },
{ translateY: interpolateImageTranslate(animatedValue) }
]
}
}
>
<FastImage source={source} onError={handleError} {...imageProps}>
{shouldBlur || animatedValue ? (
<AnimatedBlurView
blurType='light'
blurAmount={20}
style={[
{ ...StyleSheet.absoluteFillObject, zIndex: 2 },
animatedValue && !shouldBlur
? {
opacity: interpolateBlurViewOpacity(animatedValue)
}
: undefined
]}
/>
) : null}
</FastImage>
</Animated.View>
)
}
81 changes: 0 additions & 81 deletions packages/mobile/src/components/image/UserCoverImage.tsx

This file was deleted.

65 changes: 42 additions & 23 deletions packages/mobile/src/components/image/UserImage.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,67 @@
import type { Nullable, SquareSizes, User } from '@audius/common'
import type { ID } from '@audius/common'
import {
cacheUsersSelectors,
type Nullable,
type SquareSizes
} from '@audius/common'
import { useSelector } from 'react-redux'

import profilePicEmpty from 'app/assets/images/imageProfilePicEmpty2X.png'
import type { ContentNodeImageSource } from 'app/hooks/useContentNodeImage'
import { useContentNodeImage } from 'app/hooks/useContentNodeImage'

import type { FastImageProps } from './FastImage'
import { FastImage } from './FastImage'

const { getUser } = cacheUsersSelectors

type UseUserImageOptions = {
user: Nullable<
Pick<
User,
| 'profile_picture_sizes'
| 'profile_picture_cids'
| 'profile_picture'
| 'updatedProfilePicture'
>
>
userId: Nullable<ID>
size: SquareSizes
}

export const useUserImage = ({ user, size }: UseUserImageOptions) => {
const cid = user ? user.profile_picture_sizes || user.profile_picture : null
export const useProfilePicture = (
userId: Nullable<number>,
size: SquareSizes
): ContentNodeImageSource => {
const cid = useSelector((state) => {
const user = getUser(state, { id: userId })
if (!user) return null
const { profile_picture_sizes, profile_picture } = user
return profile_picture_sizes || profile_picture
})

const cidMap = useSelector(
(state) => getUser(state, { id: userId })?.profile_picture_cids
)

const updatedSource = useSelector((state) => {
const user = getUser(state, { id: userId })
if (!user) return null
const { updatedProfilePicture } = user
if (!updatedProfilePicture) return null
return {
source: { uri: updatedProfilePicture.url },
handleError: () => {},
isFallbackImage: false
}
})

const contentNodeImage = useContentNodeImage({
const imageSource = useContentNodeImage({
cid,
size,
fallbackImageSource: profilePicEmpty,
cidMap: user?.profile_picture_cids
cidMap
})

if (user?.updatedProfilePicture) {
return {
source: { uri: user.updatedProfilePicture.url },
handleError: () => {}
}
}
return contentNodeImage
return updatedSource ?? imageSource
}

export type UserImageProps = UseUserImageOptions & Partial<FastImageProps>

export const UserImage = (props: UserImageProps) => {
const { user, size, ...imageProps } = props
const { source, handleError } = useUserImage({ user, size })
const { userId, size, ...imageProps } = props
const { source, handleError } = useProfilePicture(userId, size)

return <FastImage {...imageProps} source={source} onError={handleError} />
}
10 changes: 7 additions & 3 deletions packages/mobile/src/components/profile-list/ProfileCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type ProfileCardProps = Partial<BaseProfileCardProps> & {

export const ProfileCard = (props: ProfileCardProps) => {
const { profile, onPress, ...other } = props
const { handle } = profile
const { user_id, handle } = profile
const navigation = useNavigation()

const handlePress = useCallback(() => {
Expand All @@ -31,9 +31,13 @@ export const ProfileCard = (props: ProfileCardProps) => {

const renderImage = useCallback(
(props: ImageProps) => (
<UserImage user={profile} size={SquareSizes.SIZE_480_BY_480} {...props} />
<UserImage
userId={user_id}
size={SquareSizes.SIZE_480_BY_480}
{...props}
/>
),
[profile]
[user_id]
)

return (
Expand Down
20 changes: 14 additions & 6 deletions packages/mobile/src/components/user/ProfilePicture.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ID } from '@audius/common'
import { SquareSizes } from '@audius/common'

import type { UserImageProps } from 'app/components/image/UserImage'
import { UserImage } from 'app/components/image/UserImage'
import { makeStyles } from 'app/styles'

Expand All @@ -19,17 +19,25 @@ const useStyles = makeStyles(({ palette }) => ({
}
}))

export type ProfilePictureProps = Partial<FastImageProps> & {
profile: UserImageProps['user']
}
export type ProfilePictureProps = Partial<FastImageProps> &
(
| {
/** @deprecated Use `userId` directly instead */
profile: { user_id: ID }
}
| {
userId: ID
}
)

export const ProfilePicture = (props: ProfilePictureProps) => {
const { profile, style: styleProp, ...other } = props
const { style: styleProp, ...other } = props
const userId = 'userId' in other ? other.userId : other.profile.user_id
const styles = useStyles()

return (
<UserImage
user={profile}
userId={userId}
size={SquareSizes.SIZE_150_BY_150}
style={[styles.profilePhoto, styleProp]}
{...other}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ProfilePicture } from 'app/components/user'
import { useRemoteVar, useFeatureFlag } from 'app/hooks/useRemoteConfig'
import { makeStyles } from 'app/styles'

const { getAccountUser } = accountSelectors
const { getUserId } = accountSelectors
const { getHasUnreadMessages } = chatSelectors

const useStyles = makeStyles(({ spacing, palette }) => ({
Expand Down Expand Up @@ -51,7 +51,7 @@ export const AccountPictureHeader = (props: AccountPictureHeaderProps) => {
const { onPress } = props
const drawerProgress = useDrawerProgress()
const styles = useStyles()
const accountUser = useSelector(getAccountUser)
const accountId = useSelector(getUserId)!
const challengeRewardIds = useRemoteVar(StringKeys.CHALLENGE_REWARD_IDS)
const hasClaimableRewards = useAccountHasClaimableRewards(challengeRewardIds)
const { isEnabled: isChatEnabled } = useFeatureFlag(FeatureFlags.CHAT_ENABLED)
Expand All @@ -71,7 +71,7 @@ export const AccountPictureHeader = (props: AccountPictureHeaderProps) => {
<Animated.View style={{ opacity }}>
<TouchableOpacity onPress={onPress}>
<ProfilePicture
profile={accountUser}
userId={accountId}
style={styles.root}
priority='high'
/>
Expand Down
Loading