Parcourir la source

feat(gif, rn) Added GIPHY integration on native (#11236)

Update Android build to support gif
Use GIF format instead of animated webp
Show GIFs in chat messages
Display GIF over tile
Add Giphy button in reactions menu
Added Giphy dialog
Fix isGifMessage to also allow upper case
factor2
Robert Pintilii il y a 3 ans
Parent
révision
1355876f83
Aucun compte lié à l'adresse e-mail de l'auteur
32 fichiers modifiés avec 362 ajouts et 25 suppressions
  1. 3
    1
      android/sdk/build.gradle
  2. 1
    0
      android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java
  3. 2
    0
      android/settings.gradle
  4. 22
    0
      ios/Podfile.lock
  5. 6
    0
      ios/app/app.xcodeproj/project.pbxproj
  6. 1
    0
      lang/main.json
  7. 41
    0
      package-lock.json
  8. 1
    0
      package.json
  9. 0
    1
      react/features/app/middlewares.any.js
  10. 1
    0
      react/features/app/middlewares.native.js
  11. 1
    0
      react/features/app/middlewares.web.js
  12. 2
    1
      react/features/base/react/components/web/Message.js
  13. 11
    3
      react/features/chat/components/native/ChatMessage.js
  14. 27
    0
      react/features/chat/components/native/GifMessage.js
  15. 11
    0
      react/features/chat/components/native/styles.js
  16. 26
    10
      react/features/filmstrip/components/native/Thumbnail.js
  17. 5
    0
      react/features/filmstrip/components/native/styles.js
  18. 1
    0
      react/features/gifs/components/_.native.js
  19. 66
    0
      react/features/gifs/components/native/GifsMenu.js
  20. 3
    0
      react/features/gifs/components/native/index.js
  21. 40
    0
      react/features/gifs/components/native/styles.js
  22. 5
    4
      react/features/gifs/functions.js
  23. 0
    0
      react/features/gifs/middleware.any.js
  24. 3
    0
      react/features/gifs/middleware.native.js
  25. 2
    0
      react/features/gifs/middleware.web.js
  26. 20
    0
      react/features/gifs/subscriber.native.js
  27. 9
    0
      react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.js
  28. 1
    0
      react/features/mobile/navigation/routes.js
  29. 5
    0
      react/features/mobile/navigation/screenOptions.js
  30. 20
    3
      react/features/reactions/components/native/ReactionButton.js
  31. 20
    2
      react/features/reactions/components/native/ReactionMenu.js
  32. 6
    0
      react/features/toolbox/components/native/styles.js

+ 3
- 1
android/sdk/build.gradle Voir le fichier

50
     api 'com.facebook.react:react-native:+'
50
     api 'com.facebook.react:react-native:+'
51
     //noinspection GradleDynamicVersion
51
     //noinspection GradleDynamicVersion
52
     implementation 'org.webkit:android-jsc:+'
52
     implementation 'org.webkit:android-jsc:+'
53
-
53
+    
54
+    implementation 'com.facebook.fresco:animated-gif:2.5.0'
54
     implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
55
     implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
55
     implementation 'com.jakewharton.timber:timber:4.7.1'
56
     implementation 'com.jakewharton.timber:timber:4.7.1'
56
     implementation 'com.squareup.duktape:duktape-android:1.3.0'
57
     implementation 'com.squareup.duktape:duktape-android:1.3.0'
80
     implementation project(':react-native-default-preference')
81
     implementation project(':react-native-default-preference')
81
     implementation project(':react-native-gesture-handler')
82
     implementation project(':react-native-gesture-handler')
82
     implementation project(':react-native-get-random-values')
83
     implementation project(':react-native-get-random-values')
84
+    implementation project(':react-native-giphy')
83
     implementation project(':react-native-immersive')
85
     implementation project(':react-native-immersive')
84
     implementation project(':react-native-keep-awake')
86
     implementation project(':react-native-keep-awake')
85
     implementation project(':react-native-masked-view_masked-view')
87
     implementation project(':react-native-masked-view_masked-view')

+ 1
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java Voir le fichier

110
             new com.corbt.keepawake.KCKeepAwakePackage(),
110
             new com.corbt.keepawake.KCKeepAwakePackage(),
111
             new com.facebook.react.shell.MainReactPackage(),
111
             new com.facebook.react.shell.MainReactPackage(),
112
             new com.reactnativecommunity.clipboard.ClipboardPackage(),
112
             new com.reactnativecommunity.clipboard.ClipboardPackage(),
113
+            new com.giphyreactnativesdk.GiphyReactNativeSdkPackage(),
113
             new com.reactnativecommunity.netinfo.NetInfoPackage(),
114
             new com.reactnativecommunity.netinfo.NetInfoPackage(),
114
             new com.reactnativepagerview.PagerViewPackage(),
115
             new com.reactnativepagerview.PagerViewPackage(),
115
             new com.oblador.performance.PerformancePackage(),
116
             new com.oblador.performance.PerformancePackage(),

+ 2
- 0
android/settings.gradle Voir le fichier

21
 project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
21
 project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
22
 include ':react-native-get-random-values'
22
 include ':react-native-get-random-values'
23
 project(':react-native-get-random-values').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-get-random-values/android')
23
 project(':react-native-get-random-values').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-get-random-values/android')
24
+include ':react-native-giphy'
25
+project(':react-native-giphy').projectDir = new File(rootProject.projectDir, '../node_modules/@giphy/react-native-sdk/android')
24
 include ':react-native-google-signin'
26
 include ':react-native-google-signin'
25
 project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-google-signin/google-signin/android')
27
 project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-google-signin/google-signin/android')
26
 include ':react-native-immersive'
28
 include ':react-native-immersive'

+ 22
- 0
ios/Podfile.lock Voir le fichier

66
     - GoogleUtilities/UserDefaults (~> 6.7)
66
     - GoogleUtilities/UserDefaults (~> 6.7)
67
     - PromisesObjC (~> 1.2)
67
     - PromisesObjC (~> 1.2)
68
   - fmt (6.2.1)
68
   - fmt (6.2.1)
69
+  - Giphy (2.1.20):
70
+    - libwebp
71
+  - giphy-react-native-sdk (1.7.0):
72
+    - Giphy (= 2.1.20)
73
+    - React-Core
69
   - glog (0.3.5)
74
   - glog (0.3.5)
70
   - GoogleAppMeasurement (6.8.3):
75
   - GoogleAppMeasurement (6.8.3):
71
     - GoogleUtilities/AppDelegateSwizzler (~> 6.7)
76
     - GoogleUtilities/AppDelegateSwizzler (~> 6.7)
102
     - AppAuth/Core (~> 1.4)
107
     - AppAuth/Core (~> 1.4)
103
     - GTMSessionFetcher/Core (~> 1.5)
108
     - GTMSessionFetcher/Core (~> 1.5)
104
   - GTMSessionFetcher/Core (1.7.0)
109
   - GTMSessionFetcher/Core (1.7.0)
110
+  - libwebp (1.2.1):
111
+    - libwebp/demux (= 1.2.1)
112
+    - libwebp/mux (= 1.2.1)
113
+    - libwebp/webp (= 1.2.1)
114
+  - libwebp/demux (1.2.1):
115
+    - libwebp/webp
116
+  - libwebp/mux (1.2.1):
117
+    - libwebp/demux
118
+  - libwebp/webp (1.2.1)
105
   - nanopb (1.30906.0):
119
   - nanopb (1.30906.0):
106
     - nanopb/decode (= 1.30906.0)
120
     - nanopb/decode (= 1.30906.0)
107
     - nanopb/encode (= 1.30906.0)
121
     - nanopb/encode (= 1.30906.0)
442
   - Firebase/Analytics (~> 6.33.0)
456
   - Firebase/Analytics (~> 6.33.0)
443
   - Firebase/Crashlytics (~> 6.33.0)
457
   - Firebase/Crashlytics (~> 6.33.0)
444
   - Firebase/DynamicLinks (~> 6.33.0)
458
   - Firebase/DynamicLinks (~> 6.33.0)
459
+  - "giphy-react-native-sdk (from `../node_modules/@giphy/react-native-sdk`)"
445
   - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
460
   - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
446
   - ObjectiveDropboxOfficial (= 6.2.3)
461
   - ObjectiveDropboxOfficial (= 6.2.3)
447
   - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
462
   - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
510
     - FirebaseDynamicLinks
525
     - FirebaseDynamicLinks
511
     - FirebaseInstallations
526
     - FirebaseInstallations
512
     - fmt
527
     - fmt
528
+    - Giphy
513
     - GoogleAppMeasurement
529
     - GoogleAppMeasurement
514
     - GoogleDataTransport
530
     - GoogleDataTransport
515
     - GoogleSignIn
531
     - GoogleSignIn
516
     - GoogleUtilities
532
     - GoogleUtilities
517
     - GTMAppAuth
533
     - GTMAppAuth
518
     - GTMSessionFetcher
534
     - GTMSessionFetcher
535
+    - libwebp
519
     - nanopb
536
     - nanopb
520
     - ObjectiveDropboxOfficial
537
     - ObjectiveDropboxOfficial
521
     - PromisesObjC
538
     - PromisesObjC
531
     :path: "../node_modules/react-native/Libraries/FBLazyVector"
548
     :path: "../node_modules/react-native/Libraries/FBLazyVector"
532
   FBReactNativeSpec:
549
   FBReactNativeSpec:
533
     :path: "../node_modules/react-native/React/FBReactNativeSpec"
550
     :path: "../node_modules/react-native/React/FBReactNativeSpec"
551
+  giphy-react-native-sdk:
552
+    :path: "../node_modules/@giphy/react-native-sdk"
534
   glog:
553
   glog:
535
     :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
554
     :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
536
   RCT-Folly:
555
   RCT-Folly:
651
   FirebaseDynamicLinks: 6eac37d86910382eafb6315d952cc44c9e176094
670
   FirebaseDynamicLinks: 6eac37d86910382eafb6315d952cc44c9e176094
652
   FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
671
   FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
653
   fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
672
   fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
673
+  Giphy: b6d5087521d251bb8c99cdc0eb07bbdf86d142d5
674
+  giphy-react-native-sdk: 7abccf2b52123a0f30ce99da895ab6288023680c
654
   glog: 5337263514dd6f09803962437687240c5dc39aa4
675
   glog: 5337263514dd6f09803962437687240c5dc39aa4
655
   GoogleAppMeasurement: 966e88df9d19c15715137bb2ddaf52373f111436
676
   GoogleAppMeasurement: 966e88df9d19c15715137bb2ddaf52373f111436
656
   GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833
677
   GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833
658
   GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
679
   GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
659
   GTMAppAuth: ad5c2b70b9a8689e1a04033c9369c4915bfcbe89
680
   GTMAppAuth: ad5c2b70b9a8689e1a04033c9369c4915bfcbe89
660
   GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91
681
   GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91
682
+  libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
661
   nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
683
   nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
662
   ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
684
   ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
663
   PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
685
   PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97

+ 6
- 0
ios/app/app.xcodeproj/project.pbxproj Voir le fichier

38
 		DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
38
 		DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
39
 		E588011722789D43008B0561 /* JitsiMeetContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58801132278944E008B0561 /* JitsiMeetContext.swift */; };
