@momo-kits/avatar 0.0.38-beta → 0.0.40-beta.2

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 +324 -117
  2. package/Avatar.web.js +129 -65
  3. package/package.json +3 -3
  4. package/publish.sh +1 -1
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
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
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_05_b,
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
- const subIconSize = StyleSheet.create({
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,124 +213,157 @@ const subIconSize = StyleSheet.create({
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
- const avatarSize = StyleSheet.create({
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
- const shortName = StyleSheet.create({
308
+ const shortName = (width) => ({
268
309
  tiny: {
269
- fontSize: 5
310
+ fontSize: ScaleSize(5, width),
270
311
  },
271
312
  small: {
272
- fontSize: 11
313
+ fontSize: ScaleSize(11, width),
273
314
  },
274
315
  medium: {
275
- fontSize: 15
316
+ fontSize: ScaleSize(15, width),
317
+ },
318
+ middle: {
319
+ fontSize: ScaleSize(16, width),
276
320
  },
277
321
  large: {
278
- fontSize: 20
322
+ fontSize: ScaleSize(20, width),
279
323
  },
280
324
  giant: {
281
- fontSize: 25
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
- source: props.source
338
+ source: props.source,
339
+ dimensions: {},
292
340
  };
341
+ this.imageSource = '';
342
+ }
343
+
344
+ componentDidMount() {
345
+ const { scaleSize } = this.props;
346
+ if (scaleSize) {
347
+ this.subscription = Dimensions.addEventListener(
348
+ 'change',
349
+ ({ window }) => {
350
+ if (
351
+ window?.height > 0 &&
352
+ window?.width > 0 &&
353
+ Platform.OS === 'android'
354
+ ) {
355
+ this.setState({ dimensions: window });
356
+ }
357
+ },
358
+ );
359
+ }
360
+ }
361
+
362
+ componentWillUnmount() {
363
+ const { scaleSize } = this.props;
364
+ if (scaleSize) {
365
+ this.subscription?.remove?.();
366
+ }
293
367
  }
294
368
 
295
369
  static getDerivedStateFromProps(nextProps, prevState) {
@@ -297,7 +371,8 @@ export default class Avatar extends Component {
297
371
  return {
298
372
  ownUpdate: false,
299
373
  };
300
- } if (!isEqual(nextProps.source, prevState.source)) {
374
+ }
375
+ if (!isEqual(nextProps.source, prevState.source)) {
301
376
  return { source: nextProps.source, hideSource: false };
302
377
  }
303
378
  return null;
@@ -313,72 +388,141 @@ export default class Avatar extends Component {
313
388
  }
314
389
  });
315
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;
316
403
  }
317
404
 
318
- renderShortName = (name, avatarStyle, size) => {
405
+ renderShortName = (name, avatarStyle, size, color) => {
319
406
  if (name) {
320
407
  const shortedName = this.getContactShortName(name);
321
408
  const shortNameStyle = this.mapShortNameStyle(size);
322
409
  return (
323
- <View style={[styles.shortNameView, avatarStyle]}>
324
- <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>
325
425
  </View>
326
426
  );
327
427
  }
328
428
  return <View />;
329
- }
429
+ };
330
430
 
331
431
  isUrl(url) {
332
- 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;
333
434
  return expression.test(url);
334
435
  }
335
436
 
336
437
  isValidImageUrl = (source) => {
337
- const validSource = source && (((source.uri && this.isUrl(source.uri)) || (typeof source === 'number')));
438
+ const validSource = source && source.uri && this.isUrl(source.uri);
338
439
  return validSource;
339
- }
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
+ };
340
455
 
341
456
  mapStyleFromSize = (size) => {
342
- const avatarStyle = avatarSize.small;
457
+ const { dimensions } = this.state;
458
+ const avatarStyle = avatarSize(
459
+ dimensions?.width > 0 ? dimensions?.width : null,
460
+ ).small;
343
461
  if (typeof size === 'object') return size;
344
- if (avatarSize[size]) return avatarSize[size];
462
+ if (avatarSize(dimensions?.width > 0 ? dimensions?.width : null)[size])
463
+ return avatarSize(dimensions?.width > 0 ? dimensions?.width : null)[
464
+ size
465
+ ];
345
466
  return avatarStyle;
346
- }
467
+ };
347
468
 
348
469
  mapShortNameStyle = (size) => {
349
470
  let shortNameStyle = {};
471
+ const { dimensions } = this.state;
350
472
  switch (size) {
351
473
  case AvatarSize.tiny: {
352
- shortNameStyle = shortName.tiny;
474
+ shortNameStyle = shortName(
475
+ dimensions?.width > 0 ? dimensions?.width : null,
476
+ ).tiny;
353
477
  break;
354
478
  }
355
479
  case AvatarSize.small: {
356
- shortNameStyle = shortName.small;
480
+ shortNameStyle = shortName(
481
+ dimensions?.width > 0 ? dimensions?.width : null,
482
+ ).small;
357
483
  break;
358
484
  }
359
485
  case AvatarSize.medium: {
360
- shortNameStyle = shortName.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;
361
493
  break;
362
494
  }
363
495
  case AvatarSize.large: {
364
- shortNameStyle = shortName.large;
496
+ shortNameStyle = shortName(
497
+ dimensions?.width > 0 ? dimensions?.width : null,
498
+ ).large;
365
499
  break;
366
500
  }
367
501
  case AvatarSize.giant: {
368
- shortNameStyle = shortName.giant;
502
+ shortNameStyle = shortName(
503
+ dimensions?.width > 0 ? dimensions?.width : null,
504
+ ).giant;
369
505
  break;
370
506
  }
371
507
  default:
372
- shortNameStyle = shortName.small;
508
+ shortNameStyle = shortName(
509
+ dimensions?.width > 0 ? dimensions?.width : null,
510
+ ).small;
373
511
  }
374
512
  return shortNameStyle;
375
- }
513
+ };
376
514
 
377
515
  mapSubIconSize = (size) => {
378
- const subIconStyle = subIconSize.small;
379
- if (subIconSize[size]) return subIconSize[size];
516
+ const { dimensions } = this.state;
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];
380
524
  return subIconStyle;
381
- }
525
+ };
382
526
 
383
527
  extractSize = (avatarStyle) => {
384
528
  const { style } = this.props;
@@ -389,78 +533,128 @@ export default class Avatar extends Component {
389
533
  height: containerWidth,
390
534
  borderRadius: containerWidth / 2,
391
535
  borderWidth,
392
- borderColor: Colors.black_01
536
+ borderColor: Colors.black_01,
393
537
  };
394
- }
538
+ };
395
539
 
396
- onLoadSourceError = () => {
397
- this.setState({ hideSource: true, ownUpdate: true });
398
- }
540
+ onLoadSourceError = (imageSource) => {
541
+ if (this.imageSource?.uri !== imageSource?.uri) {
542
+ this.imageSource = imageSource;
543
+ this.setState({ hideSource: true, ownUpdate: true });
544
+ }
545
+ };
399
546
 
400
547
  renderSubIcon = () => {
401
548
  const { subIcon, size, subIconSource } = this.props;
402
- if ((!subIcon || subIcon === SubIconType.none) && !subIconSource) return <View />;
549
+ if ((!subIcon || subIcon === SubIconType.none) && !subIconSource)
550
+ return <View />;
403
551
  const subIconSizeStyle = this.mapSubIconSize(size);
404
552
  const iconSource = ValueUtil.getImageSource(subIconSource);
405
553
  return (
406
554
  <View style={styles.subIcon}>
407
- {subIcon === SubIconType.momo
408
- ? (
409
- <Image
410
- style={[styles.subIconImage, subIconSizeStyle]}
411
- source={IconSource.ic_round_momo_tiny}
412
- />
413
- )
414
- : <View />}
415
- {subIcon === SubIconType.online
416
- ? <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
+ )}
417
570
 
418
571
  {iconSource && subIconSource && !subIcon ? (
419
572
  <Image
420
573
  style={[styles.subIconImage, subIconSizeStyle]}
421
574
  source={iconSource}
422
575
  />
423
- )
424
- : <View />}
576
+ ) : (
577
+ <View />
578
+ )}
425
579
  </View>
