about summary refs log tree commit diff
path: root/src/state/cache
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2023-11-13 18:35:15 +0000
committerGitHub <noreply@github.com>2023-11-13 10:35:15 -0800
commite1938931e028f4486cce8cd9da39ffd940c10ec2 (patch)
treed7d22d93a95503350ab4d2d0ace409cd873815cb /src/state/cache
parentc3edde8ac6f9c65eac1004cd8e2fc14b0493cba8 (diff)
downloadvoidsky-e1938931e028f4486cce8cd9da39ffd940c10ec2.tar.zst
Refactor profile screen to use new pager and react-query (#1870)
* Profile tabs WIP

* Refactor the profile screen to use react-query (WIP)

* Add the profile shadow and get follow, mute, and block working

* Cleanup

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Diffstat (limited to 'src/state/cache')
-rw-r--r--src/state/cache/profile-shadow.ts88
1 files changed, 88 insertions, 0 deletions
diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts
new file mode 100644
index 000000000..a1cf59954
--- /dev/null
+++ b/src/state/cache/profile-shadow.ts
@@ -0,0 +1,88 @@
+import {useEffect, useState, useCallback, useRef} from 'react'
+import EventEmitter from 'eventemitter3'
+import {AppBskyActorDefs} from '@atproto/api'
+
+const emitter = new EventEmitter()
+
+export interface ProfileShadow {
+  followingUri: string | undefined
+  muted: boolean | undefined
+  blockingUri: string | undefined
+}
+
+interface CacheEntry {
+  ts: number
+  value: ProfileShadow
+}
+
+type ProfileView =
+  | AppBskyActorDefs.ProfileView
+  | AppBskyActorDefs.ProfileViewBasic
+  | AppBskyActorDefs.ProfileViewDetailed
+
+export function useProfileShadow<T extends ProfileView>(
+  profile: T,
+  ifAfterTS: number,
+): T {
+  const [state, setState] = useState<CacheEntry>({
+    ts: Date.now(),
+    value: fromProfile(profile),
+  })
+  const firstRun = useRef(true)
+
+  const onUpdate = useCallback(
+    (value: Partial<ProfileShadow>) => {
+      setState(s => ({ts: Date.now(), value: {...s.value, ...value}}))
+    },
+    [setState],
+  )
+
+  // react to shadow updates
+  useEffect(() => {
+    emitter.addListener(profile.did, onUpdate)
+    return () => {
+      emitter.removeListener(profile.did, onUpdate)
+    }
+  }, [profile.did, onUpdate])
+
+  // react to profile updates
+  useEffect(() => {
+    // dont fire on first run to avoid needless re-renders
+    if (!firstRun.current) {
+      setState({ts: Date.now(), value: fromProfile(profile)})
+    }
+    firstRun.current = false
+  }, [profile])
+
+  return state.ts > ifAfterTS ? mergeShadow(profile, state.value) : profile
+}
+
+export function updateProfileShadow(
+  uri: string,
+  value: Partial<ProfileShadow>,
+) {
+  emitter.emit(uri, value)
+}
+
+function fromProfile(profile: ProfileView): ProfileShadow {
+  return {
+    followingUri: profile.viewer?.following,
+    muted: profile.viewer?.muted,
+    blockingUri: profile.viewer?.blocking,
+  }
+}
+
+function mergeShadow<T extends ProfileView>(
+  profile: T,
+  shadow: ProfileShadow,
+): T {
+  return {
+    ...profile,
+    viewer: {
+      ...(profile.viewer || {}),
+      following: shadow.followingUri,
+      muted: shadow.muted,
+      blocking: shadow.blockingUri,
+    },
+  }
+}