Browse Source

feat: initial based avatars

master
Bettenbuk Zoltan 5 years ago
parent
commit
72137a2811
49 changed files with 899 additions and 697 deletions
  1. 14
    0
      css/_avatar.scss
  2. 0
    7
      css/_videolayout_default.scss
  3. 1
    0
      css/main.scss
  4. BIN
      images/avatar.png
  5. 2
    2
      modules/UI/UI.js
  6. 1
    1
      modules/UI/shared_video/SharedVideoThumb.js
  7. 10
    16
      modules/UI/videolayout/LargeVideoManager.js
  8. 1
    5
      modules/UI/videolayout/LocalVideo.js
  9. 10
    9
      modules/UI/videolayout/SmallVideo.js
  10. 1
    1
      modules/UI/videolayout/VideoLayout.js
  11. 2
    2
      package-lock.json
  12. 1
    1
      package.json
  13. 177
    0
      react/features/base/avatar/components/AbstractAvatar.js
  14. 3
    0
      react/features/base/avatar/components/index.native.js
  15. 3
    0
      react/features/base/avatar/components/index.web.js
  16. 106
    0
      react/features/base/avatar/components/native/Avatar.js
  17. 50
    0
      react/features/base/avatar/components/native/RemoteAvatar.js
  18. 3
    0
      react/features/base/avatar/components/native/index.js
  19. 47
    0
      react/features/base/avatar/components/native/styles.js
  20. 98
    0
      react/features/base/avatar/components/web/Avatar.js
  21. 3
    0
      react/features/base/avatar/components/web/index.js
  22. 54
    0
      react/features/base/avatar/functions.js
  23. 3
    0
      react/features/base/avatar/index.js
  24. 16
    0
      react/features/base/participants/actionTypes.js
  25. 25
    1
      react/features/base/participants/actions.js
  26. 0
    320
      react/features/base/participants/components/Avatar.native.js
  27. 0
    38
      react/features/base/participants/components/Avatar.web.js
  28. 9
    56
      react/features/base/participants/components/ParticipantView.native.js
  29. 2
    1
      react/features/base/participants/components/index.js
  30. 9
    7
      react/features/base/participants/components/styles.js
  31. 84
    2
      react/features/base/participants/functions.js
  32. 23
    18
      react/features/base/participants/middleware.js
  33. 16
    0
      react/features/base/participants/preloadImage.native.js
  34. 24
    0
      react/features/base/participants/preloadImage.web.js
  35. 6
    1
      react/features/base/participants/reducer.js
  36. 8
    75
      react/features/base/react/components/native/AvatarListItem.js
  37. 0
    35
      react/features/base/react/components/native/styles.js
  38. 16
    0
      react/features/base/util/helpers.js
  39. 0
    23
      react/features/chat/components/AbstractChatMessage.js
  40. 5
    9
      react/features/chat/components/native/ChatMessage.js
  41. 26
    2
      react/features/external-api/middleware.js
  42. 5
    6
      react/features/invite/components/callee-info/CalleeInfo.js
  43. 1
    1
      react/features/mobile/incoming-call/components/IncomingCallPage.js
  44. 7
    10
      react/features/overlay/components/web/FilmstripOnlyOverlayFrame.js
  45. 6
    15
      react/features/remote-video-menu/components/native/RemoteVideoMenu.js
  46. 5
    0
      react/features/settings/components/web/ProfileTab.js
  47. 5
    8
      react/features/toolbox/components/web/OverflowMenuProfileItem.js
  48. 11
    17
      react/features/welcome/components/WelcomePageSideBar.native.js
  49. 0
    8
      react/features/welcome/components/styles.js

+ 14
- 0
css/_avatar.scss View File

@@ -0,0 +1,14 @@
1
+.avatar {
2
+    align-items: center;
3
+    background-color: #AAA;
4
+    display: flex;
5
+    border-radius: 50%;
6
+    color: rgba(255, 255, 255, 0.6);
7
+    font-weight: 100;
8
+    justify-content: center;
9
+    object-fit: cover;
10
+}
11
+
12
+.defaultAvatar {
13
+    opacity: 0.6
14
+}

+ 0
- 7
css/_videolayout_default.scss View File

@@ -493,7 +493,6 @@
493 493
 }
494 494
 
495 495
 #dominantSpeakerAvatarContainer,
496
-#dominantSpeakerAvatar,
497 496
 .dynamic-shadow {
498 497
     width: 200px;
499 498
     height: 200px;
@@ -503,14 +502,9 @@
503 502
     top: 50px;
504 503
     margin: auto;
505 504
     position: relative;
506
-    border-radius: 100px;
507 505
     overflow: hidden;
508 506
     visibility: inherit;
509 507
 }
510
-#dominantSpeakerAvatar {
511
-    background-color: #000000;
512
-    object-fit: cover;
513
-}
514 508
 
