[IMPROVE] Disable Jitsi native module on Android (#4708)

* temp: disable jitsi on android

* update props and subscription

* add open intent

* add request permissions

* disable react-native-jitsi-meet on android and separate implementations

* fix ios

* fix import alias

* revert android manifest indentation

* add catch to method

* return comment

* remove is iOS

* fix queries

* remove unused data

* webview audio

* fix android permissions

* fix audio android

* change how to open jitsi app

* remove loading

* update close logic
This commit is contained in:
Gleidson Daniel Silva 2022-12-01 14:20:22 -03:00 committed by Diego Mello
parent 11c979f552
commit 52d56074d7
13 changed files with 176 additions and 53 deletions

View File

@ -2,7 +2,7 @@ module.exports = {
settings: { settings: {
'import/resolver': { 'import/resolver': {
node: { 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']
} }
} }
}, },

View File

@ -357,9 +357,6 @@ dependencies {
playImplementation project(':@react-native-firebase_app') playImplementation project(':@react-native-firebase_app')
playImplementation project(':@react-native-firebase_analytics') playImplementation project(':@react-native-firebase_analytics')
playImplementation project(':@react-native-firebase_crashlytics') 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"]) implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion //noinspection GradleDynamicVersion

View File

@ -5,6 +5,12 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
<application <application
android:name="chat.rocket.reactnative.MainApplication" android:name="chat.rocket.reactnative.MainApplication"
android:allowBackup="false" android:allowBackup="false"
@ -14,6 +20,7 @@
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/BootTheme" android:theme="@style/BootTheme"
android:hardwareAccelerated="true"
tools:replace="android:allowBackup"> tools:replace="android:allowBackup">
<activity <activity
android:name="chat.rocket.reactnative.MainActivity" android:name="chat.rocket.reactnative.MainActivity"
@ -69,5 +76,10 @@
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
<queries>
<package android:name="org.jitsi.meet" />
<intent>
<action android:name="android.intent.action.SEND" />
</intent>
</queries>
</manifest> </manifest>

View File

@ -26,8 +26,6 @@ buildscript {
kotlinVersion = '1.6.10' kotlinVersion = '1.6.10'
supportLibVersion = "28.0.0" supportLibVersion = "28.0.0"
libre_build = !(isPlay.toBoolean()) libre_build = !(isPlay.toBoolean())
jitsi_url = "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases"
jitsi_version = "3.7.0"
} }
repositories { repositories {
@ -68,9 +66,6 @@ allprojects {
url "$rootDir/../node_modules/detox/Detox-android" url "$rootDir/../node_modules/detox/Detox-android"
} }
maven {
url jitsi_url
}
mavenCentral { mavenCentral {
content { content {
excludeGroup "com.facebook.react" excludeGroup "com.facebook.react"

View File

@ -1,14 +1,22 @@
import navigation from '../navigation/appNavigation'; import { PermissionsAndroid } from 'react-native';
import openLink from './helpers/openLink';
import { Services } from '../services';
import log from './helpers/log';
import { showErrorAlert } from './helpers';
import i18n from '../../i18n'; 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) => { export const videoConfJoin = async (callId: string, cam: boolean) => {
try { try {
const result = await Services.videoConferenceJoin(callId, cam); const result = await Services.videoConferenceJoin(callId, cam);
if (result.success) { if (result.success) {
if (isAndroid) {
await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.CAMERA,
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO
]);
}
const { url, providerName } = result; const { url, providerName } = result;
if (providerName === 'jitsi') { if (providerName === 'jitsi') {
navigation.navigate('JitsiMeetView', { url, onlyAudio: !cam, videoConf: true }); navigation.navigate('JitsiMeetView', { url, onlyAudio: !cam, videoConf: true });

View File

@ -80,6 +80,7 @@ import {
ProfileStackParamList, ProfileStackParamList,
SettingsStackParamList SettingsStackParamList
} from './types'; } from './types';
import { isIOS } from '../lib/methods/helpers';
// ChatsStackNavigator // ChatsStackNavigator
const ChatsStack = createStackNavigator<ChatsStackParamList>(); const ChatsStack = createStackNavigator<ChatsStackParamList>();
@ -135,7 +136,11 @@ const ChatsStackNavigator = () => {
<ChatsStack.Screen name='QueueListView' component={QueueListView} /> <ChatsStack.Screen name='QueueListView' component={QueueListView} />
<ChatsStack.Screen name='CannedResponsesListView' component={CannedResponsesListView} /> <ChatsStack.Screen name='CannedResponsesListView' component={CannedResponsesListView} />
<ChatsStack.Screen name='CannedResponseDetail' component={CannedResponseDetail} /> <ChatsStack.Screen name='CannedResponseDetail' component={CannedResponseDetail} />
<ChatsStack.Screen name='JitsiMeetView' component={JitsiMeetView} options={{ headerShown: false }} /> <ChatsStack.Screen
name='JitsiMeetView'
component={JitsiMeetView}
options={{ headerShown: false, animationEnabled: isIOS }}
/>
</ChatsStack.Navigator> </ChatsStack.Navigator>
); );
}; };

View File

@ -73,6 +73,7 @@ import {
MasterDetailInsideStackParamList, MasterDetailInsideStackParamList,
ModalStackParamList ModalStackParamList
} from './types'; } from './types';
import { isIOS } from '../../lib/methods/helpers';
// ChatsStackNavigator // ChatsStackNavigator
const ChatsStack = createStackNavigator<MasterDetailChatsStackParamList>(); const ChatsStack = createStackNavigator<MasterDetailChatsStackParamList>();
@ -222,7 +223,11 @@ const InsideStackNavigator = React.memo(() => {
<InsideStack.Screen name='ModalStackNavigator' component={ModalStackNavigator} options={{ headerShown: false }} /> <InsideStack.Screen name='ModalStackNavigator' component={ModalStackNavigator} options={{ headerShown: false }} />
<InsideStack.Screen name='AttachmentView' component={AttachmentView} /> <InsideStack.Screen name='AttachmentView' component={AttachmentView} />
<InsideStack.Screen name='ModalBlockView' component={ModalBlockView} options={ModalBlockView.navigationOptions} /> <InsideStack.Screen name='ModalBlockView' component={ModalBlockView} options={ModalBlockView.navigationOptions} />
<InsideStack.Screen name='JitsiMeetView' component={JitsiMeetView} options={{ headerShown: false }} /> <InsideStack.Screen
name='JitsiMeetView'
component={JitsiMeetView}
options={{ headerShown: false, animationEnabled: isIOS }}
/>
<InsideStack.Screen name='ShareView' component={ShareView} /> <InsideStack.Screen name='ShareView' component={ShareView} />
</InsideStack.Navigator> </InsideStack.Navigator>
); );

View File

@ -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<ChatsStackParamList, 'JitsiMeetView'>;
class JitsiMeetView extends React.Component<TJitsiMeetViewProps> {
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 (
<WebView
source={{ uri: `${this.url}&config.disableDeepLinking=true` }}
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
onNavigationStateChange={this.onNavigationStateChange}
style={{ flex: 1 }}
javaScriptEnabled
domStorageEnabled
mediaPlaybackRequiresUserAction={false}
/>
);
}
}
export default withTheme(JitsiMeetView);

5
app/views/JitsiMeetView.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import React from 'react';
declare const JitsiMeetView: React.SFC<>;
export default JitsiMeetView;

View File

@ -1,17 +1,16 @@
import React from 'react'; import React from 'react';
import { BackHandler, StyleSheet } from 'react-native'; import { StyleSheet } from 'react-native';
import JitsiMeet, { JitsiMeetView as RNJitsiMeetView } from 'react-native-jitsi-meet';
import BackgroundTimer from 'react-native-background-timer'; import BackgroundTimer from 'react-native-background-timer';
import JitsiMeet, { JitsiMeetView as RNJitsiMeetView } from 'react-native-jitsi-meet';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getUserSelector } from '../selectors/login'; import RCActivityIndicator from '../containers/ActivityIndicator';
import ActivityIndicator from '../containers/ActivityIndicator'; import { IApplicationState, IBaseScreen, IUser } from '../definitions';
import { events, logEvent } from '../lib/methods/helpers/log'; 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 { 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) => const formatUrl = (url: string, baseUrl: string, uriSize: number, avatarAuthURLFragment: string) =>
`${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`; `${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`;
@ -59,11 +58,6 @@ class JitsiMeetView extends React.Component<IJitsiMeetViewProps, IJitsiMeetViewS
const { route } = this.props; const { route } = this.props;
const { userInfo } = this.state; const { userInfo } = this.state;
setTimeout(() => {
this.setState({ loading: false });
}, 1000);
if (isIOS) {
setTimeout(() => { setTimeout(() => {
const onlyAudio = route.params?.onlyAudio ?? false; const onlyAudio = route.params?.onlyAudio ?? false;
if (onlyAudio) { if (onlyAudio) {
@ -71,10 +65,9 @@ class JitsiMeetView extends React.Component<IJitsiMeetViewProps, IJitsiMeetViewS
} else { } else {
JitsiMeet.call(this.url, userInfo); JitsiMeet.call(this.url, userInfo);
} }
this.setState({ loading: false });
}, 1000); }, 1000);
} }
BackHandler.addEventListener('hardwareBackPress', () => null);
}
componentWillUnmount() { componentWillUnmount() {
logEvent(events.JM_CONFERENCE_TERMINATE); logEvent(events.JM_CONFERENCE_TERMINATE);
@ -83,16 +76,8 @@ class JitsiMeetView extends React.Component<IJitsiMeetViewProps, IJitsiMeetViewS
this.jitsiTimeout = null; this.jitsiTimeout = null;
BackgroundTimer.stopBackgroundTimer(); BackgroundTimer.stopBackgroundTimer();
} }
BackHandler.removeEventListener('hardwareBackPress', () => null);
if (isIOS) {
JitsiMeet.endCall(); JitsiMeet.endCall();
} }
}
endCall = () => {
JitsiMeet.endCall();
return null;
};
onConferenceWillJoin = () => { onConferenceWillJoin = () => {
this.setState({ loading: false }); this.setState({ loading: false });
@ -117,8 +102,8 @@ class JitsiMeetView extends React.Component<IJitsiMeetViewProps, IJitsiMeetViewS
}; };
onConferenceTerminated = () => { onConferenceTerminated = () => {
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
const { navigation } = this.props; const { navigation } = this.props;
logEvent(this.videoConf ? events.LIVECHAT_VIDEOCONF_TERMINATE : events.JM_CONFERENCE_TERMINATE);
// fix to go back when the call ends // fix to go back when the call ends
setTimeout(() => { setTimeout(() => {
JitsiMeet.endCall(); JitsiMeet.endCall();
@ -127,10 +112,8 @@ class JitsiMeetView extends React.Component<IJitsiMeetViewProps, IJitsiMeetViewS
}; };
render() { render() {
const { userInfo, loading } = this.state; const { loading } = this.state;
const { route } = this.props;
const onlyAudio = route.params?.onlyAudio ?? false;
const options = isAndroid ? { url: this.url, userInfo, audioOnly: onlyAudio } : null;
return ( return (
<> <>
<RNJitsiMeetView <RNJitsiMeetView
@ -138,9 +121,9 @@ class JitsiMeetView extends React.Component<IJitsiMeetViewProps, IJitsiMeetViewS
onConferenceTerminated={this.onConferenceTerminated} onConferenceTerminated={this.onConferenceTerminated}
onConferenceJoined={this.onConferenceJoined} onConferenceJoined={this.onConferenceJoined}
style={StyleSheet.absoluteFill} style={StyleSheet.absoluteFill}
options={options} options={null}
/> />
{loading ? <ActivityIndicator /> : null} {loading ? <RCActivityIndicator absolute size='large' /> : null}
</> </>
); );
} }

View File

@ -118,6 +118,7 @@
"react-native-safe-area-context": "3.2.0", "react-native-safe-area-context": "3.2.0",
"react-native-screens": "3.13.1", "react-native-screens": "3.13.1",
"react-native-scrollable-tab-view": "ptomasroos/react-native-scrollable-tab-view", "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-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.1",
"react-native-skeleton-placeholder": "^5.2.3", "react-native-skeleton-placeholder": "^5.2.3",
"react-native-slowlog": "^1.0.2", "react-native-slowlog": "^1.0.2",

View File

@ -14,6 +14,11 @@ module.exports = {
platforms: { platforms: {
android: null android: null
} }
},
'react-native-jitsi-meet': {
platforms: {
android: null
}
} }
} }
}; };

View File

@ -17330,6 +17330,11 @@ react-native-scrollable-tab-view@ptomasroos/react-native-scrollable-tab-view:
prop-types "^15.6.0" prop-types "^15.6.0"
react-timer-mixin "^0.13.3" 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: react-native-simple-crypto@RocketChat/react-native-simple-crypto#0.5.1:
version "0.5.1" version "0.5.1"
resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/dcf6eef5359c739d521371918e13a73f2ea6cb42" resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/dcf6eef5359c739d521371918e13a73f2ea6cb42"