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>zio/stable
parent
c687172de9
commit
0a26e78dcb
|
@ -82,12 +82,11 @@ interface PostOpts {
|
||||||
extLink?: ExternalEmbedDraft
|
extLink?: ExternalEmbedDraft
|
||||||
images?: ImageModel[]
|
images?: ImageModel[]
|
||||||
labels?: string[]
|
labels?: string[]
|
||||||
knownHandles?: Set<string>
|
|
||||||
onStateChange?: (state: string) => void
|
onStateChange?: (state: string) => void
|
||||||
langs?: string[]
|
langs?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function post(store: RootStoreModel, opts: PostOpts) {
|
export async function post(agent: BskyAgent, opts: PostOpts) {
|
||||||
let embed:
|
let embed:
|
||||||
| AppBskyEmbedImages.Main
|
| AppBskyEmbedImages.Main
|
||||||
| AppBskyEmbedExternal.Main
|
| AppBskyEmbedExternal.Main
|
||||||
|
@ -103,7 +102,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
||||||
)
|
)
|
||||||
|
|
||||||
opts.onStateChange?.('Processing...')
|
opts.onStateChange?.('Processing...')
|
||||||
await rt.detectFacets(store.agent)
|
await rt.detectFacets(agent)
|
||||||
rt = shortenLinks(rt)
|
rt = shortenLinks(rt)
|
||||||
|
|
||||||
// filter out any mention facets that didn't map to a user
|
// filter out any mention facets that didn't map to a user
|
||||||
|
@ -136,7 +135,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
||||||
await image.compress()
|
await image.compress()
|
||||||
const path = image.compressed?.path ?? image.path
|
const path = image.compressed?.path ?? image.path
|
||||||
const {width, height} = image.compressed || image
|
const {width, height} = image.compressed || image
|
||||||
const res = await uploadBlob(store.agent, path, 'image/jpeg')
|
const res = await uploadBlob(agent, path, 'image/jpeg')
|
||||||
images.push({
|
images.push({
|
||||||
image: res.data.blob,
|
image: res.data.blob,
|
||||||
alt: image.altText ?? '',
|
alt: image.altText ?? '',
|
||||||
|
@ -186,7 +185,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
||||||
}
|
}
|
||||||
if (encoding) {
|
if (encoding) {
|
||||||
const thumbUploadRes = await uploadBlob(
|
const thumbUploadRes = await uploadBlob(
|
||||||
store.agent,
|
agent,
|
||||||
opts.extLink.localThumb.path,
|
opts.extLink.localThumb.path,
|
||||||
encoding,
|
encoding,
|
||||||
)
|
)
|
||||||
|
@ -225,7 +224,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
||||||
// add replyTo if post is a reply to another post
|
// add replyTo if post is a reply to another post
|
||||||
if (opts.replyTo) {
|
if (opts.replyTo) {
|
||||||
const replyToUrip = new AtUri(opts.replyTo)
|
const replyToUrip = new AtUri(opts.replyTo)
|
||||||
const parentPost = await store.agent.getPost({
|
const parentPost = await agent.getPost({
|
||||||
repo: replyToUrip.host,
|
repo: replyToUrip.host,
|
||||||
rkey: replyToUrip.rkey,
|
rkey: replyToUrip.rkey,
|
||||||
})
|
})
|
||||||
|
@ -258,7 +257,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
opts.onStateChange?.('Posting...')
|
opts.onStateChange?.('Posting...')
|
||||||
return await store.agent.post({
|
return await agent.post({
|
||||||
text: rt.text,
|
text: rt.text,
|
||||||
facets: rt.facets,
|
facets: rt.facets,
|
||||||
reply,
|
reply,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {LikelyType, LinkMeta} from './link-meta'
|
||||||
import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers'
|
import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers'
|
||||||
import {RootStoreModel} from 'state/index'
|
import {RootStoreModel} from 'state/index'
|
||||||
import {PostThreadModel} from 'state/models/content/post-thread'
|
import {PostThreadModel} from 'state/models/content/post-thread'
|
||||||
import {ComposerOptsQuote} from 'state/models/ui/shell'
|
import {ComposerOptsQuote} from 'state/shell/composer'
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// import {Home} from 'view/screens/Home'
|
// import {Home} from 'view/screens/Home'
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {
|
||||||
openCropper as openCropperFn,
|
openCropper as openCropperFn,
|
||||||
Image as RNImage,
|
Image as RNImage,
|
||||||
} from 'react-native-image-crop-picker'
|
} from 'react-native-image-crop-picker'
|
||||||
import {RootStoreModel} from 'state/index'
|
|
||||||
import {CameraOpts, CropperOptions} from './types'
|
import {CameraOpts, CropperOptions} from './types'
|
||||||
export {openPicker} from './picker.shared'
|
export {openPicker} from './picker.shared'
|
||||||
|
|
||||||
|
@ -16,10 +15,7 @@ export {openPicker} from './picker.shared'
|
||||||
* -prf
|
* -prf
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function openCamera(
|
export async function openCamera(opts: CameraOpts): Promise<RNImage> {
|
||||||
_store: RootStoreModel,
|
|
||||||
opts: CameraOpts,
|
|
||||||
): Promise<RNImage> {
|
|
||||||
const item = await openCameraFn({
|
const item = await openCameraFn({
|
||||||
width: opts.width,
|
width: opts.width,
|
||||||
height: opts.height,
|
height: opts.height,
|
||||||
|
@ -39,10 +35,7 @@ export async function openCamera(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openCropper(
|
export async function openCropper(opts: CropperOptions) {
|
||||||
_store: RootStoreModel,
|
|
||||||
opts: CropperOptions,
|
|
||||||
) {
|
|
||||||
const item = await openCropperFn({
|
const item = await openCropperFn({
|
||||||
...opts,
|
...opts,
|
||||||
forceJpg: true, // ios only
|
forceJpg: true, // ios only
|
||||||
|
|
|
@ -1,23 +1,16 @@
|
||||||
/// <reference lib="dom" />
|
/// <reference lib="dom" />
|
||||||
|
|
||||||
import {CameraOpts, CropperOptions} from './types'
|
import {CameraOpts, CropperOptions} from './types'
|
||||||
import {RootStoreModel} from 'state/index'
|
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||||
export {openPicker} from './picker.shared'
|
export {openPicker} from './picker.shared'
|
||||||
import {unstable__openModal} from '#/state/modals'
|
import {unstable__openModal} from '#/state/modals'
|
||||||
|
|
||||||
export async function openCamera(
|
export async function openCamera(_opts: CameraOpts): Promise<RNImage> {
|
||||||
_store: RootStoreModel,
|
|
||||||
_opts: CameraOpts,
|
|
||||||
): Promise<RNImage> {
|
|
||||||
// const mediaType = opts.mediaType || 'photo' TODO
|
// const mediaType = opts.mediaType || 'photo' TODO
|
||||||
throw new Error('TODO')
|
throw new Error('TODO')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openCropper(
|
export async function openCropper(opts: CropperOptions): Promise<RNImage> {
|
||||||
_store: RootStoreModel,
|
|
||||||
opts: CropperOptions,
|
|
||||||
): Promise<RNImage> {
|
|
||||||
// TODO handle more opts
|
// TODO handle more opts
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
unstable__openModal({
|
unstable__openModal({
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {makeAutoObservable, runInAction} from 'mobx'
|
import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {RootStoreModel} from 'state/index'
|
|
||||||
import {ImageModel} from './image'
|
import {ImageModel} from './image'
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||||
import {openPicker} from 'lib/media/picker'
|
import {openPicker} from 'lib/media/picker'
|
||||||
|
@ -8,10 +7,8 @@ import {getImageDim} from 'lib/media/manip'
|
||||||
export class GalleryModel {
|
export class GalleryModel {
|
||||||
images: ImageModel[] = []
|
images: ImageModel[] = []
|
||||||
|
|
||||||
constructor(public rootStore: RootStoreModel) {
|
constructor() {
|
||||||
makeAutoObservable(this, {
|
makeAutoObservable(this)
|
||||||
rootStore: false,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEmpty() {
|
get isEmpty() {
|
||||||
|
@ -33,7 +30,7 @@ export class GalleryModel {
|
||||||
|
|
||||||
// Temporarily enforce uniqueness but can eventually also use index
|
// Temporarily enforce uniqueness but can eventually also use index
|
||||||
if (!this.images.some(i => i.path === image_.path)) {
|
if (!this.images.some(i => i.path === image_.path)) {
|
||||||
const image = new ImageModel(this.rootStore, image_)
|
const image = new ImageModel(image_)
|
||||||
|
|
||||||
// Initial resize
|
// Initial resize
|
||||||
image.manipulate({})
|
image.manipulate({})
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {Image as RNImage} from 'react-native-image-crop-picker'
|
import {Image as RNImage} from 'react-native-image-crop-picker'
|
||||||
import {RootStoreModel} from 'state/index'
|
|
||||||
import {makeAutoObservable, runInAction} from 'mobx'
|
import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {POST_IMG_MAX} from 'lib/constants'
|
import {POST_IMG_MAX} from 'lib/constants'
|
||||||
import * as ImageManipulator from 'expo-image-manipulator'
|
import * as ImageManipulator from 'expo-image-manipulator'
|
||||||
|
@ -42,10 +41,8 @@ export class ImageModel implements Omit<RNImage, 'size'> {
|
||||||
}
|
}
|
||||||
prevAttributes: ImageManipulationAttributes = {}
|
prevAttributes: ImageManipulationAttributes = {}
|
||||||
|
|
||||||
constructor(public rootStore: RootStoreModel, image: Omit<RNImage, 'size'>) {
|
constructor(image: Omit<RNImage, 'size'>) {
|
||||||
makeAutoObservable(this, {
|
makeAutoObservable(this)
|
||||||
rootStore: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.path = image.path
|
this.path = image.path
|
||||||
this.width = image.width
|
this.width = image.width
|
||||||
|
@ -178,7 +175,7 @@ export class ImageModel implements Omit<RNImage, 'size'> {
|
||||||
height: this.height,
|
height: this.height,
|
||||||
})
|
})
|
||||||
|
|
||||||
const cropped = await openCropper(this.rootStore, {
|
const cropped = await openCropper({
|
||||||
mediaType: 'photo',
|
mediaType: 'photo',
|
||||||
path: this.path,
|
path: this.path,
|
||||||
freeStyleCropEnabled: true,
|
freeStyleCropEnabled: true,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {AppBskyEmbedRecord, AppBskyActorDefs} from '@atproto/api'
|
import {AppBskyActorDefs} from '@atproto/api'
|
||||||
import {RootStoreModel} from '../root-store'
|
import {RootStoreModel} from '../root-store'
|
||||||
import {makeAutoObservable, runInAction} from 'mobx'
|
import {makeAutoObservable, runInAction} from 'mobx'
|
||||||
import {
|
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 {
|
export class ShellUiModel {
|
||||||
isLightboxActive = false
|
isLightboxActive = false
|
||||||
activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null
|
activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null
|
||||||
isComposerActive = false
|
|
||||||
composerOpts: ComposerOpts | undefined
|
|
||||||
tickEveryMinute = Date.now()
|
tickEveryMinute = Date.now()
|
||||||
|
|
||||||
constructor(public rootStore: RootStoreModel) {
|
constructor(public rootStore: RootStoreModel) {
|
||||||
|
@ -92,10 +60,6 @@ export class ShellUiModel {
|
||||||
this.closeLightbox()
|
this.closeLightbox()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (this.isComposerActive) {
|
|
||||||
this.closeComposer()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,9 +70,6 @@ export class ShellUiModel {
|
||||||
if (this.isLightboxActive) {
|
if (this.isLightboxActive) {
|
||||||
this.closeLightbox()
|
this.closeLightbox()
|
||||||
}
|
}
|
||||||
if (this.isComposerActive) {
|
|
||||||
this.closeComposer()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) {
|
openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) {
|
||||||
|
@ -122,17 +83,6 @@ export class ShellUiModel {
|
||||||
this.activeLightbox = null
|
this.activeLightbox = null
|
||||||
}
|
}
|
||||||
|
|
||||||
openComposer(opts: ComposerOpts) {
|
|
||||||
this.rootStore.emitNavigation()
|
|
||||||
this.isComposerActive = true
|
|
||||||
this.composerOpts = opts
|
|
||||||
}
|
|
||||||
|
|
||||||
closeComposer() {
|
|
||||||
this.isComposerActive = false
|
|
||||||
this.composerOpts = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
setupClock() {
|
setupClock() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import {AppBskyActorDefs} from '@atproto/api'
|
import {AppBskyActorDefs, BskyAgent} from '@atproto/api'
|
||||||
import {useQuery} from '@tanstack/react-query'
|
import {useQuery} from '@tanstack/react-query'
|
||||||
import {useSession} from '../session'
|
import {useSession} from '../session'
|
||||||
import {useMyFollowsQuery} from './my-follows'
|
import {useMyFollowsQuery} from './my-follows'
|
||||||
|
import AwaitLock from 'await-lock'
|
||||||
|
|
||||||
export const RQKEY = (prefix: string) => ['actor-autocomplete', prefix]
|
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(
|
function computeSuggestions(
|
||||||
prefix: string,
|
prefix: string,
|
||||||
follows: AppBskyActorDefs.ProfileViewBasic[] = [],
|
follows: AppBskyActorDefs.ProfileViewBasic[] = [],
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled'
|
||||||
import {Provider as MinimalModeProvider} from './minimal-mode'
|
import {Provider as MinimalModeProvider} from './minimal-mode'
|
||||||
import {Provider as ColorModeProvider} from './color-mode'
|
import {Provider as ColorModeProvider} from './color-mode'
|
||||||
import {Provider as OnboardingProvider} from './onboarding'
|
import {Provider as OnboardingProvider} from './onboarding'
|
||||||
|
import {Provider as ComposerProvider} from './composer'
|
||||||
|
|
||||||
export {useIsDrawerOpen, useSetDrawerOpen} from './drawer-open'
|
export {useIsDrawerOpen, useSetDrawerOpen} from './drawer-open'
|
||||||
export {
|
export {
|
||||||
|
@ -22,7 +23,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
<DrawerSwipableProvider>
|
<DrawerSwipableProvider>
|
||||||
<MinimalModeProvider>
|
<MinimalModeProvider>
|
||||||
<ColorModeProvider>
|
<ColorModeProvider>
|
||||||
<OnboardingProvider>{children}</OnboardingProvider>
|
<OnboardingProvider>
|
||||||
|
<ComposerProvider>{children}</ComposerProvider>
|
||||||
|
</OnboardingProvider>
|
||||||
</ColorModeProvider>
|
</ColorModeProvider>
|
||||||
</MinimalModeProvider>
|
</MinimalModeProvider>
|
||||||
</DrawerSwipableProvider>
|
</DrawerSwipableProvider>
|
||||||
|
|
|
@ -16,7 +16,6 @@ import LinearGradient from 'react-native-linear-gradient'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {RichText} from '@atproto/api'
|
import {RichText} from '@atproto/api'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
|
|
||||||
import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible'
|
import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible'
|
||||||
import {ExternalEmbed} from './ExternalEmbed'
|
import {ExternalEmbed} from './ExternalEmbed'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
|
@ -26,9 +25,8 @@ import * as Toast from '../util/Toast'
|
||||||
import {TextInput, TextInputRef} from './text-input/TextInput'
|
import {TextInput, TextInputRef} from './text-input/TextInput'
|
||||||
import {CharProgress} from './char-progress/CharProgress'
|
import {CharProgress} from './char-progress/CharProgress'
|
||||||
import {UserAvatar} from '../util/UserAvatar'
|
import {UserAvatar} from '../util/UserAvatar'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import * as apilib from 'lib/api/index'
|
import * as apilib from 'lib/api/index'
|
||||||
import {ComposerOpts} from 'state/models/ui/shell'
|
import {ComposerOpts} from 'state/shell/composer'
|
||||||
import {s, colors, gradients} from 'lib/styles'
|
import {s, colors, gradients} from 'lib/styles'
|
||||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
import {sanitizeHandle} from 'lib/strings/handles'
|
||||||
|
@ -58,6 +56,9 @@ import {
|
||||||
useLanguagePrefsApi,
|
useLanguagePrefsApi,
|
||||||
toPostLanguages,
|
toPostLanguages,
|
||||||
} from '#/state/preferences/languages'
|
} from '#/state/preferences/languages'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
type Props = ComposerOpts
|
type Props = ComposerOpts
|
||||||
export const ComposePost = observer(function ComposePost({
|
export const ComposePost = observer(function ComposePost({
|
||||||
|
@ -66,12 +67,14 @@ export const ComposePost = observer(function ComposePost({
|
||||||
quote: initQuote,
|
quote: initQuote,
|
||||||
mention: initMention,
|
mention: initMention,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const {agent, currentAccount} = useSession()
|
||||||
|
const {data: currentProfile} = useProfileQuery({did: currentAccount!.did})
|
||||||
const {activeModals} = useModals()
|
const {activeModals} = useModals()
|
||||||
const {openModal, closeModal} = useModalControls()
|
const {openModal, closeModal} = useModalControls()
|
||||||
|
const {closeComposer} = useComposerControls()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {isDesktop, isMobile} = useWebMediaQueries()
|
const {isDesktop, isMobile} = useWebMediaQueries()
|
||||||
const store = useStores()
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const requireAltTextEnabled = useRequireAltTextEnabled()
|
const requireAltTextEnabled = useRequireAltTextEnabled()
|
||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
|
@ -101,15 +104,10 @@ export const ComposePost = observer(function ComposePost({
|
||||||
const {extLink, setExtLink} = useExternalLinkFetch({setQuote})
|
const {extLink, setExtLink} = useExternalLinkFetch({setQuote})
|
||||||
const [labels, setLabels] = useState<string[]>([])
|
const [labels, setLabels] = useState<string[]>([])
|
||||||
const [suggestedLinks, setSuggestedLinks] = useState<Set<string>>(new Set())
|
const [suggestedLinks, setSuggestedLinks] = useState<Set<string>>(new Set())
|
||||||
const gallery = useMemo(() => new GalleryModel(store), [store])
|
const gallery = useMemo(() => new GalleryModel(), [])
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
store.shell.closeComposer()
|
closeComposer()
|
||||||
}, [store])
|
}, [closeComposer])
|
||||||
|
|
||||||
const autocompleteView = useMemo<UserAutocompleteModel>(
|
|
||||||
() => new UserAutocompleteModel(store),
|
|
||||||
[store],
|
|
||||||
)
|
|
||||||
|
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const viewStyles = useMemo(
|
const viewStyles = useMemo(
|
||||||
|
@ -162,11 +160,6 @@ export const ComposePost = observer(function ComposePost({
|
||||||
}
|
}
|
||||||
}, [onPressCancel])
|
}, [onPressCancel])
|
||||||
|
|
||||||
// initial setup
|
|
||||||
useEffect(() => {
|
|
||||||
autocompleteView.setup()
|
|
||||||
}, [autocompleteView])
|
|
||||||
|
|
||||||
// listen to escape key on desktop web
|
// listen to escape key on desktop web
|
||||||
const onEscape = useCallback(
|
const onEscape = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
|
@ -216,7 +209,7 @@ export const ComposePost = observer(function ComposePost({
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await apilib.post(store, {
|
await apilib.post(agent, {
|
||||||
rawText: richtext.text,
|
rawText: richtext.text,
|
||||||
replyTo: replyTo?.uri,
|
replyTo: replyTo?.uri,
|
||||||
images: gallery.images,
|
images: gallery.images,
|
||||||
|
@ -224,7 +217,6 @@ export const ComposePost = observer(function ComposePost({
|
||||||
extLink,
|
extLink,
|
||||||
labels,
|
labels,
|
||||||
onStateChange: setProcessingState,
|
onStateChange: setProcessingState,
|
||||||
knownHandles: autocompleteView.knownHandles,
|
|
||||||
langs: toPostLanguages(langPrefs.postLanguage),
|
langs: toPostLanguages(langPrefs.postLanguage),
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
@ -381,13 +373,12 @@ export const ComposePost = observer(function ComposePost({
|
||||||
styles.textInputLayout,
|
styles.textInputLayout,
|
||||||
isNative && styles.textInputLayoutMobile,
|
isNative && styles.textInputLayoutMobile,
|
||||||
]}>
|
]}>
|
||||||
<UserAvatar avatar={store.me.avatar} size={50} />
|
<UserAvatar avatar={currentProfile?.avatar} size={50} />
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={textInput}
|
ref={textInput}
|
||||||
richtext={richtext}
|
richtext={richtext}
|
||||||
placeholder={selectTextInputPlaceholder}
|
placeholder={selectTextInputPlaceholder}
|
||||||
suggestedLinks={suggestedLinks}
|
suggestedLinks={suggestedLinks}
|
||||||
autocompleteView={autocompleteView}
|
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
setRichText={setRichText}
|
setRichText={setRichText}
|
||||||
onPhotoPasted={onPhotoPasted}
|
onPhotoPasted={onPhotoPasted}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useRef,
|
useRef,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useState,
|
||||||
ComponentProps,
|
ComponentProps,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
|
@ -18,7 +19,6 @@ import PasteInput, {
|
||||||
} from '@mattermost/react-native-paste-input'
|
} from '@mattermost/react-native-paste-input'
|
||||||
import {AppBskyRichtextFacet, RichText} from '@atproto/api'
|
import {AppBskyRichtextFacet, RichText} from '@atproto/api'
|
||||||
import isEqual from 'lodash.isequal'
|
import isEqual from 'lodash.isequal'
|
||||||
import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
|
|
||||||
import {Autocomplete} from './mobile/Autocomplete'
|
import {Autocomplete} from './mobile/Autocomplete'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {cleanError} from 'lib/strings/errors'
|
import {cleanError} from 'lib/strings/errors'
|
||||||
|
@ -38,7 +38,6 @@ interface TextInputProps extends ComponentProps<typeof RNTextInput> {
|
||||||
richtext: RichText
|
richtext: RichText
|
||||||
placeholder: string
|
placeholder: string
|
||||||
suggestedLinks: Set<string>
|
suggestedLinks: Set<string>
|
||||||
autocompleteView: UserAutocompleteModel
|
|
||||||
setRichText: (v: RichText | ((v: RichText) => RichText)) => void
|
setRichText: (v: RichText | ((v: RichText) => RichText)) => void
|
||||||
onPhotoPasted: (uri: string) => void
|
onPhotoPasted: (uri: string) => void
|
||||||
onPressPublish: (richtext: RichText) => Promise<void>
|
onPressPublish: (richtext: RichText) => Promise<void>
|
||||||
|
@ -56,7 +55,6 @@ export const TextInput = forwardRef(function TextInputImpl(
|
||||||
richtext,
|
richtext,
|
||||||
placeholder,
|
placeholder,
|
||||||
suggestedLinks,
|
suggestedLinks,
|
||||||
autocompleteView,
|
|
||||||
setRichText,
|
setRichText,
|
||||||
onPhotoPasted,
|
onPhotoPasted,
|
||||||
onSuggestedLinksChanged,
|
onSuggestedLinksChanged,
|
||||||
|
@ -69,6 +67,7 @@ export const TextInput = forwardRef(function TextInputImpl(
|
||||||
const textInput = useRef<PasteInputRef>(null)
|
const textInput = useRef<PasteInputRef>(null)
|
||||||
const textInputSelection = useRef<Selection>({start: 0, end: 0})
|
const textInputSelection = useRef<Selection>({start: 0, end: 0})
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
const [autocompletePrefix, setAutocompletePrefix] = useState('')
|
||||||
|
|
||||||
React.useImperativeHandle(ref, () => ({
|
React.useImperativeHandle(ref, () => ({
|
||||||
focus: () => textInput.current?.focus(),
|
focus: () => textInput.current?.focus(),
|
||||||
|
@ -99,10 +98,9 @@ export const TextInput = forwardRef(function TextInputImpl(
|
||||||
textInputSelection.current?.start || 0,
|
textInputSelection.current?.start || 0,
|
||||||
)
|
)
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
autocompleteView.setActive(true)
|
setAutocompletePrefix(prefix.value)
|
||||||
autocompleteView.setPrefix(prefix.value)
|
} else if (autocompletePrefix) {
|
||||||
} else {
|
setAutocompletePrefix('')
|
||||||
autocompleteView.setActive(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const set: Set<string> = new Set()
|
const set: Set<string> = new Set()
|
||||||
|
@ -139,7 +137,8 @@ export const TextInput = forwardRef(function TextInputImpl(
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
setRichText,
|
setRichText,
|
||||||
autocompleteView,
|
autocompletePrefix,
|
||||||
|
setAutocompletePrefix,
|
||||||
suggestedLinks,
|
suggestedLinks,
|
||||||
onSuggestedLinksChanged,
|
onSuggestedLinksChanged,
|
||||||
onPhotoPasted,
|
onPhotoPasted,
|
||||||
|
@ -179,9 +178,9 @@ export const TextInput = forwardRef(function TextInputImpl(
|
||||||
item,
|
item,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
autocompleteView.setActive(false)
|
setAutocompletePrefix('')
|
||||||
},
|
},
|
||||||
[onChangeText, richtext, autocompleteView],
|
[onChangeText, richtext, setAutocompletePrefix],
|
||||||
)
|
)
|
||||||
|
|
||||||
const textDecorated = useMemo(() => {
|
const textDecorated = useMemo(() => {
|
||||||
|
@ -221,7 +220,7 @@ export const TextInput = forwardRef(function TextInputImpl(
|
||||||
{textDecorated}
|
{textDecorated}
|
||||||
</PasteInput>
|
</PasteInput>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
view={autocompleteView}
|
prefix={autocompletePrefix}
|
||||||
onSelect={onSelectAutocompleteItem}
|
onSelect={onSelectAutocompleteItem}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -11,13 +11,15 @@ import {Paragraph} from '@tiptap/extension-paragraph'
|
||||||
import {Placeholder} from '@tiptap/extension-placeholder'
|
import {Placeholder} from '@tiptap/extension-placeholder'
|
||||||
import {Text} from '@tiptap/extension-text'
|
import {Text} from '@tiptap/extension-text'
|
||||||
import isEqual from 'lodash.isequal'
|
import isEqual from 'lodash.isequal'
|
||||||
import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
|
|
||||||
import {createSuggestion} from './web/Autocomplete'
|
import {createSuggestion} from './web/Autocomplete'
|
||||||
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
|
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
|
||||||
import {isUriImage, blobToDataUri} from 'lib/media/util'
|
import {isUriImage, blobToDataUri} from 'lib/media/util'
|
||||||
import {Emoji} from './web/EmojiPicker.web'
|
import {Emoji} from './web/EmojiPicker.web'
|
||||||
import {LinkDecorator} from './web/LinkDecorator'
|
import {LinkDecorator} from './web/LinkDecorator'
|
||||||
import {generateJSON} from '@tiptap/html'
|
import {generateJSON} from '@tiptap/html'
|
||||||
|
import {ActorAutocomplete} from '#/state/queries/actor-autocomplete'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {useMyFollowsQuery} from '#/state/queries/my-follows'
|
||||||
|
|
||||||
export interface TextInputRef {
|
export interface TextInputRef {
|
||||||
focus: () => void
|
focus: () => void
|
||||||
|
@ -28,7 +30,6 @@ interface TextInputProps {
|
||||||
richtext: RichText
|
richtext: RichText
|
||||||
placeholder: string
|
placeholder: string
|
||||||
suggestedLinks: Set<string>
|
suggestedLinks: Set<string>
|
||||||
autocompleteView: UserAutocompleteModel
|
|
||||||
setRichText: (v: RichText | ((v: RichText) => RichText)) => void
|
setRichText: (v: RichText | ((v: RichText) => RichText)) => void
|
||||||
onPhotoPasted: (uri: string) => void
|
onPhotoPasted: (uri: string) => void
|
||||||
onPressPublish: (richtext: RichText) => Promise<void>
|
onPressPublish: (richtext: RichText) => Promise<void>
|
||||||
|
@ -43,7 +44,6 @@ export const TextInput = React.forwardRef(function TextInputImpl(
|
||||||
richtext,
|
richtext,
|
||||||
placeholder,
|
placeholder,
|
||||||
suggestedLinks,
|
suggestedLinks,
|
||||||
autocompleteView,
|
|
||||||
setRichText,
|
setRichText,
|
||||||
onPhotoPasted,
|
onPhotoPasted,
|
||||||
onPressPublish,
|
onPressPublish,
|
||||||
|
@ -52,6 +52,16 @@ export const TextInput = React.forwardRef(function TextInputImpl(
|
||||||
TextInputProps,
|
TextInputProps,
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
|
const {agent} = useSession()
|
||||||
|
const autocomplete = React.useMemo(
|
||||||
|
() => new ActorAutocomplete(agent),
|
||||||
|
[agent],
|
||||||
|
)
|
||||||
|
const {data: follows} = useMyFollowsQuery()
|
||||||
|
if (follows) {
|
||||||
|
autocomplete.setFollows(follows)
|
||||||
|
}
|
||||||
|
|
||||||
const modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark')
|
const modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark')
|
||||||
const extensions = React.useMemo(
|
const extensions = React.useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
@ -61,7 +71,7 @@ export const TextInput = React.forwardRef(function TextInputImpl(
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: 'mention',
|
class: 'mention',
|
||||||
},
|
},
|
||||||
suggestion: createSuggestion({autocompleteView}),
|
suggestion: createSuggestion({autocomplete}),
|
||||||
}),
|
}),
|
||||||
Paragraph,
|
Paragraph,
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
|
@ -71,7 +81,7 @@ export const TextInput = React.forwardRef(function TextInputImpl(
|
||||||
History,
|
History,
|
||||||
Hardbreak,
|
Hardbreak,
|
||||||
],
|
],
|
||||||
[autocompleteView, placeholder],
|
[autocomplete, placeholder],
|
||||||
)
|
)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
|
@ -1,31 +1,33 @@
|
||||||
import React, {useEffect} from 'react'
|
import React, {useEffect} from 'react'
|
||||||
import {Animated, TouchableOpacity, StyleSheet, View} from 'react-native'
|
import {Animated, TouchableOpacity, StyleSheet, View} from 'react-native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
|
|
||||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {UserAvatar} from 'view/com/util/UserAvatar'
|
import {UserAvatar} from 'view/com/util/UserAvatar'
|
||||||
import {useGrapheme} from '../hooks/useGrapheme'
|
import {useGrapheme} from '../hooks/useGrapheme'
|
||||||
|
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
||||||
|
|
||||||
export const Autocomplete = observer(function AutocompleteImpl({
|
export const Autocomplete = observer(function AutocompleteImpl({
|
||||||
view,
|
prefix,
|
||||||
onSelect,
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
view: UserAutocompleteModel
|
prefix: string
|
||||||
onSelect: (item: string) => void
|
onSelect: (item: string) => void
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const positionInterp = useAnimatedValue(0)
|
const positionInterp = useAnimatedValue(0)
|
||||||
const {getGraphemeString} = useGrapheme()
|
const {getGraphemeString} = useGrapheme()
|
||||||
|
const isActive = !!prefix
|
||||||
|
const {data: suggestions} = useActorAutocompleteQuery(prefix)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Animated.timing(positionInterp, {
|
Animated.timing(positionInterp, {
|
||||||
toValue: view.isActive ? 1 : 0,
|
toValue: isActive ? 1 : 0,
|
||||||
duration: 200,
|
duration: 200,
|
||||||
useNativeDriver: true,
|
useNativeDriver: true,
|
||||||
}).start()
|
}).start()
|
||||||
}, [positionInterp, view.isActive])
|
}, [positionInterp, isActive])
|
||||||
|
|
||||||
const topAnimStyle = {
|
const topAnimStyle = {
|
||||||
transform: [
|
transform: [
|
||||||
|
@ -40,10 +42,10 @@ export const Autocomplete = observer(function AutocompleteImpl({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={topAnimStyle}>
|
<Animated.View style={topAnimStyle}>
|
||||||
{view.isActive ? (
|
{isActive ? (
|
||||||
<View style={[pal.view, styles.container, pal.border]}>
|
<View style={[pal.view, styles.container, pal.border]}>
|
||||||
{view.suggestions.length > 0 ? (
|
{suggestions?.length ? (
|
||||||
view.suggestions.slice(0, 5).map(item => {
|
suggestions.slice(0, 5).map(item => {
|
||||||
// Eventually use an average length
|
// Eventually use an average length
|
||||||
const MAX_CHARS = 40
|
const MAX_CHARS = 40
|
||||||
const MAX_HANDLE_CHARS = 20
|
const MAX_HANDLE_CHARS = 20
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
SuggestionProps,
|
SuggestionProps,
|
||||||
SuggestionKeyDownProps,
|
SuggestionKeyDownProps,
|
||||||
} from '@tiptap/suggestion'
|
} from '@tiptap/suggestion'
|
||||||
import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
|
import {ActorAutocomplete} from '#/state/queries/actor-autocomplete'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {UserAvatar} from 'view/com/util/UserAvatar'
|
import {UserAvatar} from 'view/com/util/UserAvatar'
|
||||||
|
@ -23,15 +23,14 @@ interface MentionListRef {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSuggestion({
|
export function createSuggestion({
|
||||||
autocompleteView,
|
autocomplete,
|
||||||
}: {
|
}: {
|
||||||
autocompleteView: UserAutocompleteModel
|
autocomplete: ActorAutocomplete
|
||||||
}): Omit<SuggestionOptions, 'editor'> {
|
}): Omit<SuggestionOptions, 'editor'> {
|
||||||
return {
|
return {
|
||||||
async items({query}) {
|
async items({query}) {
|
||||||
autocompleteView.setActive(true)
|
await autocomplete.query(query)
|
||||||
await autocompleteView.setPrefix(query)
|
return autocomplete.suggestions.slice(0, 8)
|
||||||
return autocompleteView.suggestions.slice(0, 8)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: () => {
|
render: () => {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
isBskyCustomFeedUrl,
|
isBskyCustomFeedUrl,
|
||||||
isBskyListUrl,
|
isBskyListUrl,
|
||||||
} from 'lib/strings/url-helpers'
|
} from 'lib/strings/url-helpers'
|
||||||
import {ComposerOpts} from 'state/models/ui/shell'
|
import {ComposerOpts} from 'state/shell/composer'
|
||||||
import {POST_IMG_MAX} from 'lib/constants'
|
import {POST_IMG_MAX} from 'lib/constants'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
const POLL_FREQ = 30e3 // 30sec
|
const POLL_FREQ = 30e3 // 30sec
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ export function FeedPage({
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {isDesktop} = useWebMediaQueries()
|
const {isDesktop} = useWebMediaQueries()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
const {openComposer} = useComposerControls()
|
||||||
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll()
|
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll()
|
||||||
const {screen, track} = useAnalytics()
|
const {screen, track} = useAnalytics()
|
||||||
const headerOffset = useHeaderOffset()
|
const headerOffset = useHeaderOffset()
|
||||||
|
@ -80,8 +82,8 @@ export function FeedPage({
|
||||||
|
|
||||||
const onPressCompose = React.useCallback(() => {
|
const onPressCompose = React.useCallback(() => {
|
||||||
track('HomeScreen:PressCompose')
|
track('HomeScreen:PressCompose')
|
||||||
store.shell.openComposer({})
|
openComposer({})
|
||||||
}, [store, track])
|
}, [openComposer, track])
|
||||||
|
|
||||||
const onPressLoadLatest = React.useCallback(() => {
|
const onPressLoadLatest = React.useCallback(() => {
|
||||||
scrollToTop()
|
scrollToTop()
|
||||||
|
|
|
@ -20,7 +20,6 @@ import {sanitizeHandle} from 'lib/strings/handles'
|
||||||
import {countLines, pluralize} from 'lib/strings/helpers'
|
import {countLines, pluralize} from 'lib/strings/helpers'
|
||||||
import {isEmbedByEmbedder} from 'lib/embeds'
|
import {isEmbedByEmbedder} from 'lib/embeds'
|
||||||
import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers'
|
import {getTranslatorLink, isPostInLanguage} from '../../../locale/helpers'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {PostMeta} from '../util/PostMeta'
|
import {PostMeta} from '../util/PostMeta'
|
||||||
import {PostEmbeds} from '../util/post-embeds'
|
import {PostEmbeds} from '../util/post-embeds'
|
||||||
import {PostCtrls} from '../util/post-ctrls/PostCtrls'
|
import {PostCtrls} from '../util/post-ctrls/PostCtrls'
|
||||||
|
@ -39,6 +38,8 @@ import {MAX_POST_LINES} from 'lib/constants'
|
||||||
import {Trans} from '@lingui/macro'
|
import {Trans} from '@lingui/macro'
|
||||||
import {useLanguagePrefs} from '#/state/preferences'
|
import {useLanguagePrefs} from '#/state/preferences'
|
||||||
import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
|
import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
import {useModerationOpts} from '#/state/queries/preferences'
|
||||||
|
|
||||||
export function PostThreadItem({
|
export function PostThreadItem({
|
||||||
post,
|
post,
|
||||||
|
@ -65,7 +66,7 @@ export function PostThreadItem({
|
||||||
hasPrecedingItem: boolean
|
hasPrecedingItem: boolean
|
||||||
onPostReply: () => void
|
onPostReply: () => void
|
||||||
}) {
|
}) {
|
||||||
const store = useStores()
|
const moderationOpts = useModerationOpts()
|
||||||
const postShadowed = usePostShadow(post, dataUpdatedAt)
|
const postShadowed = usePostShadow(post, dataUpdatedAt)
|
||||||
const richText = useMemo(
|
const richText = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -77,8 +78,8 @@ export function PostThreadItem({
|
||||||
)
|
)
|
||||||
const moderation = useMemo(
|
const moderation = useMemo(
|
||||||
() =>
|
() =>
|
||||||
post ? moderatePost(post, store.preferences.moderationOpts) : undefined,
|
post && moderationOpts ? moderatePost(post, moderationOpts) : undefined,
|
||||||
[post, store],
|
[post, moderationOpts],
|
||||||
)
|
)
|
||||||
if (postShadowed === POST_TOMBSTONE) {
|
if (postShadowed === POST_TOMBSTONE) {
|
||||||
return <PostThreadItemDeleted />
|
return <PostThreadItemDeleted />
|
||||||
|
@ -145,8 +146,8 @@ function PostThreadItemLoaded({
|
||||||
onPostReply: () => void
|
onPostReply: () => void
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
|
||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
|
const {openComposer} = useComposerControls()
|
||||||
const [limitLines, setLimitLines] = React.useState(
|
const [limitLines, setLimitLines] = React.useState(
|
||||||
countLines(richText?.text) >= MAX_POST_LINES,
|
countLines(richText?.text) >= MAX_POST_LINES,
|
||||||
)
|
)
|
||||||
|
@ -187,7 +188,7 @@ function PostThreadItemLoaded({
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPressReply = React.useCallback(() => {
|
const onPressReply = React.useCallback(() => {
|
||||||
store.shell.openComposer({
|
openComposer({
|
||||||
replyTo: {
|
replyTo: {
|
||||||
uri: post.uri,
|
uri: post.uri,
|
||||||
cid: post.cid,
|
cid: post.cid,
|
||||||
|
@ -200,7 +201,7 @@ function PostThreadItemLoaded({
|
||||||
},
|
},
|
||||||
onPost: onPostReply,
|
onPost: onPostReply,
|
||||||
})
|
})
|
||||||
}, [store, post, record, onPostReply])
|
}, [openComposer, post, record, onPostReply])
|
||||||
|
|
||||||
const onPressShowMore = React.useCallback(() => {
|
const onPressShowMore = React.useCallback(() => {
|
||||||
setLimitLines(false)
|
setLimitLines(false)
|
||||||
|
|
|
@ -19,7 +19,6 @@ import {PostAlerts} from '../util/moderation/PostAlerts'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {RichText} from '../util/text/RichText'
|
import {RichText} from '../util/text/RichText'
|
||||||
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {s, colors} from 'lib/styles'
|
import {s, colors} from 'lib/styles'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {makeProfileLink} from 'lib/routes/links'
|
import {makeProfileLink} from 'lib/routes/links'
|
||||||
|
@ -27,6 +26,7 @@ import {MAX_POST_LINES} from 'lib/constants'
|
||||||
import {countLines} from 'lib/strings/helpers'
|
import {countLines} from 'lib/strings/helpers'
|
||||||
import {useModerationOpts} from '#/state/queries/preferences'
|
import {useModerationOpts} from '#/state/queries/preferences'
|
||||||
import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
|
import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
export function Post({
|
export function Post({
|
||||||
post,
|
post,
|
||||||
|
@ -97,7 +97,7 @@ function PostInner({
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
const {openComposer} = useComposerControls()
|
||||||
const [limitLines, setLimitLines] = useState(
|
const [limitLines, setLimitLines] = useState(
|
||||||
countLines(richText?.text) >= MAX_POST_LINES,
|
countLines(richText?.text) >= MAX_POST_LINES,
|
||||||
)
|
)
|
||||||
|
@ -110,7 +110,7 @@ function PostInner({
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPressReply = React.useCallback(() => {
|
const onPressReply = React.useCallback(() => {
|
||||||
store.shell.openComposer({
|
openComposer({
|
||||||
replyTo: {
|
replyTo: {
|
||||||
uri: post.uri,
|
uri: post.uri,
|
||||||
cid: post.cid,
|
cid: post.cid,
|
||||||
|
@ -122,7 +122,7 @@ function PostInner({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [store, post, record])
|
}, [openComposer, post, record])
|
||||||
|
|
||||||
const onPressShowMore = React.useCallback(() => {
|
const onPressShowMore = React.useCallback(() => {
|
||||||
setLimitLines(false)
|
setLimitLines(false)
|
||||||
|
|
|
@ -24,7 +24,6 @@ import {RichText} from '../util/text/RichText'
|
||||||
import {PostSandboxWarning} from '../util/PostSandboxWarning'
|
import {PostSandboxWarning} from '../util/PostSandboxWarning'
|
||||||
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||||
|
@ -34,6 +33,7 @@ import {isEmbedByEmbedder} from 'lib/embeds'
|
||||||
import {MAX_POST_LINES} from 'lib/constants'
|
import {MAX_POST_LINES} from 'lib/constants'
|
||||||
import {countLines} from 'lib/strings/helpers'
|
import {countLines} from 'lib/strings/helpers'
|
||||||
import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
|
import {usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
export function FeedItem({
|
export function FeedItem({
|
||||||
post,
|
post,
|
||||||
|
@ -102,7 +102,7 @@ function FeedItemInner({
|
||||||
isThreadLastChild?: boolean
|
isThreadLastChild?: boolean
|
||||||
isThreadParent?: boolean
|
isThreadParent?: boolean
|
||||||
}) {
|
}) {
|
||||||
const store = useStores()
|
const {openComposer} = useComposerControls()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const [limitLines, setLimitLines] = useState(
|
const [limitLines, setLimitLines] = useState(
|
||||||
|
@ -124,7 +124,7 @@ function FeedItemInner({
|
||||||
|
|
||||||
const onPressReply = React.useCallback(() => {
|
const onPressReply = React.useCallback(() => {
|
||||||
track('FeedItem:PostReply')
|
track('FeedItem:PostReply')
|
||||||
store.shell.openComposer({
|
openComposer({
|
||||||
replyTo: {
|
replyTo: {
|
||||||
uri: post.uri,
|
uri: post.uri,
|
||||||
cid: post.cid,
|
cid: post.cid,
|
||||||
|
@ -136,7 +136,7 @@ function FeedItemInner({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}, [post, record, track, store])
|
}, [post, record, track, openComposer])
|
||||||
|
|
||||||
const onPressShowMore = React.useCallback(() => {
|
const onPressShowMore = React.useCallback(() => {
|
||||||
setLimitLines(false)
|
setLimitLines(false)
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {HeartIcon, HeartIconSolid, CommentBottomArrow} from 'lib/icons'
|
||||||
import {s, colors} from 'lib/styles'
|
import {s, colors} from 'lib/styles'
|
||||||
import {pluralize} from 'lib/strings/helpers'
|
import {pluralize} from 'lib/strings/helpers'
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {RepostButton} from './RepostButton'
|
import {RepostButton} from './RepostButton'
|
||||||
import {Haptics} from 'lib/haptics'
|
import {Haptics} from 'lib/haptics'
|
||||||
import {HITSLOP_10, HITSLOP_20} from 'lib/constants'
|
import {HITSLOP_10, HITSLOP_20} from 'lib/constants'
|
||||||
|
@ -24,6 +23,7 @@ import {
|
||||||
usePostRepostMutation,
|
usePostRepostMutation,
|
||||||
usePostUnrepostMutation,
|
usePostUnrepostMutation,
|
||||||
} from '#/state/queries/post'
|
} from '#/state/queries/post'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
export function PostCtrls({
|
export function PostCtrls({
|
||||||
big,
|
big,
|
||||||
|
@ -38,8 +38,8 @@ export function PostCtrls({
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
onPressReply: () => void
|
onPressReply: () => void
|
||||||
}) {
|
}) {
|
||||||
const store = useStores()
|
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
const {openComposer} = useComposerControls()
|
||||||
const {closeModal} = useModalControls()
|
const {closeModal} = useModalControls()
|
||||||
const postLikeMutation = usePostLikeMutation()
|
const postLikeMutation = usePostLikeMutation()
|
||||||
const postUnlikeMutation = usePostUnlikeMutation()
|
const postUnlikeMutation = usePostUnlikeMutation()
|
||||||
|
@ -90,7 +90,7 @@ export function PostCtrls({
|
||||||
|
|
||||||
const onQuote = useCallback(() => {
|
const onQuote = useCallback(() => {
|
||||||
closeModal()
|
closeModal()
|
||||||
store.shell.openComposer({
|
openComposer({
|
||||||
quote: {
|
quote: {
|
||||||
uri: post.uri,
|
uri: post.uri,
|
||||||
cid: post.cid,
|
cid: post.cid,
|
||||||
|
@ -100,7 +100,7 @@ export function PostCtrls({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
Haptics.default()
|
Haptics.default()
|
||||||
}, [post, record, store.shell, closeModal])
|
}, [post, record, openComposer, closeModal])
|
||||||
return (
|
return (
|
||||||
<View style={[styles.ctrls, style]}>
|
<View style={[styles.ctrls, style]}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {PostMeta} from '../PostMeta'
|
||||||
import {Link} from '../Link'
|
import {Link} from '../Link'
|
||||||
import {Text} from '../text/Text'
|
import {Text} from '../text/Text'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {ComposerOptsQuote} from 'state/models/ui/shell'
|
import {ComposerOptsQuote} from 'state/shell/composer'
|
||||||
import {PostEmbeds} from '.'
|
import {PostEmbeds} from '.'
|
||||||
import {PostAlerts} from '../moderation/PostAlerts'
|
import {PostAlerts} from '../moderation/PostAlerts'
|
||||||
import {makeProfileLink} from 'lib/routes/links'
|
import {makeProfileLink} from 'lib/routes/links'
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {FAB} from 'view/com/util/fab/FAB'
|
||||||
import {Link} from 'view/com/util/Link'
|
import {Link} from 'view/com/util/Link'
|
||||||
import {NativeStackScreenProps, FeedsTabNavigatorParams} from 'lib/routes/types'
|
import {NativeStackScreenProps, FeedsTabNavigatorParams} from 'lib/routes/types'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {ComposeIcon2, CogIcon} from 'lib/icons'
|
import {ComposeIcon2, CogIcon} from 'lib/icons'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
|
@ -34,6 +33,7 @@ import {
|
||||||
useSearchPopularFeedsMutation,
|
useSearchPopularFeedsMutation,
|
||||||
} from '#/state/queries/feed'
|
} from '#/state/queries/feed'
|
||||||
import {cleanError} from 'lib/strings/errors'
|
import {cleanError} from 'lib/strings/errors'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'>
|
type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'>
|
||||||
|
|
||||||
|
@ -90,8 +90,8 @@ type FlatlistSlice =
|
||||||
export const FeedsScreen = withAuthRequired(function FeedsScreenImpl(
|
export const FeedsScreen = withAuthRequired(function FeedsScreenImpl(
|
||||||
_props: Props,
|
_props: Props,
|
||||||
) {
|
) {
|
||||||
const store = useStores()
|
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
|
const {openComposer} = useComposerControls()
|
||||||
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
|
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
|
||||||
const [query, setQuery] = React.useState('')
|
const [query, setQuery] = React.useState('')
|
||||||
const [isPTR, setIsPTR] = React.useState(false)
|
const [isPTR, setIsPTR] = React.useState(false)
|
||||||
|
@ -128,8 +128,8 @@ export const FeedsScreen = withAuthRequired(function FeedsScreenImpl(
|
||||||
[search],
|
[search],
|
||||||
)
|
)
|
||||||
const onPressCompose = React.useCallback(() => {
|
const onPressCompose = React.useCallback(() => {
|
||||||
store.shell.openComposer({})
|
openComposer({})
|
||||||
}, [store])
|
}, [openComposer])
|
||||||
const onChangeQuery = React.useCallback(
|
const onChangeQuery = React.useCallback(
|
||||||
(text: string) => {
|
(text: string) => {
|
||||||
setQuery(text)
|
setQuery(text)
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
||||||
import {ViewHeader} from '../com/util/ViewHeader'
|
import {ViewHeader} from '../com/util/ViewHeader'
|
||||||
import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
|
import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
|
||||||
import {ComposePrompt} from 'view/com/composer/Prompt'
|
import {ComposePrompt} from 'view/com/composer/Prompt'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {
|
import {
|
||||||
|
@ -24,14 +23,15 @@ import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||||
import {ErrorMessage} from '../com/util/error/ErrorMessage'
|
import {ErrorMessage} from '../com/util/error/ErrorMessage'
|
||||||
import {CenteredView} from '../com/util/Views'
|
import {CenteredView} from '../com/util/Views'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
||||||
export const PostThreadScreen = withAuthRequired(
|
export const PostThreadScreen = withAuthRequired(
|
||||||
observer(function PostThreadScreenImpl({route}: Props) {
|
observer(function PostThreadScreenImpl({route}: Props) {
|
||||||
const store = useStores()
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const {fabMinimalShellTransform} = useMinimalShellMode()
|
const {fabMinimalShellTransform} = useMinimalShellMode()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
|
const {openComposer} = useComposerControls()
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
const {name, rkey} = route.params
|
const {name, rkey} = route.params
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
|
@ -54,7 +54,7 @@ export const PostThreadScreen = withAuthRequired(
|
||||||
if (thread?.type !== 'post') {
|
if (thread?.type !== 'post') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.shell.openComposer({
|
openComposer({
|
||||||
replyTo: {
|
replyTo: {
|
||||||
uri: thread.post.uri,
|
uri: thread.post.uri,
|
||||||
cid: thread.post.cid,
|
cid: thread.post.cid,
|
||||||
|
@ -70,7 +70,7 @@ export const PostThreadScreen = withAuthRequired(
|
||||||
queryKey: POST_THREAD_RQKEY(resolvedUri.uri || ''),
|
queryKey: POST_THREAD_RQKEY(resolvedUri.uri || ''),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}, [store, queryClient, resolvedUri])
|
}, [openComposer, queryClient, resolvedUri])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
|
|
|
@ -36,6 +36,7 @@ import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn'
|
import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
|
||||||
export const ProfileScreen = withAuthRequired(function ProfileScreenImpl({
|
export const ProfileScreen = withAuthRequired(function ProfileScreenImpl({
|
||||||
|
@ -128,6 +129,7 @@ function ProfileScreenLoaded({
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
|
const {openComposer} = useComposerControls()
|
||||||
const {screen, track} = useAnalytics()
|
const {screen, track} = useAnalytics()
|
||||||
const [currentPage, setCurrentPage] = React.useState(0)
|
const [currentPage, setCurrentPage] = React.useState(0)
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
@ -193,8 +195,8 @@ function ProfileScreenLoaded({
|
||||||
profile.handle === 'handle.invalid'
|
profile.handle === 'handle.invalid'
|
||||||
? undefined
|
? undefined
|
||||||
: profile.handle
|
: profile.handle
|
||||||
store.shell.openComposer({mention})
|
openComposer({mention})
|
||||||
}, [store, currentAccount, track, profile])
|
}, [openComposer, currentAccount, track, profile])
|
||||||
|
|
||||||
const onPageSelected = React.useCallback(
|
const onPageSelected = React.useCallback(
|
||||||
i => {
|
i => {
|
||||||
|
|
|
@ -16,7 +16,6 @@ import {CommonNavigatorParams} from 'lib/routes/types'
|
||||||
import {makeRecordUri} from 'lib/strings/url-helpers'
|
import {makeRecordUri} from 'lib/strings/url-helpers'
|
||||||
import {colors, s} from 'lib/styles'
|
import {colors, s} from 'lib/styles'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {FeedDescriptor} from '#/state/queries/post-feed'
|
import {FeedDescriptor} from '#/state/queries/post-feed'
|
||||||
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
||||||
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
|
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
|
||||||
|
@ -62,6 +61,7 @@ import {
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
|
import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
const SECTION_TITLES = ['Posts', 'About']
|
const SECTION_TITLES = ['Posts', 'About']
|
||||||
|
|
||||||
|
@ -163,9 +163,9 @@ export const ProfileFeedScreenInner = function ProfileFeedScreenInnerImpl({
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
|
const {openComposer} = useComposerControls()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
const feedSectionRef = React.useRef<SectionRef>(null)
|
const feedSectionRef = React.useRef<SectionRef>(null)
|
||||||
|
|
||||||
|
@ -420,7 +420,7 @@ export const ProfileFeedScreenInner = function ProfileFeedScreenInnerImpl({
|
||||||
</PagerWithHeader>
|
</PagerWithHeader>
|
||||||
<FAB
|
<FAB
|
||||||
testID="composeFAB"
|
testID="composeFAB"
|
||||||
onPress={() => store.shell.openComposer({})}
|
onPress={() => openComposer({})}
|
||||||
icon={
|
icon={
|
||||||
<ComposeIcon2 strokeWidth={1.5} size={29} style={{color: 'white'}} />
|
<ComposeIcon2 strokeWidth={1.5} size={29} style={{color: 'white'}} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
||||||
import {FAB} from 'view/com/util/fab/FAB'
|
import {FAB} from 'view/com/util/fab/FAB'
|
||||||
import {Haptics} from 'lib/haptics'
|
import {Haptics} from 'lib/haptics'
|
||||||
import {FeedDescriptor} from '#/state/queries/post-feed'
|
import {FeedDescriptor} from '#/state/queries/post-feed'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
|
@ -55,6 +54,7 @@ import {
|
||||||
} from '#/state/queries/list'
|
} from '#/state/queries/list'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
const SECTION_TITLES_CURATE = ['Posts', 'About']
|
const SECTION_TITLES_CURATE = ['Posts', 'About']
|
||||||
const SECTION_TITLES_MOD = ['About']
|
const SECTION_TITLES_MOD = ['About']
|
||||||
|
@ -106,9 +106,9 @@ function ProfileListScreenLoaded({
|
||||||
uri,
|
uri,
|
||||||
list,
|
list,
|
||||||
}: Props & {uri: string; list: AppBskyGraphDefs.ListView}) {
|
}: Props & {uri: string; list: AppBskyGraphDefs.ListView}) {
|
||||||
const store = useStores()
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
const {openComposer} = useComposerControls()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {rkey} = route.params
|
const {rkey} = route.params
|
||||||
const feedSectionRef = React.useRef<SectionRef>(null)
|
const feedSectionRef = React.useRef<SectionRef>(null)
|
||||||
|
@ -191,7 +191,7 @@ function ProfileListScreenLoaded({
|
||||||
</PagerWithHeader>
|
</PagerWithHeader>
|
||||||
<FAB
|
<FAB
|
||||||
testID="composeFAB"
|
testID="composeFAB"
|
||||||
onPress={() => store.shell.openComposer({})}
|
onPress={() => openComposer({})}
|
||||||
icon={
|
icon={
|
||||||
<ComposeIcon2
|
<ComposeIcon2
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
|
@ -227,7 +227,7 @@ function ProfileListScreenLoaded({
|
||||||
</PagerWithHeader>
|
</PagerWithHeader>
|
||||||
<FAB
|
<FAB
|
||||||
testID="composeFAB"
|
testID="composeFAB"
|
||||||
onPress={() => store.shell.openComposer({})}
|
onPress={() => openComposer({})}
|
||||||
icon={
|
icon={
|
||||||
<ComposeIcon2 strokeWidth={1.5} size={29} style={{color: 'white'}} />
|
<ComposeIcon2 strokeWidth={1.5} size={29} style={{color: 'white'}} />
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,30 +2,21 @@ import React, {useEffect} from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {Animated, Easing, Platform, StyleSheet, View} from 'react-native'
|
import {Animated, Easing, Platform, StyleSheet, View} from 'react-native'
|
||||||
import {ComposePost} from '../com/composer/Composer'
|
import {ComposePost} from '../com/composer/Composer'
|
||||||
import {ComposerOpts} from 'state/models/ui/shell'
|
import {useComposerState} from 'state/shell/composer'
|
||||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
|
||||||
export const Composer = observer(function ComposerImpl({
|
export const Composer = observer(function ComposerImpl({
|
||||||
active,
|
|
||||||
winHeight,
|
winHeight,
|
||||||
replyTo,
|
|
||||||
onPost,
|
|
||||||
quote,
|
|
||||||
mention,
|
|
||||||
}: {
|
}: {
|
||||||
active: boolean
|
|
||||||
winHeight: number
|
winHeight: number
|
||||||
replyTo?: ComposerOpts['replyTo']
|
|
||||||
onPost?: ComposerOpts['onPost']
|
|
||||||
quote?: ComposerOpts['quote']
|
|
||||||
mention?: ComposerOpts['mention']
|
|
||||||
}) {
|
}) {
|
||||||
|
const state = useComposerState()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const initInterp = useAnimatedValue(0)
|
const initInterp = useAnimatedValue(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (active) {
|
if (state) {
|
||||||
Animated.timing(initInterp, {
|
Animated.timing(initInterp, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
|
@ -35,7 +26,7 @@ export const Composer = observer(function ComposerImpl({
|
||||||
} else {
|
} else {
|
||||||
initInterp.setValue(0)
|
initInterp.setValue(0)
|
||||||
}
|
}
|
||||||
}, [initInterp, active])
|
}, [initInterp, state])
|
||||||
const wrapperAnimStyle = {
|
const wrapperAnimStyle = {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
|
@ -50,7 +41,7 @@ export const Composer = observer(function ComposerImpl({
|
||||||
// rendering
|
// rendering
|
||||||
// =
|
// =
|
||||||
|
|
||||||
if (!active) {
|
if (!state) {
|
||||||
return <View />
|
return <View />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,10 +51,10 @@ export const Composer = observer(function ComposerImpl({
|
||||||
aria-modal
|
aria-modal
|
||||||
accessibilityViewIsModal>
|
accessibilityViewIsModal>
|
||||||
<ComposePost
|
<ComposePost
|
||||||
replyTo={replyTo}
|
replyTo={state.replyTo}
|
||||||
onPost={onPost}
|
onPost={state.onPost}
|
||||||
quote={quote}
|
quote={state.quote}
|
||||||
mention={mention}
|
mention={state.mention}
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,34 +1,21 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {observer} from 'mobx-react-lite'
|
|
||||||
import {StyleSheet, View} from 'react-native'
|
import {StyleSheet, View} from 'react-native'
|
||||||
import {ComposePost} from '../com/composer/Composer'
|
import {ComposePost} from '../com/composer/Composer'
|
||||||
import {ComposerOpts} from 'state/models/ui/shell'
|
import {useComposerState} from 'state/shell/composer'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
|
|
||||||
const BOTTOM_BAR_HEIGHT = 61
|
const BOTTOM_BAR_HEIGHT = 61
|
||||||
|
|
||||||
export const Composer = observer(function ComposerImpl({
|
export function Composer({}: {winHeight: number}) {
|
||||||
active,
|
|
||||||
replyTo,
|
|
||||||
quote,
|
|
||||||
onPost,
|
|
||||||
mention,
|
|
||||||
}: {
|
|
||||||
active: boolean
|
|
||||||
winHeight: number
|
|
||||||
replyTo?: ComposerOpts['replyTo']
|
|
||||||
quote: ComposerOpts['quote']
|
|
||||||
onPost?: ComposerOpts['onPost']
|
|
||||||
mention?: ComposerOpts['mention']
|
|
||||||
}) {
|
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
|
const state = useComposerState()
|
||||||
|
|
||||||
// rendering
|
// rendering
|
||||||
// =
|
// =
|
||||||
|
|
||||||
if (!active) {
|
if (!state) {
|
||||||
return <View />
|
return <View />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,15 +29,15 @@ export const Composer = observer(function ComposerImpl({
|
||||||
pal.border,
|
pal.border,
|
||||||
]}>
|
]}>
|
||||||
<ComposePost
|
<ComposePost
|
||||||
replyTo={replyTo}
|
replyTo={state.replyTo}
|
||||||
quote={quote}
|
quote={state.quote}
|
||||||
onPost={onPost}
|
onPost={state.onPost}
|
||||||
mention={mention}
|
mention={state.mention}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
mask: {
|
mask: {
|
||||||
|
|
|
@ -44,6 +44,7 @@ import {Trans, msg} from '@lingui/macro'
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
|
||||||
const ProfileCard = observer(function ProfileCardImpl() {
|
const ProfileCard = observer(function ProfileCardImpl() {
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
|
@ -195,6 +196,7 @@ const NavItem = observer(function NavItemImpl({
|
||||||
function ComposeBtn() {
|
function ComposeBtn() {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const {getState} = useNavigation()
|
const {getState} = useNavigation()
|
||||||
|
const {openComposer} = useComposerControls()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {isTablet} = useWebMediaQueries()
|
const {isTablet} = useWebMediaQueries()
|
||||||
|
|
||||||
|
@ -224,7 +226,7 @@ function ComposeBtn() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPressCompose = async () =>
|
const onPressCompose = async () =>
|
||||||
store.shell.openComposer({mention: await getProfileHandle()})
|
openComposer({mention: await getProfileHandle()})
|
||||||
|
|
||||||
if (isTablet) {
|
if (isTablet) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -89,14 +89,7 @@ const ShellInner = observer(function ShellInnerImpl() {
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</View>
|
</View>
|
||||||
<Composer
|
<Composer winHeight={winDim.height} />
|
||||||
active={store.shell.isComposerActive}
|
|
||||||
winHeight={winDim.height}
|
|
||||||
replyTo={store.shell.composerOpts?.replyTo}
|
|
||||||
onPost={store.shell.composerOpts?.onPost}
|
|
||||||
quote={store.shell.composerOpts?.quote}
|
|
||||||
mention={store.shell.composerOpts?.mention}
|
|
||||||
/>
|
|
||||||
<ModalsContainer />
|
<ModalsContainer />
|
||||||
<Lightbox />
|
<Lightbox />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -61,14 +61,7 @@ const ShellInner = observer(function ShellInnerImpl() {
|
||||||
<DesktopRightNav />
|
<DesktopRightNav />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Composer
|
<Composer winHeight={0} />
|
||||||
active={store.shell.isComposerActive}
|
|
||||||
winHeight={0}
|
|
||||||
replyTo={store.shell.composerOpts?.replyTo}
|
|
||||||
quote={store.shell.composerOpts?.quote}
|
|
||||||
onPost={store.shell.composerOpts?.onPost}
|
|
||||||
mention={store.shell.composerOpts?.mention}
|
|
||||||
/>
|
|
||||||
{showBottomBar && <BottomBarWeb />}
|
{showBottomBar && <BottomBarWeb />}
|
||||||
<ModalsContainer />
|
<ModalsContainer />
|
||||||
<Lightbox />
|
<Lightbox />
|
||||||
|
|
Loading…
Reference in New Issue