浏览代码

feat: initial based avatars

master
Bettenbuk Zoltan 5 年前
父节点
当前提交
72137a2811
共有 49 个文件被更改,包括 899 次插入697 次删除
  1. 14
    0
      css/_avatar.scss
  2. 0
    7
      css/_videolayout_default.scss
  3. 1
    0
      css/main.scss
  4. 二进制
      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 查看文件

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 查看文件

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

+ 1
- 0
css/main.scss 查看文件

86
 @import 'navigate_section_list';
86
 @import 'navigate_section_list';
87
 @import 'third-party-branding/google';
87
 @import 'third-party-branding/google';
88
 @import 'third-party-branding/microsoft';
88
 @import 'third-party-branding/microsoft';
89
+@import 'avatar';
89
 
90
 
90
 /* Modules END */
91
 /* Modules END */

二进制
images/avatar.png 查看文件


+ 2
- 2
modules/UI/UI.js 查看文件

510
  * @param {string} avatarURL - The URL to avatar image to display.
510
  * @param {string} avatarURL - The URL to avatar image to display.
511
  * @returns {void}
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 查看文件

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

+ 10
- 16
modules/UI/videolayout/LargeVideoManager.js 查看文件

5
 import { I18nextProvider } from 'react-i18next';
5
 import { I18nextProvider } from 'react-i18next';
6
 import { Provider } from 'react-redux';
6
 import { Provider } from 'react-redux';
7
 
7
 
8
+import { Avatar } from '../../../react/features/base/avatar';
8
 import { i18next } from '../../../react/features/base/i18n';
9
 import { i18next } from '../../../react/features/base/i18n';
9
-import {
10
-    Avatar,
11
-    getAvatarURLByParticipantId
12
-} from '../../../react/features/base/participants';
13
 import { PresenceLabel } from '../../../react/features/presence-status';
10
 import { PresenceLabel } from '../../../react/features/presence-status';
14
 /* eslint-enable no-unused-vars */
11
 /* eslint-enable no-unused-vars */
15
 
12
 
214
             container.setStream(id, stream, videoType);
211
             container.setStream(id, stream, videoType);
215
 
212
 
216
             // change the avatar url on large
213
             // change the avatar url on large
217
-            this.updateAvatar(
218
-                getAvatarURLByParticipantId(APP.store.getState(), id));
214
+            this.updateAvatar();
219
 
215
 
220
             // If the user's connection is disrupted then the avatar will be
216
             // If the user's connection is disrupted then the avatar will be
221
             // displayed in case we have no video image cached. That is if
217
             // displayed in case we have no video image cached. That is if
406
     /**
402
     /**
407
      * Updates the src of the dominant speaker avatar
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
                 <Avatar
408
                 <Avatar
413
                     id = "dominantSpeakerAvatar"
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 查看文件

7
 
7
 
8
 import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
8
 import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
9
 import { VideoTrack } from '../../../react/features/base/media';
9
 import { VideoTrack } from '../../../react/features/base/media';
10
-import {
11
-    getAvatarURLByParticipantId
12
-} from '../../../react/features/base/participants';
13
 import { updateSettings } from '../../../react/features/base/settings';
10
 import { updateSettings } from '../../../react/features/base/settings';
14
 import { getLocalVideoTrack } from '../../../react/features/base/tracks';
11
 import { getLocalVideoTrack } from '../../../react/features/base/tracks';
15
 import { shouldDisplayTileView } from '../../../react/features/video-layout';
12
 import { shouldDisplayTileView } from '../../../react/features/video-layout';
55
     // Initialize the avatar display with an avatar url selected from the redux
52
     // Initialize the avatar display with an avatar url selected from the redux
56
     // state. Redux stores the local user with a hardcoded participant id of
53
     // state. Redux stores the local user with a hardcoded participant id of
57
     // 'local' if no id has been assigned yet.
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
     this.addAudioLevelIndicator();
57
     this.addAudioLevelIndicator();
62
     this.updateIndicators();
58
     this.updateIndicators();

+ 10
- 9
modules/UI/videolayout/SmallVideo.js 查看文件

10
 import { i18next } from '../../../react/features/base/i18n';
10
 import { i18next } from '../../../react/features/base/i18n';
11
 import { AudioLevelIndicator }
11
 import { AudioLevelIndicator }
12
     from '../../../react/features/audio-level-indicator';
12
     from '../../../react/features/audio-level-indicator';
13
+import { Avatar as AvatarDisplay } from '../../../react/features/base/avatar';
13
 import {
14
 import {
14
-    Avatar as AvatarDisplay,
15
-    getAvatarURLByParticipantId,
16
     getPinnedParticipant,
15
     getPinnedParticipant,
17
     pinParticipant
16
     pinParticipant
18
 } from '../../../react/features/base/participants';
17
 } from '../../../react/features/base/participants';
570
     if (!this.hasAvatar) {
569
     if (!this.hasAvatar) {
571
         if (this.id) {
570
         if (this.id) {
572
             // Init avatar
571
             // Init avatar
573
-            this.avatarChanged(
574
-                getAvatarURLByParticipantId(APP.store.getState(), this.id));
572
+            this.initializeAvatar();
575
         } else {
573
         } else {
576
             logger.error('Unable to init avatar - no id', this);
574
             logger.error('Unable to init avatar - no id', this);
577
 
575
 
609
  * Updates the react component displaying the avatar with the passed in avatar
607
  * Updates the react component displaying the avatar with the passed in avatar
610
  * url.
608
  * url.
611
  *
609
  *
612
- * @param {string} avatarUrl - The uri to the avatar image.
613
  * @returns {void}
610
  * @returns {void}
614
  */
