this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add reply indicator

+73 -62
+17 -47
apps/expo/src/app/(tabs)/notifications.tsx
··· 14 14 } from "@atproto/api"; 15 15 import { FlashList } from "@shopify/flash-list"; 16 16 import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; 17 - import { 18 - AtSign, 19 - Heart, 20 - MessageCircle, 21 - Quote, 22 - Repeat, 23 - UserPlus, 24 - } from "lucide-react-native"; 17 + import { Heart, Repeat, UserPlus } from "lucide-react-native"; 25 18 26 19 import { Button } from "../../components/button"; 27 20 import { Embed } from "../../components/embed"; ··· 218 211 case "quote": 219 212 case "mention": 220 213 if (!subject) return null; 221 - return ( 222 - <PostNotification reason={reason} uri={subject} unread={!isRead} /> 223 - ); 214 + return <PostNotification uri={subject} unread={!isRead} />; 224 215 default: 225 216 console.warn("Unknown notification reason", reason); 226 217 return null; ··· 297 288 uri, 298 289 unread, 299 290 inline, 300 - reason, 301 291 }: { 302 292 uri: string; 303 293 unread: boolean; 304 294 inline?: boolean; 305 - reason?: "mention" | "reply" | "quote" | (string & {}); 306 295 }) => { 307 296 const agent = useAuthedAgent(); 308 297 ··· 318 307 throw Error("Post not found"); 319 308 assert(AppBskyFeedDefs.validateThreadViewPost(data.thread)); 320 309 321 - return data.thread; 310 + return { 311 + post: data.thread.post, 312 + ...(AppBskyFeedDefs.isThreadViewPost(data.thread.parent) && 313 + AppBskyFeedDefs.validateFeedViewPost(data.thread.parent.post).success 314 + ? { 315 + reply: { 316 + parent: data.thread.parent.post, 317 + // not technically correct but we don't use this field 318 + root: data.thread.parent.post, 319 + }, 320 + } 321 + : {}), 322 + } satisfies AppBskyFeedDefs.FeedViewPost; 322 323 }, 323 324 }); 324 325 ··· 349 350 </View> 350 351 ); 351 352 } 352 - let inlineReason = null; 353 - switch (reason) { 354 - case "mention": 355 - inlineReason = ( 356 - <View className="flex-row items-center"> 357 - <AtSign size={12} color="#737373" /> 358 - <Text className="ml-1 text-neutral-500">mentioned you</Text> 359 - </View> 360 - ); 361 - break; 362 - case "reply": 363 - inlineReason = ( 364 - <View className="flex-row items-center"> 365 - <MessageCircle size={12} color="#737373" /> 366 - <Text className="ml-1 text-neutral-500">replied to you</Text> 367 - </View> 368 - ); 369 - break; 370 - case "quote": 371 - inlineReason = ( 372 - <View className="flex-row items-center"> 373 - <Quote size={12} color="#737373" /> 374 - <Text className="ml-1 text-neutral-500">quoted you</Text> 375 - </View> 376 - ); 377 - } 378 - return ( 379 - <FeedPost 380 - item={post.data} 381 - inlineReason={inlineReason} 382 - unread={unread} 383 - /> 384 - ); 353 + 354 + return <FeedPost item={post.data} inlineParent unread={unread} />; 385 355 } 386 356 };
+24 -5
apps/expo/src/components/feed-post.tsx
··· 1 1 import { Image, Pressable, Text, TouchableOpacity, View } from "react-native"; 2 2 import { Link } from "expo-router"; 3 3 import { AppBskyFeedDefs, AppBskyFeedPost } from "@atproto/api"; 4 - import { Heart, MessageSquare, Repeat, User } from "lucide-react-native"; 4 + import { 5 + Heart, 6 + MessageCircle, 7 + MessageSquare, 8 + Repeat, 9 + User, 10 + } from "lucide-react-native"; 5 11 6 12 import { useLike, useRepost } from "../lib/hooks"; 7 13 import { assert } from "../lib/utils/assert"; ··· 14 20 item: AppBskyFeedDefs.FeedViewPost; 15 21 hasReply?: boolean; 16 22 unread?: boolean; 17 - inlineReason?: React.ReactNode; 23 + inlineParent?: boolean; 18 24 } 19 25 20 26 export const FeedPost = ({ 21 27 item, 22 28 hasReply = false, 23 29 unread, 24 - inlineReason, 30 + inlineParent, 25 31 }: Props) => { 26 32 const { liked, likeCount, toggleLike } = useLike(item.post); 27 33 const { reposted, repostCount, toggleRepost } = useRepost(item.post); ··· 36 42 37 43 assert(AppBskyFeedPost.validateRecord(item.post.record)); 38 44 45 + const displayInlineParent = inlineParent || !!item.reason; 46 + 39 47 // TODO - don't nest feedposts! 40 48 41 49 return ( 42 50 <View 43 51 className={cx( 44 52 "bg-white px-2 pt-2", 45 - item.reply?.parent && "pt-0", 53 + // todo: better approach 54 + // item.reply?.parent && "pt-0", 46 55 !hasReply && "border-b border-neutral-200", 47 56 unread && "border-blue-200 bg-blue-50", 48 57 )} ··· 91 100 </Text> 92 101 </TouchableOpacity> 93 102 </Link> 103 + {/* inline "replying to so-and-so" */} 104 + {displayInlineParent && !!item.reply && ( 105 + <TouchableOpacity className="flex-row items-center"> 106 + <MessageCircle size={12} color="#737373" /> 107 + <Text className="ml-1 text-neutral-500"> 108 + replying to{" "} 109 + {item.reply.parent.author.displayName ?? 110 + `@${item.reply.parent.author.handle}`} 111 + </Text> 112 + </TouchableOpacity> 113 + )} 94 114 {/* text content */} 95 115 <Link href={postHref} asChild> 96 116 <Pressable> 97 - {inlineReason} 98 117 <RichText value={item.post.record.text} /> 99 118 </Pressable> 100 119 </Link>
+32 -10
apps/expo/src/components/profile-view.tsx
··· 71 71 console.warn(`Missing post: ${record.value.subject.uri}`); 72 72 return null; 73 73 } 74 - return post.data.thread; 74 + 75 + // convert thread view post to feed view post 76 + return { 77 + post: post.data.thread.post, 78 + ...(AppBskyFeedDefs.isThreadViewPost(post.data.thread.parent) && 79 + AppBskyFeedDefs.validateThreadViewPost(post.data.thread.parent) 80 + .success 81 + ? { 82 + reply: { 83 + parent: post.data.thread.parent.post, 84 + // not technically correct but we don't use this field 85 + root: post.data.thread.parent.post, 86 + }, 87 + } 88 + : {}), 89 + } satisfies AppBskyFeedDefs.FeedViewPost; 75 90 }), 76 91 ); 77 92 return { ··· 89 104 if (timeline.status !== "success") return []; 90 105 const flat = timeline.data.pages.flatMap((page) => page.feed); 91 106 return flat 92 - .map((item) => 93 - mode === "replies" && item.reply 94 - ? [ 95 - { item: { post: item.reply.parent }, hasReply: true }, 96 - { item, hasReply: false }, 97 - ] 98 - : [{ item, hasReply: false }], 99 - ) 107 + .map((item) => { 108 + switch (mode) { 109 + case "posts": 110 + return item.reply ? [] : [{ item, hasReply: false }]; 111 + case "replies": 112 + return item.reply && !item.reason 113 + ? [ 114 + { item: { post: item.reply.parent }, hasReply: true }, 115 + { item, hasReply: false }, 116 + ] 117 + : [{ item, hasReply: false }]; 118 + case "likes": 119 + return [{ item, hasReply: false }]; 120 + } 121 + }) 100 122 .flat(); 101 123 }, [timeline, mode]); 102 124 ··· 195 217 /> 196 218 </Tabs> 197 219 ) : ( 198 - <FeedPost {...item} /> 220 + <FeedPost {...item} inlineParent={mode === "likes"} /> 199 221 ) 200 222 } 201 223 stickyHeaderIndices={[0]}