forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {lazy, useState} from 'react'
2import {View} from 'react-native'
3// @ts-expect-error missing types
4import QRCode from 'react-native-qrcode-styled'
5import type ViewShot from 'react-native-view-shot'
6import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
7import {Trans} from '@lingui/react/macro'
8
9import {Logo} from '#/view/icons/Logo'
10import {Logotype} from '#/view/icons/Logotype'
11import {atoms as a, useTheme} from '#/alf'
12import {LinearGradientBackground} from '#/components/LinearGradientBackground'
13import {Text} from '#/components/Typography'
14import {IS_WEB} from '#/env'
15import * as bsky from '#/types/bsky'
16
17const LazyViewShot = lazy(
18 // @ts-expect-error dynamic import
19 () => import('react-native-view-shot/src/index'),
20)
21
22export function QrCode({
23 starterPack,
24 link,
25 ref,
26}: {
27 starterPack: AppBskyGraphDefs.StarterPackView
28 link: string
29 ref: React.Ref<ViewShot>
30}) {
31 const {record} = starterPack
32
33 if (
34 !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
35 record,
36 AppBskyGraphStarterpack.isRecord,
37 )
38 ) {
39 return null
40 }
41
42 return (
43 <LazyViewShot ref={ref}>
44 <LinearGradientBackground
45 style={[
46 {width: 300, minHeight: 390},
47 a.align_center,
48 a.px_sm,
49 a.py_xl,
50 a.rounded_sm,
51 a.justify_between,
52 a.gap_md,
53 ]}>
54 <View style={[a.gap_sm]}>
55 <Text
56 style={[
57 a.font_semi_bold,
58 a.text_3xl,
59 a.text_center,
60 {color: 'white'},
61 ]}>
62 {record.name}
63 </Text>
64 </View>
65 <View style={[a.gap_xl, a.align_center]}>
66 <Text
67 style={[
68 a.font_semi_bold,
69 a.text_center,
70 {color: 'white', fontSize: 18},
71 ]}>
72 <Trans>Join the conversation</Trans>
73 </Text>
74 <View style={[a.rounded_sm, a.overflow_hidden]}>
75 <QrCodeInner link={link} />
76 </View>
77
78 <Text
79 style={[
80 a.flex,
81 a.flex_row,
82 a.align_center,
83 a.font_semi_bold,
84 {color: 'white', fontSize: 18, gap: 6},
85 ]}>
86 <Trans>
87 on
88 <View style={[a.flex_row, a.align_center, {gap: 6}]}>
89 <Logo width={25} fill="white" />
90 <View style={[{marginTop: 3.5}]}>
91 <Logotype width={72} fill="white" />
92 </View>
93 </View>
94 </Trans>
95 </Text>
96 </View>
97 </LinearGradientBackground>
98 </LazyViewShot>
99 )
100}
101
102export function QrCodeInner({link}: {link: string}) {
103 const t = useTheme()
104 const [logoArea, setLogoArea] = useState<{
105 x: number
106 y: number
107 width: number
108 height: number
109 } | null>(null)
110
111 const onLogoAreaChange = (area: {
112 x: number
113 y: number
114 width: number
115 height: number
116 }) => {
117 setLogoArea(area)
118 }
119
120 return (
121 <View style={{position: 'relative'}}>
122 {/* An SVG version of the logo is placed on top of normal `QRCode` `logo` prop, since the PNG fails to load before the export completes on web. */}
123 {IS_WEB && logoArea && (
124 <View
125 style={{
126 position: 'absolute',
127 left: logoArea.x,
128 top: logoArea.y + 1,
129 zIndex: 1,
130 padding: 4,
131 }}>
132 <Logo width={logoArea.width - 14} height={logoArea.height - 14} />
133 </View>
134 )}
135 <QRCode
136 data={link}
137 style={[
138 a.rounded_sm,
139 {height: 225, width: 225, backgroundColor: '#f3f3f3'},
140 ]}
141 pieceSize={IS_WEB ? 8 : 6}
142 padding={20}
143 pieceBorderRadius={IS_WEB ? 4.5 : 3.5}
144 outerEyesOptions={{
145 topLeft: {
146 borderRadius: [12, 12, 0, 12],
147 color: t.palette.primary_500,
148 },
149 topRight: {
150 borderRadius: [12, 12, 12, 0],
151 color: t.palette.primary_500,
152 },
153 bottomLeft: {
154 borderRadius: [12, 0, 12, 12],
155 color: t.palette.primary_500,
156 },
157 }}
158 innerEyesOptions={{borderRadius: 3}}
159 logo={{
160 href: require('../../../assets/logo.png'),
161 ...(IS_WEB && {
162 onChange: onLogoAreaChange,
163 padding: 28,
164 }),
165 ...(!IS_WEB && {
166 padding: 2,
167 scale: 0.95,
168 }),
169 hidePieces: true,
170 }}
171 />
172 </View>
173 )
174}