diff --git a/.eslintrc.js b/.eslintrc.js
index 1f0105d18..963e50abf 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,7 +2,7 @@ module.exports = {
settings: {
'import/resolver': {
node: {
- extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js', '.native.js']
+ extensions: ['.ts', '.tsx', '.js', '.ios.js', '.android.js', '.native.js', '.ios.tsx', '.android.tsx']
}
}
},
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 493b9874d..fe11088a0 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -357,9 +357,6 @@ dependencies {
playImplementation project(':@react-native-firebase_app')
playImplementation project(':@react-native-firebase_analytics')
playImplementation project(':@react-native-firebase_crashlytics')
- implementation(project(':react-native-jitsi-meet')) { // https://github.com/skrafft/react-native-jitsi-meet#side-note
- exclude group: 'com.facebook.react',module:'react-native-svg'
- }
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 9c72e6b32..0765164fb 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -5,6 +5,12 @@
+
+
+
+
+
+
-
+
+
+
+
+
+
diff --git a/android/build.gradle b/android/build.gradle
index 8680080c2..b05e6a6f1 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -26,8 +26,6 @@ buildscript {
kotlinVersion = '1.6.10'
supportLibVersion = "28.0.0"
libre_build = !(isPlay.toBoolean())
- jitsi_url = "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases"
- jitsi_version = "3.7.0"
}
repositories {
@@ -68,9 +66,6 @@ allprojects {
url "$rootDir/../node_modules/detox/Detox-android"
}
- maven {
- url jitsi_url
- }
mavenCentral {
content {
excludeGroup "com.facebook.react"
diff --git a/app/lib/methods/videoConf.ts b/app/lib/methods/videoConf.ts
index f10b2ad14..13c604164 100644
--- a/app/lib/methods/videoConf.ts
+++ b/app/lib/methods/videoConf.ts
@@ -1,14 +1,22 @@
-import navigation from '../navigation/appNavigation';
-import openLink from './helpers/openLink';
-import { Services } from '../services';
-import log from './helpers/log';
-import { showErrorAlert } from './helpers';
+import { PermissionsAndroid } from 'react-native';
+
import i18n from '../../i18n';
+import navigation from '../navigation/appNavigation';
+import { Services } from '../services';
+import { isAndroid, showErrorAlert } from './helpers';
+import log from './helpers/log';
+import openLink from './helpers/openLink';
export const videoConfJoin = async (callId: string, cam: boolean) => {
try {
const result = await Services.videoConferenceJoin(callId, cam);
if (result.success) {
+ if (isAndroid) {
+ await PermissionsAndroid.requestMultiple([
+ PermissionsAndroid.PERMISSIONS.CAMERA,
+ PermissionsAndroid.PERMISSIONS.RECORD_AUDIO
+ ]);
+ }
const { url, providerName } = result;
if (providerName === 'jitsi') {
navigation.navigate('JitsiMeetView', { url, onlyAudio: !cam, videoConf: true });
diff --git a/app/stacks/InsideStack.tsx b/app/stacks/InsideStack.tsx
index 0b6475640..9d9899393 100644
--- a/app/stacks/InsideStack.tsx
+++ b/app/stacks/InsideStack.tsx
@@ -80,6 +80,7 @@ import {
ProfileStackParamList,
SettingsStackParamList
} from './types';
+import { isIOS } from '../lib/methods/helpers';
// ChatsStackNavigator
const ChatsStack = createStackNavigator();
@@ -135,7 +136,11 @@ const ChatsStackNavigator = () => {
-
+
);
};
diff --git a/app/stacks/MasterDetailStack/index.tsx b/app/stacks/MasterDetailStack/index.tsx
index 695efc3b9..0b315a284 100644
--- a/app/stacks/MasterDetailStack/index.tsx
+++ b/app/stacks/MasterDetailStack/index.tsx
@@ -73,6 +73,7 @@ import {
MasterDetailInsideStackParamList,
ModalStackParamList
} from './types';
+import { isIOS } from '../../lib/methods/helpers';
// ChatsStackNavigator
const ChatsStack = createStackNavigator();
@@ -222,7 +223,11 @@ const InsideStackNavigator = React.memo(() => {
-
+
);
diff --git a/app/views/JitsiMeetView.android.tsx b/app/views/JitsiMeetView.android.tsx
new file mode 100644
index 000000000..344a656af
--- /dev/null
+++ b/app/views/JitsiMeetView.android.tsx
@@ -0,0 +1,102 @@
+import React from 'react';
+import { BackHandler, NativeEventSubscription } from 'react-native';
+import BackgroundTimer from 'react-native-background-timer';
+import { isAppInstalled, openAppWithUri } from 'react-native-send-intent';
+import WebView from 'react-native-webview';
+import { WebViewMessage, WebViewNavigation } from 'react-native-webview/lib/WebViewTypes';
+
+import { IBaseScreen } from '../definitions';
+import { events, logEvent } from '../lib/methods/helpers/log';
+import { Services } from '../lib/services';
+import { ChatsStackParamList } from '../stacks/types';
+import { withTheme } from '../theme';
+
+const JITSI_INTENT = 'org.jitsi.meet';
+
+type TJitsiMeetViewProps = IBaseScreen;
+
+class JitsiMeetView extends React.Component {
+ private rid: string;
+ private url: string;
+ private videoConf: boolean;
+ private jitsiTimeout: number | null;
+ private backHandler!: NativeEventSubscription;
+
+ constructor(props: TJitsiMeetViewProps) {
+ super(props);
+ this.rid = props.route.params?.rid;
+ this.url = props.route.params?.url;
+ this.videoConf = !!props.route.params?.videoConf;
+ this.jitsiTimeout = null;
+ }
+
+ componentDidMount() {
+ const { route, navigation } = this.props;
+ isAppInstalled(JITSI_INTENT)
+ .then(function (isInstalled) {
+ if (isInstalled) {
+ const callUrl = route.params.url.replace(/^https?:\/\//, '').split('#')[0];
+ openAppWithUri(`intent://${callUrl}#Intent;scheme=${JITSI_INTENT};package=${JITSI_INTENT};end`)
+ .then(() => navigation.pop())
+ .catch(() => {});
+ }
+ })
+ .catch(() => {});
+ this.onConferenceJoined();
+ this.backHandler = BackHandler.addEventListener('hardwareBackPress', () => true);
+ }
+
+ componentWillUnmount() {
+ logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
+ if (this.jitsiTimeout && !this.videoConf) {
+ BackgroundTimer.clearInterval(this.jitsiTimeout);
+ this.jitsiTimeout = null;
+ BackgroundTimer.stopBackgroundTimer();
+ }
+ this.backHandler.remove();
+ }
+
+ // Jitsi Update Timeout needs to be called every 10 seconds to make sure
+ // call is not ended and is available to web users.
+ onConferenceJoined = () => {
+ logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_JOIN : events.JM_CONFERENCE_JOIN);
+ if (this.rid && !this.videoConf) {
+ Services.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e));
+ if (this.jitsiTimeout) {
+ BackgroundTimer.clearInterval(this.jitsiTimeout);
+ BackgroundTimer.stopBackgroundTimer();
+ this.jitsiTimeout = null;
+ }
+ this.jitsiTimeout = BackgroundTimer.setInterval(() => {
+ Services.updateJitsiTimeout(this.rid).catch((e: unknown) => console.log(e));
+ }, 10000);
+ }
+ };
+
+ onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => {
+ const { navigation, route } = this.props;
+ const jitsiRoomId = route.params.url
+ ?.split(/^https?:\/\//)[1]
+ ?.split('#')[0]
+ ?.split('/')[1];
+ if ((jitsiRoomId && !webViewState.url.includes(jitsiRoomId)) || webViewState.url.includes('close')) {
+ navigation.pop();
+ }
+ };
+
+ render() {
+ return (
+ this.onNavigationStateChange(nativeEvent)}
+ onNavigationStateChange={this.onNavigationStateChange}
+ style={{ flex: 1 }}
+ javaScriptEnabled
+ domStorageEnabled
+ mediaPlaybackRequiresUserAction={false}
+ />
+ );
+ }
+}
+
+export default withTheme(JitsiMeetView);
diff --git a/app/views/JitsiMeetView.d.ts b/app/views/JitsiMeetView.d.ts
new file mode 100644
index 000000000..c1ad42e90
--- /dev/null
+++ b/app/views/JitsiMeetView.d.ts
@@ -0,0 +1,5 @@
+import React from 'react';
+
+declare const JitsiMeetView: React.SFC<>;
+
+export default JitsiMeetView;
diff --git a/app/views/JitsiMeetView.tsx b/app/views/JitsiMeetView.ios.tsx
similarity index 78%
rename from app/views/JitsiMeetView.tsx
rename to app/views/JitsiMeetView.ios.tsx
index a4af6b835..60cf7198a 100644
--- a/app/views/JitsiMeetView.tsx
+++ b/app/views/JitsiMeetView.ios.tsx
@@ -1,17 +1,16 @@
import React from 'react';
-import { BackHandler, StyleSheet } from 'react-native';
-import JitsiMeet, { JitsiMeetView as RNJitsiMeetView } from 'react-native-jitsi-meet';
+import { StyleSheet } from 'react-native';
import BackgroundTimer from 'react-native-background-timer';
+import JitsiMeet, { JitsiMeetView as RNJitsiMeetView } from 'react-native-jitsi-meet';
import { connect } from 'react-redux';
-import { getUserSelector } from '../selectors/login';
-import ActivityIndicator from '../containers/ActivityIndicator';
+import RCActivityIndicator from '../containers/ActivityIndicator';
+import { IApplicationState, IBaseScreen, IUser } from '../definitions';
import { events, logEvent } from '../lib/methods/helpers/log';
-import { isAndroid, isIOS } from '../lib/methods/helpers';
-import { withTheme } from '../theme';
-import { ChatsStackParamList } from '../stacks/types';
-import { IApplicationState, IUser, IBaseScreen } from '../definitions';
import { Services } from '../lib/services';
+import { getUserSelector } from '../selectors/login';
+import { ChatsStackParamList } from '../stacks/types';
+import { withTheme } from '../theme';
const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLFragment: string) =>
`${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`;
@@ -60,20 +59,14 @@ class JitsiMeetView extends React.Component {
+ const onlyAudio = route.params?.onlyAudio ?? false;
+ if (onlyAudio) {
+ JitsiMeet.audioCall(this.url, userInfo);
+ } else {
+ JitsiMeet.call(this.url, userInfo);
+ }
this.setState({ loading: false });
}, 1000);
-
- if (isIOS) {
- setTimeout(() => {
- const onlyAudio = route.params?.onlyAudio ?? false;
- if (onlyAudio) {
- JitsiMeet.audioCall(this.url, userInfo);
- } else {
- JitsiMeet.call(this.url, userInfo);
- }
- }, 1000);
- }
- BackHandler.addEventListener('hardwareBackPress', () => null);
}
componentWillUnmount() {
@@ -83,16 +76,8 @@ class JitsiMeetView extends React.Component null);
- if (isIOS) {
- JitsiMeet.endCall();
- }
- }
-
- endCall = () => {
JitsiMeet.endCall();
- return null;
- };
+ }
onConferenceWillJoin = () => {
this.setState({ loading: false });
@@ -117,8 +102,8 @@ class JitsiMeetView extends React.Component {
- logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
const { navigation } = this.props;
+ logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
// fix to go back when the call ends
setTimeout(() => {
JitsiMeet.endCall();
@@ -127,10 +112,8 @@ class JitsiMeetView extends React.Component
- {loading ? : null}
+ {loading ? : null}
>
);
}
diff --git a/package.json b/package.json
index c2c2dc2ef..04b433fc5 100644
--- a/package.json
+++ b/package.json
@@ -118,6 +118,7 @@
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "3.13.1",
"react-native-scrollable-tab-view": "ptomasroos/react-native-scrollable-tab-view",
+ "react-native-send-intent": "^1.3.0",
"react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.1",
"react-native-skeleton-placeholder": "^5.2.3",
"react-native-slowlog": "^1.0.2",
diff --git a/react-native.config.js b/react-native.config.js
index 9e78b41ad..1dbd95952 100644
--- a/react-native.config.js
+++ b/react-native.config.js
@@ -14,6 +14,11 @@ module.exports = {
platforms: {
android: null
}
+ },
+ 'react-native-jitsi-meet': {
+ platforms: {
+ android: null
+ }
}
}
};
diff --git a/yarn.lock b/yarn.lock
index 124d75a28..bc9ac843c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -17330,6 +17330,11 @@ react-native-scrollable-tab-view@ptomasroos/react-native-scrollable-tab-view:
prop-types "^15.6.0"
react-timer-mixin "^0.13.3"
+react-native-send-intent@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/react-native-send-intent/-/react-native-send-intent-1.3.0.tgz#d8c7898827da1b8b10e25a645ce6802d1a0b440c"
+ integrity sha512-ODTX7BHITFxdcAL0K2iHfa3qVYnqG8GPcv1NbLBNC1DyCaOSJiiGtVH6Kc5YBqzQ8+1pV9uN5nfQ5wyFgiq74g==
+
react-native-simple-crypto@RocketChat/react-native-simple-crypto#0.5.1:
version "0.5.1"
resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/dcf6eef5359c739d521371918e13a73f2ea6cb42"