Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

memoize things, clean code, and replace deprecated resizeMode with contentFit (#526)

authored by

Ansh and committed by
GitHub
c5222db3 1b356556

+95 -78
+36 -27
src/view/com/notifications/FeedItem.tsx
··· 1 - import React from 'react' 1 + import React, {useMemo, useState, useEffect} from 'react' 2 2 import {observer} from 'mobx-react-lite' 3 3 import { 4 4 Animated, ··· 47 47 item: NotificationsFeedItemModel 48 48 }) { 49 49 const pal = usePalette('default') 50 - const [isAuthorsExpanded, setAuthorsExpanded] = React.useState<boolean>(false) 51 - const itemHref = React.useMemo(() => { 50 + const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false) 51 + const itemHref = useMemo(() => { 52 52 if (item.isLike || item.isRepost) { 53 53 const urip = new AtUri(item.subjectUri) 54 54 return `/profile/${urip.host}/post/${urip.rkey}` ··· 60 60 } 61 61 return '' 62 62 }, [item]) 63 - const itemTitle = React.useMemo(() => { 63 + const itemTitle = useMemo(() => { 64 64 if (item.isLike || item.isRepost) { 65 65 return 'Post' 66 66 } else if (item.isFollow) { ··· 74 74 setAuthorsExpanded(!isAuthorsExpanded) 75 75 } 76 76 77 + const authors: Author[] = useMemo(() => { 78 + return [ 79 + { 80 + href: `/profile/${item.author.handle}`, 81 + handle: item.author.handle, 82 + displayName: item.author.displayName, 83 + avatar: item.author.avatar, 84 + labels: item.author.labels, 85 + }, 86 + ...(item.additional?.map( 87 + ({author: {avatar, labels, handle, displayName}}) => { 88 + return { 89 + href: `/profile/${handle}`, 90 + handle, 91 + displayName, 92 + avatar, 93 + labels, 94 + } 95 + }, 96 + ) || []), 97 + ] 98 + }, [ 99 + item.additional, 100 + item.author.avatar, 101 + item.author.displayName, 102 + item.author.handle, 103 + item.author.labels, 104 + ]) 105 + 77 106 if (item.additionalPost?.notFound) { 78 107 // don't render anything if the target post was deleted or unfindable 79 108 return <View /> ··· 125 154 icon = 'user-plus' 126 155 iconStyle = [s.blue3 as FontAwesomeIconStyle] 127 156 } else { 128 - return <></> 157 + return null 129 158 } 130 - 131 - const authors: Author[] = [ 132 - { 133 - href: `/profile/${item.author.handle}`, 134 - handle: item.author.handle, 135 - displayName: item.author.displayName, 136 - avatar: item.author.avatar, 137 - labels: item.author.labels, 138 - }, 139 - ...(item.additional?.map( 140 - ({author: {avatar, labels, handle, displayName}}) => { 141 - return { 142 - href: `/profile/${handle}`, 143 - handle, 144 - displayName, 145 - avatar, 146 - labels, 147 - } 148 - }, 149 - ) || []), 150 - ] 151 159 152 160 return ( 153 161 <Link ··· 301 309 const heightStyle = { 302 310 height: Animated.multiply(heightInterp, targetHeight), 303 311 } 304 - React.useEffect(() => { 312 + useEffect(() => { 305 313 Animated.timing(heightInterp, { 306 314 toValue: visible ? 1 : 0, 307 315 duration: 200, 308 316 useNativeDriver: false, 309 317 }).start() 310 318 }, [heightInterp, visible]) 319 + 311 320 return ( 312 321 <Animated.View 313 322 style={[
+59 -51
src/view/com/util/UserAvatar.tsx
··· 1 - import React from 'react' 1 + import React, {useMemo} from 'react' 2 2 import {StyleSheet, View} from 'react-native' 3 3 import Svg, {Circle, Path} from 'react-native-svg' 4 4 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' ··· 53 53 const {requestCameraAccessIfNeeded} = useCameraPermission() 54 54 const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() 55 55 56 - const dropdownItems = [ 57 - !isWeb && { 58 - testID: 'changeAvatarCameraBtn', 59 - label: 'Camera', 60 - icon: 'camera' as IconProp, 61 - onPress: async () => { 62 - if (!(await requestCameraAccessIfNeeded())) { 63 - return 64 - } 65 - onSelectNewAvatar?.( 66 - await openCamera(store, { 67 - width: 1000, 68 - height: 1000, 69 - cropperCircleOverlay: true, 70 - }), 71 - ) 56 + const dropdownItems = useMemo( 57 + () => [ 58 + !isWeb && { 59 + testID: 'changeAvatarCameraBtn', 60 + label: 'Camera', 61 + icon: 'camera' as IconProp, 62 + onPress: async () => { 63 + if (!(await requestCameraAccessIfNeeded())) { 64 + return 65 + } 66 + onSelectNewAvatar?.( 67 + await openCamera(store, { 68 + width: 1000, 69 + height: 1000, 70 + cropperCircleOverlay: true, 71 + }), 72 + ) 73 + }, 72 74 }, 73 - }, 74 - { 75 - testID: 'changeAvatarLibraryBtn', 76 - label: 'Library', 77 - icon: 'image' as IconProp, 78 - onPress: async () => { 79 - if (!(await requestPhotoAccessIfNeeded())) { 80 - return 81 - } 82 - const items = await openPicker(store, { 83 - mediaType: 'photo', 84 - multiple: false, 85 - }) 86 - 87 - onSelectNewAvatar?.( 88 - await openCropper(store, { 75 + { 76 + testID: 'changeAvatarLibraryBtn', 77 + label: 'Library', 78 + icon: 'image' as IconProp, 79 + onPress: async () => { 80 + if (!(await requestPhotoAccessIfNeeded())) { 81 + return 82 + } 83 + const items = await openPicker(store, { 89 84 mediaType: 'photo', 90 - path: items[0].path, 91 - width: 1000, 92 - height: 1000, 93 - cropperCircleOverlay: true, 94 - }), 95 - ) 85 + multiple: false, 86 + }) 87 + 88 + onSelectNewAvatar?.( 89 + await openCropper(store, { 90 + mediaType: 'photo', 91 + path: items[0].path, 92 + width: 1000, 93 + height: 1000, 94 + cropperCircleOverlay: true, 95 + }), 96 + ) 97 + }, 96 98 }, 97 - }, 98 - { 99 - testID: 'changeAvatarRemoveBtn', 100 - label: 'Remove', 101 - icon: ['far', 'trash-can'] as IconProp, 102 - onPress: async () => { 103 - onSelectNewAvatar?.(null) 99 + { 100 + testID: 'changeAvatarRemoveBtn', 101 + label: 'Remove', 102 + icon: ['far', 'trash-can'] as IconProp, 103 + onPress: async () => { 104 + onSelectNewAvatar?.(null) 105 + }, 104 106 }, 105 - }, 106 - ] 107 + ], 108 + [ 109 + onSelectNewAvatar, 110 + requestCameraAccessIfNeeded, 111 + requestPhotoAccessIfNeeded, 112 + store, 113 + ], 114 + ) 107 115 108 - const warning = React.useMemo(() => { 116 + const warning = useMemo(() => { 109 117 if (!hasWarning) { 110 - return <></> 118 + return null 111 119 } 112 120 return ( 113 121 <View style={[styles.warningIconContainer, pal.view]}> ··· 156 164 <HighPriorityImage 157 165 testID="userAvatarImage" 158 166 style={{width: size, height: size, borderRadius: Math.floor(size / 2)}} 159 - resizeMode="stretch" 167 + contentFit="cover" 160 168 source={{uri: avatar}} 161 169 /> 162 170 {warning}