Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Merge branch 'main' of github.com:bluesky-social/social-app into main

+117 -83
+3 -3
src/lib/analytics/analytics.tsx
··· 51 51 store.onSessionLoaded(() => { 52 52 const sess = store.session.currentSession 53 53 if (sess) { 54 - if (sess.email) { 54 + if (sess.did) { 55 + const did_hashed = sha256(sess.did) 56 + segmentClient.identify(did_hashed, {did_hashed}) 55 57 store.log.debug('Ping w/hash') 56 - const email_hashed = sha256(sess.email) 57 - segmentClient.identify(email_hashed, {email_hashed}) 58 58 } else { 59 59 store.log.debug('Ping w/o hash') 60 60 segmentClient.identify()
+3 -3
src/lib/analytics/analytics.web.tsx
··· 46 46 store.onSessionLoaded(() => { 47 47 const sess = store.session.currentSession 48 48 if (sess) { 49 - if (sess.email) { 49 + if (sess.did) { 50 + const did_hashed = sha256(sess.did) 51 + segmentClient.identify(did_hashed, {did_hashed}) 50 52 store.log.debug('Ping w/hash') 51 - const email_hashed = sha256(sess.email) 52 - segmentClient.identify(email_hashed, {email_hashed}) 53 53 } else { 54 54 store.log.debug('Ping w/o hash') 55 55 segmentClient.identify()
+8 -6
src/state/models/feeds/post.ts
··· 116 116 }, 117 117 () => this.rootStore.agent.deleteLike(url), 118 118 ) 119 + track('Post:Unlike') 119 120 } else { 120 121 // like 121 122 await updateDataOptimistically( ··· 129 130 this.post.viewer!.like = res.uri 130 131 }, 131 132 ) 133 + track('Post:Like') 132 134 } 133 135 } catch (error) { 134 136 this.rootStore.log.error('Failed to toggle like', error) 135 - } finally { 136 - track(this.post.viewer.like ? 'Post:Unlike' : 'Post:Like') 137 137 } 138 138 } 139 139 ··· 141 141 this.post.viewer = this.post.viewer || {} 142 142 try { 143 143 if (this.post.viewer?.repost) { 144 + // unrepost 144 145 const url = this.post.viewer.repost 145 146 await updateDataOptimistically( 146 147 this.post, ··· 150 151 }, 151 152 () => this.rootStore.agent.deleteRepost(url), 152 153 ) 154 + track('Post:Unrepost') 153 155 } else { 156 + // repost 154 157 await updateDataOptimistically( 155 158 this.post, 156 159 () => { ··· 162 165 this.post.viewer!.repost = res.uri 163 166 }, 164 167 ) 168 + track('Post:Repost') 165 169 } 166 170 } catch (error) { 167 171 this.rootStore.log.error('Failed to toggle repost', error) 168 - } finally { 169 - track(this.post.viewer.repost ? 'Post:Unrepost' : 'Post:Repost') 170 172 } 171 173 } 172 174 ··· 174 176 try { 175 177 if (this.isThreadMuted) { 176 178 this.rootStore.mutedThreads.uris.delete(this.rootUri) 179 + track('Post:ThreadUnmute') 177 180 } else { 178 181 this.rootStore.mutedThreads.uris.add(this.rootUri) 182 + track('Post:ThreadMute') 179 183 } 180 184 } catch (error) { 181 185 this.rootStore.log.error('Failed to toggle thread mute', error) 182 - } finally { 183 - track(this.isThreadMuted ? 'Post:ThreadUnmute' : 'Post:ThreadMute') 184 186 } 185 187 } 186 188
+2 -2
src/view/com/modals/CreateOrEditMuteList.tsx
··· 18 18 import {s, colors, gradients} from 'lib/styles' 19 19 import {enforceLen} from 'lib/strings/helpers' 20 20 import {compressIfNeeded} from 'lib/media/manip' 21 - import {UserAvatar} from '../util/UserAvatar' 21 + import {EditableUserAvatar} from '../util/UserAvatar' 22 22 import {usePalette} from 'lib/hooks/usePalette' 23 23 import {useTheme} from 'lib/ThemeContext' 24 24 import {useAnalytics} from 'lib/analytics/analytics' ··· 148 148 )} 149 149 <Text style={[styles.label, pal.text]}>List Avatar</Text> 150 150 <View style={[styles.avi, {borderColor: pal.colors.background}]}> 151 - <UserAvatar 151 + <EditableUserAvatar 152 152 type="list" 153 153 size={80} 154 154 avatar={avatar}
+2 -2
src/view/com/modals/EditProfile.tsx
··· 20 20 import {MAX_DISPLAY_NAME, MAX_DESCRIPTION} from 'lib/constants' 21 21 import {compressIfNeeded} from 'lib/media/manip' 22 22 import {UserBanner} from '../util/UserBanner' 23 - import {UserAvatar} from '../util/UserAvatar' 23 + import {EditableUserAvatar} from '../util/UserAvatar' 24 24 import {usePalette} from 'lib/hooks/usePalette' 25 25 import {useTheme} from 'lib/ThemeContext' 26 26 import {useAnalytics} from 'lib/analytics/analytics' ··· 153 153 onSelectNewBanner={onSelectNewBanner} 154 154 /> 155 155 <View style={[styles.avi, {borderColor: pal.colors.background}]}> 156 - <UserAvatar 156 + <EditableUserAvatar 157 157 size={80} 158 158 avatar={userAvatar} 159 159 onSelectNewAvatar={onSelectNewAvatar}
+2
src/view/com/modals/Waitlist.tsx
··· 77 77 keyboardAppearance={theme.colorScheme} 78 78 value={email} 79 79 onChangeText={setEmail} 80 + onSubmitEditing={onPressSignup} 81 + enterKeyHint="done" 80 82 accessible={true} 81 83 accessibilityLabel="Email" 82 84 accessibilityHint="Input your email to get on the Bluesky waitlist"
+3 -3
src/view/com/modals/crop-image/CropImage.web.tsx
··· 100 100 accessibilityHint="Sets image aspect ratio to wide"> 101 101 <RectWideIcon 102 102 size={24} 103 - style={as === AspectRatio.Wide ? s.blue3 : undefined} 103 + style={as === AspectRatio.Wide ? s.blue3 : pal.text} 104 104 /> 105 105 </TouchableOpacity> 106 106 <TouchableOpacity ··· 110 110 accessibilityHint="Sets image aspect ratio to tall"> 111 111 <RectTallIcon 112 112 size={24} 113 - style={as === AspectRatio.Tall ? s.blue3 : undefined} 113 + style={as === AspectRatio.Tall ? s.blue3 : pal.text} 114 114 /> 115 115 </TouchableOpacity> 116 116 <TouchableOpacity ··· 120 120 accessibilityHint="Sets image aspect ratio to square"> 121 121 <SquareIcon 122 122 size={24} 123 - style={as === AspectRatio.Square ? s.blue3 : undefined} 123 + style={as === AspectRatio.Square ? s.blue3 : pal.text} 124 124 /> 125 125 </TouchableOpacity> 126 126 </View>
+5 -6
src/view/com/profile/ProfileHeader.tsx
··· 132 132 }, [store, view]) 133 133 134 134 const onPressToggleFollow = React.useCallback(() => { 135 - track( 136 - view.viewer.following 137 - ? 'ProfileHeader:FollowButtonClicked' 138 - : 'ProfileHeader:UnfollowButtonClicked', 139 - ) 140 135 view?.toggleFollowing().then( 141 136 () => { 142 137 setShowSuggestedFollows(Boolean(view.viewer.following)) 143 - 144 138 Toast.show( 145 139 `${ 146 140 view.viewer.following ? 'Following' : 'No longer following' 147 141 } ${sanitizeDisplayName(view.displayName || view.handle)}`, 142 + ) 143 + track( 144 + view.viewer.following 145 + ? 'ProfileHeader:FollowButtonClicked' 146 + : 'ProfileHeader:UnfollowButtonClicked', 148 147 ) 149 148 }, 150 149 err => store.log.error('Failed to toggle follow', err),
+8 -4
src/view/com/profile/ProfileHeaderSuggestedFollows.tsx
··· 1 1 import React from 'react' 2 - import {View, StyleSheet, ScrollView, Pressable} from 'react-native' 2 + import {View, StyleSheet, Pressable, ScrollView} from 'react-native' 3 3 import Animated, { 4 4 useSharedValue, 5 5 withTiming, ··· 26 26 import {makeProfileLink} from 'lib/routes/links' 27 27 import {Link} from 'view/com/util/Link' 28 28 import {useAnalytics} from 'lib/analytics/analytics' 29 + import {isWeb} from 'platform/detection' 29 30 30 31 const OUTER_PADDING = 10 31 32 const INNER_PADDING = 14 ··· 100 101 backgroundColor: pal.viewLight.backgroundColor, 101 102 height: '100%', 102 103 paddingTop: INNER_PADDING / 2, 103 - paddingBottom: INNER_PADDING, 104 104 }}> 105 105 <View 106 106 style={{ ··· 130 130 </View> 131 131 132 132 <ScrollView 133 - horizontal 134 - showsHorizontalScrollIndicator={false} 133 + horizontal={true} 134 + showsHorizontalScrollIndicator={isWeb} 135 + persistentScrollbar={true} 136 + scrollIndicatorInsets={{bottom: 0}} 137 + scrollEnabled={true} 135 138 contentContainerStyle={{ 136 139 alignItems: 'flex-start', 137 140 paddingLeft: INNER_PADDING / 2, 141 + paddingBottom: INNER_PADDING, 138 142 }}> 139 143 {isLoading ? ( 140 144 <>
+4 -5
src/view/com/util/Link.tsx
··· 1 - import React, {ComponentProps, useMemo} from 'react' 2 - import {observer} from 'mobx-react-lite' 1 + import React, {ComponentProps, memo, useMemo} from 'react' 3 2 import { 4 3 Linking, 5 4 GestureResponderEvent, ··· 50 49 anchorNoUnderline?: boolean 51 50 } 52 51 53 - export const Link = observer(function Link({ 52 + export const Link = memo(function Link({ 54 53 testID, 55 54 style, 56 55 href, ··· 136 135 ) 137 136 }) 138 137 139 - export const TextLink = observer(function TextLink({ 138 + export const TextLink = memo(function TextLink({ 140 139 testID, 141 140 type = 'md', 142 141 style, ··· 236 235 accessibilityHint?: string 237 236 title?: string 238 237 } 239 - export const DesktopWebTextLink = observer(function DesktopWebTextLink({ 238 + export const DesktopWebTextLink = memo(function DesktopWebTextLink({ 240 239 testID, 241 240 type = 'md', 242 241 style,
+68 -40
src/view/com/util/UserAvatar.tsx
··· 23 23 type?: Type 24 24 size: number 25 25 avatar?: string | null 26 - moderation?: ModerationUI 27 26 } 28 27 29 28 interface UserAvatarProps extends BaseUserAvatarProps { 30 - onSelectNewAvatar?: (img: RNImage | null) => void 29 + moderation?: ModerationUI 30 + } 31 + 32 + interface EditableUserAvatarProps extends BaseUserAvatarProps { 33 + onSelectNewAvatar: (img: RNImage | null) => void 31 34 } 32 35 33 36 interface PreviewableUserAvatarProps extends BaseUserAvatarProps { 37 + moderation?: ModerationUI 34 38 did: string 35 39 handle: string 36 40 } ··· 106 110 size, 107 111 avatar, 108 112 moderation, 109 - onSelectNewAvatar, 110 113 }: UserAvatarProps) { 114 + const pal = usePalette('default') 115 + 116 + const aviStyle = useMemo(() => { 117 + if (type === 'algo' || type === 'list') { 118 + return { 119 + width: size, 120 + height: size, 121 + borderRadius: size > 32 ? 8 : 3, 122 + } 123 + } 124 + return { 125 + width: size, 126 + height: size, 127 + borderRadius: Math.floor(size / 2), 128 + } 129 + }, [type, size]) 130 + 131 + const alert = useMemo(() => { 132 + if (!moderation?.alert) { 133 + return null 134 + } 135 + return ( 136 + <View style={[styles.alertIconContainer, pal.view]}> 137 + <FontAwesomeIcon 138 + icon="exclamation-circle" 139 + style={styles.alertIcon} 140 + size={Math.floor(size / 3)} 141 + /> 142 + </View> 143 + ) 144 + }, [moderation?.alert, size, pal]) 145 + 146 + return avatar && 147 + !((moderation?.blur && isAndroid) /* android crashes with blur */) ? ( 148 + <View style={{width: size, height: size}}> 149 + <HighPriorityImage 150 + testID="userAvatarImage" 151 + style={aviStyle} 152 + contentFit="cover" 153 + source={{uri: avatar}} 154 + blurRadius={moderation?.blur ? BLUR_AMOUNT : 0} 155 + /> 156 + {alert} 157 + </View> 158 + ) : ( 159 + <View style={{width: size, height: size}}> 160 + <DefaultAvatar type={type} size={size} /> 161 + {alert} 162 + </View> 163 + ) 164 + } 165 + 166 + export function EditableUserAvatar({ 167 + type = 'user', 168 + size, 169 + avatar, 170 + onSelectNewAvatar, 171 + }: EditableUserAvatarProps) { 111 172 const store = useStores() 112 173 const pal = usePalette('default') 113 174 const {requestCameraAccessIfNeeded} = useCameraPermission() ··· 146 207 return 147 208 } 148 209 149 - onSelectNewAvatar?.( 210 + onSelectNewAvatar( 150 211 await openCamera(store, { 151 212 width: 1000, 152 213 height: 1000, ··· 186 247 path: item.path, 187 248 }) 188 249 189 - onSelectNewAvatar?.(croppedImage) 250 + onSelectNewAvatar(croppedImage) 190 251 }, 191 252 }, 192 253 !!avatar && { ··· 203 264 web: 'trash', 204 265 }, 205 266 onPress: async () => { 206 - onSelectNewAvatar?.(null) 267 + onSelectNewAvatar(null) 207 268 }, 208 269 }, 209 270 ].filter(Boolean) as DropdownItem[], ··· 216 277 ], 217 278 ) 218 279 219 - const alert = useMemo(() => { 220 - if (!moderation?.alert) { 221 - return null 222 - } 223 - return ( 224 - <View style={[styles.alertIconContainer, pal.view]}> 225 - <FontAwesomeIcon 226 - icon="exclamation-circle" 227 - style={styles.alertIcon} 228 - size={Math.floor(size / 3)} 229 - /> 230 - </View> 231 - ) 232 - }, [moderation?.alert, size, pal]) 233 - 234 - // onSelectNewAvatar is only passed as prop on the EditProfile component 235 - return onSelectNewAvatar ? ( 280 + return ( 236 281 <NativeDropdown 237 282 testID="changeAvatarBtn" 238 283 items={dropdownItems} ··· 256 301 /> 257 302 </View> 258 303 </NativeDropdown> 259 - ) : avatar && 260 - !((moderation?.blur && isAndroid) /* android crashes with blur */) ? ( 261 - <View style={{width: size, height: size}}> 262 - <HighPriorityImage 263 - testID="userAvatarImage" 264 - style={aviStyle} 265 - contentFit="cover" 266 - source={{uri: avatar}} 267 - blurRadius={moderation?.blur ? BLUR_AMOUNT : 0} 268 - /> 269 - {alert} 270 - </View> 271 - ) : ( 272 - <View style={{width: size, height: size}}> 273 - <DefaultAvatar type={type} size={size} /> 274 - {alert} 275 - </View> 276 304 ) 277 305 } 278 306
+4 -4
src/view/com/util/images/ImageLayoutGrid.tsx
··· 63 63 64 64 case 4: 65 65 return ( 66 - <View style={styles.flexRow}> 67 - <View style={{flex: 1}}> 66 + <> 67 + <View style={styles.flexRow}> 68 68 <View style={styles.smallItem}> 69 69 <GalleryItem {...props} index={0} imageStyle={styles.image} /> 70 70 </View> ··· 72 72 <GalleryItem {...props} index={2} imageStyle={styles.image} /> 73 73 </View> 74 74 </View> 75 - <View style={{flex: 1}}> 75 + <View style={styles.flexRow}> 76 76 <View style={styles.smallItem}> 77 77 <GalleryItem {...props} index={1} imageStyle={styles.image} /> 78 78 </View> ··· 80 80 <GalleryItem {...props} index={3} imageStyle={styles.image} /> 81 81 </View> 82 82 </View> 83 - </View> 83 + </> 84 84 ) 85 85 86 86 default:
+5 -5
src/view/screens/Support.tsx
··· 9 9 import {CenteredView} from 'view/com/util/Views' 10 10 import {usePalette} from 'lib/hooks/usePalette' 11 11 import {s} from 'lib/styles' 12 + import {HELP_DESK_URL} from 'lib/constants' 12 13 13 14 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Support'> 14 15 export const SupportScreen = (_props: Props) => { ··· 29 30 Support 30 31 </Text> 31 32 <Text style={[pal.text, s.p20]}> 32 - If you need help, email us at{' '} 33 + The support form has been moved. If you need help, please 33 34 <TextLink 34 - href="mailto:support@bsky.app" 35 - text="support@bsky.app" 35 + href={HELP_DESK_URL} 36 + text=" click here" 36 37 style={pal.link} 37 38 />{' '} 38 - with a description of your issue and information about how we can help 39 - you. 39 + or visit {HELP_DESK_URL} to get in touch with us. 40 40 </Text> 41 41 </CenteredView> 42 42 </View>