mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
473 Create hold to delete photo interaction (#479)
* Create hold to delete photo interaction * Delete w/o modal * Clean code * move delete back to review * lint --------- Co-authored-by: Johannes Klein <johannes.t.klein@gmail.com>
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import MediaViewerModal from "components/MediaViewer/MediaViewerModal";
|
||||
import DeletePhotoDialog from "components/SharedComponents/DeletePhotoDialog";
|
||||
import PhotoCarousel from "components/SharedComponents/PhotoCarousel";
|
||||
import { Text, View } from "components/styledComponents";
|
||||
import { t } from "i18next";
|
||||
@@ -23,8 +21,6 @@ const PhotoPreview = ( {
|
||||
deviceOrientation
|
||||
}: Props ): Node => {
|
||||
const { deletePhotoFromObservation } = useContext( ObsEditContext );
|
||||
const [deleteDialogVisible, setDeleteDialogVisible] = useState( false );
|
||||
const [photoUriToDelete, setPhotoUriToDelete] = useState( null );
|
||||
const [initialPhotoSelected, setInitialPhotoSelected] = useState( null );
|
||||
const [mediaViewerVisible, setMediaViewerVisible] = useState( false );
|
||||
|
||||
@@ -36,16 +32,8 @@ const PhotoPreview = ( {
|
||||
showModal( );
|
||||
};
|
||||
|
||||
const showDialog = ( ) => setDeleteDialogVisible( true );
|
||||
|
||||
const hideDialog = ( ) => {
|
||||
setPhotoUriToDelete( null );
|
||||
setDeleteDialogVisible( false );
|
||||
};
|
||||
|
||||
const handleDelete = photoUri => {
|
||||
setPhotoUriToDelete( photoUri );
|
||||
showDialog( );
|
||||
const deletePhoto = photoUri => {
|
||||
deletePhotoFromObservation( photoUri, photoUris, setPhotoUris );
|
||||
};
|
||||
|
||||
const emptyDescription = ( ) => (
|
||||
@@ -54,18 +42,8 @@ const PhotoPreview = ( {
|
||||
</Text>
|
||||
);
|
||||
|
||||
const deletePhoto = ( ) => {
|
||||
deletePhotoFromObservation( photoUriToDelete, photoUris, setPhotoUris );
|
||||
hideDialog( );
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeletePhotoDialog
|
||||
deleteDialogVisible={deleteDialogVisible}
|
||||
deletePhoto={deletePhoto}
|
||||
hideDialog={hideDialog}
|
||||
/>
|
||||
<MediaViewerModal
|
||||
mediaViewerVisible={mediaViewerVisible}
|
||||
hideModal={hideModal}
|
||||
@@ -75,10 +53,10 @@ const PhotoPreview = ( {
|
||||
/>
|
||||
<View className="bg-black h-32">
|
||||
<PhotoCarousel
|
||||
deletePhoto={deletePhoto}
|
||||
photoUris={photoUris}
|
||||
emptyComponent={emptyDescription}
|
||||
containerStyle="camera"
|
||||
handleDelete={handleDelete}
|
||||
setSelectedPhotoIndex={handleSelection}
|
||||
savingPhoto={savingPhoto}
|
||||
deviceOrientation={deviceOrientation}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
// @flow
|
||||
|
||||
import { Image, Pressable, View } from "components/styledComponents";
|
||||
import classnames from "classnames";
|
||||
import { ImageBackground, Pressable, View } from "components/styledComponents";
|
||||
import type { Node } from "react";
|
||||
import React from "react";
|
||||
import React, {
|
||||
useEffect, useState
|
||||
} from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList
|
||||
} from "react-native";
|
||||
import DeviceInfo from "react-native-device-info";
|
||||
import LinearGradient from "react-native-linear-gradient";
|
||||
import Modal from "react-native-modal";
|
||||
import { IconButton, useTheme } from "react-native-paper";
|
||||
import Icon from "react-native-vector-icons/MaterialIcons";
|
||||
import colors from "styles/tailwindColors";
|
||||
@@ -18,11 +23,11 @@ type Props = {
|
||||
setSelectedPhotoIndex?: Function,
|
||||
selectedPhotoIndex?: number,
|
||||
containerStyle?: string,
|
||||
handleDelete?: Function,
|
||||
savingPhoto?: boolean,
|
||||
handleAddEvidence?: Function,
|
||||
showAddButton?: boolean,
|
||||
deviceOrientation?: string
|
||||
deviceOrientation?: string,
|
||||
deletePhoto?: Function
|
||||
}
|
||||
|
||||
const PhotoCarousel = ( {
|
||||
@@ -31,41 +36,28 @@ const PhotoCarousel = ( {
|
||||
setSelectedPhotoIndex,
|
||||
selectedPhotoIndex,
|
||||
containerStyle,
|
||||
handleDelete,
|
||||
savingPhoto,
|
||||
handleAddEvidence,
|
||||
showAddButton = false,
|
||||
deviceOrientation
|
||||
|
||||
deviceOrientation,
|
||||
deletePhoto
|
||||
}: Props ): Node => {
|
||||
const theme = useTheme( );
|
||||
const [deletePhotoMode, setDeletePhotoMode] = useState( false );
|
||||
const imageClass = "h-16 w-16 justify-center mx-1.5 rounded-lg";
|
||||
const isTablet = DeviceInfo.isTablet();
|
||||
|
||||
const renderDeleteButton = photoUri => (
|
||||
<IconButton
|
||||
icon="trash-can"
|
||||
iconColor={theme.colors.onPrimary}
|
||||
containerColor={theme.colors.primary}
|
||||
size={30}
|
||||
onPress={( ) => {
|
||||
if ( !handleDelete ) { return; }
|
||||
handleDelete( photoUri );
|
||||
}}
|
||||
className="absolute top-10 right-0"
|
||||
/>
|
||||
);
|
||||
|
||||
const renderSkeleton = ( ) => {
|
||||
if ( savingPhoto ) {
|
||||
return (
|
||||
<View className={`${imageClass} bg-midGray mt-12`}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
);
|
||||
useEffect( () => {
|
||||
if ( photoUris.length === 0 && deletePhotoMode ) {
|
||||
setDeletePhotoMode( false );
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}, [photoUris.length, deletePhotoMode] );
|
||||
|
||||
const renderSkeleton = ( ) => ( savingPhoto ? (
|
||||
<View className={`${imageClass} bg-midGray mt-12`}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
) : null );
|
||||
|
||||
const renderPhotoOrEvidenceButton = ( { item, index } ) => {
|
||||
if ( index === photoUris.length ) {
|
||||
@@ -79,49 +71,55 @@ const PhotoCarousel = ( {
|
||||
);
|
||||
}
|
||||
|
||||
const setClassName = ( ) => {
|
||||
let className = imageClass;
|
||||
if ( containerStyle === "camera" ) {
|
||||
className += " mt-12";
|
||||
} else {
|
||||
className += " mt-6";
|
||||
}
|
||||
if ( selectedPhotoIndex === index ) {
|
||||
className += " border border-selectionGreen border-4";
|
||||
}
|
||||
return className;
|
||||
};
|
||||
|
||||
const imageClassName = () => {
|
||||
let className = "w-fit h-full ";
|
||||
if ( deviceOrientation && !isTablet ) {
|
||||
if ( deviceOrientation === "portrait" ) {
|
||||
className += "rotate-0";
|
||||
} else if ( deviceOrientation === "landscapeLeft" ) {
|
||||
className += "-rotate-90";
|
||||
} else if ( deviceOrientation === "landscapeRight" ) {
|
||||
className += "rotate-90";
|
||||
}
|
||||
}
|
||||
return className;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Pressable
|
||||
onLongPress={( ) => {
|
||||
if ( deletePhoto ) {
|
||||
setDeletePhotoMode( mode => !mode );
|
||||
}
|
||||
}}
|
||||
onPress={( ) => {
|
||||
if ( setSelectedPhotoIndex ) {
|
||||
if ( deletePhotoMode && deletePhoto ) {
|
||||
deletePhoto( item );
|
||||
} else if ( setSelectedPhotoIndex ) {
|
||||
setSelectedPhotoIndex( index );
|
||||
}
|
||||
}}
|
||||
className={setClassName( )}
|
||||
className={classnames( imageClass, {
|
||||
"mt-12": containerStyle === "camera",
|
||||
"mt-6": containerStyle !== "camera",
|
||||
"border border-selectionGreen border-4":
|
||||
selectedPhotoIndex === index
|
||||
} )}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: item }}
|
||||
testID="ObsEdit.photo"
|
||||
className={imageClassName()}
|
||||
/>
|
||||
{( containerStyle === "camera" ) && renderDeleteButton( item )}
|
||||
<View className="rounded-lg overflow-hidden">
|
||||
<ImageBackground
|
||||
source={{ uri: item }}
|
||||
testID="ObsEdit.photo"
|
||||
className={classnames( "w-fit h-full flex items-center justify-center", {
|
||||
"rotate-0": deviceOrientation === "portrait" && !isTablet,
|
||||
"-rotate-90": deviceOrientation === "landscapeLeft" && !isTablet,
|
||||
"rotate-90": deviceOrientation === "landscapeRight" && !isTablet
|
||||
} )}
|
||||
>
|
||||
{deletePhotoMode && (
|
||||
<LinearGradient
|
||||
className="bg-transparent absolute inset-0"
|
||||
colors={["rgba(0, 0, 0, 0.5)", "rgba(0, 0, 0, 0.5)"]}
|
||||
/>
|
||||
)}
|
||||
{( containerStyle === "camera" && deletePhotoMode ) && (
|
||||
<IconButton
|
||||
icon="trash-can"
|
||||
mode="contained-tonal"
|
||||
iconColor={theme.colors.onPrimary}
|
||||
containerColor="rgba(0, 0, 0, 0.5)"
|
||||
size={30}
|
||||
/>
|
||||
)}
|
||||
</ImageBackground>
|
||||
</View>
|
||||
</Pressable>
|
||||
{index === photoUris.length - 1 && renderSkeleton( )}
|
||||
</>
|
||||
@@ -131,7 +129,7 @@ const PhotoCarousel = ( {
|
||||
const data = [...photoUris];
|
||||
if ( showAddButton ) data.push( "add" );
|
||||
|
||||
return (
|
||||
const photoPreviewsList = (
|
||||
<FlatList
|
||||
data={data}
|
||||
renderItem={renderPhotoOrEvidenceButton}
|
||||
@@ -139,6 +137,20 @@ const PhotoCarousel = ( {
|
||||
ListEmptyComponent={savingPhoto ? renderSkeleton( ) : emptyComponent}
|
||||
/>
|
||||
);
|
||||
|
||||
return deletePhotoMode ? (
|
||||
<Modal
|
||||
visible
|
||||
onBackdropPress={() => setDeletePhotoMode( false )}
|
||||
backdropOpacity={0}
|
||||
// eslint-disable-next-line react-native/no-inline-styles
|
||||
style={{ margin: 0 }}
|
||||
>
|
||||
<View className="absolute top-0">
|
||||
{photoPreviewsList}
|
||||
</View>
|
||||
</Modal>
|
||||
) : photoPreviewsList;
|
||||
};
|
||||
|
||||
export default PhotoCarousel;
|
||||
|
||||
Reference in New Issue
Block a user