diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/modals/index.tsx | 3 | ||||
-rw-r--r-- | src/state/models/ui/shell.ts | 5 | ||||
-rw-r--r-- | src/state/queries/profile-extra-info.ts | 31 | ||||
-rw-r--r-- | src/state/queries/profile.ts | 99 |
4 files changed, 130 insertions, 8 deletions
diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index 6c63d9fc1..57f486630 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -3,7 +3,6 @@ import {AppBskyActorDefs, AppBskyGraphDefs, ModerationUI} from '@atproto/api' import {StyleProp, ViewStyle, DeviceEventEmitter} from 'react-native' import {Image as RNImage} from 'react-native-image-crop-picker' -import {ProfileModel} from '#/state/models/content/profile' import {ImageModel} from '#/state/models/media/image' import {GalleryModel} from '#/state/models/media/gallery' @@ -20,7 +19,7 @@ export interface ConfirmModal { export interface EditProfileModal { name: 'edit-profile' - profileView: ProfileModel + profile: AppBskyActorDefs.ProfileViewDetailed onUpdate?: () => void } diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index 8ef322db5..9ce9b6635 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -1,7 +1,6 @@ -import {AppBskyEmbedRecord} from '@atproto/api' +import {AppBskyEmbedRecord, AppBskyActorDefs} from '@atproto/api' import {RootStoreModel} from '../root-store' import {makeAutoObservable, runInAction} from 'mobx' -import {ProfileModel} from '../content/profile' import { shouldRequestEmailConfirmation, setEmailConfirmationRequested, @@ -18,7 +17,7 @@ interface LightboxModel {} export class ProfileImageLightbox implements LightboxModel { name = 'profile-image' - constructor(public profileView: ProfileModel) { + constructor(public profile: AppBskyActorDefs.ProfileViewDetailed) { makeAutoObservable(this) } } diff --git a/src/state/queries/profile-extra-info.ts b/src/state/queries/profile-extra-info.ts new file mode 100644 index 000000000..54b19c89a --- /dev/null +++ b/src/state/queries/profile-extra-info.ts @@ -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, + } + }, + }) +} diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 1bd28d5b1..63367b261 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -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}), + ) +} |