515 509
 .dynamic-shadow {
516 510
     border-radius: 50%;
@@ -524,7 +518,6 @@
524 518
 .avatar-container {
525 519
     @include maxSize(60px);
526 520
     @include absoluteAligning();
527
-    border-radius: 50%;
528 521
     display: flex;
529 522
     justify-content: center;
530 523
     height: 50%;

+ 1
- 0
css/main.scss View File

@@ -86,5 +86,6 @@ $flagsImagePath: "../images/";
86 86
 @import 'navigate_section_list';
87 87
 @import 'third-party-branding/google';
88 88
 @import 'third-party-branding/microsoft';
89
+@import 'avatar';
89 90
 
90 91
 /* Modules END */

BIN
images/avatar.png View File


+ 2
- 2
modules/UI/UI.js View File

@@ -510,8 +510,8 @@ UI.dockToolbar = dock => APP.store.dispatch(dockToolbox(dock));
510 510
  * @param {string} avatarURL - The URL to avatar image to display.
511 511
  * @returns {void}
512 512
  */
513
-UI.refreshAvatarDisplay = function(id, avatarURL) {
514
-    VideoLayout.changeUserAvatar(id, avatarURL);
513
+UI.refreshAvatarDisplay = function(id) {
514
+    VideoLayout.changeUserAvatar(id);
515 515
 };
516 516
 
517 517
 /**

+ 1
- 1
modules/UI/shared_video/SharedVideoThumb.js View File

@@ -33,7 +33,7 @@ SharedVideoThumb.prototype.constructor = SharedVideoThumb;
33 33
 SharedVideoThumb.prototype.setDeviceAvailabilityIcons = function() {};
34 34
 
35 35
 // eslint-disable-next-line no-empty-function
36
-SharedVideoThumb.prototype.avatarChanged = function() {};
36
+SharedVideoThumb.prototype.initializeAvatar = function() {};
37 37
 
38 38
 SharedVideoThumb.prototype.createContainer = function(spanId) {
39 39
     const container = document.createElement('span');

+ 10
- 16
modules/UI/videolayout/LargeVideoManager.js View File

@@ -5,11 +5,8 @@ import ReactDOM from 'react-dom';
5 5
 import { I18nextProvider } from 'react-i18next';
6 6
 import { Provider } from 'react-redux';
7 7
 
8
+import { Avatar } from '../../../react/features/base/avatar';
8 9
 import { i18next } from '../../../react/features/base/i18n';
9
-import {
10
-    Avatar,
11
-    getAvatarURLByParticipantId
12
-} from '../../../react/features/base/participants';
13 10
 import { PresenceLabel } from '../../../react/features/presence-status';
14 11
 /* eslint-enable no-unused-vars */
15 12
 
@@ -214,8 +211,7 @@ export default class LargeVideoManager {
214 211
             container.setStream(id, stream, videoType);
215 212
 
216 213
             // change the avatar url on large
217
-            this.updateAvatar(
218
-                getAvatarURLByParticipantId(APP.store.getState(), id));
214
+            this.updateAvatar();
219 215
 
220 216
             // If the user's connection is disrupted then the avatar will be
221 217
             // displayed in case we have no video image cached. That is if
@@ -406,18 +402,16 @@ export default class LargeVideoManager {
406 402
     /**
407 403
      * Updates the src of the dominant speaker avatar
408 404
      */
409
-    updateAvatar(avatarUrl) {
410
-        if (avatarUrl) {
411
-            ReactDOM.render(
405
+    updateAvatar() {
406
+        ReactDOM.render(
407
+            <Provider store = { APP.store }>
412 408
                 <Avatar
413 409
                     id = "dominantSpeakerAvatar"
414
-                    uri = { avatarUrl } />,
415
-                this._dominantSpeakerAvatarContainer
416
-            );
417
-        } else {
418
-            ReactDOM.unmountComponentAtNode(
419
-                this._dominantSpeakerAvatarContainer);
420
-        }
410
+                    participantId = { this.id }
411
+                    size = { 200 } />
412
+            </Provider>,
413
+            this._dominantSpeakerAvatarContainer
414
+        );
421 415
     }
422 416
 
423 417
     /**

+ 1
- 5
modules/UI/videolayout/LocalVideo.js View File

@@ -7,9 +7,6 @@ import { Provider } from 'react-redux';
7 7
 
8 8
 import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
9 9
 import { VideoTrack } from '../../../react/features/base/media';
10
-import {
11
-    getAvatarURLByParticipantId
12
-} from '../../../react/features/base/participants';
13 10
 import { updateSettings } from '../../../react/features/base/settings';
14 11
 import { getLocalVideoTrack } from '../../../react/features/base/tracks';
15 12
 import { shouldDisplayTileView } from '../../../react/features/video-layout';
@@ -55,8 +52,7 @@ function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
55 52
     // Initialize the avatar display with an avatar url selected from the redux
56 53
     // state. Redux stores the local user with a hardcoded participant id of
57 54
     // 'local' if no id has been assigned yet.
58
-    this.avatarChanged(
59
-        getAvatarURLByParticipantId(APP.store.getState(), this.id));
55
+    this.initializeAvatar();
60 56
 
61 57
     this.addAudioLevelIndicator();
62 58
     this.updateIndicators();

+ 10
- 9
modules/UI/videolayout/SmallVideo.js View File

@@ -10,9 +10,8 @@ import { Provider } from 'react-redux';
10 10
 import { i18next } from '../../../react/features/base/i18n';
11 11
 import { AudioLevelIndicator }
12 12
     from '../../../react/features/audio-level-indicator';
13
+import { Avatar as AvatarDisplay } from '../../../react/features/base/avatar';
13 14
 import {
14
-    Avatar as AvatarDisplay,
15
-    getAvatarURLByParticipantId,
16 15
     getPinnedParticipant,
17 16
     pinParticipant
18 17
 } from '../../../react/features/base/participants';
@@ -570,8 +569,7 @@ SmallVideo.prototype.updateView = function() {
570 569
     if (!this.hasAvatar) {
571 570
         if (this.id) {
572 571
             // Init avatar
573
-            this.avatarChanged(
574
-                getAvatarURLByParticipantId(APP.store.getState(), this.id));
572
+            this.initializeAvatar();
575 573
         } else {
576 574
             logger.error('Unable to init avatar - no id', this);
577 575
 
@@ -609,19 +607,22 @@ SmallVideo.prototype.updateView = function() {
609 607
  * Updates the react component displaying the avatar with the passed in avatar
610 608
  * url.
611 609
  *
612
- * @param {string} avatarUrl - The uri to the avatar image.
613 610
  * @returns {void}
614 611
  */
615
-SmallVideo.prototype.avatarChanged = function(avatarUrl) {
612
+SmallVideo.prototype.initializeAvatar = function() {
616 613
     const thumbnail = this.$avatar().get(0);
617 614
 
618 615
     this.hasAvatar = true;
619 616
 
620 617
     if (thumbnail) {
618
+        // Maybe add a special case for local participant, as on init of
619
+        // LocalVideo.js the id is set to "local" but will get updated later.
621 620
         ReactDOM.render(
622
-            <AvatarDisplay
623
-                className = 'userAvatar'
624
-                uri = { avatarUrl } />,
621
+            <Provider store = { APP.store }>
622
+                <AvatarDisplay
623
+                    className = 'userAvatar'
624
+                    participantId = { this.id } />
625
+            </Provider>,
625 626
             thumbnail
626 627
         );
627 628
     }

+ 1
- 1
modules/UI/videolayout/VideoLayout.js View File

@@ -880,7 +880,7 @@ const VideoLayout = {
880 880
         const smallVideo = VideoLayout.getSmallVideo(id);
881 881
 
882 882
         if (smallVideo) {
883
-            smallVideo.avatarChanged(avatarUrl);
883
+            smallVideo.initializeAvatar();
884 884
         } else {
885 885
             logger.warn(
886 886
                 `Missed avatar update - no small video yet for ${id}`

+ 2
- 2
package-lock.json View File

@@ -8738,8 +8738,8 @@
8738 8738
       "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
8739 8739
     },
8740 8740
     "js-utils": {
8741
-      "version": "github:jitsi/js-utils#73a67a7a60d52f8e895f50939c8fcbd1f20fe7b5",
8742
-      "from": "github:jitsi/js-utils#73a67a7a60d52f8e895f50939c8fcbd1f20fe7b5",
8741
+      "version": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
8742
+      "from": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
8743 8743
       "requires": {
8744 8744
         "bowser": "1.9.1",
8745 8745
         "js-md5": "0.7.3"

+ 1
- 1
package.json View File

@@ -49,7 +49,7 @@
49 49
     "jquery-contextmenu": "2.4.5",
50 50
     "jquery-i18next": "1.2.0",
51 51
     "js-md5": "0.6.1",
52
-    "js-utils": "github:jitsi/js-utils#73a67a7a60d52f8e895f50939c8fcbd1f20fe7b5",
52
+    "js-utils": "github:jitsi/js-utils#192b1c996e8c05530eb1f19e82a31069c3021e31",
53 53
     "jsrsasign": "8.0.12",
54 54
     "jwt-decode": "2.2.0",
55 55
     "lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#9bcc2a26cc94683b8ed302418695a331b450df97",

+ 177
- 0
react/features/base/avatar/components/AbstractAvatar.js View File

@@ -0,0 +1,177 @@
1
+// @flow
2
+
3
+import { PureComponent } from 'react';
4
+
5
+import { getParticipantById } from '../../participants';
6
+
7
+import { getAvatarColor, getInitials } from '../functions';
8
+
9
+export type Props = {
10
+
11
+    /**
12
+     * The string we base the initials on (this is generated from a list of precendences).
13
+     */
14
+    _initialsBase: ?string,
15
+
16
+    /**
17
+     * An URL that we validated that it can be loaded.
18
+     */
19
+    _loadableAvatarUrl: ?string,
20
+
21
+    /**
22
+     * A string to override the initials to generate a color of. This is handy if you don't want to make
23
+     * the background color match the string that the initials are generated from.
24
+     */
25
+    colorBase?: string,
26
+
27
+    /**
28
+     * Display name of the entity to render an avatar for (if any). This is handy when we need
29
+     * an avatar for a non-participasnt entity (e.g. a recent list item).
30
+     */
31
+    displayName?: string,
32
+
33
+    /**
34
+     * The ID of the participant to render an avatar for (if it's a participant avatar).
35
+     */
36
+    participantId?: string,
37
+
38
+    /**
39
+     * The size of the avatar.
40
+     */
41
+    size: number,
42
+
43
+    /**
44
+     * URI of the avatar, if any.
45
+     */
46
+    uri: ?string,
47
+}
48
+
49
+type State = {
50
+    avatarFailed: boolean
51
+}
52
+
53
+export const DEFAULT_SIZE = 65;
54
+
55
+/**
56
+ * Implements an abstract class to render avatars in the app.
57
+ */
58
+export default class AbstractAvatar<P: Props> extends PureComponent<P, State> {
59
+    /**
60
+     * Instantiates a new {@code Component}.
61
+     *
62
+     * @inheritdoc
63
+     */
64
+    constructor(props: P) {
65
+        super(props);
66
+
67
+        this.state = {
68
+            avatarFailed: false
69
+        };
70
+
71
+        this._onAvatarLoadError = this._onAvatarLoadError.bind(this);
72
+    }
73
+
74
+    /**
75
+     * Implements {@code Component#componentDidUpdate}.
76
+     *
77
+     * @inheritdoc
78
+     */
79
+    componentDidUpdate(prevProps: P) {
80
+        if (prevProps.uri !== this.props.uri) {
81
+
82
+            // URI changed, so we need to try to fetch it again.
83
+            // Eslint doesn't like this statement, but based on the React doc, it's safe if it's
84
+            // wrapped in a condition: https://reactjs.org/docs/react-component.html#componentdidupdate
85
+
86
+            // eslint-disable-next-line react/no-did-update-set-state
87
+            this.setState({
88
+                avatarFailed: false
89
+            });
90
+        }
91
+    }
92
+
93
+    /**
94
+     * Implements {@code Componenr#render}.
95
+     *
96
+     * @inheritdoc
97
+     */
98
+    render() {
99
+        const {
100
+            _initialsBase,
101
+            _loadableAvatarUrl,
102
+            colorBase,
103
+            uri
104
+        } = this.props;
105
+        const { avatarFailed } = this.state;
106
+
107
+        // _loadableAvatarUrl is validated that it can be loaded, but uri (if present) is not, so
108
+        // we still need to do a check for that. And an explicitly provided URI is higher priority than
109
+        // an avatar URL anyhow.
110
+        if ((uri && !avatarFailed) || _loadableAvatarUrl) {
111
+            return this._renderURLAvatar((!avatarFailed && uri) || _loadableAvatarUrl);
112
+        }
113
+
114
+        const _initials = getInitials(_initialsBase);
115
+
116
+        if (_initials) {
117
+            return this._renderInitialsAvatar(_initials, getAvatarColor(colorBase || _initialsBase));
118
+        }
119
+
120
+        return this._renderDefaultAvatar();
121
+    }
122
+
123
+    _onAvatarLoadError: () => void;
124
+
125
+    /**
126
+     * Callback to handle the error while loading of the avatar URI.
127
+     *
128
+     * @returns {void}
129
+     */
130
+    _onAvatarLoadError() {
131
+        this.setState({
132
+            avatarFailed: true
133
+        });
134
+    }
135
+
136
+    /**
137
+     * Function to render the actual, platform specific default avatar component.
138
+     *
139
+     * @returns {React$Element<*>}
140
+     */
141
+    _renderDefaultAvatar: () => React$Element<*>
142
+
143
+    /**
144
+     * Function to render the actual, platform specific initials-based avatar component.
145
+     *
146
+     * @param {string} initials - The initials to use.
147
+     * @param {string} color - The color to use.
148
+     * @returns {React$Element<*>}
149
+     */
150
+    _renderInitialsAvatar: (string, string) => React$Element<*>
151
+
152
+    /**
153
+     * Function to render the actual, platform specific URL-based avatar component.
154
+     *
155
+     * @param {string} uri - The URI of the avatar.
156
+     * @returns {React$Element<*>}
157
+     */
158
+    _renderURLAvatar: ?string => React$Element<*>
159
+}
160
+
161
+/**
162
+ * Maps part of the Redux state to the props of this component.
163
+ *
164
+ * @param {Object} state - The Redux state.
165
+ * @param {Props} ownProps - The own props of the component.
166
+ * @returns {Props}
167
+ */
168
+export function _mapStateToProps(state: Object, ownProps: Props) {
169
+    const { displayName, participantId } = ownProps;
170
+    const _participant = participantId && getParticipantById(state, participantId);
171
+    const _initialsBase = (_participant && (_participant.name || _participant.email)) || displayName;
172
+
173
+    return {
174
+        _initialsBase,
175
+        _loadableAvatarUrl: _participant && _participant.loadableAvatarUrl
176
+    };
177
+}

+ 3
- 0
react/features/base/avatar/components/index.native.js View File

@@ -0,0 +1,3 @@
1
+// @flow
2
+
3
+export * from './native';

+ 3
- 0
react/features/base/avatar/components/index.web.js View File

@@ -0,0 +1,3 @@
1
+// @flow
2
+
3
+export * from './web';

+ 106
- 0
react/features/base/avatar/components/native/Avatar.js View File

@@ -0,0 +1,106 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { Image, Text, View } from 'react-native';
5
+
6
+import { connect } from '../../../redux';
7
+import { type StyleType } from '../../../styles';
8
+
9
+import AbstractAvatar, {
10
+    _mapStateToProps,
11
+    type Props as AbstractProps,
12
+    DEFAULT_SIZE
13
+} from '../AbstractAvatar';
14
+
15
+import RemoteAvatar, { DEFAULT_AVATAR } from './RemoteAvatar';
16
+import styles from './styles';
17
+
18
+type Props = AbstractProps & {
19
+
20
+    /**
21
+     * External style of the component.
22
+     */
23
+    style?: StyleType
24
+}
25
+
26
+/**
27
+ * Implements an avatar component that has 4 ways to render an avatar:
28
+ *
29
+ * - Based on an explicit avatar URI, if provided
30
+ * - Gravatar, if there is any
31
+ * - Based on initials generated from name or email
32
+ * - Default avatar icon, if any of the above fails
33
+ */
34
+class Avatar extends AbstractAvatar<Props> {
35
+
36
+    _onAvatarLoadError: () => void;
37
+
38
+    /**
39
+     * Implements {@code AbstractAvatar#_renderDefaultAvatar}.
40
+     *
41
+     * @inheritdoc
42
+     */
43
+    _renderDefaultAvatar() {
44
+        return this._wrapAvatar(
45
+            <Image
46
+                source = { DEFAULT_AVATAR }
47
+                style = { [
48
+                    styles.avatarContent(this.props.size || DEFAULT_SIZE),
49
+                    styles.staticAvatar
50
+                ] } />
51
+        );
52
+    }
53
+
54
+    /**
55
+     * Implements {@code AbstractAvatar#_renderGravatar}.
56
+     *
57
+     * @inheritdoc
58
+     */
59
+    _renderInitialsAvatar(initials, color) {
60
+        return this._wrapAvatar(
61
+            <View
62
+                style = { [
63
+                    styles.initialsContainer,
64
+                    {
65
+                        backgroundColor: color
66
+                    }
67
+                ] }>
68
+                <Text style = { styles.initialsText(this.props.size || DEFAULT_SIZE) }> { initials } </Text>
69
+            </View>
70
+        );
71
+    }
72
+
73
+    /**
74
+     * Implements {@code AbstractAvatar#_renderGravatar}.
75
+     *
76
+     * @inheritdoc
77
+     */
78
+    _renderURLAvatar(uri) {
79
+        return this._wrapAvatar(
80
+            <RemoteAvatar
81
+                onError = { this._onAvatarLoadError }
82
+                size = { this.props.size || DEFAULT_SIZE }
83
+                uri = { uri } />
84
+        );
85
+    }
86
+
87
+    /**
88
+     * Wraps an avatar into a common wrapper.
89
+     *
90
+     * @param {React#Component} avatar - The avatar component.
91
+     * @returns {React#Component}
92
+     */
93
+    _wrapAvatar(avatar) {
94
+        return (
95
+            <View
96
+                style = { [
97
+                    styles.avatarContainer(this.props.size || DEFAULT_SIZE),
98
+                    this.props.style
99
+                ] }>
100
+                { avatar }
101
+            </View>
102
+        );
103
+    }
104
+}
105
+
106
+export default connect(_mapStateToProps)(Avatar);

+ 50
- 0
react/features/base/avatar/components/native/RemoteAvatar.js View File

@@ -0,0 +1,50 @@
1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+import { Image } from 'react-native';
5
+
6
+import styles from './styles';
7
+
8
+export const DEFAULT_AVATAR = require('../../../../../../images/avatar.png');
9
+
10
+type Props = {
11
+
12
+    /**
13
+     * Callback for load errors.
14
+     */
15
+    onError: Function,
16
+
17
+    /**
18
+     * Size of the avatar.
19
+     */
20
+    size: number,
21
+
22
+    /**
23
+     * URI of the avatar to load.
24
+     */
25
+    uri: string
26
+};
27
+
28
+/**
29
+ * Implements a private class that is used to fetch and render remote avatars based on an URI.
30
+ */
31
+export default class RemoteAvatar extends PureComponent<Props> {
32
+
33
+    /**
34
+     * Implements {@code Component#render}.
35
+     *
36
+     * @inheritdoc
37
+     */
38
+    render() {
39
+        const { onError, size, uri } = this.props;
40
+
41
+        return (
42
+            <Image
43
+                defaultSource = { DEFAULT_AVATAR }
44
+                onError = { onError }
45
+                resizeMode = 'cover'
46
+                source = {{ uri }}
47
+                style = { styles.avatarContent(size) } />
48
+        );
49
+    }
50
+}

+ 3
- 0
react/features/base/avatar/components/native/index.js View File

@@ -0,0 +1,3 @@
1
+// @flow
2
+
3
+export { default as Avatar } from './Avatar';

+ 47
- 0
react/features/base/avatar/components/native/styles.js View File

@@ -0,0 +1,47 @@
1
+// @flow
2
+
3
+import { ColorPalette } from '../../../styles';
4
+
5
+/**
6
+ * The styles of the feature base/participants.
7
+ */
8
+export default {
9
+
10
+    avatarContainer: (size: number) => {
11
+        return {
12
+            alignItems: 'center',
13
+            borderRadius: size / 2,
14
+            height: size,
15
+            justifyContent: 'center',
16
+            overflow: 'hidden',
17
+            width: size
18
+        };
19
+    },
20
+
21
+    avatarContent: (size: number) => {
22
+        return {
23
+            height: size,
24
+            width: size
25
+        };
26
+    },
27
+
28
+    initialsContainer: {
29
+        alignItems: 'center',
30
+        alignSelf: 'stretch',
31
+        flex: 1,
32
+        justifyContent: 'center'
33
+    },
34
+
35
+    initialsText: (size: number) => {
36
+        return {
37
+            color: 'rgba(255, 255, 255, 0.6)',
38
+            fontSize: size * 0.5,
39
+            fontWeight: '100'
40
+        };
41
+    },
42
+
43
+    staticAvatar: {
44
+        backgroundColor: ColorPalette.lightGrey,
45
+        opacity: 0.4
46
+    }
47
+};

+ 98
- 0
react/features/base/avatar/components/web/Avatar.js View File

@@ -0,0 +1,98 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+import { connect } from '../../../redux';
6
+
7
+import AbstractAvatar, {
8
+    _mapStateToProps,
9
+    type Props as AbstractProps
10
+} from '../AbstractAvatar';
11
+
12
+type Props = AbstractProps & {
13
+    className?: string,
14
+    id: string
15
+};
16
+
17
+/**
18
+ * Implements an avatar as a React/Web {@link Component}.
19
+ */
20
+class Avatar extends AbstractAvatar<Props> {
21
+    /**
22
+     * Constructs a style object to be used on the avatars.
23
+     *
24
+     * @param {string?} color - The desired background color.
25
+     * @returns {Object}
26
+     */
27
+    _getAvatarStyle(color) {
28
+        const { size } = this.props;
29
+
30
+        return {
31
+            backgroundColor: color || undefined,
32
+            fontSize: size ? size * 0.5 : '180%',
33
+            height: size || '100%',
34
+            width: size || '100%'
35
+        };
36
+    }
37
+
38
+    /**
39
+     * Constructs a list of class names required for the avatar component.
40
+     *
41
+     * @param {string} additional - Any additional class to add.
42
+     * @returns {string}
43
+     */
44
+    _getAvatarClassName(additional) {
45
+        return `avatar ${additional || ''} ${this.props.className || ''}`;
46
+    }
47
+
48
+    _onAvatarLoadError: () => void;
49
+
50
+    /**
51
+     * Implements {@code AbstractAvatar#_renderDefaultAvatar}.
52
+     *
53
+     * @inheritdoc
54
+     */
55
+    _renderDefaultAvatar() {
56
+        return (
57
+            <img
58
+                className = { this._getAvatarClassName('defaultAvatar') }
59
+                id = { this.props.id }
60
+                src = '/images/avatar.png'
61
+                style = { this._getAvatarStyle() } />
62
+        );
63
+    }
64
+
65
+    /**
66
+     * Implements {@code AbstractAvatar#_renderGravatar}.
67
+     *
68
+     * @inheritdoc
69
+     */
70
+    _renderInitialsAvatar(initials, color) {
71
+        return (
72
+            <div
73
+                className = { this._getAvatarClassName() }
74
+                id = { this.props.id }
75
+                style = { this._getAvatarStyle(color) }>
76
+                { initials }
77
+            </div>
78
+        );
79
+    }
80
+
81
+    /**
82
+     * Implements {@code AbstractAvatar#_renderGravatar}.
83
+     *
84
+     * @inheritdoc
85
+     */
86
+    _renderURLAvatar(uri) {
87
+        return (
88
+            <img
89
+                className = { this._getAvatarClassName() }
90
+                id = { this.props.id }
91
+                onError = { this._onAvatarLoadError }
92
+                src = { uri }
93
+                style = { this._getAvatarStyle() } />
94
+        );
95
+    }
96
+}
97
+
98
+export default connect(_mapStateToProps)(Avatar);

+ 3
- 0
react/features/base/avatar/components/web/index.js View File

@@ -0,0 +1,3 @@
1
+// @flow
2
+
3
+export { default as Avatar } from './Avatar';

+ 54
- 0
react/features/base/avatar/functions.js View File

@@ -0,0 +1,54 @@
1
+// @flow
2
+
3
+import _ from 'lodash';
4
+
5
+const AVATAR_COLORS = [
6
+    '232, 105, 156',
7
+    '255, 198, 115',
8
+    '128, 128, 255',
9
+    '105, 232, 194',
10
+    '234, 255, 128'
11
+];
12
+
13
+const AVATAR_OPACITY = 0.4;
14
+
15
+/**
16
+ * Generates the background color of an initials based avatar.
17
+ *
18
+ * @param {string?} initials - The initials of the avatar.
19
+ * @returns {string}
20
+ */
21
+export function getAvatarColor(initials: ?string) {
22
+    let colorIndex = 0;
23
+
24
+    if (initials) {
25
+        let nameHash = 0;
26
+
27
+        for (const s of initials) {
28
+            nameHash += s.codePointAt(0);
29
+        }
30
+
31
+        colorIndex = nameHash % AVATAR_COLORS.length;
32
+    }
33
+
34
+    return `rgba(${AVATAR_COLORS[colorIndex]}, ${AVATAR_OPACITY})`;
35
+}
36
+
37
+/**
38
+ * Generates initials for a simple string.
39
+ *
40
+ * @param {string?} s - The string to generate initials for.
41
+ * @returns {string?}
42
+ */
43
+export function getInitials(s: ?string) {
44
+    // We don't want to use the domain part of an email address, if it is one
45
+    const initialsBasis = _.split(s, '@')[0];
46
+    const words = _.words(initialsBasis);
47
+    let initials = '';
48
+
49
+    for (const w of words) {
50
+        (initials.length < 2) && (initials += w.substr(0, 1).toUpperCase());
51
+    }
52
+
53
+    return initials;
54
+}

+ 3
- 0
react/features/base/avatar/index.js View File

@@ -0,0 +1,3 @@
1
+// @flow
2
+
3
+export * from './components';

+ 16
- 0
react/features/base/participants/actionTypes.js View File

@@ -1,3 +1,5 @@
1
+// @flow
2
+
1 3
 /**
2 4
  * Create an action for when dominant speaker changes.
3 5
  *
@@ -132,3 +134,17 @@ export const HIDDEN_PARTICIPANT_JOINED = 'HIDDEN_PARTICIPANT_JOINED';
132 134
  * }
133 135
  */
134 136
 export const HIDDEN_PARTICIPANT_LEFT = 'HIDDEN_PARTICIPANT_LEFT';
137
+
138
+/**
139
+ * The type of Redux action which notifies the app that the loadable avatar URL has changed.
140
+ *
141
+ * {
142
+ *     type: SET_LOADABLE_AVATAR_URL,
143
+ *     participant: {
144
+ *         id: string,
145
+           loadableAvatarUrl: string
146
+ *     }
147
+ * }
148
+ */
149
+export const SET_LOADABLE_AVATAR_URL = 'SET_LOADABLE_AVATAR_URL';
150
+

+ 25
- 1
react/features/base/participants/actions.js View File

@@ -12,7 +12,8 @@ import {
12 12
     PARTICIPANT_KICKED,
13 13
     PARTICIPANT_LEFT,
14 14
     PARTICIPANT_UPDATED,
15
-    PIN_PARTICIPANT
15
+    PIN_PARTICIPANT,
16
+    SET_LOADABLE_AVATAR_URL
16 17
 } from './actionTypes';
17 18
 import {
18 19
     getLocalParticipant,
@@ -454,3 +455,26 @@ export function pinParticipant(id) {
454 455
         }
455 456
     };
456 457
 }
458
+
459
+/**
460
+ * Creates an action which notifies the app that the loadable URL of the avatar of a participant got updated.
461
+ *
462
+ * @param {string} participantId - The ID of the participant.
463
+ * @param {string} url - The new URL.
464
+ * @returns {{
465
+ *     type: SET_LOADABLE_AVATAR_URL,
466
+ *     participant: {
467
+ *         id: string,
468
+ *         loadableAvatarUrl: string
469
+ *     }
470
+ * }}
471
+*/
472
+export function setLoadableAvatarUrl(participantId, url) {
473
+    return {
474
+        type: SET_LOADABLE_AVATAR_URL,
475
+        participant: {
476
+            id: participantId,
477
+            loadableAvatarUrl: url
478
+        }
479
+    };
480
+}

+ 0
- 320
react/features/base/participants/components/Avatar.native.js View File

@@ -1,320 +0,0 @@
1
-// @flow
2
-
3
-import React, { Component, Fragment, PureComponent } from 'react';
4
-import { Dimensions, Image, Platform, View } from 'react-native';
5
-import FastImage, {
6
-    type CacheControls,
7
-    type Priorities
8
-} from 'react-native-fast-image';
9
-
10
-import { ColorPalette } from '../../styles';
11
-
12
-import styles from './styles';
13
-
14
-/**
15
- * The default image/source to be used in case none is specified or the
16
- * specified one fails to load.
17
- *
18
- * XXX The relative path to the default/stock (image) file is defined by the
19
- * {@code const} {@code DEFAULT_AVATAR_RELATIVE_PATH}. Unfortunately, the
20
- * packager of React Native cannot deal with it early enough for the following
21
- * {@code require} to succeed at runtime. Anyway, be sure to synchronize the
22
- * relative path on Web and mobile for the purposes of consistency.
23
- *
24
- * @private
25
- * @type {string}
26
- */
27
-const _DEFAULT_SOURCE = require('../../../../../images/avatar.png');
28
-
29
-/**
30
- * The type of the React {@link Component} props of {@link Avatar}.
31
- */
32
-type Props = {
33
-
34
-    /**
35
-     * The size for the {@link Avatar}.
36
-     */
37
-    size: number,
38
-
39
-
40
-    /**
41
-     * The URI of the {@link Avatar}.
42
-     */
43
-    uri: string
44
-};
45
-
46
-/**
47
- * The type of the React {@link Component} state of {@link Avatar}.
48
- */
49
-type State = {
50
-
51
-    /**
52
-     * Background color for the locally generated avatar.
53
-     */
54
-    backgroundColor: string,
55
-
56
-    /**
57
-     * Error indicator for non-local avatars.
58
-     */
59
-    error: boolean,
60
-
61
-    /**
62
-     * Indicates if the non-local avatar was loaded or not.
63
-     */
64
-    loaded: boolean,
65
-
66
-    /**
67
-     * Source for the non-local avatar.
68
-     */
69
-    source: {
70
-        uri?: string,
71
-        headers?: Object,
72
-        priority?: Priorities,
73
-        cache?: CacheControls,
74
-    }
75
-};
76
-
77
-/**
78
- * Implements a React Native/mobile {@link Component} wich renders the content
79
- * of an Avatar.
80
- */
81
-class AvatarContent extends Component<Props, State> {
82
-    /**
83
-     * Initializes a new Avatar instance.
84
-     *
85
-     * @param {Props} props - The read-only React Component props with which
86
-     * the new instance is to be initialized.
87
-     */
88
-    constructor(props: Props) {
89
-        super(props);
90
-
91
-        // Set the image source. The logic for the character # below is as
92
-        // follows:
93
-        // - Technically, URI is supposed to start with a scheme and scheme
94
-        //   cannot contain the character #.
95
-        // - Technically, the character # in a URI signals the start of the
96
-        //   fragment/hash.
97
-        // - Technically, the fragment/hash does not imply a retrieval
98
-        //   action.
99
-        // - Practically, the fragment/hash does not always mandate a
100
-        //   retrieval action. For example, an HTML anchor with an href that
101
-        //   starts with the character # does not cause a Web browser to
102
-        //   initiate a retrieval action.
103
-        // So I'll use the character # at the start of URI to not initiate
104
-        // an image retrieval action.
105
-        const source = {};
106
-
107
-        if (props.uri && !props.uri.startsWith('#')) {
108
-            source.uri = props.uri;
109
-        }
110
-
111
-        this.state = {
112
-            backgroundColor: this._getBackgroundColor(props),
113
-            error: false,
114
-            loaded: false,
115
-            source
116
-        };
117
-
118
-        // Bind event handlers so they are only bound once per instance.
119
-        this._onAvatarLoaded = this._onAvatarLoaded.bind(this);
120
-        this._onAvatarLoadError = this._onAvatarLoadError.bind(this);
121
-    }
122
-
123
-    /**
124
-     * Computes if the default avatar (ie, locally generated) should be used
125
-     * or not.
126
-     */
127
-    get useDefaultAvatar() {
128
-        const { error, loaded, source } = this.state;
129
-
130
-        return !source.uri || error || !loaded;
131
-    }
132
-
133
-    /**
134
-     * Computes a hash over the URI and returns a HSL background color. We use
135
-     * 75% as lightness, for nice pastel style colors.
136
-     *
137
-     * @param {Object} props - The read-only React {@code Component} props from
138
-     * which the background color is to be generated.
139
-     * @private
140
-     * @returns {string} - The HSL CSS property.
141
-     */
142
-    _getBackgroundColor({ uri }) {
143
-        if (!uri) {
144
-            return ColorPalette.white;
145
-        }
146
-
147
-        let hash = 0;
148
-
149
-        /* eslint-disable no-bitwise */
150
-
151
-        for (let i = 0; i < uri.length; i++) {
152
-            hash = uri.charCodeAt(i) + ((hash << 5) - hash);
153
-            hash |= 0; // Convert to 32-bit integer
154
-        }
155
-
156
-        /* eslint-enable no-bitwise */
157
-
158
-        return `hsl(${hash % 360}, 100%, 75%)`;
159
-    }
160
-
161
-    /**
162
-     * Helper which computes the style for the {@code Image} / {@code FastImage}
163
-     * component.
164
-     *
165
-     * @private
166
-     * @returns {Object}
167
-     */
168
-    _getImageStyle() {
169
-        const { size } = this.props;
170
-
171
-        return {
172
-            ...styles.avatar,
173
-            borderRadius: size / 2,
174
-            height: size,
175
-            width: size
176
-        };
177
-    }
178
-
179
-    _onAvatarLoaded: () => void;
180
-
181
-    /**
182
-     * Handler called when the remote image loading finishes. This doesn't
183
-     * necessarily mean the load was successful.
184
-     *
185
-     * @private
186
-     * @returns {void}
187
-     */
188
-    _onAvatarLoaded() {
189
-        this.setState({ loaded: true });
190
-    }
191
-
192
-    _onAvatarLoadError: () => void;
193
-
194
-    /**
195
-     * Handler called when the remote image loading failed.
196
-     *
197
-     * @private
198
-     * @returns {void}
199
-     */
200
-    _onAvatarLoadError() {
201
-        this.setState({ error: true });
202
-    }
203
-
204
-    /**
205
-     * Renders a default, locally generated avatar image.
206
-     *
207
-     * @private
208
-     * @returns {ReactElement}
209
-     */
210
-    _renderDefaultAvatar() {
211
-        // When using a local image, react-native-fastimage falls back to a
212
-        // regular Image, so we need to wrap it in a view to make it round.
213
-        // https://github.com/facebook/react-native/issues/3198
214
-
215
-        const { backgroundColor } = this.state;
216
-        const imageStyle = this._getImageStyle();
217
-        const viewStyle = {
218
-            ...imageStyle,
219
-
220
-            backgroundColor,
221
-
222
-            // FIXME @lyubomir: Without the opacity below I feel like the
223
-            // avatar colors are too strong. Besides, we use opacity for the
224
-            // ToolbarButtons. That's where I copied the value from and we
225
-            // may want to think about "standardizing" the opacity in the
226
-            // app in a way similar to ColorPalette.
227
-            opacity: 0.1,
228
-            overflow: 'hidden'
229
-        };
230
-
231
-        return (
232
-            <View style = { viewStyle }>
233
-                <Image
234
-
235
-                    // The Image adds a fade effect without asking, so lets
236
-                    // explicitly disable it. More info here:
237
-                    // https://github.com/facebook/react-native/issues/10194
238
-                    fadeDuration = { 0 }
239
-                    resizeMode = 'contain'
240
-                    source = { _DEFAULT_SOURCE }
241
-                    style = { imageStyle } />
242
-            </View>
243
-        );
244
-    }
245
-
246
-    /**
247
-     * Renders an avatar using a remote image.
248
-     *
249
-     * @private
250
-     * @returns {ReactElement}
251
-     */
252
-    _renderAvatar() {
253
-        const { source } = this.state;
254
-        let extraStyle;
255
-
256
-        if (this.useDefaultAvatar) {
257
-            // On Android, the image loading indicators don't work unless the
258
-            // Glide image is actually created, so we cannot use display: none.
259
-            // Instead, render it off-screen, which does the trick.
260
-            if (Platform.OS === 'android') {
261
-                const windowDimensions = Dimensions.get('window');
262
-
263
-                extraStyle = {
264
-                    bottom: -windowDimensions.height,
265
-                    right: -windowDimensions.width
266
-                };
267
-            } else {
268
-                extraStyle = { display: 'none' };
269
-            }
270
-        }
271
-
272
-        return (
273
-            <FastImage
274
-                onError = { this._onAvatarLoadError }
275
-                onLoadEnd = { this._onAvatarLoaded }
276
-                resizeMode = 'contain'
277
-                source = { source }
278
-                style = { [ this._getImageStyle(), extraStyle ] } />
279
-        );
280
-    }
281
-
282
-    /**
283
-     * Implements React's {@link Component#render()}.
284
-     *
285
-     * @inheritdoc
286
-     */
287
-    render() {
288
-        const { source } = this.state;
289
-
290
-        return (
291
-            <Fragment>
292
-                { source.uri && this._renderAvatar() }
293
-                { this.useDefaultAvatar && this._renderDefaultAvatar() }
294
-            </Fragment>
295
-        );
296
-    }
297
-}
298
-
299
-/* eslint-disable react/no-multi-comp */
300
-
301
-/**
302
- * Implements an avatar as a React Native/mobile {@link Component}.
303
- *
304
- * Note: we use `key` in order to trigger a new component creation in case
305
- * the URI changes.
306
- */
307
-export default class Avatar extends PureComponent<Props> {
308
-    /**
309
-     * Implements React's {@link Component#render()}.
310
-     *
311
-     * @inheritdoc
312
-     */
313
-    render() {
314
-        return (
315
-            <AvatarContent
316
-                key = { this.props.uri }
317
-                { ...this.props } />
318
-        );
319
-    }
320
-}

+ 0
- 38
react/features/base/participants/components/Avatar.web.js View File

@@ -1,38 +0,0 @@
1
-// @flow
2
-
3
-import React, { Component } from 'react';
4
-
5
-/**
6
- * The type of the React {@link Component} props of {@link Avatar}.
7
- */
8
-type Props = {
9
-
10
-    /**
11
-     * The URI of the {@link Avatar}.
12
-     */
13
-    uri: string
14
-};
15
-
16
-/**
17
- * Implements an avatar as a React/Web {@link Component}.
18
- */
19
-export default class Avatar extends Component<Props> {
20
-    /**
21
-     * Implements React's {@link Component#render()}.
22
-     *
23
-     * @inheritdoc
24
-     */
25
-    render() {
26
-        // Propagate all props of this Avatar but the ones consumed by this
27
-        // Avatar to the img it renders.
28
-
29
-        // eslint-disable-next-line no-unused-vars
30
-        const { uri, ...props } = this.props;
31
-
32
-        return (
33
-            <img
34
-                { ...props }
35
-                src = { uri } />
36
-        );
37
-    }
38
-}

+ 9
- 56
react/features/base/participants/components/ParticipantView.native.js View File

@@ -2,8 +2,8 @@
2 2
 
3 3
 import React, { Component } from 'react';
4 4
 import { Text, View } from 'react-native';
5
-import FastImage from 'react-native-fast-image';
6 5
 
6
+import { Avatar } from '../../avatar';
7 7
 import { translate } from '../../i18n';
8 8
 import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
9 9
 import {
@@ -16,13 +16,7 @@ import { StyleType } from '../../styles';
16 16
 import { TestHint } from '../../testing/components';
17 17
 import { getTrackByMediaTypeAndParticipant } from '../../tracks';
18 18
 
19
-import Avatar from './Avatar';
20
-import {
21
-    getAvatarURL,
22
-    getParticipantById,
23
-    getParticipantDisplayName,
24
-    shouldRenderParticipantVideo
25
-} from '../functions';
19
+import { shouldRenderParticipantVideo } from '../functions';
26 20
 import styles from './styles';
27 21
 
28 22
 /**
@@ -30,14 +24,6 @@ import styles from './styles';
30 24
  */
31 25
 type Props = {
32 26
 
33
-    /**
34
-     * The source (e.g. URI, URL) of the avatar image of the participant with
35
-     * {@link #participantId}.
36
-     *
37
-     * @private
38
-     */
39
-    _avatar: string,
40
-
41 27
     /**
42 28
      * The connection status of the participant. Her video will only be rendered
43 29
      * if the connection status is 'active'; otherwise, the avatar will be
@@ -192,7 +178,6 @@ class ParticipantView extends Component<Props> {
192 178
      */
193 179
     render() {
194 180
         const {
195
-            _avatar: avatar,
196 181
             _connectionStatus: connectionStatus,
197 182
             _renderVideo: renderVideo,
198 183
             _videoTrack: videoTrack,
@@ -202,9 +187,6 @@ class ParticipantView extends Component<Props> {
202 187
 
203 188
         const waitForVideoStarted = false;
204 189
 
205
-        // Is the avatar to be rendered?
206
-        const renderAvatar = Boolean(!renderVideo && avatar);
207
-
208 190
         // If the connection has problems, we will "tint" the video / avatar.
209 191
         const connectionProblem
210 192
             = connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE;
@@ -238,10 +220,12 @@ class ParticipantView extends Component<Props> {
238 220
                         zOrder = { this.props.zOrder }
239 221
                         zoomEnabled = { this.props.zoomEnabled } /> }
240 222
 
241
-                { renderAvatar
242
-                    && <Avatar
243
-                        size = { this.props.avatarSize }
244
-                        uri = { avatar } /> }
223
+                { !renderVideo
224
+                    && <View style = { styles.avatarContainer }>
225
+                        <Avatar
226
+                            participantId = { this.props.participantId }
227
+                            size = { this.props.avatarSize } />
228
+                    </View> }
245 229
 
246 230
                 { useTint
247 231
 
@@ -265,45 +249,14 @@ class ParticipantView extends Component<Props> {
265 249
  * @param {Object} ownProps - The React {@code Component} props passed to the
266 250
  * associated (instance of) {@code ParticipantView}.
267 251
  * @private
268
- * @returns {{
269
- *     _avatar: string,
270
- *     _connectionStatus: string,
271
- *     _participantName: string,
272
- *     _renderVideo: boolean,
273
- *     _videoTrack: Track
274
- * }}
252
+ * @returns {Props}
275 253
  */
276 254
 function _mapStateToProps(state, ownProps) {
277 255
     const { participantId } = ownProps;
278
-    const participant = getParticipantById(state, participantId);
279
-    let avatar;
280 256
     let connectionStatus;
281 257
     let participantName;
282 258
 
283
-    if (participant) {
284
-        avatar = getAvatarURL(participant);
285
-        connectionStatus = participant.connectionStatus;
286
-        participantName = getParticipantDisplayName(state, participant.id);
287
-
288
-        // Avatar (on React Native) now has the ability to generate an
289
-        // automatically-colored default image when no URI/URL is specified or
290
-        // when it fails to load. In order to make the coloring permanent(ish)
291
-        // per participant, Avatar will need something permanent(ish) per
292
-        // perticipant, obviously. A participant's ID is such a piece of data.
293
-        // But the local participant changes her ID as she joins, leaves.
294
-        // TODO @lyubomir: The participants may change their avatar URLs at
295
-        // runtime which means that, if their old and new avatar URLs fail to
296
-        // download, Avatar will change their automatically-generated colors.
297
-        avatar || participant.local || (avatar = `#${participant.id}`);
298
-
299
-        // ParticipantView knows before Avatar that an avatar URL will be used
300
-        // so it's advisable to prefetch here.
301
-        avatar && !avatar.startsWith('#')
302
-            && FastImage.preload([ { uri: avatar } ]);
303
-    }
304
-
305 259
     return {
306
-        _avatar: avatar,
307 260
         _connectionStatus:
308 261
             connectionStatus
309 262
                 || JitsiParticipantConnectionStatus.ACTIVE,

+ 2
- 1
react/features/base/participants/components/index.js View File

@@ -1,2 +1,3 @@
1
-export { default as Avatar } from './Avatar';
1
+// @flow
2
+
2 3
 export { default as ParticipantView } from './ParticipantView';

+ 9
- 7
react/features/base/participants/components/styles.js View File

@@ -1,15 +1,17 @@
1
-import { BoxModel, ColorPalette, createStyleSheet } from '../../styles';
1
+// @flow
2
+
3
+import { BoxModel, ColorPalette } from '../../styles';
2 4
 
3 5
 /**
4 6
  * The styles of the feature base/participants.
5 7
  */
6
-export default createStyleSheet({
8
+export default {
7 9
     /**
8
-     * The style of the avatar of the participant.
10
+     * Container for the avatar in the view.
9 11
      */
10
-    avatar: {
11
-        alignSelf: 'center',
12
-        flex: 0
12
+    avatarContainer: {
13
+        alignItems: 'center',
14
+        justifyContent: 'center'
13 15
     },
14 16
 
15 17
     /**
@@ -42,4 +44,4 @@ export default createStyleSheet({
42 44
         flex: 1,
43 45
         justifyContent: 'center'
44 46
     }
45
-});
47
+};

+ 84
- 2
react/features/base/participants/functions.js View File

@@ -1,11 +1,15 @@
1 1
 // @flow
2
-import { getAvatarURL as _getAvatarURL } from 'js-utils/avatar';
2
+import {
3
+    getAvatarURL as _getAvatarURL,
4
+    getGravatarURL
5
+} from 'js-utils/avatar';
3 6
 
4 7
 import { toState } from '../redux';
5 8
 
6 9
 import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
7 10
 import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
8 11
 import { getTrackByMediaTypeAndParticipant } from '../tracks';
12
+import { createDeferred } from '../util';
9 13
 
10 14
 import {
11 15
     DEFAULT_AVATAR_RELATIVE_PATH,
@@ -13,10 +17,27 @@ import {
13 17
     MAX_DISPLAY_NAME_LENGTH,
14 18
     PARTICIPANT_ROLE
15 19
 } from './constants';
20
+import { preloadImage } from './preloadImage';
16 21
 
17 22
 declare var config: Object;
18 23
 declare var interfaceConfig: Object;
19 24
 
25
+/**
26
+ * Temp structures for avatar urls to be checked/preloaded.
27
+ */
28
+const AVATAR_QUEUE = [];
29
+const AVATAR_CHECKED_URLS = new Map();
30
+/* eslint-disable arrow-body-style */
31
+const AVATAR_CHECKER_FUNCTIONS = [
32
+    participant => {
33
+        return participant && participant.avatarURL ? participant.avatarURL : null;
34
+    },
35
+    participant => {
36
+        return participant && participant.email ? getGravatarURL(participant.email) : null;
37
+    }
38
+];
39
+/* eslint-enable arrow-body-style */
40
+
20 41
 /**
21 42
  * Returns the URL of the image for the avatar of a specific participant.
22 43
  *
@@ -84,6 +105,36 @@ export function getAvatarURLByParticipantId(
84 105
     return participant && getAvatarURL(participant);
85 106
 }
86 107
 
108
+/**
109
+ * Resolves the first loadable avatar URL for a participant.
110
+ *
111
+ * @param {Object} participant - The participant to resolve avatars for.
112
+ * @returns {Promise}
113
+ */
114
+export function getFirstLoadableAvatarUrl(participant: Object) {
115
+    const deferred = createDeferred();
116
+    const fullPromise = deferred.promise
117
+        .then(() => _getFirstLoadableAvatarUrl(participant))
118
+        .then(src => {
119
+
120
+            if (AVATAR_QUEUE.length) {
121
+                const next = AVATAR_QUEUE.shift();
122
+
123
+                next.resolve();
124
+            }
125
+
126
+            return src;
127
+        });
128
+
129
+    if (AVATAR_QUEUE.length) {
130
+        AVATAR_QUEUE.push(deferred);
131
+    } else {
132
+        deferred.resolve();
133
+    }
134
+
135
+    return fullPromise;
136
+}
137
+
87 138
 /**
88 139
  * Returns local participant from Redux state.
89 140
  *
@@ -169,7 +220,6 @@ export function getParticipantCountWithFake(stateful: Object | Function) {
169 220
  * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
170 221
  * {@code getState} function to be used to retrieve the state.
171 222
  * @param {string} id - The ID of the participant's display name to retrieve.
172
- * @private
173 223
  * @returns {string}
174 224
  */
175 225
 export function getParticipantDisplayName(
@@ -346,3 +396,35 @@ export function shouldRenderParticipantVideo(
346 396
         && shouldRenderVideoTrack(videoTrack, waitForVideoStarted);
347 397
 
348 398
 }
399
+
400
+/**
401
+ * Resolves the first loadable avatar URL for a participant.
402
+ *
403
+ * @param {Object} participant - The participant to resolve avatars for.
404
+ * @returns {?string}
405
+ */
406
+async function _getFirstLoadableAvatarUrl(participant) {
407
+    for (let i = 0; i < AVATAR_CHECKER_FUNCTIONS.length; i++) {
408
+        const url = AVATAR_CHECKER_FUNCTIONS[i](participant);
409
+
410
+        if (url) {
411
+            if (AVATAR_CHECKED_URLS.has(url)) {
412
+                if (AVATAR_CHECKED_URLS.get(url)) {
413
+                    return url;
414
+                }
415
+            } else {
416
+                try {
417
+                    const finalUrl = await preloadImage(url);
418
+
419
+                    AVATAR_CHECKED_URLS.set(finalUrl, true);
420
+
421
+                    return finalUrl;
422
+                } catch (e) {
423
+                    AVATAR_CHECKED_URLS.set(url, false);
424
+                }
425
+            }
426
+        }
427
+    }
428
+
429
+    return undefined;
430
+}

+ 23
- 18
react/features/base/participants/middleware.js View File

@@ -20,7 +20,8 @@ import {
20 20
     localParticipantJoined,
21 21
     localParticipantLeft,
22 22
     participantLeft,
23
-    participantUpdated
23
+    participantUpdated,
24
+    setLoadableAvatarUrl
24 25
 } from './actions';
25 26
 import {
26 27
     DOMINANT_SPEAKER_CHANGED,
@@ -37,8 +38,9 @@ import {
37 38
     PARTICIPANT_LEFT_SOUND_ID
38 39
 } from './constants';
39 40
 import {
40
-    getAvatarURLByParticipantId,
41
+    getFirstLoadableAvatarUrl,
41 42
     getLocalParticipant,
43
+    getParticipantById,
42 44
     getParticipantCount,
43 45
     getParticipantDisplayName
44 46
 } from './functions';
@@ -314,8 +316,8 @@ function _maybePlaySounds({ getState, dispatch }, action) {
314 316
  * @private
315 317
  * @returns {Object} The value returned by {@code next(action)}.
316 318
  */
317
-function _participantJoinedOrUpdated({ getState }, next, action) {
318
-    const { participant: { id, local, raisedHand } } = action;
319
+function _participantJoinedOrUpdated({ dispatch, getState }, next, action) {
320
+    const { participant: { avatarURL, email, id, local, name, raisedHand } } = action;
319 321
 
320 322
     // Send an external update of the local participant's raised hand state
321 323
     // if a new raised hand state is defined in the action.
@@ -330,26 +332,29 @@ function _participantJoinedOrUpdated({ getState }, next, action) {
330 332
         }
331 333
     }
332 334
 
333
-    // Notify external listeners of potential avatarURL changes.
334
-    if (typeof APP === 'object') {
335
-        const oldAvatarURL = getAvatarURLByParticipantId(getState(), id);
335
+    // Allow the redux update to go through and compare the old avatar
336
+    // to the new avatar and emit out change events if necessary.
337
+    const result = next(action);
336 338
 
337
-        // Allow the redux update to go through and compare the old avatar
338
-        // to the new avatar and emit out change events if necessary.
339
-        const result = next(action);
340
-        const newAvatarURL = getAvatarURLByParticipantId(getState(), id);
339
+    if (avatarURL || email || id || name) {
340
+        const participantId = !id && local ? getLocalParticipant(getState()).id : id;
341
+        const updatedParticipant = getParticipantById(getState(), participantId);
341 342
 
342
-        if (oldAvatarURL !== newAvatarURL) {
343
-            const currentKnownId = local ? APP.conference.getMyUserId() : id;
343
+        getFirstLoadableAvatarUrl(updatedParticipant)
344
+            .then(url => {
345
+                dispatch(setLoadableAvatarUrl(participantId, url));
346
+            });
347
+    }
344 348
 
345
-            APP.UI.refreshAvatarDisplay(currentKnownId, newAvatarURL);
346
-            APP.API.notifyAvatarChanged(currentKnownId, newAvatarURL);
347
-        }
349
+    // Notify external listeners of potential avatarURL changes.
350
+    if (typeof APP === 'object') {
351
+        const currentKnownId = local ? APP.conference.getMyUserId() : id;
348 352
 
349
-        return result;
353
+        // Force update of local video getting a new id.
354
+        APP.UI.refreshAvatarDisplay(currentKnownId);
350 355
     }
351 356
 
352
-    return next(action);
357
+    return result;
353 358
 }
354 359
 
355 360
 /**

+ 16
- 0
react/features/base/participants/preloadImage.native.js View File

@@ -0,0 +1,16 @@
1
+
2
+// @flow
3
+
4
+import { Image } from 'react-native';
5
+
6
+/**
7
+ * Tries to preload an image.
8
+ *
9
+ * @param {string} src - Source of the avatar.
10
+ * @returns {Promise}
11
+ */
12
+export function preloadImage(src: string): Promise<string> {
13
+    return new Promise((resolve, reject) => {
14
+        Image.prefetch(src).then(() => resolve(src), reject);
15
+    });
16
+}

+ 24
- 0
react/features/base/participants/preloadImage.web.js View File

@@ -0,0 +1,24 @@
1
+
2
+// @flow
3
+
4
+declare var config: Object;
5
+
6
+/**
7
+ * Tries to preload an image.
8
+ *
9
+ * @param {string} src - Source of the avatar.
10
+ * @returns {Promise}
11
+ */
12
+export function preloadImage(src: string): Promise<string> {
13
+    if (typeof config === 'object' && config.disableThirdPartyRequests) {
14
+        return Promise.reject();
15
+    }
16
+
17
+    return new Promise((resolve, reject) => {
18
+        const image = document.createElement('img');
19
+
20
+        image.onload = () => resolve(src);
21
+        image.onerror = reject;
22
+        image.src = src;
23
+    });
24
+}

+ 6
- 1
react/features/base/participants/reducer.js View File

@@ -10,7 +10,8 @@ import {
10 10
     PARTICIPANT_JOINED,
11 11
     PARTICIPANT_LEFT,
12 12
     PARTICIPANT_UPDATED,
13
-    PIN_PARTICIPANT
13
+    PIN_PARTICIPANT,
14
+    SET_LOADABLE_AVATAR_URL
14 15
 } from './actionTypes';
15 16
 import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
16 17
 
@@ -65,6 +66,7 @@ const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
65 66
  */
66 67
 ReducerRegistry.register('features/base/participants', (state = [], action) => {
67 68
     switch (action.type) {
69
+    case SET_LOADABLE_AVATAR_URL:
68 70
     case DOMINANT_SPEAKER_CHANGED:
69 71
     case PARTICIPANT_ID_CHANGED:
70 72
     case PARTICIPANT_UPDATED:
@@ -133,6 +135,7 @@ function _participant(state: Object = {}, action) {
133 135
         break;
134 136
     }
135 137
 
138
+    case SET_LOADABLE_AVATAR_URL:
136 139
     case PARTICIPANT_UPDATED: {
137 140
         const { participant } = action; // eslint-disable-line no-shadow
138 141
         let { id } = participant;
@@ -186,6 +189,7 @@ function _participantJoined({ participant }) {
186 189
         dominantSpeaker,
187 190
         email,
188 191
         isFakeParticipant,
192
+        loadableAvatarUrl,
189 193
         local,
190 194
         name,
191 195
         pinned,
@@ -221,6 +225,7 @@ function _participantJoined({ participant }) {
221 225
         email,
222 226
         id,
223 227
         isFakeParticipant,
228
+        loadableAvatarUrl,
224 229
         local: local || false,
225 230
         name,
226 231
         pinned: pinned || false,

+ 8
- 75
react/features/base/react/components/native/AvatarListItem.js View File

@@ -3,8 +3,7 @@
3 3
 import React, { Component } from 'react';
4 4
 import { Text } from 'react-native';
5 5
 
6
-import { Icon } from '../../../font-icons';
7
-import { Avatar } from '../../../participants';
6
+import { Avatar } from '../../../avatar';
8 7
 import { StyleType } from '../../../styles';
9 8
 
10 9
 import { type Item } from '../../Types';
@@ -70,44 +69,6 @@ export default class AvatarListItem extends Component<Props> {
70 69
         this._renderItemLine = this._renderItemLine.bind(this);
71 70
     }
72 71
 
73
-    /**
74
-     * Helper function to render the content in the avatar container.
75
-     *
76
-     * @returns {React$Element}
77
-     */
78
-    _getAvatarContent() {
79
-        const {
80
-            avatarSize = AVATAR_SIZE,
81
-            avatarTextStyle
82
-        } = this.props;
83
-        const { avatar, title } = this.props.item;
84
-        const isAvatarURL = Boolean(avatar && avatar.match(/^http[s]*:\/\//i));
85
-
86
-        if (isAvatarURL) {
87
-            return (
88
-                <Avatar
89
-                    size = { avatarSize }
90
-                    uri = { avatar } />
91
-            );
92
-        }
93
-
94
-        if (avatar && !isAvatarURL) {
95
-            return (
96
-                <Icon name = { avatar } />
97
-            );
98
-        }
99
-
100
-        return (
101
-            <Text
102
-                style = { [
103
-                    styles.avatarContent,
104
-                    avatarTextStyle
105
-                ] }>
106
-                { title.substr(0, 1).toUpperCase() }
107
-            </Text>
108
-        );
109
-    }
110
-
111 72
     /**
112 73
      * Implements {@code Component#render}.
113 74
      *
@@ -118,26 +79,19 @@ export default class AvatarListItem extends Component<Props> {
118 79
             avatarSize = AVATAR_SIZE,
119 80
             avatarStyle
120 81
         } = this.props;
121
-        const { colorBase, lines, title } = this.props.item;
122
-        const avatarStyles = {
123
-            ...styles.avatar,
124
-            ...this._getAvatarColor(colorBase),
125
-            ...avatarStyle,
126
-            borderRadius: avatarSize / 2,
127
-            height: avatarSize,
128
-            width: avatarSize
129
-        };
82
+        const { avatar, colorBase, lines, title } = this.props.item;
130 83
 
131 84
         return (
132 85
             <Container
133 86
                 onClick = { this.props.onPress }
134 87
                 style = { styles.listItem }
135 88
                 underlayColor = { UNDERLAY_COLOR }>
136
-                <Container style = { styles.avatarContainer }>
137
-                    <Container style = { avatarStyles }>
138
-                        { this._getAvatarContent() }
139
-                    </Container>
140
-                </Container>
89
+                <Avatar
90
+                    colorBase = { colorBase }
91
+                    displayName = { title }
92
+                    size = { avatarSize }
93
+                    style = { avatarStyle }
94
+                    uri = { avatar } />
141 95
                 <Container style = { styles.listItemDetails }>
142 96
                     <Text
143 97
                         numberOfLines = { 1 }
@@ -155,27 +109,6 @@ export default class AvatarListItem extends Component<Props> {
155 109
         );
156 110
     }
157 111
 
158
-    /**
159
-     * Returns a style (color) based on the string that determines the color of
160
-     * the avatar.
161
-     *
162
-     * @param {string} colorBase - The string that is the base of the color.
163
-     * @private
164
-     * @returns {Object}
165
-     */
166
-    _getAvatarColor(colorBase) {
167
-        if (!colorBase) {
168
-            return null;
169
-        }
170
-        let nameHash = 0;
171
-
172
-        for (let i = 0; i < colorBase.length; i++) {
173
-            nameHash += colorBase.codePointAt(i);
174
-        }
175
-
176
-        return styles[`avatarColor${(nameHash % 5) + 1}`];
177
-    }
178
-
179 112
     _renderItemLine: (string, number) => React$Node;
180 113
 
181 114
     /**

+ 0
- 35
react/features/base/react/components/native/styles.js View File

@@ -2,7 +2,6 @@
2 2
 
3 3
 import { BoxModel, ColorPalette, createStyleSheet } from '../../../styles';
4 4
 
5
-const AVATAR_OPACITY = 0.4;
6 5
 const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
7 6
 
8 7
 export const AVATAR_SIZE = 65;
@@ -92,40 +91,6 @@ const PAGED_LIST_STYLES = {
92 91
 };
93 92
 
94 93
 const SECTION_LIST_STYLES = {
95
-    /**
96
-     * The style of the actual avatar.
97
-     */
98
-    avatar: {
99
-        alignItems: 'center',
100
-        backgroundColor: `rgba(23, 160, 219, ${AVATAR_OPACITY})`,
101
-        justifyContent: 'center'
102
-    },
103
-
104
-    /**
105
-     * List of styles of the avatar of a remote meeting (not the default
106
-     * server). The number of colors are limited because they should match
107
-     * nicely.
108
-     */
109
-    avatarColor1: {
110
-        backgroundColor: `rgba(232, 105, 156, ${AVATAR_OPACITY})`
111
-    },
112
-
113
-    avatarColor2: {
114
-        backgroundColor: `rgba(255, 198, 115, ${AVATAR_OPACITY})`
115
-    },
116
-
117
-    avatarColor3: {
118
-        backgroundColor: `rgba(128, 128, 255, ${AVATAR_OPACITY})`
119
-    },
120
-
121
-    avatarColor4: {
122
-        backgroundColor: `rgba(105, 232, 194, ${AVATAR_OPACITY})`
123
-    },
124
-
125
-    avatarColor5: {
126
-        backgroundColor: `rgba(234, 255, 128, ${AVATAR_OPACITY})`
127
-    },
128
-
129 94
     /**
130 95
      * The style of the avatar container that makes the avatar rounded.
131 96
      */

+ 16
- 0
react/features/base/util/helpers.js View File

@@ -2,6 +2,22 @@
2 2
 
3 3
 const logger = require('jitsi-meet-logger').getLogger(__filename);
4 4
 
5
+/**
6
+ * Creates a deferred object.
7
+ *
8
+ * @returns {{promise, resolve, reject}}
9
+ */
10
+export function createDeferred(): Object {
11
+    const deferred = {};
12
+
13
+    deferred.promise = new Promise((resolve, reject) => {
14
+        deferred.resolve = resolve;
15
+        deferred.reject = reject;
16
+    });
17
+
18
+    return deferred;
19
+}
20
+
5 21
 /**
6 22
  * Returns the namespace for all global variables, functions, etc that we need.
7 23
  *

+ 0
- 23
react/features/chat/components/AbstractChatMessage.js View File

@@ -3,7 +3,6 @@
3 3
 import { PureComponent } from 'react';
4 4
 
5 5
 import { getLocalizedDateFormatter } from '../../base/i18n';
6
-import { getAvatarURLByParticipantId } from '../../base/participants';
7 6
 
8 7
 /**
9 8
  * Formatter string to display the message timestamp.
@@ -15,11 +14,6 @@ const TIMESTAMP_FORMAT = 'H:mm';
15 14
  */
16 15
 export type Props = {
17 16
 
18
-    /**
19
-     * The URL of the avatar of the participant.
20
-     */
21
-    _avatarURL: string,
22
-
23 17
     /**
24 18
      * The representation of a chat message.
25 19
      */
@@ -63,20 +57,3 @@ export default class AbstractChatMessage<P: Props> extends PureComponent<P> {
63 57
             .format(TIMESTAMP_FORMAT);
64 58
     }
65 59
 }
66
-
67
-/**
68
- * Maps part of the Redux state to the props of this component.
69
- *
70
- * @param {Object} state - The Redux state.
71
- * @param {Props} ownProps - The own props of the component.
72
- * @returns {{
73
- *     _avatarURL: string
74
- * }}
75
- */
76
-export function _mapStateToProps(state: Object, ownProps: Props) {
77
-    const { message } = ownProps;
78
-
79
-    return {
80
-        _avatarURL: getAvatarURLByParticipantId(state, message.id)
81
-    };
82
-}

+ 5
- 9
react/features/chat/components/native/ChatMessage.js View File

@@ -3,14 +3,10 @@
3 3
 import React from 'react';
4 4
 import { Text, View } from 'react-native';
5 5
 
6
+import { Avatar } from '../../../base/avatar';
6 7
 import { translate } from '../../../base/i18n';
7
-import { Avatar } from '../../../base/participants';
8
-import { connect } from '../../../base/redux';
9 8
 
10
-import AbstractChatMessage, {
11
-    _mapStateToProps,
12
-    type Props
13
-} from '../AbstractChatMessage';
9
+import AbstractChatMessage, { type Props } from '../AbstractChatMessage';
14 10
 import styles from './styles';
15 11
 
16 12
 /**
@@ -81,8 +77,8 @@ class ChatMessage extends AbstractChatMessage<Props> {
81 77
         return (
82 78
             <View style = { styles.avatarWrapper }>
83 79
                 { this.props.showAvatar && <Avatar
84
-                    size = { styles.avatarWrapper.width }
85
-                    uri = { this.props._avatarURL } />
80
+                    participantId = { this.props.message.id }
81
+                    size = { styles.avatarWrapper.width } />
86 82
                 }
87 83
             </View>
88 84
         );
@@ -115,4 +111,4 @@ class ChatMessage extends AbstractChatMessage<Props> {
115 111
     }
116 112
 }
117 113
 
118
-export default translate(connect(_mapStateToProps)(ChatMessage));
114
+export default translate(ChatMessage);

+ 26
- 2
react/features/external-api/middleware.js View File

@@ -9,8 +9,10 @@ import { NOTIFY_CAMERA_ERROR, NOTIFY_MIC_ERROR } from '../base/devices';
9 9
 import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
10 10
 import {
11 11
     PARTICIPANT_KICKED,
12
+    SET_LOADABLE_AVATAR_URL,
12 13
     getAvatarURLByParticipantId,
13
-    getLocalParticipant
14
+    getLocalParticipant,
15
+    getParticipantById
14 16
 } from '../base/participants';
15 17
 import { MiddlewareRegistry } from '../base/redux';
16 18
 import { appendSuffix } from '../display-name';
@@ -26,8 +28,31 @@ declare var interfaceConfig: Object;
26 28
  * @returns {Function}
27 29
  */
28 30
 MiddlewareRegistry.register(store => next => action => {
31
+    // We need to do these before executing the rest of the middelware chain
32
+    switch (action.type) {
33
+    case SET_LOADABLE_AVATAR_URL: {
34
+        const { id, loadableAvatarUrl } = action.participant;
35
+        const participant = getParticipantById(
36
+            store.getState(),
37
+            id
38
+        );
39
+
40
+        const result = next(action);
41
+
42
+        if (participant.loadableAvatarUrl !== loadableAvatarUrl) {
43
+            APP.API.notifyAvatarChanged(
44
+                id,
45
+                loadableAvatarUrl
46
+            );
47
+        }
48
+
49
+        return result;
50
+    }
51
+    }
52
+
29 53
     const result = next(action);
30 54
 
55
+    // These should happen after the rest of the middleware chain ran
31 56
     switch (action.type) {
32 57
     case CONFERENCE_FAILED: {
33 58
         if (action.conference
@@ -54,7 +79,6 @@ MiddlewareRegistry.register(store => next => action => {
54 79
                 avatarURL: getAvatarURLByParticipantId(state, id)
55 80
             }
56 81
         );
57
-
58 82
         break;
59 83
     }
60 84
 

+ 5
- 6
react/features/invite/components/callee-info/CalleeInfo.js View File

@@ -2,10 +2,9 @@
2 2
 
3 3
 import React, { Component } from 'react';
4 4
 
5
+import { Avatar } from '../../../base/avatar';
5 6
 import { MEDIA_TYPE } from '../../../base/media';
6 7
 import {
7
-    Avatar,
8
-    getAvatarURL,
9 8
     getParticipants,
10 9
     getParticipantDisplayName,
11 10
     getParticipantPresenceStatus
@@ -23,7 +22,7 @@ import styles from './styles';
23 22
 type Props = {
24 23
 
25 24
     /**
26
-     * The callee's information such as avatar and display name.
25
+     * The callee's information such as display name.
27 26
      */
28 27
     _callee: Object,
29 28
 
@@ -46,7 +45,7 @@ class CalleeInfo extends Component<Props> {
46 45
      */
47 46
     render() {
48 47
         const {
49
-            avatar,
48
+            id,
50 49
             name,
51 50
             status = CALLING
52 51
         } = this.props._callee;
@@ -60,7 +59,7 @@ class CalleeInfo extends Component<Props> {
60 59
                     { ...this._style('ringing__content') }>
61 60
                     <Avatar
62 61
                         { ...this._style('ringing__avatar') }
63
-                        uri = { avatar } />
62
+                        participantId = { id } />
64 63
                     <Container { ...this._style('ringing__status') }>
65 64
                         <PresenceLabel
66 65
                             defaultPresence = { status }
@@ -144,7 +143,7 @@ function _mapStateToProps(state) {
144 143
 
145 144
         return {
146 145
             _callee: {
147
-                avatar: getAvatarURL(poltergeist),
146
+                id,
148 147
                 name: getParticipantDisplayName(state, id),
149 148
                 status: getParticipantPresenceStatus(state, id)
150 149
             },

+ 1
- 1
react/features/mobile/incoming-call/components/IncomingCallPage.js View File

@@ -4,8 +4,8 @@ import React, { Component } from 'react';
4 4
 import { Image, Text, View } from 'react-native';
5 5
 import LinearGradient from 'react-native-linear-gradient';
6 6
 
7
+import { Avatar } from '../../../base/avatar';
7 8
 import { translate } from '../../../base/i18n';
8
-import { Avatar } from '../../../base/participants';
9 9
 import { connect } from '../../../base/redux';
10 10
 
11 11
 import AnswerButton from './AnswerButton';

+ 7
- 10
react/features/overlay/components/web/FilmstripOnlyOverlayFrame.js View File

@@ -2,11 +2,8 @@
2 2
 
3 3
 import React, { Component } from 'react';
4 4
 
5
-import {
6
-    Avatar,
7
-    getAvatarURL,
8
-    getLocalParticipant
9
-} from '../../../base/participants';
5
+import { Avatar } from '../../../base/avatar';
6
+import { getLocalParticipant } from '../../../base/participants';
10 7
 import { connect } from '../../../base/redux';
11 8
 
12 9
 import OverlayFrame from './OverlayFrame';
@@ -18,9 +15,9 @@ import OverlayFrame from './OverlayFrame';
18 15
 type Props = {
19 16
 
20 17
     /**
21
-     * The source (e.g. URI, URL) of the avatar image of the local participant.
18
+     * The ID of the local participant.
22 19
      */
23
-    _avatar: string,
20
+    _localParticipantId: string,
24 21
 
25 22
     /**
26 23
      * The children components to be displayed into the overlay frame for
@@ -85,7 +82,7 @@ class FilmstripOnlyOverlayFrame extends Component<Props> {
85 82
                         }
86 83
                     </div>
87 84
                     <div className = 'inlay-filmstrip-only__avatar-container'>
88
-                        <Avatar uri = { this.props._avatar } />
85
+                        <Avatar participantId = { this.props._localParticipantId } />
89 86
                         {
90 87
                             this._renderIcon()
91 88
                         }
@@ -103,12 +100,12 @@ class FilmstripOnlyOverlayFrame extends Component<Props> {
103 100
  * @param {Object} state - The Redux state.
104 101
  * @private
105 102
  * @returns {{
106
- *     _avatar: string
103
+ *     _localParticipantId: string
107 104
  * }}
108 105
  */
109 106
 function _mapStateToProps(state) {
110 107
     return {
111
-        _avatar: getAvatarURL(getLocalParticipant(state) || {})
108
+        _localParticipantId: (getLocalParticipant(state) || {}).id
112 109
     };
113 110
 }
114 111
 

+ 6
- 15
react/features/remote-video-menu/components/native/RemoteVideoMenu.js View File

@@ -3,15 +3,12 @@
3 3
 import React, { Component } from 'react';
4 4
 import { Text, View } from 'react-native';
5 5
 
6
+import { Avatar } from '../../../base/avatar';
6 7
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7 8
 import {
8 9
     BottomSheet
9 10
 } from '../../../base/dialog';
10
-import {
11
-    Avatar,
12
-    getAvatarURL,
13
-    getParticipantDisplayName
14
-} from '../../../base/participants';
11
+import { getParticipantDisplayName } from '../../../base/participants';
15 12
 import { connect } from '../../../base/redux';
16 13
 import { StyleType } from '../../../base/styles';
17 14
 
@@ -39,11 +36,6 @@ type Props = {
39 36
      */
40 37
     participant: Object,
41 38
 
42
-    /**
43
-     * URL of the avatar of the participant.
44
-     */
45
-    _avatarURL: string,
46
-
47 39
     /**
48 40
      * The color-schemed stylesheet of the BottomSheet.
49 41
      */
@@ -76,10 +68,11 @@ class RemoteVideoMenu extends Component<Props> {
76 68
      * @inheritdoc
77 69
      */
78 70
     render() {
71
+        const { participant } = this.props;
79 72
         const buttonProps = {
80 73
             afterClick: this._onCancel,
81 74
             showLabel: true,
82
-            participantID: this.props.participant.id,
75
+            participantID: participant.id,
83 76
             styles: this.props._bottomSheetStyles
84 77
         };
85 78
 
@@ -87,8 +80,8 @@ class RemoteVideoMenu extends Component<Props> {
87 80
             <BottomSheet onCancel = { this._onCancel }>
88 81
                 <View style = { styles.participantNameContainer }>
89 82
                     <Avatar
90
-                        size = { AVATAR_SIZE }
91
-                        uri = { this.props._avatarURL } />
83
+                        participantId = { participant.id }
84
+                        size = { AVATAR_SIZE } />
92 85
                     <Text style = { styles.participantNameLabel }>
93 86
                         { this.props._participantDisplayName }
94 87
                     </Text>
@@ -120,7 +113,6 @@ class RemoteVideoMenu extends Component<Props> {
120 113
  * @param {Object} ownProps - Properties of component.
121 114
  * @private
122 115
  * @returns {{
123
- *      _avatarURL: string,
124 116
  *      _bottomSheetStyles: StyleType,
125 117
  *      _participantDisplayName: string
126 118
  *  }}
@@ -129,7 +121,6 @@ function _mapStateToProps(state, ownProps) {
129 121
     const { participant } = ownProps;
130 122
 
131 123
     return {
132
-        _avatarURL: getAvatarURL(participant),
133 124
         _bottomSheetStyles:
134 125
             ColorSchemeRegistry.get(state, 'BottomSheet'),
135 126
         _participantDisplayName: getParticipantDisplayName(

+ 5
- 0
react/features/settings/components/web/ProfileTab.js View File

@@ -53,6 +53,11 @@ export type Props = {
53 53
  * @extends Component
54 54
  */
55 55
 class ProfileTab extends AbstractDialogTab<Props> {
56
+    static defaultProps = {
57
+        displayName: '',
58
+        email: ''
59
+    };
60
+
56 61
     /**
57 62
      * Initializes a new {@code ConnectedSettingsDialog} instance.
58 63
      *

+ 5
- 8
react/features/toolbox/components/web/OverflowMenuProfileItem.js View File

@@ -2,14 +2,10 @@
2 2
 
3 3
 import React, { Component } from 'react';
4 4
 
5
+import { Avatar } from '../../../base/avatar';
6
+import { getLocalParticipant } from '../../../base/participants';
5 7
 import { connect } from '../../../base/redux';
6 8
 
7
-import {
8
-    Avatar,
9
-    getAvatarURL,
10
-    getLocalParticipant
11
-} from '../../../base/participants';
12
-
13 9
 declare var interfaceConfig: Object;
14 10
 
15 11
 /**
@@ -65,7 +61,6 @@ class OverflowMenuProfileItem extends Component<Props> {
65 61
         const { _localParticipant, _unclickable } = this.props;
66 62
         const classNames = `overflow-menu-item ${
67 63
             _unclickable ? 'unclickable' : ''}`;
68
-        const avatarURL = getAvatarURL(_localParticipant);
69 64
         let displayName;
70 65
 
71 66
         if (_localParticipant && _localParticipant.name) {
@@ -80,7 +75,9 @@ class OverflowMenuProfileItem extends Component<Props> {
80 75
                 className = { classNames }
81 76
                 onClick = { this._onClick }>
82 77
                 <span className = 'overflow-menu-item-icon'>
83
-                    <Avatar uri = { avatarURL } />
78
+                    <Avatar
79
+                        participantId = { _localParticipant.id }
80
+                        size = { 24 } />
84 81
                 </span>
85 82
                 <span className = 'profile-text'>
86 83
                     { displayName }

+ 11
- 17
react/features/welcome/components/WelcomePageSideBar.native.js View File

@@ -3,9 +3,8 @@
3 3
 import React, { Component } from 'react';
4 4
 import { SafeAreaView, ScrollView, Text } from 'react-native';
5 5
 
6
+import { Avatar } from '../../base/avatar';
6 7
 import {
7
-    Avatar,
8
-    getAvatarURL,
9 8
     getLocalParticipant,
10 9
     getParticipantDisplayName
11 10
 } from '../../base/participants';
@@ -43,14 +42,14 @@ type Props = {
43 42
     dispatch: Function,
44 43
 
45 44
     /**
46
-     * The avatar URL to be rendered.
45
+     * Display name of the local participant.
47 46
      */
48
-    _avatarURL: string,
47
+    _displayName: string,
49 48
 
50 49
     /**
51
-     * Display name of the local participant.
50
+     * ID of the local participant.
52 51
      */
53
-    _displayName: string,
52
+    _localParticipantId: string,
54 53
 
55 54
     /**
56 55
      * Sets the side bar visible or hidden.
@@ -90,9 +89,8 @@ class WelcomePageSideBar extends Component<Props> {
90 89
                 style = { styles.sideBar } >
91 90
                 <Header style = { styles.sideBarHeader }>
92 91
                     <Avatar
93
-                        size = { SIDEBAR_AVATAR_SIZE }
94
-                        style = { styles.avatar }
95
-                        uri = { this.props._avatarURL } />
92
+                        participantId = { this.props._localParticipantId }
93
+                        size = { SIDEBAR_AVATAR_SIZE } />
96 94
                     <Text style = { styles.displayName }>
97 95
                         { this.props._displayName }
98 96
                     </Text>
@@ -155,18 +153,14 @@ class WelcomePageSideBar extends Component<Props> {
155 153
  *
156 154
  * @param {Object} state - The redux state.
157 155
  * @protected
158
- * @returns {{
159
- *     _avatarURL: string,
160
- *     _displayName: string,
161
- *     _visible: boolean
162
- * }}
156
+ * @returns {Props}
163 157
  */
164 158
 function _mapStateToProps(state: Object) {
165
-    const localParticipant = getLocalParticipant(state);
159
+    const _localParticipant = getLocalParticipant(state);
166 160
 
167 161
     return {
168
-        _avatarURL: getAvatarURL(localParticipant),
169
-        _displayName: getParticipantDisplayName(state, localParticipant.id),
162
+        _displayName: getParticipantDisplayName(state, _localParticipant.id),
163
+        _localParticipantId: _localParticipant.id,
170 164
         _visible: state['features/welcome'].sideBarVisible
171 165
     };
172 166
 }

+ 0
- 8
react/features/welcome/components/styles.js View File

@@ -38,14 +38,6 @@ export default createStyleSheet({
38 38
         flexDirection: 'row'
39 39
     },
40 40
 
41
-    /**
42
-     * Style of the avatar in te side bar.
43
-     */
44
-    avatar: {
45
-        alignSelf: 'center',
46
-        flex: 0
47
-    },
48
-
49 41
     /**
50 42
      * Join button style.
51 43
      */

Loading…
Cancel
Save