Bluesky app fork with some witchin' additions 💫

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

+285 -223
+4 -4
package.json
··· 131 131 "emoji-mart": "^5.5.2", 132 132 "emoji-regex": "^10.4.0", 133 133 "eventemitter3": "^5.0.1", 134 - "expo": "^54.0.20", 134 + "expo": "^54.0.22", 135 135 "expo-application": "~7.0.7", 136 136 "expo-blur": "~15.0.7", 137 137 "expo-build-properties": "~1.0.9", 138 - "expo-camera": "~17.0.8", 138 + "expo-camera": "~17.0.9", 139 139 "expo-clipboard": "~8.0.7", 140 140 "expo-dev-client": "~6.0.16", 141 141 "expo-device": "~8.0.9", ··· 160 160 "expo-system-ui": "~6.0.8", 161 161 "expo-task-manager": "~14.0.8", 162 162 "expo-updates": "~29.0.12", 163 - "expo-video": "~3.0.11", 164 - "expo-web-browser": "~15.0.8", 163 + "expo-video": "~3.0.13", 164 + "expo-web-browser": "~15.0.9", 165 165 "fast-text-encoding": "^1.0.6", 166 166 "history": "^5.3.0", 167 167 "hls.js": "^1.6.2",
patches/expo-modules-core+3.0.22.patch patches/expo-modules-core+3.0.24.patch
patches/expo-modules-core+3.0.22.patch.md patches/expo-modules-core+3.0.24.patch.md
+4 -6
src/components/Button.tsx
··· 7 7 Pressable, 8 8 type PressableProps, 9 9 type StyleProp, 10 - StyleSheet, 11 10 type TargetedEvent, 12 11 type TextProps, 13 12 type TextStyle, ··· 512 511 [state, variant, color, size, disabled], 513 512 ) 514 513 515 - const flattenedBaseStyles = flatten([baseStyles, style]) 516 - 517 514 return ( 518 515 <PressableComponent 519 516 role="button" ··· 533 530 a.align_center, 534 531 a.justify_center, 535 532 a.curve_continuous, 536 - flattenedBaseStyles, 533 + baseStyles, 534 + style, 537 535 ...(state.hovered || state.pressed 538 - ? [hoverStyles, flatten(hoverStyleProp)] 536 + ? [hoverStyles, hoverStyleProp] 539 537 : []), 540 538 ]} 541 539 onPressIn={onPressIn} ··· 726 724 baseStyles.push(a.text_xs, a.leading_snug, a.font_semi_bold) 727 725 } 728 726 729 - return StyleSheet.flatten(baseStyles) 727 + return flatten(baseStyles) 730 728 }, [t, variant, color, size, disabled]) 731 729 } 732 730
+4 -4
src/components/Dialog/index.web.tsx
··· 16 16 import {logger} from '#/logger' 17 17 import {useA11y} from '#/state/a11y' 18 18 import {useDialogStateControlContext} from '#/state/dialogs' 19 - import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf' 19 + import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 20 20 import {Button, ButtonIcon} from '#/components/Button' 21 21 import {Context} from '#/components/Dialog/context' 22 22 import { ··· 180 180 onClick={stopPropagation} 181 181 onStartShouldSetResponder={_ => true} 182 182 onTouchEnd={stopPropagation} 183 - style={flatten([ 183 + style={[ 184 184 a.relative, 185 185 a.rounded_md, 186 186 a.w_full, ··· 195 195 }, 196 196 !reduceMotionEnabled && a.zoom_fade_in, 197 197 style, 198 - ])}> 198 + ]}> 199 199 <DismissableLayer.DismissableLayer 200 200 onInteractOutside={preventDefault} 201 201 onFocusOutside={preventDefault} ··· 244 244 contentContainerStyle={[a.h_full, a.px_0, webInnerContentContainerStyle]}> 245 245 <FlatList 246 246 ref={ref} 247 - style={[a.h_full, gtMobile ? a.px_2xl : a.px_xl, flatten(style)]} 247 + style={[a.h_full, gtMobile ? a.px_2xl : a.px_xl, style]} 248 248 {...props} 249 249 /> 250 250 {footer}
+1 -1
src/components/FeedCard.tsx
··· 186 186 return rt 187 187 }, [description]) 188 188 if (!rt) return null 189 - return <RichText value={rt} style={[a.leading_snug]} disableLinks {...rest} /> 189 + return <RichText value={rt} disableLinks {...rest} /> 190 190 } 191 191 192 192 export function DescriptionPlaceholder() {
+2 -11
src/components/IconCircle.tsx
··· 2 2 3 3 import { 4 4 atoms as a, 5 - flatten, 6 5 type TextStyleProp, 7 6 useTheme, 8 7 type ViewStyleProp, ··· 33 32 height: size === 'lg' ? 52 : 64, 34 33 backgroundColor: t.palette.primary_50, 35 34 }, 36 - flatten(style), 35 + style, 37 36 ]}> 38 - <Icon 39 - size={size} 40 - style={[ 41 - { 42 - color: t.palette.primary_500, 43 - }, 44 - flatten(iconStyle), 45 - ]} 46 - /> 37 + <Icon size={size} style={[{color: t.palette.primary_500}, iconStyle]} /> 47 38 </View> 48 39 ) 49 40 }
+7 -2
src/components/InterestTabs.tsx
··· 83 83 function handleTabLayout(index: number, x: number, width: number) { 84 84 if (!tabOffsets.length) { 85 85 pendingTabOffsets.current[index] = {x, width} 86 - if (pendingTabOffsets.current.length === interests.length) { 86 + // not only do we check if the length is equal to the number of interests, 87 + // but we also need to ensure that the array isn't sparse. `.filter()` 88 + // removes any empty slots from the array 89 + if ( 90 + pendingTabOffsets.current.filter(o => !!o).length === interests.length 91 + ) { 87 92 setTabOffsets(pendingTabOffsets.current) 88 93 } 89 94 } ··· 205 210 showsHorizontalScrollIndicator={false} 206 211 decelerationRate="fast" 207 212 snapToOffsets={ 208 - tabOffsets.length === interests.length 213 + tabOffsets.filter(o => !!o).length === interests.length 209 214 ? tabOffsets.map(o => o.x - tokens.space.xl) 210 215 : undefined 211 216 }
+1 -1
src/components/LabelingServiceCard/index.tsx
··· 55 55 const {_} = useLingui() 56 56 return value ? ( 57 57 <Text numberOfLines={2}> 58 - <RichText value={value} style={[a.leading_snug]} /> 58 + <RichText value={value} /> 59 59 </Text> 60 60 ) : ( 61 61 <Text emoji style={[a.leading_snug]}>
+2 -2
src/components/Link.tsx
··· 170 170 const state = navigation.getState() 171 171 // if screen is not in the current navigator, it means it's 172 172 // most likely a tab screen. note: state can be undefined 173 - if (!state?.routeNames.includes(screen)) { 173 + if (!state?.routeNames?.includes?.(screen)) { 174 174 const parent = navigation.getParent() 175 175 if ( 176 176 parent && ··· 301 301 return ( 302 302 <Button 303 303 {...rest} 304 - style={[a.justify_start, flatten(rest.style)]} 304 + style={[a.justify_start, rest.style]} 305 305 role="link" 306 306 accessibilityRole="link" 307 307 href={href}
+2 -2
src/components/Lists.tsx
··· 5 5 6 6 import {cleanError} from '#/lib/strings/errors' 7 7 import {CenteredView} from '#/view/com/util/Views' 8 - import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf' 8 + import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 9 import {Button, ButtonText} from '#/components/Button' 10 10 import {Error} from '#/components/Error' 11 11 import {Loader} from '#/components/Loader' ··· 43 43 a.pb_lg, 44 44 t.atoms.border_contrast_low, 45 45 {height: height ?? 180, paddingTop: 30}, 46 - flatten(style), 46 + style, 47 47 ]}> 48 48 {isFetchingNextPage ? ( 49 49 <Loader size="xl" />
+2 -7
src/components/Loader.tsx
··· 7 7 withTiming, 8 8 } from 'react-native-reanimated' 9 9 10 - import {atoms as a, flatten, useTheme} from '#/alf' 10 + import {atoms as a, useTheme} from '#/alf' 11 11 import {type Props, useCommonSVGProps} from '#/components/icons/common' 12 12 import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader' 13 13 ··· 37 37 ]}> 38 38 <Icon 39 39 {...props} 40 - style={[ 41 - a.absolute, 42 - a.inset_0, 43 - t.atoms.text_contrast_high, 44 - flatten(props.style), 45 - ]} 40 + style={[a.absolute, a.inset_0, t.atoms.text_contrast_high, props.style]} 46 41 /> 47 42 </Animated.View> 48 43 )
+2 -2
src/components/Loader.web.tsx
··· 1 1 import {View} from 'react-native' 2 2 3 - import {atoms as a, flatten, useTheme} from '#/alf' 3 + import {atoms as a, useTheme} from '#/alf' 4 4 import {type Props, useCommonSVGProps} from '#/components/icons/common' 5 5 import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader' 6 6 ··· 24 24 a.absolute, 25 25 a.inset_0, 26 26 t.atoms.text_contrast_high, 27 - flatten(props.style), 27 + props.style, 28 28 ]} 29 29 /> 30 30 </div>
+6 -6
src/components/PolicyUpdateOverlay/Overlay.tsx
··· 5 5 useSafeAreaInsets, 6 6 } from 'react-native-safe-area-context' 7 7 import {LinearGradient} from 'expo-linear-gradient' 8 + import {utils} from '@bsky.app/alf' 8 9 9 10 import {isAndroid, isNative} from '#/platform/detection' 10 11 import {useA11y} from '#/state/a11y' 11 - import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf' 12 - import {transparentifyColor} from '#/alf/util/colorGeneration' 12 + import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 13 13 import {FocusScope} from '#/components/FocusScope' 14 14 import {LockScroll} from '#/components/LockScroll' 15 15 ··· 47 47 ) : ( 48 48 <LinearGradient 49 49 colors={[ 50 - transparentifyColor(t.atoms.bg.backgroundColor, 0), 50 + utils.alpha(t.atoms.bg.backgroundColor, 0), 51 51 t.atoms.bg.backgroundColor, 52 52 t.atoms.bg.backgroundColor, 53 53 ]} ··· 97 97 ]}> 98 98 <LinearGradient 99 99 colors={[ 100 - transparentifyColor(t.atoms.bg.backgroundColor, 0), 100 + utils.alpha(t.atoms.bg.backgroundColor, 0), 101 101 t.atoms.bg.backgroundColor, 102 102 ]} 103 103 start={[0.5, 0]} ··· 113 113 role="dialog" 114 114 aria-role="dialog" 115 115 aria-label={label} 116 - style={flatten([ 116 + style={[ 117 117 a.relative, 118 118 a.w_full, 119 119 a.p_2xl, ··· 128 128 maxWidth: 420, 129 129 }), 130 130 ], 131 - ])}> 131 + ]}> 132 132 {children} 133 133 </View> 134 134 </FocusScope>
+82 -7
src/components/PostControls/index.tsx
··· 1 - import {memo, useState} from 'react' 1 + import {memo, useMemo, useState} from 'react' 2 2 import {type StyleProp, View, type ViewStyle} from 'react-native' 3 3 import { 4 4 type AppBskyFeedDefs, ··· 28 28 useProgressGuideControls, 29 29 } from '#/state/shell/progress-guide' 30 30 import * as Toast from '#/view/com/util/Toast' 31 - import {atoms as a, flatten, useBreakpoints} from '#/alf' 31 + import {atoms as a, useBreakpoints} from '#/alf' 32 32 import {Reply as Bubble} from '#/components/icons/Reply' 33 33 import {useFormatPostStatCount} from '#/components/PostControls/util' 34 + import * as Skele from '#/components/Skeleton' 34 35 import {BookmarkButton} from './BookmarkButton' 35 36 import { 36 37 PostControlButton, ··· 196 197 }) 197 198 } 198 199 199 - const secondaryControlSpacingStyles = flatten([ 200 - {gap: 0}, // default, we want `gap` to be defined on the resulting object 201 - variant !== 'compact' && a.gap_xs, 202 - (big || gtPhone) && a.gap_sm, 203 - ]) 200 + const secondaryControlSpacingStyles = useSecondaryControlSpacingStyles({ 201 + variant, 202 + big, 203 + gtPhone, 204 + }) 204 205 205 206 return ( 206 207 <View ··· 352 353 } 353 354 PostControls = memo(PostControls) 354 355 export {PostControls} 356 + 357 + export function PostControlsSkeleton({ 358 + big, 359 + style, 360 + variant, 361 + }: { 362 + big?: boolean 363 + style?: StyleProp<ViewStyle> 364 + variant?: 'compact' | 'normal' | 'large' 365 + }) { 366 + const {gtPhone} = useBreakpoints() 367 + 368 + const rowHeight = big ? 32 : 28 369 + const padding = 4 370 + const size = rowHeight - padding * 2 371 + 372 + const secondaryControlSpacingStyles = useSecondaryControlSpacingStyles({ 373 + variant, 374 + big, 375 + gtPhone, 376 + }) 377 + 378 + const itemStyles = { 379 + padding, 380 + } 381 + 382 + return ( 383 + <Skele.Row 384 + style={[a.flex_row, a.justify_between, a.align_center, a.gap_md, style]}> 385 + <View style={[a.flex_row, a.flex_1, {maxWidth: 320}]}> 386 + <View 387 + style={[itemStyles, a.flex_1, a.align_start, {marginLeft: -padding}]}> 388 + <Skele.Pill blend size={size} /> 389 + </View> 390 + 391 + <View style={[itemStyles, a.flex_1, a.align_start]}> 392 + <Skele.Pill blend size={size} /> 393 + </View> 394 + 395 + <View style={[itemStyles, a.flex_1, a.align_start]}> 396 + <Skele.Pill blend size={size} /> 397 + </View> 398 + </View> 399 + <View style={[a.flex_row, a.justify_end, secondaryControlSpacingStyles]}> 400 + <View style={itemStyles}> 401 + <Skele.Circle blend size={size} /> 402 + </View> 403 + <View style={itemStyles}> 404 + <Skele.Circle blend size={size} /> 405 + </View> 406 + <View style={itemStyles}> 407 + <Skele.Circle blend size={size} /> 408 + </View> 409 + </View> 410 + </Skele.Row> 411 + ) 412 + } 413 + 414 + function useSecondaryControlSpacingStyles({ 415 + variant, 416 + big, 417 + gtPhone, 418 + }: { 419 + variant?: 'compact' | 'normal' | 'large' 420 + big?: boolean 421 + gtPhone: boolean 422 + }) { 423 + return useMemo(() => { 424 + let gap = 0 // default, we want `gap` to be defined on the resulting object 425 + if (variant !== 'compact') gap = a.gap_xs.gap 426 + if (big || gtPhone) gap = a.gap_sm.gap 427 + return {gap} 428 + }, [variant, big, gtPhone]) 429 + }
+1 -1
src/components/ProfileCard.tsx
··· 396 396 <View style={[a.pt_xs]}> 397 397 <RichText 398 398 value={rt} 399 - style={[a.leading_snug, style]} 399 + style={style} 400 400 numberOfLines={numberOfLines} 401 401 disableLinks 402 402 />
+3 -7
src/components/RichText.tsx
··· 48 48 [value], 49 49 ) 50 50 51 - const flattenedStyle = flatten(style) 52 - const plainStyles = [a.leading_snug, flattenedStyle] 53 - const interactiveStyles = [ 54 - a.leading_snug, 55 - flatten(interactiveStyle), 56 - flattenedStyle, 57 - ] 51 + const plainStyles = [a.leading_snug, style] 52 + const interactiveStyles = [plainStyles, interactiveStyle] 58 53 59 54 const {text, facets} = richText 60 55 61 56 if (!facets?.length) { 62 57 if (isOnlyEmoji(text)) { 58 + const flattenedStyle = flatten(style) 63 59 const fontSize = 64 60 (flattenedStyle.fontSize ?? a.text_sm.fontSize) * emojiMultiplier 65 61 return (
+4 -4
src/components/Tooltip/index.web.tsx
··· 1 1 import {Children, createContext, useContext, useMemo} from 'react' 2 2 import {View} from 'react-native' 3 + import {utils} from '@bsky.app/alf' 3 4 import {Popover} from 'radix-ui' 4 5 5 6 import {atoms as a, flatten, select, useTheme} from '#/alf' 6 - import {transparentifyColor} from '#/alf/util/colorGeneration' 7 7 import { 8 8 ARROW_SIZE, 9 9 BUBBLE_MAX_WIDTH, ··· 80 80 { 81 81 minWidth: 'max-content', 82 82 boxShadow: select(t.name, { 83 - light: `0 0 24px ${transparentifyColor(t.palette.black, 0.2)}`, 84 - dark: `0 0 24px ${transparentifyColor(t.palette.black, 0.2)}`, 85 - dim: `0 0 24px ${transparentifyColor(t.palette.black, 0.2)}`, 83 + light: `0 0 24px ${utils.alpha(t.palette.black, 0.2)}`, 84 + dark: `0 0 24px ${utils.alpha(t.palette.black, 0.2)}`, 85 + dim: `0 0 24px ${utils.alpha(t.palette.black, 0.2)}`, 86 86 }), 87 87 }, 88 88 ])}>
+3 -3
src/components/Typography.tsx
··· 1 1 import {UITextView} from 'react-native-uitextview' 2 2 3 3 import {logger} from '#/logger' 4 - import {atoms, flatten, useAlf, useTheme, web} from '#/alf' 4 + import {atoms, useAlf, useTheme, web} from '#/alf' 5 5 import { 6 6 childHasEmoji, 7 7 normalizeTextStyles, ··· 26 26 }: TextProps) { 27 27 const {fonts, flags} = useAlf() 28 28 const t = useTheme() 29 - const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)], { 29 + const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, style], { 30 30 fontScale: fonts.scaleMultiplier, 31 31 fontFamily: fonts.family, 32 32 flags, ··· 84 84 <Text 85 85 {...attr} 86 86 {...rest} 87 - style={[atoms.text_md, atoms.leading_normal, flatten(style)]} 87 + style={[atoms.text_md, atoms.leading_relaxed, style]} 88 88 /> 89 89 ) 90 90 }
+3 -3
src/components/WelcomeModal.tsx
··· 8 8 import {logger} from '#/logger' 9 9 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 10 10 import {Logo} from '#/view/icons/Logo' 11 - import {atoms as a, flatten, useBreakpoints, web} from '#/alf' 11 + import {atoms as a, useBreakpoints, web} from '#/alf' 12 12 import {Button, ButtonText} from '#/components/Button' 13 13 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 14 14 import {Text} from '#/components/Typography' ··· 78 78 ]}> 79 79 <FocusScope.FocusScope asChild loop trapped> 80 80 <View 81 - style={flatten([ 81 + style={[ 82 82 { 83 83 maxWidth: 800, 84 84 maxHeight: 600, ··· 89 89 a.rounded_lg, 90 90 a.overflow_hidden, 91 91 a.zoom_in, 92 - ])}> 92 + ]}> 93 93 <ImageBackground 94 94 source={welcomeModalBg} 95 95 style={[a.flex_1, a.justify_center]}
+2 -2
src/components/dms/EmojiReactionPicker.web.tsx
··· 134 134 accessibilityRole="button" 135 135 role="button" 136 136 onPress={() => setExpanded(true)} 137 - style={flatten([ 137 + style={[ 138 138 a.rounded_full, 139 139 {height: 34, width: 34}, 140 140 a.justify_center, 141 141 a.align_center, 142 - ])}> 142 + ]}> 143 143 <DotGridIcon size="lg" style={t.atoms.text_contrast_medium} /> 144 144 </Pressable> 145 145 </DropdownMenu.Item>
+1 -1
src/components/dms/ReportDialog.tsx
··· 424 424 ]}> 425 425 <RichText 426 426 value={rt} 427 - style={[a.text_md, a.leading_snug]} 427 + style={[a.text_md]} 428 428 interactiveStyle={a.underline} 429 429 enableTags 430 430 />
+2 -3
src/components/forms/Toggle.tsx
··· 6 6 import {isNative} from '#/platform/detection' 7 7 import { 8 8 atoms as a, 9 - flatten, 10 9 native, 11 10 type TextStyleProp, 12 11 useTheme, ··· 233 232 onPressOut={onPressOut} 234 233 onFocus={onFocus} 235 234 onBlur={onBlur} 236 - style={[a.flex_row, a.align_center, a.gap_sm, flatten(style)]}> 235 + style={[a.flex_row, a.align_center, a.gap_sm, style]}> 237 236 {typeof children === 'function' ? children(state) : children} 238 237 </Pressable> 239 238 </ItemContext.Provider> ··· 260 259 native({ 261 260 paddingTop: 2, 262 261 }), 263 - flatten(style), 262 + style, 264 263 ]}> 265 264 {children} 266 265 </Text>
+1 -1
src/components/icons/TEMPLATE.tsx
··· 40 40 viewBox="0 0 24 24" 41 41 width={size} 42 42 height={size} 43 - style={[style]}> 43 + style={style}> 44 44 {gradient} 45 45 <Path fill={fill} fillRule="evenodd" clipRule="evenodd" d={path} /> 46 46 </Svg>
+1 -4
src/lib/hooks/useIntentHandler.ts
··· 36 36 const handleIncomingURL = async (url: string) => { 37 37 if (isIOS) { 38 38 // Close in-app browser if it's open (iOS only) 39 - // TEMP: promise never resolves if the browser is not open, so don't await 40 - // https://github.com/expo/expo/issues/40710 41 - // add the await back when possible since it's needed to fix the IAB share bug -sfn 42 - /* await */ WebBrowser.dismissBrowser().catch(() => {}) 39 + await WebBrowser.dismissBrowser().catch(() => {}) 43 40 } 44 41 45 42 const referrerInfo = Referrer.getReferrerInfo()
+1 -1
src/lib/media/manip.ts
··· 158 158 ) 159 159 } 160 160 } else { 161 - await MediaLibrary.createAssetAsync(imagePath) 161 + await MediaLibrary.saveToLibraryAsync(imagePath) 162 162 } 163 163 } catch (err) { 164 164 logger.error(err instanceof Error ? err : String(err), {
+32
src/lib/media/save-image.ios.ts
··· 1 + import {useCallback} from 'react' 2 + import {msg} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + 5 + import {isNative} from '#/platform/detection' 6 + import * as Toast from '#/components/Toast' 7 + import {saveImageToMediaLibrary} from './manip' 8 + 9 + /** 10 + * Same as `saveImageToMediaLibrary`, but also handles permissions and toasts 11 + * 12 + * iOS doesn't not require permissions to save images to the media library, 13 + * so this file is platform-split as it's much simpler than the Android version. 14 + */ 15 + export function useSaveImageToMediaLibrary() { 16 + const {_} = useLingui() 17 + return useCallback( 18 + async (uri: string) => { 19 + if (!isNative) { 20 + throw new Error('useSaveImageToMediaLibrary is native only') 21 + } 22 + 23 + try { 24 + await saveImageToMediaLibrary({uri}) 25 + Toast.show(_(msg`Image saved`)) 26 + } catch (e: any) { 27 + Toast.show(_(msg`Failed to save image: ${String(e)}`), {type: 'error'}) 28 + } 29 + }, 30 + [_], 31 + ) 32 + }
+18 -9
src/lib/media/save-image.ts
··· 1 1 import {useCallback} from 'react' 2 2 import * as MediaLibrary from 'expo-media-library' 3 - import {t} from '@lingui/macro' 3 + import {msg} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 4 5 5 6 import {isNative} from '#/platform/detection' 6 - import * as Toast from '#/view/com/util/Toast' 7 + import * as Toast from '#/components/Toast' 7 8 import {saveImageToMediaLibrary} from './manip' 8 9 9 10 /** 10 11 * Same as `saveImageToMediaLibrary`, but also handles permissions and toasts 11 12 */ 12 13 export function useSaveImageToMediaLibrary() { 14 + const {_} = useLingui() 13 15 const [permissionResponse, requestPermission, getPermission] = 14 16 MediaLibrary.usePermissions({ 15 17 granularPermissions: ['photo'], ··· 23 25 async function save() { 24 26 try { 25 27 await saveImageToMediaLibrary({uri}) 26 - Toast.show(t`Image saved`) 28 + 29 + Toast.show(_(msg`Image saved`)) 27 30 } catch (e: any) { 28 - Toast.show(t`Failed to save image: ${String(e)}`, 'xmark') 31 + Toast.show(_(msg`Failed to save image: ${String(e)}`), { 32 + type: 'error', 33 + }) 29 34 } 30 35 } 31 36 ··· 42 47 } else { 43 48 // since we've been explicitly denied, show a toast. 44 49 Toast.show( 45 - t`Images cannot be saved unless permission is granted to access your photo library.`, 46 - 'xmark', 50 + _( 51 + msg`Images cannot be saved unless permission is granted to access your photo library.`, 52 + ), 53 + {type: 'error'}, 47 54 ) 48 55 } 49 56 } else { 50 57 Toast.show( 51 - t`Permission to access your photo library was denied. Please enable it in your system settings.`, 52 - 'xmark', 58 + _( 59 + msg`Permission to access your photo library was denied. Please enable it in your system settings.`, 60 + ), 61 + {type: 'error'}, 53 62 ) 54 63 } 55 64 } 56 65 }, 57 - [permissionResponse, requestPermission, getPermission], 66 + [permissionResponse, requestPermission, getPermission, _], 58 67 ) 59 68 }
+13 -13
src/locale/locales/en/messages.po
··· 3634 3634 msgid "Feed unavailable" 3635 3635 msgstr "" 3636 3636 3637 + #: src/view/shell/desktop/RightNav.tsx:105 3637 3638 #: src/view/shell/desktop/RightNav.tsx:106 3638 - #: src/view/shell/desktop/RightNav.tsx:107 3639 3639 #: src/view/shell/Drawer.tsx:368 3640 3640 msgid "Feedback" 3641 3641 msgstr "" ··· 3712 3712 msgid "Finance" 3713 3713 msgstr "" 3714 3714 3715 - #: src/view/com/posts/CustomFeedEmptyState.tsx:48 3715 + #: src/view/com/posts/CustomFeedEmptyState.tsx:71 3716 3716 #: src/view/com/posts/FollowingEmptyState.tsx:53 3717 3717 #: src/view/com/posts/FollowingEndOfFeed.tsx:54 3718 3718 msgid "Find accounts to follow" ··· 4213 4213 4214 4214 #: src/screens/Settings/Settings.tsx:236 4215 4215 #: src/screens/Settings/Settings.tsx:240 4216 + #: src/view/shell/desktop/RightNav.tsx:123 4216 4217 #: src/view/shell/desktop/RightNav.tsx:124 4217 - #: src/view/shell/desktop/RightNav.tsx:125 4218 4218 #: src/view/shell/Drawer.tsx:381 4219 4219 msgid "Help" 4220 4220 msgstr "" ··· 4447 4447 msgid "Illegal and Urgent" 4448 4448 msgstr "" 4449 4449 4450 - #: src/view/com/util/images/Gallery.tsx:70 4450 + #: src/view/com/util/images/Gallery.tsx:75 4451 4451 msgid "Image" 4452 4452 msgstr "" 4453 4453 ··· 5052 5052 msgid "Logged-out visibility" 5053 5053 msgstr "" 5054 5054 5055 - #: src/view/shell/desktop/RightNav.tsx:134 5055 + #: src/view/shell/desktop/RightNav.tsx:133 5056 5056 msgid "Logo by @sawaratsuki.bsky.social" 5057 5057 msgstr "" 5058 5058 5059 - #: src/view/shell/desktop/RightNav.tsx:131 5059 + #: src/view/shell/desktop/RightNav.tsx:130 5060 5060 #: src/view/shell/Drawer.tsx:709 5061 5061 msgid "Logo by <0>@sawaratsuki.bsky.social</0>" 5062 5062 msgstr "" ··· 5276 5276 msgid "Moderator has chosen to set a general warning on the content." 5277 5277 msgstr "" 5278 5278 5279 - #: src/view/shell/desktop/Feeds.tsx:108 5280 - #: src/view/shell/desktop/Feeds.tsx:118 5279 + #: src/view/shell/desktop/Feeds.tsx:104 5280 + #: src/view/shell/desktop/Feeds.tsx:114 5281 5281 msgid "More feeds" 5282 5282 msgstr "" 5283 5283 ··· 6613 6613 msgid "Prioritize your Follows" 6614 6614 msgstr "" 6615 6615 6616 + #: src/view/shell/desktop/RightNav.tsx:113 6616 6617 #: src/view/shell/desktop/RightNav.tsx:114 6617 - #: src/view/shell/desktop/RightNav.tsx:115 6618 6618 msgid "Privacy" 6619 6619 msgstr "" 6620 6620 ··· 8614 8614 msgid "Tell us a little more" 8615 8615 msgstr "" 8616 8616 8617 + #: src/view/shell/desktop/RightNav.tsx:119 8617 8618 #: src/view/shell/desktop/RightNav.tsx:120 8618 - #: src/view/shell/desktop/RightNav.tsx:121 8619 8619 msgid "Terms" 8620 8620 msgstr "" 8621 8621 ··· 9000 9000 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." 9001 9001 msgstr "" 9002 9002 9003 - #: src/view/com/posts/CustomFeedEmptyState.tsx:38 9003 + #: src/view/com/posts/CustomFeedEmptyState.tsx:61 9004 9004 msgid "This feed is empty! You may need to follow more users or tune your language settings." 9005 9005 msgstr "" 9006 9006 ··· 9987 9987 msgid "View your verifications" 9988 9988 msgstr "" 9989 9989 9990 - #: src/view/com/util/images/AutoSizedImage.tsx:206 9991 - #: src/view/com/util/images/AutoSizedImage.tsx:229 9990 + #: src/view/com/util/images/AutoSizedImage.tsx:207 9991 + #: src/view/com/util/images/AutoSizedImage.tsx:234 9992 9992 msgid "Views full image" 9993 9993 msgstr "" 9994 9994
+3 -3
src/screens/Messages/components/MessageInput.web.tsx
··· 1 1 import React from 'react' 2 - import {Pressable, StyleSheet, View} from 'react-native' 2 + import {Pressable, View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import Graphemer from 'graphemer' ··· 19 19 type EmojiPickerPosition, 20 20 } from '#/view/com/composer/text-input/web/EmojiPicker' 21 21 import * as Toast from '#/view/com/util/Toast' 22 - import {atoms as a, useTheme} from '#/alf' 22 + import {atoms as a, flatten, useTheme} from '#/alf' 23 23 import {Button} from '#/components/Button' 24 24 import {useSharedInputStyles} from '#/components/forms/TextField' 25 25 import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' ··· 199 199 </Button> 200 200 <TextareaAutosize 201 201 ref={textAreaRef} 202 - style={StyleSheet.flatten([ 202 + style={flatten([ 203 203 a.flex_1, 204 204 a.px_sm, 205 205 a.border_0,
+2 -11
src/screens/Onboarding/Layout.tsx
··· 10 10 import {Context} from '#/screens/Onboarding/state' 11 11 import { 12 12 atoms as a, 13 - flatten, 14 13 native, 15 14 type TextStyleProp, 16 15 tokens, ··· 228 227 }: React.PropsWithChildren<TextStyleProp>) { 229 228 return ( 230 229 <Text 231 - style={[ 232 - a.pb_sm, 233 - a.text_4xl, 234 - a.font_semi_bold, 235 - a.leading_tight, 236 - flatten(style), 237 - ]}> 230 + style={[a.pb_sm, a.text_4xl, a.font_semi_bold, a.leading_tight, style]}> 238 231 {children} 239 232 </Text> 240 233 ) ··· 245 238 style, 246 239 }: React.PropsWithChildren<TextStyleProp>) { 247 240 const t = useTheme() 248 - return ( 249 - <P style={[t.atoms.text_contrast_medium, flatten(style)]}>{children}</P> 250 - ) 241 + return <P style={[t.atoms.text_contrast_medium, style]}>{children}</P> 251 242 }
+2 -8
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 55 55 import {PostAlerts} from '#/components/moderation/PostAlerts' 56 56 import {type AppModerationCause} from '#/components/Pills' 57 57 import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' 58 - import {PostControls} from '#/components/PostControls' 58 + import {PostControls, PostControlsSkeleton} from '#/components/PostControls' 59 59 import {useFormatPostStatCount} from '#/components/PostControls/util' 60 60 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 61 61 import * as Prompt from '#/components/Prompt' ··· 747 747 748 748 <Skele.Text style={[a.text_sm, {width: '50%'}]} /> 749 749 750 - <Skele.Row style={[a.justify_between]}> 751 - <Skele.Pill blend size={24} /> 752 - <Skele.Pill blend size={24} /> 753 - <Skele.Pill blend size={24} /> 754 - <Skele.Circle blend size={24} /> 755 - <Skele.Circle blend size={24} /> 756 - </Skele.Row> 750 + <PostControlsSkeleton big /> 757 751 </View> 758 752 ) 759 753 }
+2 -8
src/screens/PostThread/components/ThreadItemPost.tsx
··· 39 39 import {type AppModerationCause} from '#/components/Pills' 40 40 import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' 41 41 import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' 42 - import {PostControls} from '#/components/PostControls' 42 + import {PostControls, PostControlsSkeleton} from '#/components/PostControls' 43 43 import {RichText} from '#/components/RichText' 44 44 import * as Skele from '#/components/Skeleton' 45 45 import {SubtleHover} from '#/components/SubtleHover' ··· 390 390 )} 391 391 </Skele.Col> 392 392 393 - <Skele.Row style={[a.justify_between, a.pt_xs]}> 394 - <Skele.Pill blend size={16} /> 395 - <Skele.Pill blend size={16} /> 396 - <Skele.Pill blend size={16} /> 397 - <Skele.Circle blend size={16} /> 398 - <View /> 399 - </Skele.Row> 393 + <PostControlsSkeleton /> 400 394 </Skele.Col> 401 395 </Skele.Row> 402 396 </View>
+4 -11
src/screens/PostThread/components/ThreadItemTreePost.tsx
··· 38 38 import {type AppModerationCause} from '#/components/Pills' 39 39 import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' 40 40 import {ShowMoreTextButton} from '#/components/Post/ShowMoreTextButton' 41 - import {PostControls} from '#/components/PostControls' 41 + import {PostControls, PostControlsSkeleton} from '#/components/PostControls' 42 42 import {RichText} from '#/components/RichText' 43 43 import * as Skele from '#/components/Skeleton' 44 44 import {SubtleHover} from '#/components/SubtleHover' ··· 177 177 paddingTop: OUTER_SPACE / 2, 178 178 }, 179 179 item.ui.indent === 1 && [ 180 - !item.ui.showParentReplyLine && a.pt_lg, 180 + !item.ui.showParentReplyLine && {paddingTop: OUTER_SPACE / 1.5}, 181 181 !item.ui.showChildReplyLine && a.pb_sm, 182 182 ], 183 183 item.ui.isLastChild && ··· 412 412 <View 413 413 style={[ 414 414 {paddingHorizontal: OUTER_SPACE, paddingVertical: OUTER_SPACE / 1.5}, 415 - a.gap_md, 416 415 a.border_t, 417 416 t.atoms.border_contrast_low, 418 417 ]}> 419 - <Skele.Row style={[a.align_start, a.gap_md]}> 418 + <Skele.Row style={[a.align_start, a.gap_xs]}> 420 419 <Skele.Circle size={TREE_AVI_WIDTH} /> 421 420 422 421 <Skele.Col style={[a.gap_xs]}> ··· 436 435 )} 437 436 </Skele.Col> 438 437 439 - <Skele.Row style={[a.justify_between, a.pt_xs]}> 440 - <Skele.Pill blend size={16} /> 441 - <Skele.Pill blend size={16} /> 442 - <Skele.Pill blend size={16} /> 443 - <Skele.Circle blend size={16} /> 444 - <View /> 445 - </Skele.Row> 438 + <PostControlsSkeleton /> 446 439 </Skele.Col> 447 440 </Skele.Row> 448 441 </View>
+1 -1
src/screens/Profile/components/ProfileFeedHeader.tsx
··· 481 481 </Button> 482 482 </View> 483 483 484 - <RichText value={rt} style={[a.text_md, a.leading_snug]} /> 484 + <RichText value={rt} style={[a.text_md]} /> 485 485 486 486 <View style={[a.flex_row, a.gap_sm, a.align_center]}> 487 487 {typeof likeCount === 'number' && (
+1 -1
src/screens/ProfileList/components/Header.tsx
··· 200 200 </ProfileSubpageHeader> 201 201 {descriptionRT ? ( 202 202 <View style={[a.px_lg, a.pt_sm, a.pb_sm, a.gap_md]}> 203 - <RichText value={descriptionRT} style={[a.text_md, a.leading_snug]} /> 203 + <RichText value={descriptionRT} style={[a.text_md]} /> 204 204 </View> 205 205 ) : null} 206 206 </>
+1 -3
src/screens/StarterPack/StarterPackScreen.tsx
··· 458 458 </ProfileSubpageHeader> 459 459 {!hasSession || richText || joinedAllTimeCount >= 25 ? ( 460 460 <View style={[a.px_lg, a.pt_md, a.pb_sm, a.gap_md]}> 461 - {richText ? ( 462 - <RichText value={richText} style={[a.text_md, a.leading_snug]} /> 463 - ) : null} 461 + {richText ? <RichText value={richText} style={[a.text_md]} /> : null} 464 462 {!hasSession ? ( 465 463 <Button 466 464 label={_(msg`Join Bluesky`)}
+1 -1
src/screens/VideoFeed/index.tsx
··· 955 955 ]}> 956 956 <RichText 957 957 value={value} 958 - style={[a.text_sm, a.flex_1, a.leading_normal]} 958 + style={[a.text_sm, a.flex_1, a.leading_relaxed]} 959 959 authorHandle={authorHandle} 960 960 enableTags 961 961 numberOfLines={
+6 -12
src/view/com/posts/PostFeedItem.tsx
··· 179 179 const {sendInteraction, feedSourceInfo} = useFeedFeedbackContext() 180 180 181 181 const onPressReply = () => { 182 + sendInteraction({ 183 + item: post.uri, 184 + event: 'app.bsky.feed.defs#interactionReply', 185 + feedContext, 186 + reqId, 187 + }) 182 188 if (gate('feed_reply_button_open_thread')) { 183 - sendInteraction({ 184 - item: post.uri, 185 - event: 'app.bsky.feed.defs#clickthroughItem', 186 - feedContext, 187 - reqId, 188 - }) 189 189 navigation.navigate('PostThread', { 190 190 name: post.author.did, 191 191 rkey, 192 192 }) 193 193 } else { 194 - sendInteraction({ 195 - item: post.uri, 196 - event: 'app.bsky.feed.defs#interactionReply', 197 - feedContext, 198 - reqId, 199 - }) 200 194 openComposer({ 201 195 replyTo: { 202 196 uri: post.uri,
+2 -2
src/view/com/util/images/ImageLayoutGrid.tsx
··· 1 1 import React from 'react' 2 - import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' 2 + import {type StyleProp, View, type ViewStyle} from 'react-native' 3 3 import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated' 4 4 import {type AppBskyEmbedImages} from '@atproto/api' 5 5 ··· 224 224 if (corners.includes('bottomRight')) { 225 225 styles.push({borderBottomRightRadius: 0}) 226 226 } 227 - return StyleSheet.flatten(styles) 227 + return styles 228 228 }
+4 -2
src/view/icons/Logo.tsx
··· 1 1 import React from 'react' 2 - import {StyleSheet, type TextProps} from 'react-native' 2 + import {type TextProps} from 'react-native' 3 3 import Svg, { 4 4 Defs, 5 5 LinearGradient, ··· 10 10 } from 'react-native-svg' 11 11 12 12 import {colors} from '#/lib/styles' 13 + //import {useKawaiiMode} from '#/state/preferences/kawaii' 14 + import {flatten} from '#/alf' 13 15 14 16 const ratio = 512 / 512 15 17 ··· 21 23 export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) { 22 24 const {fill, ...rest} = props 23 25 const gradient = fill === 'sky' 24 - const styles = StyleSheet.flatten(props.style) 26 + const styles = flatten(props.style) 25 27 const _fill = gradient ? 'url(#sky)' : fill || styles?.color || colors.blue3 26 28 // @ts-ignore it's fiiiiine 27 29 const size = parseInt(rest.width || 32)
+52 -53
yarn.lock
··· 3963 3963 resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.47.0.tgz#5478fdf443ff8158f9de171c704ae45308696c7d" 3964 3964 integrity sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og== 3965 3965 3966 - "@expo/cli@54.0.13": 3967 - version "54.0.13" 3968 - resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-54.0.13.tgz#cbd1b272b7e79549f169d70f8ff4136733326889" 3969 - integrity sha512-wUJVTByZzDN0q8UjXDlu6WD2BWoTJCKVVBGUBNmvViDX4FhnESwefmtXPoO54QUUKs6vY89WZryHllGArGfLLw== 3966 + "@expo/cli@54.0.15": 3967 + version "54.0.15" 3968 + resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-54.0.15.tgz#63ca51d082fe0d683482c320f9b827c1637c01cb" 3969 + integrity sha512-tgaKFeYNRjZssPueZMm1+2cRek6mxEsthPoBX6NzQeDlzIzYBBpnAR6xH95UO6A7r0vduBeL2acIAV1Y5aSGJQ== 3970 3970 dependencies: 3971 3971 "@0no-co/graphql.web" "^1.0.8" 3972 3972 "@expo/code-signing-certificates" "^0.0.5" ··· 3976 3976 "@expo/env" "~2.0.7" 3977 3977 "@expo/image-utils" "^0.8.7" 3978 3978 "@expo/json-file" "^10.0.7" 3979 - "@expo/mcp-tunnel" "~0.0.7" 3979 + "@expo/mcp-tunnel" "~0.1.0" 3980 3980 "@expo/metro" "~54.1.0" 3981 - "@expo/metro-config" "~54.0.7" 3981 + "@expo/metro-config" "~54.0.8" 3982 3982 "@expo/osascript" "^2.3.7" 3983 3983 "@expo/package-manager" "^1.9.8" 3984 3984 "@expo/plist" "^0.4.7" ··· 4001 4001 connect "^3.7.0" 4002 4002 debug "^4.3.4" 4003 4003 env-editor "^0.4.1" 4004 - expo-server "^1.0.2" 4004 + expo-server "^1.0.4" 4005 4005 freeport-async "^2.0.0" 4006 4006 getenv "^2.0.0" 4007 4007 glob "^10.4.2" ··· 4186 4186 dotenv-expand "~11.0.6" 4187 4187 getenv "^2.0.0" 4188 4188 4189 - "@expo/fingerprint@0.15.2": 4190 - version "0.15.2" 4191 - resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.15.2.tgz#6f64a3dc1e45d6f93af16df551a9aec11c190b80" 4192 - integrity sha512-mA3weHEOd9B3mbDLNDKmAcFWo3kqsAJqPne7uMJndheKXPbRw15bV+ajAGBYZh2SS37xixLJ5eDpuc+Wr6jJtw== 4189 + "@expo/fingerprint@0.15.3": 4190 + version "0.15.3" 4191 + resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.15.3.tgz#26e7231d1ebd69a375c02ba595bba7b06fe882bb" 4192 + integrity sha512-8YPJpEYlmV171fi+t+cSLMX1nC5ngY9j2FiN70dHldLpd6Ct6ouGhk96svJ4BQZwsqwII2pokwzrDAwqo4Z0FQ== 4193 4193 dependencies: 4194 4194 "@expo/spawn-async" "^1.7.2" 4195 4195 arg "^5.0.2" ··· 4241 4241 json5 "^2.2.3" 4242 4242 write-file-atomic "^2.3.0" 4243 4243 4244 - "@expo/mcp-tunnel@~0.0.7": 4245 - version "0.0.8" 4246 - resolved "https://registry.yarnpkg.com/@expo/mcp-tunnel/-/mcp-tunnel-0.0.8.tgz#8c4fabec4e25e119998b22bd846e9d12435da4da" 4247 - integrity sha512-6261obzt6h9TQb6clET7Fw4Ig4AY2hfTNKI3gBt0gcTNxZipwMg8wER7ssDYieA9feD/FfPTuCPYFcR280aaWA== 4244 + "@expo/mcp-tunnel@~0.1.0": 4245 + version "0.1.0" 4246 + resolved "https://registry.yarnpkg.com/@expo/mcp-tunnel/-/mcp-tunnel-0.1.0.tgz#ae4ce4320b2f97a9891783c2316f9936c912d126" 4247 + integrity sha512-rJ6hl0GnIZj9+ssaJvFsC7fwyrmndcGz+RGFzu+0gnlm78X01957yjtHgjcmnQAgL5hWEOR6pkT0ijY5nU5AWw== 4248 4248 dependencies: 4249 4249 ws "^8.18.3" 4250 4250 zod "^3.25.76" 4251 4251 zod-to-json-schema "^3.24.6" 4252 4252 4253 - "@expo/metro-config@54.0.7", "@expo/metro-config@~54.0.7": 4254 - version "54.0.7" 4255 - resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-54.0.7.tgz#d7f7374ab2307ab2c9b9fde999d546cfa450e8cd" 4256 - integrity sha512-bXluEygLrd7cIh/erpjIIC2xDeanaebcwzF+DUMD5vAqHU3o0QXAF3jRV/LsjXZud9V5eRpyCRZ3tLQL0iv8WA== 4253 + "@expo/metro-config@54.0.8", "@expo/metro-config@~54.0.8": 4254 + version "54.0.8" 4255 + resolved "https://registry.yarnpkg.com/@expo/metro-config/-/metro-config-54.0.8.tgz#7e5bf551b23e8f4c8ec20504184e0a9988ffa86e" 4256 + integrity sha512-rCkDQ8IT6sgcGNy48O2cTE4NlazCAgAIsD5qBsNPJLZSS0XbaILvAgGsFt/4nrx0GMGj6iQcOn5ifwV4NssTmw== 4257 4257 dependencies: 4258 4258 "@babel/code-frame" "^7.20.0" 4259 4259 "@babel/core" "^7.20.0" ··· 11261 11261 ajv "^8.11.0" 11262 11262 semver "^7.6.0" 11263 11263 11264 - expo-camera@~17.0.8: 11265 - version "17.0.8" 11266 - resolved "https://registry.yarnpkg.com/expo-camera/-/expo-camera-17.0.8.tgz#f00cd97b59fce4db33d121cc0d6bccdc377283ed" 11267 - integrity sha512-BIGvS+3myaYqMtk2VXWgdcOMrewH+55BttmaYqq9tv9+o5w+RAbH9wlJSt0gdaswikiyzoWT7mOnLDleYClXmw== 11264 + expo-camera@~17.0.9: 11265 + version "17.0.9" 11266 + resolved "https://registry.yarnpkg.com/expo-camera/-/expo-camera-17.0.9.tgz#4447e63960c9b4485869e2f1fac0ef083c38669a" 11267 + integrity sha512-KgticPGurqEsaPBIwbG0T6mzAVnqZasDdM/6OoJt5zPh6tWB09+th6cBF1WafIBMPy8AWbfyUQSqQXqOrNJClg== 11268 11268 dependencies: 11269 11269 invariant "^2.2.4" 11270 11270 ··· 11430 11430 resolved "https://registry.yarnpkg.com/expo-media-library/-/expo-media-library-18.2.0.tgz#b7515e25df5951e6b579b2ca1bee934ed206fa43" 11431 11431 integrity sha512-aIYLIqmU8LFWrQcfZdwg9f/iWm0wC8uhZ7HiUiTnrigtxf417cVvNokX9afXpIOKBHAHRjVIbcs1nN8KZDE2Fw== 11432 11432 11433 - expo-modules-autolinking@3.0.19: 11434 - version "3.0.19" 11435 - resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-3.0.19.tgz#a7c21de54d666156f9fc9ca700b6d0485e7e0aa5" 11436 - integrity sha512-tSMYGnfZmAaN77X8iMLiaSgbCFnA7eh6s2ac09J2N2N0Rcf2RCE27jg0c0XenTMTWUcM4QvLhsNHof/WtlKqPw== 11433 + expo-modules-autolinking@3.0.20: 11434 + version "3.0.20" 11435 + resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-3.0.20.tgz#d29fc6d642d98649ea1f0a2a971d60152986851d" 11436 + integrity sha512-W4XFE/A2ijrqvXYrwXug+cUQl6ALYKtsrGnd+xdnoZ+yC7HZag45CJ9mXR0qfLpwXxuBu0HDFh/a+a1MD0Ppdg== 11437 11437 dependencies: 11438 11438 "@expo/spawn-async" "^1.7.2" 11439 11439 chalk "^4.1.0" 11440 11440 commander "^7.2.0" 11441 - glob "^10.4.2" 11442 11441 require-from-string "^2.0.2" 11443 11442 resolve-from "^5.0.0" 11444 11443 11445 - expo-modules-core@3.0.22: 11446 - version "3.0.22" 11447 - resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-3.0.22.tgz#3aec2b3474977cbef96a4f276d6c2eb64e3981a6" 11448 - integrity sha512-FqG5oelITFTLcIfGwoJP8Qsk65be/eiEjz354NdAurnhFARHAVYOOIsUehArvm75ISdZOIZEaTSjCudmkA3kKg== 11444 + expo-modules-core@3.0.24: 11445 + version "3.0.24" 11446 + resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-3.0.24.tgz#9e078938a9c081c87d827898a723ecf9016e2635" 11447 + integrity sha512-wmL0R3WVM2WEs0UJcq/rF1FKXbSrPmXozgzhCUujrb+crkW8p7Y/qKyPBAQwdwcqipuWYaFOgO49AdQ36jmvkA== 11449 11448 dependencies: 11450 11449 invariant "^2.2.4" 11451 11450 ··· 11477 11476 resolved "https://registry.yarnpkg.com/expo-screen-orientation/-/expo-screen-orientation-9.0.7.tgz#27eb8c9f57af22e1917fc025d318dd9bf31e05c3" 11478 11477 integrity sha512-UH/XlB9eMw+I2cyHSkXhAHRAPk83WyA3k5bst7GLu14wRuWiTch9fb6I7qEJK5CN6+XelcWxlBJymys6Fr/FKA== 11479 11478 11480 - expo-server@^1.0.2: 11481 - version "1.0.2" 11482 - resolved "https://registry.yarnpkg.com/expo-server/-/expo-server-1.0.2.tgz#673280d5528a77938374f35fc2a16bf5a917f328" 11483 - integrity sha512-QlQLjFuwgCiBc+Qq0IyBBHiZK1RS0NJSsKVB5iECMJrR04q7PhkaF7dON0fhvo00COy4fT9rJ5brrJDpFro/gA== 11479 + expo-server@^1.0.4: 11480 + version "1.0.4" 11481 + resolved "https://registry.yarnpkg.com/expo-server/-/expo-server-1.0.4.tgz#cb90f23272257f8cb0c9dceaade26bb169d8a3f7" 11482 + integrity sha512-IN06r3oPxFh3plSXdvBL7dx0x6k+0/g0bgxJlNISs6qL5Z+gyPuWS750dpTzOeu37KyBG0RcyO9cXUKzjYgd4A== 11484 11483 11485 11484 expo-sharing@~14.0.7: 11486 11485 version "14.0.7" ··· 11539 11538 ignore "^5.3.1" 11540 11539 resolve-from "^5.0.0" 11541 11540 11542 - expo-video@~3.0.11: 11543 - version "3.0.11" 11544 - resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-3.0.11.tgz#9ba2c4da694fe3d54f191f160702ddc70412e782" 11545 - integrity sha512-k/xz8Ml/LekuD2U2LomML2mUISvkHzYDz3fXY8Au1fEaYVNTfTs7Gyfo1lvF6S1X7u3XutoAfew8e8e1ZUR2fg== 11541 + expo-video@~3.0.13: 11542 + version "3.0.13" 11543 + resolved "https://registry.yarnpkg.com/expo-video/-/expo-video-3.0.13.tgz#99944a7aa36480d2e01514e3322c275e5e87c4b3" 11544 + integrity sha512-ew7+lvQsFTED8m46oYEXq5KOWoR1BBCTKF3xdj6HG9Z0egfhiStCH++cW95xYzrhTJAquzvJ5Rv27Ld0zL/vhw== 11546 11545 11547 - expo-web-browser@~15.0.8: 11548 - version "15.0.8" 11549 - resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-15.0.8.tgz#9425ad225255a49b9163006db9ed9578f399585d" 11550 - integrity sha512-gn+Y2ABQr6/EvFN/XSjTuzwsSPLU1vNVVV0wNe4xXkcSnYGdHxt9kHxs9uLfoCyPByoaGF4VxzAhHIMI7yDcSg== 11546 + expo-web-browser@~15.0.9: 11547 + version "15.0.9" 11548 + resolved "https://registry.yarnpkg.com/expo-web-browser/-/expo-web-browser-15.0.9.tgz#248b8de8f901e68e89944c85a46ee40205190f59" 11549 + integrity sha512-Dj8kNFO+oXsxqCDNlUT/GhOrJnm10kAElH++3RplLydogFm5jTzXYWDEeNIDmV+F+BzGYs+sIhxiBf7RyaxXZg== 11551 11550 11552 - expo@^54.0.20: 11553 - version "54.0.20" 11554 - resolved "https://registry.yarnpkg.com/expo/-/expo-54.0.20.tgz#fa5fa5468bdd12763324c41f509733f823d285af" 11555 - integrity sha512-mWHky+H63W60P5Oo+VbtqzF2sLvdaoSSwG57H9rlq1DrgIla++QJZuwJkXXo55lYPymVmkVhwG6FjWYKKylwpw== 11551 + expo@^54.0.22: 11552 + version "54.0.22" 11553 + resolved "https://registry.yarnpkg.com/expo/-/expo-54.0.22.tgz#1615f35b2b46ca2bc9109482f1bd6e64eab30858" 11554 + integrity sha512-w8J89M9BdVwo6urwvPeV4nAUwykv9si1UHUfZvSVWQ/b2aGs0Ci/a5RZ550rdEBgJXZAapIAhdW2M28Ojw+oGg== 11556 11555 dependencies: 11557 11556 "@babel/runtime" "^7.20.0" 11558 - "@expo/cli" "54.0.13" 11557 + "@expo/cli" "54.0.15" 11559 11558 "@expo/config" "~12.0.10" 11560 11559 "@expo/config-plugins" "~54.0.2" 11561 11560 "@expo/devtools" "0.1.7" 11562 - "@expo/fingerprint" "0.15.2" 11561 + "@expo/fingerprint" "0.15.3" 11563 11562 "@expo/metro" "~54.1.0" 11564 - "@expo/metro-config" "54.0.7" 11563 + "@expo/metro-config" "54.0.8" 11565 11564 "@expo/vector-icons" "^15.0.3" 11566 11565 "@ungap/structured-clone" "^1.3.0" 11567 11566 babel-preset-expo "~54.0.6" ··· 11570 11569 expo-file-system "~19.0.17" 11571 11570 expo-font "~14.0.9" 11572 11571 expo-keep-awake "~15.0.7" 11573 - expo-modules-autolinking "3.0.19" 11574 - expo-modules-core "3.0.22" 11572 + expo-modules-autolinking "3.0.20" 11573 + expo-modules-core "3.0.24" 11575 11574 pretty-format "^29.7.0" 11576 11575 react-refresh "^0.14.2" 11577 11576 whatwg-url-without-unicode "8.0.0-3"