39
 		E588011722789D43008B0561 /* JitsiMeetContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58801132278944E008B0561 /* JitsiMeetContext.swift */; };
40
 		E5C97B63227A1EB400199214 /* JitsiMeetCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */; };
40
 		E5C97B63227A1EB400199214 /* JitsiMeetCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */; };
41
+		FD572B9827EDF32300A800FB /* GiphyUISDK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */; };
42
+		FD572B9927EDF32300A800FB /* GiphyUISDK.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
41
 /* End PBXBuildFile section */
43
 /* End PBXBuildFile section */
42
 
44
 
43
 /* Begin PBXContainerItemProxy section */
45
 /* Begin PBXContainerItemProxy section */
72
 			dstSubfolderSpec = 10;
74
 			dstSubfolderSpec = 10;
73
 			files = (
75
 			files = (
74
 				DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */,
76
 				DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */,
77
+				FD572B9927EDF32300A800FB /* GiphyUISDK.xcframework in Embed Frameworks */,
75
 				DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */,
78
 				DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */,
76
 			);
79
 			);
77
 			name = "Embed Frameworks";
80
 			name = "Embed Frameworks";
158
 		DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.xcframework"; sourceTree = "<group>"; };
161
 		DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.xcframework"; sourceTree = "<group>"; };
