Add share intent

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-29 19:14:19 +03:00
parent a0b7a6310b
commit f33fe2c54b
13 changed files with 224 additions and 26 deletions

View File

@@ -5,7 +5,7 @@ import { ParamListBase, useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { pick } from 'react-native-document-picker';
import { useDeviceOrientation } from '@react-native-community/hooks';
import { ROUTE } from '../types';
import { documentPickerResponseToAddMemeFile, ROUTE } from '../types';
import { allowedMimeTypes, noOp } from '../utilities';
const floatingActionButtonStyles = StyleSheet.create({
@@ -54,22 +54,16 @@ const FloatingActionButton = ({ visible = true }: { visible?: boolean }) => {
label: 'Tag',
onPress: () => navigate(ROUTE.ADD_TAG),
},
{
icon: 'note-text',
label: 'Text',
onPress: () => {
throw new Error('Not yet implemented');
},
},
]}
onStateChange={({ open }) => setState(open)}
onPress={async () => {
if (!state) return;
const files = await pick({
const response = await pick({
type: allowedMimeTypes,
allowMultiSelection: true,
}).catch(noOp);
if (!files) return;
if (!response) return;
const files = documentPickerResponseToAddMemeFile(response);
navigate(ROUTE.ADD_MEME, { files });
}}
style={

View File

@@ -1,5 +1,9 @@
import React from 'react';
import { NavigationContainer as NavigationContainerBase } from '@react-navigation/native';
import React, { useCallback, useEffect } from 'react';
import {
NavigationContainer as NavigationContainerBase,
ParamListBase,
createNavigationContainerRef,
} from '@react-navigation/native';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
@@ -20,8 +24,14 @@ import {
FloatingActionButton,
HideableBottomNavigationBar,
} from './components';
import { ROUTE, RootStackParamList } from './types';
import {
ROUTE,
RootStackParamList,
SharedItem,
sharedItemToAddMemeFile,
} from './types';
import { RootState } from './state';
import ShareMenu from 'react-native-share-menu';
const TabNavigator = () => {
const navVisible = useSelector(
@@ -84,10 +94,28 @@ const TabNavigator = () => {
const NavigationContainer = () => {
const theme = useTheme();
const navigationRef = createNavigationContainerRef<ParamListBase>();
const handleShare = useCallback(
(item: SharedItem | undefined) => {
if (!item) return;
const files = sharedItemToAddMemeFile(item);
navigationRef.current?.navigate(ROUTE.ADD_MEME, { files });
},
[navigationRef],
);
useEffect(() => {
ShareMenu.getInitialShare(handleShare);
const listener = ShareMenu.addNewShareListener(handleShare);
return () => listener.remove();
}, [handleShare]);
const StackNavigatorBase = createNativeStackNavigator<RootStackParamList>();
return (
<NavigationContainerBase
ref={navigationRef}
theme={theme.dark ? darkNavigationTheme : lightNavigationTheme}>
<StackNavigatorBase.Navigator
screenOptions={{

View File

@@ -9,8 +9,12 @@ import { AndroidScoped, FileSystem } from 'react-native-file-access';
import { useSelector } from 'react-redux';
import { extension } from 'react-native-mime-types';
import { useDeviceOrientation } from '@react-native-community/hooks';
import { DocumentPickerResponse, pick } from 'react-native-document-picker';
import { ROUTE, RootStackParamList } from '../../types';
import { pick } from 'react-native-document-picker';
import {
documentPickerResponseToAddMemeFile,
ROUTE,
RootStackParamList,
} from '../../types';
import { Meme, Tag } from '../../database';
import { RootState } from '../../state';
import {
@@ -39,9 +43,7 @@ const AddMeme = ({
const isLastFile = index === files.current.length - 1;
const [memeUri, setMemeUri] = useState(file.current.uri);
const [memeFilename, setMemeFilename] = useState(
file.current.name ?? undefined,
);
const [memeFilename, setMemeFilename] = useState(file.current.filename);
const [memeError, setMemeError] = useState<Error>();
const [memeTitle, setMemeTitle] = useState(validateMemeTitle('New Meme'));
const [memeIsFavorite, setMemeIsFavorite] = useState(false);
@@ -105,7 +107,7 @@ const AddMeme = ({
file.current = files.current[index + 1];
setMemeUri(file.current.uri);
setMemeFilename(file.current.name ?? undefined);
setMemeFilename(file.current.filename);
setMemeTitle(validateMemeTitle('New Meme'));
setMemeIsFavorite(false);
setMemeTags(new Map<string, Tag>());
@@ -117,14 +119,16 @@ const AddMeme = ({
setIsSavingAndAddingMore(false);
setIndex(0);
files.current = (await pick({
const response = await pick({
type: allowedMimeTypes,
allowMultiSelection: true,
}).catch(goBack)) as DocumentPickerResponse[];
}).catch(goBack);
if (!response) return;
files.current = documentPickerResponseToAddMemeFile(response);
file.current = files.current[0];
setMemeUri(file.current.uri);
setMemeFilename(file.current.name ?? undefined);
setMemeFilename(file.current.filename);
setMemeTitle(validateMemeTitle('New Meme'));
setMemeIsFavorite(false);
setMemeTags(new Map<string, Tag>());

View File

@@ -1,5 +1,11 @@
export { type Dimensions } from './dimensions';
export { ROUTE, type RootStackParamList } from './route';
export {
ROUTE,
type RootStackParamList,
documentPickerResponseToAddMemeFile,
sharedItemToAddMemeFile,
} from './route';
export { type SharedItem } from './share';
export {
MEME_SORT,
memesSortQuery,

View File

@@ -1,4 +1,6 @@
import { DocumentPickerResponse } from 'react-native-document-picker';
import { getFilenameFromUri, guessMimeType } from '../utilities';
import { SharedItem } from './share';
enum ROUTE {
MAIN = 'Main',
@@ -17,8 +19,48 @@ interface MemeViewRouteParams {
index: number;
}
interface AddMemeFile {
uri: string;
filename: string;
type?: string;
}
const documentPickerResponseToAddMemeFile = (
response: DocumentPickerResponse[],
): AddMemeFile[] => {
return response.map(item => {
const { uri, name, type } = item;
return {
uri,
filename: name ?? getFilenameFromUri(uri),
type: type ?? guessMimeType(uri),
};
});
};
const sharedItemToAddMemeFile = (item: SharedItem): AddMemeFile[] => {
const { data, mimeType } = item;
if (typeof data === 'string') {
return [
{
uri: data,
filename: getFilenameFromUri(data),
type: mimeType,
},
];
}
return data.map(uri => ({
uri,
filename: getFilenameFromUri(uri),
type: guessMimeType(uri),
}));
};
interface AddMemeRouteParams {
files: DocumentPickerResponse[];
files: AddMemeFile[];
}
interface EditMemeRouteParams {
@@ -44,4 +86,9 @@ interface RootStackParamList {
[ROUTE.EDIT_TAG]: EditTagRouteParams;
}
export { ROUTE, type RootStackParamList };
export {
ROUTE,
type RootStackParamList,
documentPickerResponseToAddMemeFile,
sharedItemToAddMemeFile,
};

7
src/types/share.ts Normal file
View File

@@ -0,0 +1,7 @@
interface SharedItem {
data: string | string[];
mimeType: string;
extraData?: object;
}
export { type SharedItem };

View File

@@ -25,9 +25,32 @@ const getMemeType = (mimeType: string): MEME_TYPE | undefined => {
}
};
const guessMimeType = (filename: string): string | undefined => {
const extension = filename.split('.').pop()?.toLowerCase();
switch (extension) {
case 'bmp': {
return 'image/bmp';
}
case 'jpg':
case 'jpeg': {
return 'image/jpeg';
}
case 'png': {
return 'image/png';
}
case 'webp': {
return 'image/webp';
}
case 'gif': {
return 'image/gif';
}
}
};
export {
allowedImageMimeTypes,
allowedGifMimeTypes,
allowedMimeTypes,
getMemeType,
guessMimeType,
};

View File

@@ -14,6 +14,7 @@ export {
allowedGifMimeTypes,
allowedMimeTypes,
getMemeType,
guessMimeType,
} from './filesystem';
export { getSortIcon, getViewIcon } from './icon';
export {
@@ -26,6 +27,7 @@ export {
export {
isPermissionForPath,
clearPermissions,
getFilenameFromUri,
} from './permissions';
export { deleteTag } from './tag';
export {

View File

@@ -1,3 +1,4 @@
import { Util } from 'react-native-file-access';
import {
getPersistedUriPermissions,
releasePersistableUriPermission,
@@ -16,4 +17,8 @@ const clearPermissions = async (excepts: string[] = []) => {
});
};
export { isPermissionForPath, clearPermissions };
const getFilenameFromUri = (uri: string) => {
return Util.basename(uri.replaceAll('%2F', '/'));
};
export { isPermissionForPath, clearPermissions, getFilenameFromUri };