More profile refactor updates (#1886)

* Update the profile avatar lightbox

* Update profile editor

* Add dynamic likes tab

* Add dynamic feeds and lists tabs

* Implement lists listing on profiles
This commit is contained in:
Paul Frazee 2023-11-13 13:29:33 -08:00 committed by GitHub
parent 8217761363
commit a01463788d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 432 additions and 84 deletions

View file

@ -0,0 +1,31 @@
import {useQuery} from '@tanstack/react-query'
import {useSession} from '../session'
export const RQKEY = (did: string) => ['profile-extra-info', did]
/**
* Fetches some additional information for the profile screen which
* is not available in the API's ProfileView
*/
export function useProfileExtraInfoQuery(did: string) {
const {agent} = useSession()
return useQuery({
queryKey: RQKEY(did),
async queryFn() {
const [listsRes, feedsRes] = await Promise.all([
agent.app.bsky.graph.getLists({
actor: did,
limit: 1,
}),
agent.app.bsky.feed.getActorFeeds({
actor: did,
limit: 1,
}),
])
return {
hasLists: listsRes.data.lists.length > 0,
hasFeeds: feedsRes.data.feeds.length > 0,
}
},
})
}

View file

@ -1,14 +1,23 @@
import {AtUri} from '@atproto/api'
import {useQuery, useMutation} from '@tanstack/react-query'
import {
AtUri,
AppBskyActorDefs,
AppBskyActorProfile,
AppBskyActorGetProfile,
BskyAgent,
} from '@atproto/api'
import {useQuery, useQueryClient, useMutation} from '@tanstack/react-query'
import {Image as RNImage} from 'react-native-image-crop-picker'
import {useSession} from '../session'
import {updateProfileShadow} from '../cache/profile-shadow'
import {uploadBlob} from '#/lib/api'
import {until} from '#/lib/async/until'
export const RQKEY = (did: string) => ['profile', did]
export function useProfileQuery({did}: {did: string | undefined}) {
const {agent} = useSession()
return useQuery({
queryKey: RQKEY(did),
queryKey: RQKEY(did || ''),
queryFn: async () => {
const res = await agent.getProfile({actor: did || ''})
return res.data
@ -17,6 +26,77 @@ export function useProfileQuery({did}: {did: string | undefined}) {
})
}
interface ProfileUpdateParams {
profile: AppBskyActorDefs.ProfileView
updates: AppBskyActorProfile.Record
newUserAvatar: RNImage | undefined | null
newUserBanner: RNImage | undefined | null
}
export function useProfileUpdateMutation() {
const {agent} = useSession()
const queryClient = useQueryClient()
return useMutation<void, Error, ProfileUpdateParams>({
mutationFn: async ({profile, updates, newUserAvatar, newUserBanner}) => {
await agent.upsertProfile(async existing => {
existing = existing || {}
existing.displayName = updates.displayName
existing.description = updates.description
if (newUserAvatar) {
const res = await uploadBlob(
agent,
newUserAvatar.path,
newUserAvatar.mime,
)
existing.avatar = res.data.blob
} else if (newUserAvatar === null) {
existing.avatar = undefined
}
if (newUserBanner) {
const res = await uploadBlob(
agent,
newUserBanner.path,
newUserBanner.mime,
)
existing.banner = res.data.blob
} else if (newUserBanner === null) {
existing.banner = undefined
}
return existing
})
await whenAppViewReady(agent, profile.did, res => {
if (typeof newUserAvatar !== 'undefined') {
if (newUserAvatar === null && res.data.avatar) {
// url hasnt cleared yet
return false
} else if (res.data.avatar === profile.avatar) {
// url hasnt changed yet
return false
}
}
if (typeof newUserBanner !== 'undefined') {
if (newUserBanner === null && res.data.banner) {
// url hasnt cleared yet
return false
} else if (res.data.banner === profile.banner) {
// url hasnt changed yet
return false
}
}
return (
res.data.displayName === updates.displayName &&
res.data.description === updates.description
)
})
},
onSuccess(data, variables) {
// invalidate cache
queryClient.invalidateQueries({
queryKey: RQKEY(variables.profile.did),
})
},
})
}
export function useProfileFollowMutation() {
const {agent} = useSession()
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
@ -167,3 +247,16 @@ export function useProfileUnblockMutation() {
},
})
}
async function whenAppViewReady(
agent: BskyAgent,
actor: string,
fn: (res: AppBskyActorGetProfile.Response) => boolean,
) {
await until(
5, // 5 tries
1e3, // 1s delay between tries
fn,
() => agent.app.bsky.actor.getProfile({actor}),
)
}