Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lists updates: curate lists and blocklists #1689

Merged
merged 67 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
833f525
Add lists screen
pfrazee Oct 9, 2023
5546ad8
Update Lists screen and List create/edit modal to support curate lists
pfrazee Oct 10, 2023
fa599a6
Rework the ProfileList screen and add curatelist support
pfrazee Oct 10, 2023
3f2b787
More ProfileList progress
pfrazee Oct 11, 2023
e41e65e
Update list modals
pfrazee Oct 11, 2023
207a861
Rename mutelists to modlists
pfrazee Oct 11, 2023
8e08f3d
Layout updates/fixes
pfrazee Oct 11, 2023
dc28bde
More layout fixes
pfrazee Oct 11, 2023
743ca9d
Modal fixes
pfrazee Oct 11, 2023
276aa7f
List list screen updates
pfrazee Oct 11, 2023
7c42ec9
Update feed page to give more info
pfrazee Oct 12, 2023
059d908
Layout fixes to ListAddUser modal
pfrazee Oct 12, 2023
744d0be
Layout fixes to FlatList and Feed on desktop
pfrazee Oct 12, 2023
fd7182b
Layout fix to LoadLatestBtn on Web
pfrazee Oct 12, 2023
6e2d37b
Handle did resolution before showing the ProfileList screen
pfrazee Oct 12, 2023
d7753e0
Rename the CustomFeed routes to ProfileFeed for consistency
pfrazee Oct 12, 2023
1c76aba
Fix layout issues with the pager and feeds
pfrazee Oct 12, 2023
922615c
Factor out some common code
pfrazee Oct 12, 2023
9bc1352
Fix UIs for mobile
pfrazee Oct 12, 2023
b843094
Fix user list rendering
pfrazee Oct 12, 2023
95340ca
Fix: dont bubble custom feed errors in the merge feed
pfrazee Oct 12, 2023
a30e846
Refactor feed models to reduce usage of the SavedFeeds model
pfrazee Oct 12, 2023
1baefab
Replace CustomFeedModel with FeedSourceModel which abstracts feed-gen…
pfrazee Oct 13, 2023
fc0932f
Add the ability to pin lists
pfrazee Oct 13, 2023
1ef1160
Add pinned lists to mobile
pfrazee Oct 13, 2023
f085a91
Remove dead code
pfrazee Oct 13, 2023
6d576c7
Rework the ProfileScreenHeader to create more real-estate for action …
pfrazee Oct 14, 2023
a823f94
Improve layout behavior on web mobile breakpoints
pfrazee Oct 14, 2023
cf57252
Refactor feed & list pages to use new Tabs layout component
pfrazee Oct 25, 2023
db6fbd6
Refactor to ProfileSubpageHeader
pfrazee Oct 26, 2023
7109d97
Implement modlist block and mute
pfrazee Oct 26, 2023
f11e9f3
Switch to new api and just modify state on modlist actions
pfrazee Oct 26, 2023
499eea2
Fix some UI overflows
pfrazee Oct 26, 2023
8204959
Fix: dont show edit buttons on lists you dont own
pfrazee Oct 26, 2023
bcf591b
Fix alignment issue on long titles
pfrazee Oct 26, 2023
e5b0658
Improve loading and error states for feeds & lists
pfrazee Oct 26, 2023
ff487f7
Update list dropdown icons for ios
pfrazee Oct 26, 2023
3edf5d6
Fetch feed display names in the mergefeed
pfrazee Oct 26, 2023
259e0b8
Improve rendering off offline feeds in the feed-listing page
pfrazee Oct 26, 2023
21d23fc
Update Feeds listing UI to react to changes in saved/pinned state
pfrazee Oct 26, 2023
8dd228d
Refresh list and feed on posts tab press
pfrazee Oct 26, 2023
17e308b
Fix pinned feed ordering UI
pfrazee Oct 26, 2023
0eb9b3a
Fixes to list pinning
pfrazee Oct 26, 2023
647d0a8
Remove view=simple qp
pfrazee Oct 26, 2023
9a28f2f
Add list to feed tuners
pfrazee Oct 26, 2023
9472ddc
Render richtext
pfrazee Oct 26, 2023
31fe65a
Add list href
pfrazee Oct 26, 2023
55990ea
Add 'view avatar'
pfrazee Oct 26, 2023
cb9954d
Remove unused import
pfrazee Oct 26, 2023
c104dba
Fix missing import
pfrazee Oct 26, 2023
a0a8f08
Correctly reflect block by list state
pfrazee Oct 31, 2023
dce0fe9
Replace the <Tabs> component with the more effective <PagerWithHeader…
pfrazee Nov 1, 2023
b9ed8d6
Improve the responsiveness of the PagerWithHeader
pfrazee Nov 1, 2023
05a4406
Fix visual jank in the feed loading state
pfrazee Nov 1, 2023
9ed23af
Improve performance of the PagerWithHeader
pfrazee Nov 1, 2023
ab89453
Fix a case that would cause the header to animate too aggressively
pfrazee Nov 1, 2023
b2bafc7
Add the ability to scroll to top by tapping the selected tab
pfrazee Nov 1, 2023
c5a265d
Fix unit test runner
pfrazee Nov 1, 2023
daf8f95
Update modlists test
pfrazee Nov 1, 2023
2b7b07c
Add curatelist tests
pfrazee Nov 1, 2023
db519a0
Fix: remove link behavior in ListAddUser modal
pfrazee Nov 1, 2023
4ce013b
Fix some layout jank in the PagerWithHeader on iOS
pfrazee Nov 1, 2023
0525ce0
Simplify ListItems header rendering
pfrazee Nov 1, 2023
5b73242
Wait for the appview to recognize the list before proceeding with lis…
pfrazee Nov 1, 2023
d43cefc
Fix glitch in the onPageSelecting index of the Pager
pfrazee Nov 1, 2023
f28aaac
Fix until()
pfrazee Nov 1, 2023
dd16aa9
Copy fix
pfrazee Nov 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bskyweb/cmd/bskyweb/server.go
estrattonbailey marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,9 @@ func serve(cctx *cli.Context) error {
e.GET("/search", server.WebGeneric)
e.GET("/feeds", server.WebGeneric)
e.GET("/notifications", server.WebGeneric)
e.GET("/lists", server.WebGeneric)
e.GET("/moderation", server.WebGeneric)
e.GET("/moderation/mute-lists", server.WebGeneric)
e.GET("/moderation/modlists", server.WebGeneric)
e.GET("/moderation/muted-accounts", server.WebGeneric)
e.GET("/moderation/blocked-accounts", server.WebGeneric)
e.GET("/settings", server.WebGeneric)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"sentry-expo": "~7.0.1",
"tippy.js": "^6.3.7",
"tlds": "^1.234.0",
"use-deep-compare": "^1.1.0",
"zeego": "^1.6.2",
"zod": "^3.20.2"
},
Expand Down
28 changes: 17 additions & 11 deletions src/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,17 @@ import {HomeScreen} from './view/screens/Home'
import {SearchScreen} from './view/screens/Search'
import {FeedsScreen} from './view/screens/Feeds'
import {NotificationsScreen} from './view/screens/Notifications'
import {ListsScreen} from './view/screens/Lists'
import {ModerationScreen} from './view/screens/Moderation'
import {ModerationMuteListsScreen} from './view/screens/ModerationMuteLists'
import {ModerationModlistsScreen} from './view/screens/ModerationModlists'
import {NotFoundScreen} from './view/screens/NotFound'
import {SettingsScreen} from './view/screens/Settings'
import {LanguageSettingsScreen} from './view/screens/LanguageSettings'
import {ProfileScreen} from './view/screens/Profile'
import {ProfileFollowersScreen} from './view/screens/ProfileFollowers'
import {ProfileFollowsScreen} from './view/screens/ProfileFollows'
import {CustomFeedScreen} from './view/screens/CustomFeed'
import {CustomFeedLikedByScreen} from './view/screens/CustomFeedLikedBy'
import {ProfileFeedScreen} from './view/screens/ProfileFeed'
import {ProfileFeedLikedByScreen} from './view/screens/ProfileFeedLikedBy'
import {ProfileListScreen} from './view/screens/ProfileList'
import {PostThreadScreen} from './view/screens/PostThread'
import {PostLikedByScreen} from './view/screens/PostLikedBy'
Expand Down Expand Up @@ -95,15 +96,20 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => NotFoundScreen}
options={{title: title('Not Found')}}
/>
<Stack.Screen
name="Lists"
component={ListsScreen}
options={{title: title('Lists')}}
/>
<Stack.Screen
name="Moderation"
getComponent={() => ModerationScreen}
options={{title: title('Moderation')}}
/>
<Stack.Screen
name="ModerationMuteLists"
getComponent={() => ModerationMuteListsScreen}
options={{title: title('Mute Lists')}}
name="ModerationModlists"
getComponent={() => ModerationModlistsScreen}
options={{title: title('Moderation Lists')}}
/>
<Stack.Screen
name="ModerationMutedAccounts"
Expand Down Expand Up @@ -150,7 +156,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
<Stack.Screen
name="ProfileList"
getComponent={() => ProfileListScreen}
options={{title: title('Mute List')}}
options={{title: title('List')}}
/>
<Stack.Screen
name="PostThread"
Expand All @@ -168,13 +174,13 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
options={({route}) => ({title: title(`Post by @${route.params.name}`)})}
/>
<Stack.Screen
name="CustomFeed"
getComponent={() => CustomFeedScreen}
name="ProfileFeed"
getComponent={() => ProfileFeedScreen}
options={{title: title('Feed')}}
/>
<Stack.Screen
name="CustomFeedLikedBy"
getComponent={() => CustomFeedLikedByScreen}
name="ProfileFeedLikedBy"
getComponent={() => ProfileFeedLikedByScreen}
estrattonbailey marked this conversation as resolved.
Show resolved Hide resolved
options={{title: title('Liked by')}}
/>
<Stack.Screen
Expand Down
11 changes: 7 additions & 4 deletions src/lib/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,13 @@ interface TrackPropertiesMap {
// LISTS events
'Lists:onRefresh': {}
'Lists:onEndReached': {}
'CreateMuteList:AvatarSelected': {}
'CreateMuteList:Save': {} // CAN BE SERVER
'Lists:Subscribe': {} // CAN BE SERVER
'Lists:Unsubscribe': {} // CAN BE SERVER
'CreateList:AvatarSelected': {}
'CreateList:SaveCurateList': {} // CAN BE SERVER
'CreateList:SaveModList': {} // CAN BE SERVER
estrattonbailey marked this conversation as resolved.
Show resolved Hide resolved
'Lists:Mute': {} // CAN BE SERVER
'Lists:Unmute': {} // CAN BE SERVER
'Lists:Block': {} // CAN BE SERVER
'Lists:Unblock': {} // CAN BE SERVER
// CUSTOM FEED events
'CustomFeed:Save': {}
'CustomFeed:Unsave': {}
Expand Down
45 changes: 45 additions & 0 deletions src/lib/api/feed/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
AppBskyFeedDefs,
AppBskyFeedGetListFeed as GetListFeed,
} from '@atproto/api'
import {RootStoreModel} from 'state/index'
import {FeedAPI, FeedAPIResponse} from './types'

