Files
iNaturalistReactNative/src/stores/createObservationFlowSlice.js
Amanda Bullington 5a2de653c8 Add logging for unsuccessful AICamera flows (#2537)
* 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
2024-12-18 07:03:35 -08:00

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;