159
 		E58801132278944E008B0561 /* JitsiMeetContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetContext.swift; sourceTree = "<group>"; };
162
 		E58801132278944E008B0561 /* JitsiMeetContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetContext.swift; sourceTree = "<group>"; };
160
 		E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetCommands.swift; sourceTree = "<group>"; };
163
 		E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetCommands.swift; sourceTree = "<group>"; };
164
+		FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = GiphyUISDK.xcframework; path = ../Pods/Giphy/GiphySDK/GiphyUISDK.xcframework; sourceTree = "<group>"; };
161
 /* End PBXFileReference section */
165
 /* End PBXFileReference section */
162
 
166
 
163
 /* Begin PBXFrameworksBuildPhase section */
167
 /* Begin PBXFrameworksBuildPhase section */
174
 			files = (
178
 			files = (
175
 				DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */,
179
 				DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */,
176
 				DEA9F289258A6EA800D4CD74 /* JitsiMeetSDK.framework in Frameworks */,
180
 				DEA9F289258A6EA800D4CD74 /* JitsiMeetSDK.framework in Frameworks */,
181
+				FD572B9827EDF32300A800FB /* GiphyUISDK.xcframework in Frameworks */,
177
 				2681BB562C7A0B42CFBA6719 /* libPods-JitsiMeet.a in Frameworks */,
182
 				2681BB562C7A0B42CFBA6719 /* libPods-JitsiMeet.a in Frameworks */,
178
 			);
183
 			);
179
 			runOnlyForDeploymentPostprocessing = 0;
184
 			runOnlyForDeploymentPostprocessing = 0;
199
 		0B26BE711EC5BC4D00EEFB41 /* Frameworks */ = {
204
 		0B26BE711EC5BC4D00EEFB41 /* Frameworks */ = {
200
 			isa = PBXGroup;
205
 			isa = PBXGroup;
201
 			children = (
206
 			children = (
207
+				FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */,
202
 				DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */,
208
 				DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */,
203
 				DE050388256E904600DEE3A5 /* WebRTC.xcframework */,
209
 				DE050388256E904600DEE3A5 /* WebRTC.xcframework */,
204
 				0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */,
210
 				0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */,

+ 1
- 0
lang/main.json Voir le fichier

629
         "displayNotifications": "Display notifications for",
629
         "displayNotifications": "Display notifications for",
630
         "focus": "Conference focus",
630
         "focus": "Conference focus",
631
         "focusFail": "{{component}} not available - retry in {{ms}} sec",
631
         "focusFail": "{{component}} not available - retry in {{ms}} sec",
632
+        "gifsMenu": "GIPHY",
632
         "groupTitle": "Notifications",
633
         "groupTitle": "Notifications",
633
         "hostAskedUnmute": "The moderator would like you to speak",
634
         "hostAskedUnmute": "The moderator would like you to speak",
634
         "invitedOneMember": "{{name}} has been invited",
635
         "invitedOneMember": "{{name}} has been invited",

+ 41
- 0
package-lock.json Voir le fichier

30
         "@atlaskit/tooltip": "17.1.2",
30
         "@atlaskit/tooltip": "17.1.2",
31
         "@giphy/js-fetch-api": "4.1.2",
31
         "@giphy/js-fetch-api": "4.1.2",
32
         "@giphy/react-components": "5.6.0",
32
         "@giphy/react-components": "5.6.0",
33
+        "@giphy/react-native-sdk": "1.7.0",
33
         "@hapi/bourne": "2.0.0",
34
         "@hapi/bourne": "2.0.0",
34
         "@jitsi/js-utils": "2.0.0",
35
         "@jitsi/js-utils": "2.0.0",
35
         "@jitsi/logger": "2.0.0",
36
         "@jitsi/logger": "2.0.0",
3378
         "react": ">=16.3.0"
3379
         "react": ">=16.3.0"
3379
       }
3380
       }
3380
     },
3381
     },
3382
+    "node_modules/@giphy/react-native-sdk": {
3383
+      "version": "1.7.0",
3384
+      "resolved": "https://registry.npmjs.org/@giphy/react-native-sdk/-/react-native-sdk-1.7.0.tgz",
3385
+      "integrity": "sha512-mCIqtPkDAstL+BDTbC1EQ4SiRkND3zd9uLKUeR4RkK2AhjRTUIheGzfxOZrdR014LVwcwKw5s9qpogoXr66mgw==",
3386
+      "dependencies": {
3387
+        "@giphy/js-types": "^4.0.3",
3388
+        "type-fest": "^2.10.0"
3389
+      },
3390
+      "peerDependencies": {
3391
+        "react": "*",
3392
+        "react-native": "*"
3393
+      }
3394
+    },
3395
+    "node_modules/@giphy/react-native-sdk/node_modules/type-fest": {
3396
+      "version": "2.12.1",
3397
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.12.1.tgz",
3398
+      "integrity": "sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ==",
3399
+      "engines": {
3400
+        "node": ">=12.20"
3401
+      },
3402
+      "funding": {
3403
+        "url": "https://github.com/sponsors/sindresorhus"
3404
+      }
3405
+    },
3381
     "node_modules/@hapi/bourne": {
3406
     "node_modules/@hapi/bourne": {
3382
       "version": "2.0.0",
3407
       "version": "2.0.0",
3383
       "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
3408
       "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
22246
         }
22271
         }
