diff options
author | Hailey <me@haileyok.com> | 2024-02-27 15:22:03 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-27 15:22:03 -0800 |
commit | d451f82f54974b7b3da1477a7e1f221628860f62 (patch) | |
tree | 631d60b9d9ef47129529068753b10029fb99c34f /src | |
parent | ac726497a475f7492ee0269851979817b17d98c2 (diff) | |
download | voidsky-d451f82f54974b7b3da1477a7e1f221628860f62.tar.zst |
Share Extension/Intents (#2587)
* add native ios code outside of ios project * helper script * going to be a lot of these commits to squash...backing up * save * start of an expo plugin * create info.plist * copy the view controller * maybe working * working * wait working now * working plugin * use current scheme * update intent path * use better params * support text in uri * build * use better encoding * handle images * cleanup ios plugin * android * move bash script to /scripts * handle cases where loaded data is uiimage rather than uri * remove unnecessary logic, allow more than 4 images and just take first 4 * android build plugin * limit images to four on android * use js for plugins, no need to build * revert changes to app config * use correct scheme on android * android readme * move ios extension to /modules * remove unnecessary event * revert typo * plugin readme * scripts readme * add configurable scheme to .env, default to `bluesky` * remove debug * revert .gitignore change * add comment about updating .env to app.config.js for those modifying scheme * modify .env * update android module to use the proper url * update ios extension * remove comment * parse and validate incoming image uris * fix types * rm oops * fix a few typos
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/hooks/useIntentHandler.ts | 35 | ||||
-rw-r--r-- | src/state/models/media/gallery.ts | 25 | ||||
-rw-r--r-- | src/state/shell/composer.tsx | 2 | ||||
-rw-r--r-- | src/view/com/composer/Composer.tsx | 11 | ||||
-rw-r--r-- | src/view/shell/Composer.tsx | 2 | ||||
-rw-r--r-- | src/view/shell/Composer.web.tsx | 3 |
6 files changed, 67 insertions, 11 deletions
diff --git a/src/lib/hooks/useIntentHandler.ts b/src/lib/hooks/useIntentHandler.ts index 249e6898e..de9a96da9 100644 --- a/src/lib/hooks/useIntentHandler.ts +++ b/src/lib/hooks/useIntentHandler.ts @@ -6,6 +6,8 @@ import {useSession} from 'state/session' type IntentType = 'compose' +const VALID_IMAGE_REGEX = /^[\w.:\-_/]+\|\d+(\.\d+)?\|\d+(\.\d+)?$/ + export function useIntentHandler() { const incomingUrl = Linking.useURL() const composeIntent = useComposeIntent() @@ -29,7 +31,7 @@ export function useIntentHandler() { case 'compose': { composeIntent({ text: params.get('text'), - imageUris: params.get('imageUris'), + imageUrisStr: params.get('imageUris'), }) } } @@ -45,18 +47,39 @@ function useComposeIntent() { return React.useCallback( ({ - // eslint-disable-next-line @typescript-eslint/no-unused-vars text, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - imageUris, + imageUrisStr, }: { text: string | null - imageUris: string | null // unused for right now, will be used later with intents + imageUrisStr: string | null // unused for right now, will be used later with intents }) => { if (!hasSession) return + const imageUris = imageUrisStr + ?.split(',') + .filter(part => { + // For some security, we're going to filter out any image uri that is external. We don't want someone to + // be able to provide some link like "bluesky://intent/compose?imageUris=https://IHaveYourIpNow.com/image.jpeg + // and we load that image + if (part.includes('https://') || part.includes('http://')) { + return false + } + // We also should just filter out cases that don't have all the info we need + if (!VALID_IMAGE_REGEX.test(part)) { + return false + } + return true + }) + .map(part => { + const [uri, width, height] = part.split('|') + return {uri, width: Number(width), height: Number(height)} + }) + setTimeout(() => { - openComposer({}) // will pass in values to the composer here in the share extension + openComposer({ + text: text ?? undefined, + imageUris: isNative ? imageUris : undefined, + }) }, 500) }, [openComposer, hasSession], diff --git a/src/state/models/media/gallery.ts b/src/state/models/media/gallery.ts index 04023bf82..9c8c13010 100644 --- a/src/state/models/media/gallery.ts +++ b/src/state/models/media/gallery.ts @@ -4,11 +4,21 @@ import {Image as RNImage} from 'react-native-image-crop-picker' import {openPicker} from 'lib/media/picker' import {getImageDim} from 'lib/media/manip' +interface InitialImageUri { + uri: string + width: number + height: number +} + export class GalleryModel { images: ImageModel[] = [] - constructor() { + constructor(uris?: {uri: string; width: number; height: number}[]) { makeAutoObservable(this) + + if (uris) { + this.addFromUris(uris) + } } get isEmpty() { @@ -23,7 +33,7 @@ export class GalleryModel { return this.images.some(image => image.altText.trim() === '') } - async add(image_: Omit<RNImage, 'size'>) { + *add(image_: Omit<RNImage, 'size'>) { if (this.size >= 4) { return } @@ -86,4 +96,15 @@ export class GalleryModel { }), ) } + + async addFromUris(uris: InitialImageUri[]) { + for (const uriObj of uris) { + this.add({ + mime: 'image/jpeg', + height: uriObj.height, + width: uriObj.width, + path: uriObj.uri, + }) + } + } } diff --git a/src/state/shell/composer.tsx b/src/state/shell/composer.tsx index 696a3c5ba..c9dbfbeac 100644 --- a/src/state/shell/composer.tsx +++ b/src/state/shell/composer.tsx @@ -38,6 +38,8 @@ export interface ComposerOpts { quote?: ComposerOptsQuote mention?: string // handle of user to mention openPicker?: (pos: DOMRect | undefined) => void + text?: string + imageUris?: {uri: string; width: number; height: number}[] } type StateContext = ComposerOpts | undefined diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 1ed6b98a5..2855d4232 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -71,6 +71,8 @@ export const ComposePost = observer(function ComposePost({ quote: initQuote, mention: initMention, openPicker, + text: initText, + imageUris: initImageUris, }: Props) { const {currentAccount} = useSession() const {data: currentProfile} = useProfileQuery({did: currentAccount!.did}) @@ -91,7 +93,9 @@ export const ComposePost = observer(function ComposePost({ const [error, setError] = useState('') const [richtext, setRichText] = useState( new RichText({ - text: initMention + text: initText + ? initText + : initMention ? insertMentionAt( `@${initMention}`, initMention.length + 1, @@ -110,7 +114,10 @@ export const ComposePost = observer(function ComposePost({ const [labels, setLabels] = useState<string[]>([]) const [threadgate, setThreadgate] = useState<ThreadgateSetting[]>([]) const [suggestedLinks, setSuggestedLinks] = useState<Set<string>>(new Set()) - const gallery = useMemo(() => new GalleryModel(), []) + const gallery = useMemo( + () => new GalleryModel(initImageUris), + [initImageUris], + ) const onClose = useCallback(() => { closeComposer() }, [closeComposer]) diff --git a/src/view/shell/Composer.tsx b/src/view/shell/Composer.tsx index d37ff4fb7..1937fcb6e 100644 --- a/src/view/shell/Composer.tsx +++ b/src/view/shell/Composer.tsx @@ -55,6 +55,8 @@ export const Composer = observer(function ComposerImpl({ onPost={state.onPost} quote={state.quote} mention={state.mention} + text={state.text} + imageUris={state.imageUris} /> </Animated.View> ) diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx index 99e659d62..00233f66a 100644 --- a/src/view/shell/Composer.web.tsx +++ b/src/view/shell/Composer.web.tsx @@ -9,7 +9,7 @@ import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock' import { EmojiPicker, EmojiPickerState, -} from 'view/com/composer/text-input/web/EmojiPicker.web.tsx' +} from 'view/com/composer/text-input/web/EmojiPicker.web' const BOTTOM_BAR_HEIGHT = 61 @@ -69,6 +69,7 @@ export function Composer({}: {winHeight: number}) { onPost={state.onPost} mention={state.mention} openPicker={onOpenPicker} + text={state.text} /> </Animated.View> <EmojiPicker state={pickerState} close={onClosePicker} /> |