@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dubsdotapp/expo",
3
- "version": "0.2.77",
3
+ "version": "0.2.78",
4
4
  "description": "React Native SDK for the Dubs betting platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -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
- <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
- </>
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 { ensurePngAvatar } from '../utils/avatarUrl';
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 handleSelectStyle = useCallback(
136
- (style: string) => {
137
- setAvatarStyle(style);
138
- saveAvatar(getAvatarUrl(style, avatarSeed, bgColor));
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 handleSelectBgColor = useCallback(
150
- (color: string) => {
151
- setBgColor(color);
152
- saveAvatar(getAvatarUrl(avatarStyle, avatarSeed, color));
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
- <ScrollView
230
- horizontal
231
- showsHorizontalScrollIndicator={false}
232
- contentContainerStyle={styles.stylePickerContent}
233
- >
234
- {DICEBEAR_STYLES.map((style) => {
235
- const isSelected = style === avatarStyle;
236
- return (
237
- <TouchableOpacity
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,