@momo-kits/avatar 0.0.60-beta → 0.0.60-beta.5

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.
Files changed (4) hide show
  1. package/Avatar.js +298 -116
  2. package/Avatar.web.js +73 -4
  3. package/package.json +14 -14
  4. package/publish.sh +2 -2
package/Avatar.js CHANGED
@@ -2,13 +2,28 @@
2
2
  import PropTypes from 'prop-types';
3
3
  import React, { Component } from 'react';
4
4
  import {
5
- View, StyleSheet, TouchableOpacity, Dimensions
5
+ View,
6
+ StyleSheet,
7
+ TouchableOpacity,
8
+ Dimensions,
9
+ Platform,
6
10
  } from 'react-native';
7
11
  import { get, isEqual } from 'lodash';
8
12
  import {
9
- ValueUtil, Colors, Text, IconSource, Image, ScaleSize
13
+ ValueUtil,
14
+ Colors,
15
+ Text,
16
+ IconSource,
17
+ Image,
18
+ ScaleSize,
10
19
  } from '@momo-kits/core';
11
20
 
21
+ export const AvatarQuality = {
22
+ low: 'low',
23
+ medium: 'medium',
24
+ high: 'high',
25
+ };
26
+
12
27
  export const AvatarSize = {
13
28
  tiny: 'tiny',
14
29
  small: 'small',
@@ -26,20 +41,43 @@ export const AvatarSize = {
26
41
  size72: 'size72',
27
42
  size80: 'size80',
28
43
  size88: 'size88',
29
- size96: 'size96'
44
+ size96: 'size96',
30
45
  };
31
46
 
47
+ const COLORS = [
48
+ '#3683f3',
49
+ '#ea63af',
50
+ '#f94889',
51
+ '#82b6da',
52
+ '#2fc093',
53
+ '#7b4a41',
54
+ '#ff97bc',
55
+ '#4130d7',
56
+ '#c3c3c3',
57
+ '#f7ae2f',
58
+ '#b04595',
59
+ '#57b748',
60
+ '#ff8900',
61
+ '#7841bf',
62
+ '#be3f3e',
63
+ '#c51515',
64
+ '#8e6472',
65
+ '#fed27d',
66
+ '#ff6f5d',
67
+ '#05b8ae',
68
+ ];
69
+
32
70
  export const SubIconType = {
33
71
  momo: 'momo',
34
72
  online: 'online',
35
73
  key: 'key',
36
- none: 'none'
74
+ none: 'none',
37
75
  };
38
76
 
39
77
  const styles = StyleSheet.create({
40
78
  container: {
41
79
  justifyContent: 'center',
42
- alignItems: 'center'
80
+ alignItems: 'center',
43
81
  },
44
82
  shortNameView: {
45
83
  justifyContent: 'center',
@@ -47,90 +85,93 @@ const styles = StyleSheet.create({
47
85
 
48
86
  backgroundColor: Colors.very_light_pink_four,
49
87
  width: '100%',
50
- height: '100%'
88
+ height: '100%',
51
89
  },
52
90
  shortNameText: {
53
91
  // fontSize: 17,
54
- color: '#f6f6f6'
92
+ color: Colors.pink_03,
93
+ },
94
+ shortNameTextCustom: {
95
+ color: '#f6f6f6',
55
96
  },
56
97
  absolute: {
57
98
  position: 'absolute',
58
99
  top: 0,
59
100
  left: 0,
60
101
  right: 0,
61
- bottom: 0
102
+ bottom: 0,
62
103
  },
63
104
  subIcon: {
64
105
  position: 'absolute',
65
106
  bottom: 0,
66
- right: 0
107
+ right: 0,
67
108
  },
68
109
  subIconImage: {
69
110
  width: 22,
70
- height: 22
111
+ height: 22,
71
112
  },
72
113
  subIconImageOnline: {
73
114
  backgroundColor: Colors.kiwi_green,
74
115
  borderColor: 'white',
75
- borderWidth: 1
76
- }
116
+ borderWidth: 1,
117
+ },
77
118
  });
78
119
 
79
120
  const subIconSize = (width) => ({
80
121
  tiny: {
81
122
  width: 8,
82
123
  height: 8,
83
- borderRadius: 4
124
+ borderRadius: 4,
84
125
  },
85
126
  small: {
86
127
  width: 10,
87
128
  height: 10,
88
- borderRadius: 5
129
+ borderRadius: 5,
89
130
  },
90
131
  medium: {
91
132
  width: 12,
92
133
  height: 12,
93
- borderRadius: 6
134
+ borderRadius: 6,
94
135
  },
95
136
  large: {
96
137
  width: 16,
97
138
  height: 16,
98
- borderRadius: 8
139
+ borderRadius: 8,
99
140
  },
100
141
  size8: {
101
142
  width: 8,
102
143
  height: 8,
103
- borderRadius: 4
144
+ borderRadius: 4,
104
145
  },
105
146
  size24: {
106
147
  width: 10,
107
148
  height: 10,
108
- borderRadius: 5
149
+ borderRadius: 5,
109
150
  },
110
151
  size36: {
111
152
  width: 10,
112
153
  height: 10,
113
- borderRadius: 5
154
+ borderRadius: 5,
114
155
  },
115
156
  size44: {
116
157
  width: 14,
117
158
  height: 14,
118
- borderRadius: 7
159
+ borderRadius: 7,
119
160
  },
120
161
  size48: {
121
162
  width: 14,
122
163
  height: 14,
123
- borderRadius: 7
164
+ borderRadius: 7,
124
165
  },
125
166
  size52: {
126
167
  width: 16,
127
168
  height: 16,
128
- borderRadius: 8
169
+ borderRadius: 8,
129
170
  },
130
171
  size56: {
131
172
  width: 16,
132
173
  height: 16,
133
- borderRadius: 8
174
+ borderRadius: 8,
134
175
  },
135
176
  size64: {
136
177
  width: 16,
@@ -172,125 +213,132 @@ const subIconSize = (width) => ({
172
213
  left: -5,
173
214
  width: 24,
174
215
  height: 24,
175
- borderRadius: 12
176
- }
216
+ borderRadius: 12,
217
+ },
177
218
  });
178
219
 
179
220
  const avatarSize = (width) => ({
180
221
  tiny: {
181
222
  width: 16,
182
223
  height: 16,
183
- borderRadius: 8
224
+ borderRadius: 8,
184
225
  },
185
226
  small: {
186
227
  width: 32,
187
228
  height: 32,
188
- borderRadius: 16
229
+ borderRadius: 16,
189
230
  },
190
231
  medium: {
191
232
  width: 40,
192
233
  height: 40,
193
- borderRadius: 20
234
+ borderRadius: 20,
194
235
  },
195
236
  large: {
196
237
  width: 60,
197
238
  height: 60,
198
- borderRadius: 30
239
+ borderRadius: 30,
199
240
  },
200
241
  giant: {
201
242
  width: 120,
202
243
  height: 120,
203
- borderRadius: 60
244
+ borderRadius: 60,
204
245
  },
205
246
  size8: {
206
247
  width: 8,
207
248
  height: 8,
208
- borderRadius: 4
249
+ borderRadius: 4,
209
250
  },
210
251
  size24: {
211
252
  width: 24,
212
253
  height: 24,
213
- borderRadius: 12
254
+ borderRadius: 12,
214
255
  },
215
256
  size36: {
216
257
  width: 36,
217
258
  height: 36,
218
- borderRadius: 18
259
+ borderRadius: 18,
219
260
  },
220
261
  size44: {
221
262
  width: 44,
222
263
  height: 44,
223
- borderRadius: 22
264
+ borderRadius: 22,
224
265
  },
225
266
  size48: {
226
267
  width: 48,
227
268
  height: 48,
228
- borderRadius: 24
269
+ borderRadius: 24,
229
270
  },
230
271
  size52: {
231
272
  width: 52,
232
273
  height: 52,
233
- borderRadius: 26
274
+ borderRadius: 26,
234
275
  },
235
276
  size56: {
236
277
  width: 56,
237
278
  height: 56,
238
- borderRadius: 28
279
+ borderRadius: 28,
239
280
  },
240
281
  size64: {
241
282
  width: 64,
242
283
  height: 64,
243
- borderRadius: 32
284
+ borderRadius: 32,
244
285
  },
245
286
  size72: {
246
287
  width: 72,
247
288
  height: 72,
248
- borderRadius: 36
289
+ borderRadius: 36,
249
290
  },
250
291
  size80: {
251
292
  width: 80,
252
293
  height: 80,
253
- borderRadius: 40
294
+ borderRadius: 40,
254
295
  },
255
296
  size88: {
256
297
  width: 88,
257
298
  height: 88,
258
- borderRadius: 44
299
+ borderRadius: 44,
259
300
  },
260
301
  size96: {
261
302
  width: 96,
262
303
  height: 96,
263
- borderRadius: 48
264
- }
304
+ borderRadius: 48,
305
+ },
265
306
  });
266
307
 
267
308
  const shortName = (width) => ({
268
309
  tiny: {
269
- fontSize: ScaleSize(5, width)
310
+ fontSize: ScaleSize(5, width),
270
311
  },
271
312
  small: {
272
- fontSize: ScaleSize(11, width)
313
+ fontSize: ScaleSize(11, width),
273
314
  },
274
315
  medium: {
275
- fontSize: ScaleSize(15, width)
316
+ fontSize: ScaleSize(15, width),
317
+ },
318
+ middle: {
319
+ fontSize: ScaleSize(16, width),
276
320
  },
277
321
  large: {
278
- fontSize: ScaleSize(20, width)
322
+ fontSize: ScaleSize(20, width),
279
323
  },
280
324
  giant: {
281
- fontSize: ScaleSize(25, width)
282
- }
325
+ fontSize: ScaleSize(25, width),
326
+ },
283
327
  });
284
328
 
285
329
  export default class Avatar extends Component {
286
330
  constructor(props) {
287
331
  super(props);
332
+ this.color = props.custom
333
+ ? this.getAvatarColor(props.name)
334
+ : Colors.pink_10;
288
335
  this.state = {
289
336
  hideSource: false,
290
337
  ownUpdate: false,
291
338
  source: props.source,
292
- dimensions: {}
339
+ dimensions: {},
293
340
  };
341
+ this.imageSource = '';
294
342
  }
295
343
 
296
344
  componentDidMount() {
@@ -299,8 +347,14 @@ export default class Avatar extends Component {
299
347
  this.subscription = Dimensions.addEventListener(
300
348
  'change',
301
349
  ({ window }) => {
302
- this.setState({ dimensions: window });
303
- }
350
+ if (
351
+ window?.height > 0 &&
352
+ window?.width > 0 &&
353
+ Platform.OS === 'android'
354
+ ) {
355
+ this.setState({ dimensions: window });
356
+ }
357
+ },
304
358
  );
305
359
  }
306
360
  }
@@ -317,7 +371,8 @@ export default class Avatar extends Component {
317
371
  return {
318
372
  ownUpdate: false,
319
373
  };
320
- } if (!isEqual(nextProps.source, prevState.source)) {
374
+ }
375
+ if (!isEqual(nextProps.source, prevState.source)) {
321
376
  return { source: nextProps.source, hideSource: false };
322
377
  }
323
378
  return null;
@@ -333,75 +388,141 @@ export default class Avatar extends Component {
333
388
  }
334
389
  });
