Bluesky app fork with some witchin' additions 💫

Merge with upstream for 1.116

xan.lol 83f9cfb5 472772fd

verified
+102 -20
+1 -1
package.json
··· 1 { 2 "name": "witchsky-app", 3 - "version": "1.115.0", 4 "private": true, 5 "engines": { 6 "node": ">=18"
··· 1 { 2 "name": "witchsky-app", 3 + "version": "1.116.0", 4 "private": true, 5 "engines": { 6 "node": ">=18"
+1 -1
src/analytics/metrics/client.ts
··· 51 } 52 this.queue.push(e) 53 54 - logger.info(`event: ${e.event as string}`, e) 55 56 if (this.queue.length > this.maxBatchSize) { 57 this.flush()
··· 51 } 52 this.queue.push(e) 53 54 + logger.debug(`event: ${e.event as string}`, e) 55 56 if (this.queue.length > this.maxBatchSize) { 57 this.flush()
+86 -9
src/features/liveEvents/components/SidebarLiveEventFeedsBanner.tsx
··· 1 import {LiveEventFeedCardCompact} from '#/features/liveEvents/components/LiveEventFeedCardCompact' 2 - import {useLiveEvents} from '#/features/liveEvents/context' 3 4 export function SidebarLiveEventFeedsBanner() { 5 - const events = useLiveEvents() 6 - return events.feeds.map(feed => ( 7 - <LiveEventFeedCardCompact 8 - key={feed.id} 9 - feed={feed} 10 - metricContext="sidebar" 11 - /> 12 - )) 13 }
··· 1 + import {View} from 'react-native' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + 5 + import {atoms as a} from '#/alf' 6 + import {Button} from '#/components/Button' 7 + import {TimesLarge_Stroke2_Corner0_Rounded as CloseIcon} from '#/components/icons/Times' 8 + import * as Toast from '#/components/Toast' 9 import {LiveEventFeedCardCompact} from '#/features/liveEvents/components/LiveEventFeedCardCompact' 10 + import {useUserPreferencedLiveEvents} from '#/features/liveEvents/context' 11 + import {useUpdateLiveEventPreferences} from '#/features/liveEvents/preferences' 12 + import {type LiveEventFeed} from '#/features/liveEvents/types' 13 14 export function SidebarLiveEventFeedsBanner() { 15 + const events = useUserPreferencedLiveEvents() 16 + return events.feeds.map(feed => <Inner key={feed.id} feed={feed} />) 17 + } 18 + 19 + function Inner({feed}: {feed: LiveEventFeed}) { 20 + const {_} = useLingui() 21 + const layout = feed.layouts.wide 22 + 23 + const {mutate: update, variables} = useUpdateLiveEventPreferences({ 24 + feed, 25 + metricContext: 'sidebar', 26 + onUpdateSuccess({undoAction}) { 27 + Toast.show( 28 + <Toast.Outer> 29 + <Toast.Icon /> 30 + <Toast.Text> 31 + {undoAction ? ( 32 + <Trans>Live event hidden</Trans> 33 + ) : ( 34 + <Trans>Live event unhidden</Trans> 35 + )} 36 + </Toast.Text> 37 + {undoAction && ( 38 + <Toast.Action 39 + label={_(msg`Undo`)} 40 + onPress={() => { 41 + if (undoAction) { 42 + update(undoAction) 43 + } 44 + }}> 45 + <Trans>Undo</Trans> 46 + </Toast.Action> 47 + )} 48 + </Toast.Outer>, 49 + {type: 'success'}, 50 + ) 51 + }, 52 + }) 53 + 54 + if (variables) return null 55 + 56 + return ( 57 + <View style={[a.relative]}> 58 + <LiveEventFeedCardCompact feed={feed} metricContext="sidebar" /> 59 + 60 + <View 61 + style={[a.justify_center, a.absolute, {top: 0, right: 6, bottom: 0}]}> 62 + <Button 63 + label={_(msg`Dismiss live event banner`)} 64 + size="tiny" 65 + shape="round" 66 + style={[a.z_10]} 67 + onPress={() => { 68 + update({type: 'hideFeed', id: feed.id}) 69 + }}> 70 + {({hovered, pressed}) => ( 71 + <> 72 + <View 73 + style={[ 74 + a.absolute, 75 + a.inset_0, 76 + a.rounded_full, 77 + { 78 + backgroundColor: layout.overlayColor, 79 + opacity: hovered || pressed ? 0.8 : 0.6, 80 + }, 81 + ]} 82 + /> 83 + <CloseIcon size="xs" fill={layout.textColor} style={[a.z_20]} /> 84 + </> 85 + )} 86 + </Button> 87 + </View> 88 + </View> 89 + ) 90 }
+7 -2
src/features/liveEvents/context.tsx
··· 1 import {createContext, useContext, useMemo} from 'react' 2 import {QueryClient, useQuery} from '@tanstack/react-query' 3 4 import {useIsBskyTeam} from '#/lib/hooks/useIsBskyTeam' 5 import { 6 convertBskyAppUrlIfNeeded, ··· 35 export function Provider({children}: React.PropsWithChildren<{}>) { 36 const [isDevMode] = useDevMode() 37 const isBskyTeam = useIsBskyTeam() 38 - const {data} = useQuery( 39 { 40 // keep this, prefectching handles initial load 41 staleTime: 1000 * 15, 42 queryKey: liveEventsQueryKey, 43 - refetchInterval: 1000 * 60 * 5, 44 async queryFn() { 45 return fetchLiveEvents() 46 }, 47 }, 48 qc, 49 ) 50 51 const ctx = useMemo(() => { 52 if (!data) return DEFAULT_LIVE_EVENTS
··· 1 import {createContext, useContext, useMemo} from 'react' 2 import {QueryClient, useQuery} from '@tanstack/react-query' 3 4 + import {useOnAppStateChange} from '#/lib/appState' 5 import {useIsBskyTeam} from '#/lib/hooks/useIsBskyTeam' 6 import { 7 convertBskyAppUrlIfNeeded, ··· 36 export function Provider({children}: React.PropsWithChildren<{}>) { 37 const [isDevMode] = useDevMode() 38 const isBskyTeam = useIsBskyTeam() 39 + const {data, refetch} = useQuery( 40 { 41 // keep this, prefectching handles initial load 42 staleTime: 1000 * 15, 43 queryKey: liveEventsQueryKey, 44 + refetchInterval: 1000 * 60 * 5, // refetch every 5 minutes 45 async queryFn() { 46 return fetchLiveEvents() 47 }, 48 }, 49 qc, 50 ) 51 + 52 + useOnAppStateChange(state => { 53 + if (state === 'active') refetch() 54 + }) 55 56 const ctx = useMemo(() => { 57 if (!data) return DEFAULT_LIVE_EVENTS
+7 -7
src/lib/appState.ts
··· 12 }) 13 } 14 15 - export function useAppState() { 16 - const [state, setState] = useState(AppState.currentState) 17 - 18 useEffect(() => { 19 - const sub = onAppStateChange(next => { 20 - setState(next) 21 - }) 22 return () => sub.remove() 23 - }, []) 24 25 return state 26 }
··· 12 }) 13 } 14 15 + export function useOnAppStateChange(cb: (state: AppStateStatus) => void) { 16 useEffect(() => { 17 + const sub = onAppStateChange(next => cb(next)) 18 return () => sub.remove() 19 + }, [cb]) 20 + } 21 22 + export function useAppState() { 23 + const [state, setState] = useState(AppState.currentState) 24 + useOnAppStateChange(setState) 25 return state 26 }