Phecda

feat: link, url scheme, and navigate

@@ -41,6 +41,13 @@ @@ -41,6 +41,13 @@
41 <action android:name="android.intent.action.MAIN" /> 41 <action android:name="android.intent.action.MAIN" />
42 <category android:name="android.intent.category.LAUNCHER" /> 42 <category android:name="android.intent.category.LAUNCHER" />
43 </intent-filter> 43 </intent-filter>
  44 + <intent-filter>
  45 + <action android:name="android.intent.action.VIEW" />
  46 + <category android:name="android.intent.category.DEFAULT" />
  47 + <category android:name="android.intent.category.BROWSABLE" />
  48 + <!-- Accepts URIs that begin with "example://gizmos” -->
  49 + <data android:scheme="ngplay" android:host="open.my.app" />
  50 + </intent-filter>
44 </activity> 51 </activity>
45 <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> 52 <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
46 </application> 53 </application>
@@ -20,6 +20,19 @@ @@ -20,6 +20,19 @@
20 <string>1.0</string> 20 <string>1.0</string>
21 <key>CFBundleSignature</key> 21 <key>CFBundleSignature</key>
22 <string>????</string> 22 <string>????</string>
  23 + <key>CFBundleURLTypes</key>
  24 + <array>
  25 + <dict>
  26 + <key>CFBundleTypeRole</key>
  27 + <string>Editor</string>
  28 + <key>CFBundleURLName</key>
  29 + <string>base</string>
  30 + <key>CFBundleURLSchemes</key>
  31 + <array>
  32 + <string>ngplay</string>
  33 + </array>
  34 + </dict>
  35 + </array>
23 <key>CFBundleVersion</key> 36 <key>CFBundleVersion</key>
24 <string>1</string> 37 <string>1</string>
25 <key>LSRequiresIPhoneOS</key> 38 <key>LSRequiresIPhoneOS</key>
@@ -23,6 +23,10 @@ import { useI18nStrings } from '../i18n'; @@ -23,6 +23,10 @@ import { useI18nStrings } from '../i18n';
23 import CameraScreen from './CameraScreen'; 23 import CameraScreen from './CameraScreen';
24 import Library from './Library'; 24 import Library from './Library';
25 import ReadableCode from './ReadableCode'; 25 import ReadableCode from './ReadableCode';
  26 +import { useDeepLinking } from '../utility/handleDeepLinking';
  27 +import { navigationRef, useMountedRef } from '../utility/rootNavigation';
  28 +import ShortcutPage from './ShortcutItem';
  29 +import useQuickAction from '../utility/useQuickAction';
