Lists updates: curate lists and blocklists (#1689)

* Add lists screen

* Update Lists screen and List create/edit modal to support curate lists

* Rework the ProfileList screen and add curatelist support

* More ProfileList progress

* Update list modals

* Rename mutelists to modlists

* Layout updates/fixes

* More layout fixes

* Modal fixes

* List list screen updates

* Update feed page to give more info

* Layout fixes to ListAddUser modal

* Layout fixes to FlatList and Feed on desktop

* Layout fix to LoadLatestBtn on Web

* Handle did resolution before showing the ProfileList screen

* Rename the CustomFeed routes to ProfileFeed for consistency

* Fix layout issues with the pager and feeds

* Factor out some common code

* Fix UIs for mobile

* Fix user list rendering

* Fix: dont bubble custom feed errors in the merge feed

* Refactor feed models to reduce usage of the SavedFeeds model

* Replace CustomFeedModel with FeedSourceModel which abstracts feed-generators and lists

* Add the ability to pin lists

* Add pinned lists to mobile

* Remove dead code

* Rework the ProfileScreenHeader to create more real-estate for action buttons

* Improve layout behavior on web mobile breakpoints

* Refactor feed & list pages to use new Tabs layout component

* Refactor to ProfileSubpageHeader

* Implement modlist block and mute

* Switch to new api and just modify state on modlist actions

* Fix some UI overflows

* Fix: dont show edit buttons on lists you dont own

* Fix alignment issue on long titles

* Improve loading and error states for feeds & lists

* Update list dropdown icons for ios

* Fetch feed display names in the mergefeed

* Improve rendering off offline feeds in the feed-listing page

* Update Feeds listing UI to react to changes in saved/pinned state

* Refresh list and feed on posts tab press

* Fix pinned feed ordering UI

* Fixes to list pinning

* Remove view=simple qp

* Add list to feed tuners

* Render richtext

* Add list href

* Add 'view avatar'

* Remove unused import

* Fix missing import

* Correctly reflect block by list state

* Replace the <Tabs> component with the more effective <PagerWithHeader> component

* Improve the responsiveness of the PagerWithHeader

* Fix visual jank in the feed loading state

* Improve performance of the PagerWithHeader

* Fix a case that would cause the header to animate too aggressively

* Add the ability to scroll to top by tapping the selected tab

* Fix unit test runner

* Update modlists test

* Add curatelist tests

* Fix: remove link behavior in ListAddUser modal

* Fix some layout jank in the PagerWithHeader on iOS

* Simplify ListItems header rendering

* Wait for the appview to recognize the list before proceeding with list creation

* Fix glitch in the onPageSelecting index of the Pager

* Fix until()

* Copy fix

Co-authored-by: Eric Bailey <git@esb.lol>

---------

Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
Paul Frazee 2023-11-01 16:15:40 -07:00 committed by GitHub
parent f9944b55e2
commit f57a8cf8ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 4090 additions and 1988 deletions

45
src/lib/api/feed/list.ts Normal file
View file

@ -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: [],
}
}
}

View file

@ -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),
),
)
}
@ -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)
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: []}}
}
// 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
}
}