335
390
  return alphabet.join('');
391
+ };
392
+
393
+ hashUrl(url) {
394
+ let hash = 0;
395
+ let char;
396
+ if (url?.length === 0) return hash;
397
+ for (let i = 0; i < url?.length; i++) {
398
+ char = url?.charCodeAt(i);
399
+ hash = (hash << 5) - hash + char;
400
+ hash &= hash; // Convert to 32bit integer
401
+ }
402
+ return hash;
336
403
  }
337
404
 
338
- renderShortName = (name, avatarStyle, size) => {
405
+ renderShortName = (name, avatarStyle, size, color) => {
339
406
  if (name) {
340
407
  const shortedName = this.getContactShortName(name);
341
408
  const shortNameStyle = this.mapShortNameStyle(size);
342
409
  return (
343
- <View style={[styles.shortNameView, avatarStyle]}>
344
- <Text.H4 style={[styles.shortNameText, shortNameStyle]}>{shortedName}</Text.H4>
410
+ <View
411
+ style={[
412
+ styles.shortNameView,
413
+ avatarStyle,
414
+ { backgroundColor: color },
415
+ ]}>
416
+ <Text.H4
417
+ style={[
418
+ this.props.custom
419
+ ? styles.shortNameTextCustom
420
+ : styles.shortNameText,
421
+ shortNameStyle,
422
+ ]}>
423
+ {shortedName}
424
+ </Text.H4>
345
425
  </View>
346
426
  );
347
427
  }
348
428
  return <View />;
349
- }
429
+ };
350
430
 
