about summary refs log tree commit diff
path: root/src/state
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-11-14 10:41:55 -0800
committerGitHub <noreply@github.com>2023-11-14 10:41:55 -0800
commit0a26e78dcbbf48dad5daae73b210e236d706b22c (patch)
treec06c737ed49e8294bf5cbec1a75c36b591cb6669 /src/state
parentc687172de96bd6aa85d3aa025c2e0f024640f345 (diff)
downloadvoidsky-0a26e78dcbbf48dad5daae73b210e236d706b22c.tar.zst
Composer update (react-query refactor) (#1899)
* Move composer state to a context

* Rework composer to use RQ

---------

Co-authored-by: Eric Bailey <git@esb.lol>
Diffstat (limited to 'src/state')
-rw-r--r--src/state/models/media/gallery.ts9
-rw-r--r--src/state/models/media/image.ts9
-rw-r--r--src/state/models/ui/shell.ts52
-rw-r--r--src/state/queries/actor-autocomplete.ts54
-rw-r--r--src/state/shell/composer.tsx74
-rw-r--r--src/state/shell/index.tsx5
6 files changed, 138 insertions, 65 deletions
diff --git a/src/state/models/media/gallery.ts b/src/state/models/media/gallery.ts
index f9c3efcad..04023bf82 100644
--- a/src/state/models/media/gallery.ts
+++ b/src/state/models/media/gallery.ts
@@ -1,5 +1,4 @@
 import {makeAutoObservable, runInAction} from 'mobx'
-import {RootStoreModel} from 'state/index'
 import {ImageModel} from './image'
 import {Image as RNImage} from 'react-native-image-crop-picker'
 import {openPicker} from 'lib/media/picker'
@@ -8,10 +7,8 @@ import {getImageDim} from 'lib/media/manip'
 export class GalleryModel {
   images: ImageModel[] = []
 
-  constructor(public rootStore: RootStoreModel) {
-    makeAutoObservable(this, {
-      rootStore: false,
-    })
+  constructor() {
+    makeAutoObservable(this)
   }
 
   get isEmpty() {
@@ -33,7 +30,7 @@ export class GalleryModel {
 
     // Temporarily enforce uniqueness but can eventually also use index
     if (!this.images.some(i => i.path === image_.path)) {
-      const image = new ImageModel(this.rootStore, image_)
+      const image = new ImageModel(image_)
 
       // Initial resize
       image.manipulate({})
diff --git a/src/state/models/media/image.ts b/src/state/models/media/image.ts
index b3796060c..6a226484e 100644
--- a/src/state/models/media/image.ts
+++ b/src/state/models/media/image.ts
@@ -1,5 +1,4 @@
 import {Image as RNImage} from 'react-native-image-crop-picker'
-import {RootStoreModel} from 'state/index'
 import {makeAutoObservable, runInAction} from 'mobx'
 import {POST_IMG_MAX} from 'lib/constants'
 import * as ImageManipulator from 'expo-image-manipulator'
@@ -42,10 +41,8 @@ export class ImageModel implements Omit<RNImage, 'size'> {
   }
   prevAttributes: ImageManipulationAttributes = {}
 
-  constructor(public rootStore: RootStoreModel, image: Omit<RNImage, 'size'>) {
-    makeAutoObservable(this, {
-      rootStore: false,
-    })
+  constructor(image: Omit<RNImage, 'size'>) {
+    makeAutoObservable(this)
 
     this.path = image.path
     this.width = image.width
@@ -178,7 +175,7 @@ export class ImageModel implements Omit<RNImage, 'size'> {
         height: this.height,
       })
 
-      const cropped = await openCropper(this.rootStore, {
+      const cropped = await openCropper({
         mediaType: 'photo',
         path: this.path,
         freeStyleCropEnabled: true,
diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts
index 9ce9b6635..310d4f0f9 100644
--- a/src/state/models/ui/shell.ts
+++ b/src/state/models/ui/shell.ts
@@ -1,4 +1,4 @@
-import {AppBskyEmbedRecord, AppBskyActorDefs} from '@atproto/api'
+import {AppBskyActorDefs} from '@atproto/api'
 import {RootStoreModel} from '../root-store'
 import {makeAutoObservable, runInAction} from 'mobx'
 import {
@@ -37,41 +37,9 @@ export class ImagesLightbox implements LightboxModel {
   }
 }
 
-export interface ComposerOptsPostRef {
-  uri: string
-  cid: string
-  text: string
-  author: {
-    handle: string
-    displayName?: string
-    avatar?: string
-  }
-}
-export interface ComposerOptsQuote {
-  uri: string
-  cid: string
-  text: string
-  indexedAt: string
-  author: {
-    did: string
-    handle: string
-    displayName?: string
-    avatar?: string
-  }
-  embeds?: AppBskyEmbedRecord.ViewRecord['embeds']
-}
-export interface ComposerOpts {
-  replyTo?: ComposerOptsPostRef
-  onPost?: () => void
-  quote?: ComposerOptsQuote
-  mention?: string // handle of user to mention
-}
-
 export class ShellUiModel {
   isLightboxActive = false
   activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null
-  isComposerActive = false
-  composerOpts: ComposerOpts | undefined
   tickEveryMinute = Date.now()
 
   constructor(public rootStore: RootStoreModel) {
@@ -92,10 +60,6 @@ export class ShellUiModel {
       this.closeLightbox()
       return true
     }
-    if (this.isComposerActive) {
-      this.closeComposer()
-      return true
-    }
     return false
   }
 
@@ -106,9 +70,6 @@ export class ShellUiModel {
     if (this.isLightboxActive) {
       this.closeLightbox()
     }
-    if (this.isComposerActive) {
-      this.closeComposer()
-    }
   }
 
   openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) {
@@ -122,17 +83,6 @@ export class ShellUiModel {
     this.activeLightbox = null
   }
 
-  openComposer(opts: ComposerOpts) {
-    this.rootStore.emitNavigation()
-    this.isComposerActive = true
-    this.composerOpts = opts
-  }
-
-  closeComposer() {
-    this.isComposerActive = false
-    this.composerOpts = undefined
-  }
-
   setupClock() {
     setInterval(() => {
       runInAction(() => {
diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts
index 18abb6314..62c4781c4 100644
--- a/src/state/queries/actor-autocomplete.ts
+++ b/src/state/queries/actor-autocomplete.ts
@@ -1,7 +1,8 @@
-import {AppBskyActorDefs} from '@atproto/api'
+import {AppBskyActorDefs, BskyAgent} from '@atproto/api'
 import {useQuery} from '@tanstack/react-query'
 import {useSession} from '../session'
 import {useMyFollowsQuery} from './my-follows'
+import AwaitLock from 'await-lock'
 
 export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix]
 
@@ -21,6 +22,57 @@ export function useActorAutocompleteQuery(prefix: string) {
   })
 }
 
+export class ActorAutocomplete {
+  // state
+  isLoading = false
+  isActive = false
+  prefix = ''
+  lock = new AwaitLock()
+
+  // data
+  suggestions: AppBskyActorDefs.ProfileViewBasic[] = []
+
+  constructor(
+    public agent: BskyAgent,
+    public follows?: AppBskyActorDefs.ProfileViewBasic[] | undefined,
+  ) {}
+
+  setFollows(follows: AppBskyActorDefs.ProfileViewBasic[]) {
+    this.follows = follows
+  }
+
+  async query(prefix: string) {
+    const origPrefix = prefix.trim().toLocaleLowerCase()
+    this.prefix = origPrefix
+    await this.lock.acquireAsync()
+    try {
+      if (this.prefix) {
+        if (this.prefix !== origPrefix) {
+          return // another prefix was set before we got our chance
+        }
+
+        // start with follow results
+        this.suggestions = computeSuggestions(this.prefix, this.follows)
+
+        // ask backend
+        const res = await this.agent.searchActorsTypeahead({
+          term: this.prefix,
+          limit: 8,
+        })
+        this.suggestions = computeSuggestions(
+          this.prefix,
+          this.follows,
+          res.data.actors,
+        )
+      } else {
+        this.suggestions = computeSuggestions(this.prefix, this.follows)
+      }
+    } finally {
+      this.lock.release()
+    }
+  }
+}
+
 function computeSuggestions(
   prefix: string,
   follows: AppBskyActorDefs.ProfileViewBasic[] = [],
diff --git a/src/state/shell/composer.tsx b/src/state/shell/composer.tsx
new file mode 100644
index 000000000..a350bd7f3
--- /dev/null
+++ b/src/state/shell/composer.tsx
@@ -0,0 +1,74 @@
+import React from 'react'
+import {AppBskyEmbedRecord} from '@atproto/api'
+
+export interface ComposerOptsPostRef {
+  uri: string
+  cid: string
+  text: string
+  author: {
+    handle: string
+    displayName?: string
+    avatar?: string
+  }
+}
+export interface ComposerOptsQuote {
+  uri: string
+  cid: string
+  text: string
+  indexedAt: string
+  author: {
+    did: string
+    handle: string
+    displayName?: string
+    avatar?: string
+  }
+  embeds?: AppBskyEmbedRecord.ViewRecord['embeds']
+}
+export interface ComposerOpts {
+  replyTo?: ComposerOptsPostRef
+  onPost?: () => void
+  quote?: ComposerOptsQuote
+  mention?: string // handle of user to mention
+}
+
+type StateContext = ComposerOpts | undefined
+type ControlsContext = {
+  openComposer: (opts: ComposerOpts) => void
+  closeComposer: () => void
+}
+
+const stateContext = React.createContext<StateContext>(undefined)
+const controlsContext = React.createContext<ControlsContext>({
+  openComposer(_opts: ComposerOpts) {},
+  closeComposer() {},
+})
+
+export function Provider({children}: React.PropsWithChildren<{}>) {
+  const [state, setState] = React.useState<StateContext>()
+  const api = React.useMemo(
+    () => ({
+      openComposer(opts: ComposerOpts) {
+        setState(opts)
+      },
+      closeComposer() {
+        setState(undefined)
+      },
+    }),
+    [setState],
+  )
+  return (
+    <stateContext.Provider value={state}>
+      <controlsContext.Provider value={api}>
+        {children}
+      </controlsContext.Provider>
+    </stateContext.Provider>
+  )
+}
+
+export function useComposerState() {
+  return React.useContext(stateContext)
+}
+
+export function useComposerControls() {
+  return React.useContext(controlsContext)
+}
diff --git a/src/state/shell/index.tsx b/src/state/shell/index.tsx
index eb549b9f9..63c3763d1 100644
--- a/src/state/shell/index.tsx
+++ b/src/state/shell/index.tsx
@@ -5,6 +5,7 @@ import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled'
 import {Provider as MinimalModeProvider} from './minimal-mode'
 import {Provider as ColorModeProvider} from './color-mode'
 import {Provider as OnboardingProvider} from './onboarding'
+import {Provider as ComposerProvider} from './composer'
 
 export {useIsDrawerOpen, useSetDrawerOpen} from './drawer-open'
 export {
@@ -22,7 +23,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
         <DrawerSwipableProvider>
           <MinimalModeProvider>
             <ColorModeProvider>
-              <OnboardingProvider>{children}</OnboardingProvider>
+              <OnboardingProvider>
+                <ComposerProvider>{children}</ComposerProvider>
+              </OnboardingProvider>
             </ColorModeProvider>
           </MinimalModeProvider>
         </DrawerSwipableProvider>