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:
Amanda Bullington
2024-03-15 11:51:26 +11:00
committed by GitHub
parent 33a5ab2f39
commit 46748bac97
3 changed files with 144 additions and 70 deletions

View 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;

View File

@@ -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>
);

View File

@@ -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;