426
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];
427
590
  }
428
591
 
429
592
  onPress = () => {
430
593
  const { onPress } = this.props;
431
594
  if (onPress && typeof onPress === 'function') onPress();
432
- }
595
+ };
433
596
 
434
597
  render() {
435
598
  const {
436
- 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,
437
610
  } = this.props;
438
611
  const { hideSource } = this.state;
439
612
  const avatarStyle = this.mapStyleFromSize(size);
440
613
  const containerSize = this.extractSize(avatarStyle);
441
614
  const imageSource = ValueUtil.getImageSource(source);
442
- 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
+
443
630
  const inner = (
444
631
  <View style={[styles.container, containerSize, style]}>
445
- {(showName || hideSource) ? this.renderShortName(name, avatarStyle, size) : <View />}
446
- {this.isValidImageUrl(imageSource) && !hideSource
447
- ? (
448
- <Image
449
- cached={cached}
450
- loading={loading || isShowLoading}
451
- source={imageSource}
452
- onError={this.onLoadSourceError}
453
- style={avatarStyle}
454
- resizeMode={resizeMode}
455
- {...extraPropsImage}
456
- />
457
- ) : <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
+ )}
458
650
  {this.renderSubIcon()}
459
651
  </View>
460
652
  );
461
653
  if (onPress) {
462
654
  return (
463
- <TouchableOpacity activeOpacity={onPress ? 0.5 : 1} onPress={this.onPress}>
655
+ <TouchableOpacity
656
+ activeOpacity={onPress ? 0.5 : 1}
657
+ onPress={this.onPress}>
464
658
  {inner}
465
659
  </TouchableOpacity>
466
660
  );
@@ -472,21 +666,34 @@ export default class Avatar extends Component {
472
666
  Avatar.propTypes = {
473
667
  name: PropTypes.string,
474
668
  resizeMode: PropTypes.string,
475
- size: PropTypes.oneOf(['tiny', 'small', 'medium', 'large', 'giant', 'size8', 'size24', 'size36', 'size44', 'size48', 'size52', 'size56', 'size64', 'size72', 'size80', 'size88', 'size96',
476
- PropTypes.shape({
477
- width: PropTypes.number, height: PropTypes.number, borderRadius: PropTypes.number
478
- })]),
479
- 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
+ ]),
480
676
  subIcon: PropTypes.oneOf(['momo', 'online', 'key', 'none']),
481
- 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
+ ]),
482
682
  style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