26 30
27 const MainTab = createBottomTabNavigator<MainTabParamList>(); 31 const MainTab = createBottomTabNavigator<MainTabParamList>();
28 32
@@ -93,8 +97,14 @@ const MainStack = createStackNavigator<MainStackParamList>(); @@ -93,8 +97,14 @@ const MainStack = createStackNavigator<MainStackParamList>();
93 const Container = () => { 97 const Container = () => {
94 const inDarkMode = useDarkMode(); 98 const inDarkMode = useDarkMode();
95 const strings = useI18nStrings(); 99 const strings = useI18nStrings();
  100 +
  101 + useMountedRef();
  102 + useDeepLinking();
  103 + useQuickAction();
  104 +
96 return ( 105 return (
97 <NavigationContainer 106 <NavigationContainer
  107 + ref={navigationRef}
98 theme={inDarkMode ? themeForNav.dark : themeForNav.light} 108 theme={inDarkMode ? themeForNav.dark : themeForNav.light}
99 > 109 >
100 <MainStack.Navigator 110 <MainStack.Navigator
@@ -125,6 +135,7 @@ const Container = () => { @@ -125,6 +135,7 @@ const Container = () => {
125 <MainStack.Screen name="RNLocalize" component={RNLocalize} /> 135 <MainStack.Screen name="RNLocalize" component={RNLocalize} />
126 <MainStack.Screen name="RNCamera" component={CameraScreen} /> 136 <MainStack.Screen name="RNCamera" component={CameraScreen} />
127 <MainStack.Screen name="RNCode" component={ReadableCode} /> 137 <MainStack.Screen name="RNCode" component={ReadableCode} />
  138 + <MainStack.Screen name="ShortcutItem" component={ShortcutPage} />
128 </MainStack.Navigator> 139 </MainStack.Navigator>
129 </NavigationContainer> 140 </NavigationContainer>
130 ); 141 );
@@ -9,12 +9,19 @@ import { NativeModules, Alert } from 'react-native'; @@ -9,12 +9,19 @@ import { NativeModules, Alert } from 'react-native';
9 9
10 const items: ShortcutItem[] = [ 10 const items: ShortcutItem[] = [
11 { 11 {
12 - type: 'Library', 12 + type: 'Shortcut',
13 title: 'Shortcut', 13 title: 'Shortcut',
14 subtitle: 'Show shortcut page', 14 subtitle: 'Show shortcut page',
15 userInfo: { url: 'ngplay://open.my.app/ShortcutItem?id=trigger' }, 15 userInfo: { url: 'ngplay://open.my.app/ShortcutItem?id=trigger' },
16 icon: 'Compose', 16 icon: 'Compose',
17 }, 17 },
  18 + {
  19 + type: 'SystemInfo',
  20 + title: 'System Info',
  21 + subtitle: 'Show SystemInfo tab',
  22 + userInfo: { url: 'ngplay://open.my.app/SystemInfo' },
  23 + icon: 'Order',
  24 + },
18 ]; 25 ];
19 26
20 const ShortcutPage = () => { 27 const ShortcutPage = () => {
@@ -9,12 +9,13 @@ export type MainTabParamList = { @@ -9,12 +9,13 @@ export type MainTabParamList = {
9 }; 9 };
10 10
11 export type MainStackParamList = { 11 export type MainStackParamList = {
12 - MainTab: undefined; 12 + MainTab: { screen?: keyof MainTabParamList };
13 RNDeviceInfoList: undefined; 13 RNDeviceInfoList: undefined;
14 WebviewScreen: { uri: string } | undefined; 14 WebviewScreen: { uri: string } | undefined;
15 RNLocalize: undefined; 15 RNLocalize: undefined;
16 RNCamera: undefined; 16 RNCamera: undefined;
17 RNCode: undefined; 17 RNCode: undefined;
  18 + ShortcutItem: { id?: string };
18 }; 19 };
19 20
20 export type MainTabScreenProps<RouteName extends keyof MainTabParamList> = { 21 export type MainTabScreenProps<RouteName extends keyof MainTabParamList> = {
1 declare type NVPair<V = string> = { name: string; value: V }; 1 declare type NVPair<V = string> = { name: string; value: V };
  2 +
  3 +declare type PromiseResult<T extends Promise<any>> = T extends Promise<infer U>
  4 + ? U
  5 + : never;
  6 +
  7 +declare type ResultOf<T extends () => any> = ReturnType<T> extends Promise<
  8 + infer R
  9 +>
  10 + ? R
  11 + : ReturnType<T>;
  1 +import { Linking } from 'react-native';
  2 +import { useEffect } from 'react';
  3 +import { rootNavigate, AllRouteNames } from './rootNavigation';
  4 +import URLParse from 'url-parse';
  5 +
  6 +export const appScheme = 'ngplay:';
  7 +export const universalLink = '';
  8 +
  9 +export function handleUrl(url?: string | null) {
  10 + if (!url) return;
  11 + const urlParse = new URLParse(url, true);
  12 + if (urlParse.protocol === appScheme) {
  13 + const routeName = urlParse.pathname.substring(1) as AllRouteNames;
  14 + const params = urlParse.query;
  15 + if (routeName) {
  16 + rootNavigate(routeName, params);
  17 + }
  18 + }
  19 +}
  20 +
  21 +export function handleUrlEvent(event: { url: string }) {
  22 + handleUrl(event.url);
  23 +}
  24 +
  25 +export function useDeepLinking() {
  26 + Linking.getInitialURL().then(handleUrl);
  27 + useEffect(() => {
  28 + Linking.addEventListener('url', handleUrlEvent);
  29 + return () => Linking.removeListener('url', handleUrlEvent);
  30 + }, []);
  31 +}
  1 +import * as React from 'react';
  2 +import { NavigationContainerRef } from '@react-navigation/native';
  3 +import { MainStackParamList, MainTabParamList } from '../type/Navigation';
  4 +
  5 +export const navigationRef = React.createRef<NavigationContainerRef>();
  6 +export const isMountedRef = React.createRef<
  7 + boolean
  8 +>() as React.MutableRefObject<boolean>;
  9 +
  10 +export function useMountedRef() {
  11 + isMountedRef.current = true;
  12 + return () => {
  13 + isMountedRef.current = false;
  14 + };
  15 +}
  16 +
  17 +export type ParamList = MainStackParamList & MainTabParamList;
  18 +export type AllRouteNames = keyof ParamList;
  19 +
  20 +export function rootNavigate<K extends AllRouteNames>(
  21 + name: K,
  22 + params: ParamList[K]
  23 +) {
  24 + if (isMountedRef.current && navigationRef.current) {
  25 + navigationRef.current.navigate(name, params);
  26 + }
  27 +}
  1 +import {
  2 + QuickActionEmitter,
  3 + QuickActionEventName,
  4 + ShortcutItem,
  5 + popInitialAction,
  6 +} from 'react-native-quick-actions';
  7 +import { useEffect } from 'react';
  8 +import { handleUrl } from './handleDeepLinking';
  9 +
  10 +export default function useQuickAction() {
  11 + useEffect(() => {
  12 + popInitialAction()
  13 + .then(item => {
  14 + handleUrl(item?.userInfo.url);
  15 + })
  16 + .catch(() => {});
  17 + const subscription = QuickActionEmitter.addListener(
  18 + QuickActionEventName,
  19 + (item?: ShortcutItem) => {
  20 + handleUrl(item?.userInfo.url);
  21 + }
  22 + );
  23 + return () => {
  24 + subscription.remove();
  25 + };
  26 + }, []);
  27 +}