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
This commit is contained in:
parent
1a3432d580
commit
1355876f83
|
@ -50,7 +50,8 @@ dependencies {
|
|||
api 'com.facebook.react:react-native:+'
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation 'org.webkit:android-jsc:+'
|
||||
|
||||
|
||||
implementation 'com.facebook.fresco:animated-gif:2.5.0'
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
|
@ -80,6 +81,7 @@ dependencies {
|
|||
implementation project(':react-native-default-preference')
|
||||
implementation project(':react-native-gesture-handler')
|
||||
implementation project(':react-native-get-random-values')
|
||||
implementation project(':react-native-giphy')
|
||||
implementation project(':react-native-immersive')
|
||||
implementation project(':react-native-keep-awake')
|
||||
implementation project(':react-native-masked-view_masked-view')
|
||||
|
|
|
@ -110,6 +110,7 @@ class ReactInstanceManagerHolder {
|
|||
new com.corbt.keepawake.KCKeepAwakePackage(),
|
||||
new com.facebook.react.shell.MainReactPackage(),
|
||||
new com.reactnativecommunity.clipboard.ClipboardPackage(),
|
||||
new com.giphyreactnativesdk.GiphyReactNativeSdkPackage(),
|
||||
new com.reactnativecommunity.netinfo.NetInfoPackage(),
|
||||
new com.reactnativepagerview.PagerViewPackage(),
|
||||
new com.oblador.performance.PerformancePackage(),
|
||||
|
|
|
@ -21,6 +21,8 @@ include ':react-native-gesture-handler'
|
|||
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
|
||||
include ':react-native-get-random-values'
|
||||
project(':react-native-get-random-values').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-get-random-values/android')
|
||||
include ':react-native-giphy'
|
||||
project(':react-native-giphy').projectDir = new File(rootProject.projectDir, '../node_modules/@giphy/react-native-sdk/android')
|
||||
include ':react-native-google-signin'
|
||||
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-google-signin/google-signin/android')
|
||||
include ':react-native-immersive'
|
||||
|
|
|
@ -66,6 +66,11 @@ PODS:
|
|||
- GoogleUtilities/UserDefaults (~> 6.7)
|
||||
- PromisesObjC (~> 1.2)
|
||||
- fmt (6.2.1)
|
||||
- Giphy (2.1.20):
|
||||
- libwebp
|
||||
- giphy-react-native-sdk (1.7.0):
|
||||
- Giphy (= 2.1.20)
|
||||
- React-Core
|
||||
- glog (0.3.5)
|
||||
- GoogleAppMeasurement (6.8.3):
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 6.7)
|
||||
|
@ -102,6 +107,15 @@ PODS:
|
|||
- AppAuth/Core (~> 1.4)
|
||||
- GTMSessionFetcher/Core (~> 1.5)
|
||||
- GTMSessionFetcher/Core (1.7.0)
|
||||
- libwebp (1.2.1):
|
||||
- libwebp/demux (= 1.2.1)
|
||||
- libwebp/mux (= 1.2.1)
|
||||
- libwebp/webp (= 1.2.1)
|
||||
- libwebp/demux (1.2.1):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.2.1):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.2.1)
|
||||
- nanopb (1.30906.0):
|
||||
- nanopb/decode (= 1.30906.0)
|
||||
- nanopb/encode (= 1.30906.0)
|
||||
|
@ -442,6 +456,7 @@ DEPENDENCIES:
|
|||
- Firebase/Analytics (~> 6.33.0)
|
||||
- Firebase/Crashlytics (~> 6.33.0)
|
||||
- Firebase/DynamicLinks (~> 6.33.0)
|
||||
- "giphy-react-native-sdk (from `../node_modules/@giphy/react-native-sdk`)"
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- ObjectiveDropboxOfficial (= 6.2.3)
|
||||
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||
|
@ -510,12 +525,14 @@ SPEC REPOS:
|
|||
- FirebaseDynamicLinks
|
||||
- FirebaseInstallations
|
||||
- fmt
|
||||
- Giphy
|
||||
- GoogleAppMeasurement
|
||||
- GoogleDataTransport
|
||||
- GoogleSignIn
|
||||
- GoogleUtilities
|
||||
- GTMAppAuth
|
||||
- GTMSessionFetcher
|
||||
- libwebp
|
||||
- nanopb
|
||||
- ObjectiveDropboxOfficial
|
||||
- PromisesObjC
|
||||
|
@ -531,6 +548,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native/Libraries/FBLazyVector"
|
||||
FBReactNativeSpec:
|
||||
:path: "../node_modules/react-native/React/FBReactNativeSpec"
|
||||
giphy-react-native-sdk:
|
||||
:path: "../node_modules/@giphy/react-native-sdk"
|
||||
glog:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
|
||||
RCT-Folly:
|
||||
|
@ -651,6 +670,8 @@ SPEC CHECKSUMS:
|
|||
FirebaseDynamicLinks: 6eac37d86910382eafb6315d952cc44c9e176094
|
||||
FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
Giphy: b6d5087521d251bb8c99cdc0eb07bbdf86d142d5
|
||||
giphy-react-native-sdk: 7abccf2b52123a0f30ce99da895ab6288023680c
|
||||
glog: 5337263514dd6f09803962437687240c5dc39aa4
|
||||
GoogleAppMeasurement: 966e88df9d19c15715137bb2ddaf52373f111436
|
||||
GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833
|
||||
|
@ -658,6 +679,7 @@ SPEC CHECKSUMS:
|
|||
GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
|
||||
GTMAppAuth: ad5c2b70b9a8689e1a04033c9369c4915bfcbe89
|
||||
GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91
|
||||
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
|
||||
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
|
||||
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
|
||||
PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
E588011722789D43008B0561 /* JitsiMeetContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58801132278944E008B0561 /* JitsiMeetContext.swift */; };
|
||||
E5C97B63227A1EB400199214 /* JitsiMeetCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */; };
|
||||
FD572B9827EDF32300A800FB /* GiphyUISDK.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */; };
|
||||
FD572B9927EDF32300A800FB /* GiphyUISDK.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -72,6 +74,7 @@
|
|||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
DEA9F28A258A6EA800D4CD74 /* JitsiMeetSDK.framework in Embed Frameworks */,
|
||||
FD572B9927EDF32300A800FB /* GiphyUISDK.xcframework in Embed Frameworks */,
|
||||
DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
|
@ -158,6 +161,7 @@
|
|||
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WebRTC.xcframework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.xcframework"; sourceTree = "<group>"; };
|
||||
E58801132278944E008B0561 /* JitsiMeetContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetContext.swift; sourceTree = "<group>"; };
|
||||
E5C97B62227A1EB400199214 /* JitsiMeetCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JitsiMeetCommands.swift; sourceTree = "<group>"; };
|
||||
FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = GiphyUISDK.xcframework; path = ../Pods/Giphy/GiphySDK/GiphyUISDK.xcframework; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -174,6 +178,7 @@
|
|||
files = (
|
||||
DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */,
|
||||
DEA9F289258A6EA800D4CD74 /* JitsiMeetSDK.framework in Frameworks */,
|
||||
FD572B9827EDF32300A800FB /* GiphyUISDK.xcframework in Frameworks */,
|
||||
2681BB562C7A0B42CFBA6719 /* libPods-JitsiMeet.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -199,6 +204,7 @@
|
|||
0B26BE711EC5BC4D00EEFB41 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD572B9727EDF32300A800FB /* GiphyUISDK.xcframework */,
|
||||
DEA9F288258A6EA800D4CD74 /* JitsiMeetSDK.framework */,
|
||||
DE050388256E904600DEE3A5 /* WebRTC.xcframework */,
|
||||
0B26BE6D1EC5BC3C00EEFB41 /* JitsiMeet.framework */,
|
||||
|
|
|
@ -629,6 +629,7 @@
|
|||
"displayNotifications": "Display notifications for",
|
||||
"focus": "Conference focus",
|
||||
"focusFail": "{{component}} not available - retry in {{ms}} sec",
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Notifications",
|
||||
"hostAskedUnmute": "The moderator would like you to speak",
|
||||
"invitedOneMember": "{{name}} has been invited",
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"@atlaskit/tooltip": "17.1.2",
|
||||
"@giphy/js-fetch-api": "4.1.2",
|
||||
"@giphy/react-components": "5.6.0",
|
||||
"@giphy/react-native-sdk": "1.7.0",
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
|
@ -3378,6 +3379,30 @@
|
|||
"react": ">=16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@giphy/react-native-sdk": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@giphy/react-native-sdk/-/react-native-sdk-1.7.0.tgz",
|
||||
"integrity": "sha512-mCIqtPkDAstL+BDTbC1EQ4SiRkND3zd9uLKUeR4RkK2AhjRTUIheGzfxOZrdR014LVwcwKw5s9qpogoXr66mgw==",
|
||||
"dependencies": {
|
||||
"@giphy/js-types": "^4.0.3",
|
||||
"type-fest": "^2.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@giphy/react-native-sdk/node_modules/type-fest": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.12.1.tgz",
|
||||
"integrity": "sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ==",
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/bourne": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
|
||||
|
@ -22246,6 +22271,22 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@giphy/react-native-sdk": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@giphy/react-native-sdk/-/react-native-sdk-1.7.0.tgz",
|
||||
"integrity": "sha512-mCIqtPkDAstL+BDTbC1EQ4SiRkND3zd9uLKUeR4RkK2AhjRTUIheGzfxOZrdR014LVwcwKw5s9qpogoXr66mgw==",
|
||||
"requires": {
|
||||
"@giphy/js-types": "^4.0.3",
|
||||
"type-fest": "^2.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"type-fest": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.12.1.tgz",
|
||||
"integrity": "sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@hapi/bourne": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"@atlaskit/tooltip": "17.1.2",
|
||||
"@giphy/js-fetch-api": "4.1.2",
|
||||
"@giphy/react-components": "5.6.0",
|
||||
"@giphy/react-native-sdk": "1.7.0",
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
|
|
|
@ -30,7 +30,6 @@ import '../display-name/middleware';
|
|||
import '../etherpad/middleware';
|
||||
import '../filmstrip/middleware';
|
||||
import '../follow-me/middleware';
|
||||
import '../gifs/middleware';
|
||||
import '../invite/middleware';
|
||||
import '../jaas/middleware';
|
||||
import '../large-video/middleware';
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import '../authentication/middleware';
|
||||
import '../gifs/middleware';
|
||||
import '../mobile/audio-mode/middleware';
|
||||
import '../mobile/background/middleware';
|
||||
import '../mobile/call-integration/middleware';
|
||||
|
|
|
@ -22,5 +22,6 @@ import '../talk-while-muted/middleware';
|
|||
import '../virtual-background/middleware';
|
||||
import '../face-centering/middleware';
|
||||
import '../facial-recognition/middleware';
|
||||
import '../gifs/middleware';
|
||||
|
||||
import './middlewares.any';
|
||||
|
|
|
@ -4,6 +4,7 @@ import React, { Component } from 'react';
|
|||
import { toArray } from 'react-emoji-render';
|
||||
|
||||
import GifMessage from '../../../../chat/components/web/GifMessage';
|
||||
import { GIF_PREFIX } from '../../../../gifs/constants';
|
||||
import { isGifMessage } from '../../../../gifs/functions';
|
||||
|
||||
import Linkify from './Linkify';
|
||||
|
@ -49,7 +50,7 @@ class Message extends Component<Props> {
|
|||
|
||||
// check if the message is a GIF
|
||||
if (isGifMessage(text)) {
|
||||
const url = text.substring(4, text.length - 1);
|
||||
const url = text.substring(GIF_PREFIX.length, text.length - 1);
|
||||
|
||||
content.push(<GifMessage
|
||||
key = { url }
|
||||
|
|
|
@ -9,10 +9,12 @@ import { translate } from '../../../base/i18n';
|
|||
import { Linkify } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { type StyleType } from '../../../base/styles';
|
||||
import { isGifMessage } from '../../../gifs/functions';
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import { replaceNonUnicodeEmojis } from '../../functions';
|
||||
import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage';
|
||||
|
||||
import GifMessage from './GifMessage';
|
||||
import PrivateMessageButton from './PrivateMessageButton';
|
||||
import styles from './styles';
|
||||
|
||||
|
@ -75,6 +77,8 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
|||
messageBubbleStyle.push(_styles.lobbyMessageBubble);
|
||||
}
|
||||
|
||||
const messageText = replaceNonUnicodeEmojis(this._getMessageText());
|
||||
|
||||
return (
|
||||
<View style = { styles.messageWrapper } >
|
||||
{ this._renderAvatar() }
|
||||
|
@ -82,9 +86,13 @@ class ChatMessage extends AbstractChatMessage<Props> {
|
|||
<View style = { messageBubbleStyle }>
|
||||
<View style = { styles.textWrapper } >
|
||||
{ this._renderDisplayName() }
|
||||
<Linkify linkStyle = { styles.chatLink }>
|
||||
{ replaceNonUnicodeEmojis(this._getMessageText()) }
|
||||
</Linkify>
|
||||
{isGifMessage(messageText)
|
||||
? <GifMessage message = { messageText } />
|
||||
: (
|
||||
<Linkify linkStyle = { styles.chatLink }>
|
||||
{messageText}
|
||||
</Linkify>
|
||||
)}
|
||||
{ this._renderPrivateNotice() }
|
||||
</View>
|
||||
{ this._renderPrivateReplyButton() }
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import { Image, View } from 'react-native';
|
||||
|
||||
import { GIF_PREFIX } from '../../../gifs/constants';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The formatted gif message.
|
||||
*/
|
||||
message: string
|
||||
}
|
||||
|
||||
const GifMessage = ({ message }: Props) => {
|
||||
const url = message.substring(GIF_PREFIX.length, message.length - 1);
|
||||
|
||||
return (<View
|
||||
style = { styles.gifContainer }>
|
||||
<Image
|
||||
source = {{ uri: url }}
|
||||
style = { styles.gifImage } />
|
||||
</View>);
|
||||
};
|
||||
|
||||
export default GifMessage;
|
|
@ -145,6 +145,17 @@ export default {
|
|||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0
|
||||
},
|
||||
|
||||
gifContainer: {
|
||||
maxHeight: 150
|
||||
},
|
||||
|
||||
gifImage: {
|
||||
resizeMode: 'contain',
|
||||
width: 250,
|
||||
height: undefined,
|
||||
flexGrow: 1
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { Image, View } from 'react-native';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
|
@ -22,6 +22,7 @@ import { StyleType } from '../../../base/styles';
|
|||
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
||||
import { ConnectionIndicator } from '../../../connection-indicator';
|
||||
import { DisplayNameLabel } from '../../../display-name';
|
||||
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
|
||||
import {
|
||||
showContextMenuDetails,
|
||||
showSharedVideoMenu
|
||||
|
@ -45,6 +46,11 @@ type Props = {
|
|||
*/
|
||||
_audioMuted: boolean,
|
||||
|
||||
/**
|
||||
* URL of GIF sent by this participant, null if there's none.
|
||||
*/
|
||||
_gifSrc: ?string,
|
||||
|
||||
/**
|
||||
* Indicates whether the participant is fake.
|
||||
*/
|
||||
|
@ -247,6 +253,7 @@ class Thumbnail extends PureComponent<Props> {
|
|||
*/
|
||||
render() {
|
||||
const {
|
||||
_gifSrc,
|
||||
_isScreenShare: isScreenShare,
|
||||
_isFakeParticipant,
|
||||
_participantId: participantId,
|
||||
|
@ -280,15 +287,21 @@ class Thumbnail extends PureComponent<Props> {
|
|||
_renderDominantSpeakerIndicator ? styles.thumbnailDominantSpeaker : null
|
||||
] }
|
||||
touchFeedback = { false }>
|
||||
<ParticipantView
|
||||
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
|
||||
disableVideo = { isScreenShare || _isFakeParticipant }
|
||||
participantId = { participantId }
|
||||
tintEnabled = { participantInLargeVideo && !disableTint }
|
||||
tintStyle = { _styles.activeThumbnailTint }
|
||||
zOrder = { 1 } />
|
||||
{
|
||||
this._renderIndicators()
|
||||
{_gifSrc ? <Image
|
||||
source = {{ uri: _gifSrc }}
|
||||
style = { styles.thumbnailGif } />
|
||||
: <>
|
||||
<ParticipantView
|
||||
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
|
||||
disableVideo = { isScreenShare || _isFakeParticipant }
|
||||
participantId = { participantId }
|
||||
tintEnabled = { participantInLargeVideo && !disableTint }
|
||||
tintStyle = { _styles.activeThumbnailTint }
|
||||
zOrder = { 1 } />
|
||||
{
|
||||
this._renderIndicators()
|
||||
}
|
||||
</>
|
||||
}
|
||||
</Container>
|
||||
);
|
||||
|
@ -324,9 +337,12 @@ function _mapStateToProps(state, ownProps) {
|
|||
const renderModeratorIndicator = !_isEveryoneModerator
|
||||
&& participant?.role === PARTICIPANT_ROLE.MODERATOR;
|
||||
const participantInLargeVideo = id === largeVideo.participantId;
|
||||
const { gifUrl: gifSrc } = getGifForParticipant(state, id);
|
||||
const mode = getGifDisplayMode(state);
|
||||
|
||||
return {
|
||||
_audioMuted: audioTrack?.muted ?? true,
|
||||
_gifSrc: mode === 'chat' ? null : gifSrc,
|
||||
_isFakeParticipant: participant?.isFakeParticipant,
|
||||
_isScreenShare: isScreenShare,
|
||||
_local: participant?.local,
|
||||
|
|
|
@ -171,6 +171,11 @@ export default {
|
|||
thumbnailDominantSpeaker: {
|
||||
borderWidth: 4,
|
||||
borderColor: BaseTheme.palette.action01Hover
|
||||
},
|
||||
|
||||
thumbnailGif: {
|
||||
flexGrow: 1,
|
||||
resizeMode: 'contain'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './native';
|
|
@ -0,0 +1,66 @@
|
|||
import { GiphyContent, GiphyGridView, GiphyMediaType } from '@giphy/react-native-sdk';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Image, Keyboard, Text, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { createGifSentEvent, sendAnalytics } from '../../../analytics';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { sendMessage } from '../../../chat/actions.any';
|
||||
import { goBack } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import ClearableInput from '../../../participants-pane/components/native/ClearableInput';
|
||||
import { formatGifUrlMessage, getGifUrl } from '../../functions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
const GifsMenu = () => {
|
||||
const [ searchQuery, setSearchQuery ] = useState('');
|
||||
const dispatch = useDispatch();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const content = searchQuery === ''
|
||||
? GiphyContent.trending({ mediaType: GiphyMediaType.Gif })
|
||||
: GiphyContent.search({
|
||||
searchQuery,
|
||||
mediaType: GiphyMediaType.Gif
|
||||
});
|
||||
|
||||
const sendGif = useCallback(e => {
|
||||
const url = getGifUrl(e.nativeEvent.media);
|
||||
|
||||
sendAnalytics(createGifSentEvent());
|
||||
|
||||
dispatch(sendMessage(formatGifUrlMessage(url), true));
|
||||
goBack();
|
||||
}, []);
|
||||
|
||||
const onScroll = useCallback(Keyboard.dismiss, []);
|
||||
|
||||
return (<JitsiScreen
|
||||
style = { styles.container }>
|
||||
<ClearableInput
|
||||
autoFocus = { true }
|
||||
customStyles = { styles.clearableInput }
|
||||
onChange = { setSearchQuery }
|
||||
placeholder = 'Search GIPHY'
|
||||
value = { searchQuery } />
|
||||
<GiphyGridView
|
||||
cellPadding = { 5 }
|
||||
content = { content }
|
||||
onMediaSelect = { sendGif }
|
||||
onScroll = { onScroll }
|
||||
style = { styles.grid } />
|
||||
<View
|
||||
style = { [ styles.credit, {
|
||||
bottom: insets.bottom,
|
||||
left: insets.left,
|
||||
right: insets.right
|
||||
} ] }>
|
||||
<Text
|
||||
style = { styles.creditText }>Powered by</Text>
|
||||
<Image source = { require('../../../../../images/GIPHY_logo.png') } />
|
||||
</View>
|
||||
</JitsiScreen>);
|
||||
};
|
||||
|
||||
export default GifsMenu;
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
|
||||
export { default as GifsMenu } from './GifsMenu';
|
|
@ -0,0 +1,40 @@
|
|||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
export default {
|
||||
container: {
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
clearableInput: {
|
||||
wrapper: {
|
||||
marginBottom: BaseTheme.spacing[3],
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
input: { textAlign: 'left' }
|
||||
},
|
||||
|
||||
grid: {
|
||||
flex: 1,
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
marginRight: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
credit: {
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
width: '100%',
|
||||
height: 40,
|
||||
position: 'absolute',
|
||||
marginBottom: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
creditText: {
|
||||
color: 'white',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
};
|
|
@ -10,7 +10,7 @@ import { GIF_PREFIX } from './constants';
|
|||
* @returns {Object}
|
||||
*/
|
||||
export function getGifForParticipant(state, participantId) {
|
||||
return state['features/gifs'].gifList.get(participantId) || {};
|
||||
return isGifEnabled(state) ? state['features/gifs'].gifList.get(participantId) || {} : {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,8 @@ export function getGifForParticipant(state, participantId) {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
export function isGifMessage(message) {
|
||||
return message.trim().startsWith(GIF_PREFIX);
|
||||
return message.trim().toLowerCase()
|
||||
.startsWith(GIF_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,11 +44,11 @@ export function isGifsMenuOpen(state) {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
export function getGifUrl(gif) {
|
||||
const embedUrl = gif?.embed_url || '';
|
||||
const embedUrl = gif?.embed_url || gif?.data?.embed_url || '';
|
||||
const idx = embedUrl.lastIndexOf('/');
|
||||
const id = embedUrl.substr(idx + 1);
|
||||
|
||||
return `https://i.giphy.com/media/${id}/giphy.webp`;
|
||||
return `https://i.giphy.com/media/${id}/giphy.gif`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
import './middleware.any';
|
||||
import './subscriber.native';
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
import './middleware.any';
|
|
@ -0,0 +1,20 @@
|
|||
import { GiphySDK } from '@giphy/react-native-sdk';
|
||||
|
||||
import { StateListenerRegistry } from '../base/redux';
|
||||
|
||||
import { isGifEnabled } from './functions';
|
||||
|
||||
/**
|
||||
* Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/config']?.giphy,
|
||||
/* listener */ (_, store) => {
|
||||
const state = store.getState();
|
||||
|
||||
if (isGifEnabled(state)) {
|
||||
GiphySDK.configure({ apiKey: state['features/base/config'].giphy?.sdkKey });
|
||||
}
|
||||
}, {
|
||||
deepEquals: true
|
||||
});
|
|
@ -10,6 +10,7 @@ import { Chat } from '../../../../../chat';
|
|||
import Conference from '../../../../../conference/components/native/Conference';
|
||||
import { getDisablePolls } from '../../../../../conference/functions';
|
||||
import { SharedDocument } from '../../../../../etherpad';
|
||||
import { GifsMenu } from '../../../../../gifs/components';
|
||||
import AddPeopleDialog
|
||||
from '../../../../../invite/components/add-people-dialog/native/AddPeopleDialog';
|
||||
import LobbyScreen from '../../../../../lobby/components/native/LobbyScreen';
|
||||
|
@ -27,6 +28,7 @@ import { screen } from '../../../routes';
|
|||
import {
|
||||
chatScreenOptions,
|
||||
conferenceScreenOptions,
|
||||
gifsMenuOptions,
|
||||
inviteScreenOptions,
|
||||
liveStreamScreenOptions,
|
||||
lobbyScreenOptions,
|
||||
|
@ -124,6 +126,13 @@ const ConferenceNavigationContainer = () => {
|
|||
...salesforceScreenOptions,
|
||||
title: t('notify.linkToSalesforce')
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { GifsMenu }
|
||||
name = { screen.conference.gifsMenu }
|
||||
options = {{
|
||||
...gifsMenuOptions,
|
||||
title: t('notify.gifsMenu')
|
||||
}} />
|
||||
<ConferenceStack.Screen
|
||||
component = { LobbyScreen }
|
||||
name = { screen.lobby }
|
||||
|
|
|
@ -30,6 +30,7 @@ export const screen = {
|
|||
speakerStats: 'Speaker Stats',
|
||||
salesforce: 'Link to Salesforce',
|
||||
participants: 'Participants',
|
||||
gifsMenu: 'GIPHY',
|
||||
invite: 'Invite',
|
||||
sharedDocument: 'Shared document'
|
||||
},
|
||||
|
|
|
@ -278,6 +278,11 @@ export const liveStreamScreenOptions = {
|
|||
*/
|
||||
export const salesforceScreenOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for GIPHY integration modal.
|
||||
*/
|
||||
export const gifsMenuOptions = presentationScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for shared document.
|
||||
*/
|
||||
|
|
|
@ -13,6 +13,11 @@ import { REACTIONS } from '../../constants';
|
|||
|
||||
export type ReactionStyles = {
|
||||
|
||||
/**
|
||||
* Style for the gif button.
|
||||
*/
|
||||
gifButton: StyleType,
|
||||
|
||||
/**
|
||||
* Style for the button.
|
||||
*/
|
||||
|
@ -45,6 +50,16 @@ export type ReactionStyles = {
|
|||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Component children.
|
||||
*/
|
||||
children?: ReactNode,
|
||||
|
||||
/**
|
||||
* External click handler.
|
||||
*/
|
||||
onClick?: Function,
|
||||
|
||||
/**
|
||||
* Collection of styles for the button.
|
||||
*/
|
||||
|
@ -67,6 +82,8 @@ type Props = {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
function ReactionButton({
|
||||
children,
|
||||
onClick,
|
||||
styles,
|
||||
reaction,
|
||||
t
|
||||
|
@ -81,10 +98,10 @@ function ReactionButton({
|
|||
<TouchableHighlight
|
||||
accessibilityLabel = { t(`toolbar.accessibilityLabel.${reaction}`) }
|
||||
accessibilityRole = 'button'
|
||||
onPress = { _onClick }
|
||||
style = { styles.style }
|
||||
onPress = { onClick || _onClick }
|
||||
style = { [ styles.style, children && styles?.gifButton ] }
|
||||
underlayColor = { styles.underlayColor }>
|
||||
<Text style = { styles.emoji }>{REACTIONS[reaction].emoji}</Text>
|
||||
{children ?? <Text style = { styles.emoji }>{REACTIONS[reaction].emoji}</Text>}
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Image, View } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { isGifEnabled } from '../../../gifs/functions';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { REACTIONS } from '../../constants';
|
||||
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
|
@ -36,6 +39,12 @@ function ReactionMenu({
|
|||
overflowMenu
|
||||
}: Props) {
|
||||
const _styles = useSelector(state => ColorSchemeRegistry.get(state, 'Toolbox'));
|
||||
const gifEnabled = useSelector(isGifEnabled);
|
||||
|
||||
const openGifMenu = useCallback(() => {
|
||||
navigate(screen.conference.gifsMenu);
|
||||
onCancel();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style = { overflowMenu ? _styles.overflowReactionMenu : _styles.reactionMenu }>
|
||||
|
@ -46,6 +55,15 @@ function ReactionMenu({
|
|||
reaction = { key }
|
||||
styles = { _styles.reactionButton } />
|
||||
))}
|
||||
{gifEnabled && (
|
||||
<ReactionButton
|
||||
onClick = { openGifMenu }
|
||||
styles = { _styles.reactionButton }>
|
||||
<Image
|
||||
height = { 22 }
|
||||
source = { require('../../../../../images/GIPHY_icon.png') } />
|
||||
</ReactionButton>
|
||||
)}
|
||||
</View>
|
||||
<RaiseHandButton onCancel = { onCancel } />
|
||||
</View>
|
||||
|
|
|
@ -51,6 +51,11 @@ const reactionButton = {
|
|||
marginHorizontal: 0
|
||||
};
|
||||
|
||||
const gifButton = {
|
||||
...reactionButton,
|
||||
backgroundColor: '#000'
|
||||
};
|
||||
|
||||
/**
|
||||
* The style of the emoji on the reaction buttons.
|
||||
*/
|
||||
|
@ -161,6 +166,7 @@ ColorSchemeRegistry.register('Toolbox', {
|
|||
},
|
||||
|
||||
reactionButton: {
|
||||
gifButton,
|
||||
style: reactionButton,
|
||||
underlayColor: BaseTheme.palette.ui13,
|
||||
emoji: reactionEmoji
|
||||
|
|
Loading…
Reference in New Issue