483
683
  onPress: PropTypes.func,
484
684
  loading: PropTypes.bool,
485
685
  cached: PropTypes.bool,
486
- extraPropsImage: PropTypes.object
686
+ scaleSize: PropTypes.bool,
687
+ extraPropsImage: PropTypes.object,
688
+ number: PropTypes.string,
689
+ custom: PropTypes.bool,
487
690
  };
488
691
 
489
692
  Avatar.defaultProps = {
490
693
  resizeMode: 'cover',
491
- cached: true
694
+ cached: true,
695
+ scaleSize: false,
696
+ quality: 'medium',
697
+ size: 'small',
698
+ custom: false,
492
699
  };
package/Avatar.web.js CHANGED
@@ -2,13 +2,43 @@
2
2
  import PropTypes from 'prop-types';
3
3
  import React, { Component } from 'react';
4
4
  import {
5
- View, StyleSheet, TouchableOpacity, Image
5
+ View, StyleSheet, TouchableOpacity, Image, Dimensions
6
6
  } from 'react-native';
7
7
  import { get, isEqual } from 'lodash';
8
- import ValueUtils from '../../core/utils/ValueUtil';
8
+ import ValueUtil from '../../core/utils/ValueUtil';
9
9
  import Colors from '../../core/colors';
10
- import { Icons } from '../../core/icons';
10
+ import { Icons as IconSource } from '../../core/icons';
11
11
  import Text from '../../core/components/typography';
