Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Intent handler (#2992)

* Handle URL params

* Add resources

* Add other params

* refactor for scope

* modify the pr to support intents rather than utm

remove linebreak

remove linebreak

handle web

adjust path check to work on web

add a short delay for opening the composer

setup compose intent, move to `intents` directory

fix intent logic

ignore incoming intents in the navigation router

* refactor

---------

Co-authored-by: Eric Bailey <git@esb.lol>

authored by

Hailey
Eric Bailey
and committed by
GitHub
2a04546c c8d02a79

+88 -1
+4
app.config.js
··· 89 89 scheme: 'https', 90 90 host: 'bsky.app', 91 91 }, 92 + { 93 + scheme: 'http', 94 + host: 'localhost:19006', 95 + }, 92 96 ], 93 97 category: ['BROWSABLE', 'DEFAULT'], 94 98 },
+1
package.json
··· 109 109 "expo-image": "~1.10.3", 110 110 "expo-image-manipulator": "^11.8.0", 111 111 "expo-image-picker": "~14.7.1", 112 + "expo-linking": "^6.2.2", 112 113 "expo-localization": "~14.8.2", 113 114 "expo-media-library": "~15.9.1", 114 115 "expo-notifications": "~0.27.3",
+2
src/App.native.tsx
··· 45 45 import {Provider as PortalProvider} from '#/components/Portal' 46 46 import {msg} from '@lingui/macro' 47 47 import {useLingui} from '@lingui/react' 48 + import {useIntentHandler} from 'lib/hooks/useIntentHandler' 48 49 49 50 SplashScreen.preventAutoHideAsync() 50 51 ··· 53 54 const {resumeSession} = useSessionApi() 54 55 const theme = useColorModeTheme() 55 56 const {_} = useLingui() 57 + useIntentHandler() 56 58 57 59 // init 58 60 useEffect(() => {
+2
src/App.web.tsx
··· 32 32 import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' 33 33 import * as persisted from '#/state/persisted' 34 34 import {Provider as PortalProvider} from '#/components/Portal' 35 + import {useIntentHandler} from 'lib/hooks/useIntentHandler' 35 36 36 37 function InnerApp() { 37 38 const {isInitialLoad, currentAccount} = useSession() 38 39 const {resumeSession} = useSessionApi() 39 40 const theme = useColorModeTheme() 41 + useIntentHandler() 40 42 41 43 // init 42 44 useEffect(() => {
+7 -1
src/Navigation.tsx
··· 460 460 */ 461 461 462 462 const LINKING = { 463 - prefixes: ['bsky://', 'https://bsky.app'], 463 + // TODO figure out what we are going to use 464 + prefixes: ['bsky://', 'bluesky://', 'https://bsky.app'], 464 465 465 466 getPathFromState(state: State) { 466 467 // find the current node in the navigation tree ··· 478 479 }, 479 480 480 481 getStateFromPath(path: string) { 482 + // Any time we receive a url that starts with `intent/` we want to ignore it here. It will be handled in the 483 + // intent handler hook. We should check for the trailing slash, because if there isn't one then it isn't a valid 484 + // intent 485 + if (path.includes('intent/')) return 486 + 481 487 const [name, params] = router.matchPath(path) 482 488 if (isNative) { 483 489 if (name === 'Search') {
+64
src/lib/hooks/useIntentHandler.ts
··· 1 + import React from 'react' 2 + import * as Linking from 'expo-linking' 3 + import {isNative} from 'platform/detection' 4 + import {useComposerControls} from 'state/shell' 5 + import {useSession} from 'state/session' 6 + 7 + type IntentType = 'compose' 8 + 9 + export function useIntentHandler() { 10 + const incomingUrl = Linking.useURL() 11 + const composeIntent = useComposeIntent() 12 + 13 + React.useEffect(() => { 14 + const handleIncomingURL = (url: string) => { 15 + const urlp = new URL(url) 16 + const [_, intentTypeNative, intentTypeWeb] = urlp.pathname.split('/') 17 + 18 + // On native, our links look like bluesky://intent/SomeIntent, so we have to check the hostname for the 19 + // intent check. On web, we have to check the first part of the path since we have an actual hostname 20 + const intentType = isNative ? intentTypeNative : intentTypeWeb 21 + const isIntent = isNative 22 + ? urlp.hostname === 'intent' 23 + : intentTypeNative === 'intent' 24 + const params = urlp.searchParams 25 + 26 + if (!isIntent) return 27 + 28 + switch (intentType as IntentType) { 29 + case 'compose': { 30 + composeIntent({ 31 + text: params.get('text'), 32 + imageUris: params.get('imageUris'), 33 + }) 34 + } 35 + } 36 + } 37 + 38 + if (incomingUrl) handleIncomingURL(incomingUrl) 39 + }, [incomingUrl, composeIntent]) 40 + } 41 + 42 + function useComposeIntent() { 43 + const {openComposer} = useComposerControls() 44 + const {hasSession} = useSession() 45 + 46 + return React.useCallback( 47 + ({ 48 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 49 + text, 50 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 51 + imageUris, 52 + }: { 53 + text: string | null 54 + imageUris: string | null // unused for right now, will be used later with intents 55 + }) => { 56 + if (!hasSession) return 57 + 58 + setTimeout(() => { 59 + openComposer({}) // will pass in values to the composer here in the share extension 60 + }, 500) 61 + }, 62 + [openComposer, hasSession], 63 + ) 64 + }
+8
yarn.lock
··· 11739 11739 resolved "https://registry.yarnpkg.com/expo-keep-awake/-/expo-keep-awake-12.8.1.tgz#3c8df9d86c265741b5e7bdd36965aa0c6fc17df0" 11740 11740 integrity sha512-P/VZFV02Rzgj13skMwH+ceGOGZSEdaUu5n7pCS3wThh2LppZjPJ7sBxUwyzeLa3DXEVUtwLZi+BiQ91wPwy9Gg== 11741 11741 11742 + expo-linking@^6.2.2: 11743 + version "6.2.2" 11744 + resolved "https://registry.yarnpkg.com/expo-linking/-/expo-linking-6.2.2.tgz#b7e148068ae49fd9ad814428c16fdf7a236e8aca" 11745 + integrity sha512-FEe6lP4f7xFT/vjoHRG+tt6EPVtkEGaWNK1smpaUevmNdyCJKqW0PDB8o8sfG6y7fly8ULe8qg3HhKh5J7aqUQ== 11746 + dependencies: 11747 + expo-constants "~15.4.3" 11748 + invariant "^2.2.4" 11749 + 11742 11750 expo-localization@~14.8.2: 11743 11751 version "14.8.2" 11744 11752 resolved "https://registry.yarnpkg.com/expo-localization/-/expo-localization-14.8.2.tgz#e0bbed2293265834d21a1c58d3a5f8d265bd04ae"