@momo-kits/avatar 0.0.36-beta → 0.0.36

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 (3) hide show
  1. package/Avatar.js +324 -117
  2. package/Avatar.web.js +132 -69
  3. package/package.json +3 -3
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,14 +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 '../../utils/ValueUtil';
9
- import Colors from '../../colors';
10
- import { Icons } from '../../icons';
11
- import Text from '../typography';
12
- import { ColorCore } from '../../colors/colorCore';
8
+ import ValueUtil from '../../core/utils/ValueUtil';
9
+ import Colors from '../../core/colors';
10
+ import { Icons as IconSource } from '../../core/icons';
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
+ ];
13
42
 
14
43
  export const AvatarSize = {
15
44
  tiny: 'tiny',
@@ -78,7 +107,7 @@ const styles = StyleSheet.create({
78
107
  }
79
108
  });
80
109
 
81
- const subIconSize = StyleSheet.create({
110
+ const subIconSize = (width) => ({
82
111
  tiny: {
83
112
  width: 8,
84
113
  height: 8,
@@ -178,7 +207,7 @@ const subIconSize = StyleSheet.create({
178
207
  }
179
208
  });
180
209
 
181
- const avatarSize = StyleSheet.create({
210
+ const avatarSize = (width) => ({
182
211
  tiny: {
183
212
  width: 16,
184
213
  height: 16,
@@ -266,34 +295,57 @@ const avatarSize = StyleSheet.create({
266
295
  }
267
296
  });
268
297
 
269
- const shortName = StyleSheet.create({
298
+ const shortName = (width) => ({
270
299
  tiny: {
271
- fontSize: 5
300
+ fontSize: ScaleSize(5, width)
272
301
  },
273
302
  small: {
274
- fontSize: 11
303
+ fontSize: ScaleSize(11, width)
275
304
  },
276
305
  medium: {
277
- fontSize: 15
306
+ fontSize: ScaleSize(15, width)
278
307
  },
279
308
  large: {
280
- fontSize: 20
309
+ fontSize: ScaleSize(20, width)
281
310
  },
282
311
  giant: {
283
- fontSize: 25
312
+ fontSize: ScaleSize(25, width)
284
313
  }
285
314
  });
286
315
 
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,
293
- source: props.source
323
+ source: props.source,
324
+ dimensions: {}
294
325
  };
295
326
  }
296
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
+
297
349
  static getDerivedStateFromProps(nextProps, prevState) {
298
350
  if (prevState.ownUpdate) {
299
351
  return {
@@ -336,49 +388,66 @@ export default class Avatar extends Component {
336
388
  }
337
389
 
338
390
  isValidImageUrl = (source) => {
339
- const validSource = source && (((source.uri && this.isUrl(source.uri)) || (typeof source === 'number')));
391
+ const validSource = source && source.uri && this.isUrl(source.uri);
340
392
  return validSource;
341
393
  }
342
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
+
343
409
  mapStyleFromSize = (size) => {
344
- const avatarStyle = avatarSize.small;
410
+ const { dimensions } = this.state;
411
+ const avatarStyle = avatarSize(dimensions?.width || null).small;
345
412
  if (typeof size === 'object') return size;
346
- if (avatarSize[size]) return avatarSize[size];
413
+ if (avatarSize(dimensions?.width || null)[size]) return avatarSize(dimensions?.width || null)[size];
347
414
  return avatarStyle;
348
415
  }
349
416
 
350
417
  mapShortNameStyle = (size) => {
351
418
  let shortNameStyle = {};
419
+ const { dimensions } = this.state;
352
420
  switch (size) {
353
421
  case AvatarSize.tiny: {
354
- shortNameStyle = shortName.tiny;
422
+ shortNameStyle = shortName(dimensions?.width || null).tiny;
355
423
  break;
356
424
  }
357
425
  case AvatarSize.small: {
358
- shortNameStyle = shortName.small;
426
+ shortNameStyle = shortName(dimensions?.width || null).small;
359
427
  break;
360
428
  }
361
429
  case AvatarSize.medium: {
362
- shortNameStyle = shortName.medium;
430
+ shortNameStyle = shortName(dimensions?.width || null).medium;
363
431
  break;
364
432
  }
365
433
  case AvatarSize.large: {
366
- shortNameStyle = shortName.large;
434
+ shortNameStyle = shortName(dimensions?.width || null).large;
367
435
  break;
368
436
  }
369
437
  case AvatarSize.giant: {
370
- shortNameStyle = shortName.giant;
438
+ shortNameStyle = shortName(dimensions?.width || null).giant;
371
439
  break;
372
440
  }
373
441
  default:
374
- shortNameStyle = shortName.small;
442
+ shortNameStyle = shortName(dimensions?.width || null).small;
375
443
  }
376
444
  return shortNameStyle;
377
445
  }
378
446
 
379
447
  mapSubIconSize = (size) => {
380
- const subIconStyle = subIconSize.small;
381
- 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];
382
451
  return subIconStyle;
383
452
  }
384
453
 
@@ -391,10 +460,20 @@ export default class Avatar extends Component {
391
460
  height: containerWidth,
392
461
  borderRadius: containerWidth / 2,
393
462
  borderWidth,
394
- borderColor: ColorCore.black_01
463
+ borderColor: Colors.black_01
395
464
  };
396
465
  }
397
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
+
398
477
  onLoadSourceError = () => {
399
478
  this.setState({ hideSource: true, ownUpdate: true });
400
479
  }
@@ -403,14 +482,14 @@ export default class Avatar extends Component {
403
482
  const { subIcon, size, subIconSource } = this.props;
404
483
  if ((!subIcon || subIcon === SubIconType.none) && !subIconSource) return <View />;
405
484
  const subIconSizeStyle = this.mapSubIconSize(size);
406
- const iconSource = ValueUtils.getImageSource(subIconSource);
485
+ const iconSource = ValueUtil.getImageSource(subIconSource);
407
486
  return (
408
487
  <View style={styles.subIcon}>
409
488
  {subIcon === SubIconType.momo
410
489
  ? (
411
490
  <Image
412
491
  style={[styles.subIconImage, subIconSizeStyle]}
413
- source={Icons.ic_round_momo_tiny}
492
+ source={IconSource.ic_round_momo_tiny}
414
493
  />
415
494
  )
416
495
  : <View />}
@@ -435,16 +514,27 @@ export default class Avatar extends Component {
435
514
 
436
515
  render() {
437
516
  const {
438
- size, source, name, resizeMode, style, onPress, isShowLoading, loading, cached, extraPropsImage
517
+ size, source, name, resizeMode, style, onPress, isShowLoading, loading, cached, extraPropsImage, quality
439
518
  } = this.props;
440
519
  const { hideSource } = this.state;
441
520
  const avatarStyle = this.mapStyleFromSize(size);
442
521
  const containerSize = this.extractSize(avatarStyle);
443
- const imageSource = ValueUtils.getImageSource(source);
522
+ const imageSource = ValueUtil.getImageSource(source);
444
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
+
445
535
  const inner = (
446
- <View style={[styles.container, avatarStyle, style]}>
447
- {(showName || hideSource) ? this.renderShortName(name, avatarStyle, size) : null}
536
+ <View style={[styles.container, containerSize, style]}>
537
+ {(showName || hideSource) ? this.renderShortName(name, avatarStyle, size) : <View />}
448
538
  {this.isValidImageUrl(imageSource) && !hideSource
449
539
  ? (
450
540
  <Image
@@ -472,54 +562,27 @@ export default class Avatar extends Component {
472
562
  }
473
563
 
474
564
  Avatar.propTypes = {
475
- /**
476
- * The default name when image load fail.
477
- */
478
565
  name: PropTypes.string,
479
- /**
480
- * How resize should the avatar be?
481
- */
482
566
  resizeMode: PropTypes.string,
483
- /**
484
- * How large should the avatar be?
485
- */
486
- size: PropTypes.oneOf(['tiny', 'small', 'medium', 'large', 'giant', 'size8', 'size24', 'size36', 'size44', 'size48', 'size52', 'size56', 'size64', 'size72', 'size80', 'size88', 'size96']),
487
- /**
488
- * What source Avatar to use?
489
- */
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
+ })]),
490
572
  source: PropTypes.oneOfType([PropTypes.shape({ uri: PropTypes.string }), PropTypes.number, PropTypes.string]),
491
- /**
492
- * What subIcon Avatar to use?
493
- */
494
573
  subIcon: PropTypes.oneOf(['momo', 'online', 'key', 'none']),
495
- /**
496
- * What custom subIcon Avatar to use?
497
- */
498
574
  subIconSource: PropTypes.oneOfType([PropTypes.shape({ uri: PropTypes.string }), PropTypes.number, PropTypes.string]),
499
- /**
500
- * What custom style Avatar to use?
501
- */
502
575
  style: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
503
- /**
504
- * Optional click handler.
505
- */
506
576
  onPress: PropTypes.func,
507
- /**
508
- * Optional loading handler.
509
- */
510
577
  loading: PropTypes.bool,
511
- /**
512
- * Optional cached handler.
513
- */
514
578
  cached: PropTypes.bool,
515
- /**
516
- * Optional extra prop of Image.
517
- */
579
+ scaleSize: PropTypes.bool,
518
580
  extraPropsImage: PropTypes.object
519
581
  };
520
582
 
521
583
  Avatar.defaultProps = {
522
584
  resizeMode: 'cover',
523
- size: 'medium',
524
- cached: true
585
+ cached: true,
586
+ scaleSize: false,
587
+ quality: 'medium',
525
588
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/avatar",
3
- "version": "0.0.36-beta",
3
+ "version": "0.0.36",
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"