diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 8b133a0912..a4af3a3233 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -1638,31 +1638,6 @@ "count": 2 } }, - "src/view/com/notifications/NotificationFeed.tsx": { - "typescript/no-explicit-any": { - "count": 2 - }, - "typescript/no-floating-promises": { - "count": 1 - }, - "typescript/no-misused-promises": { - "count": 2 - }, - "typescript/no-unsafe-member-access": { - "count": 6 - } - }, - "src/view/com/notifications/NotificationFeedItem.tsx": { - "typescript/no-explicit-any": { - "count": 3 - }, - "typescript/no-misused-promises": { - "count": 3 - }, - "typescript/no-unsafe-member-access": { - "count": 2 - } - }, "src/view/com/pager/Pager.tsx": { "typescript/no-explicit-any": { "count": 4 diff --git a/package.json b/package.json index 50fe793a1a..c6dbf8949b 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "prettier": "prettier --check ." }, "dependencies": { - "@atproto/api": "0.20.33", + "@atproto/api": "0.20.34", "@atproto/common-web": "0.5.6", "@atproto/syntax": "0.7.2", "@bitdrift/react-native": "^0.6.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86c3430c04..f49ea4a226 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,8 +242,8 @@ importers: .: dependencies: '@atproto/api': - specifier: 0.20.33 - version: 0.20.33 + specifier: 0.20.34 + version: 0.20.34 '@atproto/common-web': specifier: 0.5.6 version: 0.5.6 @@ -871,8 +871,8 @@ packages: graphql: optional: true - '@atproto/api@0.20.33': - resolution: {integrity: sha512-3YpnBVMieQFWetLvqibn2yG6vhNZ7ozSi/EUujuGcvYXt9g/NrvmHufaUtrXCasp2mHDI7t7UG5sLEBg6YNEJw==} + '@atproto/api@0.20.34': + resolution: {integrity: sha512-nKfCLkH2Al58YupLGtoVIucZ5XjtN9sU5oOTI70lp8J4nxl52C/Tk7AktcWe+Nx7st3I0fUgf+C5cHePgvwT0w==} engines: {node: '>=22'} '@atproto/common-web@0.5.6': @@ -9389,7 +9389,7 @@ snapshots: '@0no-co/graphql.web@1.2.0': {} - '@atproto/api@0.20.33': + '@atproto/api@0.20.34': dependencies: '@atproto/common-web': 0.5.6 '@atproto/lexicon': 0.7.7 diff --git a/src/state/queries/notifications/__tests__/util.test.ts b/src/state/queries/notifications/__tests__/util.test.ts new file mode 100644 index 0000000000..d7bcb5e5d0 --- /dev/null +++ b/src/state/queries/notifications/__tests__/util.test.ts @@ -0,0 +1,62 @@ +import {type AppBskyNotificationListNotifications} from '@atproto/api' +import {describe, expect, it, jest} from '@jest/globals' + +import {groupNotifications} from '../util' + +jest.mock('#/state/queries/profile', () => ({precacheProfile: jest.fn()})) + +type Notification = AppBskyNotificationListNotifications.Notification + +function makeFollowNotification( + did: string, + starterPackUri?: string, +): Notification { + return { + uri: `at://${did}/app.bsky.graph.follow/follow`, + cid: `cid-${did}`, + author: { + did, + handle: `${did}.test`, + displayName: did, + avatar: undefined, + associated: undefined, + viewer: {}, + labels: [], + createdAt: '2026-07-28T12:00:00.000Z', + }, + reason: 'follow', + record: {}, + starterPack: starterPackUri + ? ({uri: starterPackUri} as Notification['starterPack']) + : undefined, + isRead: false, + indexedAt: '2026-07-28T12:00:00.000Z', + } +} + +describe('groupNotifications', () => { + it('groups follows by starter pack', () => { + const packA = 'at://did:plc:alice/app.bsky.graph.starterpack/a' + const packB = 'at://did:plc:bob/app.bsky.graph.starterpack/b' + + const grouped = groupNotifications([ + makeFollowNotification('did:plc:a', packA), + makeFollowNotification('did:plc:b', packB), + makeFollowNotification('did:plc:c', packA), + makeFollowNotification('did:plc:d'), + makeFollowNotification('did:plc:e', packB), + makeFollowNotification('did:plc:f'), + ]) + + expect( + grouped.map(item => [ + item.notification.author.did, + ...(item.additional ?? []).map(notification => notification.author.did), + ]), + ).toEqual([ + ['did:plc:a', 'did:plc:c'], + ['did:plc:b', 'did:plc:e'], + ['did:plc:d', 'did:plc:f'], + ]) + }) +}) diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index ded66fb62e..e5d1d81885 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -163,6 +163,9 @@ export function groupNotifications( Math.abs(ts2 - ts) < MS_2DAY && notif.reason === groupedNotif.notification.reason && notif.reasonSubject === groupedNotif.notification.reasonSubject && + (notif.reason !== 'follow' || + notif.starterPack?.uri === + groupedNotif.notification.starterPack?.uri) && (notif.author.did !== groupedNotif.notification.author.did || notif.reason === 'subscribed-post') ) { diff --git a/src/view/com/notifications/NotificationFeed.tsx b/src/view/com/notifications/NotificationFeed.tsx index a4c4c6a973..82b5008078 100644 --- a/src/view/com/notifications/NotificationFeed.tsx +++ b/src/view/com/notifications/NotificationFeed.tsx @@ -1,31 +1,37 @@ import {useCallback, useEffect, useMemo, useState} from 'react' -import { - ActivityIndicator, - type ListRenderItemInfo, - StyleSheet, - View, -} from 'react-native' -import {msg} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' +import {ActivityIndicator, type ListRenderItemInfo, View} from 'react-native' +import {useLingui} from '@lingui/react/macro' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' import {cleanError} from '#/lib/strings/errors' -import {s} from '#/lib/styles' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useNotificationFeedQuery} from '#/state/queries/notifications/feed' +import { + type FeedNotification, + useNotificationFeedQuery, +} from '#/state/queries/notifications/feed' import {EmptyState} from '#/view/com/util/EmptyState' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {List, type ListProps, type ListRef} from '#/view/com/util/List' import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn' +import {atoms as a, platform} from '#/alf' import {Bell_Stroke2_Corner0_Rounded as BellIcon} from '#/components/icons/Bell' import {NotificationFeedItem} from './NotificationFeedItem' -const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} -const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} -const LOADING_ITEM = {_reactKey: '__loading__'} +const EMPTY_FEED_ITEM = {type: 'empty', _reactKey: '__empty__'} as const +const LOAD_MORE_ERROR_ITEM = { + type: 'load-more-error', + _reactKey: '__load_more_error__', +} as const +const LOADING_ITEM = {type: 'loading', _reactKey: '__loading__'} as const + +type NotificationFeedListItem = + | FeedNotification + | typeof EMPTY_FEED_ITEM + | typeof LOAD_MORE_ERROR_ITEM + | typeof LOADING_ITEM export function NotificationFeed({ filter, @@ -46,7 +52,7 @@ export function NotificationFeed({ }) { const initialNumToRender = useInitialNumToRender() const [isPTRing, setIsPTRing] = useState(false) - const {_} = useLingui() + const {t: l} = useLingui() const moderationOpts = useModerationOpts() const trackPostView = usePostViewTracking('Notifications') const { @@ -71,7 +77,7 @@ export function NotificationFeed({ !isFetching && !data?.pages.find(page => page.items.length > 0) const items = useMemo(() => { - let arr: any[] = [] + let arr: NotificationFeedListItem[] = [] if (isFetched) { if (isEmpty) { arr = arr.concat([EMPTY_FEED_ITEM]) @@ -113,29 +119,27 @@ export function NotificationFeed({ }, [isFetching, hasNextPage, isError, fetchNextPage]) const onPressRetryLoadMore = useCallback(() => { - fetchNextPage() + void fetchNextPage() }, [fetchNextPage]) const renderItem = useCallback( - ({item, index}: ListRenderItemInfo) => { - if (item === EMPTY_FEED_ITEM) { + ({item, index}: ListRenderItemInfo) => { + if (item.type === 'empty') { return ( ) - } else if (item === LOAD_MORE_ERROR_ITEM) { + } else if (item.type === 'load-more-error') { return ( ) - } else if (item === LOADING_ITEM) { + } else if (item.type === 'loading') { return } return ( @@ -147,13 +151,13 @@ export function NotificationFeed({ /> ) }, - [moderationOpts, _, onPressRetryLoadMore, filter], + [moderationOpts, l, onPressRetryLoadMore, filter], ) const FeedFooter = useCallback( () => isFetchingNextPage ? ( - + ) : ( @@ -169,7 +173,11 @@ export function NotificationFeed({ }, [enabled]) return ( - + {error && ( item._reactKey} + keyExtractor={(item: NotificationFeedListItem) => item._reactKey} renderItem={renderItem} ListHeaderComponent={ListHeaderComponent} ListFooterComponent={FeedFooter} refreshing={isPTRing} - onRefresh={onRefresh} - onEndReached={onEndReached} + onRefresh={() => void onRefresh()} + onEndReached={() => void onEndReached()} onEndReachedThreshold={2} onScrolledDownChange={onScrolledDownChange} - onItemSeen={item => { + onItemSeen={(item: NotificationFeedListItem) => { if ( (item.type === 'reply' || item.type === 'mention' || @@ -199,7 +207,7 @@ export function NotificationFeed({ trackPostView(item.subject) } }} - contentContainerStyle={s.contentContainer} + contentContainerStyle={{paddingBottom: 200}} desktopFixedHeight initialNumToRender={initialNumToRender} windowSize={11} @@ -209,8 +217,3 @@ export function NotificationFeed({ ) } - -const styles = StyleSheet.create({ - feedFooter: {paddingTop: 20}, - emptyState: {paddingVertical: 40}, -}) diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index a89431938d..49abf2f3ff 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -12,16 +12,17 @@ import { type AppBskyActorDefs, type AppBskyFeedDefs, AppBskyFeedPost, + type AppBskyGraphDefs, AppBskyGraphFollow, + AppBskyGraphStarterpack, + AtUri, moderateProfile, type ModerationDecision, type ModerationOpts, } from '@atproto/api' -import {AtUri} from '@atproto/api' import {TID} from '@atproto/common-web' -import {msg, plural} from '@lingui/core/macro' -import {useLingui} from '@lingui/react' -import {Plural, Trans} from '@lingui/react/macro' +import {plural} from '@lingui/core/macro' +import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' @@ -32,7 +33,6 @@ import {type NavigationProp} from '#/lib/routes/types' import {forceLTR} from '#/lib/strings/bidi' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {niceDate} from '#/lib/strings/time' -import {s} from '#/lib/styles' import {logger} from '#/logger' import {useProfileShadow} from '#/state/cache/profile-shadow' import {type FeedNotification} from '#/state/queries/notifications/feed' @@ -44,7 +44,7 @@ import {Post} from '#/view/com/post/Post' import {formatCount} from '#/view/com/util/numeric/format' import {TimeElapsed} from '#/view/com/util/TimeElapsed' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' -import {atoms as a, platform, useTheme, web} from '#/alf' +import {atoms as a, native, platform, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {BellRinging_Filled_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging' import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' @@ -64,7 +64,10 @@ import * as MediaPreview from '#/components/MediaPreview' import {ProfileBadges} from '#/components/ProfileBadges' import * as ProfileCard from '#/components/ProfileCard' import {ProfileHoverCard} from '#/components/ProfileHoverCard' -import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard' +import { + Notification as StarterPackCard, + useStarterPackLink, +} from '#/components/StarterPack/StarterPackCard' import {SubtleHover} from '#/components/SubtleHover' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' @@ -93,9 +96,9 @@ let NotificationFeedItem = ({ }): React.ReactNode => { const queryClient = useQueryClient() const t = useTheme() - const {_, i18n} = useLingui() + const {t: l, i18n} = useLingui() const ax = useAnalytics() - const [isAuthorsExpanded, setIsAuthorsExpanded] = useState(false) + const [isAuthorsExpanded, setIsAuthorsExpanded] = useState(false) const [isHoveringAuthorsList, setIsHoveringAuthorsList] = useState(false) const itemHref = useMemo(() => { switch (item.type) { @@ -253,7 +256,7 @@ let NotificationFeedItem = ({ to={firstAuthor.href} disableMismatchWarning emoji - label={_(msg`Go to ${firstAuthorName}'s profile`)}> + label={l`Go to ${firstAuthorName}'s profile`}> {forceLTR(firstAuthorName)} 0 + const starterPack = item.notification.starterPack + const allFollowedViaSameStarterPack = + item.type === 'follow' && + starterPack !== undefined && + (item.additional ?? []).every( + notification => notification.starterPack?.uri === starterPack.uri, + ) + const starterPackName = + allFollowedViaSameStarterPack && starterPack + ? getStarterPackName(starterPack) + : undefined const formattedAuthorsCount = hasMultipleAuthors ? formatCount(i18n, additionalAuthorsCount) : '' let a11yLabel = '' - let notificationContent: React.ReactElement + let notificationContent: React.ReactElement let icon = ( {firstAuthorLink} and{' '} @@ -317,13 +329,11 @@ let NotificationFeedItem = ({ ) } else if (item.type === 'repost') { a11yLabel = hasMultipleAuthors - ? _( - msg`${firstAuthorName} and ${plural(additionalAuthorsCount, { - one: `${formattedAuthorsCount} other`, - other: `${formattedAuthorsCount} others`, - })} reposted your post`, - ) - : _(msg`${firstAuthorName} reposted your post`) + ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { + one: `${formattedAuthorsCount} other`, + other: `${formattedAuthorsCount} others`, + })} reposted your post` + : l`${firstAuthorName} reposted your post` notificationContent = hasMultipleAuthors ? ( {firstAuthorLink} and{' '} @@ -346,17 +356,24 @@ let NotificationFeedItem = ({ * Follow-backs are ungrouped, grouped follow-backs not supported atm, * see `src/state/queries/notifications/util.ts` */ - a11yLabel = _(msg`${firstAuthorName} followed you back`) + a11yLabel = starterPackName + ? l`${firstAuthorName} followed you back via starter pack ${starterPackName}` + : l`${firstAuthorName} followed you back` notificationContent = {firstAuthorLink} followed you back } else { - a11yLabel = hasMultipleAuthors - ? _( - msg`${firstAuthorName} and ${plural(additionalAuthorsCount, { + a11yLabel = starterPackName + ? hasMultipleAuthors + ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { one: `${formattedAuthorsCount} other`, other: `${formattedAuthorsCount} others`, - })} followed you`, - ) - : _(msg`${firstAuthorName} followed you`) + })} followed you via starter pack ${starterPackName}` + : l`${firstAuthorName} followed you via starter pack ${starterPackName}` + : hasMultipleAuthors + ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { + one: `${formattedAuthorsCount} other`, + other: `${formattedAuthorsCount} others`, + })} followed you` + : l`${firstAuthorName} followed you` notificationContent = hasMultipleAuthors ? ( {firstAuthorLink} and{' '} @@ -375,7 +392,7 @@ let NotificationFeedItem = ({ } icon = } else if (item.type === 'contact-match') { - a11yLabel = _(msg`Your contact ${firstAuthorName} is on Bluesky`) + a11yLabel = l`Your contact ${firstAuthorName} is on Bluesky` notificationContent = ( Your contact {firstAuthorLink} is on Bluesky ) @@ -384,13 +401,11 @@ let NotificationFeedItem = ({ ) } else if (item.type === 'feedgen-like') { a11yLabel = hasMultipleAuthors - ? _( - msg`${firstAuthorName} and ${plural(additionalAuthorsCount, { - one: `${formattedAuthorsCount} other`, - other: `${formattedAuthorsCount} others`, - })} liked your custom feed`, - ) - : _(msg`${firstAuthorName} liked your custom feed`) + ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { + one: `${formattedAuthorsCount} other`, + other: `${formattedAuthorsCount} others`, + })} liked your custom feed` + : l`${firstAuthorName} liked your custom feed` notificationContent = hasMultipleAuthors ? ( {firstAuthorLink} and{' '} @@ -408,13 +423,11 @@ let NotificationFeedItem = ({ ) } else if (item.type === 'starterpack-joined') { a11yLabel = hasMultipleAuthors - ? _( - msg`${firstAuthorName} and ${plural(additionalAuthorsCount, { - one: `${formattedAuthorsCount} other`, - other: `${formattedAuthorsCount} others`, - })} signed up with your starter pack`, - ) - : _(msg`${firstAuthorName} signed up with your starter pack`) + ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { + one: `${formattedAuthorsCount} other`, + other: `${formattedAuthorsCount} others`, + })} signed up with your starter pack` + : l`${firstAuthorName} signed up with your starter pack` notificationContent = hasMultipleAuthors ? ( {firstAuthorLink} and{' '} @@ -437,13 +450,11 @@ let NotificationFeedItem = ({ ) } else if (item.type === 'verified') { a11yLabel = hasMultipleAuthors - ? _( - msg`${firstAuthorName} and ${plural(additionalAuthorsCount, { - one: `${formattedAuthorsCount} other`, - other: `${formattedAuthorsCount} others`, - })} verified you`, - ) - : _(msg`${firstAuthorName} verified you`) + ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { + one: `${formattedAuthorsCount} other`, + other: `${formattedAuthorsCount} others`, + })} verified you` + : l`${firstAuthorName} verified you` notificationContent = hasMultipleAuthors ? ( {firstAuthorLink} and{' '} @@ -462,13 +473,11 @@ let NotificationFeedItem = ({ icon = } else if (item.type === 'unverified') { a11yLabel = hasMultipleAuthors - ? _( - msg`${firstAuthorName} and ${plural(additionalAuthorsCount, { - one: `${formattedAuthorsCount} other`, - other: `${formattedAuthorsCount} others`, - })} removed their verifications from your account`, - ) - : _(msg`${firstAuthorName} removed their verification from your account`) + ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { + one: `${formattedAuthorsCount} other`, + other: `${formattedAuthorsCount} others`, + })} removed their verifications from your account` + : l`${firstAuthorName} removed their verification from your account` notificationContent = hasMultipleAuthors ? ( {firstAuthorLink} and{' '} @@ -489,13 +498,11 @@ let NotificationFeedItem = ({ icon = } else if (item.type === 'like-via-repost') { a11yLabel = hasMultipleAuthors - ? _( - msg`${firstAuthorName} and ${plural(additionalAuthorsCount, { - one: `${formattedAuthorsCount} other`, - other: `${formattedAuthorsCount} others`, - })} liked your repost`, - ) - : _(msg`${firstAuthorName} liked your repost`) + ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { + one: `${formattedAuthorsCount} other`, + other: `${formattedAuthorsCount} others`, + })} liked your repost` + : l`${firstAuthorName} liked your repost` notificationContent = hasMultipleAuthors ? ( {firstAuthorLink} and{' '} @@ -513,13 +520,11 @@ let NotificationFeedItem = ({ ) } else if (item.type === 'repost-via-repost') { a11yLabel = hasMultipleAuthors - ? _( - msg`${firstAuthorName} and ${plural(additionalAuthorsCount, { - one: `${formattedAuthorsCount} other`, - other: `${formattedAuthorsCount} others`, - })} reposted your repost`, - ) - : _(msg`${firstAuthorName} reposted your repost`) + ? l`${firstAuthorName} and ${plural(additionalAuthorsCount, { + one: `${formattedAuthorsCount} other`, + other: `${formattedAuthorsCount} others`, + })} reposted your repost` + : l`${firstAuthorName} reposted your repost` notificationContent = hasMultipleAuthors ? ( {firstAuthorLink} and{' '} @@ -539,21 +544,17 @@ let NotificationFeedItem = ({ } else if (item.type === 'subscribed-post') { const postsCount = 1 + (item.additional?.length || 0) a11yLabel = hasMultipleAuthors - ? _( - msg`New posts from ${firstAuthorName} and ${plural( - additionalAuthorsCount, - { - one: `${formattedAuthorsCount} other`, - other: `${formattedAuthorsCount} others`, - }, - )}`, - ) - : _( - msg`New ${plural(postsCount, { - one: 'post', - other: 'posts', - })} from ${firstAuthorName}`, - ) + ? l`New posts from ${firstAuthorName} and ${plural( + additionalAuthorsCount, + { + one: `${formattedAuthorsCount} other`, + other: `${formattedAuthorsCount} others`, + }, + )}` + : l`New ${plural(postsCount, { + one: 'post', + other: 'posts', + })} from ${firstAuthorName}` notificationContent = hasMultipleAuthors ? ( New posts from {firstAuthorLink} and{' '} @@ -608,18 +609,16 @@ let NotificationFeedItem = ({ { name: 'toggleAuthorsExpanded', label: isAuthorsExpanded - ? _(msg`Collapse list of users`) - : _(msg`Expand list of users`), + ? l`Collapse list of users` + : l`Expand list of users`, }, ] : [ { name: 'viewProfile', - label: _( - msg`View ${ - authors[0].profile.displayName || authors[0].profile.handle - }'s profile`, - ), + label: l`View ${ + authors[0].profile.displayName || authors[0].profile.handle + }'s profile`, }, ] } @@ -686,6 +685,9 @@ let NotificationFeedItem = ({ + {allFollowedViaSameStarterPack && starterPack ? ( + + ) : null} {(item.type === 'follow' && !hasMultipleAuthors && !isFollowBack) || (item.type === 'contact-match' && !item.notification.author.viewer?.following) ? ( @@ -737,6 +739,53 @@ let NotificationFeedItem = ({ NotificationFeedItem = memo(NotificationFeedItem) export {NotificationFeedItem} +function FollowedViaStarterPack({ + starterPack, +}: { + starterPack: AppBskyGraphDefs.StarterPackViewBasic +}) { + const t = useTheme() + const link = useStarterPackLink({view: starterPack}) + + const starterPackName = getStarterPackName(starterPack) + + if (!starterPackName) { + return null + } + + return ( + + + via starter pack{' '} + + + {starterPackName} + + + + ) +} + +function getStarterPackName( + starterPack: AppBskyGraphDefs.StarterPackViewBasic, +) { + return bsky.dangerousIsType( + starterPack.record, + AppBskyGraphStarterpack.isRecord, + ) + ? starterPack.record.name + : undefined +} + function ExpandListPressable({ hasMultipleAuthors, children, @@ -767,7 +816,7 @@ function ExpandListPressable({ } function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { - const {_} = useLingui() + const {t: l} = useLingui() const {currentAccount, hasSession} = useSession() const profileShadow = useProfileShadow(profile) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( @@ -787,15 +836,14 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { try { await queueFollow() Toast.show( - _( - msg`Following ${sanitizeDisplayName( - profile.displayName || profile.handle, - )}`, - ), + l`Following ${sanitizeDisplayName( + profile.displayName || profile.handle, + )}`, ) - } catch (err: any) { + } catch (error) { + const err = error as Error if (err?.name !== 'AbortError') { - Toast.show(_(msg`An issue occurred, please try again.`), { + Toast.show(l`An issue occurred, please try again.`, { type: 'error', }) } @@ -809,15 +857,14 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { try { await queueUnfollow() Toast.show( - _( - msg`No longer following ${sanitizeDisplayName( - profile.displayName || profile.handle, - )}`, - ), + l`No longer following ${sanitizeDisplayName( + profile.displayName || profile.handle, + )}`, ) - } catch (err: any) { + } catch (error) { + const err = error as Error if (err?.name !== 'AbortError') { - Toast.show(_(msg`An issue occurred, please try again.`), { + Toast.show(l`An issue occurred, please try again.`, { type: 'error', }) } @@ -838,12 +885,10 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { const isFollowing = profileShadow.viewer.following const isFollowedBy = profileShadow.viewer.followedBy - const followingLabel = _( - msg({ - message: 'Following', - comment: 'User is following this account, click to unfollow', - }), - ) + const followingLabel = l({ + message: 'Following', + comment: 'User is following this account, click to unfollow', + }) return ( @@ -853,7 +898,7 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { color="secondary" size="small" style={[a.self_start]} - onPress={onPressUnfollow}> + onPress={(e: GestureResponderEvent) => void onPressUnfollow(e)}> Following @@ -861,11 +906,11 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { ) : (