Phecda

feat: link, url scheme, and navigate

... ... @@ -41,6 +41,13 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "example://gizmos” -->
<data android:scheme="ngplay" android:host="open.my.app" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
... ...
... ... @@ -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>base</string>
<key>CFBundleURLSchemes</key>
<array>
<string>ngplay</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
... ...
... ... @@ -23,6 +23,10 @@ import { useI18nStrings } from '../i18n';
import CameraScreen from './CameraScreen';
import Library from './Library';
import ReadableCode from './ReadableCode';
import { useDeepLinking } from '../utility/handleDeepLinking';
import { navigationRef, useMountedRef } from '../utility/rootNavigation';
import ShortcutPage from './ShortcutItem';
import useQuickAction from '../utility/useQuickAction';
const MainTab = createBottomTabNavigator<MainTabParamList>();
... ... @@ -93,8 +97,14 @@ const MainStack = createStackNavigator<MainStackParamList>();
const Container = () => {
const inDarkMode = useDarkMode();
const strings = useI18nStrings();
useMountedRef();
useDeepLinking();
useQuickAction();
return (
<NavigationContainer
ref={navigationRef}
theme={inDarkMode ? themeForNav.dark : themeForNav.light}
>
<MainStack.Navigator
... ... @@ -125,6 +135,7 @@ const Container = () => {
<MainStack.Screen name="RNLocalize" component={RNLocalize} />
<MainStack.Screen name="RNCamera" component={CameraScreen} />
<MainStack.Screen name="RNCode" component={ReadableCode} />
<MainStack.Screen name="ShortcutItem" component={ShortcutPage} />
</MainStack.Navigator>
</NavigationContainer>
);
... ...
... ... @@ -9,12 +9,19 @@ import { NativeModules, Alert } from 'react-native';
const items: ShortcutItem[] = [
{
type: 'Library',
type: 'Shortcut',
title: 'Shortcut',
subtitle: 'Show shortcut page',
userInfo: { url: 'ngplay://open.my.app/ShortcutItem?id=trigger' },
icon: 'Compose',
},
{
type: 'SystemInfo',
title: 'System Info',
subtitle: 'Show SystemInfo tab',
userInfo: { url: 'ngplay://open.my.app/SystemInfo' },
icon: 'Order',
},
];
const ShortcutPage = () => {
... ...
... ... @@ -9,12 +9,13 @@ export type MainTabParamList = {
};
export type MainStackParamList = {
MainTab: undefined;
MainTab: { screen?: keyof MainTabParamList };
RNDeviceInfoList: undefined;
WebviewScreen: { uri: string } | undefined;
RNLocalize: undefined;
RNCamera: undefined;
RNCode: undefined;
ShortcutItem: { id?: string };
};
export type MainTabScreenProps<RouteName extends keyof MainTabParamList> = {
... ...
declare type NVPair<V = string> = { name: string; value: V };
declare type PromiseResult<T extends Promise<any>> = T extends Promise<infer U>
? U
: never;
declare type ResultOf<T extends () => any> = ReturnType<T> extends Promise<
infer R
>
? R
: ReturnType<T>;
... ...
import { Linking } from 'react-native';
import { useEffect } from 'react';
import { rootNavigate, AllRouteNames } from './rootNavigation';
import URLParse from 'url-parse';
export const appScheme = 'ngplay:';
export const universalLink = '';
export function handleUrl(url?: string | null) {
if (!url) return;
const urlParse = new URLParse(url, true);
if (urlParse.protocol === appScheme) {
const routeName = urlParse.pathname.substring(1) as AllRouteNames;
const params = urlParse.query;
if (routeName) {
rootNavigate(routeName, params);
}
}
}
export function handleUrlEvent(event: { url: string }) {
handleUrl(event.url);
}
export function useDeepLinking() {
Linking.getInitialURL().then(handleUrl);
useEffect(() => {
Linking.addEventListener('url', handleUrlEvent);
return () => Linking.removeListener('url', handleUrlEvent);
}, []);
}
... ...
import * as React from 'react';
import { NavigationContainerRef } from '@react-navigation/native';
import { MainStackParamList, MainTabParamList } from '../type/Navigation';
export const navigationRef = React.createRef<NavigationContainerRef>();
export const isMountedRef = React.createRef<
boolean
>() as React.MutableRefObject<boolean>;
export function useMountedRef() {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}
export type ParamList = MainStackParamList & MainTabParamList;
export type AllRouteNames = keyof ParamList;
export function rootNavigate<K extends AllRouteNames>(
name: K,
params: ParamList[K]
) {
if (isMountedRef.current && navigationRef.current) {
navigationRef.current.navigate(name, params);
}
}
... ...
import {
QuickActionEmitter,
QuickActionEventName,
ShortcutItem,
popInitialAction,
} from 'react-native-quick-actions';
import { useEffect } from 'react';
import { handleUrl } from './handleDeepLinking';
export default function useQuickAction() {
useEffect(() => {
popInitialAction()
.then(item => {
handleUrl(item?.userInfo.url);
})
.catch(() => {});
const subscription = QuickActionEmitter.addListener(
QuickActionEventName,
(item?: ShortcutItem) => {
handleUrl(item?.userInfo.url);
}
);
return () => {
subscription.remove();
};
}, []);
}
... ...