351
431
  isUrl(url) {
352
- const expression = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/gi;
432
+ const expression =
433
+ /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-/]))?/gi;
353
434
  return expression.test(url);
354
435
  }
355
436
 
356
437
  isValidImageUrl = (source) => {
357
- const validSource = source && (((source.uri && this.isUrl(source.uri)) || (typeof source === 'number')));
438
+ const validSource = source && source.uri && this.isUrl(source.uri);
358
439
  return validSource;
359
- }
440
+ };
441
+
442
+ mapAvatarQuality = (url, quality) => {
443
+ if (!quality || quality === AvatarQuality.medium) {
444
+ return url;
445
+ }
446
+ if (quality === AvatarQuality.low) {
447
+ return url.replace('.png', '_s64.png');
448
+ }
449
+ if (quality === AvatarQuality.high) {
450
+ return url.replace('.png', '_s512.png');
451
+ }
452
+
453
+ return url;
454
+ };
360
455
 
361
456
  mapStyleFromSize = (size) => {
362
457
  const { dimensions } = this.state;
363
- const avatarStyle = avatarSize(dimensions?.width || null).small;
458
+ const avatarStyle = avatarSize(
459
+ dimensions?.width > 0 ? dimensions?.width : null,
460
+ ).small;
364
461
  if (typeof size === 'object') return size;
365
- if (avatarSize(dimensions?.width || null)[size]) return avatarSize(dimensions?.width || null)[size];
462
+ if (avatarSize(dimensions?.width > 0 ? dimensions?.width : null)[size])
463
+ return avatarSize(dimensions?.width > 0 ? dimensions?.width : null)[
464
+ size
465
+ ];
366
466
  return avatarStyle;
367
- }
467
+ };
368
468
 
