mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {Pressable, StyleSheet, View} from 'react-native'
3import {Image as RNImage} from 'react-native-image-crop-picker'
4import {Image} from 'expo-image'
5import {ModerationUI} from '@atproto/api'
6import {msg, Trans} from '@lingui/macro'
7import {useLingui} from '@lingui/react'
8
9import {usePalette} from '#/lib/hooks/usePalette'
10import {
11 useCameraPermission,
12 usePhotoLibraryPermission,
13} from '#/lib/hooks/usePermissions'
14import {colors} from '#/lib/styles'
15import {useTheme} from '#/lib/ThemeContext'
16import {logger} from '#/logger'
17import {isAndroid, isNative} from '#/platform/detection'
18import {EventStopper} from '#/view/com/util/EventStopper'
19import {tokens, useTheme as useAlfTheme} from '#/alf'
20import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
21import {
22 Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled,
23 Camera_Stroke2_Corner0_Rounded as Camera,
24} from '#/components/icons/Camera'
25import {StreamingLive_Stroke2_Corner0_Rounded as Library} from '#/components/icons/StreamingLive'
26import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
27import * as Menu from '#/components/Menu'
28import {openCamera, openCropper, openPicker} from '../../../lib/media/picker'
29
30export function UserBanner({
31 type,
32 banner,
33 moderation,
34 onSelectNewBanner,
35}: {
36 type?: 'labeler' | 'default'
37 banner?: string | null
38 moderation?: ModerationUI
39 onSelectNewBanner?: (img: RNImage | null) => void
40}) {
41 const pal = usePalette('default')
42 const theme = useTheme()
43 const t = useAlfTheme()
44 const {_} = useLingui()
45 const {requestCameraAccessIfNeeded} = useCameraPermission()
46 const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission()
47 const sheetWrapper = useSheetWrapper()
48
49 const onOpenCamera = React.useCallback(async () => {
50 if (!(await requestCameraAccessIfNeeded())) {
51 return
52 }
53 onSelectNewBanner?.(
54 await openCamera({
55 width: 3000,
56 height: 1000,
57 }),
58 )
59 }, [onSelectNewBanner, requestCameraAccessIfNeeded])
60
61 const onOpenLibrary = React.useCallback(async () => {
62 if (!(await requestPhotoAccessIfNeeded())) {
63 return
64 }
65 const items = await sheetWrapper(openPicker())
66 if (!items[0]) {
67 return
68 }
69
70 try {
71 onSelectNewBanner?.(
72 await openCropper({
73 mediaType: 'photo',
74 path: items[0].path,
75 width: 3000,
76 height: 1000,
77 webAspectRatio: 3,
78 }),
79 )
80 } catch (e: any) {
81 if (!String(e).includes('Canceled')) {
82 logger.error('Failed to crop banner', {error: e})
83 }
84 }
85 }, [onSelectNewBanner, requestPhotoAccessIfNeeded, sheetWrapper])
86
87 const onRemoveBanner = React.useCallback(() => {
88 onSelectNewBanner?.(null)
89 }, [onSelectNewBanner])
90
91 // setUserBanner is only passed as prop on the EditProfile component
92 return onSelectNewBanner ? (
93 <EventStopper onKeyDown={true}>
94 <Menu.Root>
95 <Menu.Trigger label={_(msg`Edit avatar`)}>
96 {({props}) => (
97 <Pressable {...props} testID="changeBannerBtn">
98 {banner ? (
99 <Image
100 testID="userBannerImage"
101 style={styles.bannerImage}
102 source={{uri: banner}}
103 accessible={true}
104 accessibilityIgnoresInvertColors
105 />
106 ) : (
107 <View
108 testID="userBannerFallback"
109 style={[styles.bannerImage, t.atoms.bg_contrast_25]}
110 />
111 )}
112 <View style={[styles.editButtonContainer, pal.btn]}>
113 <CameraFilled height={14} width={14} style={t.atoms.text} />
114 </View>
115 </Pressable>
116 )}
117 </Menu.Trigger>
118 <Menu.Outer showCancel>
119 <Menu.Group>
120 {isNative && (
121 <Menu.Item
122 testID="changeBannerCameraBtn"
123 label={_(msg`Upload from Camera`)}
124 onPress={onOpenCamera}>
125 <Menu.ItemText>
126 <Trans>Upload from Camera</Trans>
127 </Menu.ItemText>
128 <Menu.ItemIcon icon={Camera} />
129 </Menu.Item>
130 )}
131
132 <Menu.Item
133 testID="changeBannerLibraryBtn"
134 label={_(msg`Upload from Library`)}
135 onPress={onOpenLibrary}>
136 <Menu.ItemText>
137 {isNative ? (
138 <Trans>Upload from Library</Trans>
139 ) : (
140 <Trans>Upload from Files</Trans>
141 )}
142 </Menu.ItemText>
143 <Menu.ItemIcon icon={Library} />
144 </Menu.Item>
145 </Menu.Group>
146 {!!banner && (
147 <>
148 <Menu.Divider />
149 <Menu.Group>
150 <Menu.Item
151 testID="changeBannerRemoveBtn"
152 label={_(`Remove Banner`)}
153 onPress={onRemoveBanner}>
154 <Menu.ItemText>
155 <Trans>Remove Banner</Trans>
156 </Menu.ItemText>
157 <Menu.ItemIcon icon={Trash} />
158 </Menu.Item>
159 </Menu.Group>
160 </>
161 )}
162 </Menu.Outer>
163 </Menu.Root>
164 </EventStopper>
165 ) : banner &&
166 !((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
167 <Image
168 testID="userBannerImage"
169 style={[
170 styles.bannerImage,
171 {backgroundColor: theme.palette.default.backgroundLight},
172 ]}
173 resizeMode="cover"
174 source={{uri: banner}}
175 blurRadius={moderation?.blur ? 100 : 0}
176 accessible={true}
177 accessibilityIgnoresInvertColors
178 />
179 ) : (
180 <View
181 testID="userBannerFallback"
182 style={[
183 styles.bannerImage,
184 type === 'labeler' ? styles.labelerBanner : t.atoms.bg_contrast_25,
185 ]}
186 />
187 )
188}
189
190const styles = StyleSheet.create({
191 editButtonContainer: {
192 position: 'absolute',
193 width: 24,
194 height: 24,
195 bottom: 8,
196 right: 24,
197 borderRadius: 12,
198 alignItems: 'center',
199 justifyContent: 'center',
200 backgroundColor: colors.gray5,
201 },
202 bannerImage: {
203 width: '100%',
204 height: 150,
205 },
206 labelerBanner: {
207 backgroundColor: tokens.color.temp_purple,
208 },
209})