22247
       }
22272
       }
22248
     },
22273
     },
22274
+    "@giphy/react-native-sdk": {
22275
+      "version": "1.7.0",
22276
+      "resolved": "https://registry.npmjs.org/@giphy/react-native-sdk/-/react-native-sdk-1.7.0.tgz",
22277
+      "integrity": "sha512-mCIqtPkDAstL+BDTbC1EQ4SiRkND3zd9uLKUeR4RkK2AhjRTUIheGzfxOZrdR014LVwcwKw5s9qpogoXr66mgw==",
22278
+      "requires": {
22279
+        "@giphy/js-types": "^4.0.3",
22280
+        "type-fest": "^2.10.0"
22281
+      },
22282
+      "dependencies": {
22283
+        "type-fest": {
22284
+          "version": "2.12.1",
22285
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.12.1.tgz",
22286
+          "integrity": "sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ=="
22287
+        }
22288
+      }
22289
+    },
22249
     "@hapi/bourne": {
22290
     "@hapi/bourne": {
22250
       "version": "2.0.0",
22291
       "version": "2.0.0",
22251
       "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
22292
       "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",

+ 1
- 0
package.json Voir le fichier

35
     "@atlaskit/tooltip": "17.1.2",
35
     "@atlaskit/tooltip": "17.1.2",
36
     "@giphy/js-fetch-api": "4.1.2",
36
     "@giphy/js-fetch-api": "4.1.2",
37
     "@giphy/react-components": "5.6.0",
37
     "@giphy/react-components": "5.6.0",
38
+    "@giphy/react-native-sdk": "1.7.0",
38
     "@hapi/bourne": "2.0.0",
39
     "@hapi/bourne": "2.0.0",
39
     "@jitsi/js-utils": "2.0.0",
40
     "@jitsi/js-utils": "2.0.0",
40
     "@jitsi/logger": "2.0.0",
41
     "@jitsi/logger": "2.0.0",

+ 0
- 1
react/features/app/middlewares.any.js Voir le fichier

30
 import '../etherpad/middleware';
30
 import '../etherpad/middleware';
31
 import '../filmstrip/middleware';
31
 import '../filmstrip/middleware';
32
 import '../follow-me/middleware';
32
 import '../follow-me/middleware';
33
-import '../gifs/middleware';
34
 import '../invite/middleware';
33
 import '../invite/middleware';
35
 import '../jaas/middleware';
34
 import '../jaas/middleware';
36
 import '../large-video/middleware';
35
 import '../large-video/middleware';

+ 1
- 0
react/features/app/middlewares.native.js Voir le fichier

1
 // @flow
1
 // @flow
2
 
2
 
3
 import '../authentication/middleware';
3
 import '../authentication/middleware';
4
+import '../gifs/middleware';
4
 import '../mobile/audio-mode/middleware';
5
 import '../mobile/audio-mode/middleware';
5
 import '../mobile/background/middleware';
6
 import '../mobile/background/middleware';
6
 import '../mobile/call-integration/middleware';
7
 import '../mobile/call-integration/middleware';

+ 1
- 0
react/features/app/middlewares.web.js Voir le fichier

22
 import '../virtual-background/middleware';
22
 import '../virtual-background/middleware';
23
 import '../face-centering/middleware';
23
 import '../face-centering/middleware';
24
 import '../facial-recognition/middleware';
24
 import '../facial-recognition/middleware';
25
+import '../gifs/middleware';
25
 
26
 
26
 import './middlewares.any';
27
 import './middlewares.any';

+ 2
- 1
react/features/base/react/components/web/Message.js Voir le fichier

4
 import { toArray } from 'react-emoji-render';
4
 import { toArray } from 'react-emoji-render';
5
 
5
 
6
 import GifMessage from '../../../../chat/components/web/GifMessage';
6
 import GifMessage from '../../../../chat/components/web/GifMessage';
7
+import { GIF_PREFIX } from '../../../../gifs/constants';
7
 import { isGifMessage } from '../../../../gifs/functions';
8
 import { isGifMessage } from '../../../../gifs/functions';
8
 
9
 
9
 import Linkify from './Linkify';
10
 import Linkify from './Linkify';
49
 
50
 
50
         // check if the message is a GIF
51
         // check if the message is a GIF
51
         if (isGifMessage(text)) {
52
         if (isGifMessage(text)) {
52
-            const url = text.substring(4, text.length - 1);
53
+            const url = text.substring(GIF_PREFIX.length, text.length - 1);
53
 
54
 
54
             content.push(<GifMessage
55
             content.push(<GifMessage
55
                 key = { url }
56
                 key = { url }

+ 11
- 3
react/features/chat/components/native/ChatMessage.js Voir le fichier

9
 import { Linkify } from '../../../base/react';
9
 import { Linkify } from '../../../base/react';
10
 import { connect } from '../../../base/redux';
10
 import { connect } from '../../../base/redux';
11
 import { type StyleType } from '../../../base/styles';
11
 import { type StyleType } from '../../../base/styles';
12
+import { isGifMessage } from '../../../gifs/functions';
12
 import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
13
 import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
13
 import { replaceNonUnicodeEmojis } from '../../functions';
14
 import { replaceNonUnicodeEmojis } from '../../functions';
14
 import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage';
15
 import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage';
15
 
16
 
17
+import GifMessage from './GifMessage';
16
 import PrivateMessageButton from './PrivateMessageButton';
18
 import PrivateMessageButton from './PrivateMessageButton';
17
 import styles from './styles';
19
 import styles from './styles';
18
 
20
 
75
             messageBubbleStyle.push(_styles.lobbyMessageBubble);
77
             messageBubbleStyle.push(_styles.lobbyMessageBubble);
76
         }
78
         }
77
 
79
 
80
+        const messageText = replaceNonUnicodeEmojis(this._getMessageText());
81
+
78
         return (
82
         return (
79
             <View style = { styles.messageWrapper } >
83
             <View style = { styles.messageWrapper } >
80
                 { this._renderAvatar() }
84
                 { this._renderAvatar() }
82
                     <View style = { messageBubbleStyle }>
86
                     <View style = { messageBubbleStyle }>
83
                         <View style = { styles.textWrapper } >
87
                         <View style = { styles.textWrapper } >
84
                             { this._renderDisplayName() }
88
                             { this._renderDisplayName() }
85
-                            <Linkify linkStyle = { styles.chatLink }>
86
-                                { replaceNonUnicodeEmojis(this._getMessageText()) }
87
-                            </Linkify>
89
+                            {isGifMessage(messageText)
90
+                                ? <GifMessage message = { messageText } />
91
+                                : (
92
+                                    <Linkify linkStyle = { styles.chatLink }>
93
+                                        {messageText}
94
+                                    </Linkify>
95
+                                )}
88
                             { this._renderPrivateNotice() }
96
                             { this._renderPrivateNotice() }
89
                         </View>
97
                         </View>
90
                         { this._renderPrivateReplyButton() }
98
                         { this._renderPrivateReplyButton() }

+ 27
- 0
react/features/chat/components/native/GifMessage.js Voir le fichier

1
+import React from 'react';
2
+import { Image, View } from 'react-native';
3
+
4
+import { GIF_PREFIX } from '../../../gifs/constants';
5
+
6
+import styles from './styles';
7
+
8
+type Props = {
9
+
10
+    /**
11
+     * The formatted gif message.
12
+     */
13
+    message: string
14
+}
15
+
16
+const GifMessage = ({ message }: Props) => {
17
+    const url = message.substring(GIF_PREFIX.length, message.length - 1);
18
+
19
+    return (<View
20
+        style = { styles.gifContainer }>
21
+        <Image
22
+            source = {{ uri: url }}
23
+            style = { styles.gifImage } />
24
+    </View>);
25
+};
26
+
27
+export default GifMessage;

+ 11
- 0
react/features/chat/components/native/styles.js Voir le fichier

145
         borderTopLeftRadius: 0,
145
         borderTopLeftRadius: 0,
146
         borderTopRightRadius: 0,
146
         borderTopRightRadius: 0,
147
         borderBottomRightRadius: 0
147
         borderBottomRightRadius: 0
148
+    },
149
+
150
+    gifContainer: {
151
+        maxHeight: 150
152
+    },
153
+
154
+    gifImage: {
155
+        resizeMode: 'contain',
156
+        width: 250,
157
+        height: undefined,
158
+        flexGrow: 1
148
     }
159
     }
149
 };
160
 };
150
 
161
 

+ 26
- 10
react/features/filmstrip/components/native/Thumbnail.js Voir le fichier

1
 // @flow
1
 // @flow
2
 
2
 
3
 import React, { PureComponent } from 'react';
3
 import React, { PureComponent } from 'react';
4
-import { View } from 'react-native';
4
+import { Image, View } from 'react-native';
5
 import type { Dispatch } from 'redux';
5
 import type { Dispatch } from 'redux';
6
 
6
 
7
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
22
 import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
22
 import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
23
 import { ConnectionIndicator } from '../../../connection-indicator';
23
 import { ConnectionIndicator } from '../../../connection-indicator';
24
 import { DisplayNameLabel } from '../../../display-name';
24
 import { DisplayNameLabel } from '../../../display-name';
25
+import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
25
 import {
26
 import {
26
     showContextMenuDetails,
27
     showContextMenuDetails,
27
     showSharedVideoMenu
28
     showSharedVideoMenu
45
      */
46
      */
46
     _audioMuted: boolean,
47
     _audioMuted: boolean,
47
 
48
 
49
+    /**
50
+     * URL of GIF sent by this participant, null if there's none.
51
+     */
52
+    _gifSrc: ?string,
53
+
48
     /**
54
     /**
49
      * Indicates whether the participant is fake.
55
      * Indicates whether the participant is fake.
50
      */
56
      */
247
      */
253
      */
248
     render() {
254
     render() {
249
         const {
255
         const {
256
+            _gifSrc,
250
             _isScreenShare: isScreenShare,
257
             _isScreenShare: isScreenShare,
251
             _isFakeParticipant,
258
             _isFakeParticipant,
252
             _participantId: participantId,
259
             _participantId: participantId,
280
                     _renderDominantSpeakerIndicator ? styles.thumbnailDominantSpeaker : null
287
                     _renderDominantSpeakerIndicator ? styles.thumbnailDominantSpeaker : null
281
                 ] }
288
                 ] }
282
                 touchFeedback = { false }>
289
                 touchFeedback = { false }>
283
-                <ParticipantView
284
-                    avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
285
-                    disableVideo = { isScreenShare || _isFakeParticipant }
286
-                    participantId = { participantId }
287
-                    tintEnabled = { participantInLargeVideo && !disableTint }
288
-                    tintStyle = { _styles.activeThumbnailTint }
289
-                    zOrder = { 1 } />
290
-                {
291
-                    this._renderIndicators()
290
+                {_gifSrc ? <Image
291
+                    source = {{ uri: _gifSrc }}
292
+                    style = { styles.thumbnailGif } />
293
+                    : <>
294
+                        <ParticipantView
295
+                            avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
296
+                            disableVideo = { isScreenShare || _isFakeParticipant }
297
+                            participantId = { participantId }
298
+                            tintEnabled = { participantInLargeVideo && !disableTint }
299
+                            tintStyle = { _styles.activeThumbnailTint }
300
+                            zOrder = { 1 } />
301
+                        {
302
+                            this._renderIndicators()
303
+                        }
304
+                    </>
292
                 }
305
                 }
293
             </Container>
306
             </Container>
294
         );
307
         );
324
     const renderModeratorIndicator = !_isEveryoneModerator
337
     const renderModeratorIndicator = !_isEveryoneModerator
325
         && participant?.role === PARTICIPANT_ROLE.MODERATOR;
338
         && participant?.role === PARTICIPANT_ROLE.MODERATOR;
326
     const participantInLargeVideo = id === largeVideo.participantId;
339
     const participantInLargeVideo = id === largeVideo.participantId;
340
+    const { gifUrl: gifSrc } = getGifForParticipant(state, id);
341
+    const mode = getGifDisplayMode(state);
327
 
342
 
328
     return {
343
     return {
329
         _audioMuted: audioTrack?.muted ?? true,
344
         _audioMuted: audioTrack?.muted ?? true,
345
+        _gifSrc: mode === 'chat' ? null : gifSrc,
330
         _isFakeParticipant: participant?.isFakeParticipant,
346
         _isFakeParticipant: participant?.isFakeParticipant,
331
         _isScreenShare: isScreenShare,
347
         _isScreenShare: isScreenShare,
332
         _local: participant?.local,
348
         _local: participant?.local,

+ 5
- 0
react/features/filmstrip/components/native/styles.js Voir le fichier

171
     thumbnailDominantSpeaker: {
171
     thumbnailDominantSpeaker: {
172
         borderWidth: 4,
172
         borderWidth: 4,
173
         borderColor: BaseTheme.palette.action01Hover
173
         borderColor: BaseTheme.palette.action01Hover
174
+    },
175
+
176
+    thumbnailGif: {
177
+        flexGrow: 1,
178
+        resizeMode: 'contain'
174
     }
179
     }
175
 };
180
 };
176
 
181
 

+ 1
- 0
react/features/gifs/components/_.native.js Voir le fichier

1
+export * from './native';

+ 66
- 0
react/features/gifs/components/native/GifsMenu.js Voir le fichier

1
+import { GiphyContent, GiphyGridView, GiphyMediaType } from '@giphy/react-native-sdk';
2
+import React, { useCallback, useState } from 'react';
3
+import { Image, Keyboard, Text, View } from 'react-native';
4
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
5
+import { useDispatch } from 'react-redux';
6
+
7
+import { createGifSentEvent, sendAnalytics } from '../../../analytics';
8
+import JitsiScreen from '../../../base/modal/components/JitsiScreen';
9
+import { sendMessage } from '../../../chat/actions.any';
10
+import { goBack } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
11
+import ClearableInput from '../../../participants-pane/components/native/ClearableInput';
12
+import { formatGifUrlMessage, getGifUrl } from '../../functions';
13
+
14
+import styles from './styles';
15
+
16
+const GifsMenu = () => {
17
+    const [ searchQuery, setSearchQuery ] = useState('');
18
+    const dispatch = useDispatch();
19
+    const insets = useSafeAreaInsets();
20
+
21
+    const content = searchQuery === ''
22
+        ? GiphyContent.trending({ mediaType: GiphyMediaType.Gif })
23
+        : GiphyContent.search({
24
+            searchQuery,
25
+            mediaType: GiphyMediaType.Gif
26
+        });
27
+
28
+    const sendGif = useCallback(e => {
29
+        const url = getGifUrl(e.nativeEvent.media);
30
+
31
+        sendAnalytics(createGifSentEvent());
32
+
33
+        dispatch(sendMessage(formatGifUrlMessage(url), true));
34
+        goBack();
35
+    }, []);
36
+
37
+    const onScroll = useCallback(Keyboard.dismiss, []);
38
+
39
+    return (<JitsiScreen
40
+        style = { styles.container }>
41
+        <ClearableInput
42
+            autoFocus = { true }
43
+            customStyles = { styles.clearableInput }
44
+            onChange = { setSearchQuery }
45
+            placeholder = 'Search GIPHY'
46
+            value = { searchQuery } />
47
+        <GiphyGridView
48
+            cellPadding = { 5 }
49
+            content = { content }
50
+            onMediaSelect = { sendGif }
51
+            onScroll = { onScroll }
52
+            style = { styles.grid } />
53
+        <View
54
+            style = { [ styles.credit, {
55
+                bottom: insets.bottom,
56
+                left: insets.left,
57
+                right: insets.right
58
+            } ] }>
59
+            <Text
60
+                style = { styles.creditText }>Powered by</Text>
61
+            <Image source = { require('../../../../../images/GIPHY_logo.png') } />
62
+        </View>
63
+    </JitsiScreen>);
64
+};
65
+
66
+export default GifsMenu;

+ 3
- 0
react/features/gifs/components/native/index.js Voir le fichier

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

+ 40
- 0
react/features/gifs/components/native/styles.js Voir le fichier

1
+import BaseTheme from '../../../base/ui/components/BaseTheme.native';
2
+
3
+export default {
4
+    container: {
5
+        backgroundColor: BaseTheme.palette.ui01,
6
+        flex: 1
7
+    },
8
+
9
+    clearableInput: {
10
+        wrapper: {
11
+            marginBottom: BaseTheme.spacing[3],
12
+            marginTop: BaseTheme.spacing[3]
13
+        },
14
+
15
+        input: { textAlign: 'left' }
16
+    },
17
+
18
+    grid: {
19
+        flex: 1,
20
+        marginLeft: BaseTheme.spacing[3],
21
+        marginRight: BaseTheme.spacing[3]
22
+    },
23
+
24
+    credit: {
25
+        backgroundColor: BaseTheme.palette.ui01,
26
+        width: '100%',
27
+        height: 40,
28
+        position: 'absolute',
29
+        marginBottom: 0,
30
+        display: 'flex',
31
+        flexDirection: 'row',
32
+        alignItems: 'center',
33
+        justifyContent: 'center'
34
+    },
35
+
36
+    creditText: {
37
+        color: 'white',
38
+        fontWeight: 'bold'
39
+    }
40
+};

+ 5
- 4
react/features/gifs/functions.js Voir le fichier

10
  * @returns {Object}
10
  * @returns {Object}
11
  */
11
  */
12
 export function getGifForParticipant(state, participantId) {
12
 export function getGifForParticipant(state, participantId) {
13
-    return state['features/gifs'].gifList.get(participantId) || {};
13
+    return isGifEnabled(state) ? state['features/gifs'].gifList.get(participantId) || {} : {};
14
 }
14
 }
15
 
15
 
16
 /**
16
 /**
20
  * @returns {boolean}
20
  * @returns {boolean}
21
  */
21
  */
22
 export function isGifMessage(message) {
22
 export function isGifMessage(message) {
23
-    return message.trim().startsWith(GIF_PREFIX);
23
+    return message.trim().toLowerCase()
24
+        .startsWith(GIF_PREFIX);
24
 }
25
 }
25
 
26
 
26
 /**
27
 /**
43
  * @returns {boolean}
44
  * @returns {boolean}
44
  */
45
  */
45
 export function getGifUrl(gif) {
46
 export function getGifUrl(gif) {
46
-    const embedUrl = gif?.embed_url || '';
47
+    const embedUrl = gif?.embed_url || gif?.data?.embed_url || '';
47
     const idx = embedUrl.lastIndexOf('/');
48
     const idx = embedUrl.lastIndexOf('/');
48
     const id = embedUrl.substr(idx + 1);
49
     const id = embedUrl.substr(idx + 1);
49
 
50
 
50
-    return `https://i.giphy.com/media/${id}/giphy.webp`;
51
+    return `https://i.giphy.com/media/${id}/giphy.gif`;
51
 }
52
 }
52
 
53
 
53
 /**
54
 /**

react/features/gifs/middleware.js → react/features/gifs/middleware.any.js Voir le fichier


+ 3
- 0
react/features/gifs/middleware.native.js Voir le fichier

1
+
2
+import './middleware.any';
3
+import './subscriber.native';

+ 2
- 0
react/features/gifs/middleware.web.js Voir le fichier

1
+
2
+import './middleware.any';

+ 20
- 0
react/features/gifs/subscriber.native.js Voir le fichier

1
+import { GiphySDK } from '@giphy/react-native-sdk';
2
+
3
+import { StateListenerRegistry } from '../base/redux';
4
+
5
+import { isGifEnabled } from './functions';
6
+
7
+/**
8
+ * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
9
+ */
10
+StateListenerRegistry.register(
11
+    /* selector */ state => state['features/base/config']?.giphy,
12
+    /* listener */ (_, store) => {
13
+        const state = store.getState();
14
+
15
+        if (isGifEnabled(state)) {
16
+            GiphySDK.configure({ apiKey: state['features/base/config'].giphy?.sdkKey });
17
+        }
18
+    }, {
19
+        deepEquals: true
20
+    });

+ 9
- 0
react/features/mobile/navigation/components/conference/components/ConferenceNavigationContainer.js Voir le fichier

10
 import Conference from '../../../../../conference/components/native/Conference';
10
 import Conference from '../../../../../conference/components/native/Conference';
11
 import { getDisablePolls } from '../../../../../conference/functions';
11
 import { getDisablePolls } from '../../../../../conference/functions';
12
 import { SharedDocument } from '../../../../../etherpad';
12
 import { SharedDocument } from '../../../../../etherpad';
13
+import { GifsMenu } from '../../../../../gifs/components';
13
 import AddPeopleDialog
14
 import AddPeopleDialog
14
     from '../../../../../invite/components/add-people-dialog/native/AddPeopleDialog';
15
     from '../../../../../invite/components/add-people-dialog/native/AddPeopleDialog';
15
 import LobbyScreen from '../../../../../lobby/components/native/LobbyScreen';
16
 import LobbyScreen from '../../../../../lobby/components/native/LobbyScreen';
27
 import {
28
 import {
28
     chatScreenOptions,
29
     chatScreenOptions,
29
     conferenceScreenOptions,
30
     conferenceScreenOptions,
31
+    gifsMenuOptions,
30
     inviteScreenOptions,
32
     inviteScreenOptions,
31
     liveStreamScreenOptions,
33
     liveStreamScreenOptions,
32
     lobbyScreenOptions,
34
     lobbyScreenOptions,
124
                         ...salesforceScreenOptions,
126
                         ...salesforceScreenOptions,
125
                         title: t('notify.linkToSalesforce')
127
                         title: t('notify.linkToSalesforce')
126
                     }} />
128
                     }} />
