Преглед на файлове

[RN] Add chat functionality

Co-authored-by: DimaG <dgeorgiev06@gmail.com>
j8
Bettenbuk Zoltan преди 6 години
родител
ревизия
8a241ba2b7
променени са 43 файла, в които са добавени 1014 реда и са изтрити 198 реда
  1. 1
    0
      android/sdk/build.gradle
  2. 3
    0
      css/_font.scss
  3. Двоични данни
      fonts/jitsi.eot
  4. 1
    0
      fonts/jitsi.svg
  5. Двоични данни
      fonts/jitsi.ttf
  6. Двоични данни
      fonts/jitsi.woff
  7. 1
    1
      fonts/selection.json
  8. 4
    0
      ios/sdk/sdk.xcodeproj/project.pbxproj
  9. 4
    3
      lang/main.json
  10. 77
    3
      package-lock.json
  11. 1
    0
      package.json
  12. 3
    1
      react/features/chat/actionTypes.js
  13. 5
    3
      react/features/chat/actions.js
  14. 113
    0
      react/features/chat/components/AbstractChat.js
  15. 48
    0
      react/features/chat/components/AbstractChatMessage.js
  16. 0
    0
      react/features/chat/components/Chat.native.js
  17. 0
    0
      react/features/chat/components/ChatCounter.native.js
  18. 0
    0
      react/features/chat/components/ChatInput.native.js
  19. 0
    0
      react/features/chat/components/ChatMessage.native.js
  20. 0
    0
      react/features/chat/components/DisplayNameForm.native.js
  21. 0
    0
      react/features/chat/components/SmileysPanel.native.js
  22. 0
    2
      react/features/chat/components/index.js
  23. 3
    0
      react/features/chat/components/index.native.js
  24. 3
    0
      react/features/chat/components/index.web.js
  25. 168
    0
      react/features/chat/components/native/Chat.js
  26. 129
    0
      react/features/chat/components/native/ChatButton.js
  27. 152
    0
      react/features/chat/components/native/ChatMessage.js
  28. 4
    0
      react/features/chat/components/native/index.js
  29. 124
    0
      react/features/chat/components/native/styles.js
  30. 12
    119
      react/features/chat/components/web/Chat.js
  31. 1
    1
      react/features/chat/components/web/ChatCounter.js
  32. 1
    1
      react/features/chat/components/web/ChatInput.js
  33. 7
    21
      react/features/chat/components/web/ChatMessage.js
  34. 2
    2
      react/features/chat/components/web/DisplayNameForm.js
  35. 1
    1
      react/features/chat/components/web/SmileysPanel.js
  36. 4
    0
      react/features/chat/components/web/index.js
  37. 61
    38
      react/features/chat/middleware.js
  38. 0
    0
      react/features/chat/sounds.js
  39. 3
    0
      react/features/conference/components/Conference.native.js
  40. 30
    0
      react/features/display-name/components/DisplayNamePrompt.native.js
  41. 2
    0
      react/features/toolbox/components/native/OverflowMenu.js
  42. 36
    2
      react/features/toolbox/components/native/Toolbox.js
  43. 10
    0
      react/features/toolbox/components/native/styles.js

+ 1
- 0
android/sdk/build.gradle Целия файл

@@ -124,6 +124,7 @@ android.libraryVariants.all { def variant ->
124 124
         // Bundle sounds
125 125
         //
126 126
         copy {
127
+            from("${projectDir}/../../sounds/incomingMessage.wav")
127 128
             from("${projectDir}/../../sounds/joined.wav")
128 129
             from("${projectDir}/../../sounds/left.wav")
129 130
             from("${projectDir}/../../sounds/outgoingRinging.wav")

+ 3
- 0
css/_font.scss Целия файл

@@ -25,6 +25,9 @@
25 25
     -moz-osx-font-smoothing: grayscale;
26 26
 }
27 27
 
28
+.icon-chat-unread:before {
29
+  content: "\e0b7";
30
+}
28 31
 .icon-arrow_back:before {
29 32
   content: "\e5c4";
30 33
 }

Двоични данни
fonts/jitsi.eot Целия файл


+ 1
- 0
fonts/jitsi.svg Целия файл

@@ -7,6 +7,7 @@
7 7
 <font-face units-per-em="1024" ascent="1024" descent="0" />
8 8
 <missing-glyph horiz-adv-x="1024" />
9 9
 <glyph unicode="&#x20;" d="" />
10
+<glyph unicode="&#xe0b7;" glyph-name="chat-unread" d="M768 682v86h-512v-86h512zM598 426v86h-342v-86h342zM256 640v-86h512v86h-512zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
10 11
 <glyph unicode="&#xe0cd;" glyph-name="phone" d="M282 564c62-120 162-220 282-282l94 94c12 12 30 16 44 10 48-16 100-24 152-24 24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44z" />
11 12
 <glyph unicode="&#xe145;" glyph-name="add" d="M810 470h-256v-256h-84v256h-256v84h256v256h84v-256h256v-84z" />
12 13
 <glyph unicode="&#xe1aa;" glyph-name="bluetooth" d="M550 328l-80 82v-162zM470 776v-162l80 82zM670 696l-184-184 184-184-244-242h-42v324l-196-196-60 60 238 238-238 238 60 60 196-196v324h42zM834 738c40-64 62-142 62-222 0-84-24-160-66-226l-50 50c26 52 42 110 42 172s-16 120-42 172zM608 512l98 98c12-30 20-64 20-98s-8-70-20-100z" />

Двоични данни
fonts/jitsi.ttf Целия файл


Двоични данни
fonts/jitsi.woff Целия файл


+ 1
- 1
fonts/selection.json
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 4
- 0
ios/sdk/sdk.xcodeproj/project.pbxproj Целия файл

@@ -32,6 +32,7 @@
32 32
 		6C31EDCA20C06D530089C899 /* recordingOff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC920C06D530089C899 /* recordingOff.mp3 */; };
33 33
 		75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
34 34
 		75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
35
+		87FE6F3321E52437004A5DC7 /* incomingMessage.wav in Resources */ = {isa = PBXBuildFile; fileRef = 87FE6F3221E52437004A5DC7 /* incomingMessage.wav */; };
35 36
 		A4414AE020B37F1A003546E6 /* rejected.wav in Resources */ = {isa = PBXBuildFile; fileRef = A4414ADF20B37F1A003546E6 /* rejected.wav */; };
36 37
 		A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */ = {isa = PBXBuildFile; fileRef = A4A934E8212F3ADB001E9388 /* Dropbox.m */; };
37 38
 		B386B85720981A75000DEF7A /* InviteController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85020981A74000DEF7A /* InviteController.m */; };
@@ -85,6 +86,7 @@
85 86
 		6C31EDC920C06D530089C899 /* recordingOff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOff.mp3; path = ../../sounds/recordingOff.mp3; sourceTree = "<group>"; };
86 87
 		75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
87 88
 		75635B0920751D6D00F29C9F /* left.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = left.wav; path = ../../sounds/left.wav; sourceTree = "<group>"; };
89
+		87FE6F3221E52437004A5DC7 /* incomingMessage.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = incomingMessage.wav; path = ../../sounds/incomingMessage.wav; sourceTree = "<group>"; };
88 90
 		98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
89 91
 		9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
90 92
 		A4414ADF20B37F1A003546E6 /* rejected.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = rejected.wav; path = ../../sounds/rejected.wav; sourceTree = "<group>"; };
@@ -125,6 +127,7 @@
125 127
 		0BCA49681EC4BBE500B793EE /* Resources */ = {
126 128
 			isa = PBXGroup;
127 129
 			children = (
130
+				87FE6F3221E52437004A5DC7 /* incomingMessage.wav */,
128 131
 				0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
129 132
 				C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
130 133
 				C6245F5C2053091D0040BE68 /* image-resize@3x.png */,
@@ -335,6 +338,7 @@
335 338
 			isa = PBXResourcesBuildPhase;
336 339
 			buildActionMask = 2147483647;
337 340
 			files = (
341
+				87FE6F3321E52437004A5DC7 /* incomingMessage.wav in Resources */,
338 342
 				0B49424520AD8DBD00BD2DE0 /* outgoingStart.wav in Resources */,
339 343
 				6C31EDCA20C06D530089C899 /* recordingOff.mp3 in Resources */,
340 344
 				A4414AE020B37F1A003546E6 /* rejected.wav in Resources */,

+ 4
- 3
lang/main.json Целия файл

@@ -53,8 +53,9 @@
53 53
         "messagebox": "Enter text...",
54 54
         "nickname": {
55 55
             "popover": "Choose a nickname",
56
-            "title": "Enter a nickname in the box below"
57
-        }
56
+            "title": "Enter a nickname to use chat"
57
+        },
58
+        "title": "Chat"
58 59
     },
59 60
     "connection": {
60 61
         "ATTACHED": "Attached",
@@ -642,7 +643,7 @@
642 643
         "Settings": "Settings",
643 644
         "sharedvideo": "Share a YouTube video",
644 645
         "sharedVideoMutedPopup": "Your shared video has been muted so that you can talk to the other members.",
645
-        "shareRoom": "Share room",
646
+        "shareRoom": "Invite someone",
646 647
         "shortcuts": "View shortcuts",
647 648
         "sip": "Call SIP number",
648 649
         "speakerStats": "Speaker stats",

+ 77
- 3
package-lock.json Целия файл

@@ -2166,6 +2166,15 @@
2166 2166
         }
2167 2167
       }
