mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {View} from 'react-native'
3import type ViewShot from 'react-native-view-shot'
4import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
5import {createAssetAsync} from 'expo-media-library'
6import * as Sharing from 'expo-sharing'
7import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
8import {msg, Trans} from '@lingui/macro'
9import {useLingui} from '@lingui/react'
10
11import {logEvent} from '#/lib/statsig/statsig'
12import {logger} from '#/logger'
13import {isNative, isWeb} from '#/platform/detection'
14import * as Toast from '#/view/com/util/Toast'
15import {atoms as a} from '#/alf'
16import {Button, ButtonText} from '#/components/Button'
17import * as Dialog from '#/components/Dialog'
18import {type DialogControlProps} from '#/components/Dialog'
19import {Loader} from '#/components/Loader'
20import {QrCode} from '#/components/StarterPack/QrCode'
21import * as bsky from '#/types/bsky'
22
23export function QrCodeDialog({
24 starterPack,
25 link,
26 control,
27}: {
28 starterPack: AppBskyGraphDefs.StarterPackView
29 link?: string
30 control: DialogControlProps
31}) {
32 const {_} = useLingui()
33 const [isProcessing, setIsProcessing] = React.useState(false)
34
35 const ref = React.useRef<ViewShot>(null)
36
37 const getCanvas = (base64: string): Promise<HTMLCanvasElement> => {
38 return new Promise(resolve => {
39 const image = new Image()
40 image.onload = () => {
41 const canvas = document.createElement('canvas')
42 canvas.width = image.width
43 canvas.height = image.height
44
45 const ctx = canvas.getContext('2d')
46 ctx?.drawImage(image, 0, 0)
47 resolve(canvas)
48 }
49 image.src = base64
50 })
51 }
52
53 const onSavePress = async () => {
54 ref.current?.capture?.().then(async (uri: string) => {
55 if (isNative) {
56 const res = await requestMediaLibraryPermissionsAsync()
57
58 if (!res.granted) {
59 Toast.show(
60 _(
61 msg`You must grant access to your photo library to save a QR code`,
62 ),
63 )
64 return
65 }
66
67 // Incase of a FS failure, don't crash the app
68 try {
69 await createAssetAsync(`file://${uri}`)
70 } catch (e: unknown) {
71 Toast.show(
72 _(msg`An error occurred while saving the QR code!`),
73 'xmark',
74 )
75 logger.error('Failed to save QR code', {error: e})
76 return
77 }
78 } else {
79 setIsProcessing(true)
80
81 if (
82 !bsky.validate(
83 starterPack.record,
84 AppBskyGraphStarterpack.validateRecord,
85 )
86 ) {
87 return
88 }
89
90 const canvas = await getCanvas(uri)
91 const imgHref = canvas
92 .toDataURL('image/png')
93 .replace('image/png', 'image/octet-stream')
94
95 const link = document.createElement('a')
96 link.setAttribute(
97 'download',
98 `${starterPack.record.name.replaceAll(' ', '_')}_Share_Card.png`,
99 )
100 link.setAttribute('href', imgHref)
101 link.click()
102 }
103
104 logEvent('starterPack:share', {
105 starterPack: starterPack.uri,
106 shareType: 'qrcode',
107 qrShareType: 'save',
108 })
109 setIsProcessing(false)
110 Toast.show(
111 isWeb
112 ? _(msg`QR code has been downloaded!`)
113 : _(msg`QR code saved to your camera roll!`),
114 )
115 control.close()
116 })
117 }
118
119 const onCopyPress = async () => {
120 setIsProcessing(true)
121 ref.current?.capture?.().then(async (uri: string) => {
122 const canvas = await getCanvas(uri)
123 // @ts-expect-error web only
124 canvas.toBlob((blob: Blob) => {
125 const item = new ClipboardItem({'image/png': blob})
126 navigator.clipboard.write([item])
127 })
128
129 logEvent('starterPack:share', {
130 starterPack: starterPack.uri,
131 shareType: 'qrcode',
132 qrShareType: 'copy',
133 })
134 Toast.show(_(msg`QR code copied to your clipboard!`))
135 setIsProcessing(false)
136 control.close()
137 })
138 }
139
140 const onSharePress = async () => {
141 ref.current?.capture?.().then(async (uri: string) => {
142 control.close(() => {
143 Sharing.shareAsync(uri, {mimeType: 'image/png', UTI: 'image/png'}).then(
144 () => {
145 logEvent('starterPack:share', {
146 starterPack: starterPack.uri,
147 shareType: 'qrcode',
148 qrShareType: 'share',
149 })
150 },
151 )
152 })
153 })
154 }
155
156 return (
157 <Dialog.Outer control={control}>
158 <Dialog.Handle />
159 <Dialog.ScrollableInner
160 label={_(msg`Create a QR code for a starter pack`)}>
161 <View style={[a.flex_1, a.align_center, a.gap_5xl]}>
162 <React.Suspense fallback={<Loading />}>
163 {!link ? (
164 <Loading />
165 ) : (
166 <>
167 <QrCode starterPack={starterPack} link={link} ref={ref} />
168 {isProcessing ? (
169 <View>
170 <Loader size="xl" />
171 </View>
172 ) : (
173 <View
174 style={[a.w_full, a.gap_md, isWeb && [a.flex_row_reverse]]}>
175 <Button
176 label={_(msg`Copy QR code`)}
177 variant="solid"
178 color="secondary"
179 size="small"
180 onPress={isWeb ? onCopyPress : onSharePress}>
181 <ButtonText>
182 {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>}
183 </ButtonText>
184 </Button>
185 <Button
186 label={_(msg`Save QR code`)}
187 variant="solid"
188 color="secondary"
189 size="small"
190 onPress={onSavePress}>
191 <ButtonText>
192 <Trans>Save</Trans>
193 </ButtonText>
194 </Button>
195 </View>
196 )}
197 </>
198 )}
199 </React.Suspense>
200 </View>
201 <Dialog.Close />
202 </Dialog.ScrollableInner>
203 </Dialog.Outer>
204 )
205}
206
207function Loading() {
208 return (
209 <View style={[a.align_center, a.p_xl]}>
210 <Loader size="xl" />
211 </View>
212 )
213}