mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 14:08:56 -05:00
204 lines
5.7 KiB
JavaScript
204 lines
5.7 KiB
JavaScript
// @flow
|
|
|
|
import { useNavigation } from "@react-navigation/native";
|
|
import { MAX_PHOTOS_ALLOWED } from "components/Camera/StandardCamera/StandardCamera";
|
|
import {
|
|
Body2,
|
|
Button,
|
|
ButtonBar,
|
|
CustomFlashList,
|
|
FloatingActionBar,
|
|
INatIcon,
|
|
INatIconButton,
|
|
} from "components/SharedComponents";
|
|
import ViewWrapper from "components/SharedComponents/ViewWrapper";
|
|
import { Pressable, View } from "components/styledComponents";
|
|
import { t } from "i18next";
|
|
import type { Node } from "react";
|
|
import React, { useCallback, useMemo, useState } from "react";
|
|
import { useGridLayout } from "sharedHooks";
|
|
import colors from "styles/tailwindColors";
|
|
|
|
import GroupPhotoImage from "./GroupPhotoImage";
|
|
|
|
type Props = {
|
|
combinePhotos: Function,
|
|
groupedPhotos: Object[],
|
|
isCreatingObservations?: boolean,
|
|
navBasedOnUserSettings: Function,
|
|
removePhotos: Function,
|
|
selectedObservations: Object[],
|
|
selectObservationPhotos: Function,
|
|
separatePhotos: Function,
|
|
totalPhotos: number
|
|
}
|
|
|
|
const GroupPhotos = ( {
|
|
combinePhotos,
|
|
groupedPhotos,
|
|
isCreatingObservations,
|
|
navBasedOnUserSettings,
|
|
removePhotos,
|
|
selectedObservations,
|
|
selectObservationPhotos,
|
|
separatePhotos,
|
|
totalPhotos,
|
|
}: Props ): Node => {
|
|
const navigation = useNavigation( );
|
|
const {
|
|
flashListStyle,
|
|
gridItemStyle,
|
|
gridItemWidth,
|
|
numColumns,
|
|
} = useGridLayout( );
|
|
const [buttonBarHeight, setButtonBarHeight] = useState( null );
|
|
const extractKey = ( item, index ) => ( item.empty
|
|
? "empty"
|
|
: `${item.photos[0].uri}${index}` );
|
|
|
|
const noObsSelected = selectedObservations.length === 0;
|
|
const oneObsSelected = selectedObservations.length === 1;
|
|
const obsWithMultiplePhotosSelected
|
|
= selectedObservations?.[0]?.photos?.length > 1;
|
|
|
|
const renderImage = useCallback( item => (
|
|
<GroupPhotoImage
|
|
item={item}
|
|
selectedObservations={selectedObservations}
|
|
selectObservationPhotos={selectObservationPhotos}
|
|
style={gridItemStyle}
|
|
/>
|
|
), [gridItemStyle, selectedObservations, selectObservationPhotos] );
|
|
|
|
const addPhotos = useCallback( () => {
|
|
navigation.navigate( "NoBottomTabStackNavigator", {
|
|
screen: "PhotoLibrary",
|
|
params: { fromGroupPhotos: true },
|
|
} );
|
|
}, [navigation] );
|
|
|
|
// $FlowIgnore
|
|
const renderItem = useCallback( ( { item } ) => {
|
|
if ( item.empty ) {
|
|
return (
|
|
<Pressable
|
|
accessibilityRole="button"
|
|
onPress={addPhotos}
|
|
className="rounded-[15px] justify-center items-center"
|
|
// Sorry, couldn't get this to work with tailwind
|
|
// eslint-disable-next-line react-native/no-inline-styles
|
|
style={[gridItemStyle, {
|
|
borderWidth: 4,
|
|
borderStyle: "dashed",
|
|
borderColor: colors.mediumGray,
|
|
}]}
|
|
>
|
|
<INatIcon name="plus" size={50} color={colors.mediumGray} />
|
|
</Pressable>
|
|
);
|
|
}
|
|
// $FlowIgnore
|
|
return renderImage( item );
|
|
}, [gridItemStyle, renderImage, addPhotos] );
|
|
|
|
const renderHeader = ( ) => (
|
|
<View className="m-5">
|
|
<Body2>{t( "Group-photos-onboarding" )}</Body2>
|
|
</View>
|
|
);
|
|
|
|
const onLayout = event => {
|
|
const {
|
|
height,
|
|
} = event.nativeEvent.layout;
|
|
setButtonBarHeight( height );
|
|
};
|
|
|
|
const data = useMemo( ( ) => {
|
|
const newData = [].concat( groupedPhotos );
|
|
if ( totalPhotos < MAX_PHOTOS_ALLOWED ) {
|
|
newData.push( { empty: true } );
|
|
}
|
|
return newData;
|
|
}, [groupedPhotos, totalPhotos] );
|
|
|
|
const extraData = {
|
|
selectedObservations,
|
|
gridItemWidth,
|
|
};
|
|
|
|
return (
|
|
<ViewWrapper>
|
|
<CustomFlashList
|
|
ListHeaderComponent={renderHeader}
|
|
contentContainerStyle={flashListStyle}
|
|
data={data}
|
|
extraData={extraData}
|
|
key={numColumns}
|
|
keyExtractor={extractKey}
|
|
numColumns={numColumns}
|
|
renderItem={renderItem}
|
|
testID="GroupPhotos.list"
|
|
/>
|
|
<FloatingActionBar
|
|
show={selectedObservations.length > 0 && typeof buttonBarHeight === "number"}
|
|
position="bottomStart"
|
|
containerClass="ml-[15px] rounded-md"
|
|
footerHeight={buttonBarHeight}
|
|
>
|
|
<View className="rounded-md overflow-hidden flex-row">
|
|
<INatIconButton
|
|
icon="combine"
|
|
mode="contained"
|
|
size={20}
|
|
color={colors.white}
|
|
backgroundColor={colors.darkGray}
|
|
className="m-4"
|
|
accessibilityLabel={t( "Combine-Photos" )}
|
|
disabled={noObsSelected || oneObsSelected}
|
|
onPress={combinePhotos}
|
|
/>
|
|
<INatIconButton
|
|
icon="separate"
|
|
mode="contained"
|
|
size={20}
|
|
color={colors.white}
|
|
backgroundColor={colors.darkGray}
|
|
className="m-4"
|
|
accessibilityLabel={t( "Separate-Photos" )}
|
|
disabled={!obsWithMultiplePhotosSelected}
|
|
onPress={separatePhotos}
|
|
/>
|
|
<INatIconButton
|
|
icon="trash-outline"
|
|
mode="contained"
|
|
size={20}
|
|
color={colors.white}
|
|
backgroundColor={colors.warningRed}
|
|
className="m-4"
|
|
accessibilityLabel={t( "Remove-Photos" )}
|
|
disabled={noObsSelected}
|
|
onPress={removePhotos}
|
|
/>
|
|
</View>
|
|
</FloatingActionBar>
|
|
<ButtonBar
|
|
sticky
|
|
containerClass="items-center z-50 bg-white"
|
|
onLayout={onLayout}
|
|
>
|
|
<Button
|
|
className="max-w-[500px] w-full"
|
|
level="focus"
|
|
text={t( "IMPORT-X-OBSERVATIONS", { count: groupedPhotos.length } )}
|
|
onPress={navBasedOnUserSettings}
|
|
testID="GroupPhotos.next"
|
|
loading={isCreatingObservations}
|
|
/>
|
|
</ButtonBar>
|
|
</ViewWrapper>
|
|
);
|
|
};
|
|
|
|
export default GroupPhotos;
|