Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client
117
fork

Configure Feed

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

refactor: stop filling console on web

+ refactor contacts handling
+ update video saving methods

+128 -15
+3
rspack.config.ts
··· 347 347 348 348 // Don't bundle node built-ins (shouldn't be needed on web) 349 349 externalsPresets: {node: false}, 350 + node: { 351 + __filename: false, 352 + }, 350 353 351 354 stats: GENERATE_STATS ? 'verbose' : 'normal', 352 355
+1 -1
src/alf/typography.tsx
··· 4 4 type TextProps as RNTextProps, 5 5 type TextStyle, 6 6 } from 'react-native' 7 - import {UITextView} from 'react-native-uitextview' 8 7 import createEmojiRegex from 'emoji-regex' 9 8 9 + import {UITextView} from '#/platform/ui-text-view' 10 10 import {type Alf, applyFonts, atoms, flatten} from '#/alf' 11 11 import {IS_IOS, IS_NATIVE} from '#/env' 12 12
+4 -7
src/components/PostControls/PostMenu/PostMenuItems.tsx
··· 27 27 import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' 28 28 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 29 29 import {useOpenLink} from '#/lib/hooks/useOpenLink' 30 - import {saveVideoToMediaLibrary} from '#/lib/media/manip' 31 - import {downloadVideoWeb} from '#/lib/media/manip.web' 30 + import {saveVideoToDevice} from '#/lib/media/saveVideoToDevice' 32 31 import {getCurrentRoute} from '#/lib/routes/helpers' 33 32 import {makeProfileLink} from '#/lib/routes/links' 34 33 import { ··· 109 108 import * as Prompt from '#/components/Prompt' 110 109 import * as Toast from '#/components/Toast' 111 110 import {useAnalytics} from '#/analytics' 112 - import {IS_INTERNAL, IS_WEB} from '#/env' 111 + import {IS_INTERNAL} from '#/env' 113 112 114 113 let PostMenuItems = ({ 115 114 post, ··· 617 616 Toast.show(l({message: 'Downloading video...', context: 'toast'})) 618 617 619 618 let success 620 - if (IS_WEB) success = await downloadVideoWeb({uri: uri}) 621 - else success = await saveVideoToMediaLibrary({uri: uri}) 619 + success = await saveVideoToDevice({uri: uri}) 622 620 623 621 if (success) 624 622 Toast.show(l({message: 'Video downloaded', context: 'toast'}), { ··· 636 634 Toast.show(l({message: 'Downloading GIF...', context: 'toast'})) 637 635 638 636 let success 639 - if (IS_WEB) success = await downloadVideoWeb({uri: gifEmbed.external.uri}) 640 - else success = await saveVideoToMediaLibrary({uri: gifEmbed.external.uri}) 637 + success = await saveVideoToDevice({uri: gifEmbed.external.uri}) 641 638 642 639 if (success) 643 640 Toast.show(l({message: 'GIF downloaded', context: 'toast'}), {
+1 -2
src/components/Typography.tsx
··· 1 - import {UITextView} from 'react-native-uitextview' 2 - 3 1 import {logger} from '#/logger' 2 + import {UITextView} from '#/platform/ui-text-view' 4 3 import {atoms as a, type TextStyleProp, useAlf, useTheme, web} from '#/alf' 5 4 import { 6 5 childHasEmoji,
+1 -1
src/components/contacts/screens/GetContacts.tsx
··· 1 1 import {useContext} from 'react' 2 2 import {Alert, View} from 'react-native' 3 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 - import * as Contacts from 'expo-contacts' 5 4 import type AtpAgent from '@atproto/api' 6 5 import { 7 6 type AppBskyActorProfile, ··· 16 15 import {uploadBlob} from '#/lib/api' 17 16 import {cleanError, isNetworkError} from '#/lib/strings/errors' 18 17 import {logger} from '#/logger' 18 + import * as Contacts from '#/platform/contacts' 19 19 import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' 20 20 import {useAgent} from '#/state/session' 21 21 import {
+1 -1
src/components/contacts/state.ts
··· 1 1 import {createContext, useContext, useReducer} from 'react' 2 2 import {type GestureResponderEvent} from 'react-native' 3 - import {type ExistingContact} from 'expo-contacts' 4 3 5 4 import {type CountryCode} from '#/lib/international-telephone-codes' 5 + import {type ExistingContact} from '#/platform/contacts' 6 6 import type * as bsky from '#/types/bsky' 7 7 8 8 export type Contact = ExistingContact
+5
src/lib/media/saveVideoToDevice.ts
··· 1 + import {saveVideoToMediaLibrary} from './manip' 2 + 3 + export async function saveVideoToDevice({uri}: {uri: string}) { 4 + return await saveVideoToMediaLibrary({uri}) 5 + }
+5
src/lib/media/saveVideoToDevice.web.ts
··· 1 + import {downloadVideoWeb} from './manip.web' 2 + 3 + export async function saveVideoToDevice({uri}: {uri: string}) { 4 + return await downloadVideoWeb({uri}) 5 + }
+1
src/platform/contacts/index.ts
··· 1 + export * from 'expo-contacts'
+47
src/platform/contacts/index.web.ts
··· 1 + type AccessPrivileges = 'all' | 'limited' | 'none' 2 + 3 + export type ExistingContact = { 4 + id?: string 5 + firstName?: string 6 + lastName?: string 7 + phoneNumbers?: Array<{ 8 + label?: string 9 + number?: string 10 + digits?: string 11 + countryCode?: string 12 + }> 13 + imageAvailable?: boolean 14 + } 15 + 16 + export type PermissionResponse = { 17 + granted: boolean 18 + canAskAgain: boolean 19 + accessPrivileges?: AccessPrivileges 20 + } 21 + 22 + export const Fields = { 23 + FirstName: 'firstName', 24 + LastName: 'lastName', 25 + PhoneNumbers: 'phoneNumbers', 26 + Image: 'image', 27 + } as const 28 + 29 + export async function isAvailableAsync() { 30 + return false 31 + } 32 + 33 + export async function getPermissionsAsync(): Promise<PermissionResponse> { 34 + return { 35 + granted: false, 36 + canAskAgain: false, 37 + accessPrivileges: 'none', 38 + } 39 + } 40 + 41 + export async function requestPermissionsAsync(): Promise<PermissionResponse> { 42 + return await getPermissionsAsync() 43 + } 44 + 45 + export async function getContactsAsync() { 46 + return {data: [] as ExistingContact[]} 47 + }
+1
src/platform/ui-text-view/index.ts
··· 1 + export {UITextView} from 'react-native-uitextview'
+17
src/platform/ui-text-view/index.web.tsx
··· 1 + import {Text as RNText} from 'react-native' 2 + 3 + type UITextViewProps = React.ComponentProps<typeof RNText> & { 4 + dataSet?: Record<string, string | number | undefined> 5 + uiTextView?: boolean 6 + } 7 + 8 + export function UITextView({ 9 + children, 10 + dataSet, 11 + uiTextView: _uiTextView, 12 + ...rest 13 + }: UITextViewProps) { 14 + const textProps = dataSet ? ({...rest, dataSet} as UITextViewProps) : rest 15 + 16 + return <RNText {...textProps}>{children}</RNText> 17 + }
+1 -1
src/screens/Onboarding/StepFindContactsIntro/index.tsx
··· 1 1 import {View} from 'react-native' 2 - import * as Contacts from 'expo-contacts' 3 2 import {msg} from '@lingui/core/macro' 4 3 import {useLingui} from '@lingui/react' 5 4 import {Trans} from '@lingui/react/macro' ··· 7 6 8 7 import {urls} from '#/lib/constants' 9 8 import {useCallOnce} from '#/lib/once' 9 + import * as Contacts from '#/platform/contacts' 10 10 import {atoms as a} from '#/alf' 11 11 import {Admonition} from '#/components/Admonition' 12 12 import {Button, ButtonText} from '#/components/Button'
+1 -1
src/screens/Settings/FindContactsSettings.tsx
··· 1 1 import {useCallback, useEffect, useState} from 'react' 2 2 import {type ListRenderItemInfo, View} from 'react-native' 3 - import * as Contacts from 'expo-contacts' 4 3 import { 5 4 type AppBskyContactDefs, 6 5 type AppBskyContactGetSyncStatus, ··· 21 20 } from '#/lib/routes/types' 22 21 import {cleanError, isNetworkError} from '#/lib/strings/errors' 23 22 import {logger} from '#/logger' 23 + import * as Contacts from '#/platform/contacts' 24 24 import { 25 25 updateProfileShadow, 26 26 useProfileShadow,
+38
src/state/appConfig.web.tsx
··· 1 + import {createContext, useContext} from 'react' 2 + 3 + type AppConfigResponse = { 4 + liveNow: { 5 + allow: string[] 6 + exceptions: { 7 + did: string 8 + allow: string[] 9 + }[] 10 + } 11 + } 12 + 13 + export const DEFAULT_APP_CONFIG_RESPONSE: AppConfigResponse = { 14 + liveNow: { 15 + allow: [], 16 + exceptions: [], 17 + }, 18 + } 19 + 20 + const Context = createContext<AppConfigResponse>(DEFAULT_APP_CONFIG_RESPONSE) 21 + 22 + export function Provider({children}: React.PropsWithChildren<{}>) { 23 + return ( 24 + <Context.Provider value={DEFAULT_APP_CONFIG_RESPONSE}> 25 + {children} 26 + </Context.Provider> 27 + ) 28 + } 29 + 30 + export async function prefetchAppConfig() {} 31 + 32 + export function useAppConfig() { 33 + const ctx = useContext(Context) 34 + if (!ctx) { 35 + throw new Error('useAppConfig must be used within a Provider') 36 + } 37 + return ctx 38 + }
+1 -1
src/view/com/util/text/Text.tsx
··· 1 1 import {useMemo} from 'react' 2 2 import {StyleSheet, type TextProps} from 'react-native' 3 - import {UITextView} from 'react-native-uitextview' 4 3 5 4 import {lh, s} from '#/lib/styles' 6 5 import {type TypographyVariant, useTheme} from '#/lib/ThemeContext' 7 6 import {logger} from '#/logger' 7 + import {UITextView} from '#/platform/ui-text-view' 8 8 import {applyFonts, useAlf} from '#/alf' 9 9 import { 10 10 childHasEmoji,