mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-05-18 21:35:46 -04:00
Add alerts and loading screen to photo sharing functionality (#1261)
* Add alerts and loading screen to photo sharing; closes #1203 * Prevent getting stuck on PhotoSharing when you back out of ObsEdit --------- Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
This commit is contained in:
committed by
GitHub
parent
33a5ab2f39
commit
46748bac97
125
src/components/PhotoSharing.js
Normal file
125
src/components/PhotoSharing.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// @flow
|
||||
|
||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||
import { ActivityAnimation, ViewWrapper } from "components/SharedComponents";
|
||||
import { View } from "components/styledComponents";
|
||||
import type { Node } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Alert, Platform } from "react-native";
|
||||
import Observation from "realmModels/Observation";
|
||||
import { log } from "sharedHelpers/logger";
|
||||
import useStore from "stores/useStore";
|
||||
|
||||
const logger = log.extend( "PhotoSharing" );
|
||||
|
||||
const PhotoSharing = ( ): Node => {
|
||||
const navigation = useNavigation( );
|
||||
const { params } = useRoute( );
|
||||
const { item } = params;
|
||||
const resetStore = useStore( state => state.resetStore );
|
||||
const setObservations = useStore( state => state.setObservations );
|
||||
const setPhotoImporterState = useStore( state => state.setPhotoImporterState );
|
||||
const [navigationHandled, setNavigationHandled] = useState( null );
|
||||
|
||||
const createObservationAndNavToObsEdit = useCallback( async photoUris => {
|
||||
try {
|
||||
const newObservation = await Observation.createObservationWithPhotos( photoUris );
|
||||
setObservations( [newObservation] );
|
||||
navigation.navigate( "ObsEdit" );
|
||||
} catch ( e ) {
|
||||
Alert.alert(
|
||||
"Photo sharing failed: couldn't create new observation:",
|
||||
e
|
||||
);
|
||||
}
|
||||
}, [
|
||||
navigation,
|
||||
setObservations
|
||||
] );
|
||||
|
||||
useEffect( ( ) => {
|
||||
const { mimeType, data } = item;
|
||||
|
||||
if ( Platform.OS === "android" && !mimeType.startsWith( "image/" ) ) {
|
||||
Alert.alert( "Android photo share failed: not an image file" );
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to ObsEdit screen (new observation, with shared photos).
|
||||
logger.info( "calling resetStore" );
|
||||
resetStore( );
|
||||
|
||||
// Create a new observation with multiple shared photos (one or more)
|
||||
let photoUris:any[];
|
||||
|
||||
// data is returned as a string for a single photo on Android
|
||||
// and an object with an array of data strings on iOS, i.e.
|
||||
// [{"data": "", "", "mimeType": "image/jpeg]
|
||||
if ( Array.isArray( data ) ) {
|
||||
photoUris = data;
|
||||
} else {
|
||||
photoUris = [data];
|
||||
}
|
||||
|
||||
logger.info( "photoUris: ", photoUris );
|
||||
|
||||
if ( Platform.OS === "android" ) {
|
||||
photoUris = photoUris.map( x => ( { image: { uri: x } } ) );
|
||||
} else {
|
||||
photoUris = photoUris
|
||||
.filter( x => x.mimeType && x.mimeType.startsWith( "image/" ) )
|
||||
.map( x => ( { image: { uri: x.data } } ) );
|
||||
}
|
||||
|
||||
if ( photoUris.length === 1 ) {
|
||||
// Only one photo - go to ObsEdit directly
|
||||
logger.info( "creating observation in useShare with photoUris: ", photoUris );
|
||||
createObservationAndNavToObsEdit( photoUris );
|
||||
} else {
|
||||
// Go to GroupPhotos screen
|
||||
setPhotoImporterState( {
|
||||
galleryUris: photoUris.map( x => x.image.uri ),
|
||||
groupedPhotos: photoUris.map( photo => ( {
|
||||
photos: [photo]
|
||||
} ) )
|
||||
} );
|
||||
navigation.navigate( "CameraNavigator", { screen: "GroupPhotos" } );
|
||||
}
|
||||
}, [
|
||||
createObservationAndNavToObsEdit,
|
||||
item,
|
||||
navigation,
|
||||
resetStore,
|
||||
setObservations,
|
||||
setPhotoImporterState
|
||||
] );
|
||||
|
||||
// When the user leaves this screen, we record the fact that navigation was handled...
|
||||
useEffect( ( ) => {
|
||||
const unsubscribe = navigation.addListener( "blur", ( ) => {
|
||||
setNavigationHandled( true );
|
||||
} );
|
||||
return unsubscribe;
|
||||
}, [navigation] );
|
||||
// ...and if they focus on this screen again, that means they backed out of
|
||||
// obs edit and need to back to the previous screen in the nav
|
||||
useEffect( ( ) => {
|
||||
const unsubscribe = navigation.addListener( "focus", ( ) => {
|
||||
if ( navigationHandled ) navigation.goBack( );
|
||||
} );
|
||||
return unsubscribe;
|
||||
}, [
|
||||
navigation,
|
||||
navigationHandled
|
||||
] );
|
||||
|
||||
return (
|
||||
<ViewWrapper testID="PhotoSharing">
|
||||
<View className="flex-1 w-full h-full justify-center items-center">
|
||||
<ActivityAnimation />
|
||||
</View>
|
||||
</ViewWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default PhotoSharing;
|
||||
@@ -4,6 +4,7 @@
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
import LocationPickerContainer from "components/LocationPicker/LocationPickerContainer";
|
||||
import ObsEdit from "components/ObsEdit/ObsEdit";
|
||||
import PhotoSharing from "components/PhotoSharing";
|
||||
import { Heading4 } from "components/SharedComponents";
|
||||
import SuggestionsContainer from "components/Suggestions/SuggestionsContainer";
|
||||
import TaxonSearch from "components/Suggestions/TaxonSearch";
|
||||
@@ -64,6 +65,11 @@ const SharedStackScreens = ( ): Node => (
|
||||
component={TaxonDetails}
|
||||
options={hideHeader}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="PhotoSharing"
|
||||
component={PhotoSharing}
|
||||
options={hideHeader}
|
||||
/>
|
||||
</Stack.Group>
|
||||
);
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import ShareMenu from "react-native-share-menu";
|
||||
import Observation from "realmModels/Observation";
|
||||
import useStore from "stores/useStore";
|
||||
|
||||
import { log } from "../../react-native-logs.config";
|
||||
|
||||
type SharedItem = {
|
||||
mimeType: string,
|
||||
@@ -15,90 +10,38 @@ type SharedItem = {
|
||||
extraData: any,
|
||||
};
|
||||
|
||||
const logger = log.extend( "useShare" );
|
||||
|
||||
const useShare = ( ): void => {
|
||||
const navigation = useNavigation( );
|
||||
const resetStore = useStore( state => state.resetStore );
|
||||
const setObservations = useStore( state => state.setObservations );
|
||||
const setPhotoImporterState = useStore( state => state.setPhotoImporterState );
|
||||
|
||||
const handleShare = useCallback( async ( item: ?SharedItem ) => {
|
||||
if ( !item ) {
|
||||
logger.info( "no item" );
|
||||
// user hasn't shared any items
|
||||
return;
|
||||
}
|
||||
|
||||
const { mimeType, data } = item;
|
||||
|
||||
if ( ( !data )
|
||||
|| (
|
||||
( Platform.OS === "android" )
|
||||
&& ( ( !mimeType ) || ( !mimeType.startsWith( "image/" ) ) )
|
||||
)
|
||||
) {
|
||||
logger.info( "no data or not an image" );
|
||||
if ( !mimeType && !data ) {
|
||||
// user hasn't shared any images
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to ObsEdit screen (new observation, with shared photos).
|
||||
logger.info( "calling resetStore" );
|
||||
resetStore( );
|
||||
// show user a loading animation screen (like PhotoGallery)
|
||||
// while observations are created
|
||||
navigation.navigate( "PhotoSharing", { item } );
|
||||
}, [navigation] );
|
||||
|
||||
// Create a new observation with multiple shared photos (one or more)
|
||||
|
||||
let photoUris:any[];
|
||||
|
||||
if ( Array.isArray( data ) ) {
|
||||
photoUris = data;
|
||||
} else {
|
||||
photoUris = [data];
|
||||
}
|
||||
|
||||
logger.info( "photoUris: ", photoUris );
|
||||
|
||||
if ( Platform.OS === "android" ) {
|
||||
photoUris = photoUris.map( x => ( { image: { uri: x } } ) );
|
||||
} else {
|
||||
photoUris = photoUris
|
||||
.filter( x => x.mimeType && x.mimeType.startsWith( "image/" ) )
|
||||
.map( x => ( { image: { uri: x.data } } ) );
|
||||
}
|
||||
|
||||
if ( photoUris.length === 1 ) {
|
||||
// Only one photo - go to ObsEdit directly
|
||||
logger.info( "creating observations in useShare with photoUris: ", photoUris );
|
||||
const newObservations = await Promise.all( [{ photos: photoUris }].map(
|
||||
( { photos } ) => Observation.createObservationWithPhotos( photos )
|
||||
) );
|
||||
setObservations( newObservations );
|
||||
|
||||
navigation.navigate( "ObsEdit" );
|
||||
} else {
|
||||
// Go to GroupPhotos screen
|
||||
setPhotoImporterState( {
|
||||
galleryUris: photoUris.map( x => x.image.uri ),
|
||||
groupedPhotos: photoUris.map( photo => ( {
|
||||
photos: [photo]
|
||||
} ) )
|
||||
} );
|
||||
navigation.navigate( "CameraNavigator", { screen: "GroupPhotos" } );
|
||||
}
|
||||
}, [resetStore, setObservations, navigation, setPhotoImporterState] );
|
||||
|
||||
useEffect( () => {
|
||||
useEffect( ( ) => {
|
||||
ShareMenu.getInitialShare( handleShare );
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [] );
|
||||
}, [handleShare] );
|
||||
|
||||
useEffect( () => {
|
||||
useEffect( ( ) => {
|
||||
const listener = ShareMenu.addNewShareListener( handleShare );
|
||||
|
||||
return () => {
|
||||
listener?.remove();
|
||||
return ( ) => {
|
||||
listener?.remove( );
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [] );
|
||||
}, [handleShare] );
|
||||
};
|
||||
|
||||
export default useShare;
|
||||
|
||||
Reference in New Issue
Block a user