mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at samuel/exp-cli 213 lines 6.3 kB view raw
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}