Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
120
fork

Configure Feed

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

at a876aae44ea07494ebea9727350aa060b81f317b 141 lines 4.2 kB view raw
1import {useMemo} from 'react' 2import {View} from 'react-native' 3import {Image} from 'expo-image' 4import {LinearGradient} from 'expo-linear-gradient' 5import {msg} from '@lingui/core/macro' 6import {useLingui} from '@lingui/react' 7import {Trans} from '@lingui/react/macro' 8 9import {useCallOnce} from '#/lib/once' 10import {isBskyCustomFeedUrl} from '#/lib/strings/url-helpers' 11import {atoms as a, useBreakpoints, utils} from '#/alf' 12import {Link} from '#/components/Link' 13import {Text} from '#/components/Typography' 14import {useAnalytics} from '#/analytics' 15import { 16 type LiveEventFeed, 17 type LiveEventFeedMetricContext, 18} from '#/features/liveEvents/types' 19 20const roundedStyles = [a.rounded_lg, a.curve_continuous] 21 22export function LiveEventFeedCardWide({ 23 feed, 24 metricContext, 25}: { 26 feed: LiveEventFeed 27 metricContext: LiveEventFeedMetricContext 28}) { 29 const ax = useAnalytics() 30 const {_} = useLingui() 31 const {gtPhone} = useBreakpoints() 32 33 const layout = feed.layouts.wide 34 const overlayColor = layout.overlayColor 35 const textColor = layout.textColor 36 const url = useMemo(() => { 37 // Validated in multiple places on the backend 38 if (isBskyCustomFeedUrl(feed.url)) { 39 return new URL(feed.url).pathname 40 } 41 return '/' 42 }, [feed.url]) 43 44 useCallOnce(() => { 45 ax.metric('liveEvents:feedBanner:seen', { 46 feed: feed.url, 47 context: metricContext, 48 }) 49 })() 50 51 return ( 52 <Link 53 to={url} 54 label={_(msg`Live event happening now: ${feed.title}`)} 55 style={[a.w_full]} 56 onPress={() => { 57 ax.metric('liveEvents:feedBanner:click', { 58 feed: feed.url, 59 context: metricContext, 60 }) 61 }}> 62 {({hovered, pressed}) => ( 63 <View style={[roundedStyles, a.shadow_md, a.w_full]}> 64 <View 65 style={[ 66 a.align_start, 67 roundedStyles, 68 a.overflow_hidden, 69 { 70 aspectRatio: gtPhone ? 576 / 144 : 369 / 100, 71 }, 72 ]}> 73 <Image 74 accessibilityIgnoresInvertColors 75 source={{uri: layout.image}} 76 placeholder={{blurhash: layout.blurhash}} 77 style={[a.absolute, a.inset_0, a.w_full, a.h_full]} 78 contentFit="cover" 79 placeholderContentFit="cover" 80 /> 81 82 <LinearGradient 83 colors={[overlayColor, utils.alpha(overlayColor, 0)]} 84 locations={[0, 1]} 85 start={{x: 0, y: 0}} 86 end={{x: 1, y: 0}} 87 style={[ 88 a.absolute, 89 a.inset_0, 90 a.transition_opacity, 91 { 92 transitionDuration: '200ms', 93 opacity: hovered || pressed ? 0.6 : 0, 94 }, 95 ]} 96 /> 97 98 <View style={[a.flex_1, a.justify_end]}> 99 <LinearGradient 100 colors={[overlayColor, utils.alpha(overlayColor, 0)]} 101 locations={[0, 1]} 102 start={{x: 0, y: 0}} 103 end={{x: 1, y: 0}} 104 style={[a.absolute, a.inset_0]} 105 /> 106 107 <View 108 style={[ 109 a.z_10, 110 gtPhone ? [a.pl_xl, a.pb_lg] : [a.pl_lg, a.pb_md], 111 {paddingRight: 64}, 112 ]}> 113 <Text 114 style={[ 115 a.leading_snug, 116 gtPhone ? a.text_xs : a.text_2xs, 117 {color: textColor, opacity: 0.8}, 118 ]}> 119 {feed.preview ? ( 120 <Trans>Preview</Trans> 121 ) : ( 122 <Trans>Happening now</Trans> 123 )} 124 </Text> 125 <Text 126 style={[ 127 a.leading_snug, 128 a.font_bold, 129 gtPhone ? a.text_3xl : a.text_lg, 130 {color: textColor}, 131 ]}> 132 {layout.title} 133 </Text> 134 </View> 135 </View> 136 </View> 137 </View> 138 )} 139 </Link> 140 ) 141}