129
+                <ConferenceStack.Screen
130
+                    component = { GifsMenu }
131
+                    name = { screen.conference.gifsMenu }
132
+                    options = {{
133
+                        ...gifsMenuOptions,
134
+                        title: t('notify.gifsMenu')
135
+                    }} />
127
                 <ConferenceStack.Screen
136
                 <ConferenceStack.Screen
128
                     component = { LobbyScreen }
137
                     component = { LobbyScreen }
129
                     name = { screen.lobby }
138
                     name = { screen.lobby }

+ 1
- 0
react/features/mobile/navigation/routes.js Voir le fichier

30
         speakerStats: 'Speaker Stats',
30
         speakerStats: 'Speaker Stats',
31
         salesforce: 'Link to Salesforce',
31
         salesforce: 'Link to Salesforce',
32
         participants: 'Participants',
32
         participants: 'Participants',
33
+        gifsMenu: 'GIPHY',
33
         invite: 'Invite',
34
         invite: 'Invite',
34
         sharedDocument: 'Shared document'
35
         sharedDocument: 'Shared document'
35
     },
36
     },

+ 5
- 0
react/features/mobile/navigation/screenOptions.js Voir le fichier

278
  */
278
  */
279
 export const salesforceScreenOptions = presentationScreenOptions;