export class ListFeedAPI implements FeedAPI {
cursor: string | undefined

constructor(
public rootStore: RootStoreModel,
public params: GetListFeed.QueryParams,
) {}

reset() {
this.cursor = undefined
}

async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.rootStore.agent.app.bsky.feed.getListFeed({
...this.params,
limit: 1,
})
return res.data.feed[0]
}

async fetchNext({limit}: {limit: number}): Promise<FeedAPIResponse> {
const res = await this.rootStore.agent.app.bsky.feed.getListFeed({
...this.params,
cursor: this.cursor,
limit,
})
if (res.success) {
this.cursor = res.data.cursor
return {
cursor: res.data.cursor,
feed: res.data.feed,
}
}
return {
feed: [],
}
}
}
74 changes: 41 additions & 33 deletions src/lib/api/feed/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,8 @@ export class MergeFeedAPI implements FeedAPI {
}
if (this.customFeeds.length === 0) {
this.customFeeds = shuffle(
this.rootStore.me.savedFeeds.all.map(
feed =>
new MergeFeedSource_Custom(
this.rootStore,
feed.uri,
feed.displayName,
),
this.rootStore.preferences.savedFeeds.map(
feedUri => new MergeFeedSource_Custom(this.rootStore, feedUri),
),
)
}
Expand Down Expand Up @@ -213,43 +208,56 @@ class MergeFeedSource_Following extends MergeFeedSource {
class MergeFeedSource_Custom extends MergeFeedSource {
minDate: Date

constructor(
public rootStore: RootStoreModel,
public feedUri: string,
public feedDisplayName: string,
) {
constructor(public rootStore: RootStoreModel, public feedUri: string) {
super(rootStore)
this.sourceInfo = {
displayName: feedDisplayName,
displayName: feedUri.split('/').pop() || '',
uri: feedUriToHref(feedUri),
}
this.minDate = new Date(Date.now() - POST_AGE_CUTOFF)
this.rootStore.agent.app.bsky.feed
.getFeedGenerator({
feed: feedUri,
})
.then(
res => {
if (this.sourceInfo) {
this.sourceInfo.displayName = res.data.view.displayName
}
},
_err => {},
)
}

protected async _getFeed(
cursor: string | undefined,
limit: number,
): Promise<AppBskyFeedGetTimeline.Response> {
const res = await this.rootStore.agent.app.bsky.feed.getFeed({
cursor,
limit,
feed: this.feedUri,
})
// NOTE
// some custom feeds fail to enforce the pagination limit
// so we manually truncate here
// -prf
if (limit && res.data.feed.length > limit) {
res.data.feed = res.data.feed.slice(0, limit)
}
// filter out older posts
res.data.feed = res.data.feed.filter(
post => new Date(post.post.indexedAt) > this.minDate,
)
// attach source info
for (const post of res.data.feed) {
post.__source = this.sourceInfo
try {
const res = await this.rootStore.agent.app.bsky.feed.getFeed({
cursor,
limit,
feed: this.feedUri,
})
// NOTE
// some custom feeds fail to enforce the pagination limit
// so we manually truncate here
// -prf
if (limit && res.data.feed.length > limit) {
res.data.feed = res.data.feed.slice(0, limit)
}
// filter out older posts
res.data.feed = res.data.feed.filter(
post => new Date(post.post.indexedAt) > this.minDate,
)
// attach source info
for (const post of res.data.feed) {
post.__source = this.sourceInfo
}
return res
} catch {
// dont bubble custom-feed errors
return {success: false, headers: {}, data: {feed: []}}
estrattonbailey marked this conversation as resolved.
Show resolved Hide resolved
}
return res
}
}
25 changes: 25 additions & 0 deletions src/lib/async/accumulate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export interface AccumulateResponse<T> {
cursor?: string
items: T[]
}

export type AccumulateFetchFn<T> = (
cursor: string | undefined,
) => Promise<AccumulateResponse<T>>

export async function accumulate<T>(
fn: AccumulateFetchFn<T>,
pageLimit = 100,
): Promise<T[]> {
let cursor: string | undefined
let acc: T[] = []
for (let i = 0; i < pageLimit; i++) {
const res = await fn(cursor)
estrattonbailey marked this conversation as resolved.
Show resolved Hide resolved
cursor = res.cursor
acc = acc.concat(res.items)
if (!cursor) {
break
}
}
return acc
}
21 changes: 6 additions & 15 deletions src/lib/hooks/useCustomFeed.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import {useEffect, useState} from 'react'
import {useStores} from 'state/index'
import {CustomFeedModel} from 'state/models/feeds/custom-feed'
import {FeedSourceModel} from 'state/models/content/feed-source'

export function useCustomFeed(uri: string): CustomFeedModel | undefined {
export function useCustomFeed(uri: string): FeedSourceModel | undefined {
const store = useStores()
const [item, setItem] = useState<CustomFeedModel | undefined>()
const [item, setItem] = useState<FeedSourceModel | undefined>()
useEffect(() => {
async function fetchView() {
const res = await store.agent.app.bsky.feed.getFeedGenerator({
feed: uri,
})
const view = res.data.view
return view
}
async function buildFeedItem() {
const view = await fetchView()
if (view) {
const temp = new CustomFeedModel(store, view)
setItem(temp)
}
const model = new FeedSourceModel(store, uri)
await model.setup()
setItem(model)
}
buildFeedItem()
}, [store, uri])
Expand Down
51 changes: 51 additions & 0 deletions src/lib/hooks/useDesktopRightNavItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {useEffect, useState} from 'react'
import {useStores} from 'state/index'
import isEqual from 'lodash.isequal'
import {AtUri} from '@atproto/api'
import {FeedSourceModel} from 'state/models/content/feed-source'

interface RightNavItem {
uri: string
href: string
hostname: string
collection: string
rkey: string
displayName: string
}

export function useDesktopRightNavItems(uris: string[]): RightNavItem[] {
estrattonbailey marked this conversation as resolved.
Show resolved Hide resolved
const store = useStores()
const [items, setItems] = useState<RightNavItem[]>([])
const [lastUris, setLastUris] = useState<string[]>([])

useEffect(() => {
if (isEqual(uris, lastUris)) {
// no changes
return
}

async function fetchFeedInfo() {
const models = uris
.slice(0, 25)
.map(uri => new FeedSourceModel(store, uri))
await Promise.all(models.map(m => m.setup()))
setItems(
models.map(model => {
const {hostname, collection, rkey} = new AtUri(model.uri)
return {
uri: model.uri,
href: model.href,
hostname,
collection,
rkey,
displayName: model.displayName,
}
}),
)
setLastUris(uris)
}
fetchFeedInfo()
}, [store, uris, lastUris, setLastUris, setItems])

return items
}
29 changes: 29 additions & 0 deletions src/lib/hooks/useHomeTabs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {useEffect, useState} from 'react'
import {useStores} from 'state/index'
import isEqual from 'lodash.isequal'
import {FeedSourceModel} from 'state/models/content/feed-source'

export function useHomeTabs(uris: string[]): string[] {
const store = useStores()
const [tabs, setTabs] = useState<string[]>(['Following'])
const [lastUris, setLastUris] = useState<string[]>([])

useEffect(() => {
if (isEqual(uris, lastUris)) {
// no changes
return
}

async function fetchFeedInfo() {
const models = uris
.slice(0, 25)
.map(uri => new FeedSourceModel(store, uri))
await Promise.all(models.map(m => m.setup()))
setTabs(['Following'].concat(models.map(f => f.displayName)))
setLastUris(uris)
}
fetchFeedInfo()
}, [store, uris, lastUris, setLastUris, setTabs])

return tabs
}
Loading