Browse Source

feat: improve chat UX

master
Bettenbuk Zoltan 5 years ago
parent
commit
6121e9fc65

+ 2
- 0
lang/main.json View File

@@ -47,8 +47,10 @@
47 47
     },
48 48
     "chat": {
49 49
         "error": "Error: your message was not sent. Reason: {{error}}",
50
+        "fieldPlaceHolder": "Type your message here",
50 51
         "messagebox": "Type a message",
51 52
         "messageTo": "Private message to {{recipient}}",
53
+        "noMessagesMessage": "There are no messages in the meeting yet. Start a conversation here!",
52 54
         "nickname": {
53 55
             "popover": "Choose a nickname",
54 56
             "title": "Enter a nickname to use chat"

+ 2
- 2
react/features/chat/components/AbstractMessageContainer.js View File

@@ -15,7 +15,7 @@ export type Props = {
15 15
  *
16 16
  * @extends PureComponent
17 17
  */
18
-export default class AbstractMessageContainer extends PureComponent<Props> {
18
+export default class AbstractMessageContainer<P: Props> extends PureComponent<P> {
19 19
     static defaultProps = {
20 20
         messages: []
21 21
     };
@@ -46,7 +46,7 @@ export default class AbstractMessageContainer extends PureComponent<Props> {
46 46
             }
47 47
         }
48 48
 
49
-        groups.push(currentGrouping);
49
+        currentGrouping.length && groups.push(currentGrouping);
50 50
 
51 51
         return groups;
52 52
     }

+ 25
- 2
react/features/chat/components/native/ChatInputBar.js View File

@@ -3,6 +3,7 @@
3 3
 import React, { Component } from 'react';
4 4
 import { TextInput, TouchableOpacity, View } from 'react-native';
5 5
 
6
+import { translate } from '../../../base/i18n';
6 7
 import { Icon, IconChatSend } from '../../../base/icons';
7 8
 import { Platform } from '../../../base/react';
8 9
 
@@ -13,7 +14,12 @@ type Props = {
13 14
     /**
14 15
      * Callback to invoke on message send.
15 16
      */
16
-    onSend: Function
17
+    onSend: Function,
18
+
19
+    /**
20
+     * Function to be used to translate i18n labels.
21
+     */
22
+    t: Function
17 23
 };
18 24
 
19 25
 type State = {
@@ -37,7 +43,7 @@ type State = {
37 43
 /**
38 44
  * Implements the chat input bar with text field and action(s).
39 45
  */
40
-export default class ChatInputBar extends Component<Props, State> {
46
+class ChatInputBar extends Component<Props, State> {
41 47
     /**
42 48
      * Instantiates a new instance of the component.
43 49
      *
@@ -53,6 +59,7 @@ export default class ChatInputBar extends Component<Props, State> {
53 59
         };
54 60
 
55 61
         this._onChangeText = this._onChangeText.bind(this);
62
+        this._onFieldReferenceAvailable = this._onFieldReferenceAvailable.bind(this);
56 63
         this._onFocused = this._onFocused.bind(this);
57 64
         this._onSubmit = this._onSubmit.bind(this);
58 65
     }
@@ -76,6 +83,8 @@ export default class ChatInputBar extends Component<Props, State> {
76 83
                     onChangeText = { this._onChangeText }
77 84
                     onFocus = { this._onFocused(true) }
78 85
                     onSubmitEditing = { this._onSubmit }
86
+                    placeholder = { this.props.t('chat.fieldPlaceHolder') }
87
+                    ref = { this._onFieldReferenceAvailable }
79 88
                     returnKeyType = 'send'
80 89
                     style = { styles.inputField }
81 90
                     value = { this.state.message } />
@@ -105,6 +114,18 @@ export default class ChatInputBar extends Component<Props, State> {
105 114
         });
106 115
     }
107 116
 
117
+    _onFieldReferenceAvailable: Object => void;
118
+
119
+    /**
120
+     * Callback to be invoked when the field reference is available.
121
+     *
122
+     * @param {Object} field - The reference to the field.
123
+     * @returns {void}
124
+     */
125
+    _onFieldReferenceAvailable(field) {
126
+        field && field.focus();
127
+    }
128
+
108 129
     _onFocused: boolean => Function;
109 130
 
110 131
     /**
@@ -138,3 +159,5 @@ export default class ChatInputBar extends Component<Props, State> {
138 159
         });
139 160
     }
140 161
 }
162
+
163
+export default translate(ChatInputBar);

+ 64
- 6
react/features/chat/components/native/MessageContainer.js View File

@@ -1,18 +1,36 @@
1 1
 // @flow
2 2
 
3 3
 import React from 'react';
4
-import { FlatList } from 'react-native';
4
+import { FlatList, Text, View } from 'react-native';
5 5
 
6
-import AbstractMessageContainer, { type Props }
6
+import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
+import { translate } from '../../../base/i18n';
8
+import { connect } from '../../../base/redux';
9
+import { StyleType } from '../../../base/styles';
10
+
11
+import AbstractMessageContainer, { type Props as AbstractProps }
7 12
     from '../AbstractMessageContainer';
8 13
 
9 14
 import ChatMessageGroup from './ChatMessageGroup';
10 15
 import styles from './styles';
11 16
 
17
+type Props = AbstractProps & {
18
+
19
+    /**
20
+     * The color-schemed stylesheet of the feature.
21
+     */
22
+    _styles: StyleType,
23
+
24
+    /**
25
+     * Function to be used to translate i18n labels.
26
+     */
27
+    t: Function
28
+};
29
+
12 30
 /**
13 31
  * Implements a container to render all the chat messages in a conference.
14 32
  */
15
-export default class MessageContainer extends AbstractMessageContainer {
33
+class MessageContainer extends AbstractMessageContainer<Props> {
16 34
     /**
17 35
      * Instantiates a new instance of the component.
18 36
      *
@@ -22,6 +40,7 @@ export default class MessageContainer extends AbstractMessageContainer {
22 40
         super(props);
23 41
 
24 42
         this._keyExtractor = this._keyExtractor.bind(this);
43
+        this._renderListEmptyComponent = this._renderListEmptyComponent.bind(this);
25 44
         this._renderMessageGroup = this._renderMessageGroup.bind(this);
26 45
     }
27 46
 
@@ -31,10 +50,16 @@ export default class MessageContainer extends AbstractMessageContainer {
31 50
      * @inheritdoc
32 51
      */
33 52
     render() {
53
+        const data = this._getMessagesGroupedBySender();
54
+
34 55
         return (
35 56
             <FlatList
36
-                data = { this._getMessagesGroupedBySender() }
37
-                inverted = { true }
57
+                ListEmptyComponent = { this._renderListEmptyComponent }
58
+                data = { data }
59
+
60
+                // Workaround for RN bug:
61
+                // https://github.com/facebook/react-native/issues/21196
62
+                inverted = { Boolean(data.length) }
38 63
                 keyExtractor = { this._keyExtractor }
39 64
                 keyboardShouldPersistTaps = 'always'
40 65
                 renderItem = { this._renderMessageGroup }
@@ -58,7 +83,26 @@ export default class MessageContainer extends AbstractMessageContainer {
58 83
         return `key_${index}`;
59 84
     }
60 85
 
61
-    _renderMessageGroup: Object => React$Element<*>;
86
+    _renderListEmptyComponent: () => React$Element<any>;
87
+
88
+    /**
89
+     * Renders a message when there are no messages in the chat yet.
90
+     *
91
+     * @returns {React$Element<any>}
92
+     */
93
+    _renderListEmptyComponent() {
94
+        const { _styles, t } = this.props;
95
+
96
+        return (
97
+            <View style = { styles.emptyComponentWrapper }>
98
+                <Text style = { _styles.emptyComponentText }>
99
+                    { t('chat.noMessagesMessage') }
100
+                </Text>
101
+            </View>
102
+        );
103
+    }
104
+
105
+    _renderMessageGroup: Object => React$Element<any>;
62 106
 
63 107
     /**
64 108
      * Renders a single chat message.
@@ -70,3 +114,17 @@ export default class MessageContainer extends AbstractMessageContainer {
70 114
         return <ChatMessageGroup messages = { messages } />;
71 115
     }
72 116
 }
117
+
118
+/**
119
+ * Maps part of the redux state to the props of this component.
120
+ *
121
+ * @param {Object} state - The Redux state.
122
+ * @returns {Props}
123
+ */
124
+function _mapStateToProps(state) {
125
+    return {
126
+        _styles: ColorSchemeRegistry.get(state, 'Chat')
127
+    };
128
+}
129
+
130
+export default translate(connect(_mapStateToProps)(MessageContainer));

+ 12
- 0
react/features/chat/components/native/styles.js View File

@@ -42,6 +42,13 @@ export default {
42 42
         flexDirection: 'column'
43 43
     },
44 44
 
45
+    emptyComponentWrapper: {
46
+        alignSelf: 'center',
47
+        flex: 1,
48
+        padding: BoxModel.padding,
49
+        paddingTop: '10%'
50
+    },
51
+
45 52
     /**
46 53
      * A special padding to avoid issues on some devices (such as Android devices with custom suggestions bar).
47 54
      */
@@ -143,6 +150,11 @@ ColorSchemeRegistry.register('Chat', {
143 150
         fontSize: 13
144 151
     },
145 152
 
153
+    emptyComponentText: {
154
+        color: schemeColor('displayName'),
155
+        textAlign: 'center'
156
+    },
157
+
146 158
     localMessageBubble: {
147 159
         backgroundColor: schemeColor('localMsgBackground'),
148 160
         borderTopRightRadius: 0

+ 1
- 1
react/features/chat/components/web/MessageContainer.js View File

@@ -14,7 +14,7 @@ import ChatMessageGroup from './ChatMessageGroup';
14 14
  *
15 15
  * @extends AbstractMessageContainer
16 16
  */
17
-export default class MessageContainer extends AbstractMessageContainer {
17
+export default class MessageContainer extends AbstractMessageContainer<Props> {
18 18
     /**
19 19
      * Whether or not chat has been scrolled to the bottom of the screen. Used
20 20
      * to determine if chat should be scrolled automatically to the bottom when

Loading…
Cancel
Save