Implementing deep link (#41)
This commit is contained in:
committed by
GitHub
parent
f2115e23c7
commit
b4dbb55548
@@ -35,6 +35,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, RCTBridgeDelegate {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||
BigBlueButtonSDK.handleDeepLink(app, open: url, options: options)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
|
||||
func sourceURL(for bridge: RCTBridge!) -> URL! {
|
||||
//#if DEBUG
|
||||
|
||||
@@ -20,6 +20,19 @@
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.bigbluebutton.mobile</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>bigbluebutton</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
||||
14
react-native/app/emitter/emitter.ts
Normal file
14
react-native/app/emitter/emitter.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
NativeModules,
|
||||
NativeEventEmitter,
|
||||
} from 'react-native';
|
||||
|
||||
const {
|
||||
ReactNativeEventEmitter,
|
||||
} = NativeModules;
|
||||
|
||||
|
||||
export const emitter: NativeEventEmitter = new NativeEventEmitter(
|
||||
ReactNativeEventEmitter
|
||||
)
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {ICreatePortal, IPortal, IPortalProp, IPortalToAdd} from './types';
|
||||
import { IPortal, IPortalProp, IPortalToAdd} from './types';
|
||||
|
||||
export async function createNewPortal({
|
||||
name,
|
||||
url,
|
||||
}: ICreatePortal): Promise<IPortal[]> {
|
||||
temporary = false
|
||||
}: IPortal): Promise<IPortal[]> {
|
||||
let portalsStorage: IPortalProp = await getPortals();
|
||||
|
||||
if (portalsStorage === null || portalsStorage === '[]')
|
||||
return await createFromEmptyStorage(name, url);
|
||||
return await createFromEmptyStorage({name, url, temporary});
|
||||
|
||||
portalsStorage = parseString(portalsStorage);
|
||||
|
||||
return await addPortalToStorage({portals: portalsStorage, name, url});
|
||||
return await addPortalToStorage({portals: portalsStorage, name, url, temporary});
|
||||
}
|
||||
|
||||
async function createFromEmptyStorage(name: string, url: string) {
|
||||
async function createFromEmptyStorage({name, url, temporary}:IPortal) {
|
||||
await createStorageEmpty();
|
||||
let portalsStorage = await getPortals();
|
||||
const portalStorage = parseString(portalsStorage!);
|
||||
await addPortalToStorage({portals: portalStorage, name, url});
|
||||
await addPortalToStorage({portals: portalStorage, name, url, temporary});
|
||||
return portalStorage;
|
||||
}
|
||||
|
||||
@@ -39,8 +40,9 @@ async function addPortalToStorage({
|
||||
portals,
|
||||
name,
|
||||
url,
|
||||
temporary
|
||||
}: IPortalToAdd): Promise<IPortal[]> {
|
||||
portals.push({name, url});
|
||||
portals.push({name, url, temporary});
|
||||
await AsyncStorage.setItem('portal', JSON.stringify(portals));
|
||||
return portals;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
export type ICreatePortal = {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type IPortal = {
|
||||
name: string;
|
||||
url: string;
|
||||
temporary: boolean
|
||||
};
|
||||
|
||||
export type IPortalToAdd = {
|
||||
portals: Array<IPortal>;
|
||||
name: string;
|
||||
url: string;
|
||||
temporary: boolean;
|
||||
};
|
||||
|
||||
export type IPortalProp = string | null | IPortal[];
|
||||
|
||||
60
react-native/app/routes/component.js
vendored
60
react-native/app/routes/component.js
vendored
@@ -1,60 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import {NavigationContainer} from '@react-navigation/native';
|
||||
import {ListPortals} from '../pages/list_portals/component';
|
||||
import {usePortal} from '../contexts/portals/hook';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import i18next from 'i18next';
|
||||
|
||||
import {createDrawerNavigator} from '@react-navigation/drawer';
|
||||
import SdkContainer from '../../bootstrap/sdk/container';
|
||||
import {initTranslation} from '../translations/index';
|
||||
|
||||
const Drawer = createDrawerNavigator();
|
||||
export const Routes = () => {
|
||||
initTranslation();
|
||||
|
||||
const {portals, setPortals} = usePortal();
|
||||
async function getPortals() {
|
||||
try {
|
||||
let items = await AsyncStorage.getAllKeys();
|
||||
if (items.includes('portal')) {
|
||||
let portalsStorage = await AsyncStorage.getItem('portal');
|
||||
portalsStorage = JSON.parse(portalsStorage);
|
||||
setPortals(portalsStorage);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
getPortals();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NavigationContainer>
|
||||
<Drawer.Navigator
|
||||
initialRouteName="Portals"
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
}}>
|
||||
<Drawer.Screen
|
||||
name={i18next.t('mobileApp.portals.drawerNavigation.button.label')}
|
||||
component={ListPortals}
|
||||
/>
|
||||
{portals && portals.length
|
||||
? portals.map(item => {
|
||||
return (
|
||||
<Drawer.Screen
|
||||
key={item.name}
|
||||
name={item.name}
|
||||
children={() => <SdkContainer url={item.url} />}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</Drawer.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
133
react-native/app/routes/component.tsx
Normal file
133
react-native/app/routes/component.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import * as React from 'react';
|
||||
import { NavigationContainer, useNavigation} from '@react-navigation/native';
|
||||
import {ListPortals} from '../pages/list_portals/component';
|
||||
import {usePortal} from '../contexts/portals/hook';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import i18next from 'i18next';
|
||||
|
||||
import {createDrawerNavigator} from '@react-navigation/drawer';
|
||||
import SdkContainer from '../../bootstrap/sdk/container';
|
||||
import {initTranslation} from '../translations/index';
|
||||
import { Alert, Linking } from 'react-native';
|
||||
import { createNewPortal } from '../pages/utils/createNewPortal';
|
||||
import { emitter } from '../emitter/emitter';
|
||||
import { IPortal } from '../pages/utils/types';
|
||||
|
||||
const DeepLink = ()=>{
|
||||
initTranslation();
|
||||
|
||||
const SCHEME = 'bigbluebutton://';
|
||||
const SCHEME_DEFAULT = 'https://'
|
||||
var NAME_PORTALS_DEEP_LINK = i18next.t('mobileApp.portals.namePortal.deepLink');
|
||||
|
||||
const navigate = useNavigation();
|
||||
const {portals, setPortals} = usePortal();
|
||||
|
||||
async function createTemporaryPortalFromDeepLink(link: string){
|
||||
const linkWithoutScheme = link.replace(SCHEME, '');
|
||||
|
||||
if(linkWithoutScheme === ''){
|
||||
navigate.navigate(i18next.t('mobileApp.portals.drawerNavigation.button.label'))
|
||||
return Alert.alert(i18next.t('mobileApp.portals.handleWithoutURL'))
|
||||
}
|
||||
const roomName = linkWithoutScheme.match(/^\w+/)
|
||||
if(roomName != 'bigbluebutton'){
|
||||
NAME_PORTALS_DEEP_LINK = roomName[0]
|
||||
}
|
||||
|
||||
const linkWhitoutSchemeAndName = linkWithoutScheme.replace(/^\w+[\/]/, '')
|
||||
const portalToAdd:IPortal = {
|
||||
name: NAME_PORTALS_DEEP_LINK,
|
||||
url: SCHEME_DEFAULT+linkWhitoutSchemeAndName,
|
||||
temporary: true
|
||||
}
|
||||
|
||||
// Adding LinkedPortal to AsyncStorage
|
||||
const newPortals = await createNewPortal(portalToAdd)
|
||||
// Adding to context
|
||||
setPortals(newPortals)
|
||||
// Navigation to portal
|
||||
navigate.navigate(NAME_PORTALS_DEEP_LINK)
|
||||
}
|
||||
|
||||
async function checkIfHaveTemporaryPortal(){
|
||||
const portalsFromStorage = await AsyncStorage.getItem('portal')
|
||||
const portalsParsed = JSON.parse(portalsFromStorage)
|
||||
const portalsWithoutTemporary = portalsParsed.filter((portal: IPortal)=>{
|
||||
if(portal.temporary == true) return false
|
||||
if(portal.name == NAME_PORTALS_DEEP_LINK) return false
|
||||
return portal
|
||||
})
|
||||
await AsyncStorage.setItem('portal', JSON.stringify(portalsWithoutTemporary))
|
||||
setPortals(portalsWithoutTemporary)
|
||||
|
||||
getLinkFromAppClosed() //To app running in backgrond then open throug deep link
|
||||
Linking.addEventListener('url', (url)=>{
|
||||
// if app is open when it's minimized (not running in background)
|
||||
createTemporaryPortalFromDeepLink(url.url)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
async function getLinkFromAppClosed(){
|
||||
const linkFromAppClosed = await Linking.getInitialURL()
|
||||
if(linkFromAppClosed === null) return
|
||||
createTemporaryPortalFromDeepLink(linkFromAppClosed)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
checkIfHaveTemporaryPortal()
|
||||
|
||||
}, []);
|
||||
return null
|
||||
}
|
||||
|
||||
const Drawer = createDrawerNavigator();
|
||||
export const Routes = () => {
|
||||
initTranslation();
|
||||
const {portals, setPortals} = usePortal();
|
||||
async function getPortals() {
|
||||
try {
|
||||
let items = await AsyncStorage.getAllKeys();
|
||||
if (items.includes('portal')) {
|
||||
let portalsStorage = await AsyncStorage.getItem('portal');
|
||||
portalsStorage = JSON.parse(portalsStorage);
|
||||
setPortals(portalsStorage);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
getPortals();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NavigationContainer>
|
||||
<DeepLink/>
|
||||
<Drawer.Navigator
|
||||
initialRouteName={i18next.t('mobileApp.portals.drawerNavigation.button.label')}
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
}}>
|
||||
<Drawer.Screen
|
||||
name={i18next.t('mobileApp.portals.drawerNavigation.button.label')}
|
||||
component={ListPortals}
|
||||
/>
|
||||
{portals && portals.length
|
||||
? portals.map((item: { name: React.Key | null | undefined; url: string; }) => {
|
||||
return (
|
||||
<Drawer.Screen
|
||||
key={item.name}
|
||||
name={item.name}
|
||||
children={() => <SdkContainer url={item.url} />}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</Drawer.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
@@ -9,5 +9,7 @@ export default {
|
||||
"mobileApp.portals.drawerNavigation.button.label": "Portals",
|
||||
"mobileApp.portals.addPortalPopup.validation.emptyFields": "Required Fields",
|
||||
"mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "Name already in use",
|
||||
"mobileApp.portals.addPortalPopup.validation.urlInvalid": "Error trying to load the page - check URL and network connection"
|
||||
"mobileApp.portals.addPortalPopup.validation.urlInvalid": "Error trying to load the page - check URL and network connection",
|
||||
"mobileApp.portals.namePortal.deepLink": "Linked Portal",
|
||||
"mobileApp.portals.handleWithoutURL": "Dont have link, you was redirected to list portals home.",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user