Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client

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

+605 -509
+2 -9
src/components/Divider.tsx
··· 1 import {View} from 'react-native' 2 3 - import {atoms as a, flatten, useTheme, ViewStyleProp} from '#/alf' 4 5 export function Divider({style}: ViewStyleProp) { 6 const t = useTheme() 7 8 return ( 9 - <View 10 - style={[ 11 - a.w_full, 12 - a.border_t, 13 - t.atoms.border_contrast_low, 14 - flatten(style), 15 - ]} 16 - /> 17 ) 18 }
··· 1 import {View} from 'react-native' 2 3 + import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 4 5 export function Divider({style}: ViewStyleProp) { 6 const t = useTheme() 7 8 return ( 9 + <View style={[a.w_full, a.border_t, t.atoms.border_contrast_low, style]} /> 10 ) 11 }
+1 -1
src/components/FeedCard.tsx
··· 214 export function Likes({count}: {count: number}) { 215 const t = useTheme() 216 return ( 217 - <Text style={[a.text_sm, t.atoms.text_contrast_medium]}> 218 <Trans> 219 Liked by <Plural value={count || 0} one="# user" other="# users" /> 220 </Trans>
··· 214 export function Likes({count}: {count: number}) { 215 const t = useTheme() 216 return ( 217 + <Text style={[a.text_sm, t.atoms.text_contrast_medium, a.font_bold]}> 218 <Trans> 219 Liked by <Plural value={count || 0} one="# user" other="# users" /> 220 </Trans>
+7 -5
src/components/ListCard.tsx
··· 1 import React from 'react' 2 import {View} from 'react-native' 3 import { 4 - AppBskyGraphDefs, 5 AtUri, 6 moderateUserList, 7 - ModerationUI, 8 } from '@atproto/api' 9 import {msg, Trans} from '@lingui/macro' 10 import {useLingui} from '@lingui/react' ··· 22 Outer, 23 SaveButton, 24 } from '#/components/FeedCard' 25 - import {Link as InternalLink, LinkProps} from '#/components/Link' 26 import * as Hider from '#/components/moderation/Hider' 27 import {Text} from '#/components/Typography' 28 - import * as bsky from '#/types/bsky' 29 30 /* 31 * This component is based on `FeedCard` and is tightly coupled with that ··· 50 showPinButton?: boolean 51 } 52 53 - export function Default(props: Props) { 54 const {view, showPinButton} = props 55 const moderationOpts = useModerationOpts() 56 const moderation = moderationOpts
··· 1 import React from 'react' 2 import {View} from 'react-native' 3 import { 4 + type AppBskyGraphDefs, 5 AtUri, 6 moderateUserList, 7 + type ModerationUI, 8 } from '@atproto/api' 9 import {msg, Trans} from '@lingui/macro' 10 import {useLingui} from '@lingui/react' ··· 22 Outer, 23 SaveButton, 24 } from '#/components/FeedCard' 25 + import {Link as InternalLink, type LinkProps} from '#/components/Link' 26 import * as Hider from '#/components/moderation/Hider' 27 import {Text} from '#/components/Typography' 28 + import type * as bsky from '#/types/bsky' 29 30 /* 31 * This component is based on `FeedCard` and is tightly coupled with that ··· 50 showPinButton?: boolean 51 } 52 53 + export function Default( 54 + props: Props & Omit<LinkProps, 'to' | 'label' | 'children'>, 55 + ) { 56 const {view, showPinButton} = props 57 const moderationOpts = useModerationOpts() 58 const moderation = moderationOpts
+22 -21
src/components/Post/Embed/FeedEmbed.tsx
··· 1 - import React from 'react' 2 - import {StyleSheet} from 'react-native' 3 import {moderateFeedGenerator} from '@atproto/api' 4 5 - import {usePalette} from '#/lib/hooks/usePalette' 6 import {useModerationOpts} from '#/state/preferences/moderation-opts' 7 - import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard' 8 import {ContentHider} from '#/components/moderation/ContentHider' 9 import {type EmbedType} from '#/types/bsky/post' 10 import {type CommonProps} from './types' ··· 14 }: CommonProps & { 15 embed: EmbedType<'feed'> 16 }) { 17 - const pal = usePalette('default') 18 return ( 19 - <FeedSourceCard 20 - feedUri={embed.view.uri} 21 - style={[pal.view, pal.border, styles.customFeedOuter]} 22 - showLikes 23 - /> 24 ) 25 } 26 ··· 30 embed: EmbedType<'feed'> 31 }) { 32 const moderationOpts = useModerationOpts() 33 - const moderation = React.useMemo(() => { 34 return moderationOpts 35 ? moderateFeedGenerator(embed.view, moderationOpts) 36 : undefined 37 }, [embed.view, moderationOpts]) 38 return ( 39 - <ContentHider modui={moderation?.ui('contentList')}> 40 <FeedEmbed embed={embed} /> 41 </ContentHider> 42 ) 43 } 44 - 45 - const styles = StyleSheet.create({ 46 - customFeedOuter: { 47 - borderWidth: StyleSheet.hairlineWidth, 48 - borderRadius: 8, 49 - paddingHorizontal: 12, 50 - paddingVertical: 12, 51 - }, 52 - })
··· 1 + import {useMemo} from 'react' 2 import {moderateFeedGenerator} from '@atproto/api' 3 4 import {useModerationOpts} from '#/state/preferences/moderation-opts' 5 + import {atoms as a, useTheme} from '#/alf' 6 + import * as FeedCard from '#/components/FeedCard' 7 import {ContentHider} from '#/components/moderation/ContentHider' 8 import {type EmbedType} from '#/types/bsky/post' 9 import {type CommonProps} from './types' ··· 13 }: CommonProps & { 14 embed: EmbedType<'feed'> 15 }) { 16 + const t = useTheme() 17 return ( 18 + <FeedCard.Link 19 + view={embed.view} 20 + style={[a.border, t.atoms.border_contrast_medium, a.p_md, a.rounded_sm]}> 21 + <FeedCard.Outer> 22 + <FeedCard.Header> 23 + <FeedCard.Avatar src={embed.view.avatar} /> 24 + <FeedCard.TitleAndByline 25 + title={embed.view.displayName} 26 + creator={embed.view.creator} 27 + /> 28 + </FeedCard.Header> 29 + <FeedCard.Likes count={embed.view.likeCount || 0} /> 30 + </FeedCard.Outer> 31 + </FeedCard.Link> 32 ) 33 } 34 ··· 38 embed: EmbedType<'feed'> 39 }) { 40 const moderationOpts = useModerationOpts() 41 + const moderation = useMemo(() => { 42 return moderationOpts 43 ? moderateFeedGenerator(embed.view, moderationOpts) 44 : undefined 45 }, [embed.view, moderationOpts]) 46 return ( 47 + <ContentHider 48 + modui={moderation?.ui('contentList')} 49 + childContainerStyle={[a.pt_xs]}> 50 <FeedEmbed embed={embed} /> 51 </ContentHider> 52 ) 53 }
+9 -8
src/components/Post/Embed/ListEmbed.tsx
··· 1 - import React from 'react' 2 - import {View} from 'react-native' 3 import {moderateUserList} from '@atproto/api' 4 5 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 16 }) { 17 const t = useTheme() 18 return ( 19 - <View 20 - style={[a.border, t.atoms.border_contrast_medium, a.p_md, a.rounded_sm]}> 21 - <ListCard.Default view={embed.view} /> 22 - </View> 23 ) 24 } 25 ··· 29 embed: EmbedType<'list'> 30 }) { 31 const moderationOpts = useModerationOpts() 32 - const moderation = React.useMemo(() => { 33 return moderationOpts 34 ? moderateUserList(embed.view, moderationOpts) 35 : undefined 36 }, [embed.view, moderationOpts]) 37 return ( 38 - <ContentHider modui={moderation?.ui('contentList')}> 39 <ListEmbed embed={embed} /> 40 </ContentHider> 41 )
··· 1 + import {useMemo} from 'react' 2 import {moderateUserList} from '@atproto/api' 3 4 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 15 }) { 16 const t = useTheme() 17 return ( 18 + <ListCard.Default 19 + view={embed.view} 20 + style={[a.border, t.atoms.border_contrast_medium, a.p_md, a.rounded_sm]} 21 + /> 22 ) 23 } 24 ··· 28 embed: EmbedType<'list'> 29 }) { 30 const moderationOpts = useModerationOpts() 31 + const moderation = useMemo(() => { 32 return moderationOpts 33 ? moderateUserList(embed.view, moderationOpts) 34 : undefined 35 }, [embed.view, moderationOpts]) 36 return ( 37 + <ContentHider 38 + modui={moderation?.ui('contentList')} 39 + childContainerStyle={[a.pt_xs]}> 40 <ListEmbed embed={embed} /> 41 </ContentHider> 42 )
+51 -75
src/components/Post/Embed/index.tsx
··· 1 import React from 'react' 2 - import {StyleSheet, View} from 'react-native' 3 import { 4 type $Typed, 5 type AppBskyFeedDefs, ··· 21 import {useSession} from '#/state/session' 22 import {Link} from '#/view/com/util/Link' 23 import {PostMeta} from '#/view/com/util/PostMeta' 24 - import {Text} from '#/view/com/util/text/Text' 25 import {atoms as a, useTheme} from '#/alf' 26 - import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlashIcon} from '#/components/icons/EyeSlash' 27 import {ContentHider} from '#/components/moderation/ContentHider' 28 import {PostAlerts} from '#/components/moderation/PostAlerts' 29 import {RichText} from '#/components/RichText' ··· 308 style, 309 isWithinQuote: parentIsWithinQuote, 310 allowNestedQuotes: parentAllowNestedQuotes, 311 - visibilityLabel, 312 }: Omit<CommonProps, 'viewContext'> & { 313 embed: EmbedType<'post'> 314 viewContext?: QuoteEmbedViewContext ··· 357 const [hover, setHover] = React.useState(false) 358 return ( 359 <View 360 - onPointerEnter={() => { 361 - setHover(true) 362 - }} 363 - onPointerLeave={() => { 364 - setHover(false) 365 - }}> 366 <ContentHider 367 modui={moderation?.ui('contentList')} 368 - style={[ 369 - a.rounded_md, 370 - a.p_md, 371 - a.mt_sm, 372 - a.border, 373 - t.atoms.border_contrast_low, 374 - style, 375 - ]} 376 childContainerStyle={[a.pt_sm]}> 377 - <SubtleWebHover hover={hover} /> 378 - <Link 379 - hoverStyle={{borderColor: pal.colors.borderLinkHover}} 380 - href={itemHref} 381 - title={itemTitle} 382 - onBeforePress={onBeforePress}> 383 - <View pointerEvents="none"> 384 - {visibilityLabel !== undefined ? ( 385 - <View style={[styles.blockHeader, t.atoms.border_contrast_low]}> 386 - <EyeSlashIcon size="sm" style={pal.text} /> 387 - <Text type="lg" style={pal.text}> 388 - {visibilityLabel} 389 - </Text> 390 </View> 391 - ) : undefined} 392 - <PostMeta 393 - author={quote.author} 394 - moderation={moderation} 395 - showAvatar 396 - postHref={itemHref} 397 - timestamp={quote.indexedAt} 398 - /> 399 - </View> 400 - {moderation ? ( 401 - <PostAlerts 402 - modui={moderation.ui('contentView')} 403 - style={[a.py_xs]} 404 - /> 405 - ) : null} 406 - {richText ? ( 407 - <RichText 408 - value={richText} 409 - style={a.text_md} 410 - numberOfLines={20} 411 - disableLinks 412 - /> 413 - ) : null} 414 - {quote.embed && ( 415 - <Embed 416 - embed={quote.embed} 417 - moderation={moderation} 418 - isWithinQuote={parentIsWithinQuote ?? true} 419 - // already within quote? override nested 420 - allowNestedQuotes={ 421 - parentIsWithinQuote ? false : parentAllowNestedQuotes 422 - } 423 - /> 424 - )} 425 - </Link> 426 </ContentHider> 427 </View> 428 ) 429 } 430 - 431 - const styles = StyleSheet.create({ 432 - blockHeader: { 433 - flexDirection: 'row', 434 - alignItems: 'center', 435 - gap: 4, 436 - marginBottom: 8, 437 - }, 438 - })
··· 1 import React from 'react' 2 + import {View} from 'react-native' 3 import { 4 type $Typed, 5 type AppBskyFeedDefs, ··· 21 import {useSession} from '#/state/session' 22 import {Link} from '#/view/com/util/Link' 23 import {PostMeta} from '#/view/com/util/PostMeta' 24 import {atoms as a, useTheme} from '#/alf' 25 import {ContentHider} from '#/components/moderation/ContentHider' 26 import {PostAlerts} from '#/components/moderation/PostAlerts' 27 import {RichText} from '#/components/RichText' ··· 306 style, 307 isWithinQuote: parentIsWithinQuote, 308 allowNestedQuotes: parentAllowNestedQuotes, 309 }: Omit<CommonProps, 'viewContext'> & { 310 embed: EmbedType<'post'> 311 viewContext?: QuoteEmbedViewContext ··· 354 const [hover, setHover] = React.useState(false) 355 return ( 356 <View 357 + style={[a.mt_sm]} 358 + onPointerEnter={() => setHover(true)} 359 + onPointerLeave={() => setHover(false)}> 360 <ContentHider 361 modui={moderation?.ui('contentList')} 362 + style={[a.rounded_md, a.border, t.atoms.border_contrast_low, style]} 363 + activeStyle={[a.p_md, a.pt_sm]} 364 childContainerStyle={[a.pt_sm]}> 365 + {({active}) => ( 366 + <> 367 + {!active && <SubtleWebHover hover={hover} style={[a.rounded_md]} />} 368 + <Link 369 + style={[!active && a.p_md]} 370 + hoverStyle={{borderColor: pal.colors.borderLinkHover}} 371 + href={itemHref} 372 + title={itemTitle} 373 + onBeforePress={onBeforePress}> 374 + <View pointerEvents="none"> 375 + <PostMeta 376 + author={quote.author} 377 + moderation={moderation} 378 + showAvatar 379 + postHref={itemHref} 380 + timestamp={quote.indexedAt} 381 + /> 382 </View> 383 + {moderation ? ( 384 + <PostAlerts 385 + modui={moderation.ui('contentView')} 386 + style={[a.py_xs]} 387 + /> 388 + ) : null} 389 + {richText ? ( 390 + <RichText 391 + value={richText} 392 + style={a.text_md} 393 + numberOfLines={20} 394 + disableLinks 395 + /> 396 + ) : null} 397 + {quote.embed && ( 398 + <Embed 399 + embed={quote.embed} 400 + moderation={moderation} 401 + isWithinQuote={parentIsWithinQuote ?? true} 402 + // already within quote? override nested 403 + allowNestedQuotes={ 404 + parentIsWithinQuote ? false : parentAllowNestedQuotes 405 + } 406 + /> 407 + )} 408 + </Link> 409 + </> 410 + )} 411 </ContentHider> 412 </View> 413 ) 414 }
+8 -5
src/components/moderation/ContentHider.tsx
··· 23 modui, 24 ignoreMute, 25 style, 26 childContainerStyle, 27 children, 28 - }: React.PropsWithChildren<{ 29 testID?: string 30 modui: ModerationUI | undefined 31 ignoreMute?: boolean 32 style?: StyleProp<ViewStyle> 33 childContainerStyle?: StyleProp<ViewStyle> 34 - }>) { 35 const blur = modui?.blurs[0] 36 if (!blur || (ignoreMute && isJustAMute(modui))) { 37 return ( 38 <View testID={testID} style={style}> 39 - {children} 40 </View> 41 ) 42 } ··· 44 <ContentHiderActive 45 testID={testID} 46 modui={modui} 47 - style={style} 48 childContainerStyle={childContainerStyle}> 49 - {children} 50 </ContentHiderActive> 51 ) 52 }
··· 23 modui, 24 ignoreMute, 25 style, 26 + activeStyle, 27 childContainerStyle, 28 children, 29 + }: { 30 testID?: string 31 modui: ModerationUI | undefined 32 ignoreMute?: boolean 33 style?: StyleProp<ViewStyle> 34 + activeStyle?: StyleProp<ViewStyle> 35 childContainerStyle?: StyleProp<ViewStyle> 36 + children?: React.ReactNode | ((props: {active: boolean}) => React.ReactNode) 37 + }) { 38 const blur = modui?.blurs[0] 39 if (!blur || (ignoreMute && isJustAMute(modui))) { 40 return ( 41 <View testID={testID} style={style}> 42 + {typeof children === 'function' ? children({active: false}) : children} 43 </View> 44 ) 45 } ··· 47 <ContentHiderActive 48 testID={testID} 49 modui={modui} 50 + style={[style, activeStyle]} 51 childContainerStyle={childContainerStyle}> 52 + {typeof children === 'function' ? children({active: true}) : children} 53 </ContentHiderActive> 54 ) 55 }
+109 -61
src/locale/locales/en/messages.po
··· 162 msgid "{0} reacted {1} to {2}" 163 msgstr "" 164 165 #: src/view/com/util/UserAvatar.tsx:570 166 #: src/view/com/util/UserAvatar.tsx:588 167 msgid "{0}'s avatar" ··· 733 msgid "Add to lists" 734 msgstr "" 735 736 - #: src/view/com/feeds/FeedSourceCard.tsx:269 737 - msgid "Add to my feeds" 738 - msgstr "" 739 - 740 #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:156 741 msgid "Add user to list" 742 msgstr "" ··· 746 msgid "Added to list" 747 msgstr "" 748 749 - #: src/view/com/feeds/FeedSourceCard.tsx:125 750 - msgid "Added to my feeds" 751 - msgstr "" 752 - 753 #: src/components/moderation/ReportDialog/index.tsx:418 754 msgid "Additional details (limit 300 characters)" 755 msgstr "" ··· 758 msgid "Adult" 759 msgstr "" 760 761 - #: src/components/moderation/ContentHider.tsx:113 762 #: src/lib/moderation/useGlobalLabelStrings.ts:34 763 #: src/lib/moderation/useModerationCauseDescription.ts:148 764 #: src/view/com/composer/labels/LabelsBtn.tsx:127 ··· 1109 1110 #: src/components/dms/LeaveConvoPrompt.tsx:45 1111 msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participant." 1112 - msgstr "" 1113 - 1114 - #: src/view/com/feeds/FeedSourceCard.tsx:319 1115 - msgid "Are you sure you want to remove {0} from your feeds?" 1116 msgstr "" 1117 1118 #: src/components/FeedCard.tsx:340 ··· 1718 msgid "Clear search query" 1719 msgstr "" 1720 1721 #: src/view/screens/Support.tsx:41 1722 msgid "click here" 1723 msgstr "" ··· 1775 #: src/components/verification/VerifierDialog.tsx:144 1776 #: src/components/WhoCanReply.tsx:200 1777 #: src/components/WhoCanReply.tsx:207 1778 #: src/view/com/modals/ChangePassword.tsx:279 1779 #: src/view/com/modals/ChangePassword.tsx:282 1780 msgid "Close" ··· 1851 msgid "Collapse list of users" 1852 msgstr "" 1853 1854 - #: src/view/com/notifications/NotificationFeedItem.tsx:799 1855 msgid "Collapses list of users for a given notification" 1856 msgstr "" 1857 ··· 2157 msgid "Copyright Policy" 2158 msgstr "" 2159 2160 #: src/components/dms/LeaveConvoPrompt.tsx:35 2161 #: src/components/dms/ReportDialog.tsx:310 2162 msgid "Could not leave chat" ··· 2423 msgid "Deleted Account" 2424 msgstr "" 2425 2426 #: src/view/com/post-thread/PostThread.tsx:474 2427 msgid "Deleted post." 2428 msgstr "" ··· 3014 msgid "Error loading preference" 3015 msgstr "" 3016 3017 #: src/screens/Settings/components/ExportCarDialog.tsx:47 3018 msgid "Error occurred while saving file" 3019 msgstr "" ··· 3367 msgstr "" 3368 3369 #: src/components/FeedCard.tsx:139 3370 - #: src/view/com/feeds/FeedSourceCard.tsx:253 3371 msgid "Feed by {0}" 3372 msgstr "" 3373 3374 #: src/screens/Profile/components/ProfileFeedHeader.tsx:354 3375 msgid "Feed menu" 3376 msgstr "" ··· 3379 msgid "Feed toggle" 3380 msgstr "" 3381 3382 #: src/view/shell/desktop/RightNav.tsx:102 3383 #: src/view/shell/desktop/RightNav.tsx:103 3384 #: src/view/shell/Drawer.tsx:357 ··· 3396 #: src/screens/StarterPack/StarterPackScreen.tsx:190 3397 #: src/view/screens/Feeds.tsx:511 3398 #: src/view/screens/Profile.tsx:230 3399 - #: src/view/screens/SavedFeeds.tsx:103 3400 #: src/view/shell/desktop/LeftNav.tsx:673 3401 #: src/view/shell/Drawer.tsx:519 3402 msgid "Feeds" 3403 msgstr "" 3404 3405 - #: src/view/screens/SavedFeeds.tsx:205 3406 msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." 3407 msgstr "" 3408 3409 #: src/components/FeedCard.tsx:282 3410 - #: src/view/screens/SavedFeeds.tsx:85 3411 msgctxt "toast" 3412 msgid "Feeds updated!" 3413 msgstr "" ··· 3577 #: src/screens/VideoFeed/index.tsx:848 3578 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:134 3579 #: src/view/screens/Feeds.tsx:602 3580 - #: src/view/screens/SavedFeeds.tsx:429 3581 msgid "Following" 3582 msgstr "" 3583 ··· 3924 msgid "Hidden by your moderation settings." 3925 msgstr "" 3926 3927 - #: src/components/ListCard.tsx:130 3928 msgid "Hidden list" 3929 msgstr "" 3930 3931 #: src/components/interstitials/Trending.tsx:131 3932 #: src/components/interstitials/TrendingVideos.tsx:140 3933 - #: src/components/moderation/ContentHider.tsx:200 3934 #: src/components/moderation/LabelPreference.tsx:135 3935 #: src/components/moderation/PostHider.tsx:134 3936 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:712 ··· 3942 msgid "Hide" 3943 msgstr "" 3944 3945 - #: src/view/com/notifications/NotificationFeedItem.tsx:806 3946 msgctxt "action" 3947 msgid "Hide" 3948 msgstr "" ··· 3993 msgid "Hide trending videos?" 3994 msgstr "" 3995 3996 - #: src/view/com/notifications/NotificationFeedItem.tsx:797 3997 msgid "Hide user list" 3998 msgstr "" 3999 ··· 4002 msgid "Hide verification badges" 4003 msgstr "" 4004 4005 - #: src/components/moderation/ContentHider.tsx:151 4006 #: src/components/moderation/PostHider.tsx:89 4007 msgid "Hides the content" 4008 msgstr "" ··· 4332 msgid "Keep me posted" 4333 msgstr "" 4334 4335 - #: src/components/moderation/ContentHider.tsx:231 4336 msgid "Labeled by {0}." 4337 msgstr "" 4338 4339 - #: src/components/moderation/ContentHider.tsx:229 4340 msgid "Labeled by the author." 4341 msgstr "" 4342 ··· 4409 msgid "Learn more about self hosting your PDS." 4410 msgstr "" 4411 4412 - #: src/components/moderation/ContentHider.tsx:149 4413 msgid "Learn more about the moderation applied to this content" 4414 msgstr "" 4415 4416 - #: src/components/moderation/ContentHider.tsx:215 4417 msgid "Learn more about the moderation applied to this content." 4418 msgstr "" 4419 ··· 4432 msgid "Learn more about what is public on Bluesky." 4433 msgstr "" 4434 4435 - #: src/components/moderation/ContentHider.tsx:239 4436 #: src/view/com/auth/server-input/index.tsx:220 4437 msgid "Learn more." 4438 msgstr "" ··· 4533 msgstr "" 4534 4535 #: src/components/FeedCard.tsx:218 4536 - #: src/view/com/feeds/FeedSourceCard.tsx:303 4537 msgid "Liked by {0, plural, one {# user} other {# users}}" 4538 msgstr "" 4539 ··· 4586 msgid "List blocked" 4587 msgstr "" 4588 4589 - #: src/components/ListCard.tsx:150 4590 - #: src/view/com/feeds/FeedSourceCard.tsx:255 4591 msgid "List by {0}" 4592 msgstr "" 4593 ··· 4597 4598 #: src/view/com/profile/ProfileSubpageHeader.tsx:158 4599 msgid "List by you" 4600 msgstr "" 4601 4602 #: src/view/screens/ProfileList.tsx:485 ··· 4866 msgid "Moderation details" 4867 msgstr "" 4868 4869 - #: src/components/ListCard.tsx:149 4870 #: src/view/com/modals/UserAddRemoveLists.tsx:222 4871 msgid "Moderation list by {0}" 4872 msgstr "" ··· 5743 msgid "Opens password reset form" 5744 msgstr "" 5745 5746 - #: src/view/com/notifications/NotificationFeedItem.tsx:900 5747 #: src/view/com/util/UserAvatar.tsx:592 5748 msgid "Opens this profile" 5749 msgstr "" ··· 5926 msgid "Pinned {0} to Home" 5927 msgstr "" 5928 5929 - #: src/view/screens/SavedFeeds.tsx:130 5930 msgid "Pinned Feeds" 5931 msgstr "" 5932 ··· 6508 #: src/components/StarterPack/Wizard/WizardListCard.tsx:102 6509 #: src/components/StarterPack/Wizard/WizardListCard.tsx:109 6510 #: src/screens/Settings/Settings.tsx:567 6511 - #: src/view/com/feeds/FeedSourceCard.tsx:322 6512 #: src/view/com/modals/UserAddRemoveLists.tsx:235 6513 #: src/view/com/posts/PostFeedErrorMessage.tsx:217 6514 msgid "Remove" ··· 6557 6558 #: src/screens/Profile/components/ProfileFeedHeader.tsx:319 6559 #: src/screens/Profile/components/ProfileFeedHeader.tsx:325 6560 - #: src/view/com/feeds/FeedSourceCard.tsx:190 6561 - #: src/view/com/feeds/FeedSourceCard.tsx:268 6562 #: src/view/screens/ProfileList.tsx:528 6563 - #: src/view/screens/SavedFeeds.tsx:349 6564 msgid "Remove from my feeds" 6565 msgstr "" 6566 ··· 6574 msgstr "" 6575 6576 #: src/components/FeedCard.tsx:338 6577 - #: src/view/com/feeds/FeedSourceCard.tsx:317 6578 msgid "Remove from your feeds?" 6579 msgstr "" 6580 ··· 6636 msgid "Removed from list" 6637 msgstr "" 6638 6639 - #: src/view/com/feeds/FeedSourceCard.tsx:138 6640 - msgid "Removed from my feeds" 6641 - msgstr "" 6642 - 6643 #: src/screens/List/ListHiddenScreen.tsx:94 6644 msgid "Removed from saved feeds" 6645 msgstr "" ··· 7033 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:153 7034 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:163 7035 #: src/view/com/modals/CreateOrEditList.tsx:315 7036 - #: src/view/screens/SavedFeeds.tsx:116 7037 msgid "Save" 7038 msgstr "" 7039 ··· 7049 7050 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:191 7051 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:200 7052 - #: src/view/screens/SavedFeeds.tsx:112 7053 - #: src/view/screens/SavedFeeds.tsx:116 7054 msgid "Save changes" 7055 msgstr "" 7056 ··· 7076 msgid "Save to my feeds" 7077 msgstr "" 7078 7079 - #: src/view/screens/SavedFeeds.tsx:171 7080 msgid "Saved Feeds" 7081 msgstr "" 7082 ··· 7091 7092 #: src/components/dms/ChatEmptyPill.tsx:33 7093 #: src/components/NewskieDialog.tsx:105 7094 - #: src/view/com/notifications/NotificationFeedItem.tsx:745 7095 - #: src/view/com/notifications/NotificationFeedItem.tsx:770 7096 msgid "Say hello!" 7097 msgstr "" 7098 ··· 7211 msgid "See jobs at Bluesky" 7212 msgstr "" 7213 7214 - #: src/view/screens/SavedFeeds.tsx:212 7215 msgid "See this guide" 7216 msgstr "" 7217 ··· 7574 msgid "Shared Preferences Tester" 7575 msgstr "" 7576 7577 - #: src/components/moderation/ContentHider.tsx:200 7578 #: src/components/moderation/LabelPreference.tsx:137 7579 #: src/components/moderation/PostHider.tsx:134 7580 msgid "Show" ··· 7695 msgid "Shows other accounts you can switch to" 7696 msgstr "" 7697 7698 - #: src/components/moderation/ContentHider.tsx:152 7699 #: src/components/moderation/PostHider.tsx:89 7700 msgid "Shows the content" 7701 msgstr "" ··· 8095 msgid "Tags only" 8096 msgstr "" 8097 8098 #: src/components/ContextMenu/Backdrop.ios.tsx:54 8099 #: src/components/ContextMenu/Backdrop.ios.tsx:80 8100 #: src/components/ContextMenu/Backdrop.tsx:46 ··· 8317 #: src/screens/Profile/components/ProfileFeedHeader.tsx:178 8318 #: src/view/screens/ProfileList.tsx:375 8319 #: src/view/screens/ProfileList.tsx:394 8320 - #: src/view/screens/SavedFeeds.tsx:92 8321 msgid "There was an issue contacting the server" 8322 msgstr "" 8323 ··· 8326 msgid "There was an issue contacting the server, please check your internet connection and try again." 8327 msgstr "" 8328 8329 - #: src/view/com/feeds/FeedSourceCard.tsx:127 8330 - #: src/view/com/feeds/FeedSourceCard.tsx:140 8331 - msgid "There was an issue contacting your server" 8332 - msgstr "" 8333 - 8334 #: src/view/com/notifications/NotificationFeed.tsx:124 8335 msgid "There was an issue fetching notifications. Tap here to try again." 8336 msgstr "" ··· 8803 8804 #: src/screens/StarterPack/StarterPackScreen.tsx:673 8805 msgid "Unable to delete" 8806 msgstr "" 8807 8808 #: src/components/dms/MessagesListBlockedFooter.tsx:97 ··· 9455 msgid "Watch now" 9456 msgstr "" 9457 9458 #: src/screens/Hashtag.tsx:205 9459 msgid "We couldn't find any results for that hashtag." 9460 msgstr "" ··· 9822 msgid "You don't have any invite codes yet! We'll send you some when you've been on Bluesky for a little longer." 9823 msgstr "" 9824 9825 - #: src/view/screens/SavedFeeds.tsx:144 9826 msgid "You don't have any pinned feeds." 9827 msgstr "" 9828 9829 - #: src/view/screens/SavedFeeds.tsx:184 9830 msgid "You don't have any saved feeds." 9831 msgstr "" 9832
··· 162 msgid "{0} reacted {1} to {2}" 163 msgstr "" 164 165 + #: src/view/com/feeds/FeedSourceCard.tsx:187 166 + msgid "{0}, a feed by {1}, liked by {2}" 167 + msgstr "" 168 + 169 + #: src/view/com/feeds/FeedSourceCard.tsx:188 170 + msgid "{0}, a list by {1}" 171 + msgstr "" 172 + 173 #: src/view/com/util/UserAvatar.tsx:570 174 #: src/view/com/util/UserAvatar.tsx:588 175 msgid "{0}'s avatar" ··· 741 msgid "Add to lists" 742 msgstr "" 743 744 #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:156 745 msgid "Add user to list" 746 msgstr "" ··· 750 msgid "Added to list" 751 msgstr "" 752 753 #: src/components/moderation/ReportDialog/index.tsx:418 754 msgid "Additional details (limit 300 characters)" 755 msgstr "" ··· 758 msgid "Adult" 759 msgstr "" 760 761 + #: src/components/moderation/ContentHider.tsx:116 762 #: src/lib/moderation/useGlobalLabelStrings.ts:34 763 #: src/lib/moderation/useModerationCauseDescription.ts:148 764 #: src/view/com/composer/labels/LabelsBtn.tsx:127 ··· 1109 1110 #: src/components/dms/LeaveConvoPrompt.tsx:45 1111 msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participant." 1112 msgstr "" 1113 1114 #: src/components/FeedCard.tsx:340 ··· 1714 msgid "Clear search query" 1715 msgstr "" 1716 1717 + #: src/view/com/feeds/MissingFeed.tsx:87 1718 + msgid "Click for information" 1719 + msgstr "" 1720 + 1721 #: src/view/screens/Support.tsx:41 1722 msgid "click here" 1723 msgstr "" ··· 1775 #: src/components/verification/VerifierDialog.tsx:144 1776 #: src/components/WhoCanReply.tsx:200 1777 #: src/components/WhoCanReply.tsx:207 1778 + #: src/view/com/feeds/MissingFeed.tsx:208 1779 + #: src/view/com/feeds/MissingFeed.tsx:215 1780 #: src/view/com/modals/ChangePassword.tsx:279 1781 #: src/view/com/modals/ChangePassword.tsx:282 1782 msgid "Close" ··· 1853 msgid "Collapse list of users" 1854 msgstr "" 1855 1856 + #: src/view/com/notifications/NotificationFeedItem.tsx:801 1857 msgid "Collapses list of users for a given notification" 1858 msgstr "" 1859 ··· 2159 msgid "Copyright Policy" 2160 msgstr "" 2161 2162 + #: src/view/com/feeds/MissingFeed.tsx:41 2163 + msgid "Could not connect to custom feed" 2164 + msgstr "" 2165 + 2166 + #: src/view/com/feeds/MissingFeed.tsx:133 2167 + msgid "Could not connect to feed service" 2168 + msgstr "" 2169 + 2170 + #: src/view/com/feeds/MissingFeed.tsx:182 2171 + msgid "Could not find profile" 2172 + msgstr "" 2173 + 2174 #: src/components/dms/LeaveConvoPrompt.tsx:35 2175 #: src/components/dms/ReportDialog.tsx:310 2176 msgid "Could not leave chat" ··· 2437 msgid "Deleted Account" 2438 msgstr "" 2439 2440 + #: src/view/com/feeds/MissingFeed.tsx:42 2441 + #: src/view/com/feeds/MissingFeed.tsx:75 2442 + #: src/view/com/feeds/MissingFeed.tsx:127 2443 + #: src/view/com/feeds/MissingFeed.tsx:135 2444 + msgid "Deleted list" 2445 + msgstr "" 2446 + 2447 #: src/view/com/post-thread/PostThread.tsx:474 2448 msgid "Deleted post." 2449 msgstr "" ··· 3035 msgid "Error loading preference" 3036 msgstr "" 3037 3038 + #: src/view/com/feeds/MissingFeed.tsx:198 3039 + msgid "Error message" 3040 + msgstr "" 3041 + 3042 #: src/screens/Settings/components/ExportCarDialog.tsx:47 3043 msgid "Error occurred while saving file" 3044 msgstr "" ··· 3392 msgstr "" 3393 3394 #: src/components/FeedCard.tsx:139 3395 + #: src/view/com/feeds/FeedSourceCard.tsx:150 3396 msgid "Feed by {0}" 3397 msgstr "" 3398 3399 + #: src/view/com/feeds/MissingFeed.tsx:152 3400 + msgid "Feed creator" 3401 + msgstr "" 3402 + 3403 + #: src/view/com/feeds/MissingFeed.tsx:188 3404 + msgid "Feed identifier" 3405 + msgstr "" 3406 + 3407 #: src/screens/Profile/components/ProfileFeedHeader.tsx:354 3408 msgid "Feed menu" 3409 msgstr "" ··· 3412 msgid "Feed toggle" 3413 msgstr "" 3414 3415 + #: src/view/com/feeds/MissingFeed.tsx:73 3416 + msgid "Feed unavailable" 3417 + msgstr "" 3418 + 3419 #: src/view/shell/desktop/RightNav.tsx:102 3420 #: src/view/shell/desktop/RightNav.tsx:103 3421 #: src/view/shell/Drawer.tsx:357 ··· 3433 #: src/screens/StarterPack/StarterPackScreen.tsx:190 3434 #: src/view/screens/Feeds.tsx:511 3435 #: src/view/screens/Profile.tsx:230 3436 + #: src/view/screens/SavedFeeds.tsx:104 3437 #: src/view/shell/desktop/LeftNav.tsx:673 3438 #: src/view/shell/Drawer.tsx:519 3439 msgid "Feeds" 3440 msgstr "" 3441 3442 + #: src/view/screens/SavedFeeds.tsx:206 3443 msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." 3444 msgstr "" 3445 3446 #: src/components/FeedCard.tsx:282 3447 + #: src/view/screens/SavedFeeds.tsx:86 3448 msgctxt "toast" 3449 msgid "Feeds updated!" 3450 msgstr "" ··· 3614 #: src/screens/VideoFeed/index.tsx:848 3615 #: src/view/com/post-thread/PostThreadFollowBtn.tsx:134 3616 #: src/view/screens/Feeds.tsx:602 3617 + #: src/view/screens/SavedFeeds.tsx:420 3618 msgid "Following" 3619 msgstr "" 3620 ··· 3961 msgid "Hidden by your moderation settings." 3962 msgstr "" 3963 3964 + #: src/components/ListCard.tsx:132 3965 msgid "Hidden list" 3966 msgstr "" 3967 3968 #: src/components/interstitials/Trending.tsx:131 3969 #: src/components/interstitials/TrendingVideos.tsx:140 3970 + #: src/components/moderation/ContentHider.tsx:203 3971 #: src/components/moderation/LabelPreference.tsx:135 3972 #: src/components/moderation/PostHider.tsx:134 3973 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:712 ··· 3979 msgid "Hide" 3980 msgstr "" 3981 3982 + #: src/view/com/notifications/NotificationFeedItem.tsx:808 3983 msgctxt "action" 3984 msgid "Hide" 3985 msgstr "" ··· 4030 msgid "Hide trending videos?" 4031 msgstr "" 4032 4033 + #: src/view/com/notifications/NotificationFeedItem.tsx:799 4034 msgid "Hide user list" 4035 msgstr "" 4036 ··· 4039 msgid "Hide verification badges" 4040 msgstr "" 4041 4042 + #: src/components/moderation/ContentHider.tsx:154 4043 #: src/components/moderation/PostHider.tsx:89 4044 msgid "Hides the content" 4045 msgstr "" ··· 4369 msgid "Keep me posted" 4370 msgstr "" 4371 4372 + #: src/components/moderation/ContentHider.tsx:234 4373 msgid "Labeled by {0}." 4374 msgstr "" 4375 4376 + #: src/components/moderation/ContentHider.tsx:232 4377 msgid "Labeled by the author." 4378 msgstr "" 4379 ··· 4446 msgid "Learn more about self hosting your PDS." 4447 msgstr "" 4448 4449 + #: src/components/moderation/ContentHider.tsx:152 4450 msgid "Learn more about the moderation applied to this content" 4451 msgstr "" 4452 4453 + #: src/components/moderation/ContentHider.tsx:218 4454 msgid "Learn more about the moderation applied to this content." 4455 msgstr "" 4456 ··· 4469 msgid "Learn more about what is public on Bluesky." 4470 msgstr "" 4471 4472 + #: src/components/moderation/ContentHider.tsx:242 4473 #: src/view/com/auth/server-input/index.tsx:220 4474 msgid "Learn more." 4475 msgstr "" ··· 4570 msgstr "" 4571 4572 #: src/components/FeedCard.tsx:218 4573 + #: src/view/com/feeds/FeedSourceCard.tsx:172 4574 msgid "Liked by {0, plural, one {# user} other {# users}}" 4575 msgstr "" 4576 ··· 4623 msgid "List blocked" 4624 msgstr "" 4625 4626 + #: src/components/ListCard.tsx:152 4627 + #: src/view/com/feeds/FeedSourceCard.tsx:152 4628 msgid "List by {0}" 4629 msgstr "" 4630 ··· 4634 4635 #: src/view/com/profile/ProfileSubpageHeader.tsx:158 4636 msgid "List by you" 4637 + msgstr "" 4638 + 4639 + #: src/view/com/feeds/MissingFeed.tsx:154 4640 + msgid "List creator" 4641 msgstr "" 4642 4643 #: src/view/screens/ProfileList.tsx:485 ··· 4907 msgid "Moderation details" 4908 msgstr "" 4909 4910 + #: src/components/ListCard.tsx:151 4911 #: src/view/com/modals/UserAddRemoveLists.tsx:222 4912 msgid "Moderation list by {0}" 4913 msgstr "" ··· 5784 msgid "Opens password reset form" 5785 msgstr "" 5786 5787 + #: src/view/com/notifications/NotificationFeedItem.tsx:902 5788 #: src/view/com/util/UserAvatar.tsx:592 5789 msgid "Opens this profile" 5790 msgstr "" ··· 5967 msgid "Pinned {0} to Home" 5968 msgstr "" 5969 5970 + #: src/view/screens/SavedFeeds.tsx:131 5971 msgid "Pinned Feeds" 5972 msgstr "" 5973 ··· 6549 #: src/components/StarterPack/Wizard/WizardListCard.tsx:102 6550 #: src/components/StarterPack/Wizard/WizardListCard.tsx:109 6551 #: src/screens/Settings/Settings.tsx:567 6552 #: src/view/com/modals/UserAddRemoveLists.tsx:235 6553 #: src/view/com/posts/PostFeedErrorMessage.tsx:217 6554 msgid "Remove" ··· 6597 6598 #: src/screens/Profile/components/ProfileFeedHeader.tsx:319 6599 #: src/screens/Profile/components/ProfileFeedHeader.tsx:325 6600 #: src/view/screens/ProfileList.tsx:528 6601 + #: src/view/screens/SavedFeeds.tsx:350 6602 msgid "Remove from my feeds" 6603 msgstr "" 6604 ··· 6612 msgstr "" 6613 6614 #: src/components/FeedCard.tsx:338 6615 msgid "Remove from your feeds?" 6616 msgstr "" 6617 ··· 6673 msgid "Removed from list" 6674 msgstr "" 6675 6676 #: src/screens/List/ListHiddenScreen.tsx:94 6677 msgid "Removed from saved feeds" 6678 msgstr "" ··· 7066 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:153 7067 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:163 7068 #: src/view/com/modals/CreateOrEditList.tsx:315 7069 + #: src/view/screens/SavedFeeds.tsx:117 7070 msgid "Save" 7071 msgstr "" 7072 ··· 7082 7083 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:191 7084 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:200 7085 + #: src/view/screens/SavedFeeds.tsx:113 7086 + #: src/view/screens/SavedFeeds.tsx:117 7087 msgid "Save changes" 7088 msgstr "" 7089 ··· 7109 msgid "Save to my feeds" 7110 msgstr "" 7111 7112 + #: src/view/screens/SavedFeeds.tsx:172 7113 msgid "Saved Feeds" 7114 msgstr "" 7115 ··· 7124 7125 #: src/components/dms/ChatEmptyPill.tsx:33 7126 #: src/components/NewskieDialog.tsx:105 7127 + #: src/view/com/notifications/NotificationFeedItem.tsx:747 7128 + #: src/view/com/notifications/NotificationFeedItem.tsx:772 7129 msgid "Say hello!" 7130 msgstr "" 7131 ··· 7244 msgid "See jobs at Bluesky" 7245 msgstr "" 7246 7247 + #: src/view/screens/SavedFeeds.tsx:213 7248 msgid "See this guide" 7249 msgstr "" 7250 ··· 7607 msgid "Shared Preferences Tester" 7608 msgstr "" 7609 7610 + #: src/components/moderation/ContentHider.tsx:203 7611 #: src/components/moderation/LabelPreference.tsx:137 7612 #: src/components/moderation/PostHider.tsx:134 7613 msgid "Show" ··· 7728 msgid "Shows other accounts you can switch to" 7729 msgstr "" 7730 7731 + #: src/components/moderation/ContentHider.tsx:155 7732 #: src/components/moderation/PostHider.tsx:89 7733 msgid "Shows the content" 7734 msgstr "" ··· 8128 msgid "Tags only" 8129 msgstr "" 8130 8131 + #: src/view/com/feeds/MissingFeed.tsx:89 8132 + msgid "Tap for information" 8133 + msgstr "" 8134 + 8135 + #: src/view/com/feeds/MissingFeed.tsx:44 8136 + msgid "Tap for more information" 8137 + msgstr "" 8138 + 8139 #: src/components/ContextMenu/Backdrop.ios.tsx:54 8140 #: src/components/ContextMenu/Backdrop.ios.tsx:80 8141 #: src/components/ContextMenu/Backdrop.tsx:46 ··· 8358 #: src/screens/Profile/components/ProfileFeedHeader.tsx:178 8359 #: src/view/screens/ProfileList.tsx:375 8360 #: src/view/screens/ProfileList.tsx:394 8361 + #: src/view/screens/SavedFeeds.tsx:93 8362 msgid "There was an issue contacting the server" 8363 msgstr "" 8364 ··· 8367 msgid "There was an issue contacting the server, please check your internet connection and try again." 8368 msgstr "" 8369 8370 #: src/view/com/notifications/NotificationFeed.tsx:124 8371 msgid "There was an issue fetching notifications. Tap here to try again." 8372 msgstr "" ··· 8839 8840 #: src/screens/StarterPack/StarterPackScreen.tsx:673 8841 msgid "Unable to delete" 8842 + msgstr "" 8843 + 8844 + #: src/view/com/feeds/MissingFeed.tsx:126 8845 + msgid "Unavailable feed information" 8846 msgstr "" 8847 8848 #: src/components/dms/MessagesListBlockedFooter.tsx:97 ··· 9495 msgid "Watch now" 9496 msgstr "" 9497 9498 + #: src/view/com/feeds/MissingFeed.tsx:140 9499 + msgid "We could not connect to the service that provides this custom feed. It may be temporarily unavailable and experiencing issues, or permanently unavailable." 9500 + msgstr "" 9501 + 9502 + #: src/view/com/feeds/MissingFeed.tsx:146 9503 + msgid "We could not find this list. It was probably deleted." 9504 + msgstr "" 9505 + 9506 #: src/screens/Hashtag.tsx:205 9507 msgid "We couldn't find any results for that hashtag." 9508 msgstr "" ··· 9870 msgid "You don't have any invite codes yet! We'll send you some when you've been on Bluesky for a little longer." 9871 msgstr "" 9872 9873 + #: src/view/screens/SavedFeeds.tsx:145 9874 msgid "You don't have any pinned feeds." 9875 msgstr "" 9876 9877 + #: src/view/screens/SavedFeeds.tsx:185 9878 msgid "You don't have any saved feeds." 9879 msgstr "" 9880
+158 -293
src/view/com/feeds/FeedSourceCard.tsx
··· 1 - import React from 'react' 2 import { 3 - Linking, 4 - Pressable, 5 - StyleProp, 6 - StyleSheet, 7 - View, 8 - ViewStyle, 9 - } from 'react-native' 10 - import {AtUri} from '@atproto/api' 11 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 12 import {msg, Plural, Trans} from '@lingui/macro' 13 import {useLingui} from '@lingui/react' 14 15 - import {useNavigationDeduped} from '#/lib/hooks/useNavigationDeduped' 16 - import {usePalette} from '#/lib/hooks/usePalette' 17 import {sanitizeHandle} from '#/lib/strings/handles' 18 - import {s} from '#/lib/styles' 19 - import {logger} from '#/logger' 20 - import {FeedSourceInfo, useFeedSourceInfoQuery} from '#/state/queries/feed' 21 import { 22 - useAddSavedFeedsMutation, 23 - usePreferencesQuery, 24 - UsePreferencesQueryResponse, 25 - useRemoveFeedMutation, 26 - } from '#/state/queries/preferences' 27 import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 28 - import * as Toast from '#/view/com/util/Toast' 29 - import {useTheme} from '#/alf' 30 - import {atoms as a} from '#/alf' 31 - import {shouldClickOpenNewTab} from '#/components/Link' 32 - import * as Prompt from '#/components/Prompt' 33 import {RichText} from '#/components/RichText' 34 - import {Text} from '../util/text/Text' 35 - import {UserAvatar} from '../util/UserAvatar' 36 37 - export function FeedSourceCard({ 38 - feedUri, 39 - style, 40 - showSaveBtn = false, 41 - showDescription = false, 42 - showLikes = false, 43 - pinOnSave = false, 44 - showMinimalPlaceholder, 45 - hideTopBorder, 46 - }: { 47 feedUri: string 48 style?: StyleProp<ViewStyle> 49 showSaveBtn?: boolean 50 showDescription?: boolean ··· 52 pinOnSave?: boolean 53 showMinimalPlaceholder?: boolean 54 hideTopBorder?: boolean 55 - }) { 56 - const {data: preferences} = usePreferencesQuery() 57 - const {data: feed} = useFeedSourceInfoQuery({uri: feedUri}) 58 59 return ( 60 <FeedSourceCardLoaded 61 feedUri={feedUri} 62 feed={feed} 63 - preferences={preferences} 64 - style={style} 65 - showSaveBtn={showSaveBtn} 66 - showDescription={showDescription} 67 - showLikes={showLikes} 68 - pinOnSave={pinOnSave} 69 - showMinimalPlaceholder={showMinimalPlaceholder} 70 - hideTopBorder={hideTopBorder} 71 /> 72 ) 73 } ··· 75 export function FeedSourceCardLoaded({ 76 feedUri, 77 feed, 78 - preferences, 79 style, 80 - showSaveBtn = false, 81 showDescription = false, 82 showLikes = false, 83 - pinOnSave = false, 84 showMinimalPlaceholder, 85 hideTopBorder, 86 }: { 87 feedUri: string 88 feed?: FeedSourceInfo 89 - preferences?: UsePreferencesQueryResponse 90 style?: StyleProp<ViewStyle> 91 - showSaveBtn?: boolean 92 showDescription?: boolean 93 showLikes?: boolean 94 - pinOnSave?: boolean 95 showMinimalPlaceholder?: boolean 96 hideTopBorder?: boolean 97 }) { 98 const t = useTheme() 99 - const pal = usePalette('default') 100 const {_} = useLingui() 101 - const removePromptControl = Prompt.usePromptControl() 102 - const navigation = useNavigationDeduped() 103 - 104 - const {isPending: isAddSavedFeedPending, mutateAsync: addSavedFeeds} = 105 - useAddSavedFeedsMutation() 106 - const {isPending: isRemovePending, mutateAsync: removeFeed} = 107 - useRemoveFeedMutation() 108 - 109 - const savedFeedConfig = preferences?.savedFeeds?.find( 110 - f => f.value === feedUri, 111 - ) 112 - const isSaved = Boolean(savedFeedConfig) 113 - 114 - const onSave = React.useCallback(async () => { 115 - if (!feed || isSaved) return 116 - 117 - try { 118 - await addSavedFeeds([ 119 - { 120 - type: 'feed', 121 - value: feed.uri, 122 - pinned: pinOnSave, 123 - }, 124 - ]) 125 - Toast.show(_(msg`Added to my feeds`)) 126 - } catch (e) { 127 - Toast.show(_(msg`There was an issue contacting your server`), 'xmark') 128 - logger.error('Failed to save feed', {message: e}) 129 - } 130 - }, [_, feed, pinOnSave, addSavedFeeds, isSaved]) 131 - 132 - const onUnsave = React.useCallback(async () => { 133 - if (!savedFeedConfig) return 134 - 135 - try { 136 - await removeFeed(savedFeedConfig) 137 - // await item.unsave() 138 - Toast.show(_(msg`Removed from my feeds`)) 139 - } catch (e) { 140 - Toast.show(_(msg`There was an issue contacting your server`), 'xmark') 141 - logger.error('Failed to unsave feed', {message: e}) 142 - } 143 - }, [_, removeFeed, savedFeedConfig]) 144 - 145 - const onToggleSaved = React.useCallback(async () => { 146 - if (isSaved) { 147 - removePromptControl.open() 148 - } else { 149 - await onSave() 150 - } 151 - }, [isSaved, removePromptControl, onSave]) 152 153 /* 154 * LOAD STATE ··· 156 * This state also captures the scenario where a feed can't load for whatever 157 * reason. 158 */ 159 - if (!feed || !preferences) 160 return ( 161 - <View 162 style={[ 163 - pal.border, 164 - { 165 - borderTopWidth: 166 - showMinimalPlaceholder || hideTopBorder 167 - ? 0 168 - : StyleSheet.hairlineWidth, 169 - flexDirection: 'row', 170 - alignItems: 'center', 171 - flex: 1, 172 - paddingRight: 18, 173 - }, 174 - ]}> 175 - {showMinimalPlaceholder ? ( 176 - <FeedLoadingPlaceholder 177 - style={{flex: 1}} 178 - showTopBorder={false} 179 - showLowerPlaceholder={false} 180 - /> 181 - ) : ( 182 - <FeedLoadingPlaceholder style={{flex: 1}} showTopBorder={false} /> 183 - )} 184 - 185 - {showSaveBtn && ( 186 - <Pressable 187 - testID={`feed-${feedUri}-toggleSave`} 188 - disabled={isRemovePending} 189 - accessibilityRole="button" 190 - accessibilityLabel={_(msg`Remove from my feeds`)} 191 - accessibilityHint="" 192 - onPress={onUnsave} 193 - hitSlop={15} 194 - style={styles.btn}> 195 - <FontAwesomeIcon 196 - icon={['far', 'trash-can']} 197 - size={19} 198 - color={pal.colors.icon} 199 - /> 200 - </Pressable> 201 - )} 202 - </View> 203 ) 204 205 - return ( 206 <> 207 - <Pressable 208 - testID={`feed-${feed.displayName}`} 209 - accessibilityRole="button" 210 - style={[ 211 - styles.container, 212 - pal.border, 213 - style, 214 - {borderTopWidth: hideTopBorder ? 0 : StyleSheet.hairlineWidth}, 215 - ]} 216 - onPress={e => { 217 - const shouldOpenInNewTab = shouldClickOpenNewTab(e) 218 - if (feed.type === 'feed') { 219 - if (shouldOpenInNewTab) { 220 - Linking.openURL( 221 - `/profile/${feed.creatorDid}/feed/${new AtUri(feed.uri).rkey}`, 222 - ) 223 - } else { 224 - navigation.push('ProfileFeed', { 225 - name: feed.creatorDid, 226 - rkey: new AtUri(feed.uri).rkey, 227 - }) 228 - } 229 - } else if (feed.type === 'list') { 230 - if (shouldOpenInNewTab) { 231 - Linking.openURL( 232 - `/profile/${feed.creatorDid}/lists/${new AtUri(feed.uri).rkey}`, 233 - ) 234 - } else { 235 - navigation.push('ProfileList', { 236 - name: feed.creatorDid, 237 - rkey: new AtUri(feed.uri).rkey, 238 - }) 239 - } 240 - } 241 - }} 242 - key={feed.uri}> 243 - <View style={[styles.headerContainer, a.align_center]}> 244 - <View style={[s.mr10]}> 245 - <UserAvatar type="algo" size={36} avatar={feed.avatar} /> 246 - </View> 247 - <View style={[styles.headerTextContainer]}> 248 - <Text emoji style={[pal.text, s.bold]} numberOfLines={1}> 249 - {feed.displayName} 250 - </Text> 251 - <Text style={[pal.textLight]} numberOfLines={1}> 252 - {feed.type === 'feed' ? ( 253 - <Trans>Feed by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> 254 - ) : ( 255 - <Trans>List by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> 256 - )} 257 - </Text> 258 - </View> 259 - 260 - {showSaveBtn && ( 261 - <View style={{alignSelf: 'center'}}> 262 - <Pressable 263 - testID={`feed-${feed.displayName}-toggleSave`} 264 - disabled={isAddSavedFeedPending || isRemovePending} 265 - accessibilityRole="button" 266 - accessibilityLabel={ 267 - isSaved 268 - ? _(msg`Remove from my feeds`) 269 - : _(msg`Add to my feeds`) 270 - } 271 - accessibilityHint="" 272 - onPress={onToggleSaved} 273 - hitSlop={15} 274 - style={styles.btn}> 275 - {isSaved ? ( 276 - <FontAwesomeIcon 277 - icon={['far', 'trash-can']} 278 - size={19} 279 - color={pal.colors.icon} 280 - /> 281 - ) : ( 282 - <FontAwesomeIcon 283 - icon="plus" 284 - size={18} 285 - color={pal.colors.link} 286 - /> 287 - )} 288 - </Pressable> 289 - </View> 290 - )} 291 </View> 292 - 293 - {showDescription && feed.description ? ( 294 - <RichText 295 - style={[t.atoms.text_contrast_high, styles.description]} 296 - value={feed.description} 297 - numberOfLines={3} 298 - /> 299 - ) : null} 300 - 301 - {showLikes && feed.type === 'feed' ? ( 302 - <Text type="sm-medium" style={[pal.text, pal.textLight]}> 303 - <Trans> 304 - Liked by{' '} 305 - <Plural 306 - value={feed.likeCount || 0} 307 - one="# user" 308 - other="# users" 309 - /> 310 - </Trans> 311 </Text> 312 - ) : null} 313 - </Pressable> 314 - 315 - <Prompt.Basic 316 - control={removePromptControl} 317 - title={_(msg`Remove from your feeds?`)} 318 - description={_( 319 - msg`Are you sure you want to remove ${feed.displayName} from your feeds?`, 320 - )} 321 - onConfirm={onUnsave} 322 - confirmButtonCta={_(msg`Remove`)} 323 - confirmButtonColor="negative" 324 - /> 325 </> 326 ) 327 - } 328 329 - const styles = StyleSheet.create({ 330 - container: { 331 - paddingHorizontal: 18, 332 - paddingVertical: 20, 333 - flexDirection: 'column', 334 - flex: 1, 335 - gap: 14, 336 - }, 337 - border: { 338 - borderTopWidth: StyleSheet.hairlineWidth, 339 - }, 340 - headerContainer: { 341 - flexDirection: 'row', 342 - }, 343 - headerTextContainer: { 344 - flexDirection: 'column', 345 - columnGap: 4, 346 - flex: 1, 347 - }, 348 - description: { 349 - flex: 1, 350 - flexWrap: 'wrap', 351 - }, 352 - btn: { 353 - paddingVertical: 6, 354 - }, 355 - })
··· 1 + import {type StyleProp, View, type ViewStyle} from 'react-native' 2 import { 3 + type $Typed, 4 + AppBskyFeedDefs, 5 + type AppBskyGraphDefs, 6 + AtUri, 7 + } from '@atproto/api' 8 import {msg, Plural, Trans} from '@lingui/macro' 9 import {useLingui} from '@lingui/react' 10 11 import {sanitizeHandle} from '#/lib/strings/handles' 12 import { 13 + type FeedSourceInfo, 14 + hydrateFeedGenerator, 15 + hydrateList, 16 + useFeedSourceInfoQuery, 17 + } from '#/state/queries/feed' 18 import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' 19 + import {UserAvatar} from '#/view/com/util/UserAvatar' 20 + import {atoms as a, useTheme} from '#/alf' 21 + import {Link} from '#/components/Link' 22 import {RichText} from '#/components/RichText' 23 + import {Text} from '#/components/Typography' 24 + import {MissingFeed} from './MissingFeed' 25 26 + type FeedSourceCardProps = { 27 feedUri: string 28 + feedData?: 29 + | $Typed<AppBskyFeedDefs.GeneratorView> 30 + | $Typed<AppBskyGraphDefs.ListView> 31 style?: StyleProp<ViewStyle> 32 showSaveBtn?: boolean 33 showDescription?: boolean ··· 35 pinOnSave?: boolean 36 showMinimalPlaceholder?: boolean 37 hideTopBorder?: boolean 38 + link?: boolean 39 + } 40 + 41 + export function FeedSourceCard({ 42 + feedUri, 43 + feedData, 44 + ...props 45 + }: FeedSourceCardProps) { 46 + if (feedData) { 47 + let feed: FeedSourceInfo 48 + if (AppBskyFeedDefs.isGeneratorView(feedData)) { 49 + feed = hydrateFeedGenerator(feedData) 50 + } else { 51 + feed = hydrateList(feedData) 52 + } 53 + return <FeedSourceCardLoaded feedUri={feedUri} feed={feed} {...props} /> 54 + } else { 55 + return <FeedSourceCardWithoutData feedUri={feedUri} {...props} /> 56 + } 57 + } 58 + 59 + export function FeedSourceCardWithoutData({ 60 + feedUri, 61 + ...props 62 + }: Omit<FeedSourceCardProps, 'feedData'>) { 63 + const {data: feed, error} = useFeedSourceInfoQuery({ 64 + uri: feedUri, 65 + }) 66 67 return ( 68 <FeedSourceCardLoaded 69 feedUri={feedUri} 70 feed={feed} 71 + error={error} 72 + {...props} 73 /> 74 ) 75 } ··· 77 export function FeedSourceCardLoaded({ 78 feedUri, 79 feed, 80 style, 81 showDescription = false, 82 showLikes = false, 83 showMinimalPlaceholder, 84 hideTopBorder, 85 + link = true, 86 + error, 87 }: { 88 feedUri: string 89 feed?: FeedSourceInfo 90 style?: StyleProp<ViewStyle> 91 showDescription?: boolean 92 showLikes?: boolean 93 showMinimalPlaceholder?: boolean 94 hideTopBorder?: boolean 95 + link?: boolean 96 + error?: unknown 97 }) { 98 const t = useTheme() 99 const {_} = useLingui() 100 101 /* 102 * LOAD STATE ··· 104 * This state also captures the scenario where a feed can't load for whatever 105 * reason. 106 */ 107 + if (!feed) { 108 + if (error) { 109 + return ( 110 + <MissingFeed 111 + uri={feedUri} 112 + style={style} 113 + hideTopBorder={hideTopBorder} 114 + error={error} 115 + /> 116 + ) 117 + } 118 + 119 return ( 120 + <FeedLoadingPlaceholder 121 style={[ 122 + t.atoms.border_contrast_low, 123 + !(showMinimalPlaceholder || hideTopBorder) && a.border_t, 124 + a.flex_1, 125 + style, 126 + ]} 127 + showTopBorder={false} 128 + showLowerPlaceholder={!showMinimalPlaceholder} 129 + /> 130 ) 131 + } 132 133 + const inner = ( 134 <> 135 + <View style={[a.flex_row, a.align_center]}> 136 + <View style={[a.mr_md]}> 137 + <UserAvatar type="algo" size={36} avatar={feed.avatar} /> 138 </View> 139 + <View style={[a.flex_1]}> 140 + <Text 141 + emoji 142 + style={[a.text_sm, a.font_bold, a.leading_snug]} 143 + numberOfLines={1}> 144 + {feed.displayName} 145 + </Text> 146 + <Text 147 + style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_snug]} 148 + numberOfLines={1}> 149 + {feed.type === 'feed' ? ( 150 + <Trans>Feed by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> 151 + ) : ( 152 + <Trans>List by {sanitizeHandle(feed.creatorHandle, '@')}</Trans> 153 + )} 154 </Text> 155 + </View> 156 + </View> 157 + {showDescription && feed.description ? ( 158 + <RichText 159 + style={[t.atoms.text_contrast_high, a.flex_1, a.flex_wrap]} 160 + value={feed.description} 161 + numberOfLines={3} 162 + /> 163 + ) : null} 164 + {showLikes && feed.type === 'feed' ? ( 165 + <Text 166 + style={[ 167 + a.text_sm, 168 + a.font_bold, 169 + t.atoms.text_contrast_medium, 170 + a.leading_snug, 171 + ]}> 172 + <Trans> 173 + Liked by{' '} 174 + <Plural value={feed.likeCount || 0} one="# user" other="# users" /> 175 + </Trans> 176 + </Text> 177 + ) : null} 178 </> 179 ) 180 181 + if (link) { 182 + return ( 183 + <Link 184 + testID={`feed-${feed.displayName}`} 185 + label={_( 186 + feed.type === 'feed' 187 + ? msg`${feed.displayName}, a feed by ${sanitizeHandle(feed.creatorHandle, '@')}, liked by ${feed.likeCount || 0}` 188 + : msg`${feed.displayName}, a list by ${sanitizeHandle(feed.creatorHandle, '@')}`, 189 + )} 190 + to={{ 191 + screen: feed.type === 'feed' ? 'ProfileFeed' : 'ProfileList', 192 + params: {name: feed.creatorDid, rkey: new AtUri(feed.uri).rkey}, 193 + }} 194 + style={[ 195 + a.flex_1, 196 + a.p_lg, 197 + a.gap_md, 198 + !hideTopBorder && !a.border_t, 199 + t.atoms.border_contrast_low, 200 + style, 201 + ]}> 202 + {inner} 203 + </Link> 204 + ) 205 + } else { 206 + return ( 207 + <View 208 + style={[ 209 + a.flex_1, 210 + a.p_lg, 211 + a.gap_md, 212 + !hideTopBorder && !a.border_t, 213 + t.atoms.border_contrast_low, 214 + style, 215 + ]}> 216 + {inner} 217 + </View> 218 + ) 219 + } 220 + }
+222
src/view/com/feeds/MissingFeed.tsx
···
··· 1 + import {type StyleProp, View, type ViewStyle} from 'react-native' 2 + import {AtUri} from '@atproto/api' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + 6 + import {cleanError} from '#/lib/strings/errors' 7 + import {isNative, isWeb} from '#/platform/detection' 8 + import {useModerationOpts} from '#/state/preferences/moderation-opts' 9 + import {getFeedTypeFromUri} from '#/state/queries/feed' 10 + import {useProfileQuery} from '#/state/queries/profile' 11 + import {atoms as a, useTheme, web} from '#/alf' 12 + import {Button, ButtonText} from '#/components/Button' 13 + import * as Dialog from '#/components/Dialog' 14 + import {Divider} from '#/components/Divider' 15 + import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 16 + import * as ProfileCard from '#/components/ProfileCard' 17 + import {Text} from '#/components/Typography' 18 + 19 + export function MissingFeed({ 20 + style, 21 + hideTopBorder, 22 + uri, 23 + error, 24 + }: { 25 + style?: StyleProp<ViewStyle> 26 + hideTopBorder?: boolean 27 + uri: string 28 + error?: unknown 29 + }) { 30 + const t = useTheme() 31 + const {_} = useLingui() 32 + const control = Dialog.useDialogControl() 33 + 34 + const type = getFeedTypeFromUri(uri) 35 + 36 + return ( 37 + <> 38 + <Button 39 + label={ 40 + type === 'feed' 41 + ? _(msg`Could not connect to custom feed`) 42 + : _(msg`Deleted list`) 43 + } 44 + accessibilityHint={_(msg`Tap for more information`)} 45 + onPress={control.open} 46 + style={[ 47 + a.flex_1, 48 + a.p_lg, 49 + a.gap_md, 50 + !hideTopBorder && !a.border_t, 51 + t.atoms.border_contrast_low, 52 + a.justify_start, 53 + style, 54 + ]}> 55 + <View style={[a.flex_row, a.align_center]}> 56 + <View 57 + style={[ 58 + {width: 36, height: 36}, 59 + t.atoms.bg_contrast_25, 60 + a.rounded_sm, 61 + a.mr_md, 62 + a.align_center, 63 + a.justify_center, 64 + ]}> 65 + <WarningIcon size="lg" /> 66 + </View> 67 + <View style={[a.flex_1]}> 68 + <Text 69 + emoji 70 + style={[a.text_sm, a.font_bold, a.leading_snug, a.italic]} 71 + numberOfLines={1}> 72 + {type === 'feed' ? ( 73 + <Trans>Feed unavailable</Trans> 74 + ) : ( 75 + <Trans>Deleted list</Trans> 76 + )} 77 + </Text> 78 + <Text 79 + style={[ 80 + a.text_sm, 81 + t.atoms.text_contrast_medium, 82 + a.leading_snug, 83 + a.italic, 84 + ]} 85 + numberOfLines={1}> 86 + {isWeb ? ( 87 + <Trans>Click for information</Trans> 88 + ) : ( 89 + <Trans>Tap for information</Trans> 90 + )} 91 + </Text> 92 + </View> 93 + </View> 94 + </Button> 95 + 96 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 97 + <Dialog.Handle /> 98 + <DialogInner uri={uri} type={type} error={error} /> 99 + </Dialog.Outer> 100 + </> 101 + ) 102 + } 103 + 104 + function DialogInner({ 105 + uri, 106 + type, 107 + error, 108 + }: { 109 + uri: string 110 + type: 'feed' | 'list' 111 + error: unknown 112 + }) { 113 + const control = Dialog.useDialogContext() 114 + const t = useTheme() 115 + const {_} = useLingui() 116 + const atUri = new AtUri(uri) 117 + const {data: profile, isError: isProfileError} = useProfileQuery({ 118 + did: atUri.host, 119 + }) 120 + const moderationOpts = useModerationOpts() 121 + 122 + return ( 123 + <Dialog.ScrollableInner 124 + label={ 125 + type === 'feed' 126 + ? _(msg`Unavailable feed information`) 127 + : _(msg`Deleted list`) 128 + } 129 + style={web({maxWidth: 500})}> 130 + <View style={[a.gap_sm]}> 131 + <Text style={[a.font_heavy, a.text_2xl]}> 132 + {type === 'feed' ? ( 133 + <Trans>Could not connect to feed service</Trans> 134 + ) : ( 135 + <Trans>Deleted list</Trans> 136 + )} 137 + </Text> 138 + <Text style={[t.atoms.text_contrast_high, a.leading_snug]}> 139 + {type === 'feed' ? ( 140 + <Trans> 141 + We could not connect to the service that provides this custom 142 + feed. It may be temporarily unavailable and experiencing issues, 143 + or permanently unavailable. 144 + </Trans> 145 + ) : ( 146 + <Trans>We could not find this list. It was probably deleted.</Trans> 147 + )} 148 + </Text> 149 + <Divider style={[a.my_md]} /> 150 + <Text style={[a.font_bold, t.atoms.text_contrast_high]}> 151 + {type === 'feed' ? ( 152 + <Trans>Feed creator</Trans> 153 + ) : ( 154 + <Trans>List creator</Trans> 155 + )} 156 + </Text> 157 + {profile && moderationOpts && ( 158 + <View style={[a.w_full, a.align_start]}> 159 + <ProfileCard.Link profile={profile} onPress={() => control.close()}> 160 + <ProfileCard.Header> 161 + <ProfileCard.Avatar 162 + profile={profile} 163 + moderationOpts={moderationOpts} 164 + disabledPreview 165 + /> 166 + <ProfileCard.NameAndHandle 167 + profile={profile} 168 + moderationOpts={moderationOpts} 169 + /> 170 + </ProfileCard.Header> 171 + </ProfileCard.Link> 172 + </View> 173 + )} 174 + {isProfileError && ( 175 + <Text 176 + style={[ 177 + t.atoms.text_contrast_high, 178 + a.italic, 179 + a.text_center, 180 + a.w_full, 181 + ]}> 182 + <Trans>Could not find profile</Trans> 183 + </Text> 184 + )} 185 + {type === 'feed' && ( 186 + <> 187 + <Text style={[a.font_bold, t.atoms.text_contrast_high, a.mt_md]}> 188 + <Trans>Feed identifier</Trans> 189 + </Text> 190 + <Text style={[a.text_md, t.atoms.text_contrast_high, a.italic]}> 191 + {atUri.rkey} 192 + </Text> 193 + </> 194 + )} 195 + {error instanceof Error && ( 196 + <> 197 + <Text style={[a.font_bold, t.atoms.text_contrast_high, a.mt_md]}> 198 + <Trans>Error message</Trans> 199 + </Text> 200 + <Text style={[a.text_md, t.atoms.text_contrast_high, a.italic]}> 201 + {cleanError(error.message)} 202 + </Text> 203 + </> 204 + )} 205 + </View> 206 + {isNative && ( 207 + <Button 208 + label={_(msg`Close`)} 209 + onPress={() => control.close()} 210 + size="small" 211 + variant="solid" 212 + color="secondary" 213 + style={[a.mt_5xl]}> 214 + <ButtonText> 215 + <Trans>Close</Trans> 216 + </ButtonText> 217 + </Button> 218 + )} 219 + <Dialog.Close /> 220 + </Dialog.ScrollableInner> 221 + ) 222 + }
+2 -1
src/view/com/notifications/NotificationFeedItem.tsx
··· 671 {item.type === 'feedgen-like' && item.subjectUri ? ( 672 <FeedSourceCard 673 feedUri={item.subjectUri} 674 style={[ 675 t.atoms.bg, 676 t.atoms.border_contrast_low, 677 a.border, 678 styles.feedcard, 679 ]} 680 showLikes ··· 1000 }, 1001 feedcard: { 1002 borderRadius: 8, 1003 - paddingVertical: 12, 1004 marginTop: 6, 1005 }, 1006 addedContainer: {
··· 671 {item.type === 'feedgen-like' && item.subjectUri ? ( 672 <FeedSourceCard 673 feedUri={item.subjectUri} 674 + link={false} 675 style={[ 676 t.atoms.bg, 677 t.atoms.border_contrast_low, 678 a.border, 679 + a.p_md, 680 styles.feedcard, 681 ]} 682 showLikes ··· 1002 }, 1003 feedcard: { 1004 borderRadius: 8, 1005 marginTop: 6, 1006 }, 1007 addedContainer: {
+7 -14
src/view/com/util/LoadingPlaceholder.tsx
··· 1 import {useMemo} from 'react' 2 import { 3 - DimensionValue, 4 - StyleProp, 5 StyleSheet, 6 View, 7 - ViewStyle, 8 } from 'react-native' 9 10 import {usePalette} from '#/lib/hooks/usePalette' ··· 233 <View 234 style={[ 235 { 236 - paddingHorizontal: 12, 237 - paddingVertical: 18, 238 borderTopWidth: showTopBorder ? StyleSheet.hairlineWidth : 0, 239 }, 240 pal.border, ··· 244 <LoadingPlaceholder 245 width={36} 246 height={36} 247 - style={[styles.avatar, {borderRadius: 6}]} 248 /> 249 <View style={[s.flex1]}> 250 <LoadingPlaceholder width={100} height={8} style={[s.mt5, s.mb10]} /> ··· 252 </View> 253 </View> 254 {showLowerPlaceholder && ( 255 - <View style={{paddingHorizontal: 5, marginTop: 10}}> 256 - <LoadingPlaceholder 257 - width={260} 258 - height={8} 259 - style={{marginVertical: 12}} 260 - /> 261 <LoadingPlaceholder width={120} height={8} /> 262 </View> 263 )} ··· 352 }, 353 avatar: { 354 borderRadius: 999, 355 - marginRight: 10, 356 - marginLeft: 8, 357 }, 358 notification: { 359 flexDirection: 'row',
··· 1 import {useMemo} from 'react' 2 import { 3 + type DimensionValue, 4 + type StyleProp, 5 StyleSheet, 6 View, 7 + type ViewStyle, 8 } from 'react-native' 9 10 import {usePalette} from '#/lib/hooks/usePalette' ··· 233 <View 234 style={[ 235 { 236 + padding: 16, 237 borderTopWidth: showTopBorder ? StyleSheet.hairlineWidth : 0, 238 }, 239 pal.border, ··· 243 <LoadingPlaceholder 244 width={36} 245 height={36} 246 + style={[styles.avatar, {borderRadius: 8}]} 247 /> 248 <View style={[s.flex1]}> 249 <LoadingPlaceholder width={100} height={8} style={[s.mt5, s.mb10]} /> ··· 251 </View> 252 </View> 253 {showLowerPlaceholder && ( 254 + <View style={{marginTop: 12}}> 255 <LoadingPlaceholder width={120} height={8} /> 256 </View> 257 )} ··· 346 }, 347 avatar: { 348 borderRadius: 999, 349 + marginRight: 12, 350 }, 351 notification: { 352 flexDirection: 'row',
+7 -16
src/view/screens/SavedFeeds.tsx
··· 36 import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk' 37 import * as Layout from '#/components/Layout' 38 import {Loader} from '#/components/Loader' 39 40 type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'> 41 export function SavedFeeds({}: Props) { ··· 296 <FeedSourceCard 297 key={feedUri} 298 feedUri={feedUri} 299 - style={[isPinned && {paddingRight: 8}]} 300 showMinimalPlaceholder 301 hideTopBorder={true} 302 /> ··· 391 function FollowingFeedCard() { 392 const t = useTheme() 393 return ( 394 - <View 395 - style={[ 396 - a.flex_row, 397 - a.align_center, 398 - a.flex_1, 399 - { 400 - paddingHorizontal: 18, 401 - paddingVertical: 20, 402 - }, 403 - ]}> 404 <View 405 style={[ 406 a.align_center, 407 a.justify_center, 408 a.rounded_sm, 409 { 410 width: 36, 411 height: 36, 412 backgroundColor: t.palette.primary_500, 413 - marginRight: 10, 414 }, 415 ]}> 416 <FilterTimeline ··· 423 fill={t.palette.white} 424 /> 425 </View> 426 - <View 427 - style={{flex: 1, flexDirection: 'row', gap: 8, alignItems: 'center'}}> 428 - <Text type="lg-medium" style={[t.atoms.text]} numberOfLines={1}> 429 <Trans>Following</Trans> 430 - </Text> 431 </View> 432 </View> 433 )
··· 36 import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk' 37 import * as Layout from '#/components/Layout' 38 import {Loader} from '#/components/Loader' 39 + import {Text as NewText} from '#/components/Typography' 40 41 type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'> 42 export function SavedFeeds({}: Props) { ··· 297 <FeedSourceCard 298 key={feedUri} 299 feedUri={feedUri} 300 + style={[isPinned && a.pr_sm]} 301 showMinimalPlaceholder 302 hideTopBorder={true} 303 /> ··· 392 function FollowingFeedCard() { 393 const t = useTheme() 394 return ( 395 + <View style={[a.flex_row, a.align_center, a.flex_1, a.p_lg]}> 396 <View 397 style={[ 398 a.align_center, 399 a.justify_center, 400 a.rounded_sm, 401 + a.mr_md, 402 { 403 width: 36, 404 height: 36, 405 backgroundColor: t.palette.primary_500, 406 }, 407 ]}> 408 <FilterTimeline ··· 415 fill={t.palette.white} 416 /> 417 </View> 418 + <View style={[a.flex_1, a.flex_row, a.gap_sm, a.align_center]}> 419 + <NewText style={[a.text_sm, a.font_bold, a.leading_snug]}> 420 <Trans>Following</Trans> 421 + </NewText> 422 </View> 423 </View> 424 )