279
 export const salesforceScreenOptions = presentationScreenOptions;
280
 
280
 
281
+/**
282
+ * Screen options for GIPHY integration modal.
283
+ */
284
+export const gifsMenuOptions = presentationScreenOptions;
285
+
281
 /**
286
 /**
282
  * Screen options for shared document.
287
  * Screen options for shared document.
283
  */
288
  */

+ 20
- 3
react/features/reactions/components/native/ReactionButton.js Voir le fichier

13
 
13
 
14
 export type ReactionStyles = {
14
 export type ReactionStyles = {
15
 
15
 
16
+    /**
17
+     * Style for the gif button.
18
+     */
19
+    gifButton: StyleType,
20
+
16
     /**
21
     /**
17
      * Style for the button.
22
      * Style for the button.
18
      */
23
      */
45
  */
50
  */
46
 type Props = {
51
 type Props = {
47
 
52
 
53
+    /**
54
+     * Component children.
55
+     */
56
+    children?: ReactNode,
57
+
58
+    /**
59
+     * External click handler.
60
+     */
61
+    onClick?: Function,
62
+
48
     /**
63
     /**
49
      * Collection of styles for the button.
64
      * Collection of styles for the button.
50
      */
65
      */
67
  * @returns {ReactElement}
82
  * @returns {ReactElement}
68
  */
83
  */
69
 function ReactionButton({
84
 function ReactionButton({
85
+    children,
86
+    onClick,
70
     styles,
87
     styles,
71
     reaction,
88
     reaction,
72
     t
89
     t
81
         <TouchableHighlight
98
         <TouchableHighlight
82
             accessibilityLabel = { t(`toolbar.accessibilityLabel.${reaction}`) }
99
             accessibilityLabel = { t(`toolbar.accessibilityLabel.${reaction}`) }
83
             accessibilityRole = 'button'
100
             accessibilityRole = 'button'
84
-            onPress = { _onClick }
85
-            style = { styles.style }
101
+            onPress = { onClick || _onClick }
102
+            style = { [ styles.style, children && styles?.gifButton ] }
86
             underlayColor = { styles.underlayColor }>
103
             underlayColor = { styles.underlayColor }>
87
-            <Text style = { styles.emoji }>{REACTIONS[reaction].emoji}</Text>
104
+            {children ?? <Text style = { styles.emoji }>{REACTIONS[reaction].emoji}</Text>}
88
         </TouchableHighlight>
105
         </TouchableHighlight>
89
     );
106
     );
90
 }
107
 }

+ 20
- 2
react/features/reactions/components/native/ReactionMenu.js Voir le fichier

1
 // @flow
1
 // @flow
2
 
2
 
3
-import React from 'react';
4
-import { View } from 'react-native';
3
+import React, { useCallback } from 'react';
4
+import { Image, View } from 'react-native';
5
 import { useSelector } from 'react-redux';
5
 import { useSelector } from 'react-redux';
6
 
6
 
7
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
7
 import { ColorSchemeRegistry } from '../../../base/color-scheme';
8
+import { isGifEnabled } from '../../../gifs/functions';
9
+import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
10
+import { screen } from '../../../mobile/navigation/routes';
8
 import { REACTIONS } from '../../constants';
11
 import { REACTIONS } from '../../constants';
9
 
12
 
10
 import RaiseHandButton from './RaiseHandButton';
13
 import RaiseHandButton from './RaiseHandButton';
36
     overflowMenu
39
     overflowMenu
37
 }: Props) {
40
 }: Props) {
38
     const _styles = useSelector(state => ColorSchemeRegistry.get(state, 'Toolbox'));
41
     const _styles = useSelector(state => ColorSchemeRegistry.get(state, 'Toolbox'));
42
+    const gifEnabled = useSelector(isGifEnabled);
43
+
44
+    const openGifMenu = useCallback(() => {
45
+        navigate(screen.conference.gifsMenu);
46
+        onCancel();
47
+    }, []);
39
 
48
 
40
     return (
49
     return (
41
         <View style = { overflowMenu ? _styles.overflowReactionMenu : _styles.reactionMenu }>
50
         <View style = { overflowMenu ? _styles.overflowReactionMenu : _styles.reactionMenu }>
46
                         reaction = { key }
55
                         reaction = { key }
47
                         styles = { _styles.reactionButton } />
56
                         styles = { _styles.reactionButton } />
48
                 ))}
57
                 ))}