2168 2168
     },
2169
+    "@expo/react-native-action-sheet": {
2170
+      "version": "1.1.2",
2171
+      "resolved": "https://registry.npmjs.org/@expo/react-native-action-sheet/-/react-native-action-sheet-1.1.2.tgz",
2172
+      "integrity": "sha512-//2EvHVBFVGSAzuJvG0I1UoQVzGJBo2f1CkO+RMnEWdR0FeWYmV7+pCThIroL1czRm/oOtoMxiGS6FgXt6QgVA==",
2173
+      "requires": {
2174
+        "hoist-non-react-statics": "^2.2.2",
2175
+        "prop-types": "^15.5.10"
2176
+      }
2177
+    },
2169 2178
     "@jitsi/sdp-interop": {
2170 2179
       "version": "0.1.13",
2171 2180
       "resolved": "https://registry.npmjs.org/@jitsi/sdp-interop/-/sdp-interop-0.1.13.tgz",
@@ -2193,7 +2202,7 @@
2193 2202
     },
2194 2203
     "@segment/top-domain": {
2195 2204
       "version": "3.0.0",
2196
-      "resolved": "http://registry.npmjs.org/@segment/top-domain/-/top-domain-3.0.0.tgz",
2205
+      "resolved": "https://registry.npmjs.org/@segment/top-domain/-/top-domain-3.0.0.tgz",
2197 2206
       "integrity": "sha1-AuWlpP1CqfbPiGsF6C8QQBKjw6c=",
2198 2207
       "requires": {
2199 2208
         "component-cookie": "^1.1.2",
@@ -3142,6 +3151,14 @@
3142 3151
         "util.promisify": "^1.0.0"
3143 3152
       }
3144 3153
     },
3154
+    "babel-plugin-check-es2015-constants": {
3155
+      "version": "6.22.0",
3156
+      "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
3157
+      "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
3158
+      "requires": {
3159
+        "babel-runtime": "^6.22.0"
3160
+      }
3161
+    },
3145 3162
     "babel-plugin-syntax-trailing-function-commas": {
3146 3163
       "version": "7.0.0-beta.0",
3147 3164
       "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz",
@@ -4112,7 +4129,7 @@
4112 4129
       "dependencies": {
4113 4130
         "debug": {
4114 4131
           "version": "2.2.0",
4115
-          "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
4132
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
4116 4133
           "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
4117 4134
           "requires": {
4118 4135
             "ms": "0.7.1"
@@ -4120,7 +4137,7 @@
4120 4137
         },
4121 4138
         "ms": {
4122 4139
           "version": "0.7.1",
4123
-          "resolved": "http://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
4140
+          "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
4124 4141
           "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
4125 4142
         }
4126 4143
       }
@@ -8368,6 +8385,11 @@
8368 8385
       "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.1.9.tgz",
8369 8386
       "integrity": "sha1-lkojxU5IiUBbSGGlyfBIDUUUHfo="
8370 8387
     },
8388
+    "keymirror": {
8389
+      "version": "0.1.1",
8390
+      "resolved": "https://registry.npmjs.org/keymirror/-/keymirror-0.1.1.tgz",
8391
+      "integrity": "sha1-kYiJ6hP40KQufFVyUO7nE63JXDU="
8392
+    },
8371 8393
     "killable": {
8372 8394
       "version": "1.0.1",
8373 8395
       "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -11444,11 +11466,37 @@
11444 11466
         "jssha": "^2.2.0"
11445 11467
       }
11446 11468
     },
11469
+    "react-native-communications": {
11470
+      "version": "2.2.1",
11471
+      "resolved": "https://registry.npmjs.org/react-native-communications/-/react-native-communications-2.2.1.tgz",
11472
+      "integrity": "sha1-eIO1ayCgAu63kMET+GFuqGksp5U="
11473
+    },
11447 11474
     "react-native-fast-image": {
11448 11475
       "version": "5.1.1",
11449 11476
       "resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.1.1.tgz",
11450 11477
       "integrity": "sha512-kEzgZxbbXYhy27u5GnhrKitn+XDBFAHSDUJdYC6llMi5cDPjgcqhOAQABj0K+ga5pn+/xPZLmD882rrUGiwVVA=="
11451 11478
     },
11479
+    "react-native-gifted-chat": {
11480
+      "version": "0.6.0",
11481
+      "resolved": "https://registry.npmjs.org/react-native-gifted-chat/-/react-native-gifted-chat-0.6.0.tgz",
11482
+      "integrity": "sha512-KYI/okKUZmjcJM3I6BP10KG1WNkCKBZhY8N47wk407dr+KqLS4+LR13UKo7j3f++5SrX2Ex+7vYvIQ2pBdzCiA==",
11483
+      "requires": {
11484
+        "@expo/react-native-action-sheet": "^1.0.1",
11485
+        "moment": "^2.19.0",
11486
+        "react-native-communications": "2.2.1",
11487
+        "react-native-lightbox": "^0.7.0",
11488
+        "react-native-parsed-text": "^0.0.20",
11489
+        "react-native-video": "^3.2.1",
11490
+        "uuid": "3.3.0"
11491
+      },
11492
+      "dependencies": {
11493
+        "uuid": {
11494
+          "version": "3.3.0",
11495
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.0.tgz",
11496
+          "integrity": "sha512-ijO9N2xY/YaOqQ5yz5c4sy2ZjWmA6AR6zASb/gdpeKZ8+948CxwfMW9RrKVk5may6ev8c0/Xguu32e2Llelpqw=="
11497
+        }
11498
+      }
11499
+    },
11452 11500
     "react-native-google-signin": {
11453 11501
       "version": "1.0.2",
11454 11502
       "resolved": "https://registry.npmjs.org/react-native-google-signin/-/react-native-google-signin-1.0.2.tgz",
@@ -11464,11 +11512,28 @@
11464 11512
       "resolved": "https://registry.npmjs.org/react-native-keep-awake/-/react-native-keep-awake-4.0.0.tgz",
11465 11513
       "integrity": "sha512-0Fotox+eLXQooeibVs3P60yASYUWjtRw9MZNmbuHt5UZQrgUrAKsE4jm7gTr4tPU1m1RkwGzcgUFpcOkh/ec7g=="
11466 11514
     },
11515
+    "react-native-lightbox": {
11516
+      "version": "0.7.0",
11517
+      "resolved": "https://registry.npmjs.org/react-native-lightbox/-/react-native-lightbox-0.7.0.tgz",
11518
+      "integrity": "sha512-HS3T4WlCd0Gb3us2d6Jse5m6KjNhngnKm35Wapq30WtQa9s+/VMmtuktbGPGaWtswcDyOj6qByeJBw9W80iPCA==",
11519
+      "requires": {
11520
+        "prop-types": "^15.5.10"
11521
+      }
11522
+    },
11467 11523
     "react-native-linear-gradient": {
11468 11524
       "version": "2.5.3",
11469 11525
       "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.5.3.tgz",
11470 11526
       "integrity": "sha512-XdusrOXXlkI+yQpUW7YLeiq9cZiBwkvQX4XEkHPVrJ9H47gsKmdgBwObkZBzBQUP0dKK/Sg6aVpETEis4w43bQ=="
11471 11527
     },
