tangled
alpha
login
or
join now
robinwobin.dev
/
witchsky.app
forked from
jollywhoppers.com/witchsky.app
0
fork
atom
Bluesky app fork with some witchin' additions 💫
0
fork
atom
overview
issues
pulls
pipelines
Progress on desktoip
Eric Bailey
2 years ago
3c8b3b47
76c584d9
+331
-46
3 changed files
expand all
collapse all
unified
split
src
alf
index.tsx
components
dialogs
nudges
TenMillion.tsx
view
icons
Logomark.tsx
+15
-3
src/alf/index.tsx
···
18
18
export const Context = React.createContext<{
19
19
themeName: ThemeName
20
20
theme: Theme
21
21
+
themes: ReturnType<typeof createThemes>
21
22
}>({
22
23
themeName: 'light',
23
24
theme: defaultTheme,
25
25
+
themes: createThemes({
26
26
+
hues: {
27
27
+
primary: BLUE_HUE,
28
28
+
negative: RED_HUE,
29
29
+
positive: GREEN_HUE,
30
30
+
},
31
31
+
}),
24
32
})
25
33
26
34
export function ThemeProvider({
···
42
50
<Context.Provider
43
51
value={React.useMemo(
44
52
() => ({
53
53
+
themes,
45
54
themeName: themeName,
46
55
theme: theme,
47
56
}),
48
48
-
[theme, themeName],
57
57
+
[theme, themeName, themes],
49
58
)}>
50
59
{children}
51
60
</Context.Provider>
52
61
)
53
62
}
54
63
55
55
-
export function useTheme() {
56
56
-
return React.useContext(Context).theme
64
64
+
export function useTheme(theme?: ThemeName) {
65
65
+
const ctx = React.useContext(Context)
66
66
+
return React.useMemo(() => {
67
67
+
return theme ? ctx.themes[theme] : ctx.theme
68
68
+
}, [theme, ctx])
57
69
}
58
70
59
71
export function useBreakpoints() {
+287
-43
src/components/dialogs/nudges/TenMillion.tsx
···
1
1
import React from 'react'
2
2
-
import {useLingui} from '@lingui/react'
3
3
-
import {msg} from '@lingui/macro'
4
2
import {View} from 'react-native'
5
3
import ViewShot from 'react-native-view-shot'
4
4
+
import {moderateProfile} from '@atproto/api'
5
5
+
import {msg, Trans} from '@lingui/macro'
6
6
+
import {useLingui} from '@lingui/react'
6
7
7
7
-
import {atoms as a, useBreakpoints, tokens} from '#/alf'
8
8
+
import {sanitizeDisplayName} from '#/lib/strings/display-names'
9
9
+
import {sanitizeHandle} from '#/lib/strings/handles'
10
10
+
import {isNative} from '#/platform/detection'
11
11
+
import {useModerationOpts} from '#/state/preferences/moderation-opts'
12
12
+
import {useProfileQuery} from '#/state/queries/profile'
13
13
+
import {useSession} from '#/state/session'
14
14
+
import {useComposerControls} from 'state/shell'
15
15
+
import {formatCount} from '#/view/com/util/numeric/format'
16
16
+
import {UserAvatar} from '#/view/com/util/UserAvatar'
17
17
+
import {Logomark} from '#/view/icons/Logomark'
18
18
+
import {
19
19
+
atoms as a,
20
20
+
ThemeProvider,
21
21
+
tokens,
22
22
+
useBreakpoints,
23
23
+
useTheme,
24
24
+
} from '#/alf'
25
25
+
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
8
26
import * as Dialog from '#/components/Dialog'
27
27
+
import {useContext} from '#/components/dialogs/nudges'
28
28
+
import {Divider} from '#/components/Divider'
29
29
+
import {GradientFill} from '#/components/GradientFill'
30
30
+
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
31
31
+
import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
32
32
+
import {Loader} from '#/components/Loader'
9
33
import {Text} from '#/components/Typography'
10
10
-
import {GradientFill} from '#/components/GradientFill'
11
11
-
import {Button, ButtonText} from '#/components/Button'
12
12
-
import {useComposerControls} from 'state/shell'
34
34
+
35
35
+
const RATIO = 8 / 10
36
36
+
const WIDTH = 2000
37
37
+
const HEIGHT = WIDTH * RATIO
13
38
14
14
-
import {useContext} from '#/components/dialogs/nudges'
39
39
+
function getFontSize(count: number) {
40
40
+
const length = count.toString().length
41
41
+
if (length < 7) {
42
42
+
return 80
43
43
+
} else if (length < 5) {
44
44
+
return 100
45
45
+
} else {
46
46
+
return 70
47
47
+
}
48
48
+
}
15
49
16
50
export function TenMillion() {
17
17
-
const {_} = useLingui()
51
51
+
const t = useTheme()
52
52
+
const lightTheme = useTheme('light')
53
53
+
const {_, i18n} = useLingui()
18
54
const {controls} = useContext()
19
55
const {gtMobile} = useBreakpoints()
20
56
const {openComposer} = useComposerControls()
57
57
+
const imageRef = React.useRef<ViewShot>(null)
58
58
+
const {currentAccount} = useSession()
59
59
+
const {isLoading: isProfileLoading, data: profile} = useProfileQuery({
60
60
+
did: currentAccount!.did,
61
61
+
}) // TODO PWI
62
62
+
const moderationOpts = useModerationOpts()
63
63
+
const moderation = React.useMemo(() => {
64
64
+
return profile && moderationOpts
65
65
+
? moderateProfile(profile, moderationOpts)
66
66
+
: undefined
67
67
+
}, [profile, moderationOpts])
21
68
22
22
-
const imageRef = React.useRef<ViewShot>(null)
69
69
+
const isLoading = isProfileLoading || !moderation || !profile
70
70
+
71
71
+
const userNumber = 56738
23
72
24
73
const share = () => {
25
74
if (imageRef.current && imageRef.current.capture) {
···
31
80
imageUris: [
32
81
{
33
82
uri,
34
34
-
width: 1000,
35
35
-
height: 1000,
83
83
+
width: WIDTH,
84
84
+
height: HEIGHT,
36
85
},
37
86
],
38
87
})
···
48
97
49
98
<Dialog.ScrollableInner
50
99
label={_(msg`Ten Million`)}
51
51
-
style={
52
52
-
[
53
53
-
// gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full,
54
54
-
]
55
55
-
}>
100
100
+
style={[
101
101
+
{
102
102
+
padding: 0,
103
103
+
},
104
104
+
// gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full,
105
105
+
]}>
56
106
<View
57
107
style={[
58
58
-
a.relative,
59
59
-
a.w_full,
108
108
+
a.rounded_md,
60
109
a.overflow_hidden,
61
61
-
{
62
62
-
paddingTop: '100%',
110
110
+
isNative && {
111
111
+
borderTopLeftRadius: 40,
112
112
+
borderTopRightRadius: 40,
63
113
},
64
114
]}>
65
65
-
<ViewShot
66
66
-
ref={imageRef}
67
67
-
options={{width: 2e3, height: 2e3}}
68
68
-
style={[a.absolute, a.inset_0]}>
115
115
+
<ThemeProvider theme="light">
69
116
<View
70
117
style={[
71
71
-
a.absolute,
72
72
-
a.inset_0,
73
73
-
a.align_center,
74
74
-
a.justify_center,
118
118
+
a.relative,
119
119
+
a.w_full,
120
120
+
a.overflow_hidden,
75
121
{
76
76
-
top: -1,
77
77
-
bottom: -1,
78
78
-
left: -1,
79
79
-
right: -1,
122
122
+
paddingTop: '80%',
80
123
},
81
124
]}>
82
82
-
<GradientFill gradient={tokens.gradients.midnight} />
125
125
+
<ViewShot
126
126
+
ref={imageRef}
127
127
+
options={{width: WIDTH, height: HEIGHT}}
128
128
+
style={[a.absolute, a.inset_0]}>
129
129
+
<View
130
130
+
style={[
131
131
+
a.absolute,
132
132
+
a.inset_0,
133
133
+
a.align_center,
134
134
+
a.justify_center,
135
135
+
{
136
136
+
top: -1,
137
137
+
bottom: -1,
138
138
+
left: -1,
139
139
+
right: -1,
140
140
+
paddingVertical: 32,
141
141
+
paddingHorizontal: 48,
142
142
+
},
143
143
+
]}>
144
144
+
<GradientFill gradient={tokens.gradients.bonfire} />
83
145
84
84
-
<Text>10 milly, babyyy</Text>
146
146
+
{isLoading ? (
147
147
+
<Loader size="xl" fill="white" />
148
148
+
) : (
149
149
+
<View
150
150
+
style={[
151
151
+
a.flex_1,
152
152
+
a.w_full,
153
153
+
a.align_center,
154
154
+
a.justify_center,
155
155
+
a.rounded_md,
156
156
+
{
157
157
+
backgroundColor: 'white',
158
158
+
shadowRadius: 32,
159
159
+
shadowOpacity: 0.1,
160
160
+
elevation: 24,
161
161
+
shadowColor: tokens.gradients.bonfire.values[0][1],
162
162
+
},
163
163
+
]}>
164
164
+
<View
165
165
+
style={[
166
166
+
a.absolute,
167
167
+
a.px_xl,
168
168
+
a.py_xl,
169
169
+
{
170
170
+
top: 0,
171
171
+
left: 0,
172
172
+
},
173
173
+
]}>
174
174
+
<Logomark fill={t.palette.primary_500} width={36} />
175
175
+
</View>
176
176
+
177
177
+
{/* Centered content */}
178
178
+
<View
179
179
+
style={[
180
180
+
{
181
181
+
paddingBottom: 48,
182
182
+
},
183
183
+
]}>
184
184
+
<Text
185
185
+
style={[
186
186
+
a.text_md,
187
187
+
a.font_bold,
188
188
+
a.text_center,
189
189
+
a.pb_xs,
190
190
+
lightTheme.atoms.text_contrast_medium,
191
191
+
]}>
192
192
+
<Trans>
193
193
+
Celebrating {formatCount(i18n, 10000000)} users
194
194
+
</Trans>{' '}
195
195
+
🎉
196
196
+
</Text>
197
197
+
<Text
198
198
+
style={[
199
199
+
a.relative,
200
200
+
a.text_center,
201
201
+
{
202
202
+
fontStyle: 'italic',
203
203
+
fontSize: getFontSize(userNumber),
204
204
+
fontWeight: '900',
205
205
+
letterSpacing: -2,
206
206
+
},
207
207
+
]}>
208
208
+
<Text
209
209
+
style={[
210
210
+
a.absolute,
211
211
+
{
212
212
+
color: t.palette.primary_500,
213
213
+
fontSize: 32,
214
214
+
left: -18,
215
215
+
top: 8,
216
216
+
},
217
217
+
]}>
218
218
+
#
219
219
+
</Text>
220
220
+
{i18n.number(userNumber)}
221
221
+
</Text>
222
222
+
</View>
223
223
+
{/* End centered content */}
224
224
+
225
225
+
<View
226
226
+
style={[
227
227
+
a.absolute,
228
228
+
a.px_xl,
229
229
+
a.py_xl,
230
230
+
{
231
231
+
bottom: 0,
232
232
+
left: 0,
233
233
+
right: 0,
234
234
+
},
235
235
+
]}>
236
236
+
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
237
237
+
<UserAvatar
238
238
+
size={36}
239
239
+
avatar={profile.avatar}
240
240
+
moderation={moderation.ui('avatar')}
241
241
+
/>
242
242
+
<View style={[a.gap_2xs, a.flex_1]}>
243
243
+
<Text style={[a.text_sm, a.font_bold]}>
244
244
+
{sanitizeDisplayName(
245
245
+
profile.displayName ||
246
246
+
sanitizeHandle(profile.handle),
247
247
+
moderation.ui('displayName'),
248
248
+
)}
249
249
+
</Text>
250
250
+
<View style={[a.flex_row, a.justify_between]}>
251
251
+
<Text
252
252
+
style={[
253
253
+
a.text_sm,
254
254
+
a.font_semibold,
255
255
+
lightTheme.atoms.text_contrast_medium,
256
256
+
]}>
257
257
+
{sanitizeHandle(profile.handle, '@')}
258
258
+
</Text>
259
259
+
260
260
+
{profile.createdAt && (
261
261
+
<Text
262
262
+
style={[
263
263
+
a.text_sm,
264
264
+
a.font_semibold,
265
265
+
lightTheme.atoms.text_contrast_low,
266
266
+
]}>
267
267
+
{i18n.date(profile.createdAt, {
268
268
+
dateStyle: 'long',
269
269
+
})}
270
270
+
</Text>
271
271
+
)}
272
272
+
</View>
273
273
+
</View>
274
274
+
</View>
275
275
+
</View>
276
276
+
</View>
277
277
+
)}
278
278
+
</View>
279
279
+
</ViewShot>
85
280
</View>
86
86
-
</ViewShot>
281
281
+
</ThemeProvider>
282
282
+
283
283
+
<View style={[gtMobile ? a.p_2xl : a.p_xl]}>
284
284
+
<Text
285
285
+
style={[
286
286
+
a.text_5xl,
287
287
+
a.pb_lg,
288
288
+
{
289
289
+
fontWeight: '900',
290
290
+
},
291
291
+
]}>
292
292
+
You're part of the next wave of the internet.
293
293
+
</Text>
294
294
+
295
295
+
<Text style={[a.leading_snug, a.text_lg, a.pb_xl]}>
296
296
+
Online culture is too important to be controlled by a few
297
297
+
corporations.{' '}
298
298
+
<Text style={[a.leading_snug, a.text_lg, a.italic]}>
299
299
+
We’re dedicated to building an open foundation for the social
300
300
+
internet so that we can all shape its future.
301
301
+
</Text>
302
302
+
</Text>
303
303
+
304
304
+
<Divider />
305
305
+
306
306
+
<View
307
307
+
style={[
308
308
+
a.flex_row,
309
309
+
a.align_center,
310
310
+
a.justify_end,
311
311
+
a.gap_md,
312
312
+
a.pt_xl,
313
313
+
]}>
314
314
+
<Text style={[a.text_md, a.italic, t.atoms.text_contrast_medium]}>
315
315
+
Brag a little ;)
316
316
+
</Text>
317
317
+
318
318
+
<Button
319
319
+
label={_(msg`Share image externally`)}
320
320
+
size="large"
321
321
+
variant="solid"
322
322
+
color="secondary"
323
323
+
shape="square"
324
324
+
onPress={share}>
325
325
+
<ButtonIcon icon={Share} />
326
326
+
</Button>
327
327
+
<Button
328
328
+
label={_(msg`Share image in post`)}
329
329
+
size="large"
330
330
+
variant="solid"
331
331
+
color="primary"
332
332
+
onPress={share}>
333
333
+
<ButtonText>{_(msg`Share post`)}</ButtonText>
334
334
+
<ButtonIcon position="right" icon={ImageIcon} />
335
335
+
</Button>
336
336
+
</View>
337
337
+
</View>
87
338
</View>
88
339
89
89
-
<Button
90
90
-
label={_(msg`Generate`)}
91
91
-
size="medium"
92
92
-
variant="solid"
93
93
-
color="primary"
94
94
-
onPress={share}>
95
95
-
<ButtonText>{_(msg`Generate`)}</ButtonText>
96
96
-
</Button>
340
340
+
<Dialog.Close />
97
341
</Dialog.ScrollableInner>
98
342
</Dialog.Outer>
99
343
)
+29
src/view/icons/Logomark.tsx
···
1
1
+
import React from 'react'
2
2
+
import Svg, {Path, PathProps, SvgProps} from 'react-native-svg'
3
3
+
4
4
+
import {usePalette} from '#/lib/hooks/usePalette'
5
5
+
6
6
+
const ratio = 54 / 61
7
7
+
8
8
+
export function Logomark({
9
9
+
fill,
10
10
+
...rest
11
11
+
}: {fill?: PathProps['fill']} & SvgProps) {
12
12
+
const pal = usePalette('default')
13
13
+
// @ts-ignore it's fiiiiine
14
14
+
const size = parseInt(rest.width || 32)
15
15
+
16
16
+
return (
17
17
+
<Svg
18
18
+
fill="none"
19
19
+
viewBox="0 0 61 54"
20
20
+
{...rest}
21
21
+
width={size}
22
22
+
height={Number(size) * ratio}>
23
23
+
<Path
24
24
+
fill={fill || pal.text.color}
25
25
+
d="M13.223 3.602C20.215 8.832 27.738 19.439 30.5 25.13c2.762-5.691 10.284-16.297 17.278-21.528C52.824-.172 61-3.093 61 6.2c0 1.856-1.068 15.59-1.694 17.82-2.178 7.752-10.112 9.73-17.17 8.532 12.337 2.092 15.475 9.021 8.697 15.95-12.872 13.159-18.5-3.302-19.943-7.52-.264-.773-.388-1.135-.39-.827-.002-.308-.126.054-.39.827-1.442 4.218-7.071 20.679-19.943 7.52-6.778-6.929-3.64-13.858 8.697-15.95-7.058 1.197-14.992-.78-17.17-8.532C1.068 21.79 0 8.056 0 6.2 0-3.093 8.176-.172 13.223 3.602Z"
26
26
+
/>
27
27
+
</Svg>
28
28
+
)
29
29
+
}