handle feed item
This commit is contained in:
@@ -258,6 +258,7 @@ func serve(cctx *cli.Context) error {
|
||||
e.GET("/feeds", server.WebGeneric)
|
||||
e.GET("/notifications", server.WebGeneric)
|
||||
e.GET("/notifications/settings", server.WebGeneric)
|
||||
e.GET("/notifications/activity", server.WebGeneric)
|
||||
e.GET("/lists", server.WebGeneric)
|
||||
e.GET("/moderation", server.WebGeneric)
|
||||
e.GET("/moderation/modlists", server.WebGeneric)
|
||||
|
||||
@@ -110,6 +110,7 @@ import {
|
||||
} from '#/components/dialogs/EmailDialog'
|
||||
import {router} from '#/routes'
|
||||
import {Referrer} from '../modules/expo-bluesky-swiss-army'
|
||||
import {NotificationsActivityListScreen} from './screens/Notifications/ActivityList'
|
||||
import {LegacyNotificationSettingsScreen} from './screens/Settings/LegacyNotificationSettings'
|
||||
import {NotificationSettingsScreen} from './screens/Settings/NotificationSettings'
|
||||
import {ActivityNotificationSettingsScreen} from './screens/Settings/NotificationSettings/ActivityNotificationSettings'
|
||||
@@ -542,6 +543,11 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
|
||||
getComponent={() => MessagesInboxScreen}
|
||||
options={{title: title(msg`Chat request inbox`), requireAuth: true}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="NotificationsActivityList"
|
||||
getComponent={() => NotificationsActivityListScreen}
|
||||
options={{title: title(msg`Notifications`), requireAuth: true}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="LegacyNotificationSettings"
|
||||
getComponent={() => LegacyNotificationSettingsScreen}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import {
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedGetListFeed as GetListFeed,
|
||||
BskyAgent,
|
||||
type Agent,
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedGetListFeed as GetListFeed,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
import {type FeedAPI, type FeedAPIResponse} from './types'
|
||||
|
||||
export class ListFeedAPI implements FeedAPI {
|
||||
agent: BskyAgent
|
||||
agent: Agent
|
||||
params: GetListFeed.QueryParams
|
||||
|
||||
constructor({
|
||||
agent,
|
||||
feedParams,
|
||||
}: {
|
||||
agent: BskyAgent
|
||||
agent: Agent
|
||||
feedParams: GetListFeed.QueryParams
|
||||
}) {
|
||||
this.agent = agent
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
type Agent,
|
||||
type AppBskyFeedDefs,
|
||||
type AppBskyFeedGetPosts,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {type FeedAPI, type FeedAPIResponse} from './types'
|
||||
|
||||
export class PostListFeedAPI implements FeedAPI {
|
||||
agent: Agent
|
||||
params: AppBskyFeedGetPosts.QueryParams
|
||||
peek: AppBskyFeedDefs.FeedViewPost | null = null
|
||||
|
||||
constructor({
|
||||
agent,
|
||||
feedParams,
|
||||
}: {
|
||||
agent: Agent
|
||||
feedParams: AppBskyFeedGetPosts.QueryParams
|
||||
}) {
|
||||
this.agent = agent
|
||||
this.params = feedParams
|
||||
}
|
||||
|
||||
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
|
||||
if (this.peek) return this.peek
|
||||
throw new Error('Has not fetched yet')
|
||||
}
|
||||
|
||||
async fetch({}: {}): Promise<FeedAPIResponse> {
|
||||
const res = await this.agent.app.bsky.feed.getPosts({
|
||||
...this.params,
|
||||
})
|
||||
if (res.success) {
|
||||
this.peek = {post: res.data.posts[0]}
|
||||
return {
|
||||
feed: res.data.posts.map(post => ({post})),
|
||||
}
|
||||
}
|
||||
return {
|
||||
feed: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,7 @@ export type CommonNavigatorParams = {
|
||||
MessagesConversation: {conversation: string; embed?: string; accept?: true}
|
||||
MessagesSettings: undefined
|
||||
MessagesInbox: undefined
|
||||
NotificationsActivityList: {posts: string}
|
||||
LegacyNotificationSettings: undefined
|
||||
Feeds: undefined
|
||||
Start: {name: string; rkey: string}
|
||||
|
||||
@@ -11,6 +11,7 @@ export const router = new Router<AllNavigatableRoutes>({
|
||||
Search: '/search',
|
||||
Feeds: '/feeds',
|
||||
Notifications: '/notifications',
|
||||
NotificationsActivityList: '/notifications/activity',
|
||||
LegacyNotificationSettings: '/notifications/settings',
|
||||
Settings: '/settings',
|
||||
Lists: '/lists',
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {type AllNavigatorParams} from '#/lib/routes/types'
|
||||
import {PostFeed} from '#/view/com/posts/PostFeed'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
AllNavigatorParams,
|
||||
'NotificationsActivityList'
|
||||
>
|
||||
export function NotificationsActivityListScreen({
|
||||
route: {
|
||||
params: {posts},
|
||||
},
|
||||
}: Props) {
|
||||
const uris = decodeURIComponent(posts)
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="NotificationsActivityListScreen">
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Notifications</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<PostFeed
|
||||
feed={`posts|${uris}`}
|
||||
renderEmptyState={() => (
|
||||
<EmptyState icon="growth" message={_(msg`No posts here`)} />
|
||||
)}
|
||||
disablePoll
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -18,30 +18,30 @@
|
||||
|
||||
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
type AppBskyActorDefs,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
AtUri,
|
||||
moderatePost,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
InfiniteData,
|
||||
QueryClient,
|
||||
QueryKey,
|
||||
type InfiniteData,
|
||||
type QueryClient,
|
||||
type QueryKey,
|
||||
useInfiniteQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useThreadgateHiddenReplyUris} from '#/state/threadgate-hidden-replies'
|
||||
import {useModerationOpts} from '../../preferences/moderation-opts'
|
||||
import {STALE} from '..'
|
||||
import {
|
||||
didOrHandleUriMatches,
|
||||
embedViewRecordToPostView,
|
||||
getEmbeddedPost,
|
||||
} from '../util'
|
||||
import {FeedPage} from './types'
|
||||
import {type FeedPage} from './types'
|
||||
import {useUnreadNotificationsApi} from './unread'
|
||||
import {fetchPage} from './util'
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ type OtherNotificationType =
|
||||
| 'unverified'
|
||||
| 'like-via-repost'
|
||||
| 'repost-via-repost'
|
||||
| 'subscribed-post'
|
||||
| 'unknown'
|
||||
|
||||
type FeedNotificationBase = {
|
||||
|
||||
@@ -28,6 +28,7 @@ const GROUPABLE_REASONS = [
|
||||
'follow',
|
||||
'like-via-repost',
|
||||
'repost-via-repost',
|
||||
'subscribed-post',
|
||||
]
|
||||
const MS_1HR = 1e3 * 60 * 60
|
||||
const MS_2DAY = MS_1HR * 48
|
||||
@@ -252,7 +253,8 @@ function toKnownType(
|
||||
notif.reason === 'verified' ||
|
||||
notif.reason === 'unverified' ||
|
||||
notif.reason === 'like-via-repost' ||
|
||||
notif.reason === 'repost-via-repost'
|
||||
notif.reason === 'repost-via-repost' ||
|
||||
notif.reason === 'subscribed-post'
|
||||
) {
|
||||
return notif.reason as NotificationType
|
||||
}
|
||||
@@ -263,7 +265,12 @@ function getSubjectUri(
|
||||
type: NotificationType,
|
||||
notif: AppBskyNotificationListNotifications.Notification,
|
||||
): string | undefined {
|
||||
if (type === 'reply' || type === 'quote' || type === 'mention') {
|
||||
if (
|
||||
type === 'reply' ||
|
||||
type === 'quote' ||
|
||||
type === 'mention' ||
|
||||
type === 'subscribed-post'
|
||||
) {
|
||||
return notif.uri
|
||||
} else if (
|
||||
type === 'post-like' ||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {HomeFeedAPI} from '#/lib/api/feed/home'
|
||||
import {LikesFeedAPI} from '#/lib/api/feed/likes'
|
||||
import {ListFeedAPI} from '#/lib/api/feed/list'
|
||||
import {MergeFeedAPI} from '#/lib/api/feed/merge'
|
||||
import {PostListFeedAPI} from '#/lib/api/feed/posts'
|
||||
import {type FeedAPI, type ReasonFeedSource} from '#/lib/api/feed/types'
|
||||
import {aggregateUserInterests} from '#/lib/api/feed/utils'
|
||||
import {FeedTuner, type FeedTunerFn} from '#/lib/api/feed-manip'
|
||||
@@ -53,6 +54,7 @@ export type AuthorFilter =
|
||||
| 'posts_with_video'
|
||||
type FeedUri = string
|
||||
type ListUri = string
|
||||
type PostsUriList = string
|
||||
|
||||
export type FeedDescriptor =
|
||||
| 'following'
|
||||
@@ -60,6 +62,7 @@ export type FeedDescriptor =
|
||||
| `feedgen|${FeedUri}`
|
||||
| `likes|${ActorDid}`
|
||||
| `list|${ListUri}`
|
||||
| `posts|${PostsUriList}`
|
||||
| 'demo'
|
||||
export interface FeedParams {
|
||||
mergeFeedEnabled?: boolean
|
||||
@@ -488,6 +491,9 @@ function createApi({
|
||||
} else if (feedDesc.startsWith('list')) {
|
||||
const [_, list] = feedDesc.split('|')
|
||||
return new ListFeedAPI({agent, feedParams: {list}})
|
||||
} else if (feedDesc.startsWith('posts')) {
|
||||
const [_, uriList] = feedDesc.split('|')
|
||||
return new PostListFeedAPI({agent, feedParams: {uris: uriList.split(',')}})
|
||||
} else if (feedDesc === 'demo') {
|
||||
return new DemoFeedAPI({agent})
|
||||
} else {
|
||||
|
||||
@@ -52,6 +52,7 @@ import {TimeElapsed} from '#/view/com/util/TimeElapsed'
|
||||
import {PreviewableUserAvatar, UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, platform, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {BellRinging_Filled_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging'
|
||||
import {
|
||||
ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon,
|
||||
ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon,
|
||||
@@ -114,7 +115,9 @@ let NotificationFeedItem = ({
|
||||
case 'unverified': {
|
||||
return makeProfileLink(item.notification.author)
|
||||
}
|
||||
case 'reply': {
|
||||
case 'reply':
|
||||
case 'mention':
|
||||
case 'quote': {
|
||||
const uripReply = new AtUri(item.notification.uri)
|
||||
return `/profile/${uripReply.host}/post/${uripReply.rkey}`
|
||||
}
|
||||
@@ -126,6 +129,13 @@ let NotificationFeedItem = ({
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'subscribed-post': {
|
||||
const posts: string[] = []
|
||||
for (const post of [item.notification, ...(item.additional ?? [])]) {
|
||||
posts.push(post.uri)
|
||||
}
|
||||
return `/notifications/activity?posts=${encodeURIComponent(posts.join(','))}`
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
@@ -503,6 +513,42 @@ let NotificationFeedItem = ({
|
||||
<Trans>{firstAuthorLink} reposted your repost</Trans>
|
||||
)
|
||||
icon = <RepostIcon size="xl" style={{color: t.palette.positive_600}} />
|
||||
} 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}`,
|
||||
)
|
||||
notificationContent = hasMultipleAuthors ? (
|
||||
<Trans>
|
||||
New posts from {firstAuthorLink} and{' '}
|
||||
<Text style={[a.text_md, a.font_bold, a.leading_snug]}>
|
||||
<Plural
|
||||
value={additionalAuthorsCount}
|
||||
one={`${formattedAuthorsCount} other`}
|
||||
other={`${formattedAuthorsCount} others`}
|
||||
/>
|
||||
</Text>{' '}
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
New <Plural value={postsCount} one="post" other="posts" /> from{' '}
|
||||
{firstAuthorLink}
|
||||
</Trans>
|
||||
)
|
||||
icon = <BellRingingIcon size="xl" style={{color: t.palette.primary_500}} />
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user