369
469
  mapShortNameStyle = (size) => {
370
470
  let shortNameStyle = {};
371
471
  const { dimensions } = this.state;
372
472
  switch (size) {
373
473
  case AvatarSize.tiny: {
374
- shortNameStyle = shortName(dimensions?.width || null).tiny;
474
+ shortNameStyle = shortName(
475
+ dimensions?.width > 0 ? dimensions?.width : null,
476
+ ).tiny;
375
477
  break;
376
478
  }
377
479
  case AvatarSize.small: {
378
- shortNameStyle = shortName(dimensions?.width || null).small;
480
+ shortNameStyle = shortName(
481
+ dimensions?.width > 0 ? dimensions?.width : null,
482
+ ).small;
379
483
  break;
380
484
  }
381
485
  case AvatarSize.medium: {
382
- shortNameStyle = shortName(dimensions?.width || null).medium;
486
+ shortNameStyle = shortName(
487
+ dimensions?.width > 0 ? dimensions?.width : null,
488
+ ).medium;
489
+ break;
490
+ }
491
+ case AvatarSize.size48: {
492
+ shortNameStyle = shortName(dimensions?.width || null).middle;
383
493
  break;
384
494
  }
385
495
  case AvatarSize.large: {
386
- shortNameStyle = shortName(dimensions?.width || null).large;
496
+ shortNameStyle = shortName(
497
+ dimensions?.width > 0 ? dimensions?.width : null,
498
+ ).large;
387
499
  break;
388
500
  }
389
501
  case AvatarSize.giant: {
390
- shortNameStyle = shortName(dimensions?.width || null).giant;
502
+ shortNameStyle = shortName(
503
+ dimensions?.width > 0 ? dimensions?.width : null,
504
+ ).giant;
391
505
  break;
392
506
  }
393
507
  default:
394
- shortNameStyle = shortName(dimensions?.width || null).small;
508
+ shortNameStyle = shortName(
509
+ dimensions?.width > 0 ? dimensions?.width : null,
510
+ ).small;
395
511
  }
396
512
  return shortNameStyle;
397
- }
513
+ };
398
514
 
399
515
  mapSubIconSize = (size) => {
400
516
  const { dimensions } = this.state;
401
- const subIconStyle = subIconSize(dimensions?.width || null).small;
402
- if (subIconSize(dimensions?.width || null)[size]) return subIconSize(dimensions?.width || null)[size];
517
+ const subIconStyle = subIconSize(
518
+ dimensions?.width > 0 ? dimensions?.width : null,
519
+ ).small;
520
+ if (subIconSize(dimensions?.width > 0 ? dimensions?.width : null)[size])
521
+ return subIconSize(
522
+ dimensions?.width > 0 ? dimensions?.width : null,
523
+ )[size];
403
524
  return subIconStyle;
404
- }
525
+ };
405
526
 