611
  */
615
-SmallVideo.prototype.avatarChanged = function(avatarUrl) {
612
+SmallVideo.prototype.initializeAvatar = function() {
616
     const thumbnail = this.$avatar().get(0);
613
     const thumbnail = this.$avatar().get(0);
617
 
614
 
618
     this.hasAvatar = true;
615
     this.hasAvatar = true;
619
 
616
 
620
     if (thumbnail) {
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
         ReactDOM.render(
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
             thumbnail
626
             thumbnail
626
         );
627
         );
627
     }
628
     }

+ 1
- 1
modules/UI/videolayout/VideoLayout.js 查看文件

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

+ 2
- 2
package-lock.json 查看文件

8738
       "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
8738
       "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
8739
     },
8739
     },
8740
     "js-utils": {
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
       "requires": {
8743
       "requires": {
8744
         "bowser": "1.9.1",
8744
         "bowser": "1.9.1",
8745
         "js-md5": "0.7.3"
8745
         "js-md5": "0.7.3"

+ 1
- 1
package.json 查看文件

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

+ 177
- 0
react/features/base/avatar/components/AbstractAvatar.js 查看文件

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 查看文件

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

+ 3
- 0
react/features/base/avatar/components/index.web.js 查看文件

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

+ 106
- 0
react/features/base/avatar/components/native/Avatar.js 查看文件

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 查看文件

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 查看文件

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

+ 47
- 0
react/features/base/avatar/components/native/styles.js 查看文件

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 查看文件

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 查看文件

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

+ 54
- 0
react/features/base/avatar/functions.js 查看文件

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 查看文件

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

+ 16
- 0
react/features/base/participants/actionTypes.js 查看文件

1
+// @flow
2
+
1
 /**
3
 /**
2
  * Create an action for when dominant speaker changes.
4
  * Create an action for when dominant speaker changes.
3
  *
5
  *
132
  * }
134
  * }
133
  */
135
  */
134
 export const HIDDEN_PARTICIPANT_LEFT = 'HIDDEN_PARTICIPANT_LEFT';
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 查看文件

12
     PARTICIPANT_KICKED,
12
     PARTICIPANT_KICKED,
13
     PARTICIPANT_LEFT,
13
     PARTICIPANT_LEFT,
14
     PARTICIPANT_UPDATED,
14
     PARTICIPANT_UPDATED,
15
-    PIN_PARTICIPANT
15
+    PIN_PARTICIPANT,
16
+    SET_LOADABLE_AVATAR_URL
16
 } from './actionTypes';
17
 } from './actionTypes';
17
 import {
18
 import {
18
     getLocalParticipant,
19
     getLocalParticipant,
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 查看文件

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 查看文件

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 查看文件

2
 
2
 
3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 import { Text, View } from 'react-native';
4
 import { Text, View } from 'react-native';
5
-import FastImage from 'react-native-fast-image';
6
 
5
 
6
+import { Avatar } from '../../avatar';
7
 import { translate } from '../../i18n';
7
 import { translate } from '../../i18n';
8
 import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
8
 import { JitsiParticipantConnectionStatus } from '../../lib-jitsi-meet';
9
 import {
9
 import {
16
 import { TestHint } from '../../testing/components';
16
 import { TestHint } from '../../testing/components';
17
 import { getTrackByMediaTypeAndParticipant } from '../../tracks';
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
 import styles from './styles';
20
 import styles from './styles';
27
 
21
 
28
 /**
22
 /**
30
  */
24
  */
31
 type Props = {
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
      * The connection status of the participant. Her video will only be rendered
28
      * The connection status of the participant. Her video will only be rendered
43
      * if the connection status is 'active'; otherwise, the avatar will be
29
      * if the connection status is 'active'; otherwise, the avatar will be
192
      */
178
      */
193
     render() {
179
     render() {
194
         const {
180
         const {
195
-            _avatar: avatar,
196
             _connectionStatus: connectionStatus,
181
             _connectionStatus: connectionStatus,
197
             _renderVideo: renderVideo,
182
             _renderVideo: renderVideo,
198
             _videoTrack: videoTrack,
183
             _videoTrack: videoTrack,
202
 
187
 
203
         const waitForVideoStarted = false;
188
         const waitForVideoStarted = false;
204
 
189
 
205
-        // Is the avatar to be rendered?
206
-        const renderAvatar = Boolean(!renderVideo && avatar);
207
-
208
         // If the connection has problems, we will "tint" the video / avatar.
190
         // If the connection has problems, we will "tint" the video / avatar.
209
         const connectionProblem
191
         const connectionProblem
210
             = connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE;
192
             = connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE;
238
                         zOrder = { this.props.zOrder }
220
                         zOrder = { this.props.zOrder }
239
                         zoomEnabled = { this.props.zoomEnabled } /> }
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
                 { useTint
230
                 { useTint
247
 
231
 
265
  * @param {Object} ownProps - The React {@code Component} props passed to the
249
  * @param {Object} ownProps - The React {@code Component} props passed to the
266
  * associated (instance of) {@code ParticipantView}.
250
  * associated (instance of) {@code ParticipantView}.
267
  * @private
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
 function _mapStateToProps(state, ownProps) {
254
 function _mapStateToProps(state, ownProps) {
277
     const { participantId } = ownProps;
255
     const { participantId } = ownProps;
278
-    const participant = getParticipantById(state, participantId);
279
-    let avatar;
280
     let connectionStatus;
256
     let connectionStatus;
281
     let participantName;
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
     return {
259
     return {
306
-        _avatar: avatar,
307
         _connectionStatus:
260
         _connectionStatus:
308
             connectionStatus
261
             connectionStatus
309
                 || JitsiParticipantConnectionStatus.ACTIVE,
262
                 || JitsiParticipantConnectionStatus.ACTIVE,

+ 2
- 1
react/features/base/participants/components/index.js 查看文件

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

+ 9
- 7
react/features/base/participants/components/styles.js 查看文件

1
-import { BoxModel, ColorPalette, createStyleSheet } from '../../styles';
1
+// @flow
2
+
3
+import { BoxModel, ColorPalette } from '../../styles';
2
 
4
 
3
 /**
5
 /**
4
  * The styles of the feature base/participants.
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
         flex: 1,
44
         flex: 1,
43
         justifyContent: 'center'
45
         justifyContent: 'center'
44
     }
46
     }
45
-});
47
+};

+ 84
- 2
react/features/base/participants/functions.js 查看文件

1
 // @flow
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
 import { toState } from '../redux';
7
 import { toState } from '../redux';
5
 
8
 
6
 import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
9
 import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
7
 import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
10
 import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
8
 import { getTrackByMediaTypeAndParticipant } from '../tracks';
11
 import { getTrackByMediaTypeAndParticipant } from '../tracks';
12
+import { createDeferred } from '../util';
9
 
13
 
10
 import {
14
 import {
11
     DEFAULT_AVATAR_RELATIVE_PATH,
15
     DEFAULT_AVATAR_RELATIVE_PATH,
13
     MAX_DISPLAY_NAME_LENGTH,
17
     MAX_DISPLAY_NAME_LENGTH,
14
     PARTICIPANT_ROLE
18
     PARTICIPANT_ROLE
15
 } from './constants';
19
 } from './constants';
20
+import { preloadImage } from './preloadImage';
16
 
21
 
17
 declare var config: Object;
22
 declare var config: Object;
18
 declare var interfaceConfig: Object;
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
  * Returns the URL of the image for the avatar of a specific participant.
42
  * Returns the URL of the image for the avatar of a specific participant.
22
  *
43
  *
84
     return participant && getAvatarURL(participant);
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
  * Returns local participant from Redux state.
139
  * Returns local participant from Redux state.
89
  *
140
  *
169
  * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
220
  * @param {(Function|Object)} stateful - The (whole) redux state, or redux's
170
  * {@code getState} function to be used to retrieve the state.
221
  * {@code getState} function to be used to retrieve the state.
171
  * @param {string} id - The ID of the participant's display name to retrieve.
222
  * @param {string} id - The ID of the participant's display name to retrieve.
172
- * @private
173
  * @returns {string}
223
  * @returns {string}
174
  */
224
  */
175
 export function getParticipantDisplayName(
225
 export function getParticipantDisplayName(
346
         && shouldRenderVideoTrack(videoTrack, waitForVideoStarted);
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 查看文件

20
     localParticipantJoined,
20
     localParticipantJoined,
21
     localParticipantLeft,
21
     localParticipantLeft,
22
     participantLeft,
22
     participantLeft,
23
-    participantUpdated
23
+    participantUpdated,
24
+    setLoadableAvatarUrl
24
 } from './actions';
25
 } from './actions';
25
 import {
26
 import {
26
     DOMINANT_SPEAKER_CHANGED,
27
     DOMINANT_SPEAKER_CHANGED,
37
     PARTICIPANT_LEFT_SOUND_ID
38
     PARTICIPANT_LEFT_SOUND_ID
38
 } from './constants';
39
 } from './constants';
39
 import {
40
 import {
40
-    getAvatarURLByParticipantId,
41
+    getFirstLoadableAvatarUrl,
41
     getLocalParticipant,
42
     getLocalParticipant,
43
+    getParticipantById,
42
     getParticipantCount,
44
     getParticipantCount,
43
     getParticipantDisplayName
45
     getParticipantDisplayName
44
 } from './functions';
46
 } from './functions';
314
  * @private
316
  * @private
315
  * @returns {Object} The value returned by {@code next(action)}.
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
     // Send an external update of the local participant's raised hand state
322
     // Send an external update of the local participant's raised hand state
321
     // if a new raised hand state is defined in the action.
323
     // if a new raised hand state is defined in the 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 查看文件

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 查看文件

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 查看文件

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

+ 8
- 75
react/features/base/react/components/native/AvatarListItem.js 查看文件

3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 import { Text } from 'react-native';
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
 import { StyleType } from '../../../styles';
7
 import { StyleType } from '../../../styles';
9
 
8
 
10
 import { type Item } from '../../Types';
9
 import { type Item } from '../../Types';
70
         this._renderItemLine = this._renderItemLine.bind(this);
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
      * Implements {@code Component#render}.
73
      * Implements {@code Component#render}.
113
      *
74
      *
118
             avatarSize = AVATAR_SIZE,
79
             avatarSize = AVATAR_SIZE,
119
             avatarStyle
80
             avatarStyle
120
         } = this.props;
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
         return (
84
         return (
132
             <Container
85
             <Container
133
                 onClick = { this.props.onPress }
86
                 onClick = { this.props.onPress }
134
                 style = { styles.listItem }
87
                 style = { styles.listItem }
135
                 underlayColor = { UNDERLAY_COLOR }>
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
                 <Container style = { styles.listItemDetails }>
95
                 <Container style = { styles.listItemDetails }>
142
                     <Text
96
                     <Text
143
                         numberOfLines = { 1 }
97
                         numberOfLines = { 1 }
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
     _renderItemLine: (string, number) => React$Node;
112
     _renderItemLine: (string, number) => React$Node;
180
 
113
 
181
     /**
114
     /**

+ 0
- 35
react/features/base/react/components/native/styles.js 查看文件

2
 
2
 
3
 import { BoxModel, ColorPalette, createStyleSheet } from '../../../styles';
3
 import { BoxModel, ColorPalette, createStyleSheet } from '../../../styles';
4
 
4
 
5
-const AVATAR_OPACITY = 0.4;
6
 const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
5
 const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
7
 
6
 
8
 export const AVATAR_SIZE = 65;
7
 export const AVATAR_SIZE = 65;
92
 };
91
 };
93
 
92
 
94
 const SECTION_LIST_STYLES = {
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
      * The style of the avatar container that makes the avatar rounded.
95
      * The style of the avatar container that makes the avatar rounded.
131
      */
96
      */

+ 16
- 0
react/features/base/util/helpers.js 查看文件

2
 
2
 
3
 const logger = require('jitsi-meet-logger').getLogger(__filename);
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
  * Returns the namespace for all global variables, functions, etc that we need.
22
  * Returns the namespace for all global variables, functions, etc that we need.
7
  *
23
  *

+ 0
- 23
react/features/chat/components/AbstractChatMessage.js 查看文件

3
 import { PureComponent } from 'react';
3
 import { PureComponent } from 'react';
4
 
4
 
5
 import { getLocalizedDateFormatter } from '../../base/i18n';
5
 import { getLocalizedDateFormatter } from '../../base/i18n';
6
-import { getAvatarURLByParticipantId } from '../../base/participants';
7
 
6
 
8
 /**
7
 /**
9
  * Formatter string to display the message timestamp.
8
  * Formatter string to display the message timestamp.
15
  */
14
  */
16
 export type Props = {
15
 export type Props = {
17
 
16
 
18
-    /**
19
-     * The URL of the avatar of the participant.
20
-     */
21
-    _avatarURL: string,
22
-
23
     /**
17
     /**
24
      * The representation of a chat message.
18
      * The representation of a chat message.
25
      */
19
      */
63
             .format(TIMESTAMP_FORMAT);
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 查看文件

3
 import React from 'react';
3
 import React from 'react';
4
 import { Text, View } from 'react-native';
4
 import { Text, View } from 'react-native';
5
 
5
 
6
+import { Avatar } from '../../../base/avatar';
6
 import { translate } from '../../../base/i18n';
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
 import styles from './styles';
10
 import styles from './styles';
15
 
11
 
16
 /**
12
 /**
81
         return (
77
         return (
82
             <View style = { styles.avatarWrapper }>
78
             <View style = { styles.avatarWrapper }>
83
                 { this.props.showAvatar && <Avatar
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
             </View>
83
             </View>
88
         );
84
         );
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 查看文件

9
 import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
9
 import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
10
 import {
10
 import {
11
     PARTICIPANT_KICKED,
11
     PARTICIPANT_KICKED,
12
+    SET_LOADABLE_AVATAR_URL,
12
     getAvatarURLByParticipantId,
13
     getAvatarURLByParticipantId,
13
-    getLocalParticipant
14
+    getLocalParticipant,
15
+    getParticipantById
14
 } from '../base/participants';
16
 } from '../base/participants';
15
 import { MiddlewareRegistry } from '../base/redux';
17
 import { MiddlewareRegistry } from '../base/redux';
16
 import { appendSuffix } from '../display-name';
18
 import { appendSuffix } from '../display-name';
26
  * @returns {Function}
28
  * @returns {Function}
27
  */
29
  */
28
 MiddlewareRegistry.register(store => next => action => {
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
     const result = next(action);
53
     const result = next(action);
30
 
54
 
55
+    // These should happen after the rest of the middleware chain ran
31
     switch (action.type) {
56
     switch (action.type) {
32
     case CONFERENCE_FAILED: {
57
     case CONFERENCE_FAILED: {
33
         if (action.conference
58
         if (action.conference
54
                 avatarURL: getAvatarURLByParticipantId(state, id)
79
                 avatarURL: getAvatarURLByParticipantId(state, id)
55
             }
80
             }
56
         );
81
         );
57
-
58
         break;
82
         break;
59
     }
83
     }
60
 
84
 

+ 5
- 6
react/features/invite/components/callee-info/CalleeInfo.js 查看文件

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

+ 1
- 1
react/features/mobile/incoming-call/components/IncomingCallPage.js 查看文件

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

+ 7
- 10
react/features/overlay/components/web/FilmstripOnlyOverlayFrame.js 查看文件

2
 
2
 
3
 import React, { Component } from 'react';
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
 import { connect } from '../../../base/redux';
7
 import { connect } from '../../../base/redux';
11
 
8
 
12
 import OverlayFrame from './OverlayFrame';
9
 import OverlayFrame from './OverlayFrame';
18
 type Props = {
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
      * The children components to be displayed into the overlay frame for
23
      * The children components to be displayed into the overlay frame for
85
                         }
82
                         }
86
                     </div>
83
                     </div>
87
                     <div className = 'inlay-filmstrip-only__avatar-container'>
84
                     <div className = 'inlay-filmstrip-only__avatar-container'>
88
-                        <Avatar uri = { this.props._avatar } />
85
+                        <Avatar participantId = { this.props._localParticipantId } />
89
                         {
86
                         {
90
                             this._renderIcon()
87
                             this._renderIcon()
91
                         }
88
                         }
103
  * @param {Object} state - The Redux state.
100
  * @param {Object} state - The Redux state.
104
  * @private
101
  * @private
105
  * @returns {{
102
  * @returns {{
106
- *     _avatar: string
103
+ *     _localParticipantId: string
107
  * }}
104
  * }}
108
  */
105
  */
109
 function _mapStateToProps(state) {
106
 function _mapStateToProps(state) {
110
     return {
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 查看文件

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

+ 5
- 0
react/features/settings/components/web/ProfileTab.js 查看文件

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

+ 5
- 8
react/features/toolbox/components/web/OverflowMenuProfileItem.js 查看文件

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

+ 11
- 17
react/features/welcome/components/WelcomePageSideBar.native.js 查看文件

3
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
4
 import { SafeAreaView, ScrollView, Text } from 'react-native';
4
 import { SafeAreaView, ScrollView, Text } from 'react-native';
5
 
5
 
6
+import { Avatar } from '../../base/avatar';
6
 import {
7
 import {
7
-    Avatar,
8
-    getAvatarURL,
9
     getLocalParticipant,
8
     getLocalParticipant,
10
     getParticipantDisplayName
9
     getParticipantDisplayName
11
 } from '../../base/participants';
10
 } from '../../base/participants';
43
     dispatch: Function,
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
      * Sets the side bar visible or hidden.
55
      * Sets the side bar visible or hidden.
90
                 style = { styles.sideBar } >
89
                 style = { styles.sideBar } >
91
                 <Header style = { styles.sideBarHeader }>
90
                 <Header style = { styles.sideBarHeader }>
92
                     <Avatar
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
                     <Text style = { styles.displayName }>
94
                     <Text style = { styles.displayName }>
97
                         { this.props._displayName }
95
                         { this.props._displayName }
98
                     </Text>
96
                     </Text>
155
  *
153
  *
156
  * @param {Object} state - The redux state.
154
  * @param {Object} state - The redux state.
157
  * @protected
155
  * @protected
158
- * @returns {{
159
- *     _avatarURL: string,
160
- *     _displayName: string,
161
- *     _visible: boolean
162
- * }}
156
+ * @returns {Props}
163
  */
157
  */
164
 function _mapStateToProps(state: Object) {
158
 function _mapStateToProps(state: Object) {
165
-    const localParticipant = getLocalParticipant(state);
159
+    const _localParticipant = getLocalParticipant(state);
166
 
160
 
167
     return {
161
     return {
168
-        _avatarURL: getAvatarURL(localParticipant),
169
-        _displayName: getParticipantDisplayName(state, localParticipant.id),
162
+        _displayName: getParticipantDisplayName(state, _localParticipant.id),
163
+        _localParticipantId: _localParticipant.id,
170
         _visible: state['features/welcome'].sideBarVisible
164
         _visible: state['features/welcome'].sideBarVisible
171
     };
165
     };
172
 }
166
 }

+ 0
- 8
react/features/welcome/components/styles.js 查看文件

38
         flexDirection: 'row'
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
      * Join button style.
42
      * Join button style.
51
      */
43
      */

正在加载...
取消
保存