58
+                {gifEnabled && (
59
+                    <ReactionButton
60
+                        onClick = { openGifMenu }
61
+                        styles = { _styles.reactionButton }>
62
+                        <Image
63
+                            height = { 22 }
64
+                            source = { require('../../../../../images/GIPHY_icon.png') } />
65
+                    </ReactionButton>
66
+                )}
49
             </View>
67
             </View>
50
             <RaiseHandButton onCancel = { onCancel } />
68
             <RaiseHandButton onCancel = { onCancel } />
51
         </View>
69
         </View>

+ 6
- 0
react/features/toolbox/components/native/styles.js Voir le fichier

51
     marginHorizontal: 0
51
     marginHorizontal: 0
52
 };
52
 };
53
 
53
 
54
+const gifButton = {
55
+    ...reactionButton,
56
+    backgroundColor: '#000'
57
+};
58
+
54
 /**
59
 /**
55
  * The style of the emoji on the reaction buttons.
60
  * The style of the emoji on the reaction buttons.
56
  */
61
  */
161
     },
166
     },
162
 
167
 
163
     reactionButton: {
168
     reactionButton: {
169
+        gifButton,
164
         style: reactionButton,
170
         style: reactionButton,
165
         underlayColor: BaseTheme.palette.ui13,
171
         underlayColor: BaseTheme.palette.ui13,
166
         emoji: reactionEmoji
172
         emoji: reactionEmoji

Chargement…
Annuler
Enregistrer