From b4dbb55548629f00a0369ad2b5c45d2f30b6318f Mon Sep 17 00:00:00 2001 From: Gustavo Emanuel Farias Rosa Date: Wed, 15 Jun 2022 16:14:57 -0300 Subject: [PATCH] Implementing deep link (#41) --- ios/AppDelegate.swift | 7 + ios/BigBlueButton/Info.plist | 13 ++ react-native/app/emitter/emitter.ts | 14 ++ .../app/pages/utils/createNewPortal.ts | 16 ++- react-native/app/pages/utils/types.ts | 7 +- react-native/app/routes/component.js | 60 -------- react-native/app/routes/component.tsx | 133 ++++++++++++++++++ react-native/app/translations/resources/en.ts | 4 +- 8 files changed, 181 insertions(+), 73 deletions(-) create mode 100644 react-native/app/emitter/emitter.ts delete mode 100644 react-native/app/routes/component.js create mode 100644 react-native/app/routes/component.tsx diff --git a/ios/AppDelegate.swift b/ios/AppDelegate.swift index 2e3e889..10053b8 100644 --- a/ios/AppDelegate.swift +++ b/ios/AppDelegate.swift @@ -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 diff --git a/ios/BigBlueButton/Info.plist b/ios/BigBlueButton/Info.plist index 57c6222..398f3e1 100644 --- a/ios/BigBlueButton/Info.plist +++ b/ios/BigBlueButton/Info.plist @@ -20,6 +20,19 @@ 1.0 CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.bigbluebutton.mobile + CFBundleURLSchemes + + bigbluebutton + + + CFBundleVersion $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption diff --git a/react-native/app/emitter/emitter.ts b/react-native/app/emitter/emitter.ts new file mode 100644 index 0000000..1320ddb --- /dev/null +++ b/react-native/app/emitter/emitter.ts @@ -0,0 +1,14 @@ +import { + NativeModules, + NativeEventEmitter, +} from 'react-native'; + +const { + ReactNativeEventEmitter, +} = NativeModules; + + +export const emitter: NativeEventEmitter = new NativeEventEmitter( + ReactNativeEventEmitter +) + diff --git a/react-native/app/pages/utils/createNewPortal.ts b/react-native/app/pages/utils/createNewPortal.ts index 93e8b3d..ef40ac1 100644 --- a/react-native/app/pages/utils/createNewPortal.ts +++ b/react-native/app/pages/utils/createNewPortal.ts @@ -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 { + temporary = false +}: IPortal): Promise { 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 { - portals.push({name, url}); + portals.push({name, url, temporary}); await AsyncStorage.setItem('portal', JSON.stringify(portals)); return portals; } diff --git a/react-native/app/pages/utils/types.ts b/react-native/app/pages/utils/types.ts index 01870a6..f48ecb4 100644 --- a/react-native/app/pages/utils/types.ts +++ b/react-native/app/pages/utils/types.ts @@ -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; name: string; url: string; + temporary: boolean; }; export type IPortalProp = string | null | IPortal[]; diff --git a/react-native/app/routes/component.js b/react-native/app/routes/component.js deleted file mode 100644 index b908958..0000000 --- a/react-native/app/routes/component.js +++ /dev/null @@ -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 ( - - - - {portals && portals.length - ? portals.map(item => { - return ( - } - /> - ); - }) - : null} - - - ); -}; diff --git a/react-native/app/routes/component.tsx b/react-native/app/routes/component.tsx new file mode 100644 index 0000000..965bcb5 --- /dev/null +++ b/react-native/app/routes/component.tsx @@ -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 ( + + + + + {portals && portals.length + ? portals.map((item: { name: React.Key | null | undefined; url: string; }) => { + return ( + } + /> + ); + }) + : null} + + + ); +}; diff --git a/react-native/app/translations/resources/en.ts b/react-native/app/translations/resources/en.ts index 88435b9..4020e66 100644 --- a/react-native/app/translations/resources/en.ts +++ b/react-native/app/translations/resources/en.ts @@ -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.", }