@dubsdotapp/expo 0.2.77 → 0.2.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +656 -671
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +638 -646
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/ui/AuthGate.tsx +12 -52
- package/src/ui/AvatarEditor.tsx +159 -0
- package/src/ui/UserProfileSheet.tsx +18 -138
package/package.json
CHANGED
package/src/ui/AuthGate.tsx
CHANGED
|
@@ -18,33 +18,10 @@ import { usePushNotifications } from '../hooks/usePushNotifications';
|
|
|
18
18
|
import { useDubs } from '../provider';
|
|
19
19
|
import { AuthContext } from '../auth-context';
|
|
20
20
|
import { useDubsTheme } from './theme';
|
|
21
|
+
import { AvatarEditor, generateSeed, getAvatarUrl } from './AvatarEditor';
|
|
21
22
|
import type { AuthStatus } from '../types';
|
|
22
23
|
import type { DubsClient } from '../client';
|
|
23
24
|
|
|
24
|
-
// ── Avatar Helpers ──
|
|
25
|
-
|
|
26
|
-
const DICEBEAR_STYLES = [
|
|
27
|
-
'adventurer',
|
|
28
|
-
'avataaars',
|
|
29
|
-
'fun-emoji',
|
|
30
|
-
'bottts',
|
|
31
|
-
'big-smile',
|
|
32
|
-
'thumbs',
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
const BG_COLORS = [
|
|
36
|
-
'1a1a2e', 'f43f5e', 'f97316', 'eab308', '22c55e',
|
|
37
|
-
'3b82f6', '8b5cf6', 'ec4899', '06b6d4', '64748b',
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
function generateSeed(): string {
|
|
41
|
-
return Math.random().toString(36).slice(2, 10);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function getAvatarUrl(style: string, seed: string, bg = '1a1a2e', size = 256): string {
|
|
45
|
-
return `https://api.dicebear.com/9.x/${style}/png?seed=${seed}&backgroundColor=${bg}&size=${size}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
25
|
// ── Public Types ──
|
|
49
26
|
|
|
50
27
|
export interface RegistrationScreenProps {
|
|
@@ -389,33 +366,17 @@ function DefaultRegistrationScreen({
|
|
|
389
366
|
</View>
|
|
390
367
|
|
|
391
368
|
{showStyles && (
|
|
392
|
-
|
|
393
|
-
<
|
|
394
|
-
{
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
</ScrollView>
|
|
404
|
-
<Text style={[s.subtitle, { color: t.textMuted, marginTop: 8 }]}>Background Color</Text>
|
|
405
|
-
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={s.styleScroll}>
|
|
406
|
-
{BG_COLORS.map((color) => (
|
|
407
|
-
<TouchableOpacity
|
|
408
|
-
key={color}
|
|
409
|
-
onPress={() => setAvatarBg(color)}
|
|
410
|
-
style={[
|
|
411
|
-
s.colorSwatch,
|
|
412
|
-
{ backgroundColor: `#${color}` },
|
|
413
|
-
color === avatarBg && { borderColor: accent, borderWidth: 2.5 },
|
|
414
|
-
]}
|
|
415
|
-
/>
|
|
416
|
-
))}
|
|
417
|
-
</ScrollView>
|
|
418
|
-
</>
|
|
369
|
+
<View style={s.styleScroll}>
|
|
370
|
+
<AvatarEditor
|
|
371
|
+
style={avatarStyle}
|
|
372
|
+
seed={avatarSeed}
|
|
373
|
+
bg={avatarBg}
|
|
374
|
+
onStyleChange={setAvatarStyle}
|
|
375
|
+
onSeedChange={setAvatarSeed}
|
|
376
|
+
onBgChange={setAvatarBg}
|
|
377
|
+
accentColor={accent}
|
|
378
|
+
/>
|
|
379
|
+
</View>
|
|
419
380
|
)}
|
|
420
381
|
</View>
|
|
421
382
|
|
|
@@ -777,7 +738,6 @@ const s = StyleSheet.create({
|
|
|
777
738
|
styleScroll: { paddingHorizontal: 24, marginTop: 4 },
|
|
778
739
|
styleThumbWrap: { borderWidth: 2, borderRadius: 12, padding: 3, marginRight: 10 },
|
|
779
740
|
styleThumb: { width: 52, height: 52, borderRadius: 10, backgroundColor: '#E5E5EA' },
|
|
780
|
-
colorSwatch: { width: 32, height: 32, borderRadius: 16, borderWidth: 1.5, borderColor: 'rgba(255,255,255,0.15)', marginRight: 10 },
|
|
781
741
|
// Input
|
|
782
742
|
inputGroup: { paddingHorizontal: 24, gap: 6 },
|
|
783
743
|
inputLabel: { fontSize: 15, fontWeight: '600' },
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
Image,
|
|
7
|
+
ScrollView,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import { useDubsTheme } from './theme';
|
|
11
|
+
|
|
12
|
+
const DICEBEAR_STYLES = [
|
|
13
|
+
'adventurer',
|
|
14
|
+
'avataaars',
|
|
15
|
+
'fun-emoji',
|
|
16
|
+
'bottts',
|
|
17
|
+
'big-smile',
|
|
18
|
+
'thumbs',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const BG_COLORS = [
|
|
22
|
+
'1a1a2e', 'f43f5e', 'f97316', 'eab308', '22c55e',
|
|
23
|
+
'3b82f6', '8b5cf6', 'ec4899', '06b6d4', '64748b',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export function generateSeed(): string {
|
|
27
|
+
return Math.random().toString(36).slice(2, 10);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getAvatarUrl(style: string, seed: string, bg = '1a1a2e', size = 256): string {
|
|
31
|
+
return `https://api.dicebear.com/9.x/${style}/png?seed=${seed}&backgroundColor=${bg}&size=${size}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseAvatarUrl(url?: string | null): { style: string; seed: string; bg: string } {
|
|
35
|
+
if (!url) return { style: 'adventurer', seed: generateSeed(), bg: '1a1a2e' };
|
|
36
|
+
try {
|
|
37
|
+
const match = url.match(/\/\d+\.x\/([^/]+)\/(?:png|svg)\?seed=([^&]+)/);
|
|
38
|
+
if (match) {
|
|
39
|
+
const bgMatch = url.match(/backgroundColor=([^&]+)/);
|
|
40
|
+
return { style: match[1], seed: match[2], bg: bgMatch?.[1] || '1a1a2e' };
|
|
41
|
+
}
|
|
42
|
+
} catch {}
|
|
43
|
+
return { style: 'adventurer', seed: generateSeed(), bg: '1a1a2e' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AvatarEditorProps {
|
|
47
|
+
style: string;
|
|
48
|
+
seed: string;
|
|
49
|
+
bg: string;
|
|
50
|
+
onStyleChange: (style: string) => void;
|
|
51
|
+
onSeedChange: (seed: string) => void;
|
|
52
|
+
onBgChange: (bg: string) => void;
|
|
53
|
+
disabled?: boolean;
|
|
54
|
+
accentColor?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function AvatarEditor({
|
|
58
|
+
style: avatarStyle,
|
|
59
|
+
seed: avatarSeed,
|
|
60
|
+
bg: bgColor,
|
|
61
|
+
onStyleChange,
|
|
62
|
+
onSeedChange,
|
|
63
|
+
onBgChange,
|
|
64
|
+
disabled = false,
|
|
65
|
+
accentColor,
|
|
66
|
+
}: AvatarEditorProps) {
|
|
67
|
+
const t = useDubsTheme();
|
|
68
|
+
const accent = accentColor || t.accent;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<View style={styles.container}>
|
|
72
|
+
{/* Style picker */}
|
|
73
|
+
<ScrollView
|
|
74
|
+
horizontal
|
|
75
|
+
showsHorizontalScrollIndicator={false}
|
|
76
|
+
contentContainerStyle={styles.row}
|
|
77
|
+
>
|
|
78
|
+
{DICEBEAR_STYLES.map((st) => {
|
|
79
|
+
const isSelected = st === avatarStyle;
|
|
80
|
+
return (
|
|
81
|
+
<TouchableOpacity
|
|
82
|
+
key={st}
|
|
83
|
+
onPress={() => onStyleChange(st)}
|
|
84
|
+
activeOpacity={0.7}
|
|
85
|
+
disabled={disabled}
|
|
86
|
+
style={[
|
|
87
|
+
styles.styleTile,
|
|
88
|
+
{
|
|
89
|
+
borderColor: isSelected ? accent : t.border,
|
|
90
|
+
borderWidth: isSelected ? 2 : 1,
|
|
91
|
+
},
|
|
92
|
+
]}
|
|
93
|
+
>
|
|
94
|
+
<Image
|
|
95
|
+
source={{ uri: getAvatarUrl(st, avatarSeed, bgColor, 80) }}
|
|
96
|
+
style={styles.styleTileImage}
|
|
97
|
+
/>
|
|
98
|
+
</TouchableOpacity>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
</ScrollView>
|
|
102
|
+
|
|
103
|
+
{/* Background color picker */}
|
|
104
|
+
<Text style={[styles.label, { color: t.textSecondary }]}>Background Color</Text>
|
|
105
|
+
<ScrollView
|
|
106
|
+
horizontal
|
|
107
|
+
showsHorizontalScrollIndicator={false}
|
|
108
|
+
contentContainerStyle={styles.row}
|
|
109
|
+
>
|
|
110
|
+
{BG_COLORS.map((color) => {
|
|
111
|
+
const isSelected = color === bgColor;
|
|
112
|
+
return (
|
|
113
|
+
<TouchableOpacity
|
|
114
|
+
key={color}
|
|
115
|
+
onPress={() => onBgChange(color)}
|
|
116
|
+
activeOpacity={0.7}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
style={[
|
|
119
|
+
styles.colorSwatch,
|
|
120
|
+
{ backgroundColor: `#${color}` },
|
|
121
|
+
isSelected && { borderColor: accent, borderWidth: 2.5 },
|
|
122
|
+
]}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
})}
|
|
126
|
+
</ScrollView>
|
|
127
|
+
</View>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const styles = StyleSheet.create({
|
|
132
|
+
container: {
|
|
133
|
+
gap: 10,
|
|
134
|
+
},
|
|
135
|
+
row: {
|
|
136
|
+
gap: 10,
|
|
137
|
+
},
|
|
138
|
+
label: {
|
|
139
|
+
fontSize: 14,
|
|
140
|
+
fontWeight: '600',
|
|
141
|
+
},
|
|
142
|
+
styleTile: {
|
|
143
|
+
width: 72,
|
|
144
|
+
height: 72,
|
|
145
|
+
borderRadius: 16,
|
|
146
|
+
overflow: 'hidden',
|
|
147
|
+
},
|
|
148
|
+
styleTileImage: {
|
|
149
|
+
width: '100%',
|
|
150
|
+
height: '100%',
|
|
151
|
+
},
|
|
152
|
+
colorSwatch: {
|
|
153
|
+
width: 32,
|
|
154
|
+
height: 32,
|
|
155
|
+
borderRadius: 16,
|
|
156
|
+
borderWidth: 1.5,
|
|
157
|
+
borderColor: 'rgba(255,255,255,0.15)',
|
|
158
|
+
},
|
|
159
|
+
});
|
|
@@ -16,44 +16,7 @@ import {
|
|
|
16
16
|
import { useDubsTheme } from './theme';
|
|
17
17
|
import { useDubs } from '../provider';
|
|
18
18
|
import { usePushNotifications } from '../hooks/usePushNotifications';
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
// ── Avatar Helpers ──
|
|
22
|
-
|
|
23
|
-
const DICEBEAR_STYLES = [
|
|
24
|
-
'adventurer',
|
|
25
|
-
'avataaars',
|
|
26
|
-
'fun-emoji',
|
|
27
|
-
'bottts',
|
|
28
|
-
'big-smile',
|
|
29
|
-
'thumbs',
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
const BG_COLORS = [
|
|
33
|
-
'1a1a2e', 'f43f5e', 'f97316', 'eab308', '22c55e',
|
|
34
|
-
'3b82f6', '8b5cf6', 'ec4899', '06b6d4', '64748b',
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
function generateSeed(): string {
|
|
38
|
-
return Math.random().toString(36).slice(2, 10);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getAvatarUrl(style: string, seed: string, bg = '1a1a2e', size = 256): string {
|
|
42
|
-
return `https://api.dicebear.com/9.x/${style}/png?seed=${seed}&backgroundColor=${bg}&size=${size}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Parse style, seed, and backgroundColor from a DiceBear URL, or return defaults. */
|
|
46
|
-
function parseAvatarUrl(url?: string | null): { style: string; seed: string; bg: string } {
|
|
47
|
-
if (!url) return { style: 'adventurer', seed: generateSeed(), bg: '1a1a2e' };
|
|
48
|
-
try {
|
|
49
|
-
const match = url.match(/\/\d+\.x\/([^/]+)\/(?:png|svg)\?seed=([^&]+)/);
|
|
50
|
-
if (match) {
|
|
51
|
-
const bgMatch = url.match(/backgroundColor=([^&]+)/);
|
|
52
|
-
return { style: match[1], seed: match[2], bg: bgMatch?.[1] || '1a1a2e' };
|
|
53
|
-
}
|
|
54
|
-
} catch {}
|
|
55
|
-
return { style: 'adventurer', seed: generateSeed(), bg: '1a1a2e' };
|
|
56
|
-
}
|
|
19
|
+
import { AvatarEditor, getAvatarUrl, generateSeed, parseAvatarUrl } from './AvatarEditor';
|
|
57
20
|
|
|
58
21
|
function truncateAddress(address: string, chars = 4): string {
|
|
59
22
|
if (address.length <= chars * 2 + 3) return address;
|
|
@@ -132,13 +95,10 @@ export function UserProfileSheet({
|
|
|
132
95
|
}
|
|
133
96
|
}, [client, onAvatarUpdated]);
|
|
134
97
|
|
|
135
|
-
const
|
|
136
|
-
(style
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
},
|
|
140
|
-
[avatarSeed, bgColor, saveAvatar],
|
|
141
|
-
);
|
|
98
|
+
const handleStyleChange = useCallback((style: string) => {
|
|
99
|
+
setAvatarStyle(style);
|
|
100
|
+
saveAvatar(getAvatarUrl(style, avatarSeed, bgColor));
|
|
101
|
+
}, [avatarSeed, bgColor, saveAvatar]);
|
|
142
102
|
|
|
143
103
|
const handleShuffle = useCallback(() => {
|
|
144
104
|
const newSeed = generateSeed();
|
|
@@ -146,13 +106,10 @@ export function UserProfileSheet({
|
|
|
146
106
|
saveAvatar(getAvatarUrl(avatarStyle, newSeed, bgColor));
|
|
147
107
|
}, [avatarStyle, bgColor, saveAvatar]);
|
|
148
108
|
|
|
149
|
-
const
|
|
150
|
-
(color
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
},
|
|
154
|
-
[avatarStyle, avatarSeed, saveAvatar],
|
|
155
|
-
);
|
|
109
|
+
const handleBgChange = useCallback((color: string) => {
|
|
110
|
+
setBgColor(color);
|
|
111
|
+
saveAvatar(getAvatarUrl(avatarStyle, avatarSeed, color));
|
|
112
|
+
}, [avatarStyle, avatarSeed, saveAvatar]);
|
|
156
113
|
|
|
157
114
|
return (
|
|
158
115
|
<Modal
|
|
@@ -226,62 +183,15 @@ export function UserProfileSheet({
|
|
|
226
183
|
<Text style={[styles.shuffleText, { color: t.accent }]}>Shuffle</Text>
|
|
227
184
|
</TouchableOpacity>
|
|
228
185
|
</View>
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
key={style}
|
|
239
|
-
onPress={() => handleSelectStyle(style)}
|
|
240
|
-
activeOpacity={0.7}
|
|
241
|
-
disabled={saving}
|
|
242
|
-
style={[
|
|
243
|
-
styles.styleTile,
|
|
244
|
-
{
|
|
245
|
-
borderColor: isSelected ? t.accent : t.border,
|
|
246
|
-
borderWidth: isSelected ? 2 : 1,
|
|
247
|
-
},
|
|
248
|
-
]}
|
|
249
|
-
>
|
|
250
|
-
<Image
|
|
251
|
-
source={{ uri: getAvatarUrl(style, avatarSeed, bgColor, 80) }}
|
|
252
|
-
style={styles.styleTileImage}
|
|
253
|
-
/>
|
|
254
|
-
</TouchableOpacity>
|
|
255
|
-
);
|
|
256
|
-
})}
|
|
257
|
-
</ScrollView>
|
|
258
|
-
|
|
259
|
-
{/* Background color picker */}
|
|
260
|
-
<Text style={[styles.sectionLabel, { color: t.textSecondary, marginTop: 4 }]}>
|
|
261
|
-
Background Color
|
|
262
|
-
</Text>
|
|
263
|
-
<ScrollView
|
|
264
|
-
horizontal
|
|
265
|
-
showsHorizontalScrollIndicator={false}
|
|
266
|
-
contentContainerStyle={styles.colorPickerContent}
|
|
267
|
-
>
|
|
268
|
-
{BG_COLORS.map((color) => {
|
|
269
|
-
const isSelected = color === bgColor;
|
|
270
|
-
return (
|
|
271
|
-
<TouchableOpacity
|
|
272
|
-
key={color}
|
|
273
|
-
onPress={() => handleSelectBgColor(color)}
|
|
274
|
-
activeOpacity={0.7}
|
|
275
|
-
disabled={saving}
|
|
276
|
-
style={[
|
|
277
|
-
styles.colorSwatch,
|
|
278
|
-
{ backgroundColor: `#${color}` },
|
|
279
|
-
isSelected && { borderColor: t.accent, borderWidth: 2.5 },
|
|
280
|
-
]}
|
|
281
|
-
/>
|
|
282
|
-
);
|
|
283
|
-
})}
|
|
284
|
-
</ScrollView>
|
|
186
|
+
<AvatarEditor
|
|
187
|
+
style={avatarStyle}
|
|
188
|
+
seed={avatarSeed}
|
|
189
|
+
bg={bgColor}
|
|
190
|
+
onStyleChange={handleStyleChange}
|
|
191
|
+
onSeedChange={setAvatarSeed}
|
|
192
|
+
onBgChange={handleBgChange}
|
|
193
|
+
disabled={saving}
|
|
194
|
+
/>
|
|
285
195
|
</View>
|
|
286
196
|
|
|
287
197
|
{/* Error */}
|
|
@@ -400,7 +310,6 @@ const styles = StyleSheet.create({
|
|
|
400
310
|
scrollContentInner: {
|
|
401
311
|
paddingBottom: 8,
|
|
402
312
|
},
|
|
403
|
-
// Avatar
|
|
404
313
|
avatarSection: {
|
|
405
314
|
alignItems: 'center',
|
|
406
315
|
paddingTop: 8,
|
|
@@ -417,7 +326,6 @@ const styles = StyleSheet.create({
|
|
|
417
326
|
avatar: {
|
|
418
327
|
width: '100%',
|
|
419
328
|
height: '100%',
|
|
420
|
-
backgroundColor: '#1a1a2e',
|
|
421
329
|
},
|
|
422
330
|
avatarLoading: {
|
|
423
331
|
...StyleSheet.absoluteFillObject,
|
|
@@ -433,7 +341,6 @@ const styles = StyleSheet.create({
|
|
|
433
341
|
fontSize: 13,
|
|
434
342
|
fontFamily: 'monospace',
|
|
435
343
|
},
|
|
436
|
-
// Change Avatar
|
|
437
344
|
section: {
|
|
438
345
|
marginBottom: 20,
|
|
439
346
|
gap: 12,
|
|
@@ -457,31 +364,6 @@ const styles = StyleSheet.create({
|
|
|
457
364
|
fontSize: 13,
|
|
458
365
|
fontWeight: '600',
|
|
459
366
|
},
|
|
460
|
-
stylePickerContent: {
|
|
461
|
-
gap: 10,
|
|
462
|
-
},
|
|
463
|
-
colorPickerContent: {
|
|
464
|
-
gap: 8,
|
|
465
|
-
},
|
|
466
|
-
colorSwatch: {
|
|
467
|
-
width: 32,
|
|
468
|
-
height: 32,
|
|
469
|
-
borderRadius: 16,
|
|
470
|
-
borderWidth: 1.5,
|
|
471
|
-
borderColor: 'rgba(255,255,255,0.15)',
|
|
472
|
-
},
|
|
473
|
-
styleTile: {
|
|
474
|
-
width: 72,
|
|
475
|
-
height: 72,
|
|
476
|
-
borderRadius: 16,
|
|
477
|
-
overflow: 'hidden',
|
|
478
|
-
},
|
|
479
|
-
styleTileImage: {
|
|
480
|
-
width: '100%',
|
|
481
|
-
height: '100%',
|
|
482
|
-
backgroundColor: '#1a1a2e',
|
|
483
|
-
},
|
|
484
|
-
// Error
|
|
485
367
|
errorBox: {
|
|
486
368
|
marginBottom: 16,
|
|
487
369
|
borderRadius: 12,
|
|
@@ -492,7 +374,6 @@ const styles = StyleSheet.create({
|
|
|
492
374
|
fontSize: 13,
|
|
493
375
|
fontWeight: '500',
|
|
494
376
|
},
|
|
495
|
-
// Push Notifications
|
|
496
377
|
notifRow: {
|
|
497
378
|
flexDirection: 'row',
|
|
498
379
|
alignItems: 'center',
|
|
@@ -523,7 +404,6 @@ const styles = StyleSheet.create({
|
|
|
523
404
|
fontSize: 14,
|
|
524
405
|
fontWeight: '700',
|
|
525
406
|
},
|
|
526
|
-
// Disconnect
|
|
527
407
|
disconnectButton: {
|
|
528
408
|
height: 52,
|
|
529
409
|
borderRadius: 16,
|