Indicate if someone followed you via a starter pack (#11320)
This commit is contained in:
@@ -1638,31 +1638,6 @@
|
|||||||
"count": 2
|
"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": {
|
"src/view/com/pager/Pager.tsx": {
|
||||||
"typescript/no-explicit-any": {
|
"typescript/no-explicit-any": {
|
||||||
"count": 4
|
"count": 4
|
||||||
|
|||||||
+1
-1
@@ -96,7 +96,7 @@
|
|||||||
"prettier": "prettier --check ."
|
"prettier": "prettier --check ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "0.20.33",
|
"@atproto/api": "0.20.34",
|
||||||
"@atproto/common-web": "0.5.6",
|
"@atproto/common-web": "0.5.6",
|
||||||
"@atproto/syntax": "0.7.2",
|
"@atproto/syntax": "0.7.2",
|
||||||
"@bitdrift/react-native": "^0.6.8",
|
"@bitdrift/react-native": "^0.6.8",
|
||||||
|
|||||||
Generated
+5
-5
@@ -242,8 +242,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@atproto/api':
|
'@atproto/api':
|
||||||
specifier: 0.20.33
|
specifier: 0.20.34
|
||||||
version: 0.20.33
|
version: 0.20.34
|
||||||
'@atproto/common-web':
|
'@atproto/common-web':
|
||||||
specifier: 0.5.6
|
specifier: 0.5.6
|
||||||
version: 0.5.6
|
version: 0.5.6
|
||||||
@@ -871,8 +871,8 @@ packages:
|
|||||||
graphql:
|
graphql:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@atproto/api@0.20.33':
|
'@atproto/api@0.20.34':
|
||||||
resolution: {integrity: sha512-3YpnBVMieQFWetLvqibn2yG6vhNZ7ozSi/EUujuGcvYXt9g/NrvmHufaUtrXCasp2mHDI7t7UG5sLEBg6YNEJw==}
|
resolution: {integrity: sha512-nKfCLkH2Al58YupLGtoVIucZ5XjtN9sU5oOTI70lp8J4nxl52C/Tk7AktcWe+Nx7st3I0fUgf+C5cHePgvwT0w==}
|
||||||
engines: {node: '>=22'}
|
engines: {node: '>=22'}
|
||||||
|
|
||||||
'@atproto/common-web@0.5.6':
|
'@atproto/common-web@0.5.6':
|
||||||
@@ -9389,7 +9389,7 @@ snapshots:
|
|||||||
|
|
||||||
'@0no-co/graphql.web@1.2.0': {}
|
'@0no-co/graphql.web@1.2.0': {}
|
||||||
|
|
||||||
'@atproto/api@0.20.33':
|
'@atproto/api@0.20.34':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@atproto/common-web': 0.5.6
|
'@atproto/common-web': 0.5.6
|
||||||
'@atproto/lexicon': 0.7.7
|
'@atproto/lexicon': 0.7.7
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -163,6 +163,9 @@ export function groupNotifications(
|
|||||||
Math.abs(ts2 - ts) < MS_2DAY &&
|
Math.abs(ts2 - ts) < MS_2DAY &&
|
||||||
notif.reason === groupedNotif.notification.reason &&
|
notif.reason === groupedNotif.notification.reason &&
|
||||||
notif.reasonSubject === groupedNotif.notification.reasonSubject &&
|
notif.reasonSubject === groupedNotif.notification.reasonSubject &&
|
||||||
|
(notif.reason !== 'follow' ||
|
||||||
|
notif.starterPack?.uri ===
|
||||||
|
groupedNotif.notification.starterPack?.uri) &&
|
||||||
(notif.author.did !== groupedNotif.notification.author.did ||
|
(notif.author.did !== groupedNotif.notification.author.did ||
|
||||||
notif.reason === 'subscribed-post')
|
notif.reason === 'subscribed-post')
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,31 +1,37 @@
|
|||||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
import {useCallback, useEffect, useMemo, useState} from 'react'
|
||||||
import {
|
import {ActivityIndicator, type ListRenderItemInfo, View} from 'react-native'
|
||||||
ActivityIndicator,
|
import {useLingui} from '@lingui/react/macro'
|
||||||
type ListRenderItemInfo,
|
|
||||||
StyleSheet,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {msg} from '@lingui/core/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||||
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {s} from '#/lib/styles'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
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 {EmptyState} from '#/view/com/util/EmptyState'
|
||||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||||
import {List, type ListProps, type ListRef} from '#/view/com/util/List'
|
import {List, type ListProps, type ListRef} from '#/view/com/util/List'
|
||||||
import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||||
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
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 {Bell_Stroke2_Corner0_Rounded as BellIcon} from '#/components/icons/Bell'
|
||||||
import {NotificationFeedItem} from './NotificationFeedItem'
|
import {NotificationFeedItem} from './NotificationFeedItem'
|
||||||
|
|
||||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
const EMPTY_FEED_ITEM = {type: 'empty', _reactKey: '__empty__'} as const
|
||||||
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
const LOAD_MORE_ERROR_ITEM = {
|
||||||
const LOADING_ITEM = {_reactKey: '__loading__'}
|
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({
|
export function NotificationFeed({
|
||||||
filter,
|
filter,
|
||||||
@@ -46,7 +52,7 @@ export function NotificationFeed({
|
|||||||
}) {
|
}) {
|
||||||
const initialNumToRender = useInitialNumToRender()
|
const initialNumToRender = useInitialNumToRender()
|
||||||
const [isPTRing, setIsPTRing] = useState(false)
|
const [isPTRing, setIsPTRing] = useState(false)
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
const trackPostView = usePostViewTracking('Notifications')
|
const trackPostView = usePostViewTracking('Notifications')
|
||||||
const {
|
const {
|
||||||
@@ -71,7 +77,7 @@ export function NotificationFeed({
|
|||||||
!isFetching && !data?.pages.find(page => page.items.length > 0)
|
!isFetching && !data?.pages.find(page => page.items.length > 0)
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
let arr: any[] = []
|
let arr: NotificationFeedListItem[] = []
|
||||||
if (isFetched) {
|
if (isFetched) {
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
arr = arr.concat([EMPTY_FEED_ITEM])
|
arr = arr.concat([EMPTY_FEED_ITEM])
|
||||||
@@ -113,29 +119,27 @@ export function NotificationFeed({
|
|||||||
}, [isFetching, hasNextPage, isError, fetchNextPage])
|
}, [isFetching, hasNextPage, isError, fetchNextPage])
|
||||||
|
|
||||||
const onPressRetryLoadMore = useCallback(() => {
|
const onPressRetryLoadMore = useCallback(() => {
|
||||||
fetchNextPage()
|
void fetchNextPage()
|
||||||
}, [fetchNextPage])
|
}, [fetchNextPage])
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({item, index}: ListRenderItemInfo<any>) => {
|
({item, index}: ListRenderItemInfo<NotificationFeedListItem>) => {
|
||||||
if (item === EMPTY_FEED_ITEM) {
|
if (item.type === 'empty') {
|
||||||
return (
|
return (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
icon={BellIcon}
|
icon={BellIcon}
|
||||||
message={_(msg`No notifications yet!`)}
|
message={l`No notifications yet!`}
|
||||||
style={styles.emptyState}
|
style={[a.py_5xl]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (item === LOAD_MORE_ERROR_ITEM) {
|
} else if (item.type === 'load-more-error') {
|
||||||
return (
|
return (
|
||||||
<LoadMoreRetryBtn
|
<LoadMoreRetryBtn
|
||||||
label={_(
|
label={l`There was an issue fetching notifications. Tap here to try again.`}
|
||||||
msg`There was an issue fetching notifications. Tap here to try again.`,
|
|
||||||
)}
|
|
||||||
onPress={onPressRetryLoadMore}
|
onPress={onPressRetryLoadMore}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (item === LOADING_ITEM) {
|
} else if (item.type === 'loading') {
|
||||||
return <NotificationFeedLoadingPlaceholder />
|
return <NotificationFeedLoadingPlaceholder />
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -147,13 +151,13 @@ export function NotificationFeed({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[moderationOpts, _, onPressRetryLoadMore, filter],
|
[moderationOpts, l, onPressRetryLoadMore, filter],
|
||||||
)
|
)
|
||||||
|
|
||||||
const FeedFooter = useCallback(
|
const FeedFooter = useCallback(
|
||||||
() =>
|
() =>
|
||||||
isFetchingNextPage ? (
|
isFetchingNextPage ? (
|
||||||
<View style={styles.feedFooter}>
|
<View style={[a.pt_xl]}>
|
||||||
<ActivityIndicator />
|
<ActivityIndicator />
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
@@ -169,7 +173,11 @@ export function NotificationFeed({
|
|||||||
}, [enabled])
|
}, [enabled])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={s.hContentRegion}>
|
<View
|
||||||
|
style={platform({
|
||||||
|
web: {minHeight: '100%'},
|
||||||
|
default: {height: '100%'},
|
||||||
|
})}>
|
||||||
{error && (
|
{error && (
|
||||||
<ErrorMessage
|
<ErrorMessage
|
||||||
message={cleanError(error)}
|
message={cleanError(error)}
|
||||||
@@ -180,16 +188,16 @@ export function NotificationFeed({
|
|||||||
testID="notifsFeed"
|
testID="notifsFeed"
|
||||||
ref={scrollElRef}
|
ref={scrollElRef}
|
||||||
data={items}
|
data={items}
|
||||||
keyExtractor={item => item._reactKey}
|
keyExtractor={(item: NotificationFeedListItem) => item._reactKey}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
ListHeaderComponent={ListHeaderComponent}
|
ListHeaderComponent={ListHeaderComponent}
|
||||||
ListFooterComponent={FeedFooter}
|
ListFooterComponent={FeedFooter}
|
||||||
refreshing={isPTRing}
|
refreshing={isPTRing}
|
||||||
onRefresh={onRefresh}
|
onRefresh={() => void onRefresh()}
|
||||||
onEndReached={onEndReached}
|
onEndReached={() => void onEndReached()}
|
||||||
onEndReachedThreshold={2}
|
onEndReachedThreshold={2}
|
||||||
onScrolledDownChange={onScrolledDownChange}
|
onScrolledDownChange={onScrolledDownChange}
|
||||||
onItemSeen={item => {
|
onItemSeen={(item: NotificationFeedListItem) => {
|
||||||
if (
|
if (
|
||||||
(item.type === 'reply' ||
|
(item.type === 'reply' ||
|
||||||
item.type === 'mention' ||
|
item.type === 'mention' ||
|
||||||
@@ -199,7 +207,7 @@ export function NotificationFeed({
|
|||||||
trackPostView(item.subject)
|
trackPostView(item.subject)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
contentContainerStyle={s.contentContainer}
|
contentContainerStyle={{paddingBottom: 200}}
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
initialNumToRender={initialNumToRender}
|
initialNumToRender={initialNumToRender}
|
||||||
windowSize={11}
|
windowSize={11}
|
||||||
@@ -209,8 +217,3 @@ export function NotificationFeed({
|
|||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
feedFooter: {paddingTop: 20},
|
|
||||||
emptyState: {paddingVertical: 40},
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -12,16 +12,17 @@ import {
|
|||||||
type AppBskyActorDefs,
|
type AppBskyActorDefs,
|
||||||
type AppBskyFeedDefs,
|
type AppBskyFeedDefs,
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
|
type AppBskyGraphDefs,
|
||||||
AppBskyGraphFollow,
|
AppBskyGraphFollow,
|
||||||
|
AppBskyGraphStarterpack,
|
||||||
|
AtUri,
|
||||||
moderateProfile,
|
moderateProfile,
|
||||||
type ModerationDecision,
|
type ModerationDecision,
|
||||||
type ModerationOpts,
|
type ModerationOpts,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {AtUri} from '@atproto/api'
|
|
||||||
import {TID} from '@atproto/common-web'
|
import {TID} from '@atproto/common-web'
|
||||||
import {msg, plural} from '@lingui/core/macro'
|
import {plural} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||||
import {Plural, Trans} from '@lingui/react/macro'
|
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
@@ -32,7 +33,6 @@ import {type NavigationProp} from '#/lib/routes/types'
|
|||||||
import {forceLTR} from '#/lib/strings/bidi'
|
import {forceLTR} from '#/lib/strings/bidi'
|
||||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
import {niceDate} from '#/lib/strings/time'
|
import {niceDate} from '#/lib/strings/time'
|
||||||
import {s} from '#/lib/styles'
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||||
import {type FeedNotification} from '#/state/queries/notifications/feed'
|
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 {formatCount} from '#/view/com/util/numeric/format'
|
||||||
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
|
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
|
||||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
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 {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {BellRinging_Filled_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging'
|
import {BellRinging_Filled_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging'
|
||||||
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
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 {ProfileBadges} from '#/components/ProfileBadges'
|
||||||
import * as ProfileCard from '#/components/ProfileCard'
|
import * as ProfileCard from '#/components/ProfileCard'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
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 {SubtleHover} from '#/components/SubtleHover'
|
||||||
import * as Toast from '#/components/Toast'
|
import * as Toast from '#/components/Toast'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -93,9 +96,9 @@ let NotificationFeedItem = ({
|
|||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_, i18n} = useLingui()
|
const {t: l, i18n} = useLingui()
|
||||||
const ax = useAnalytics()
|
const ax = useAnalytics()
|
||||||
const [isAuthorsExpanded, setIsAuthorsExpanded] = useState<boolean>(false)
|
const [isAuthorsExpanded, setIsAuthorsExpanded] = useState(false)
|
||||||
const [isHoveringAuthorsList, setIsHoveringAuthorsList] = useState(false)
|
const [isHoveringAuthorsList, setIsHoveringAuthorsList] = useState(false)
|
||||||
const itemHref = useMemo(() => {
|
const itemHref = useMemo(() => {
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
@@ -253,7 +256,7 @@ let NotificationFeedItem = ({
|
|||||||
to={firstAuthor.href}
|
to={firstAuthor.href}
|
||||||
disableMismatchWarning
|
disableMismatchWarning
|
||||||
emoji
|
emoji
|
||||||
label={_(msg`Go to ${firstAuthorName}'s profile`)}>
|
label={l`Go to ${firstAuthorName}'s profile`}>
|
||||||
{forceLTR(firstAuthorName)}
|
{forceLTR(firstAuthorName)}
|
||||||
<ProfileBadges
|
<ProfileBadges
|
||||||
profile={firstAuthor.profile}
|
profile={firstAuthor.profile}
|
||||||
@@ -275,12 +278,23 @@ let NotificationFeedItem = ({
|
|||||||
)
|
)
|
||||||
const additionalAuthorsCount = authors.length - 1
|
const additionalAuthorsCount = authors.length - 1
|
||||||
const hasMultipleAuthors = additionalAuthorsCount > 0
|
const hasMultipleAuthors = additionalAuthorsCount > 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
|
const formattedAuthorsCount = hasMultipleAuthors
|
||||||
? formatCount(i18n, additionalAuthorsCount)
|
? formatCount(i18n, additionalAuthorsCount)
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
let a11yLabel = ''
|
let a11yLabel = ''
|
||||||
let notificationContent: React.ReactElement<any>
|
let notificationContent: React.ReactElement
|
||||||
let icon = (
|
let icon = (
|
||||||
<HeartIconFilled
|
<HeartIconFilled
|
||||||
size="xl"
|
size="xl"
|
||||||
@@ -293,13 +307,11 @@ let NotificationFeedItem = ({
|
|||||||
|
|
||||||
if (item.type === 'post-like') {
|
if (item.type === 'post-like') {
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = hasMultipleAuthors
|
||||||
? _(
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
})} liked your post`,
|
})} liked your post`
|
||||||
)
|
: l`${firstAuthorName} liked your post`
|
||||||
: _(msg`${firstAuthorName} liked your post`)
|
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
{firstAuthorLink} and{' '}
|
{firstAuthorLink} and{' '}
|
||||||
@@ -317,13 +329,11 @@ let NotificationFeedItem = ({
|
|||||||
)
|
)
|
||||||
} else if (item.type === 'repost') {
|
} else if (item.type === 'repost') {
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = hasMultipleAuthors
|
||||||
? _(
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
})} reposted your post`,
|
})} reposted your post`
|
||||||
)
|
: l`${firstAuthorName} reposted your post`
|
||||||
: _(msg`${firstAuthorName} reposted your post`)
|
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
{firstAuthorLink} and{' '}
|
{firstAuthorLink} and{' '}
|
||||||
@@ -346,17 +356,24 @@ let NotificationFeedItem = ({
|
|||||||
* Follow-backs are ungrouped, grouped follow-backs not supported atm,
|
* Follow-backs are ungrouped, grouped follow-backs not supported atm,
|
||||||
* see `src/state/queries/notifications/util.ts`
|
* 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 = <Trans>{firstAuthorLink} followed you back</Trans>
|
notificationContent = <Trans>{firstAuthorLink} followed you back</Trans>
|
||||||
} else {
|
} else {
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = starterPackName
|
||||||
? _(
|
? hasMultipleAuthors
|
||||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
})} followed you`,
|
})} followed you via starter pack ${starterPackName}`
|
||||||
)
|
: l`${firstAuthorName} followed you via starter pack ${starterPackName}`
|
||||||
: _(msg`${firstAuthorName} followed you`)
|
: hasMultipleAuthors
|
||||||
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
|
one: `${formattedAuthorsCount} other`,
|
||||||
|
other: `${formattedAuthorsCount} others`,
|
||||||
|
})} followed you`
|
||||||
|
: l`${firstAuthorName} followed you`
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
{firstAuthorLink} and{' '}
|
{firstAuthorLink} and{' '}
|
||||||
@@ -375,7 +392,7 @@ let NotificationFeedItem = ({
|
|||||||
}
|
}
|
||||||
icon = <PersonPlusIcon size="xl" style={{color: t.palette.primary_500}} />
|
icon = <PersonPlusIcon size="xl" style={{color: t.palette.primary_500}} />
|
||||||
} else if (item.type === 'contact-match') {
|
} else if (item.type === 'contact-match') {
|
||||||
a11yLabel = _(msg`Your contact ${firstAuthorName} is on Bluesky`)
|
a11yLabel = l`Your contact ${firstAuthorName} is on Bluesky`
|
||||||
notificationContent = (
|
notificationContent = (
|
||||||
<Trans>Your contact {firstAuthorLink} is on Bluesky</Trans>
|
<Trans>Your contact {firstAuthorLink} is on Bluesky</Trans>
|
||||||
)
|
)
|
||||||
@@ -384,13 +401,11 @@ let NotificationFeedItem = ({
|
|||||||
)
|
)
|
||||||
} else if (item.type === 'feedgen-like') {
|
} else if (item.type === 'feedgen-like') {
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = hasMultipleAuthors
|
||||||
? _(
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
})} liked your custom feed`,
|
})} liked your custom feed`
|
||||||
)
|
: l`${firstAuthorName} liked your custom feed`
|
||||||
: _(msg`${firstAuthorName} liked your custom feed`)
|
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
{firstAuthorLink} and{' '}
|
{firstAuthorLink} and{' '}
|
||||||
@@ -408,13 +423,11 @@ let NotificationFeedItem = ({
|
|||||||
)
|
)
|
||||||
} else if (item.type === 'starterpack-joined') {
|
} else if (item.type === 'starterpack-joined') {
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = hasMultipleAuthors
|
||||||
? _(
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
})} signed up with your starter pack`,
|
})} signed up with your starter pack`
|
||||||
)
|
: l`${firstAuthorName} signed up with your starter pack`
|
||||||
: _(msg`${firstAuthorName} signed up with your starter pack`)
|
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
{firstAuthorLink} and{' '}
|
{firstAuthorLink} and{' '}
|
||||||
@@ -437,13 +450,11 @@ let NotificationFeedItem = ({
|
|||||||
)
|
)
|
||||||
} else if (item.type === 'verified') {
|
} else if (item.type === 'verified') {
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = hasMultipleAuthors
|
||||||
? _(
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
})} verified you`,
|
})} verified you`
|
||||||
)
|
: l`${firstAuthorName} verified you`
|
||||||
: _(msg`${firstAuthorName} verified you`)
|
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
{firstAuthorLink} and{' '}
|
{firstAuthorLink} and{' '}
|
||||||
@@ -462,13 +473,11 @@ let NotificationFeedItem = ({
|
|||||||
icon = <VerifiedCheck size="xl" />
|
icon = <VerifiedCheck size="xl" />
|
||||||
} else if (item.type === 'unverified') {
|
} else if (item.type === 'unverified') {
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = hasMultipleAuthors
|
||||||
? _(
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
})} removed their verifications from your account`,
|
})} removed their verifications from your account`
|
||||||
)
|
: l`${firstAuthorName} removed their verification from your account`
|
||||||
: _(msg`${firstAuthorName} removed their verification from your account`)
|
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
{firstAuthorLink} and{' '}
|
{firstAuthorLink} and{' '}
|
||||||
@@ -489,13 +498,11 @@ let NotificationFeedItem = ({
|
|||||||
icon = <VerifiedCheck size="xl" fill={t.palette.contrast_500} />
|
icon = <VerifiedCheck size="xl" fill={t.palette.contrast_500} />
|
||||||
} else if (item.type === 'like-via-repost') {
|
} else if (item.type === 'like-via-repost') {
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = hasMultipleAuthors
|
||||||
? _(
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
})} liked your repost`,
|
})} liked your repost`
|
||||||
)
|
: l`${firstAuthorName} liked your repost`
|
||||||
: _(msg`${firstAuthorName} liked your repost`)
|
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
{firstAuthorLink} and{' '}
|
{firstAuthorLink} and{' '}
|
||||||
@@ -513,13 +520,11 @@ let NotificationFeedItem = ({
|
|||||||
)
|
)
|
||||||
} else if (item.type === 'repost-via-repost') {
|
} else if (item.type === 'repost-via-repost') {
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = hasMultipleAuthors
|
||||||
? _(
|
? l`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
||||||
msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
|
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
})} reposted your repost`,
|
})} reposted your repost`
|
||||||
)
|
: l`${firstAuthorName} reposted your repost`
|
||||||
: _(msg`${firstAuthorName} reposted your repost`)
|
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
{firstAuthorLink} and{' '}
|
{firstAuthorLink} and{' '}
|
||||||
@@ -539,21 +544,17 @@ let NotificationFeedItem = ({
|
|||||||
} else if (item.type === 'subscribed-post') {
|
} else if (item.type === 'subscribed-post') {
|
||||||
const postsCount = 1 + (item.additional?.length || 0)
|
const postsCount = 1 + (item.additional?.length || 0)
|
||||||
a11yLabel = hasMultipleAuthors
|
a11yLabel = hasMultipleAuthors
|
||||||
? _(
|
? l`New posts from ${firstAuthorName} and ${plural(
|
||||||
msg`New posts from ${firstAuthorName} and ${plural(
|
|
||||||
additionalAuthorsCount,
|
additionalAuthorsCount,
|
||||||
{
|
{
|
||||||
one: `${formattedAuthorsCount} other`,
|
one: `${formattedAuthorsCount} other`,
|
||||||
other: `${formattedAuthorsCount} others`,
|
other: `${formattedAuthorsCount} others`,
|
||||||
},
|
},
|
||||||
)}`,
|
)}`
|
||||||
)
|
: l`New ${plural(postsCount, {
|
||||||
: _(
|
|
||||||
msg`New ${plural(postsCount, {
|
|
||||||
one: 'post',
|
one: 'post',
|
||||||
other: 'posts',
|
other: 'posts',
|
||||||
})} from ${firstAuthorName}`,
|
})} from ${firstAuthorName}`
|
||||||
)
|
|
||||||
notificationContent = hasMultipleAuthors ? (
|
notificationContent = hasMultipleAuthors ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
New posts from {firstAuthorLink} and{' '}
|
New posts from {firstAuthorLink} and{' '}
|
||||||
@@ -608,18 +609,16 @@ let NotificationFeedItem = ({
|
|||||||
{
|
{
|
||||||
name: 'toggleAuthorsExpanded',
|
name: 'toggleAuthorsExpanded',
|
||||||
label: isAuthorsExpanded
|
label: isAuthorsExpanded
|
||||||
? _(msg`Collapse list of users`)
|
? l`Collapse list of users`
|
||||||
: _(msg`Expand list of users`),
|
: l`Expand list of users`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
name: 'viewProfile',
|
name: 'viewProfile',
|
||||||
label: _(
|
label: l`View ${
|
||||||
msg`View ${
|
|
||||||
authors[0].profile.displayName || authors[0].profile.handle
|
authors[0].profile.displayName || authors[0].profile.handle
|
||||||
}'s profile`,
|
}'s profile`,
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -686,6 +685,9 @@ let NotificationFeedItem = ({
|
|||||||
</TimeElapsed>
|
</TimeElapsed>
|
||||||
</Text>
|
</Text>
|
||||||
</ExpandListPressable>
|
</ExpandListPressable>
|
||||||
|
{allFollowedViaSameStarterPack && starterPack ? (
|
||||||
|
<FollowedViaStarterPack starterPack={starterPack} />
|
||||||
|
) : null}
|
||||||
{(item.type === 'follow' && !hasMultipleAuthors && !isFollowBack) ||
|
{(item.type === 'follow' && !hasMultipleAuthors && !isFollowBack) ||
|
||||||
(item.type === 'contact-match' &&
|
(item.type === 'contact-match' &&
|
||||||
!item.notification.author.viewer?.following) ? (
|
!item.notification.author.viewer?.following) ? (
|
||||||
@@ -737,6 +739,53 @@ let NotificationFeedItem = ({
|
|||||||
NotificationFeedItem = memo(NotificationFeedItem)
|
NotificationFeedItem = memo(NotificationFeedItem)
|
||||||
export {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 (
|
||||||
|
<Text style={[native(a.pt_xs), t.atoms.text_contrast_medium]}>
|
||||||
|
<Trans comment="When the source of a follow is a starter pack, i.e., 'via starter pack {starterPackName}'.">
|
||||||
|
via starter pack{' '}
|
||||||
|
<StarterPack
|
||||||
|
size="sm"
|
||||||
|
gradient="sky"
|
||||||
|
style={[native(a.mr_2xs), {transform: [{translateY: 4}]}]}
|
||||||
|
/>
|
||||||
|
<InlineLinkText
|
||||||
|
to={link.to}
|
||||||
|
label={link.label}
|
||||||
|
onPress={link.precache}
|
||||||
|
onMouseEnter={link.precache}
|
||||||
|
style={[a.font_semi_bold, t.atoms.text]}>
|
||||||
|
{starterPackName}
|
||||||
|
</InlineLinkText>
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStarterPackName(
|
||||||
|
starterPack: AppBskyGraphDefs.StarterPackViewBasic,
|
||||||
|
) {
|
||||||
|
return bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
|
||||||
|
starterPack.record,
|
||||||
|
AppBskyGraphStarterpack.isRecord,
|
||||||
|
)
|
||||||
|
? starterPack.record.name
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
function ExpandListPressable({
|
function ExpandListPressable({
|
||||||
hasMultipleAuthors,
|
hasMultipleAuthors,
|
||||||
children,
|
children,
|
||||||
@@ -767,7 +816,7 @@ function ExpandListPressable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const {currentAccount, hasSession} = useSession()
|
const {currentAccount, hasSession} = useSession()
|
||||||
const profileShadow = useProfileShadow(profile)
|
const profileShadow = useProfileShadow(profile)
|
||||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
||||||
@@ -787,15 +836,14 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
|||||||
try {
|
try {
|
||||||
await queueFollow()
|
await queueFollow()
|
||||||
Toast.show(
|
Toast.show(
|
||||||
_(
|
l`Following ${sanitizeDisplayName(
|
||||||
msg`Following ${sanitizeDisplayName(
|
|
||||||
profile.displayName || profile.handle,
|
profile.displayName || profile.handle,
|
||||||
)}`,
|
)}`,
|
||||||
),
|
|
||||||
)
|
)
|
||||||
} catch (err: any) {
|
} catch (error) {
|
||||||
|
const err = error as Error
|
||||||
if (err?.name !== 'AbortError') {
|
if (err?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`), {
|
Toast.show(l`An issue occurred, please try again.`, {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -809,15 +857,14 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
|||||||
try {
|
try {
|
||||||
await queueUnfollow()
|
await queueUnfollow()
|
||||||
Toast.show(
|
Toast.show(
|
||||||
_(
|
l`No longer following ${sanitizeDisplayName(
|
||||||
msg`No longer following ${sanitizeDisplayName(
|
|
||||||
profile.displayName || profile.handle,
|
profile.displayName || profile.handle,
|
||||||
)}`,
|
)}`,
|
||||||
),
|
|
||||||
)
|
)
|
||||||
} catch (err: any) {
|
} catch (error) {
|
||||||
|
const err = error as Error
|
||||||
if (err?.name !== 'AbortError') {
|
if (err?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`), {
|
Toast.show(l`An issue occurred, please try again.`, {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -838,12 +885,10 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
|||||||
|
|
||||||
const isFollowing = profileShadow.viewer.following
|
const isFollowing = profileShadow.viewer.following
|
||||||
const isFollowedBy = profileShadow.viewer.followedBy
|
const isFollowedBy = profileShadow.viewer.followedBy
|
||||||
const followingLabel = _(
|
const followingLabel = l({
|
||||||
msg({
|
|
||||||
message: 'Following',
|
message: 'Following',
|
||||||
comment: 'User is following this account, click to unfollow',
|
comment: 'User is following this account, click to unfollow',
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.pt_sm]}>
|
<View style={[a.pt_sm]}>
|
||||||
@@ -853,7 +898,7 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
size="small"
|
size="small"
|
||||||
style={[a.self_start]}
|
style={[a.self_start]}
|
||||||
onPress={onPressUnfollow}>
|
onPress={(e: GestureResponderEvent) => void onPressUnfollow(e)}>
|
||||||
<ButtonIcon icon={CheckIcon} />
|
<ButtonIcon icon={CheckIcon} />
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Following</Trans>
|
<Trans>Following</Trans>
|
||||||
@@ -861,11 +906,11 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
|||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
label={isFollowedBy ? _(msg`Follow back`) : _(msg`Follow`)}
|
label={isFollowedBy ? l`Follow back` : l`Follow`}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="small"
|
size="small"
|
||||||
style={[a.self_start]}
|
style={[a.self_start]}
|
||||||
onPress={onPressFollow}>
|
onPress={(e: GestureResponderEvent) => void onPressFollow(e)}>
|
||||||
<ButtonIcon icon={PlusIcon} />
|
<ButtonIcon icon={PlusIcon} />
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
{isFollowedBy ? <Trans>Follow back</Trans> : <Trans>Follow</Trans>}
|
{isFollowedBy ? <Trans>Follow back</Trans> : <Trans>Follow</Trans>}
|
||||||
@@ -877,28 +922,12 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
if (
|
const onPress = async () => {
|
||||||
profile.associated?.chat?.allowIncoming === 'none' ||
|
|
||||||
(profile.associated?.chat?.allowIncoming === 'following' &&
|
|
||||||
!profile.viewer?.followedBy)
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
label={_(msg`Say hello!`)}
|
|
||||||
variant="ghost"
|
|
||||||
color="primary"
|
|
||||||
size="small"
|
|
||||||
style={[a.self_center, {marginLeft: 'auto'}]}
|
|
||||||
disabled={isLoading}
|
|
||||||
onPress={async () => {
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const res = await agent.api.chat.bsky.convo.getConvoForMembers(
|
const res = await agent.api.chat.bsky.convo.getConvoForMembers(
|
||||||
@@ -915,7 +944,25 @@ function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}}>
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
profile.associated?.chat?.allowIncoming === 'none' ||
|
||||||
|
(profile.associated?.chat?.allowIncoming === 'following' &&
|
||||||
|
!profile.viewer?.followedBy)
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
label={l`Say hello!`}
|
||||||
|
variant="ghost"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
style={[a.self_center, {marginLeft: 'auto'}]}
|
||||||
|
disabled={isLoading}
|
||||||
|
onPress={() => void onPress()}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Say hello!</Trans>
|
<Trans>Say hello!</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
@@ -935,7 +982,7 @@ function CondensedAuthorsList({
|
|||||||
showDmButton?: boolean
|
showDmButton?: boolean
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {t: l} = useLingui()
|
||||||
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return (
|
return (
|
||||||
@@ -944,10 +991,8 @@ function CondensedAuthorsList({
|
|||||||
style={styles.expandedAuthorsCloseBtn}
|
style={styles.expandedAuthorsCloseBtn}
|
||||||
onPress={onToggleAuthorsExpanded}
|
onPress={onToggleAuthorsExpanded}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel={_(msg`Hide user list`)}
|
accessibilityLabel={l`Hide user list`}
|
||||||
accessibilityHint={_(
|
accessibilityHint={l`Collapses list of users for a given notification`}>
|
||||||
msg`Collapses list of users for a given notification`,
|
|
||||||
)}>
|
|
||||||
<ChevronUpIcon
|
<ChevronUpIcon
|
||||||
size="md"
|
size="md"
|
||||||
style={[a.ml_xs, a.mr_md, t.atoms.text_contrast_high]}
|
style={[a.ml_xs, a.mr_md, t.atoms.text_contrast_high]}
|
||||||
@@ -978,7 +1023,7 @@ function CondensedAuthorsList({
|
|||||||
onPress={onToggleAuthorsExpanded}>
|
onPress={onToggleAuthorsExpanded}>
|
||||||
<View style={[a.flex_row, a.align_center]}>
|
<View style={[a.flex_row, a.align_center]}>
|
||||||
{authors.slice(0, MAX_AUTHORS).map(author => (
|
{authors.slice(0, MAX_AUTHORS).map(author => (
|
||||||
<View key={author.href} style={s.mr5}>
|
<View key={author.href} style={{marginRight: 5}}>
|
||||||
<PreviewableUserAvatar
|
<PreviewableUserAvatar
|
||||||
size={35}
|
size={35}
|
||||||
profile={author.profile}
|
profile={author.profile}
|
||||||
|
|||||||
Reference in New Issue
Block a user