Bluesky app fork with some witchin' additions 馃挮
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}