mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-05-06 14:46:20 -04:00
* Add sentinel file creation, logging, and deletion logic to AICamera * Log to server and delete sentinel files after logged; fix location fetch logging * Use logger.error, not logger.debug, to log to grafana
196 lines
6.9 KiB
JavaScript
196 lines
6.9 KiB
JavaScript
// eslint-disable-next-line
|
|
import { Realm } from "@realm/react";
|
|
import _ from "lodash";
|
|
import Photo from "realmModels/Photo";
|
|
import Sound from "realmModels/Sound";
|
|
|
|
const DEFAULT_STATE = {
|
|
cameraRollUris: [],
|
|
currentObservation: null,
|
|
currentObservationIndex: 0,
|
|
evidenceToAdd: [],
|
|
galleryUris: [],
|
|
groupedPhotos: [],
|
|
observations: [],
|
|
// Track when any obs was last marked as viewed so we know when to update
|
|
// the notifications indicator
|
|
observationMarkedAsViewedAt: null,
|
|
// Array of URIs of photos taken in the camera. These should be fully
|
|
// processed, including rotation or any other transformations. It might
|
|
// also include URIs of other photos that need to be visible as previews in
|
|
// the camera
|
|
cameraUris: [],
|
|
savingPhoto: false,
|
|
savedOrUploadedMultiObsFlow: false,
|
|
unsavedChanges: false,
|
|
totalSavedObservations: 0,
|
|
sentinelFileName: null
|
|
};
|
|
|
|
const removeObsSoundFromObservation = ( currentObservation, uri ) => {
|
|
if ( _.isEmpty( currentObservation ) ) { return []; }
|
|
const updatedObservation = currentObservation;
|
|
const obsSounds = Array.from( currentObservation?.observationSounds );
|
|
if ( obsSounds.length > 0 ) {
|
|
_.remove(
|
|
obsSounds,
|
|
obsSound => (
|
|
obsSound.sound.file_url === uri
|
|
|| Sound.getLocalSoundUri( obsSound.sound.file_url ) === uri
|
|
)
|
|
);
|
|
updatedObservation.observationSounds = obsSounds;
|
|
return [updatedObservation];
|
|
}
|
|
return [currentObservation];
|
|
};
|
|
|
|
const observationToJSON = observation => ( observation instanceof Realm.Object
|
|
? observation.toJSON( )
|
|
: observation );
|
|
|
|
const updateObservationKeysWithState = ( keysAndValues, state ) => {
|
|
const {
|
|
observations,
|
|
currentObservation,
|
|
currentObservationIndex
|
|
} = state;
|
|
const updatedObservations = observations;
|
|
const updatedObservation = {
|
|
...observationToJSON( currentObservation ),
|
|
...keysAndValues
|
|
};
|
|
updatedObservations[currentObservationIndex] = updatedObservation;
|
|
return updatedObservations;
|
|
};
|
|
|
|
const createObservationFlowSlice = ( set, get ) => ( {
|
|
...DEFAULT_STATE,
|
|
deletePhotoFromObservation: uri => set( state => {
|
|
const newObservations = [...state.observations];
|
|
let newObservation = null;
|
|
|
|
if ( newObservations.length > 0 ) {
|
|
newObservation = newObservations[state.currentObservationIndex];
|
|
const index = newObservation.observationPhotos.findIndex(
|
|
op => ( Photo.getLocalPhotoUri( op.photo?.localFilePath ) || op.photo?.url ) === uri
|
|
);
|
|
if ( index > -1 ) {
|
|
newObservation.observationPhotos.splice( index, 1 );
|
|
}
|
|
}
|
|
|
|
const newCameraUris = [..._.pull( state.cameraUris, uri )];
|
|
return {
|
|
cameraUris: newCameraUris,
|
|
evidenceToAdd: [..._.pull( state.evidenceToAdd, uri )],
|
|
observations: newObservations,
|
|
currentObservation: newObservation
|
|
? observationToJSON( newObservation )
|
|
: null
|
|
};
|
|
} ),
|
|
deleteSoundFromObservation: uri => set( state => {
|
|
const newObservations = removeObsSoundFromObservation(
|
|
state.observations[state.currentObservationIndex],
|
|
uri
|
|
);
|
|
const newObservation = newObservations[state.currentObservationIndex];
|
|
return {
|
|
observations: newObservations,
|
|
currentObservation: newObservation
|
|
};
|
|
} ),
|
|
resetObservationFlowSlice: ( ) => set( DEFAULT_STATE ),
|
|
addCameraRollUris: uris => set( state => {
|
|
const savedUris = state.cameraRollUris;
|
|
// A placeholder uri means we don't know the real URI, probably b/c we
|
|
// only had write permission so we were able to write the photo to the
|
|
// camera roll but not read anything about it. Keep in mind this is just
|
|
// a hack around a bug in CameraRoll. See
|
|
// patches/@react-native-camera-roll+camera-roll+7.5.2.patch
|
|
uris.forEach( uri => {
|
|
if ( uri && !uri.match( /placeholder/ ) ) savedUris.push( uri );
|
|
} );
|
|
|
|
return ( {
|
|
cameraRollUris: savedUris,
|
|
savingPhoto: false
|
|
} );
|
|
} ),
|
|
setSavingPhoto: saving => set( { savingPhoto: saving } ),
|
|
setCameraState: options => set( state => ( {
|
|
evidenceToAdd: options?.evidenceToAdd || state.evidenceToAdd,
|
|
cameraUris:
|
|
options?.cameraUris || state.cameraUris
|
|
} ) ),
|
|
setCurrentObservationIndex: index => set( state => ( {
|
|
currentObservationIndex: index,
|
|
currentObservation: observationToJSON( state.observations[index] )
|
|
} ) ),
|
|
setGroupedPhotos: photos => set( {
|
|
groupedPhotos: photos
|
|
} ),
|
|
setObservationMarkedAsViewedAt: date => set( {
|
|
observationMarkedAsViewedAt: date
|
|
} ),
|
|
setObservations: updatedObservations => set( state => ( {
|
|
observations: updatedObservations.map( observationToJSON ),
|
|
currentObservation: observationToJSON( updatedObservations[state.currentObservationIndex] )
|
|
} ) ),
|
|
setPhotoImporterState: options => set( state => ( {
|
|
galleryUris: options?.galleryUris || state.galleryUris,
|
|
savingPhoto: options?.savingPhoto || state.savingPhoto,
|
|
evidenceToAdd: options?.evidenceToAdd || state.evidenceToAdd,
|
|
groupedPhotos: options?.groupedPhotos || state.groupedPhotos,
|
|
observations: options?.observations || state.observations,
|
|
currentObservation: observationToJSON(
|
|
options?.observations?.[state.currentObservationIndex]
|
|
|| state.observations?.[state.currentObservationIndex]
|
|
),
|
|
firstObservationDefaults: options?.firstObservationDefaults
|
|
} ) ),
|
|
setSavedOrUploadedMultiObsFlow: ( ) => set( {
|
|
savedOrUploadedMultiObsFlow: true
|
|
} ),
|
|
updateObservations: updatedObservations => set( state => ( {
|
|
observations: updatedObservations.map( observationToJSON ),
|
|
currentObservation: observationToJSON( updatedObservations[state.currentObservationIndex] )
|
|
} ) ),
|
|
updateObservationKeys: keysAndValues => set( state => ( {
|
|
observations: updateObservationKeysWithState( keysAndValues, state ),
|
|
currentObservation:
|
|
updateObservationKeysWithState( keysAndValues, state )[state.currentObservationIndex],
|
|
unsavedChanges: true
|
|
} ) ),
|
|
// For situations where a consumer needs access to this part of state
|
|
// immediately, not after a couple render cycles
|
|
getCurrentObservation: ( ) => get( ).currentObservation,
|
|
// Prepare state for showing ObsEdit for a single observation
|
|
prepareObsEdit: observation => {
|
|
get( ).resetObservationFlowSlice( );
|
|
get( ).updateObservations( [observation] );
|
|
},
|
|
prepareCamera: () => {
|
|
const existingPhotoUris = get( )
|
|
.currentObservation
|
|
?.observationPhotos
|
|
?.map( op => ( op.photo.url || Photo.getLocalPhotoUri( op.photo.localFilePath ) ) ) || [];
|
|
return set( { evidenceToAdd: [], cameraUris: existingPhotoUris } );
|
|
},
|
|
incrementTotalSavedObservations: ( ) => set( state => {
|
|
const {
|
|
totalSavedObservations: existingTotalSavedObservations
|
|
} = state;
|
|
|
|
return ( {
|
|
totalSavedObservations: existingTotalSavedObservations + 1
|
|
} );
|
|
} ),
|
|
setSentinelFileName: sentinelFileName => set( {
|
|
sentinelFileName
|
|
} )
|
|
} );
|
|
|
|
export default createObservationFlowSlice;
|