12
+ import { RFValueHorizontal as ScaleSize } from '../../core/components/typography/reponsiveSize';
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
+ ];
12
42
 
13
43
  export const AvatarSize = {
14
44
  tiny: 'tiny',
@@ -77,7 +107,7 @@ const styles = StyleSheet.create({
77
107
  }
78
108
  });
79
109
 
80
- const subIconSize = StyleSheet.create({
110
+ const subIconSize = (width) => ({
81
111
  tiny: {
82
112
  width: 8,
83
113
  height: 8,
@@ -177,7 +207,7 @@ const subIconSize = StyleSheet.create({
177
207
  }
178
208
  });
179
209
 
180
- const avatarSize = StyleSheet.create({
210
+ const avatarSize = (width) => ({
181
211
  tiny: {
182
212
  width: 16,
183
213
  height: 16,
@@ -265,34 +295,57 @@ const avatarSize = StyleSheet.create({
265
295
  }
266
296
  });
267
297
 
268
- const shortName = StyleSheet.create({
298
+ const shortName = (width) => ({
269
299
  tiny: {
270
- fontSize: 5
300
+ fontSize: ScaleSize(5, width)
271
301
  },
272
302
  small: {
273
- fontSize: 11
303
+ fontSize: ScaleSize(11, width)
274
304
  },
275
305
  medium: {
276
- fontSize: 15
306
+ fontSize: ScaleSize(15, width)
277
307
  },
278
308
  large: {
279
- fontSize: 20
309
+ fontSize: ScaleSize(20, width)
280
310
  },
281
311
  giant: {
282
- fontSize: 25
312
+ fontSize: ScaleSize(25, width)
283
313
  }
284
314
  });
285
315
 
286
316
  export default class Avatar extends Component {
287
317
  constructor(props) {
288
318
  super(props);
319
+ this.color = this.getAvatarColor(props.name);
289
320
  this.state = {
290
321
  hideSource: false,
291
322
  ownUpdate: false,
292
- source: props.source
323
+ source: props.source,
324
+ dimensions: {}
293
325
  };
294
326
  }
295
327
 
328
+ componentDidMount() {
329
+ const { scaleSize } = this.props;
330
+ if (scaleSize) {
331
+ this.subscription = Dimensions.addEventListener(
332
+ 'change',
333
+ ({ window }) => {
334
+ if(window?.height > 0 && window?.width > 0) {
335
+ this.setState({ dimensions: window });
336
+ }
337
+ }
338
+ );
339
+ }
340
+ }
341
+
342
+ componentWillUnmount() {
343
+ const { scaleSize } = this.props;
344
+ if (scaleSize) {
345
+ this.subscription?.remove?.();
346
+ }
347
+ }
348
+
296
349
  static getDerivedStateFromProps(nextProps, prevState) {
297
350
  if (prevState.ownUpdate) {
298
351
  return {
@@ -335,49 +388,66 @@ export default class Avatar extends Component {
335
388
  }
336
389
 
337
390
  isValidImageUrl = (source) => {
338
- const validSource = source && (((source.uri && this.isUrl(source.uri)) || (typeof source === 'number')));
391
+ const validSource = source && source.uri && this.isUrl(source.uri);
339
392
  return validSource;
340
393
  }
341
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
+
342
409
  mapStyleFromSize = (size) => {
343
- const avatarStyle = avatarSize.small;
410
+ const { dimensions } = this.state;
411
+ const avatarStyle = avatarSize(dimensions?.width || null).small;
344
412
  if (typeof size === 'object') return size;
345
- if (avatarSize[size]) return avatarSize[size];
413
+ if (avatarSize(dimensions?.width || null)[size]) return avatarSize(dimensions?.width || null)[size];
346
414
  return avatarStyle;
347
415
  }
348
416
 
349
417
  mapShortNameStyle = (size) => {
350
418
  let shortNameStyle = {};
419
+ const { dimensions } = this.state;
351
420
  switch (size) {
352
421
  case AvatarSize.tiny: {
353
- shortNameStyle = shortName.tiny;
422
+ shortNameStyle = shortName(dimensions?.width || null).tiny;
354
423
  break;
355
424
  }
356
425
  case AvatarSize.small: {
357
- shortNameStyle = shortName.small;
426
+ shortNameStyle = shortName(dimensions?.width || null).small;
358
427
  break;
359
428
  }
360
429
  case AvatarSize.medium: {
361
- shortNameStyle = shortName.medium;
430
+ shortNameStyle = shortName(dimensions?.width || null).medium;
362
431
  break;
363
432
  }
364
433
  case AvatarSize.large: {
365
- shortNameStyle = shortName.large;
434
+ shortNameStyle = shortName(dimensions?.width || null).large;
366
435
  break;
367
436
  }
368
437
  case AvatarSize.giant: {
369
- shortNameStyle = shortName.giant;
438
+ shortNameStyle = shortName(dimensions?.width || null).giant;
370
439
  break;
371
440
  }
372
441
  default:
373
- shortNameStyle = shortName.small;
442
+ shortNameStyle = shortName(dimensions?.width || null).small;
374
443
  }
375
444
  return shortNameStyle;
376
445
  }
377
446
 
378
447
  mapSubIconSize = (size) => {
379
- const subIconStyle = subIconSize.small;
380
- if (subIconSize[size]) return subIconSize[size];
448
+ const { dimensions } = this.state;
449
+ const subIconStyle = subIconSize(dimensions?.width || null).small;
450
+ if (subIconSize(dimensions?.width || null)[size]) return subIconSize(dimensions?.width || null)[size];
381
451
  return subIconStyle;
382
452
  }
383
453
 
@@ -394,6 +464,16 @@ export default class Avatar extends Component {
394
464
  };
395
465
  }
396
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
+
397
477
  onLoadSourceError = () => {
398
478
  this.setState({ hideSource: true, ownUpdate: true });
399
479
  }
@@ -402,14 +482,14 @@ export default class Avatar extends Component {
402
482
  const { subIcon, size, subIconSource } = this.props;
403
483
  if ((!subIcon || subIcon === SubIconType.none) && !subIconSource) return <View />;
404
484
  const subIconSizeStyle = this.mapSubIconSize(size);
405
- const iconSource = ValueUtils.getImageSource(subIconSource);
485
+ const iconSource = ValueUtil.getImageSource(subIconSource);
406
486
  return (
407
487
  <View style={styles.subIcon}>
408
488
  {subIcon === SubIconType.momo
409
489
  ? (
410
490
  <Image
411
491
  style={[styles.subIconImage, subIconSizeStyle]}
412
- source={Icons.ic_round_momo_tiny}
492
+ source={IconSource.ic_round_momo_tiny}
413
493
  />
414
494
  )
415
495
  : <View />}
@@ -434,16 +514,27 @@ export default class Avatar extends Component {
434
514
 
435
515
  render() {
436
516
  const {
437
- size, source, name, resizeMode, style, onPress, isShowLoading, loading, cached, extraPropsImage
517
+ size, source, name, resizeMode, style, onPress, isShowLoading, loading, cached, extraPropsImage, quality
438
518
  } = this.props;
439
519
  const { hideSource } = this.state;
440
520
  const avatarStyle = this.mapStyleFromSize(size);
441
521
  const containerSize = this.extractSize(avatarStyle);
442
- const imageSource = ValueUtils.getImageSource(source);
522
+ const imageSource = ValueUtil.getImageSource(source);
443
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
+
444
535
  const inner = (
445
- <View style={[styles.container, avatarStyle, style]}>
446
- {(showName || hideSource) ? this.renderShortName(name, avatarStyle, size) : null}
536
+ <View style={[styles.container, containerSize, style]}>
537
+ {(showName || hideSource) ? this.renderShortName(name, avatarStyle, size) : <View />}
447
538
  {this.isValidImageUrl(imageSource) && !hideSource
448
539
  ? (
449
540
  <Image
@@ -471,54 +562,27 @@ export default class Avatar extends Component {
471
562
  }
472
563
 
473
564
  Avatar.propTypes = {
474
- /**
475
- * The default name when image load fail.
476
- */
477
565
  name: PropTypes.string,
478
- /**
479
- * How resize should the avatar be?
480
- */
481
566
  resizeMode: PropTypes.string,
482
- /**
483
- * How large should the avatar be?
484
- */
485
- size: PropTypes.oneOf(['tiny', 'small', 'medium', 'large', 'giant', 'size8', 'size24', 'size36', 'size44', 'size48', 'size52', 'size56', 'size64', 'size72', 'size80', 'size88', 'size96']),
486
- /**
487
- * What source Avatar to use?
488
- */
567
+ quality: PropTypes.oneOf(['low', 'medium', 'high']),
568
+ size: PropTypes.oneOf(['tiny', 'small', 'medium', 'large', 'giant', 'size8', 'size24', 'size36', 'size44', 'size48', 'size52', 'size56', 'size64', 'size72', 'size80', 'size88', 'size96',
569
+ PropTypes.shape({
570
+ width: PropTypes.number, height: PropTypes.number, borderRadius: PropTypes.number
571
+ })]),
489
572
  source: PropTypes.oneOfType([PropTypes.shape({ uri: PropTypes.string }), PropTypes.number, PropTypes.string]),
490
- /**
491
- * What subIcon Avatar to use?
492
- */
493
573
  subIcon: PropTypes.oneOf(['momo', 'online', 'key', 'none']),
494
- /**
495
- * What custom subIcon Avatar to use?
496
- */
497
574
  subIconSource: PropTypes.oneOfType([PropTypes.shape({ uri: PropTypes.string }), PropTypes.number, PropTypes.string]),
498
- /**
499
- * What custom style Avatar to use?
500
- */
501
575
  style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
502
- /**
503
- * Optional click handler.
504
- */
505
576
  onPress: PropTypes.func,
506
- /**
507
- * Optional loading handler.
508
- */
509
577
  loading: PropTypes.bool,
510
- /**
511
- * Optional cached handler.
512
- */
513
578
  cached: PropTypes.bool,
514
- /**
515
- * Optional extra prop of Image.
516
- */
579
+ scaleSize: PropTypes.bool,
517
580
  extraPropsImage: PropTypes.object
518
581
  };
519
582
 
520
583
  Avatar.defaultProps = {
521
584
  resizeMode: 'cover',
522
- size: 'medium',
523
- cached: true
585
+ cached: true,
586
+ scaleSize: false,
587
+ quality: 'medium',
524
588
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/avatar",
3
- "version": "0.0.38-beta",
3
+ "version": "0.0.40-beta.2",
4
4
  "private": false,
5
5
  "main": "index.js",
6
6
  "dependencies": {},
@@ -8,8 +8,8 @@
8
8
  "react-native": ">=0.55",
9
9
  "prop-types": "^15.7.2",
10
10
  "react": "16.9.0",
11
- "lodash": "^4.17.15",
12
- "@momo-kits/core": ">=0.0.5-beta"
11
+ "@momo-kits/core": ">=0.0.5-beta",
12
+ "lodash": "^4.17.15"
13
13
  },
14
14
  "devDependencies": {},
15
15
  "license": "MoMo"
package/publish.sh CHANGED
@@ -21,7 +21,7 @@ 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