Bluesky app fork with some witchin' additions 💫

WIP

+163 -1
+2
src/App.native.tsx
··· 63 63 import {Splash} from '#/Splash' 64 64 import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 65 65 import {AudioCategory, PlatformInfo} from '../modules/expo-bluesky-swiss-army' 66 + import {NudgeDialogs} from '#/components/dialogs/nudges' 66 67 67 68 SplashScreen.preventAutoHideAsync() 68 69 ··· 131 132 style={s.h100pct}> 132 133 <TestCtrls /> 133 134 <Shell /> 135 + <NudgeDialogs /> 134 136 </GestureHandlerRootView> 135 137 </ProgressGuideProvider> 136 138 </MutedThreadsProvider>
+2
src/App.web.tsx
··· 50 50 import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs' 51 51 import {Provider as PortalProvider} from '#/components/Portal' 52 52 import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 53 + import {NudgeDialogs} from '#/components/dialogs/nudges' 53 54 54 55 function InnerApp() { 55 56 const [isReady, setIsReady] = React.useState(false) ··· 113 114 <SafeAreaProvider> 114 115 <ProgressGuideProvider> 115 116 <Shell /> 117 + <NudgeDialogs /> 116 118 </ProgressGuideProvider> 117 119 </SafeAreaProvider> 118 120 </MutedThreadsProvider>
+100
src/components/dialogs/nudges/TenMillion.tsx
··· 1 + import React from 'react' 2 + import {useLingui} from '@lingui/react' 3 + import {msg} from '@lingui/macro' 4 + import {View} from 'react-native' 5 + import ViewShot from 'react-native-view-shot' 6 + 7 + import {atoms as a, useBreakpoints, tokens} from '#/alf' 8 + import * as Dialog from '#/components/Dialog' 9 + import {Text} from '#/components/Typography' 10 + import {GradientFill} from '#/components/GradientFill' 11 + import {Button, ButtonText} from '#/components/Button' 12 + import {useComposerControls} from 'state/shell' 13 + 14 + import {useContext} from '#/components/dialogs/nudges' 15 + 16 + export function TenMillion() { 17 + const {_} = useLingui() 18 + const {controls} = useContext() 19 + const {gtMobile} = useBreakpoints() 20 + const {openComposer} = useComposerControls() 21 + 22 + const imageRef = React.useRef<ViewShot>(null) 23 + 24 + const share = () => { 25 + if (imageRef.current && imageRef.current.capture) { 26 + imageRef.current.capture().then(uri => { 27 + controls.tenMillion.close(() => { 28 + setTimeout(() => { 29 + openComposer({ 30 + text: '10 milly, babyyy', 31 + imageUris: [ 32 + { 33 + uri, 34 + width: 1000, 35 + height: 1000, 36 + }, 37 + ], 38 + }) 39 + }, 1e3) 40 + }) 41 + }) 42 + } 43 + } 44 + 45 + return ( 46 + <Dialog.Outer control={controls.tenMillion}> 47 + <Dialog.Handle /> 48 + 49 + <Dialog.ScrollableInner 50 + label={_(msg`Ten Million`)} 51 + style={ 52 + [ 53 + // gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full, 54 + ] 55 + }> 56 + <View 57 + style={[ 58 + a.relative, 59 + a.w_full, 60 + a.overflow_hidden, 61 + { 62 + paddingTop: '100%', 63 + }, 64 + ]}> 65 + <ViewShot 66 + ref={imageRef} 67 + options={{width: 2e3, height: 2e3}} 68 + style={[a.absolute, a.inset_0]}> 69 + <View 70 + style={[ 71 + a.absolute, 72 + a.inset_0, 73 + a.align_center, 74 + a.justify_center, 75 + { 76 + top: -1, 77 + bottom: -1, 78 + left: -1, 79 + right: -1, 80 + }, 81 + ]}> 82 + <GradientFill gradient={tokens.gradients.midnight} /> 83 + 84 + <Text>10 milly, babyyy</Text> 85 + </View> 86 + </ViewShot> 87 + </View> 88 + 89 + <Button 90 + label={_(msg`Generate`)} 91 + size="medium" 92 + variant="solid" 93 + color="primary" 94 + onPress={share}> 95 + <ButtonText>{_(msg`Generate`)}</ButtonText> 96 + </Button> 97 + </Dialog.ScrollableInner> 98 + </Dialog.Outer> 99 + ) 100 + }
+53
src/components/dialogs/nudges/index.tsx
··· 1 + import React from 'react' 2 + 3 + import * as Dialog from '#/components/Dialog' 4 + 5 + import {TenMillion} from '#/components/dialogs/nudges/TenMillion' 6 + 7 + type Context = { 8 + controls: { 9 + tenMillion: Dialog.DialogOuterProps['control'] 10 + } 11 + } 12 + 13 + const Context = React.createContext<Context>({ 14 + // @ts-ignore 15 + controls: {} 16 + }) 17 + 18 + export function useContext() { 19 + return React.useContext(Context) 20 + } 21 + 22 + let SHOWN = false 23 + 24 + export function NudgeDialogs() { 25 + const tenMillion = Dialog.useDialogControl() 26 + 27 + const ctx = React.useMemo(() => { 28 + return { 29 + controls: { 30 + tenMillion 31 + } 32 + } 33 + }, [tenMillion]) 34 + 35 + React.useEffect(() => { 36 + const t = setTimeout(() => { 37 + if (!SHOWN) { 38 + SHOWN = true 39 + ctx.controls.tenMillion.open() 40 + } 41 + }, 2e3) 42 + 43 + return () => { 44 + clearTimeout(t) 45 + } 46 + }, [ctx]) 47 + 48 + return ( 49 + <Context.Provider value={ctx}> 50 + <TenMillion /> 51 + </Context.Provider> 52 + ) 53 + }
+5 -1
src/lib/hooks/useIntentHandler.ts
··· 71 71 }, [incomingUrl, composeIntent, verifyEmailIntent]) 72 72 } 73 73 74 - function useComposeIntent() { 74 + export function useComposeIntent() { 75 75 const closeAllActiveElements = useCloseAllActiveElements() 76 76 const {openComposer} = useComposerControls() 77 77 const {hasSession} = useSession() ··· 97 97 if (part.includes('https://') || part.includes('http://')) { 98 98 return false 99 99 } 100 + console.log({ 101 + part, 102 + text: VALID_IMAGE_REGEX.test(part), 103 + }) 100 104 // We also should just filter out cases that don't have all the info we need 101 105 return VALID_IMAGE_REGEX.test(part) 102 106 })
+1
src/view/shell/Composer.web.tsx
··· 63 63 mention={state.mention} 64 64 openEmojiPicker={onOpenPicker} 65 65 text={state.text} 66 + imageUris={state.imageUris} 66 67 /> 67 68 </View> 68 69 <EmojiPicker state={pickerState} close={onClosePicker} />