406
527
  extractSize = (avatarStyle) => {
407
528
  const { style } = this.props;
@@ -412,78 +533,128 @@ export default class Avatar extends Component {
412
533
  height: containerWidth,
413
534
  borderRadius: containerWidth / 2,
414
535
  borderWidth,
415
- borderColor: Colors.black_01
536
+ borderColor: Colors.black_01,
416
537
  };
417
- }
538
+ };
418
539
 
419
- onLoadSourceError = () => {
420
- this.setState({ hideSource: true, ownUpdate: true });
421
- }
540
+ onLoadSourceError = (imageSource) => {
541
+ if (this.imageSource?.uri !== imageSource?.uri) {
542
+ this.imageSource = imageSource;
543
+ this.setState({ hideSource: true, ownUpdate: true });
544
+ }
545
+ };
422
546
 
423
547
  renderSubIcon = () => {
424
548
  const { subIcon, size, subIconSource } = this.props;
425
- if ((!subIcon || subIcon === SubIconType.none) && !subIconSource) return <View />;
549
+ if ((!subIcon || subIcon === SubIconType.none) && !subIconSource)
550
+ return <View />;
426
551
  const subIconSizeStyle = this.mapSubIconSize(size);
427
552
  const iconSource = ValueUtil.getImageSource(subIconSource);
428
553
  return (
429
554
  <View style={styles.subIcon}>
430
- {subIcon === SubIconType.momo
431
- ? (
432
- <Image
433
- style={[styles.subIconImage, subIconSizeStyle]}
434
- source={IconSource.ic_round_momo_tiny}
435
- />
436
- )
437
- : <View />}
438
- {subIcon === SubIconType.online
439
- ? <View style={[styles.subIconImageOnline, subIconSizeStyle]} /> : <View />}
555
+ {subIcon === SubIconType.momo ? (
556
+ <Image
557
+ style={[styles.subIconImage, subIconSizeStyle]}
558
+ source={IconSource.ic_round_momo_tiny}
559
+ />
560
+ ) : (
561
+ <View />
562
+ )}
563
+ {subIcon === SubIconType.online ? (
564
+ <View
565
+ style={[styles.subIconImageOnline, subIconSizeStyle]}
566
+ />
567
+ ) : (
568
+ <View />
569
+ )}
440
570
 
441
571
  {iconSource && subIconSource && !subIcon ? (
442
572
  <Image
443
573
  style={[styles.subIconImage, subIconSizeStyle]}
444
574
  source={iconSource}
445
575
  />
446
- )
447
- : <View />}
576
+ ) : (
577
+ <View />
578
+ )}
448
579
  </View>
449
580
  );
581
+ };
582
+
583
+ getAvatarColor(name) {
584
+ const shortName = this.getContactShortName(name);
585
+ if (!shortName) return '#d5d6d6';
586
+ const firstLetter = shortName[0].charCodeAt(0);
587
+ let colorIndex = Number(firstLetter) % 26;
588
+ if (colorIndex > 19) colorIndex = 25 - colorIndex;
589
+ return COLORS[colorIndex];
450
590
  }
451
591
 
452
592
  onPress = () => {
453
593
  const { onPress } = this.props;
454
594
  if (onPress && typeof onPress === 'function') onPress();
455
- }
595
+ };
456
596
 
