this repo has no description
0
fork

Configure Feed

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

add likes tab

+43 -10
+1 -2
apps/expo/src/app/(tabs)/timeline.tsx
··· 1 1 import { useMemo, useState } from "react"; 2 - import { ActivityIndicator, Text, TouchableOpacity, View } from "react-native"; 2 + import { ActivityIndicator, Text, View } from "react-native"; 3 3 import { Stack } from "expo-router"; 4 4 import { AppBskyFeedDefs } from "@atproto/api"; 5 5 import { FlashList } from "@shopify/flash-list"; ··· 10 10 import { Tab, Tabs } from "../../components/tabs"; 11 11 import { useAuthedAgent } from "../../lib/agent"; 12 12 import { assert } from "../../lib/utils/assert"; 13 - import { cx } from "../../lib/utils/cx"; 14 13 15 14 const actorFromPost = (item: AppBskyFeedDefs.FeedViewPost) => { 16 15 if (AppBskyFeedDefs.isReasonRepost(item.reason)) {
+42 -7
apps/expo/src/components/profile-view.tsx
··· 2 2 import { ActivityIndicator, Text, View } from "react-native"; 3 3 import { SafeAreaView } from "react-native-safe-area-context"; 4 4 import { Stack } from "expo-router"; 5 + import { AppBskyFeedDefs, AppBskyFeedLike } from "@atproto/api"; 5 6 import { FlashList } from "@shopify/flash-list"; 6 7 import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; 7 8 8 9 import { useAuthedAgent } from "../lib/agent"; 10 + import { assert } from "../lib/utils/assert"; 9 11 import { FeedPost } from "./feed-post"; 10 12 import { ProfileInfo } from "./profile-info"; 11 13 import { Tab, Tabs } from "./tabs"; ··· 37 39 cursor: pageParam as string | undefined, 38 40 }); 39 41 return feed.data; 42 + 40 43 case "likes": 41 - // ???????? 44 + // all credit to @handlerug.me for this one 45 + // https://github.com/handlerug/bluesky-liked-posts 46 + const { data } = await agent.api.com.atproto.repo.listRecords({ 47 + repo: handle, 48 + collection: "app.bsky.feed.like", 49 + limit: 3, 50 + cursor: pageParam as string | undefined, 51 + }); 52 + 53 + const likes = await Promise.all( 54 + data.records.map(async (record) => { 55 + if (!AppBskyFeedLike.isRecord(record.value)) { 56 + assert(AppBskyFeedLike.validateRecord(record.value)); 57 + console.warn(`Invalid like record ${record.uri}`); 58 + return null; 59 + } 60 + const post = await agent.getPostThread({ 61 + uri: record.value.subject.uri, 62 + depth: 0, 63 + }); 64 + 65 + if (!AppBskyFeedDefs.isThreadViewPost(post.data.thread)) { 66 + assert( 67 + AppBskyFeedDefs.validateThreadViewPost(post.data.thread), 68 + ); 69 + console.warn(`Missing post: ${record.value.subject.uri}`); 70 + return null; 71 + } 72 + return post.data.thread; 73 + }), 74 + ); 42 75 return { 43 - feed: [], 44 - cursor: undefined, 76 + feed: likes.filter((like): like is AppBskyFeedDefs.FeedViewPost => 77 + Boolean(like), 78 + ), 79 + cursor: data.cursor, 45 80 }; 46 81 } 47 82 }, ··· 142 177 stickyHeaderIndices={[0]} 143 178 onEndReachedThreshold={0.5} 144 179 onEndReached={() => void timeline.fetchNextPage()} 145 - // onRefresh={() => { 146 - // if (!timeline.isRefetching) void timeline.refetch(); 147 - // }} 148 - // refreshing={timeline.isRefetching} 180 + onRefresh={() => { 181 + if (!timeline.isRefetching) void timeline.refetch(); 182 + }} 183 + refreshing={timeline.isRefetching} 149 184 estimatedItemSize={91} 150 185 onScroll={(evt) => { 151 186 const { contentOffset } = evt.nativeEvent;
-1
apps/nextjs/src/pages/index.tsx
··· 1 - import { useState } from "react"; 2 1 import Head from "next/head"; 3 2 4 3 import { api } from "~/utils/api";