11528
+    "react-native-parsed-text": {
11529
+      "version": "0.0.20",
11530
+      "resolved": "https://registry.npmjs.org/react-native-parsed-text/-/react-native-parsed-text-0.0.20.tgz",
11531
+      "integrity": "sha512-n77hYu64Tr3oclzIXBXXaiLh1WbMKdA2Y0x6bX/yqwxAM4afcObENY5VrNB+EsTBJBEDqrypA9D1p2cLEIHkuQ==",
11532
+      "requires": {
11533
+        "babel-plugin-check-es2015-constants": "6.22.0",
11534
+        "prop-types": "^15.5.10"
11535
+      }
11536
+    },
11472 11537
     "react-native-sound": {
11473 11538
       "version": "github:jitsi/react-native-sound#e4260ed7f641eeb0377d76eac7987aba72e1cf08",
11474 11539
       "from": "github:jitsi/react-native-sound#e4260ed7f641eeb0377d76eac7987aba72e1cf08"
@@ -11524,6 +11589,15 @@
11524 11589
         }
11525 11590
       }
11526 11591
     },
11592
+    "react-native-video": {
11593
+      "version": "3.2.1",
11594
+      "resolved": "https://registry.npmjs.org/react-native-video/-/react-native-video-3.2.1.tgz",
11595
+      "integrity": "sha512-Xansfoo/to80FwhM1HKlf7pCxDZ5RtV+kG3piCVvsNAhPY4GGwiOGUH9y3Y+mFQIDEWcY8I9j16lsFYAbnue3g==",
11596
+      "requires": {
11597
+        "keymirror": "0.1.1",
11598
+        "prop-types": "^15.5.10"
11599
+      }
11600
+    },
11527 11601
     "react-native-webrtc": {
11528 11602
       "version": "github:jitsi/react-native-webrtc#c1be0cb1c6e8a83dfd406e478082a5ff205a97ec",
11529 11603
       "from": "github:jitsi/react-native-webrtc#c1be0cb1c6e8a83dfd406e478082a5ff205a97ec",

+ 1
- 0
package.json Целия файл

@@ -66,6 +66,7 @@
66 66
     "react-native-calendar-events": "1.6.4",
67 67
     "react-native-callstats": "3.53.4",
68 68
     "react-native-fast-image": "5.1.1",
69
+    "react-native-gifted-chat": "0.6.0",
69 70
     "react-native-google-signin": "1.0.2",
70 71
     "react-native-immersive": "2.0.0",
71 72
     "react-native-keep-awake": "4.0.0",

+ 3
- 1
react/features/chat/actionTypes.js Целия файл

@@ -1,3 +1,5 @@
1
+// @flow
2
+
1 3
 /**
2 4
  * The type of the action which signals to add a new chat message.
3 5
  *
@@ -14,7 +16,7 @@
14 16
 export const ADD_MESSAGE = Symbol('ADD_MESSAGE');
15 17
 
16 18
 /**
17
- * The type of the action which signals to remove all saved chat messages.
19
+ * The type of the action which signals to clear messages in Redux.
18 20
  *
19 21
  * {
20 22
  *     type: CLEAR_MESSAGES

+ 5
- 3
react/features/chat/actions.js Целия файл

@@ -1,3 +1,5 @@
1
+// @flow
2
+
1 3
 import {
2 4
     ADD_MESSAGE,
3 5
     CLEAR_MESSAGES,
@@ -27,7 +29,7 @@ import {
27 29
  *     timestamp: string,
28 30
  * }}
29 31
  */
30
-export function addMessage(messageDetails) {
32
+export function addMessage(messageDetails: Object) {
31 33
     return {
32 34
         type: ADD_MESSAGE,
33 35
         ...messageDetails
@@ -35,7 +37,7 @@ export function addMessage(messageDetails) {
35 37
 }
36 38
 
37 39
 /**
38
- * Removes all stored chat messages.
40
+ * Clears the chat messages in Redux.
39 41
  *
40 42
  * @returns {{
41 43
  *     type: CLEAR_MESSAGES
@@ -56,7 +58,7 @@ export function clearMessages() {
56 58
  *     message: string
57 59
  * }}
58 60
  */
59
-export function sendMessage(message) {
61
+export function sendMessage(message: string) {
60 62
     return {
61 63
         type: SEND_MESSAGE,
62 64
         message

+ 113
- 0
react/features/chat/components/AbstractChat.js Целия файл

@@ -0,0 +1,113 @@
1
+// @flow
2
+
3
+import { Component } from 'react';
4
+
5
+import { getLocalParticipant } from '../../base/participants';
6
+
7
+import { sendMessage, toggleChat } from '../actions';
8
+
9
+/**
10
+ * The type of the React {@code Component} props of {@code AbstractChat}.
11
+ */
12
+export type Props = {
13
+
14
+    /**
15
+     * True if the chat window should be rendered.
16
+     */
17
+    _isOpen: boolean,
18
+
19
+    /**
20
+     * All the chat messages in the conference.
21
+     */
22
+    _messages: Array<Object>,
23
+
24
+    /**
25
+     * Function to send a text message.
26
+     *
27
+     * @protected
28
+     */
29
+    _onSendMessage: Function,
30
+
31
+    /**
32
+     * Function to toggle the chat window.
33
+     */
34
+    _onToggleChat: Function,
35
+
36
+    /**
37
+     * Whether or not to block chat access with a nickname input form.
38
+     */
39
+    _showNamePrompt: boolean,
40
+
41
+    /**
42
+     * The Redux dispatch function.
43
+     */
44
+    dispatch: Dispatch<*>,
45
+
46
+    /**
47
+     * Function to be used to translate i18n labels.
48
+     */
49
+    t: Function
50
+};
51
+
52
+/**
53
+ * Implements an abstract chat panel.
54
+ */
55
+export default class AbstractChat<P: Props> extends Component<P> {}
56
+
57
+/**
58
+ * Maps redux actions to the props of the component.
59
+ *
60
+ * @param {Function} dispatch - The redux action {@code dispatch} function.
61
+ * @returns {{
62
+ *     _onSendMessage: Function,
63
+ *     _onToggleChat: Function
64
+ * }}
65
+ * @private
66
+ */
67
+export function _mapDispatchToProps(dispatch: Dispatch<*>) {
68
+    return {
69
+        /**
70
+         * Toggles the chat window.
71
+         *
72
+         * @returns {Function}
73
+         */
74
+        _onToggleChat() {
75
+            dispatch(toggleChat());
76
+        },
77
+
78
+        /**
79
+         * Sends a text message.
80
+         *
81
+         * @private
82
+         * @param {string} text - The text message to be sent.
83
+         * @returns {void}
84
+         * @type {Function}
85
+         */
86
+        _onSendMessage(text: string) {
87
+            dispatch(sendMessage(text));
88
+        }
89
+    };
90
+}
91
+
92
+/**
93
+ * Maps (parts of) the redux state to {@link Chat} React {@code Component}
94
+ * props.
95
+ *
96
+ * @param {Object} state - The redux store/state.
97
+ * @private
98
+ * @returns {{
99
+ *     _isOpen: boolean,
100
+ *     _messages: Array<Object>,
101
+ *     _showNamePrompt: boolean
102
+ * }}
103
+ */
104
+export function _mapStateToProps(state: Object) {
105
+    const { isOpen, messages } = state['features/chat'];
106
+    const _localParticipant = getLocalParticipant(state);
107
+
108
+    return {
109
+        _isOpen: isOpen,
110
+        _messages: messages,
111
+        _showNamePrompt: !_localParticipant.name
112
+    };
113
+}

+ 48
- 0
react/features/chat/components/AbstractChatMessage.js Целия файл

@@ -0,0 +1,48 @@
1
+// @flow
2
+
3
+import { PureComponent } from 'react';
4
+
5
+import { getAvatarURLByParticipantId } from '../../base/participants';
6
+
7
+/**
8
+ * The type of the React {@code Component} props of {@code AbstractChatMessage}.
9
+ */
10
+export type Props = {
11
+
12
+    /**
13
+     * The URL of the avatar of the participant.
14
+     */
15
+    _avatarURL: string,
16
+
17
+    /**
18
+     * The representation of a chat message.
19
+     */
20
+    message: Object,
21
+
22
+    /**
23
+     * Invoked to receive translated strings.
24
+     */
25
+    t: Function
26
+};
27
+
28
+/**
29
+ * Abstract component to display a chat message.
30
+ */
31
+export default class AbstractChatMessage<P: Props> extends PureComponent<P> {}
32
+
33
+/**
34
+ * Maps part of the Redux state to the props of this component.
35
+ *
36
+ * @param {Object} state - The Redux state.
37
+ * @param {Props} ownProps - The own props of the component.
38
+ * @returns {{
39
+ *     _avatarURL: string
40
+ * }}
41
+ */
42
+export function _mapStateToProps(state: Object, ownProps: Props) {
43
+    const { message } = ownProps;
44
+
45
+    return {
46
+        _avatarURL: getAvatarURLByParticipantId(state, message.user._id)
47
+    };
48
+}

+ 0
- 0
react/features/chat/components/Chat.native.js Целия файл


+ 0
- 0
react/features/chat/components/ChatCounter.native.js Целия файл


+ 0
- 0
react/features/chat/components/ChatInput.native.js Целия файл


+ 0
- 0
react/features/chat/components/ChatMessage.native.js Целия файл


+ 0
- 0
react/features/chat/components/DisplayNameForm.native.js Целия файл


+ 0
- 0
react/features/chat/components/SmileysPanel.native.js Целия файл


+ 0
- 2
react/features/chat/components/index.js Целия файл

@@ -1,2 +0,0 @@
1
-export Chat from './Chat';
2
-export ChatCounter from './ChatCounter';

+ 3
- 0
react/features/chat/components/index.native.js Целия файл

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

+ 3
- 0
react/features/chat/components/index.web.js Целия файл

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

+ 168
- 0
react/features/chat/components/native/Chat.js Целия файл

@@ -0,0 +1,168 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { SafeAreaView } from 'react-native';
5
+import { GiftedChat } from 'react-native-gifted-chat';
6
+import { connect } from 'react-redux';
7
+
8
+import { translate } from '../../../base/i18n';
9
+import { BackButton, Header, HeaderLabel, Modal } from '../../../base/react';
10
+
11
+import AbstractChat, {
12
+    _mapDispatchToProps,
13
+    _mapStateToProps as _abstractMapStateToProps,
14
+    type Props as AbstractProps
15
+} from '../AbstractChat';
16
+
17
+import ChatMessage from './ChatMessage';
18
+import styles from './styles';
19
+
20
+type Props = AbstractProps & {
21
+
22
+    /**
23
+     * True if the chat window should have a solid BG render.
24
+     */
25
+    _solidBackground: boolean
26
+}
27
+
28
+
29
+/**
30
+ * Implements a React native component that renders the chat window (modal) of
31
+ * the mobile client.
32
+ */
33
+class Chat extends AbstractChat<Props> {
34
+
35
+    /**
36
+       * Initializes a new instance.
37
+       *
38
+       * @inheritdoc
39
+       */
40
+    constructor(props: Props) {
41
+        super(props);
42
+
43
+        this._onSend = this._onSend.bind(this);
44
+        this._renderMessage = this._renderMessage.bind(this);
45
+        this._transformMessage = this._transformMessage.bind(this);
46
+    }
47
+
48
+    /**
49
+     * Implements React's {@link Component#render()}.
50
+     *
51
+     * @inheritdoc
52
+     */
53
+    render() {
54
+        const messages = this.props._messages.map(this._transformMessage);
55
+        const modalStyle = [
56
+            styles.modalBackdrop
57
+        ];
58
+
59
+        if (this.props._solidBackground) {
60
+            // We only use a transparent background, when we are in a video
61
+            // meeting to give a user a glympse of what's happening. Otherwise
62
+            // we use a non-transparent background.
63
+            modalStyle.push(styles.solidModalBackdrop);
64
+        }
65
+
66
+        return (
67
+            <Modal
68
+                onRequestClose = { this.props._onToggleChat }
69
+                visible = { this.props._isOpen }>
70
+                <Header>
71
+                    <BackButton onPress = { this.props._onToggleChat } />
72
+                    <HeaderLabel labelKey = 'chat.title' />
73
+                </Header>
74
+                <SafeAreaView style = { modalStyle }>
75
+                    <GiftedChat
76
+                        messages = { messages }
77
+                        onSend = { this._onSend }
78
+                        renderMessage = { this._renderMessage } />
79
+                </SafeAreaView>
80
+            </Modal>
81
+        );
82
+    }
83
+
84
+    _onSend: (Array<Object>) => void;
85
+
86
+    /**
87
+     * Callback to trigger a message send action.
88
+     *
89
+     * @param {string} message - The chat message to display.
90
+     * @returns {void}
91
+     */
92
+    _onSend([ message ]) {
93
+        this.props._onSendMessage(message.text);
94
+    }
95
+
96
+    _renderMessage: Object => React$Element<*>
97
+
98
+    /**
99
+     * Renders a single message.
100
+     *
101
+     * @param {Object} messageProps - The message props object to be rendered.
102
+     * @returns {React$Element<*>}
103
+     */
104
+    _renderMessage(messageProps) {
105
+        const { currentMessage } = messageProps;
106
+
107
+        return (
108
+            <ChatMessage message = { currentMessage } />
109
+        );
110
+    }
111
+
112
+    _transformMessage: (Object, number) => Object;
113
+
114
+    /**
115
+     * Transforms a Jitsi message object to a format that gifted-chat can
116
+     * handle.
117
+     *
118
+     * @param {Object} message - The chat message in our internal format.
119
+     * @param {number} index - The index of the message in the array.
120
+     * @returns {Object}
121
+     */
122
+    _transformMessage(message, index) {
123
+        const system = message.messageType === 'error';
124
+
125
+        return (
126
+            {
127
+                _id: index,
128
+                createdAt: new Date(message.timestamp),
129
+                messageType: message.messageType,
130
+                system,
131
+                text: system
132
+                    ? this.props.t('chat.error', {
133
+                        error: message.error,
134
+                        originalText: message.message
135
+                    })
136
+                    : message.message,
137
+                user: {
138
+                    _id: message.id,
139
+                    name: message.displayName
140
+                }
141
+            }
142
+        );
143
+    }
144
+}
145
+
146
+/**
147
+ * Maps part of the Redux state to the props of this component.
148
+ *
149
+ * @param {Object} state - The Redux state.
150
+ * @returns {{
151
+ *     _solidBackground: boolean
152
+ * }}
153
+ */
154
+function _mapStateToProps(state) {
155
+    const abstractReduxProps = _abstractMapStateToProps(state);
156
+
157
+    return {
158
+        ...abstractReduxProps,
159
+
160
+        // Gifted chat requires the messages to be reverse ordered.
161
+        _messages: [
162
+            ...abstractReduxProps._messages
163
+        ].reverse(),
164
+        _solidBackground: state['features/base/conference'].audioOnly
165
+    };
166
+}
167
+
168
+export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));

+ 129
- 0
react/features/chat/components/native/ChatButton.js Целия файл

@@ -0,0 +1,129 @@
1
+// @flow
2
+
3
+import { connect } from 'react-redux';
4
+
5
+import { getLocalParticipant } from '../../../base/participants';
6
+import {
7
+    AbstractButton,
8
+    type AbstractButtonProps
9
+} from '../../../base/toolbox';
10
+import { openDisplayNamePrompt } from '../../../display-name';
11
+
12
+import { toggleChat } from '../../actions';
13
+import { getUnreadCount } from '../../functions';
14
+
15
+type Props = AbstractButtonProps & {
16
+
17
+    /**
18
+     * Function to display chat.
19
+     *
20
+     * @protected
21
+     */
22
+    _displayChat: Function,
23
+
24
+    /**
25
+     * Function to diaply the name prompt before displaying the chat
26
+     * window, if the user has no display name set.
27
+     */
28
+    _displayNameInputDialog: Function,
29
+
30
+    /**
31
+     * Whether or not to block chat access with a nickname input form.
32
+     */
33
+    _showNamePrompt: boolean,
34
+
35
+    /**
36
+     * The unread message count.
37
+     */
38
+    _unreadMessageCount: number
39
+};
40
+
41
+/**
42
+ * Implements an {@link AbstractButton} to open the chat screen on mobile.
43
+ */
44
+class ChatButton extends AbstractButton<Props, *> {
45
+    accessibilityLabel = 'toolbar.accessibilityLabel.chat';
46
+    iconName = 'chat';
47
+    label = 'toolbar.chat';
48
+    toggledIconName = 'chat-unread';
49
+
50
+    /**
51
+     * Handles clicking / pressing the button, and opens the appropriate dialog.
52
+     *
53
+     * @private
54
+     * @returns {void}
55
+     */
56
+    _handleClick() {
57
+        if (this.props._showNamePrompt) {
58
+            this.props._displayNameInputDialog(() => {
59
+                this.props._displayChat();
60
+            });
61
+        } else {
62
+            this.props._displayChat();
63
+        }
64
+    }
65
+
66
+    /**
67
+     * Renders the button toggled when there are unread messages.
68
+     *
69
+     * @protected
70
+     * @returns {boolean}
71
+     */
72
+    _isToggled() {
73
+        return Boolean(this.props._unreadMessageCount);
74
+    }
75
+}
76
+
77
+/**
78
+ * Maps redux actions to the props of the component.
79
+ *
80
+ * @param {Function} dispatch - The redux action {@code dispatch} function.
81
+ * @returns {{
82
+ *     _displayChat,
83
+ *     _displayNameInputDialog
84
+ * }}
85
+ * @private
86
+ */
87
+function _mapDispatchToProps(dispatch: Function) {
88
+    return {
89
+        /**
90
+         * Launches native invite dialog.
91
+         *
92
+         * @private
93
+         * @returns {void}
94
+         */
95
+        _displayChat() {
96
+            dispatch(toggleChat());
97
+        },
98
+
99
+        /**
100
+         * Displays a diaply name prompt.
101
+         *
102
+         * @param {Function} onPostSubmit - The function to invoke after a
103
+         * succesfulsetting of the display name.
104
+         * @returns {void}
105
+         */
106
+        _displayNameInputDialog(onPostSubmit) {
107
+            dispatch(openDisplayNamePrompt(onPostSubmit));
108
+        }
109
+    };
110
+}
111
+
112
+/**
113
+ * Maps part of the redux state to the component's props.
114
+ *
115
+ * @param {Object} state - The Redux state.
116
+ * @returns {{
117
+ *     _unreadMessageCount
118
+ * }}
119
+ */
120
+function _mapStateToProps(state) {
121
+    const localParticipant = getLocalParticipant(state);
122
+
123
+    return {
124
+        _showNamePrompt: !localParticipant.name,
125
+        _unreadMessageCount: getUnreadCount(state)
126
+    };
127
+}
128
+
129
+export default connect(_mapStateToProps, _mapDispatchToProps)(ChatButton);

+ 152
- 0
react/features/chat/components/native/ChatMessage.js Целия файл

@@ -0,0 +1,152 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { Text, View } from 'react-native';
5
+import { connect } from 'react-redux';
6
+
7
+import { getLocalizedDateFormatter, translate } from '../../../base/i18n';
8
+import { Avatar } from '../../../base/participants';
9
+
10
+import AbstractChatMessage, {
11
+    _mapStateToProps as _abstractMapStateToProps,
12
+    type Props as AbstractProps
13
+} from '../AbstractChatMessage';
14
+import styles from './styles';
15
+
16
+/**
17
+ * Size of the rendered avatar in the message.
18
+ */
19
+const AVATAR_SIZE = 32;
20
+
21
+/**
22
+ * Formatter string to display the message timestamp.
23
+ */
24
+const TIMESTAMP_FORMAT = 'H:mm';
25
+
26
+type Props = AbstractProps & {
27
+
28
+    /**
29
+     * True if the chat window has a solid BG so then we have to adopt in style.
30
+     */
31
+    _solidBackground: boolean
32
+}
33
+
34
+/**
35
+ * Renders a single chat message.
36
+ */
37
+class ChatMessage extends AbstractChatMessage<Props> {
38
+    /**
39
+     * Implements {@code Component#render}.
40
+     *
41
+     * @inheritdoc
42
+     */
43
+    render() {
44
+        const { message } = this.props;
45
+        const timeStamp = getLocalizedDateFormatter(
46
+            message.createdAt).format(TIMESTAMP_FORMAT);
47
+        const localMessage = message.messageType === 'local';
48
+
49
+        // Style arrays that need to be updated in various scenarios, such as
50
+        // error messages or others.
51
+        const detailsWrapperStyle = [
52
+            styles.detailsWrapper
53
+        ];
54
+        const textWrapperStyle = [
55
+            styles.textWrapper
56
+        ];
57
+        const timeTextStyles = [
58
+            styles.timeText
59
+        ];
60
+
61
+        if (localMessage) {
62
+            // The wrapper needs to be aligned to the right.
63
+            detailsWrapperStyle.push(styles.ownMessageDetailsWrapper);
64
+
65
+            // The bubble needs to be differently styled.
66
+            textWrapperStyle.push(styles.ownTextWrapper);
67
+        } else if (message.system) {
68
+            // The bubble needs to be differently styled.
69
+            textWrapperStyle.push(styles.systemTextWrapper);
70
+        }
71
+
72
+        if (this.props._solidBackground) {
73
+            timeTextStyles.push(styles.solidBGTimeText);
74
+        }
75
+
76
+        return (
77
+            <View style = { styles.messageWrapper } >
78
+                {
79
+
80
+                    // Avatar is only rendered for remote messages.
81
+                    !localMessage && this._renderAvatar()
82
+                }
83
+                <View style = { detailsWrapperStyle }>
84
+                    <View style = { textWrapperStyle } >
85
+                        {
86
+
87
+                            // Display name is only rendered for remote
88
+                            // messages.
89
+                            !localMessage && this._renderDisplayName()
90
+                        }
91
+                        <Text style = { styles.messageText }>
92
+                            { message.text }
93
+                        </Text>
94
+                    </View>
95
+                    <Text style = { timeTextStyles }>
96
+                        { timeStamp }
97
+                    </Text>
98
+                </View>
99
+            </View>
100
+        );
101
+    }
102
+
103
+    /**
104
+     * Renders the avatar of the sender.
105
+     *
106
+     * @returns {React$Element<*>}
107
+     */
108
+    _renderAvatar() {
109
+        const { _avatarURL } = this.props;
110
+
111
+        return (
112
+            <View style = { styles.avatarWrapper }>
113
+                <Avatar
114
+                    size = { AVATAR_SIZE }
115
+                    uri = { _avatarURL } />
116
+            </View>
117
+        );
118
+    }
119
+
120
+    /**
121
+     * Renders the display name of the sender.
122
+     *
123
+     * @returns {React$Element<*>}
124
+     */
125
+    _renderDisplayName() {
126
+        const { message } = this.props;
127
+
128
+        return (
129
+            <Text style = { styles.displayName }>
130
+                { message.user.name }
131
+            </Text>
132
+        );
133
+    }
134
+}
135
+
136
+/**
137
+ * Maps part of the Redux state to the props of this component.
138
+ *
139
+ * @param {Object} state - The Redux state.
140
+ * @param {Props} ownProps - The own props of the component.
141
+ * @returns {{
142
+ *     _solidBackground: boolean
143
+ * }}
144
+ */
145
+function _mapStateToProps(state, ownProps) {
146
+    return {
147
+        ..._abstractMapStateToProps(state, ownProps),
148
+        _solidBackground: state['features/base/conference'].audioOnly
149
+    };
150
+}
151
+
152
+export default translate(connect(_mapStateToProps)(ChatMessage));

+ 4
- 0
react/features/chat/components/native/index.js Целия файл

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

+ 124
- 0
react/features/chat/components/native/styles.js Целия файл

@@ -0,0 +1,124 @@
1
+// @flow
2
+
3
+import {
4
+    ColorPalette,
5
+    createStyleSheet
6
+} from '../../../base/styles';
7
+
8
+/**
9
+ * The styles of the feature chat.
10
+ *
11
+ * NOTE: Sizes and colors come from the 8x8 guidelines. This is the first
12
+ * component to receive this treating, if others happen to have similar, we
13
+ * need to extract the brand colors and sizes into a branding feature (planned
14
+ * for the future).
15
+ */
16
+export default createStyleSheet({
17
+
18
+    /**
19
+     * Wrapper View for the avatar.
20
+     */
21
+    avatarWrapper: {
22
+        marginRight: 8
23
+    },
24
+
25
+    /**
26
+     * Wrapper for the details together, such as name, message and time.
27
+     */
28
+    detailsWrapper: {
29
+        alignItems: 'flex-start',
30
+        flex: 1,
31
+        flexDirection: 'column'
32
+    },
33
+
34
+    /**
35
+     * The text node for the display name.
36
+     */
37
+    displayName: {
38
+        color: 'rgb(118, 136, 152)',
39
+        fontSize: 13
40
+    },
41
+
42
+    /**
43
+     * The message text itself.
44
+     */
45
+    messageText: {
46
+        color: 'rgb(28, 32, 37)',
47
+        fontSize: 15
48
+    },
49
+
50
+    /**
51
+     * Wrapper View for the entire block.
52
+     */
53
+    messageWrapper: {
54
+        alignItems: 'flex-start',
55
+        flex: 1,
56
+        flexDirection: 'row',
57
+        marginHorizontal: 17,
58
+        marginVertical: 4
59
+    },
60
+
61
+    /**
62
+     * Background of the chat screen. Currently it's set to a transparent value
63
+     * as the idea is that the participant would still want to see at least a
64
+     * part of the video when he/she is in the chat window.
65
+     */
66
+    modalBackdrop: {
67
+        backgroundColor: 'rgba(127, 127, 127, 0.8)',
68
+        flex: 1
69
+    },
70
+
71
+    /**
72
+     * Style modifier for the {@code detailsWrapper} for own messages.
73
+     */
74
+    ownMessageDetailsWrapper: {
75
+        alignItems: 'flex-end'
76
+    },
77
+
78
+    /**
79
+     * Style modifier for the {@code textWrapper} for own messages.
80
+     */
81
+    ownTextWrapper: {
82
+        backgroundColor: 'rgb(210, 231, 249)',
83
+        borderTopLeftRadius: 8,
84
+        borderTopRightRadius: 0
85
+    },
86
+
87
+    solidBGTimeText: {
88
+        color: 'rgb(164, 184, 209)'
89
+    },
90
+
91
+    /**
92
+     * Style modifier for the chat window when we're in audio only mode.
93
+     */
94
+    solidModalBackdrop: {
95
+        backgroundColor: ColorPalette.white
96
+    },
97
+
98
+    /**
99
+     * Style modifier for system (error) messages.
100
+     */
101
+    systemTextWrapper: {
102
+        backgroundColor: 'rgb(247, 215, 215)'
103
+    },
104
+
105
+    /**
106
+     * Wrapper for the name and the message text.
107
+     */
108
+    textWrapper: {
109
+        alignItems: 'flex-start',
110
+        backgroundColor: 'rgb(240, 243, 247)',
111
+        borderRadius: 8,
112
+        borderTopLeftRadius: 0,
113
+        flexDirection: 'column',
114
+        padding: 9
115
+    },
116
+
117
+    /**
118
+     * Text node for the timestamp.
119
+     */
120
+    timeText: {
121
+        color: ColorPalette.white,
122
+        fontSize: 13
123
+    }
124
+});

react/features/chat/components/Chat.web.js → react/features/chat/components/web/Chat.js Целия файл

@@ -1,79 +1,25 @@
1 1
 // @flow
2 2
 
3
-import React, { Component } from 'react';
3
+import React from 'react';
4 4
 import { connect } from 'react-redux';
5 5
 import Transition from 'react-transition-group/Transition';
6 6
 
7
-import { translate } from '../../base/i18n';
8
-import { getLocalParticipant } from '../../base/participants';
9
-
10
-import { toggleChat } from '../actions';
7
+import { translate } from '../../../base/i18n';
11 8
 
9
+import AbstractChat, {
10
+    _mapDispatchToProps,
11
+    _mapStateToProps,
12
+    type Props
13
+} from '../AbstractChat';
12 14
 import ChatInput from './ChatInput';
13 15
 import ChatMessage from './ChatMessage';
14 16
 import DisplayNameForm from './DisplayNameForm';
15 17
 
16
-/**
17
- * The type of the React {@code Component} props of {@link Chat}.
18
- */
19
-type Props = {
20
-
21
-    /**
22
-     * The JitsiConference instance to send messages to.
23
-     */
24
-    _conference: Object,
25
-
26
-    /**
27
-     * Whether or not chat is displayed.
28
-     */
29
-    _isOpen: Boolean,
30
-
31
-    /**
32
-     * The local participant's ID.
33
-     */
34
-    _localUserId: String,
35
-
36
-    /**
37
-     * All the chat messages in the conference.
38
-     */
39
-    _messages: Array<Object>,
40
-
41
-    /**
42
-     * Whether or not to block chat access with a nickname input form.
43
-     */
44
-    _showNamePrompt: boolean,
45
-
46
-    /**
47
-     * Invoked to change the chat panel status.
48
-     */
49
-    dispatch: Dispatch<*>
50
-};
51
-
52
-/**
53
- * The type of the React {@code Component} state of {@Chat}.
54
- */
55
-type State = {
56
-
57
-    /**
58
-     * User provided nickname when the input text is provided in the view.
59
-     *
60
-     * @type {String}
61
-     */
62
-    message: string
63
-};
64
-
65 18
 /**
66 19
  * React Component for holding the chat feature in a side panel that slides in
67 20
  * and out of view.
68
- *
69
- * @extends Component
70 21
  */
71
-class Chat extends Component<Props, State> {
72
-
73
-    /**
74
-     * Reference to the HTML element used for typing in a chat message.
75
-     */
76
-    _chatInput: ?HTMLElement;
22
+class Chat extends AbstractChat<Props> {
77 23
 
78 24
     /**
79 25
      * Whether or not the {@code Chat} component is off-screen, having finished
@@ -96,15 +42,12 @@ class Chat extends Component<Props, State> {
96 42
     constructor(props: Props) {
97 43
         super(props);
98 44
 
99
-        this._chatInput = null;
100 45
         this._isExited = true;
101 46
         this._messagesListEnd = null;
102 47
 
103 48
         // Bind event handlers so they are only bound once for every instance.
104
-        this._onCloseClick = this._onCloseClick.bind(this);
105 49
         this._renderMessage = this._renderMessage.bind(this);
106 50
         this._renderPanelContent = this._renderPanelContent.bind(this);
107
-        this._setChatInputRef = this._setChatInputRef.bind(this);
108 51
         this._setMessageListEndRef = this._setMessageListEndRef.bind(this);
109 52
     }
110 53
 
@@ -145,17 +88,6 @@ class Chat extends Component<Props, State> {
145 88
         );
146 89
     }
147 90
 
148
-    _onCloseClick: () => void;
149
-
150
-    /**
151
-     * Callback invoked to hide {@code Chat}.
152
-     *
153
-     * @returns {void}
154
-     */
155
-    _onCloseClick() {
156
-        this.props.dispatch(toggleChat());
157
-    }
158
-
159 91
     /**
160 92
      * Returns a React Element for showing chat messages and a form to send new
161 93
      * chat messages.
@@ -177,7 +109,7 @@ class Chat extends Component<Props, State> {
177 109
                 <div id = 'chatconversation'>
178 110
                     { messages }
179 111
                 </div>
180
-                <ChatInput getChatInputRef = { this._setChatInputRef } />
112
+                <ChatInput />
181 113
             </div>
182 114
         );
183 115
     }
@@ -213,14 +145,14 @@ class Chat extends Component<Props, State> {
213 145
     _renderPanelContent(state) {
214 146
         this._isExited = state === 'exited';
215 147
 
216
-        const { _isOpen, _showNamePrompt } = this.props;
148
+        const { _isOpen, _onToggleChat, _showNamePrompt } = this.props;
217 149
         const ComponentToRender = !_isOpen && state === 'exited'
218 150
             ? null
219 151
             : (
220 152
                 <div>
221 153
                     <div
222 154
                         className = 'chat-close'
223
-                        onClick = { this._onCloseClick }>X</div>
155
+                        onClick = { _onToggleChat }>X</div>
224 156
                     { _showNamePrompt
225 157
                         ? <DisplayNameForm /> : this._renderChat() }
226 158
                 </div>
@@ -256,20 +188,6 @@ class Chat extends Component<Props, State> {
256 188
         }
257 189
     }
258 190
 
259
-    _setChatInputRef: (?HTMLElement) => void;
260
-
261
-    /**
262
-     * Sets a reference to the HTML text input element used for typing in chat
263
-     * messages.
264
-     *
265
-     * @param {Object} chatInput - The input for typing chat messages.
266
-     * @private
267
-     * @returns {void}
268
-     */
269
-    _setChatInputRef(chatInput: ?HTMLElement) {
270
-        this._chatInput = chatInput;
271
-    }
272
-
273 191
     _setMessageListEndRef: (?HTMLElement) => void;
274 192
 
275 193
     /**
@@ -284,29 +202,4 @@ class Chat extends Component<Props, State> {
284 202
     }
285 203
 }
286 204
 
287
-/**
288
- * Maps (parts of) the redux state to {@link Chat} React {@code Component}
289
- * props.
290
- *
291
- * @param {Object} state - The redux store/state.
292
- * @private
293
- * @returns {{
294
- *     _conference: Object,
295
- *     _isOpen: boolean,
296
- *     _messages: Array<Object>,
297
- *     _showNamePrompt: boolean
298
- * }}
299
- */
300
-function _mapStateToProps(state) {
301
-    const { isOpen, messages } = state['features/chat'];
302
-    const localParticipant = getLocalParticipant(state);
303
-
304
-    return {
305
-        _conference: state['features/base/conference'].conference,
306
-        _isOpen: isOpen,
307
-        _messages: messages,
308
-        _showNamePrompt: !localParticipant.name
309
-    };
310
-}
311
-
312
-export default translate(connect(_mapStateToProps)(Chat));
205
+export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));

react/features/chat/components/ChatCounter.web.js → react/features/chat/components/web/ChatCounter.js Целия файл

@@ -3,7 +3,7 @@
3 3
 import React, { Component } from 'react';
4 4
 import { connect } from 'react-redux';
5 5
 
6
-import { getUnreadCount } from '../functions';
6
+import { getUnreadCount } from '../../functions';
7 7
 
8 8
 /**
9 9
  * The type of the React {@code Component} props of {@link ChatCounter}.

react/features/chat/components/ChatInput.web.js → react/features/chat/components/web/ChatInput.js Целия файл

@@ -4,7 +4,7 @@ import React, { Component } from 'react';
4 4
 import { connect } from 'react-redux';
5 5
 import Emoji from 'react-emoji-render';
6 6
 
7
-import { sendMessage } from '../actions';
7
+import { sendMessage } from '../../actions';
8 8
 
9 9
 import SmileysPanel from './SmileysPanel';
10 10
 

react/features/chat/components/ChatMessage.web.js → react/features/chat/components/web/ChatMessage.js Целия файл

@@ -1,34 +1,20 @@
1 1
 // @flow
2 2
 
3
-import React, { PureComponent } from 'react';
3
+import React from 'react';
4 4
 import { toArray } from 'react-emoji-render';
5 5
 import Linkify from 'react-linkify';
6 6
 
7 7
 
8
-import { translate } from '../../base/i18n';
8
+import { translate } from '../../../base/i18n';
9 9
 
10
-/**
11
- * The type of the React {@code Component} props of {@link Chat}.
12
- */
13
-type Props = {
14
-
15
-    /**
16
-     * The redux representation of a chat message.
17
-     */
18
-    message: Object,
19
-
20
-    /**
21
-     * Invoked to receive translated strings.
22
-     */
23
-    t: Function
24
-};
10
+import AbstractChatMessage, {
11
+    type Props
12
+} from '../AbstractChatMessage';
25 13
 
26 14
 /**
27
- * Displays as passed in chat message.
28
- *
29
- * @extends Component
15
+ * Renders a single chat message.
30 16
  */
31
-class ChatMessage extends PureComponent<Props> {
17
+class ChatMessage extends AbstractChatMessage<Props> {
32 18
     /**
33 19
      * Implements React's {@link Component#render()}.
34 20
      *

react/features/chat/components/DisplayNameForm.web.js → react/features/chat/components/web/DisplayNameForm.js Целия файл

@@ -4,8 +4,8 @@ import { FieldTextStateless } from '@atlaskit/field-text';
4 4
 import React, { Component } from 'react';
5 5
 import { connect } from 'react-redux';
6 6
 
7
-import { translate } from '../../base/i18n';
8
-import { updateSettings } from '../../base/settings';
7
+import { translate } from '../../../base/i18n';
8
+import { updateSettings } from '../../../base/settings';
9 9
 
10 10
 /**
11 11
  * The type of the React {@code Component} props of {@DisplayNameForm}.

react/features/chat/components/SmileysPanel.web.js → react/features/chat/components/web/SmileysPanel.js Целия файл

@@ -2,7 +2,7 @@
2 2
 
3 3
 import React, { PureComponent } from 'react';
4 4
 import Emoji from 'react-emoji-render';
5
-import { smileys } from '../smileys';
5
+import { smileys } from '../../smileys';
6 6
 
7 7
 /**
8 8
  * The type of the React {@code Component} props of {@link SmileysPanel}.

+ 4
- 0
react/features/chat/components/web/index.js Целия файл

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

+ 61
- 38
react/features/chat/middleware.js Целия файл

@@ -3,15 +3,21 @@
3 3
 import UIUtil from '../../../modules/UI/util/UIUtil';
4 4
 
5 5
 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
6
-import { CONFERENCE_JOINED, CONFERENCE_WILL_LEAVE } from '../base/conference';
6
+import {
7
+    CONFERENCE_JOINED,
8
+    getCurrentConference
9
+} from '../base/conference';
7 10
 import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
8
-import { getParticipantById } from '../base/participants';
9
-import { MiddlewareRegistry } from '../base/redux';
11
+import {
12
+    getParticipantById,
13
+    getParticipantDisplayName
14
+} from '../base/participants';
15
+import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
10 16
 import { playSound, registerSound, unregisterSound } from '../base/sounds';
11 17
 import { isButtonEnabled, showToolbox } from '../toolbox';
12 18
 
13 19
 import { SEND_MESSAGE } from './actionTypes';
14
-import { addMessage, clearMessages } from './actions';
20
+import { addMessage, clearMessages, toggleChat } from './actions';
15 21
 import { INCOMING_MSG_SOUND_ID } from './constants';
16 22
 import { INCOMING_MSG_SOUND_FILE } from './sounds';
17 23
 
@@ -27,49 +33,60 @@ declare var interfaceConfig : Object;
27 33
 MiddlewareRegistry.register(store => next => action => {
28 34
     switch (action.type) {
29 35
     case APP_WILL_MOUNT:
30
-        // Register the chat message sound on Web only because there's no chat
31
-        // on mobile.
32
-        typeof APP === 'undefined'
33
-            || store.dispatch(
36
+        store.dispatch(
34 37
                 registerSound(INCOMING_MSG_SOUND_ID, INCOMING_MSG_SOUND_FILE));
35 38
         break;
36 39
 
37 40
     case APP_WILL_UNMOUNT:
38
-        // Unregister the chat message sound on Web because it's registered
39
-        // there only.
40
-        typeof APP === 'undefined'
41
-            || store.dispatch(unregisterSound(INCOMING_MSG_SOUND_ID));
41
+        store.dispatch(unregisterSound(INCOMING_MSG_SOUND_ID));
42 42
         break;
43 43
 
44 44
     case CONFERENCE_JOINED:
45
-        typeof APP === 'undefined'
46
-            || _addChatMsgListener(action.conference, store);
45
+        _addChatMsgListener(action.conference, store);
47 46
         break;
48 47
 
49
-    case CONFERENCE_WILL_LEAVE:
50
-        store.dispatch(clearMessages());
51
-        break;
52
-
53
-    case SEND_MESSAGE:
54
-        if (typeof APP !== 'undefined') {
55
-            const { conference } = store.getState()['features/base/conference'];
48
+    case SEND_MESSAGE: {
49
+        const { conference } = store.getState()['features/base/conference'];
56 50
 
57
-            if (conference) {
58
-                const escapedMessage = UIUtil.escapeHtml(action.message);
51
+        if (conference) {
52
+            const escapedMessage = UIUtil.escapeHtml(action.message);
59 53
 
54
+            if (typeof APP !== 'undefined') {
60 55
                 APP.API.notifySendingChatMessage(escapedMessage);
61
-                conference.sendTextMessage(escapedMessage);
62 56
             }
57
+            conference.sendTextMessage(escapedMessage);
63 58
         }
64 59
         break;
65 60
     }
61
+    }
66 62
 
67 63
     return next(action);
68 64
 });
69 65
 
70 66
 /**
71
- * Registers listener for {@link JitsiConferenceEvents.MESSAGE_RECEIVED} which
72
- * will play a sound on the event, given that the chat is not currently visible.
67
+ * Set up state change listener to perform maintenance tasks when the conference
68
+ * is left or failed, e.g. clear messages or close the chat modal if it's left
69
+ * open.
70
+ */
71
+StateListenerRegistry.register(
72
+    state => getCurrentConference(state),
73
+    (conference, { dispatch, getState }, previousConference) => {
74
+        if (conference !== previousConference) {
75
+            // conference changed, left or failed...
76
+
77
+            if (getState()['features/chat'].isOpen) {
78
+                // Closes the chat if it's left open.
79
+                dispatch(toggleChat());
80
+            }
81
+
82
+            // Clear chat messages.
83
+            dispatch(clearMessages());
84
+        }
85
+    });
86
+
87
+/**
88
+ * Registers listener for {@link JitsiConferenceEvents.MESSAGE_RECEIVED} that
89
+ * will perform various chat related activities.
73 90
  *
74 91
  * @param {JitsiConference} conference - The conference instance on which the
75 92
  * new event listener will be registered.
@@ -79,35 +96,28 @@ MiddlewareRegistry.register(store => next => action => {
79 96
  */
80 97
 function _addChatMsgListener(conference, { dispatch, getState }) {
81 98
     if ((typeof interfaceConfig === 'object' && interfaceConfig.filmStripOnly)
82
-        || !isButtonEnabled('chat')) {
99
+        || (typeof APP !== 'undefined' && !isButtonEnabled('chat'))) {
100
+        // We don't register anything on web if we're in filmStripOnly mode, or
101
+        // the chat button is not enabled in interfaceConfig.
83 102
         return;
84 103
     }
85 104
 
86 105
     conference.on(
87 106
         JitsiConferenceEvents.MESSAGE_RECEIVED,
88 107
         (id, message, timestamp) => {
108
+            // Logic for all platforms:
89 109
             const state = getState();
90 110
             const { isOpen: isChatOpen } = state['features/chat'];
91 111
 
92 112
             if (!isChatOpen) {
93 113
                 dispatch(playSound(INCOMING_MSG_SOUND_ID));
94
-                dispatch(showToolbox(4000));
95 114
             }
96 115
 
97 116
             // Provide a default for for the case when a message is being
98 117
             // backfilled for a participant that has left the conference.
99 118
             const participant = getParticipantById(state, id) || {};
100
-            const displayName = participant.name
101
-                || `${interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME} (${id})`;
119
+            const displayName = getParticipantDisplayName(getState, id);
102 120
             const hasRead = participant.local || isChatOpen;
103
-
104
-            APP.API.notifyReceivedChatMessage({
105
-                body: message,
106
-                id,
107
-                nick: displayName,
108
-                ts: timestamp
109
-            });
110
-
111 121
             const timestampToDate = timestamp
112 122
                 ? new Date(timestamp) : new Date();
113 123
             const millisecondsTimestamp = timestampToDate.getTime();
@@ -120,6 +130,19 @@ function _addChatMsgListener(conference, { dispatch, getState }) {
120 130
                 message,
121 131
                 timestamp: millisecondsTimestamp
122 132
             }));
133
+
134
+            if (typeof APP !== 'undefined') {
135
+                // Logic for web only:
136
+
137
+                APP.API.notifyReceivedChatMessage({
138
+                    body: message,
139
+                    id,
140
+                    nick: displayName,
141
+                    ts: timestamp
142
+                });
143
+
144
+                dispatch(showToolbox(4000));
145
+            }
123 146
         }
124 147
     );
125 148
 }

react/features/chat/sounds.web.js → react/features/chat/sounds.js Целия файл


+ 3
- 0
react/features/conference/components/Conference.native.js Целия файл

@@ -15,6 +15,7 @@ import {
15 15
 import { TestConnectionInfo } from '../../base/testing';
16 16
 import { createDesiredLocalTracks } from '../../base/tracks';
17 17
 import { ConferenceNotification } from '../../calendar-sync';
18
+import { Chat } from '../../chat';
18 19
 import {
19 20
     Filmstrip,
20 21
     isFilmstripVisible,
@@ -250,6 +251,8 @@ class Conference extends Component<Props> {
250 251
                     hidden = { true }
251 252
                     translucent = { true } />
252 253
 
254
+                <Chat />
255
+
253 256
                 {/*
254 257
                   * The LargeVideo is the lowermost stacking layer.
255 258
                   */

+ 30
- 0
react/features/display-name/components/DisplayNamePrompt.native.js Целия файл

@@ -0,0 +1,30 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { InputDialog } from '../../base/dialog';
7
+
8
+import AbstractDisplayNamePrompt from './AbstractDisplayNamePrompt';
9
+
10
+/**
11
+ * Implements a component to render a display name prompt.
12
+ */
13
+class DisplayNamePrompt extends AbstractDisplayNamePrompt<*> {
14
+    /**
15
+     * Implements React's {@link Component#render()}.
16
+     *
17
+     * @inheritdoc
18
+     */
19
+    render() {
20
+        return (
21
+            <InputDialog
22
+                contentKey = 'dialog.enterDisplayName'
23
+                onSubmit = { this._onSetDisplayName } />
24
+        );
25
+    }
26
+
27
+    _onSetDisplayName: string => boolean;
28
+}
29
+
30
+export default connect()(DisplayNamePrompt);

+ 2
- 0
react/features/toolbox/components/native/OverflowMenu.js Целия файл

@@ -9,6 +9,7 @@ import {
9 9
     bottomSheetItemStylesCombined,
10 10
     hideDialog
11 11
 } from '../../../base/dialog';
12
+import { InviteButton } from '../../../invite';
12 13
 import { AudioRouteButton } from '../../../mobile/audio-mode';
13 14
 import { PictureInPictureButton } from '../../../mobile/picture-in-picture';
14 15
 import { LiveStreamButton, RecordButton } from '../../../recording';
@@ -86,6 +87,7 @@ class OverflowMenu extends Component<Props> {
86 87
                 }
87 88
                 <LiveStreamButton { ...buttonProps } />
88 89
                 <TileViewButton { ...buttonProps } />
90
+                <InviteButton { ...buttonProps } />
89 91
                 <PictureInPictureButton { ...buttonProps } />
90 92
             </BottomSheet>
91 93
         );

+ 36
- 2
react/features/toolbox/components/native/Toolbox.js Целия файл

@@ -5,12 +5,13 @@ import { View } from 'react-native';
5 5
 import { connect } from 'react-redux';
6 6
 
7 7
 import { Container } from '../../../base/react';
8
-import { InviteButton } from '../../../invite';
8
+import { ChatButton } from '../../../chat';
9 9
 
10 10
 import AudioMuteButton from '../AudioMuteButton';
11 11
 import HangupButton from '../HangupButton';
12 12
 import OverflowMenuButton from './OverflowMenuButton';
13 13
 import styles, {
14
+    chatButtonOverride,
14 15
     hangupButtonStyles,
15 16
     toolbarButtonStyles,
16 17
     toolbarToggledButtonStyles
@@ -141,6 +142,35 @@ class Toolbox extends Component<Props, State> {
141 142
         return 2 * Math.round(buttonSize / 2);
142 143
     }
143 144
 
145
+    /**
146
+     * Constructs the toggled style of the chat button. This cannot be done by
147
+     * simple style inheritance due to the size calculation done in this
148
+     * component.
149
+     *
150
+     * @param {Object} baseStyle - The base style that was originally
151
+     * calculated.
152
+     * @returns {Object | Array}
153
+     */
154
+    _getChatButtonToggledStyle(baseStyle) {
155
+        if (Array.isArray(baseStyle.style)) {
156
+            return {
157
+                ...baseStyle,
158
+                style: [
159
+                    ...baseStyle.style,
160
+                    chatButtonOverride.toggled
161
+                ]
162
+            };
163
+        }
164
+
165
+        return {
166
+            ...baseStyle,
167
+            style: [
168
+                baseStyle.style,
169
+                chatButtonOverride.toggled
170
+            ]
171
+        };
172
+    }
173
+
144 174
     _onLayout: (Object) => void;
145 175
 
146 176
     /**
@@ -200,7 +230,11 @@ class Toolbox extends Component<Props, State> {
200 230
             <View
201 231
                 pointerEvents = 'box-none'
202 232
                 style = { styles.toolbar }>
203
-                <InviteButton styles = { buttonStyles } />
233
+                <ChatButton
234
+                    styles = { buttonStyles }
235
+                    toggledStyles = {
236
+                        this._getChatButtonToggledStyle(toggledButtonStyles)
237
+                    } />
204 238
                 <AudioMuteButton
205 239
                     styles = { buttonStyles }
206 240
                     toggledStyles = { toggledButtonStyles } />

+ 10
- 0
react/features/toolbox/components/native/styles.js Целия файл

@@ -130,3 +130,13 @@ export const toolbarToggledButtonStyles = {
130 130
     iconStyle: styles.whiteToolbarButtonIcon,
131 131
     style: styles.whiteToolbarButton
132 132
 };
133
+
134
+/**
135
+ * Overrides to the standard styles that we apply to the chat button, as that
136
+ * behaves slightly differently to other buttons.
137
+ */
138
+export const chatButtonOverride = createStyleSheet({
139
+    toggled: {
140
+        backgroundColor: ColorPalette.blue
141
+    }
142
+});

Loading…
Отказ
Запис