457
597
  render() {
458
598
  const {
459
- size, source, name, resizeMode, style, onPress, isShowLoading, loading, cached, extraPropsImage
599
+ size,
600
+ source,
601
+ name,
602
+ resizeMode,
603
+ style,
604
+ onPress,
605
+ isShowLoading,
606
+ loading,
607
+ cached,
608
+ extraPropsImage,
609
+ quality,
460
610
  } = this.props;
461
611
  const { hideSource } = this.state;
462
612
  const avatarStyle = this.mapStyleFromSize(size);
463
613
  const containerSize = this.extractSize(avatarStyle);
464
614
  const imageSource = ValueUtil.getImageSource(source);
465
- const showName = typeof imageSource === 'object' && Object.keys(imageSource).length === 0 && typeof imageSource !== 'number';
615
+ const showName =
616
+ typeof imageSource === 'object' &&
617
+ Object.keys(imageSource).length === 0 &&
618
+ typeof imageSource !== 'number';
619
+ // add priority high for avatar
620
+ if (imageSource?.uri) {
621
+ imageSource.priority = 'high';
622
+ imageSource.uri = this.mapAvatarQuality(imageSource.uri, quality);
623
+ // if doest not have time query, set default cache time to 1 day
624
+ if (!imageSource.uri?.includes('?') && cached) {
625
+ const midnight = new Date().setHours(0, 0, 0, 0);
626
+ imageSource.uri = `${imageSource.uri}?time=${midnight}`;
627
+ }
628
+ }
629
+
466
630
  const inner = (
467
631
  <View style={[styles.container, containerSize, style]}>
468
- {(showName || hideSource) ? this.renderShortName(name, avatarStyle, size) : <View />}
469
- {this.isValidImageUrl(imageSource) && !hideSource
470
- ? (
471
- <Image
472
- cached={cached}
473
- loading={loading || isShowLoading}
474
- source={imageSource}
475
- onError={this.onLoadSourceError}
476
- style={avatarStyle}
477
- resizeMode={resizeMode}
478
- {...extraPropsImage}
479
- />
480
- ) : <View />}
632
+ {showName || hideSource ? (
633
+ this.renderShortName(name, avatarStyle, size, this.color)
634
+ ) : (
635
+ <View />
636
+ )}
637
+ {this.isValidImageUrl(imageSource) && !hideSource ? (
638
+ <Image
639
+ cached={cached}
640
+ loading={loading || isShowLoading}
641
+ source={imageSource}
642
+ onError={() => this.onLoadSourceError(imageSource)}
643
+ style={avatarStyle}
644
+ resizeMode={resizeMode}
645
+ {...extraPropsImage}
646
+ />
647
+ ) : (
648
+ <View />
649
+ )}
481
650
  {this.renderSubIcon()}
482
651
  </View>
483
652
  );
484
653
  if (onPress) {
485
654
  return (
486
- <TouchableOpacity activeOpacity={onPress ? 0.5 : 1} onPress={this.onPress}>
655
+ <TouchableOpacity
656
+ activeOpacity={onPress ? 0.5 : 1}
657
+ onPress={this.onPress}>
487
658
  {inner}
488
659
  </TouchableOpacity>
489
660
  );
@@ -495,23 +666,34 @@ export default class Avatar extends Component {
495
666
  Avatar.propTypes = {
496
667
  name: PropTypes.string,
497
668
  resizeMode: PropTypes.string,
498
- size: PropTypes.oneOf(['tiny', 'small', 'medium', 'large', 'giant', 'size8', 'size24', 'size36', 'size44', 'size48', 'size52', 'size56', 'size64', 'size72', 'size80', 'size88', 'size96',
499
- PropTypes.shape({
500
- width: PropTypes.number, height: PropTypes.number, borderRadius: PropTypes.number
501
- })]),
502
- source: PropTypes.oneOfType([PropTypes.shape({ uri: PropTypes.string }), PropTypes.number, PropTypes.string]),
669
+ quality: PropTypes.oneOf(['low', 'medium', 'high']),
670
+ size: PropTypes.any,
671
+ source: PropTypes.oneOfType([
672
+ PropTypes.shape({ uri: PropTypes.string }),
673
+ PropTypes.number,
674
+ PropTypes.string,
675
+ ]),
503
676
  subIcon: PropTypes.oneOf(['momo', 'online', 'key', 'none']),
504
- subIconSource: PropTypes.oneOfType([PropTypes.shape({ uri: PropTypes.string }), PropTypes.number, PropTypes.string]),
677
+ subIconSource: PropTypes.oneOfType([
678
+ PropTypes.shape({ uri: PropTypes.string }),
679
+ PropTypes.number,
680
+ PropTypes.string,
681
+ ]),
505
682
  style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
506
683
  onPress: PropTypes.func,
507
684
  loading: PropTypes.bool,
508
685
  cached: PropTypes.bool,
509
686
  scaleSize: PropTypes.bool,
510
- extraPropsImage: PropTypes.object
687
+ extraPropsImage: PropTypes.object,
688
+ number: PropTypes.string,
689
+ custom: PropTypes.bool,
511
690
  };
512
691
 
513
692
  Avatar.defaultProps = {
514
693
  resizeMode: 'cover',
515
694
  cached: true,
516
- scaleSize: false
695
+ scaleSize: false,
696
+ quality: 'medium',
697
+ size: 'small',
698
+ custom: false,
517
699
  };
package/Avatar.web.js CHANGED
@@ -11,6 +11,35 @@ import { Icons as IconSource } from '../../core/icons';
11
11
  import Text from '../../core/components/typography';
12
12
  import { RFValueHorizontal as ScaleSize } from '../../core/components/typography/reponsiveSize';
13
13
 
14
+ export const AvatarQuality = {
15
+ low: 'low',
16
+ medium: 'medium',
17
+ high: 'high',
18
+ };
19
+
20
+ const COLORS = [
21
+ '#3683f3',
22
+ '#ea63af',
23
+ '#f94889',
24
+ '#82b6da',
25
+ '#2fc093',
26
+ '#7b4a41',
27
+ '#ff97bc',
28
+ '#4130d7',
29
+ '#c3c3c3',
30
+ '#f7ae2f',
31
+ '#b04595',
32
+ '#57b748',
33
+ '#ff8900',
34
+ '#7841bf',
35
+ '#be3f3e',
36
+ '#c51515',
37
+ '#8e6472',
38
+ '#fed27d',
39
+ '#ff6f5d',
40
+ '#05b8ae',
41
+ ];
42
+
14
43
  export const AvatarSize = {
15
44
  tiny: 'tiny',
16
45
  small: 'small',
@@ -287,6 +316,7 @@ const shortName = (width) => ({
287
316
  export default class Avatar extends Component {
288
317
  constructor(props) {
289
318
  super(props);
319
+ this.color = this.getAvatarColor(props.name);
290
320
  this.state = {
291
321
  hideSource: false,
292
322
  ownUpdate: false,
@@ -301,7 +331,9 @@ export default class Avatar extends Component {
301
331
  this.subscription = Dimensions.addEventListener(
302
332
  'change',
303
333
  ({ window }) => {
304
- this.setState({ dimensions: window });
334
+ if(window?.height > 0 && window?.width > 0) {
335
+ this.setState({ dimensions: window });
336
+ }
305
337
  }
306
338
  );
307
339
  }
@@ -356,10 +388,24 @@ export default class Avatar extends Component {
356
388
  }
357
389
 
358
390
  isValidImageUrl = (source) => {
359
- const validSource = source && (((source.uri && this.isUrl(source.uri)) || (typeof source === 'number')));
391
+ const validSource = source && source.uri && this.isUrl(source.uri);
360
392
  return validSource;
361
393
  }
362
394
 
395
+ mapAvatarQuality = (url, quality) => {
396
+ if (!quality || quality === AvatarQuality.medium) {
397
+ return url;
398
+ }
399
+ if (quality === AvatarQuality.low) {
400
+ return url.replace('.png', '_s64.png');
401
+ }
402
+ if (quality === AvatarQuality.high) {
403
+ return url.replace('.png', '_s512.png');
404
+ }
405
+
406
+ return url;
407
+ }
408
+
363
409
  mapStyleFromSize = (size) => {
364
410
  const { dimensions } = this.state;
365
411
  const avatarStyle = avatarSize(dimensions?.width || null).small;
@@ -418,6 +464,16 @@ export default class Avatar extends Component {
418
464
  };
419
465
  }
420
466
 
467
+ getAvatarColor(name) {
468
+ const shortName = this.getContactShortName(name)
469
+ if (!shortName) return "#d5d6d6"
470
+ const firstLetter = shortName[0].charCodeAt(0)
471
+ let colorIndex = Number(firstLetter) % 26
472
+ if (colorIndex > 19) colorIndex = 25 - colorIndex
473
+ return COLORS[colorIndex]
474
+ }
475
+
476
+
421
477
  onLoadSourceError = () => {
422
478
  this.setState({ hideSource: true, ownUpdate: true });
423
479
  }
@@ -458,13 +514,24 @@ export default class Avatar extends Component {
458
514
 
459
515
  render() {
460
516
  const {
461
- size, source, name, resizeMode, style, onPress, isShowLoading, loading, cached, extraPropsImage
517
+ size, source, name, resizeMode, style, onPress, isShowLoading, loading, cached, extraPropsImage, quality
462
518
  } = this.props;
463
519
  const { hideSource } = this.state;
464
520
  const avatarStyle = this.mapStyleFromSize(size);
465
521
  const containerSize = this.extractSize(avatarStyle);
466
522
  const imageSource = ValueUtil.getImageSource(source);
467
523
  const showName = typeof imageSource === 'object' && Object.keys(imageSource).length === 0 && typeof imageSource !== 'number';
524
+ // add priority high for avatar
525
+ if (imageSource?.uri) {
526
+ imageSource.priority = 'high';
527
+ imageSource.uri = this.mapAvatarQuality(imageSource.uri, quality);
528
+ // if doest not have time query, set default cache time to 1 day
529
+ if (!imageSource.uri?.includes('?') && cached) {
530
+ const midnight = new Date().setHours(0, 0, 0, 0);
531
+ imageSource.uri = `${imageSource.uri}?time=${midnight}`;
532
+ }
533
+ }
534
+
468
535
  const inner = (
469
536
  <View style={[styles.container, containerSize, style]}>
470
537
  {(showName || hideSource) ? this.renderShortName(name, avatarStyle, size) : <View />}
@@ -497,6 +564,7 @@ export default class Avatar extends Component {
497
564
  Avatar.propTypes = {
498
565
  name: PropTypes.string,
499
566
  resizeMode: PropTypes.string,
567
+ quality: PropTypes.oneOf(['low', 'medium', 'high']),
500
568
  size: PropTypes.oneOf(['tiny', 'small', 'medium', 'large', 'giant', 'size8', 'size24', 'size36', 'size44', 'size48', 'size52', 'size56', 'size64', 'size72', 'size80', 'size88', 'size96',
501
569
  PropTypes.shape({
502
570
  width: PropTypes.number, height: PropTypes.number, borderRadius: PropTypes.number
@@ -515,5 +583,6 @@ Avatar.propTypes = {
515
583
  Avatar.defaultProps = {
516
584
  resizeMode: 'cover',
517
585
  cached: true,
518
- scaleSize: false
586
+ scaleSize: false,
587
+ quality: 'medium',
519
588
  };
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
- "name": "@momo-kits/avatar",
3
- "version": "0.0.60-beta",
4
- "private": false,
5
- "main": "index.js",
6
- "dependencies": {},
7
- "peerDependencies": {
8
- "react-native": ">=0.55",
9
- "prop-types": "^15.7.2",
10
- "react": "16.9.0",
11
- "@momo-kits/core": ">=0.0.5-beta",
12
- "lodash": "^4.17.15"
13
- },
14
- "devDependencies": {},
15
- "license": "MoMo"
2
+ "name": "@momo-kits/avatar",
3
+ "version": "0.0.60-beta.5",
4
+ "private": false,
5
+ "main": "index.js",
6
+ "dependencies": {},
7
+ "peerDependencies": {
8
+ "react-native": ">=0.55",
9
+ "prop-types": "^15.7.2",
10
+ "react": "16.9.0",
11
+ "@momo-kits/core": ">=0.0.5-beta",
12
+ "lodash": "^4.17.15"
13
+ },
14
+ "devDependencies": {},
15
+ "license": "MoMo"
16
16
  }
package/publish.sh CHANGED
@@ -21,9 +21,9 @@ rsync -r --verbose --exclude '*.mdx' --exclude '*Demo.js' --exclude 'props-type
21
21
  #npm login
22
22
  #publish dist to npm
23
23
  cd dist
24
- npm publish --access=public
24
+ npm publish --tag beta --access=public
25
25
  cd ..
26
26
  rm -rf dist
27
27
 
28
28
 
29
- curl -X POST -H 'Content-Type: application/json' 'https://chat.googleapis.com/v1/spaces/AAAAbP8987c/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=UGSFRvk_oYb9uGsAgs31bVvMm6jDkmD8zihGm3eyaQA%3D&threadKey=JoaXTEYaNNkl' -d '{"text": "@momo-kits/avatar new version release: '*"$VERSION"*' https://www.npmjs.com/package/@momo-kits/avatar"}'
29
+ #curl -X POST -H 'Content-Type: application/json' 'https://chat.googleapis.com/v1/spaces/AAAAbP8987c/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=UGSFRvk_oYb9uGsAgs31bVvMm6jDkmD8zihGm3eyaQA%3D&threadKey=JoaXTEYaNNkl' -d '{"text": "@momo-kits/avatar new version release: '*"$VERSION"*' https://www.npmjs.com/package/@momo-kits/avatar"}'