forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useCallback} from 'react'
2import {type GestureResponderEvent, View} from 'react-native'
3import {msg} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5import {useNavigation} from '@react-navigation/native'
6
7import {HITSLOP_30} from '#/lib/constants'
8import {type NavigationProp} from '#/lib/routes/types'
9import {sanitizeHandle} from '#/lib/strings/handles'
10import {useFeedSourceInfoQuery} from '#/state/queries/feed'
11import {UserAvatar} from '#/view/com/util/UserAvatar'
12import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types'
13import {atoms as a, useBreakpoints} from '#/alf'
14import {Button, type ButtonProps} from '#/components/Button'
15import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow'
16import * as Layout from '#/components/Layout'
17import {BUTTON_VISUAL_ALIGNMENT_OFFSET} from '#/components/Layout/const'
18import {Text} from '#/components/Typography'
19
20export function HeaderPlaceholder() {
21 return (
22 <View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
23 <View
24 style={[
25 a.rounded_sm,
26 {
27 width: 36,
28 height: 36,
29 backgroundColor: 'white',
30 opacity: 0.8,
31 },
32 ]}
33 />
34
35 <View style={[a.flex_1, a.gap_xs]}>
36 <View
37 style={[
38 a.w_full,
39 a.rounded_xs,
40 {
41 backgroundColor: 'white',
42 height: 14,
43 width: 80,
44 opacity: 0.8,
45 },
46 ]}
47 />
48 <View
49 style={[
50 a.w_full,
51 a.rounded_xs,
52 {
53 backgroundColor: 'white',
54 height: 10,
55 width: 140,
56 opacity: 0.6,
57 },
58 ]}
59 />
60 </View>
61 </View>
62 )
63}
64
65export function Header({
66 sourceContext,
67}: {
68 sourceContext: VideoFeedSourceContext
69}) {
70 let content = null
71 switch (sourceContext.type) {
72 case 'feedgen': {
73 content = <FeedHeader sourceContext={sourceContext} />
74 break
75 }
76 case 'author':
77 // TODO
78 default: {
79 break
80 }
81 }
82
83 return (
84 <Layout.Header.Outer noBottomBorder>
85 <BackButton />
86 <Layout.Header.Content align="left">{content}</Layout.Header.Content>
87 </Layout.Header.Outer>
88 )
89}
90
91export function FeedHeader({
92 sourceContext,
93}: {
94 sourceContext: Exclude<VideoFeedSourceContext, {type: 'author'}>
95}) {
96 const {gtMobile} = useBreakpoints()
97
98 const {
99 data: info,
100 isLoading,
101 error,
102 } = useFeedSourceInfoQuery({uri: sourceContext.uri})
103
104 if (sourceContext.sourceInterstitial !== undefined) {
105 // For now, don't show the header if coming from an interstitial.
106 return null
107 }
108
109 if (isLoading) {
110 return <HeaderPlaceholder />
111 } else if (error || !info) {
112 return null
113 }
114
115 return (
116 <View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
117 {info.avatar && <UserAvatar size={36} type="algo" avatar={info.avatar} />}
118
119 <View style={[a.flex_1]}>
120 <Text
121 style={[
122 a.text_md,
123 a.font_bold,
124 a.leading_tight,
125 gtMobile && a.text_lg,
126 ]}
127 numberOfLines={2}>
128 {info.displayName}
129 </Text>
130 <View style={[a.flex_row, {gap: 6}]}>
131 <Text
132 style={[a.flex_shrink, a.text_sm, a.leading_snug]}
133 numberOfLines={1}>
134 {sanitizeHandle(info.creatorHandle, '@')}
135 </Text>
136 </View>
137 </View>
138 </View>
139 )
140}
141
142// TODO: This customization should be a part of the layout component
143export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
144 const {_} = useLingui()
145 const navigation = useNavigation<NavigationProp>()
146
147 const onPressBack = useCallback(
148 (evt: GestureResponderEvent) => {
149 onPress?.(evt)
150 if (evt.defaultPrevented) return
151 if (navigation.canGoBack()) {
152 navigation.goBack()
153 } else {
154 navigation.navigate('Home')
155 }
156 },
157 [onPress, navigation],
158 )
159
160 return (
161 <Layout.Header.Slot>
162 <Button
163 label={_(msg`Go back`)}
164 size="small"
165 variant="ghost"
166 color="secondary"
167 shape="round"
168 onPress={onPressBack}
169 hitSlop={HITSLOP_30}
170 style={[
171 {marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET},
172 a.bg_transparent,
173 style,
174 ]}
175 {...props}>
176 <ArrowLeft size="lg" fill="white" />
177 </Button>
178 </Layout.Header.Slot>
179 )
180}