@dubsdotapp/expo 0.2.76 → 0.2.77
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 +122 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +122 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/ui/AuthGate.tsx +37 -14
- package/src/ui/UserProfileSheet.tsx +80 -33
package/package.json
CHANGED
package/src/ui/AuthGate.tsx
CHANGED
|
@@ -32,12 +32,17 @@ const DICEBEAR_STYLES = [
|
|
|
32
32
|
'thumbs',
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
+
const BG_COLORS = [
|
|
36
|
+
'1a1a2e', 'f43f5e', 'f97316', 'eab308', '22c55e',
|
|
37
|
+
'3b82f6', '8b5cf6', 'ec4899', '06b6d4', '64748b',
|
|
38
|
+
];
|
|
39
|
+
|
|
35
40
|
function generateSeed(): string {
|
|
36
41
|
return Math.random().toString(36).slice(2, 10);
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
function getAvatarUrl(style: string, seed: string, size = 256): string {
|
|
40
|
-
return `https://api.dicebear.com/9.x/${style}/png?seed=${seed}&size=${size}`;
|
|
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}`;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
// ── Public Types ──
|
|
@@ -295,6 +300,7 @@ function DefaultRegistrationScreen({
|
|
|
295
300
|
const [step, setStep] = useState(0);
|
|
296
301
|
const [avatarSeed, setAvatarSeed] = useState(generateSeed);
|
|
297
302
|
const [avatarStyle, setAvatarStyle] = useState('adventurer');
|
|
303
|
+
const [avatarBg, setAvatarBg] = useState('1a1a2e');
|
|
298
304
|
const [showStyles, setShowStyles] = useState(false);
|
|
299
305
|
const [username, setUsername] = useState('');
|
|
300
306
|
const [referralCode, setReferralCode] = useState('');
|
|
@@ -306,7 +312,7 @@ function DefaultRegistrationScreen({
|
|
|
306
312
|
const fadeAnim = useRef(new Animated.Value(1)).current;
|
|
307
313
|
const slideAnim = useRef(new Animated.Value(0)).current;
|
|
308
314
|
|
|
309
|
-
const avatarUrl = getAvatarUrl(avatarStyle, avatarSeed);
|
|
315
|
+
const avatarUrl = getAvatarUrl(avatarStyle, avatarSeed, avatarBg);
|
|
310
316
|
|
|
311
317
|
// Debounced username check
|
|
312
318
|
useEffect(() => {
|
|
@@ -383,17 +389,33 @@ function DefaultRegistrationScreen({
|
|
|
383
389
|
</View>
|
|
384
390
|
|
|
385
391
|
{showStyles && (
|
|
386
|
-
|
|
387
|
-
{
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
392
|
+
<>
|
|
393
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={s.styleScroll}>
|
|
394
|
+
{DICEBEAR_STYLES.map((st) => (
|
|
395
|
+
<TouchableOpacity
|
|
396
|
+
key={st}
|
|
397
|
+
onPress={() => setAvatarStyle(st)}
|
|
398
|
+
style={[s.styleThumbWrap, { borderColor: st === avatarStyle ? accent : t.border }]}
|
|
399
|
+
>
|
|
400
|
+
<Image source={{ uri: getAvatarUrl(st, avatarSeed, avatarBg, 80) }} style={s.styleThumb} />
|
|
401
|
+
</TouchableOpacity>
|
|
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
|
+
</>
|
|
397
419
|
)}
|
|
398
420
|
</View>
|
|
399
421
|
|
|
@@ -755,6 +777,7 @@ const s = StyleSheet.create({
|
|
|
755
777
|
styleScroll: { paddingHorizontal: 24, marginTop: 4 },
|
|
756
778
|
styleThumbWrap: { borderWidth: 2, borderRadius: 12, padding: 3, marginRight: 10 },
|
|
757
779
|
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 },
|
|
758
781
|
// Input
|
|
759
782
|
inputGroup: { paddingHorizontal: 24, gap: 6 },
|
|
760
783
|
inputLabel: { fontSize: 15, fontWeight: '600' },
|
|
@@ -29,23 +29,30 @@ const DICEBEAR_STYLES = [
|
|
|
29
29
|
'thumbs',
|
|
30
30
|
];
|
|
31
31
|
|
|
32
|
+
const BG_COLORS = [
|
|
33
|
+
'1a1a2e', 'f43f5e', 'f97316', 'eab308', '22c55e',
|
|
34
|
+
'3b82f6', '8b5cf6', 'ec4899', '06b6d4', '64748b',
|
|
35
|
+
];
|
|
36
|
+
|
|
32
37
|
function generateSeed(): string {
|
|
33
38
|
return Math.random().toString(36).slice(2, 10);
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
function getAvatarUrl(style: string, seed: string, size = 256): string {
|
|
37
|
-
return `https://api.dicebear.com/9.x/${style}/png?seed=${seed}&size=${size}`;
|
|
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}`;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
/** Parse style and
|
|
41
|
-
function parseAvatarUrl(url?: string | null): { style: string; seed: string } {
|
|
42
|
-
if (!url) return { style: 'adventurer', seed: generateSeed() };
|
|
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' };
|
|
43
48
|
try {
|
|
44
|
-
// Match both png and svg formats, any API version (7.x, 9.x, etc.)
|
|
45
49
|
const match = url.match(/\/\d+\.x\/([^/]+)\/(?:png|svg)\?seed=([^&]+)/);
|
|
46
|
-
if (match)
|
|
50
|
+
if (match) {
|
|
51
|
+
const bgMatch = url.match(/backgroundColor=([^&]+)/);
|
|
52
|
+
return { style: match[1], seed: match[2], bg: bgMatch?.[1] || '1a1a2e' };
|
|
53
|
+
}
|
|
47
54
|
} catch {}
|
|
48
|
-
return { style: 'adventurer', seed: generateSeed() };
|
|
55
|
+
return { style: 'adventurer', seed: generateSeed(), bg: '1a1a2e' };
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
function truncateAddress(address: string, chars = 4): string {
|
|
@@ -84,6 +91,7 @@ export function UserProfileSheet({
|
|
|
84
91
|
const parsed = useMemo(() => parseAvatarUrl(user.avatar), [user.avatar]);
|
|
85
92
|
const [avatarStyle, setAvatarStyle] = useState(parsed.style);
|
|
86
93
|
const [avatarSeed, setAvatarSeed] = useState(parsed.seed);
|
|
94
|
+
const [bgColor, setBgColor] = useState(parsed.bg);
|
|
87
95
|
const [saving, setSaving] = useState(false);
|
|
88
96
|
const [error, setError] = useState<string | null>(null);
|
|
89
97
|
|
|
@@ -92,6 +100,7 @@ export function UserProfileSheet({
|
|
|
92
100
|
const p = parseAvatarUrl(user.avatar);
|
|
93
101
|
setAvatarStyle(p.style);
|
|
94
102
|
setAvatarSeed(p.seed);
|
|
103
|
+
setBgColor(p.bg);
|
|
95
104
|
}, [user.avatar]);
|
|
96
105
|
|
|
97
106
|
// Animate overlay
|
|
@@ -108,30 +117,9 @@ export function UserProfileSheet({
|
|
|
108
117
|
if (visible) setError(null);
|
|
109
118
|
}, [visible]);
|
|
110
119
|
|
|
111
|
-
const currentAvatarUrl = getAvatarUrl(avatarStyle, avatarSeed);
|
|
120
|
+
const currentAvatarUrl = getAvatarUrl(avatarStyle, avatarSeed, bgColor);
|
|
112
121
|
|
|
113
|
-
const
|
|
114
|
-
async (style: string) => {
|
|
115
|
-
const newUrl = getAvatarUrl(style, avatarSeed);
|
|
116
|
-
setAvatarStyle(style);
|
|
117
|
-
setSaving(true);
|
|
118
|
-
setError(null);
|
|
119
|
-
try {
|
|
120
|
-
await client.updateProfile({ avatar: newUrl });
|
|
121
|
-
onAvatarUpdated?.(newUrl);
|
|
122
|
-
} catch (err) {
|
|
123
|
-
setError(err instanceof Error ? err.message : 'Failed to update avatar');
|
|
124
|
-
} finally {
|
|
125
|
-
setSaving(false);
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
[avatarSeed, client, onAvatarUpdated],
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
const handleShuffle = useCallback(async () => {
|
|
132
|
-
const newSeed = generateSeed();
|
|
133
|
-
const newUrl = getAvatarUrl(avatarStyle, newSeed);
|
|
134
|
-
setAvatarSeed(newSeed);
|
|
122
|
+
const saveAvatar = useCallback(async (newUrl: string) => {
|
|
135
123
|
setSaving(true);
|
|
136
124
|
setError(null);
|
|
137
125
|
try {
|
|
@@ -142,7 +130,29 @@ export function UserProfileSheet({
|
|
|
142
130
|
} finally {
|
|
143
131
|
setSaving(false);
|
|
144
132
|
}
|
|
145
|
-
}, [
|
|
133
|
+
}, [client, onAvatarUpdated]);
|
|
134
|
+
|
|
135
|
+
const handleSelectStyle = useCallback(
|
|
136
|
+
(style: string) => {
|
|
137
|
+
setAvatarStyle(style);
|
|
138
|
+
saveAvatar(getAvatarUrl(style, avatarSeed, bgColor));
|
|
139
|
+
},
|
|
140
|
+
[avatarSeed, bgColor, saveAvatar],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const handleShuffle = useCallback(() => {
|
|
144
|
+
const newSeed = generateSeed();
|
|
145
|
+
setAvatarSeed(newSeed);
|
|
146
|
+
saveAvatar(getAvatarUrl(avatarStyle, newSeed, bgColor));
|
|
147
|
+
}, [avatarStyle, bgColor, saveAvatar]);
|
|
148
|
+
|
|
149
|
+
const handleSelectBgColor = useCallback(
|
|
150
|
+
(color: string) => {
|
|
151
|
+
setBgColor(color);
|
|
152
|
+
saveAvatar(getAvatarUrl(avatarStyle, avatarSeed, color));
|
|
153
|
+
},
|
|
154
|
+
[avatarStyle, avatarSeed, saveAvatar],
|
|
155
|
+
);
|
|
146
156
|
|
|
147
157
|
return (
|
|
148
158
|
<Modal
|
|
@@ -238,13 +248,40 @@ export function UserProfileSheet({
|
|
|
238
248
|
]}
|
|
239
249
|
>
|
|
240
250
|
<Image
|
|
241
|
-
source={{ uri: getAvatarUrl(style, avatarSeed, 80) }}
|
|
251
|
+
source={{ uri: getAvatarUrl(style, avatarSeed, bgColor, 80) }}
|
|
242
252
|
style={styles.styleTileImage}
|
|
243
253
|
/>
|
|
244
254
|
</TouchableOpacity>
|
|
245
255
|
);
|
|
246
256
|
})}
|
|
247
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>
|
|
248
285
|
</View>
|
|
249
286
|
|
|
250
287
|
{/* Error */}
|
|
@@ -423,6 +460,16 @@ const styles = StyleSheet.create({
|
|
|
423
460
|
stylePickerContent: {
|
|
424
461
|
gap: 10,
|
|
425
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
|
+
},
|
|
426
473
|
styleTile: {
|
|
427
474
|
width: 72,
|
|
428
475
|
height: 72,
|