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