mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
Advanced settings UI updates (#2797)
* Open more options on long press * Add tests for long press * Rearranging Settings screen with new layout * Add toggle for advanced settings in layout slice; fix default mode toggle * Update settings with navigation flows * Fix tests * Change power mode switch for e2e test * Fix settings test for green button toggle * Fix advanced user toggle in e2e test (which hides pivot cards) * Changes based on design convo; test fixes * Fix e2e tests * Follow user flow chart and update nav accordingly * Rename function * Fix test * Can be null so check for false only * Little less spacing between radio button rows * Minor UI updates * Remove check for previous setting in UI * This is no longer used anywhere * Update AICamera.test.js * Update AICamera.test.js * Update AICamera.test.js * Update Suggestions.test.js * Update Settings.test.js * Update LanguageSettings.test.js --------- Co-authored-by: Johannes Klein <johannes.t.klein@gmail.com>
This commit is contained in:
committed by
GitHub
parent
b957416abe
commit
d782538109
@@ -10,16 +10,8 @@ export default async function switchPowerMode() {
|
||||
const settingsDrawerMenuItem = element( by.id( "settings" ) );
|
||||
await waitFor( settingsDrawerMenuItem ).toBeVisible().withTimeout( 10000 );
|
||||
await settingsDrawerMenuItem.tap();
|
||||
// Tap the settings radio button for advanced interface mode
|
||||
const advancedInterfaceRadioButton = element( by.id( "advanced-interface-option" ) );
|
||||
await waitFor( advancedInterfaceRadioButton ).toBeVisible().withTimeout( 10000 );
|
||||
await advancedInterfaceRadioButton.tap();
|
||||
// Tap the settings radio button for power user mode
|
||||
const powerUserRadioButton = element( by.id( "all-observation-options" ) );
|
||||
await waitFor( powerUserRadioButton ).toBeVisible().withTimeout( 10000 );
|
||||
await powerUserRadioButton.tap();
|
||||
// Tap the settings radio button for suggestions flow first mode
|
||||
const suggestionsFlowButton = element( by.id( "suggestions-flow-mode" ) );
|
||||
await waitFor( suggestionsFlowButton ).toBeVisible().withTimeout( 10000 );
|
||||
await suggestionsFlowButton.tap();
|
||||
// Switch settings to advanced interface mode
|
||||
const advancedInterfaceSwitch = element( by.id( "advanced-interface-switch.switch" ) );
|
||||
await waitFor( advancedInterfaceSwitch ).toBeVisible().withTimeout( 10000 );
|
||||
await advancedInterfaceSwitch.tap();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
} from "react-native-vision-camera";
|
||||
import { createSentinelFile, deleteSentinelFile, logStage } from "sharedHelpers/sentinelFiles.ts";
|
||||
import {
|
||||
useDeviceOrientation, useLayoutPrefs, useTranslation, useWatchPosition
|
||||
useDeviceOrientation, useTranslation, useWatchPosition
|
||||
} from "sharedHooks";
|
||||
import useLocationPermission from "sharedHooks/useLocationPermission.tsx";
|
||||
import useStore from "stores/useStore";
|
||||
@@ -29,9 +29,6 @@ import useSavePhotoPermission from "./hooks/useSavePhotoPermission";
|
||||
export const MAX_PHOTOS_ALLOWED = 20;
|
||||
|
||||
const CameraContainer = ( ) => {
|
||||
const {
|
||||
isDefaultMode
|
||||
} = useLayoutPrefs( );
|
||||
const currentObservation = useStore( state => state.currentObservation );
|
||||
const setCameraState = useStore( state => state.setCameraState );
|
||||
const evidenceToAdd = useStore( state => state.evidenceToAdd );
|
||||
@@ -43,11 +40,6 @@ const CameraContainer = ( ) => {
|
||||
const { params } = useRoute( );
|
||||
const cameraType = params?.camera;
|
||||
|
||||
const showMatchScreen = cameraType === "AI"
|
||||
&& isDefaultMode;
|
||||
const showSuggestionsScreen = cameraType === "AI"
|
||||
&& !isDefaultMode;
|
||||
|
||||
const logStageIfAICamera = useCallback( async (
|
||||
stageName: string,
|
||||
stageData: string
|
||||
@@ -152,16 +144,14 @@ const CameraContainer = ( ) => {
|
||||
newPhotoState,
|
||||
logStageIfAICamera,
|
||||
deleteStageIfAICamera,
|
||||
showMatchScreen,
|
||||
showSuggestionsScreen
|
||||
cameraType
|
||||
} );
|
||||
}, [
|
||||
prepareStoreAndNavigate,
|
||||
navigationOptions,
|
||||
logStageIfAICamera,
|
||||
deleteStageIfAICamera,
|
||||
showMatchScreen,
|
||||
showSuggestionsScreen
|
||||
cameraType
|
||||
] );
|
||||
|
||||
const handleCheckmarkPress = useCallback( async newPhotoState => {
|
||||
|
||||
@@ -6,6 +6,9 @@ import {
|
||||
import Observation from "realmModels/Observation";
|
||||
import ObservationPhoto from "realmModels/ObservationPhoto";
|
||||
import fetchPlaceName from "sharedHelpers/fetchPlaceName";
|
||||
import {
|
||||
useLayoutPrefs
|
||||
} from "sharedHooks";
|
||||
import useStore from "stores/useStore";
|
||||
|
||||
import savePhotosToPhotoLibrary from "../helpers/savePhotosToPhotoLibrary";
|
||||
@@ -25,7 +28,7 @@ const usePrepareStoreAndNavigate = ( ): Function => {
|
||||
const setSavingPhoto = useStore( state => state.setSavingPhoto );
|
||||
const setCameraState = useStore( state => state.setCameraState );
|
||||
const setSentinelFileName = useStore( state => state.setSentinelFileName );
|
||||
const isAdvancedSuggestionsMode = useStore( state => state.layout.isAdvancedSuggestionsMode );
|
||||
const { screenAfterPhotoEvidence, isDefaultMode } = useLayoutPrefs( );
|
||||
|
||||
const { deviceStorageFull, showStorageFullAlert } = useDeviceStorageFull( );
|
||||
|
||||
@@ -137,8 +140,7 @@ const usePrepareStoreAndNavigate = ( ): Function => {
|
||||
newPhotoState,
|
||||
logStageIfAICamera,
|
||||
deleteStageIfAICamera,
|
||||
showMatchScreen,
|
||||
showSuggestionsScreen
|
||||
cameraType
|
||||
} ) => {
|
||||
if ( userLocation !== null ) {
|
||||
logStageIfAICamera( "fetch_user_location_complete" );
|
||||
@@ -162,19 +164,29 @@ const usePrepareStoreAndNavigate = ( ): Function => {
|
||||
await deleteStageIfAICamera( );
|
||||
setSentinelFileName( null );
|
||||
|
||||
if ( showMatchScreen ) {
|
||||
return navigation.push( "Match", {
|
||||
entryScreen: "CameraWithDevice",
|
||||
lastScreen: "CameraWithDevice"
|
||||
} );
|
||||
}
|
||||
if ( showSuggestionsScreen || isAdvancedSuggestionsMode ) {
|
||||
// AI camera can only go to Match/Suggestions
|
||||
if ( cameraType === "AI" ) {
|
||||
if ( isDefaultMode ) {
|
||||
return navigation.push( "Match", {
|
||||
entryScreen: "CameraWithDevice",
|
||||
lastScreen: "CameraWithDevice"
|
||||
} );
|
||||
}
|
||||
return navigation.push( "Suggestions", {
|
||||
entryScreen: "CameraWithDevice",
|
||||
lastScreen: "CameraWithDevice"
|
||||
} );
|
||||
}
|
||||
return navigation.push( "ObsEdit", {
|
||||
|
||||
// Multicapture camera in default mode should only go to Match screen
|
||||
if ( isDefaultMode ) {
|
||||
return navigation.push( "Match", {
|
||||
entryScreen: "CameraWithDevice",
|
||||
lastScreen: "CameraWithDevice"
|
||||
} );
|
||||
}
|
||||
// Multicapture camera navigates based on user settings to Match, Suggestions, or ObsEdit
|
||||
return navigation.push( screenAfterPhotoEvidence, {
|
||||
entryScreen: "CameraWithDevice",
|
||||
lastScreen: "CameraWithDevice"
|
||||
} );
|
||||
@@ -185,7 +197,8 @@ const usePrepareStoreAndNavigate = ( ): Function => {
|
||||
setSentinelFileName,
|
||||
navigation,
|
||||
updateObsWithCameraPhotos,
|
||||
isAdvancedSuggestionsMode
|
||||
screenAfterPhotoEvidence,
|
||||
isDefaultMode
|
||||
] );
|
||||
|
||||
return prepareStoreAndNavigate;
|
||||
|
||||
@@ -25,7 +25,7 @@ type Props = {
|
||||
combinePhotos: Function,
|
||||
groupedPhotos: Array<Object>,
|
||||
isCreatingObservations?: boolean,
|
||||
navToObsEditOrSuggestions: Function,
|
||||
navBasedOnUserSettings: Function,
|
||||
removePhotos: Function,
|
||||
selectedObservations: Array<Object>,
|
||||
selectObservationPhotos: Function,
|
||||
@@ -37,7 +37,7 @@ const GroupPhotos = ( {
|
||||
combinePhotos,
|
||||
groupedPhotos,
|
||||
isCreatingObservations,
|
||||
navToObsEditOrSuggestions,
|
||||
navBasedOnUserSettings,
|
||||
removePhotos,
|
||||
selectedObservations,
|
||||
selectObservationPhotos,
|
||||
@@ -193,7 +193,7 @@ const GroupPhotos = ( {
|
||||
className="max-w-[500px] w-full"
|
||||
level="focus"
|
||||
text={t( "IMPORT-X-OBSERVATIONS", { count: groupedPhotos.length } )}
|
||||
onPress={navToObsEditOrSuggestions}
|
||||
onPress={navBasedOnUserSettings}
|
||||
testID="GroupPhotos.next"
|
||||
loading={isCreatingObservations}
|
||||
/>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { t } from "i18next";
|
||||
import type { Node } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Observation from "realmModels/Observation";
|
||||
import { useLayoutPrefs } from "sharedHooks";
|
||||
import useStore from "stores/useStore";
|
||||
|
||||
import GroupPhotos from "./GroupPhotos";
|
||||
@@ -12,6 +13,9 @@ import flattenAndOrderSelectedPhotos from "./helpers/groupPhotoHelpers";
|
||||
|
||||
const GroupPhotosContainer = ( ): Node => {
|
||||
const navigation = useNavigation( );
|
||||
const {
|
||||
screenAfterPhotoEvidence, isDefaultMode
|
||||
} = useLayoutPrefs( );
|
||||
const setObservations = useStore( state => state.setObservations );
|
||||
const setGroupedPhotos = useStore( state => state.setGroupedPhotos );
|
||||
const groupedPhotos = useStore( state => state.groupedPhotos );
|
||||
@@ -130,7 +134,7 @@ const GroupPhotosContainer = ( ): Node => {
|
||||
setSelectedObservations( [] );
|
||||
};
|
||||
|
||||
const navToObsEditOrSuggestions = async ( ) => {
|
||||
const navBasedOnUserSettings = async ( ) => {
|
||||
setIsCreatingObservations( true );
|
||||
const newObservations = await Promise.all( groupedPhotos.map(
|
||||
( { photos } ) => Observation.createObservationWithPhotos( photos )
|
||||
@@ -145,10 +149,26 @@ const GroupPhotosContainer = ( ): Node => {
|
||||
} ) ) );
|
||||
setIsCreatingObservations( false );
|
||||
if ( newObservations.length === 1 ) {
|
||||
navigation.push( "Suggestions", { entryScreen: "GroupPhotos", lastScreen: "GroupPhotos" } );
|
||||
} else {
|
||||
navigation.navigate( "ObsEdit", { lastScreen: "GroupPhotos" } );
|
||||
if ( isDefaultMode ) {
|
||||
return navigation.navigate( "NoBottomTabStackNavigator", {
|
||||
screen: "Match",
|
||||
params: {
|
||||
entryScreen: "GroupPhotos",
|
||||
lastScreen: "GroupPhotos"
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
// in advanced mode, navigate based on user preference
|
||||
return navigation.navigate( "NoBottomTabStackNavigator", {
|
||||
screen: screenAfterPhotoEvidence,
|
||||
params: {
|
||||
entryScreen: "GroupPhotos",
|
||||
lastScreen: "GroupPhotos"
|
||||
}
|
||||
} );
|
||||
}
|
||||
return navigation.navigate( "ObsEdit", { lastScreen: "GroupPhotos" } );
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -156,7 +176,7 @@ const GroupPhotosContainer = ( ): Node => {
|
||||
combinePhotos={combinePhotos}
|
||||
groupedPhotos={groupedPhotos}
|
||||
isCreatingObservations={isCreatingObservations}
|
||||
navToObsEditOrSuggestions={navToObsEditOrSuggestions}
|
||||
navBasedOnUserSettings={navBasedOnUserSettings}
|
||||
removePhotos={removePhotos}
|
||||
selectObservationPhotos={selectObservationPhotos}
|
||||
selectedObservations={selectedObservations}
|
||||
|
||||
@@ -30,7 +30,7 @@ const DEFAULT_MODE_MAX_PHOTOS_ALLOWED = 1;
|
||||
|
||||
const PhotoLibrary = ( ): Node => {
|
||||
const {
|
||||
isDefaultMode
|
||||
screenAfterPhotoEvidence, isDefaultMode
|
||||
} = useLayoutPrefs( );
|
||||
const navigation = useNavigation( );
|
||||
const [photoLibraryShown, setPhotoLibraryShown] = useState( false );
|
||||
@@ -44,7 +44,6 @@ const PhotoLibrary = ( ): Node => {
|
||||
const currentObservationIndex = useStore( state => state.currentObservationIndex );
|
||||
const observations = useStore( state => state.observations );
|
||||
const numOfObsPhotos = currentObservation?.observationPhotos?.length || 0;
|
||||
const isAdvancedSuggestionsMode = useStore( state => state.layout.isAdvancedSuggestionsMode );
|
||||
const exitObservationsFlow = useExitObservationsFlow( );
|
||||
|
||||
const { params } = useRoute( );
|
||||
@@ -54,30 +53,29 @@ const PhotoLibrary = ( ): Node => {
|
||||
const fromGroupPhotos = params
|
||||
? params.fromGroupPhotos
|
||||
: false;
|
||||
const lastScreen = params?.lastScreen;
|
||||
|
||||
const navToObsEdit = useCallback( ( ) => navigation.navigate( "ObsEdit", {
|
||||
lastScreen: "PhotoLibrary"
|
||||
} ), [navigation] );
|
||||
|
||||
const advanceToMatchScreen = lastScreen === "Camera"
|
||||
&& isDefaultMode;
|
||||
|
||||
const navBasedOnUserSettings = useCallback( async ( ) => {
|
||||
if ( advanceToMatchScreen ) {
|
||||
return navigation.navigate( "Match", {
|
||||
lastScreen: "PhotoLibrary"
|
||||
if ( isDefaultMode ) {
|
||||
return navigation.navigate( "NoBottomTabStackNavigator", {
|
||||
screen: "Match",
|
||||
params: {
|
||||
lastScreen: "PhotoLibrary"
|
||||
}
|
||||
} );
|
||||
}
|
||||
if ( isAdvancedSuggestionsMode ) {
|
||||
return navigation.navigate( "Suggestions", {
|
||||
|
||||
// in advanced mode, navigate based on user preference
|
||||
return navigation.navigate( "NoBottomTabStackNavigator", {
|
||||
screen: screenAfterPhotoEvidence,
|
||||
params: {
|
||||
lastScreen: "PhotoLibrary"
|
||||
} );
|
||||
}
|
||||
return navigation.navigate( "ObsEdit", {
|
||||
lastScreen: "PhotoLibrary"
|
||||
}
|
||||
} );
|
||||
}, [navigation, advanceToMatchScreen, isAdvancedSuggestionsMode] );
|
||||
}, [navigation, screenAfterPhotoEvidence, isDefaultMode] );
|
||||
|
||||
const moveImagesToDocumentsDirectory = async selectedImages => {
|
||||
const path = photoLibraryPhotosPath;
|
||||
@@ -116,7 +114,7 @@ const PhotoLibrary = ( ): Node => {
|
||||
// According to the native code of the image picker library, it never rejects the promise,
|
||||
// just returns a response object with errorCode
|
||||
const response = await ImagePicker.launchImageLibrary( {
|
||||
selectionLimit: advanceToMatchScreen
|
||||
selectionLimit: screenAfterPhotoEvidence === "Match"
|
||||
? DEFAULT_MODE_MAX_PHOTOS_ALLOWED
|
||||
: MAX_PHOTOS_ALLOWED,
|
||||
mediaType: "photo",
|
||||
@@ -217,7 +215,6 @@ const PhotoLibrary = ( ): Node => {
|
||||
setPhotoLibraryShown( false );
|
||||
}
|
||||
}, [
|
||||
advanceToMatchScreen,
|
||||
currentObservation,
|
||||
currentObservationIndex,
|
||||
evidenceToAdd,
|
||||
@@ -232,6 +229,7 @@ const PhotoLibrary = ( ): Node => {
|
||||
observations,
|
||||
params,
|
||||
photoLibraryShown,
|
||||
screenAfterPhotoEvidence,
|
||||
setGroupedPhotos,
|
||||
setPhotoImporterState,
|
||||
skipGroupPhotos,
|
||||
|
||||
@@ -18,36 +18,44 @@ const PhotoSharing = ( ): Node => {
|
||||
const resetObservationFlowSlice = useStore( state => state.resetObservationFlowSlice );
|
||||
const prepareObsEdit = useStore( state => state.prepareObsEdit );
|
||||
const setPhotoImporterState = useStore( state => state.setPhotoImporterState );
|
||||
const { isAdvancedSuggestionsMode, isDefaultMode } = useLayoutPrefs();
|
||||
const { screenAfterPhotoEvidence, isDefaultMode } = useLayoutPrefs();
|
||||
const [navigationHandled, setNavigationHandled] = useState( null );
|
||||
|
||||
const createObservationAndNavToObsEdit = useCallback( async photoUris => {
|
||||
const createObservationAndNavigate = useCallback( async photoUris => {
|
||||
try {
|
||||
const newObservation = await Observation.createObservationWithPhotos( photoUris );
|
||||
newObservation.description = sharedText;
|
||||
prepareObsEdit( newObservation );
|
||||
|
||||
if ( isDefaultMode ) {
|
||||
navigation.navigate( "NoBottomTabStackNavigator", { screen: "Match" } );
|
||||
} else if ( isAdvancedSuggestionsMode ) {
|
||||
navigation.navigate(
|
||||
"NoBottomTabStackNavigator",
|
||||
{ screen: "Suggestions", params: { lastScreen: "PhotoSharing" } }
|
||||
);
|
||||
} else {
|
||||
navigation.navigate( "NoBottomTabStackNavigator", { screen: "ObsEdit" } );
|
||||
return navigation.navigate( "NoBottomTabStackNavigator", {
|
||||
screen: "Match",
|
||||
params: {
|
||||
lastScreen: "PhotoSharing"
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
// in advanced mode, navigate based on user preference
|
||||
return navigation.navigate( "NoBottomTabStackNavigator", {
|
||||
screen: screenAfterPhotoEvidence,
|
||||
params: {
|
||||
lastScreen: "PhotoSharing"
|
||||
}
|
||||
} );
|
||||
} catch ( e ) {
|
||||
Alert.alert(
|
||||
"Photo sharing failed: couldn't create new observation:",
|
||||
e
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}, [
|
||||
isDefaultMode,
|
||||
navigation,
|
||||
prepareObsEdit,
|
||||
sharedText,
|
||||
isAdvancedSuggestionsMode,
|
||||
isDefaultMode
|
||||
screenAfterPhotoEvidence
|
||||
] );
|
||||
|
||||
useEffect( ( ) => {
|
||||
@@ -83,8 +91,7 @@ const PhotoSharing = ( ): Node => {
|
||||
}
|
||||
|
||||
if ( photoUris.length === 1 ) {
|
||||
// Only one photo - go to ObsEdit directly
|
||||
createObservationAndNavToObsEdit( photoUris );
|
||||
createObservationAndNavigate( photoUris );
|
||||
} else {
|
||||
// Go to GroupPhotos screen
|
||||
const firstObservationDefaults = { description: sharedText };
|
||||
@@ -98,7 +105,7 @@ const PhotoSharing = ( ): Node => {
|
||||
navigation.navigate( "NoBottomTabStackNavigator", { screen: "GroupPhotos" } );
|
||||
}
|
||||
}, [
|
||||
createObservationAndNavToObsEdit,
|
||||
createObservationAndNavigate,
|
||||
item,
|
||||
navigation,
|
||||
resetObservationFlowSlice,
|
||||
|
||||
77
src/components/Settings/AdvancedSettings.tsx
Normal file
77
src/components/Settings/AdvancedSettings.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
Body2,
|
||||
RadioButtonRow
|
||||
} from "components/SharedComponents";
|
||||
import React from "react";
|
||||
import {
|
||||
View
|
||||
} from "react-native";
|
||||
import {
|
||||
useLayoutPrefs,
|
||||
useTranslation
|
||||
} from "sharedHooks";
|
||||
import { SCREEN_AFTER_PHOTO_EVIDENCE } from "stores/createLayoutSlice.ts";
|
||||
|
||||
const AdvancedSettings = ( ) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
isAllAddObsOptionsMode,
|
||||
setIsAllAddObsOptionsMode,
|
||||
screenAfterPhotoEvidence,
|
||||
setScreenAfterPhotoEvidence
|
||||
} = useLayoutPrefs();
|
||||
|
||||
const renderSettingDescription = description => (
|
||||
<Body2>{description}</Body2>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className="mt-[20px]">
|
||||
{renderSettingDescription( t( "When-tapping-the-green-observation-button" ) )}
|
||||
<RadioButtonRow
|
||||
classNames="ml-[6px] mt-[15px]"
|
||||
testID="all-observation-options"
|
||||
smallLabel
|
||||
checked={isAllAddObsOptionsMode}
|
||||
onPress={() => setIsAllAddObsOptionsMode( true )}
|
||||
label={t( "All-observation-options--list" )}
|
||||
/>
|
||||
<RadioButtonRow
|
||||
classNames="ml-[6px] mt-[15px]"
|
||||
smallLabel
|
||||
checked={!isAllAddObsOptionsMode}
|
||||
onPress={() => setIsAllAddObsOptionsMode( false )}
|
||||
label={t( "iNaturalist-AI-Camera" )}
|
||||
/>
|
||||
</View>
|
||||
<View className="mt-[20px]">
|
||||
{renderSettingDescription( t( "After-capturing-or-importing-photos-show" ) )}
|
||||
<RadioButtonRow
|
||||
classNames="ml-[6px] mt-[15px]"
|
||||
testID="suggestions-flow-mode"
|
||||
smallLabel
|
||||
checked={screenAfterPhotoEvidence === SCREEN_AFTER_PHOTO_EVIDENCE.SUGGESTIONS}
|
||||
onPress={() => setScreenAfterPhotoEvidence( SCREEN_AFTER_PHOTO_EVIDENCE.SUGGESTIONS )}
|
||||
label={t( "ID-Suggestions" )}
|
||||
/>
|
||||
<RadioButtonRow
|
||||
classNames="ml-[6px] mt-[15px]"
|
||||
smallLabel
|
||||
checked={screenAfterPhotoEvidence === SCREEN_AFTER_PHOTO_EVIDENCE.OBS_EDIT}
|
||||
onPress={() => setScreenAfterPhotoEvidence( SCREEN_AFTER_PHOTO_EVIDENCE.OBS_EDIT )}
|
||||
label={t( "Edit-Observation" )}
|
||||
/>
|
||||
<RadioButtonRow
|
||||
classNames="ml-[6px] mt-[15px]"
|
||||
smallLabel
|
||||
checked={screenAfterPhotoEvidence === SCREEN_AFTER_PHOTO_EVIDENCE.MATCH}
|
||||
onPress={() => setScreenAfterPhotoEvidence( SCREEN_AFTER_PHOTO_EVIDENCE.MATCH )}
|
||||
label={t( "Match-Screen" )}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedSettings;
|
||||
188
src/components/Settings/LoggedInDefaultSettings.tsx
Normal file
188
src/components/Settings/LoggedInDefaultSettings.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import {
|
||||
useNetInfo
|
||||
} from "@react-native-community/netinfo";
|
||||
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
signOut
|
||||
} from "components/LoginSignUp/AuthenticationService.ts";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Body2,
|
||||
Button,
|
||||
Heading4
|
||||
} from "components/SharedComponents";
|
||||
import { RealmContext } from "providers/contexts.ts";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
Alert,
|
||||
View
|
||||
} from "react-native";
|
||||
import Config from "react-native-config";
|
||||
import { EventRegister } from "react-native-event-listeners";
|
||||
import QueueItem from "realmModels/QueueItem.ts";
|
||||
import {
|
||||
useLayoutPrefs,
|
||||
useTranslation,
|
||||
useUserMe
|
||||
} from "sharedHooks";
|
||||
|
||||
import LanguageSetting from "./LanguageSetting";
|
||||
import TaxonNamesSetting from "./TaxonNamesSetting";
|
||||
|
||||
const { useRealm } = RealmContext;
|
||||
|
||||
const SETTINGS_URL = `${Config.OAUTH_API_URL}/users/edit?noh1=true`;
|
||||
const FINISHED_WEB_SETTINGS = "finished-web-settings";
|
||||
|
||||
const LoggedInDefaultSettings = ( ) => {
|
||||
const realm = useRealm( );
|
||||
const { isConnected } = useNetInfo( );
|
||||
const navigation = useNavigation( );
|
||||
const { t } = useTranslation( );
|
||||
const {
|
||||
remoteUser, isLoading, refetchUserMe
|
||||
} = useUserMe( { updateRealm: false } );
|
||||
const {
|
||||
setIsDefaultMode
|
||||
} = useLayoutPrefs();
|
||||
const [settings, setSettings] = useState( {} );
|
||||
const [isSaving, setIsSaving] = useState( false );
|
||||
const [showingWebViewSettings, setShowingWebViewSettings] = useState( false );
|
||||
|
||||
useFocusEffect(
|
||||
useCallback( () => {
|
||||
if ( showingWebViewSettings ) {
|
||||
// When we get back from the webview of settings - in case the user updated their profile
|
||||
// photo or other details
|
||||
refetchUserMe();
|
||||
setShowingWebViewSettings( false );
|
||||
}
|
||||
}, [showingWebViewSettings, refetchUserMe] )
|
||||
);
|
||||
|
||||
const confirmInternetConnection = useCallback( ( ) => {
|
||||
if ( !isConnected ) {
|
||||
Alert.alert(
|
||||
t( "Internet-Connection-Required" ),
|
||||
t( "Please-try-again-when-you-are-connected-to-the-internet" )
|
||||
);
|
||||
}
|
||||
return isConnected;
|
||||
}, [t, isConnected] );
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect( () => {
|
||||
if ( remoteUser ) {
|
||||
// logger.info( remoteUser, "remote user fetched in Settings" );
|
||||
setSettings( remoteUser );
|
||||
setIsSaving( false );
|
||||
}
|
||||
}, [remoteUser, realm] );
|
||||
|
||||
// Listen for the webview to finish so we can fetch the updates users/me
|
||||
// response
|
||||
useEffect( ( ) => {
|
||||
const listener = EventRegister.addEventListener(
|
||||
FINISHED_WEB_SETTINGS,
|
||||
refetchUserMe
|
||||
);
|
||||
return ( ) => {
|
||||
EventRegister?.removeEventListener( listener );
|
||||
};
|
||||
}, [refetchUserMe] );
|
||||
|
||||
return (
|
||||
<View className="mt-[30px]">
|
||||
{( isSaving || isLoading ) && (
|
||||
<View className="absolute z-10 bg-white/80
|
||||
w-full h-full flex items-center justify-center"
|
||||
>
|
||||
<ActivityIndicator size={50} />
|
||||
</View>
|
||||
)}
|
||||
<TaxonNamesSetting
|
||||
onChange={options => {
|
||||
// logger.info( "Enqueuing taxon name change with options:", options );
|
||||
// logger.info( `Current user ID being updated: ${settings.id}` );
|
||||
|
||||
const payload = JSON.stringify( {
|
||||
id: settings.id,
|
||||
user: {
|
||||
prefers_common_names: options.prefers_common_names,
|
||||
prefers_scientific_name_first: options.prefers_scientific_name_first
|
||||
}
|
||||
} );
|
||||
|
||||
// log.info( `Payload to be enqueued: ${payload}` );
|
||||
QueueItem.enqueue(
|
||||
realm,
|
||||
payload,
|
||||
"taxon-names-change"
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<LanguageSetting
|
||||
onChange={newLocale => {
|
||||
QueueItem.enqueue(
|
||||
realm,
|
||||
JSON.stringify( {
|
||||
id: settings.id,
|
||||
user: {
|
||||
locale: newLocale
|
||||
}
|
||||
} ),
|
||||
"locale-change"
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<View>
|
||||
<Heading4>{t( "INATURALIST-ACCOUNT-SETTINGS" )}</Heading4>
|
||||
<Body2 className="mt-2">{t( "Edit-your-profile-change-your-settings" )}</Body2>
|
||||
<Button
|
||||
className="mt-4"
|
||||
text={t( "ACCOUNT-SETTINGS" )}
|
||||
onPress={() => {
|
||||
confirmInternetConnection( );
|
||||
if ( !isConnected ) { return; }
|
||||
setShowingWebViewSettings( true );
|
||||
|
||||
navigation.navigate( "FullPageWebView", {
|
||||
title: t( "ACCOUNT-SETTINGS" ),
|
||||
loggedIn: true,
|
||||
initialUrl: SETTINGS_URL,
|
||||
blurEvent: FINISHED_WEB_SETTINGS,
|
||||
clickablePathnames: ["/users/delete"],
|
||||
skipSetSourceInShouldStartLoadWithRequest: true,
|
||||
shouldLoadUrl: url => {
|
||||
async function signOutGoHome() {
|
||||
Alert.alert(
|
||||
t( "Account-Deleted" ),
|
||||
t( "It-may-take-up-to-an-hour-to-remove-content" )
|
||||
);
|
||||
// sign out
|
||||
await signOut( { realm, clearRealm: true, queryClient } );
|
||||
// revert back to default mode
|
||||
setIsDefaultMode( true );
|
||||
// navigate to My Obs
|
||||
navigation.navigate( "ObsList" );
|
||||
}
|
||||
// If the webview navigates to a URL that indicates the account
|
||||
// was deleted, sign the current user out of the app
|
||||
if ( url === `${Config.OAUTH_API_URL}/?account_deleted=true` ) {
|
||||
signOutGoHome( );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} );
|
||||
}}
|
||||
accessibilityLabel={t( "INATURALIST-SETTINGS" )}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoggedInDefaultSettings;
|
||||
@@ -1,281 +1,53 @@
|
||||
import {
|
||||
useNetInfo
|
||||
} from "@react-native-community/netinfo";
|
||||
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
signOut
|
||||
} from "components/LoginSignUp/AuthenticationService.ts";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Body2,
|
||||
Button,
|
||||
Heading4,
|
||||
RadioButtonRow,
|
||||
ScrollViewWrapper
|
||||
ScrollViewWrapper,
|
||||
SwitchRow
|
||||
} from "components/SharedComponents";
|
||||
import { RealmContext } from "providers/contexts.ts";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import {
|
||||
Alert,
|
||||
StatusBar,
|
||||
View
|
||||
} from "react-native";
|
||||
import Config from "react-native-config";
|
||||
import { EventRegister } from "react-native-event-listeners";
|
||||
import QueueItem from "realmModels/QueueItem.ts";
|
||||
// import { log } from "sharedHelpers/logger";
|
||||
import {
|
||||
useCurrentUser,
|
||||
useLayoutPrefs,
|
||||
useTranslation,
|
||||
useUserMe
|
||||
useTranslation
|
||||
} from "sharedHooks";
|
||||
|
||||
import LanguageSetting from "./LanguageSetting";
|
||||
import TaxonNamesSetting from "./TaxonNamesSetting";
|
||||
|
||||
const { useRealm } = RealmContext;
|
||||
|
||||
const SETTINGS_URL = `${Config.OAUTH_API_URL}/users/edit?noh1=true`;
|
||||
const FINISHED_WEB_SETTINGS = "finished-web-settings";
|
||||
|
||||
// const logger = log.extend( "Settings" );
|
||||
import AdvancedSettings from "./AdvancedSettings";
|
||||
import LoggedInDefaultSettings from "./LoggedInDefaultSettings";
|
||||
|
||||
const Settings = ( ) => {
|
||||
const realm = useRealm( );
|
||||
const { isConnected } = useNetInfo( );
|
||||
const navigation = useNavigation( );
|
||||
const { t } = useTranslation();
|
||||
const currentUser = useCurrentUser( );
|
||||
const {
|
||||
remoteUser, isLoading, refetchUserMe
|
||||
} = useUserMe( { updateRealm: false } );
|
||||
const {
|
||||
isDefaultMode,
|
||||
isAllAddObsOptionsMode,
|
||||
setIsDefaultMode,
|
||||
setIsAllAddObsOptionsMode,
|
||||
isAdvancedSuggestionsMode,
|
||||
setIsSuggestionsFlowMode
|
||||
} = useLayoutPrefs();
|
||||
const [settings, setSettings] = useState( {} );
|
||||
const [isSaving, setIsSaving] = useState( false );
|
||||
const [showingWebViewSettings, setShowingWebViewSettings] = useState( false );
|
||||
setIsDefaultMode
|
||||
} = useLayoutPrefs( );
|
||||
|
||||
useFocusEffect(
|
||||
useCallback( () => {
|
||||
if ( showingWebViewSettings ) {
|
||||
// When we get back from the webview of settings - in case the user updated their profile
|
||||
// photo or other details
|
||||
refetchUserMe();
|
||||
setShowingWebViewSettings( false );
|
||||
}
|
||||
}, [showingWebViewSettings, refetchUserMe] )
|
||||
);
|
||||
const handleValueChange = useCallback( newValue => {
|
||||
setIsDefaultMode( !newValue );
|
||||
}, [setIsDefaultMode] );
|
||||
|
||||
const confirmInternetConnection = useCallback( ( ) => {
|
||||
if ( !isConnected ) {
|
||||
Alert.alert(
|
||||
t( "Internet-Connection-Required" ),
|
||||
t( "Please-try-again-when-you-are-connected-to-the-internet" )
|
||||
);
|
||||
}
|
||||
return isConnected;
|
||||
}, [t, isConnected] );
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect( () => {
|
||||
if ( remoteUser ) {
|
||||
// logger.info( remoteUser, "remote user fetched in Settings" );
|
||||
setSettings( remoteUser );
|
||||
setIsSaving( false );
|
||||
}
|
||||
}, [remoteUser, realm] );
|
||||
|
||||
// Listen for the webview to finish so we can fetch the updates users/me
|
||||
// response
|
||||
useEffect( ( ) => {
|
||||
const listener = EventRegister.addEventListener(
|
||||
FINISHED_WEB_SETTINGS,
|
||||
refetchUserMe
|
||||
);
|
||||
return ( ) => {
|
||||
EventRegister?.removeEventListener( listener );
|
||||
};
|
||||
}, [refetchUserMe] );
|
||||
|
||||
const renderLoggedIn = ( ) => (
|
||||
<View>
|
||||
{( isSaving || isLoading ) && (
|
||||
<View className="absolute z-10 bg-white/80
|
||||
w-full h-full flex items-center justify-center"
|
||||
>
|
||||
<ActivityIndicator size={50} />
|
||||
</View>
|
||||
)}
|
||||
<TaxonNamesSetting
|
||||
onChange={options => {
|
||||
// logger.info( "Enqueuing taxon name change with options:", options );
|
||||
// logger.info( `Current user ID being updated: ${settings.id}` );
|
||||
|
||||
const payload = JSON.stringify( {
|
||||
id: settings.id,
|
||||
user: {
|
||||
prefers_common_names: options.prefers_common_names,
|
||||
prefers_scientific_name_first: options.prefers_scientific_name_first
|
||||
}
|
||||
} );
|
||||
|
||||
// log.info( `Payload to be enqueued: ${payload}` );
|
||||
QueueItem.enqueue(
|
||||
realm,
|
||||
payload,
|
||||
"taxon-names-change"
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<LanguageSetting
|
||||
onChange={newLocale => {
|
||||
QueueItem.enqueue(
|
||||
realm,
|
||||
JSON.stringify( {
|
||||
id: settings.id,
|
||||
user: {
|
||||
locale: newLocale
|
||||
}
|
||||
} ),
|
||||
"locale-change"
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<View>
|
||||
<Heading4>{t( "INATURALIST-ACCOUNT-SETTINGS" )}</Heading4>
|
||||
<Body2 className="mt-2">{t( "Edit-your-profile-change-your-settings" )}</Body2>
|
||||
<Button
|
||||
className="mt-4"
|
||||
text={t( "ACCOUNT-SETTINGS" )}
|
||||
onPress={() => {
|
||||
confirmInternetConnection( );
|
||||
if ( !isConnected ) { return; }
|
||||
setShowingWebViewSettings( true );
|
||||
|
||||
navigation.navigate( "FullPageWebView", {
|
||||
title: t( "ACCOUNT-SETTINGS" ),
|
||||
loggedIn: true,
|
||||
initialUrl: SETTINGS_URL,
|
||||
blurEvent: FINISHED_WEB_SETTINGS,
|
||||
clickablePathnames: ["/users/delete"],
|
||||
skipSetSourceInShouldStartLoadWithRequest: true,
|
||||
shouldLoadUrl: url => {
|
||||
async function signOutGoHome() {
|
||||
Alert.alert(
|
||||
t( "Account-Deleted" ),
|
||||
t( "It-may-take-up-to-an-hour-to-remove-content" )
|
||||
);
|
||||
// sign out
|
||||
await signOut( { realm, clearRealm: true, queryClient } );
|
||||
// revert back to default mode
|
||||
setIsDefaultMode( true );
|
||||
// navigate to My Obs
|
||||
navigation.navigate( "ObsList" );
|
||||
}
|
||||
// If the webview navigates to a URL that indicates the account
|
||||
// was deleted, sign the current user out of the app
|
||||
if ( url === `${Config.OAUTH_API_URL}/?account_deleted=true` ) {
|
||||
signOutGoHome( );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} );
|
||||
}}
|
||||
accessibilityLabel={t( "INATURALIST-SETTINGS" )}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
// maybe there's a less confusing way to do this,
|
||||
// but this worked for my brain on a deadline
|
||||
const isAdvancedMode = !isDefaultMode;
|
||||
|
||||
return (
|
||||
<ScrollViewWrapper>
|
||||
<StatusBar barStyle="dark-content" />
|
||||
<View className="p-5">
|
||||
<View className="mb-9">
|
||||
<Heading4>{t( "INATURALIST-MODE" )}</Heading4>
|
||||
<View className="mt-[22px]">
|
||||
<RadioButtonRow
|
||||
smallLabel
|
||||
checked={isDefaultMode}
|
||||
onPress={( ) => {
|
||||
setIsDefaultMode( true );
|
||||
setIsAllAddObsOptionsMode( false );
|
||||
}}
|
||||
label={t( "Default--interface-mode" )}
|
||||
/>
|
||||
</View>
|
||||
<View className="mt-4">
|
||||
<RadioButtonRow
|
||||
testID="advanced-interface-option"
|
||||
smallLabel
|
||||
checked={!isDefaultMode}
|
||||
onPress={( ) => {
|
||||
setIsDefaultMode( false );
|
||||
setIsAllAddObsOptionsMode( true );
|
||||
}}
|
||||
label={t( "Advanced--interface-mode-with-explainer" )}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{!isDefaultMode && (
|
||||
<View className="mb-9">
|
||||
<Heading4>{t( "OBSERVATION-BUTTON" )}</Heading4>
|
||||
<Body2 className="mt-3">{t( "When-tapping-the-green-observation-button" )}</Body2>
|
||||
<View className="mt-[22px] pr-5">
|
||||
<RadioButtonRow
|
||||
smallLabel
|
||||
checked={!isAllAddObsOptionsMode}
|
||||
onPress={() => setIsAllAddObsOptionsMode( false )}
|
||||
label={t( "iNaturalist-AI-Camera" )}
|
||||
/>
|
||||
</View>
|
||||
<View className="mt-4 pr-5">
|
||||
<RadioButtonRow
|
||||
testID="all-observation-options"
|
||||
smallLabel
|
||||
checked={isAllAddObsOptionsMode}
|
||||
onPress={() => setIsAllAddObsOptionsMode( true )}
|
||||
label={t( "All-observation-options" )}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{!isDefaultMode && (
|
||||
<View className="mb-9">
|
||||
<Heading4>{t( "SUGGESTIONS" )}</Heading4>
|
||||
<Body2 className="mt-3">
|
||||
{t( "After-capturing-or-importing-photos-show" )}
|
||||
</Body2>
|
||||
<View className="mt-4 pr-5">
|
||||
<RadioButtonRow
|
||||
testID="suggestions-flow-mode"
|
||||
smallLabel
|
||||
checked={isAdvancedSuggestionsMode}
|
||||
onPress={() => setIsSuggestionsFlowMode( true )}
|
||||
label={t( "ID-Suggestions" )}
|
||||
/>
|
||||
</View>
|
||||
<View className="mt-[22px] pr-5">
|
||||
<RadioButtonRow
|
||||
smallLabel
|
||||
checked={!isAdvancedSuggestionsMode}
|
||||
onPress={() => setIsSuggestionsFlowMode( false )}
|
||||
label={t( "Edit-Observation" )}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{currentUser && renderLoggedIn()}
|
||||
<View className="p-4">
|
||||
<Heading4 className="mb-[15px]">{t( "ADVANCED-SETTINGS" )}</Heading4>
|
||||
<SwitchRow
|
||||
testID="advanced-interface-switch"
|
||||
classNames="ml-[6px]"
|
||||
smallLabel
|
||||
value={isAdvancedMode}
|
||||
onValueChange={handleValueChange}
|
||||
label={t( "View-Advanced-Settings" )}
|
||||
/>
|
||||
{isAdvancedMode && <AdvancedSettings />}
|
||||
{currentUser && <LoggedInDefaultSettings />}
|
||||
</View>
|
||||
</ScrollViewWrapper>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
import safeRealmWrite from "sharedHelpers/safeRealmWrite";
|
||||
import {
|
||||
useCurrentUser,
|
||||
useLayoutPrefs,
|
||||
useTranslation
|
||||
} from "sharedHooks";
|
||||
|
||||
@@ -35,9 +34,6 @@ const TaxonNamesSetting = ( { onChange }: Props ) => {
|
||||
const realm = useRealm( );
|
||||
const { t } = useTranslation( );
|
||||
const currentUser = useCurrentUser( );
|
||||
const {
|
||||
isDefaultMode
|
||||
} = useLayoutPrefs();
|
||||
|
||||
const changeTaxonNameDisplay = useCallback( nameDisplayPref => {
|
||||
const options = {};
|
||||
@@ -69,7 +65,7 @@ const TaxonNamesSetting = ( { onChange }: Props ) => {
|
||||
return currentUser;
|
||||
}, [currentUser, realm, onChange] );
|
||||
|
||||
if ( isDefaultMode || !currentUser ) {
|
||||
if ( !currentUser ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -91,21 +87,21 @@ const TaxonNamesSetting = ( { onChange }: Props ) => {
|
||||
checked={commonNameFirst}
|
||||
onPress={() => changeTaxonNameDisplay( NAME_DISPLAY_COM_SCI )}
|
||||
label={t( "Common-Name-Scientific-Name" )}
|
||||
classNames="mt-[22px]"
|
||||
classNames="ml-[6px] mt-[15px]"
|
||||
/>
|
||||
<RadioButtonRow
|
||||
smallLabel
|
||||
checked={scientificNameFirst}
|
||||
onPress={() => changeTaxonNameDisplay( NAME_DISPLAY_SCI_COM )}
|
||||
label={t( "Scientific-Name-Common-Name" )}
|
||||
classNames="mt-4"
|
||||
classNames="ml-[6px] mt-[15px]"
|
||||
/>
|
||||
<RadioButtonRow
|
||||
smallLabel
|
||||
checked={scientificNameOnly}
|
||||
onPress={() => changeTaxonNameDisplay( NAME_DISPLAY_SCI )}
|
||||
label={t( "Scientific-Name" )}
|
||||
classNames="mt-4"
|
||||
classNames="ml-[6px] mt-[15px]"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
89
src/components/SharedComponents/SwitchRow.tsx
Normal file
89
src/components/SharedComponents/SwitchRow.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
Body1,
|
||||
Body2,
|
||||
INatIcon,
|
||||
List2
|
||||
} from "components/SharedComponents";
|
||||
import { Pressable, View } from "components/styledComponents";
|
||||
import React from "react";
|
||||
import { GestureResponderEvent } from "react-native";
|
||||
import { Switch } from "react-native-paper";
|
||||
import colors from "styles/tailwindColors";
|
||||
|
||||
interface Props {
|
||||
value: boolean;
|
||||
classNames?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
label?: string;
|
||||
labelComponent?: React.JSX.Element;
|
||||
onValueChange: ( newValue: boolean ) => void;
|
||||
smallLabel?: boolean;
|
||||
testID?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const SwitchRow = ( {
|
||||
value,
|
||||
classNames,
|
||||
description,
|
||||
icon,
|
||||
label,
|
||||
labelComponent,
|
||||
onValueChange,
|
||||
smallLabel = false,
|
||||
testID,
|
||||
disabled = false
|
||||
}: Props ) => {
|
||||
const handlePress = ( _e: GestureResponderEvent ) => {
|
||||
if ( !disabled ) {
|
||||
onValueChange( !value );
|
||||
}
|
||||
};
|
||||
|
||||
const Label = smallLabel
|
||||
? Body2
|
||||
: Body1;
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
className={classNames}
|
||||
testID={testID}
|
||||
accessibilityRole="switch"
|
||||
accessibilityState={{ checked: value, disabled }}
|
||||
onPress={handlePress}
|
||||
disabled={disabled}
|
||||
>
|
||||
<View className="flex-row items-center">
|
||||
<Switch
|
||||
value={value}
|
||||
onValueChange={onValueChange}
|
||||
disabled={disabled}
|
||||
color={colors.inatGreen}
|
||||
testID={`${testID || "Toggle"}.switch`}
|
||||
/>
|
||||
<View className="ml-3 flex-row w-5/6">
|
||||
{labelComponent || (
|
||||
<Label
|
||||
maxFontSizeMultiplier={1.5}
|
||||
className="mr-2"
|
||||
>
|
||||
{label}
|
||||
</Label>
|
||||
)}
|
||||
{icon && <INatIcon name={icon} size={19} color={colors.inatGreen} />}
|
||||
</View>
|
||||
</View>
|
||||
{description && (
|
||||
<List2
|
||||
maxFontSizeMultiplier={1.5}
|
||||
className="ml-[32px] mt-[3px]"
|
||||
>
|
||||
{description}
|
||||
</List2>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwitchRow;
|
||||
@@ -61,6 +61,7 @@ export { default as TextInputSheet } from "./Sheets/TextInputSheet";
|
||||
export { default as TextSheet } from "./Sheets/TextSheet";
|
||||
export { default as WarningSheet } from "./Sheets/WarningSheet";
|
||||
export { default as SimpleObservationLocation } from "./SimpleObservationLocation";
|
||||
export { default as SwitchRow } from "./SwitchRow";
|
||||
export { default as Tabs } from "./Tabs/Tabs";
|
||||
export { default as TaxonResult } from "./TaxonResult";
|
||||
export { default as TaxonSearch } from "./TaxonSearch";
|
||||
|
||||
@@ -70,7 +70,7 @@ Add-optional-notes = Add optional notes
|
||||
Adds-your-vote-of-agreement = Adds your vote of agreement
|
||||
# Hint for a button that adds a vote of disagreement
|
||||
Adds-your-vote-of-disagreement = Adds your vote of disagreement
|
||||
Advanced--interface-mode-with-explainer = Advanced (Upload multiple photos and sounds)
|
||||
ADVANCED-SETTINGS = ADVANCED SETTINGS
|
||||
Affiliation = Affiliation: { $site }
|
||||
After-capturing-or-importing-photos-show = After capturing or importing photos, show:
|
||||
# Label for button that adds an identification of the same taxon as another identification
|
||||
@@ -84,7 +84,7 @@ Agree-with-ID-description = Would you like to agree with the ID and suggest the
|
||||
AI-Camera = AI Camera
|
||||
ALL = ALL
|
||||
All = All
|
||||
All-observation-options = All observation options (including iNaturalist AI Camera, Standard Camera, Uploading from Photo Library, and Sound Recorder)
|
||||
All-observation-options--list = All observation options: iNaturalist AI Camera, Standard Camera, Uploading from Photo Library, and Sound Recorder
|
||||
All-observations = All observations
|
||||
All-observations-need-a-date-and-location-to-be-used-for-science = All observations need a date and location to be used for science. Please edit observations if they need more information.
|
||||
All-organisms = All organisms
|
||||
@@ -364,7 +364,6 @@ datetime-format-short = M/d/yy h:mm a
|
||||
datetime-format-short-with-zone = M/d/yy h:mm a zzz
|
||||
# Month of December
|
||||
December = December
|
||||
Default--interface-mode = Default
|
||||
DELETE = DELETE
|
||||
Delete-all-observations = Delete all observations
|
||||
Delete-comment = Delete comment
|
||||
@@ -625,7 +624,6 @@ iNaturalist-is-supported-by = iNaturalist is supported by an independent, 501(c)
|
||||
iNaturalist-is-supported-by-our-community = iNaturalist is supported by our amazing community. From everyday naturalists who add observations and identifications, to curators who manage our taxonomy and help with moderation, to the volunteer translators who make iNaturalist more accessible to worldwide audiences, to our community-based donors, we are extraordinarily grateful to all the people in our community who make iNaturalist the platform it is.
|
||||
iNaturalist-mission-is-to-connect = iNaturalist's mission is to connect people to nature and advance biodiversity science and conservation.
|
||||
INATURALIST-MISSION-VISION = INATURALIST'S MISSION & VISION
|
||||
INATURALIST-MODE = INATURALIST MODE
|
||||
INATURALIST-NETWORK = INATURALIST NETWORK
|
||||
INATURALIST-SETTINGS = INATURALIST SETTINGS
|
||||
# Label for the role a user plays on iNaturalist, e.g. "INATURALIST STAFF"
|
||||
@@ -714,6 +712,8 @@ MAP = MAP
|
||||
Map-Area = Map Area
|
||||
# Month of March
|
||||
March = March
|
||||
# Radio button option for navigation flows in Settings
|
||||
Match-Screen = Match Screen
|
||||
# Identification category
|
||||
maverick--identification = Maverick
|
||||
# Month of May
|
||||
@@ -837,7 +837,6 @@ Obervations-must-be-manually-added = Observations must be manually added to a tr
|
||||
Obscured = Obscured
|
||||
Observation = Observation
|
||||
Observation-Attribution = Observation: © { $userName } · { $restrictions }
|
||||
OBSERVATION-BUTTON = OBSERVATION BUTTON
|
||||
Observation-Copyright = Observation Copyright: © { $userName } · { $restrictions }
|
||||
Observation-has-no-photos-and-no-sounds = This observation has no photos and no sounds.
|
||||
# Displayed when user views an obscured location on the ObsDetail map screen
|
||||
@@ -1216,7 +1215,6 @@ SUBMIT-ID-SUGGESTION = SUBMIT ID SUGGESTION
|
||||
SUGGEST-ID = SUGGEST ID
|
||||
# Label for element that suggest an identification
|
||||
Suggest-ID = SUGGEST ID
|
||||
SUGGESTIONS = SUGGESTIONS
|
||||
# Identification category
|
||||
supporting--identification = Supporting
|
||||
Switches-to-tab = Switches to { $tab } tab.
|
||||
@@ -1327,6 +1325,8 @@ Using-location = Using location
|
||||
Verified-IDs-are-used-for-science-and-conservation = Verified IDs are used for science and conservation
|
||||
# Listing of app and build versions
|
||||
Version-app-build = Version { $appVersion } ({ $buildVersion })
|
||||
# Label for toggling app mode between default and advanced settings
|
||||
View-Advanced-Settings = View Advanced Settings
|
||||
VIEW-ALL-X-PLACES = VIEW ALL { $count } PLACES
|
||||
VIEW-ALL-X-PROJECTS = VIEW ALL { $count } PROJECTS
|
||||
VIEW-ALL-X-TAXA = VIEW ALL { $count } TAXA
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"Add-optional-notes": "Add optional notes",
|
||||
"Adds-your-vote-of-agreement": "Adds your vote of agreement",
|
||||
"Adds-your-vote-of-disagreement": "Adds your vote of disagreement",
|
||||
"Advanced--interface-mode-with-explainer": "Advanced (Upload multiple photos and sounds)",
|
||||
"ADVANCED-SETTINGS": "ADVANCED SETTINGS",
|
||||
"Affiliation": "Affiliation: { $site }",
|
||||
"After-capturing-or-importing-photos-show": "After capturing or importing photos, show:",
|
||||
"Agree": "Agree",
|
||||
@@ -40,7 +40,7 @@
|
||||
"AI-Camera": "AI Camera",
|
||||
"ALL": "ALL",
|
||||
"All": "All",
|
||||
"All-observation-options": "All observation options (including iNaturalist AI Camera, Standard Camera, Uploading from Photo Library, and Sound Recorder)",
|
||||
"All-observation-options--list": "All observation options: iNaturalist AI Camera, Standard Camera, Uploading from Photo Library, and Sound Recorder",
|
||||
"All-observations": "All observations",
|
||||
"All-observations-need-a-date-and-location-to-be-used-for-science": "All observations need a date and location to be used for science. Please edit observations if they need more information.",
|
||||
"All-organisms": "All organisms",
|
||||
@@ -193,7 +193,6 @@
|
||||
"datetime-format-short": "M/d/yy h:mm a",
|
||||
"datetime-format-short-with-zone": "M/d/yy h:mm a zzz",
|
||||
"December": "December",
|
||||
"Default--interface-mode": "Default",
|
||||
"DELETE": "DELETE",
|
||||
"Delete-all-observations": "Delete all observations",
|
||||
"Delete-comment": "Delete comment",
|
||||
@@ -360,7 +359,6 @@
|
||||
"iNaturalist-is-supported-by-our-community": "iNaturalist is supported by our amazing community. From everyday naturalists who add observations and identifications, to curators who manage our taxonomy and help with moderation, to the volunteer translators who make iNaturalist more accessible to worldwide audiences, to our community-based donors, we are extraordinarily grateful to all the people in our community who make iNaturalist the platform it is.",
|
||||
"iNaturalist-mission-is-to-connect": "iNaturalist's mission is to connect people to nature and advance biodiversity science and conservation.",
|
||||
"INATURALIST-MISSION-VISION": "INATURALIST'S MISSION & VISION",
|
||||
"INATURALIST-MODE": "INATURALIST MODE",
|
||||
"INATURALIST-NETWORK": "INATURALIST NETWORK",
|
||||
"INATURALIST-SETTINGS": "INATURALIST SETTINGS",
|
||||
"INATURALIST-STAFF": "{ $inaturalist } STAFF",
|
||||
@@ -420,6 +418,7 @@
|
||||
"MAP": "MAP",
|
||||
"Map-Area": "Map Area",
|
||||
"March": "March",
|
||||
"Match-Screen": "Match Screen",
|
||||
"maverick--identification": "Maverick",
|
||||
"May": "May",
|
||||
"MEDIA": "MEDIA",
|
||||
@@ -494,7 +493,6 @@
|
||||
"Obscured": "Obscured",
|
||||
"Observation": "Observation",
|
||||
"Observation-Attribution": "Observation: © { $userName } · { $restrictions }",
|
||||
"OBSERVATION-BUTTON": "OBSERVATION BUTTON",
|
||||
"Observation-Copyright": "Observation Copyright: © { $userName } · { $restrictions }",
|
||||
"Observation-has-no-photos-and-no-sounds": "This observation has no photos and no sounds.",
|
||||
"Observation-location-obscured-randomized-point": "This observation’s location is obscured. You are seeing a randomized point within the obscuration polygon.",
|
||||
@@ -772,7 +770,6 @@
|
||||
"SUBMIT-ID-SUGGESTION": "SUBMIT ID SUGGESTION",
|
||||
"SUGGEST-ID": "SUGGEST ID",
|
||||
"Suggest-ID": "SUGGEST ID",
|
||||
"SUGGESTIONS": "SUGGESTIONS",
|
||||
"supporting--identification": "Supporting",
|
||||
"Switches-to-tab": "Switches to { $tab } tab.",
|
||||
"Sync-observations": "Sync observations",
|
||||
@@ -851,6 +848,7 @@
|
||||
"Using-location": "Using location",
|
||||
"Verified-IDs-are-used-for-science-and-conservation": "Verified IDs are used for science and conservation",
|
||||
"Version-app-build": "Version { $appVersion } ({ $buildVersion })",
|
||||
"View-Advanced-Settings": "View Advanced Settings",
|
||||
"VIEW-ALL-X-PLACES": "VIEW ALL { $count } PLACES",
|
||||
"VIEW-ALL-X-PROJECTS": "VIEW ALL { $count } PROJECTS",
|
||||
"VIEW-ALL-X-TAXA": "VIEW ALL { $count } TAXA",
|
||||
|
||||
@@ -70,7 +70,7 @@ Add-optional-notes = Add optional notes
|
||||
Adds-your-vote-of-agreement = Adds your vote of agreement
|
||||
# Hint for a button that adds a vote of disagreement
|
||||
Adds-your-vote-of-disagreement = Adds your vote of disagreement
|
||||
Advanced--interface-mode-with-explainer = Advanced (Upload multiple photos and sounds)
|
||||
ADVANCED-SETTINGS = ADVANCED SETTINGS
|
||||
Affiliation = Affiliation: { $site }
|
||||
After-capturing-or-importing-photos-show = After capturing or importing photos, show:
|
||||
# Label for button that adds an identification of the same taxon as another identification
|
||||
@@ -84,7 +84,7 @@ Agree-with-ID-description = Would you like to agree with the ID and suggest the
|
||||
AI-Camera = AI Camera
|
||||
ALL = ALL
|
||||
All = All
|
||||
All-observation-options = All observation options (including iNaturalist AI Camera, Standard Camera, Uploading from Photo Library, and Sound Recorder)
|
||||
All-observation-options--list = All observation options: iNaturalist AI Camera, Standard Camera, Uploading from Photo Library, and Sound Recorder
|
||||
All-observations = All observations
|
||||
All-observations-need-a-date-and-location-to-be-used-for-science = All observations need a date and location to be used for science. Please edit observations if they need more information.
|
||||
All-organisms = All organisms
|
||||
@@ -364,7 +364,6 @@ datetime-format-short = M/d/yy h:mm a
|
||||
datetime-format-short-with-zone = M/d/yy h:mm a zzz
|
||||
# Month of December
|
||||
December = December
|
||||
Default--interface-mode = Default
|
||||
DELETE = DELETE
|
||||
Delete-all-observations = Delete all observations
|
||||
Delete-comment = Delete comment
|
||||
@@ -625,7 +624,6 @@ iNaturalist-is-supported-by = iNaturalist is supported by an independent, 501(c)
|
||||
iNaturalist-is-supported-by-our-community = iNaturalist is supported by our amazing community. From everyday naturalists who add observations and identifications, to curators who manage our taxonomy and help with moderation, to the volunteer translators who make iNaturalist more accessible to worldwide audiences, to our community-based donors, we are extraordinarily grateful to all the people in our community who make iNaturalist the platform it is.
|
||||
iNaturalist-mission-is-to-connect = iNaturalist's mission is to connect people to nature and advance biodiversity science and conservation.
|
||||
INATURALIST-MISSION-VISION = INATURALIST'S MISSION & VISION
|
||||
INATURALIST-MODE = INATURALIST MODE
|
||||
INATURALIST-NETWORK = INATURALIST NETWORK
|
||||
INATURALIST-SETTINGS = INATURALIST SETTINGS
|
||||
# Label for the role a user plays on iNaturalist, e.g. "INATURALIST STAFF"
|
||||
@@ -714,6 +712,8 @@ MAP = MAP
|
||||
Map-Area = Map Area
|
||||
# Month of March
|
||||
March = March
|
||||
# Radio button option for navigation flows in Settings
|
||||
Match-Screen = Match Screen
|
||||
# Identification category
|
||||
maverick--identification = Maverick
|
||||
# Month of May
|
||||
@@ -837,7 +837,6 @@ Obervations-must-be-manually-added = Observations must be manually added to a tr
|
||||
Obscured = Obscured
|
||||
Observation = Observation
|
||||
Observation-Attribution = Observation: © { $userName } · { $restrictions }
|
||||
OBSERVATION-BUTTON = OBSERVATION BUTTON
|
||||
Observation-Copyright = Observation Copyright: © { $userName } · { $restrictions }
|
||||
Observation-has-no-photos-and-no-sounds = This observation has no photos and no sounds.
|
||||
# Displayed when user views an obscured location on the ObsDetail map screen
|
||||
@@ -1216,7 +1215,6 @@ SUBMIT-ID-SUGGESTION = SUBMIT ID SUGGESTION
|
||||
SUGGEST-ID = SUGGEST ID
|
||||
# Label for element that suggest an identification
|
||||
Suggest-ID = SUGGEST ID
|
||||
SUGGESTIONS = SUGGESTIONS
|
||||
# Identification category
|
||||
supporting--identification = Supporting
|
||||
Switches-to-tab = Switches to { $tab } tab.
|
||||
@@ -1327,6 +1325,8 @@ Using-location = Using location
|
||||
Verified-IDs-are-used-for-science-and-conservation = Verified IDs are used for science and conservation
|
||||
# Listing of app and build versions
|
||||
Version-app-build = Version { $appVersion } ({ $buildVersion })
|
||||
# Label for toggling app mode between default and advanced settings
|
||||
View-Advanced-Settings = View Advanced Settings
|
||||
VIEW-ALL-X-PLACES = VIEW ALL { $count } PLACES
|
||||
VIEW-ALL-X-PROJECTS = VIEW ALL { $count } PROJECTS
|
||||
VIEW-ALL-X-TAXA = VIEW ALL { $count } TAXA
|
||||
|
||||
@@ -5,8 +5,6 @@ const selector = state => ( {
|
||||
// Vestigial stuff
|
||||
obsDetailsTab: state.obsDetailsTab,
|
||||
setObsDetailsTab: state.setObsDetailsTab,
|
||||
isAllAddObsOptionsMode: state.isAdvancedUser,
|
||||
setIsAllAddObsOptionsMode: state.setIsAdvancedUser,
|
||||
loggedInWhileInDefaultMode: state.loggedInWhileInDefaultMode,
|
||||
setLoggedInWhileInDefaultMode: state.setLoggedInWhileInDefaultMode,
|
||||
// newer stuff
|
||||
|
||||
@@ -3,10 +3,15 @@ export enum OBS_DETAILS_TAB {
|
||||
DETAILS = "DETAILS"
|
||||
}
|
||||
|
||||
export enum SCREEN_AFTER_PHOTO_EVIDENCE {
|
||||
SUGGESTIONS = "Suggestions",
|
||||
OBS_EDIT = "ObsEdit",
|
||||
MATCH = "Match"
|
||||
}
|
||||
|
||||
const createLayoutSlice = set => ( {
|
||||
// Vestigial un-namespaced values
|
||||
isAdvancedUser: false,
|
||||
setIsAdvancedUser: ( newValue: boolean ) => set( { isAdvancedUser: newValue } ),
|
||||
// Values that do not need to be persisted
|
||||
obsDetailsTab: OBS_DETAILS_TAB.ACTIVITY,
|
||||
setObsDetailsTab: ( newValue: OBS_DETAILS_TAB ) => set( { obsDetailsTab: newValue } ),
|
||||
@@ -22,14 +27,36 @@ const createLayoutSlice = set => ( {
|
||||
setIsDefaultMode: ( newValue: boolean ) => set( state => ( {
|
||||
layout: {
|
||||
...state.layout,
|
||||
isDefaultMode: newValue
|
||||
isDefaultMode: newValue,
|
||||
// reset to AICamera mode if default mode is toggled on
|
||||
// and otherwise, advanced mode default is all options
|
||||
isAllAddObsOptionsMode: newValue !== true,
|
||||
// reset to Match screen if default mode is toggled on
|
||||
// and otherwise, advanced mode default is Suggestions
|
||||
screenAfterPhotoEvidence: newValue === true
|
||||
? SCREEN_AFTER_PHOTO_EVIDENCE.MATCH
|
||||
: SCREEN_AFTER_PHOTO_EVIDENCE.SUGGESTIONS
|
||||
}
|
||||
} ) ),
|
||||
isAdvancedSuggestionsMode: true,
|
||||
setIsSuggestionsFlowMode: ( newValue: boolean ) => set( state => ( {
|
||||
// leaving isAdvancedSuggestionsMode here for backwards compatibility
|
||||
// for anyone who already set ObsEdit, but setting the default value
|
||||
// to null so we can remove it in the future
|
||||
isAdvancedSuggestionsMode: null,
|
||||
screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.MATCH,
|
||||
setScreenAfterPhotoEvidence: ( newScreen: string ) => set( state => ( {
|
||||
layout: {
|
||||
...state.layout,
|
||||
isAdvancedSuggestionsMode: newValue
|
||||
screenAfterPhotoEvidence: newScreen,
|
||||
// let's stop using this isAdvancedSuggestionsMode value once users adjust their settings
|
||||
// so we can remove it in the future
|
||||
isAdvancedSuggestionsMode: null
|
||||
}
|
||||
} ) ),
|
||||
isAllAddObsOptionsMode: false,
|
||||
setIsAllAddObsOptionsMode: ( newValue: boolean ) => set( state => ( {
|
||||
layout: {
|
||||
...state.layout,
|
||||
isAllAddObsOptionsMode: newValue
|
||||
}
|
||||
} ) ),
|
||||
// State to control pivot cards and other onboarding material being shown only once
|
||||
|
||||
@@ -35,11 +35,6 @@ beforeAll( uniqueRealmBeforeAll );
|
||||
afterAll( uniqueRealmAfterAll );
|
||||
// /UNIQUE REALM SETUP
|
||||
|
||||
const toggleAdvancedMode = async ( ) => {
|
||||
const advancedRadioButton = await screen.findByTestId( "advanced-interface-option" );
|
||||
fireEvent.press( advancedRadioButton );
|
||||
};
|
||||
|
||||
describe( "LanguageSettings", ( ) => {
|
||||
it( "uses locale preference of the local device", ( ) => {
|
||||
renderAppWithComponent( <Settings /> );
|
||||
@@ -68,7 +63,6 @@ describe( "LanguageSettings", ( ) => {
|
||||
|
||||
it( "uses locale preference from server", async ( ) => {
|
||||
renderAppWithComponent( <Settings /> );
|
||||
await toggleAdvancedMode( );
|
||||
const sciNameText = await screen.findByText(
|
||||
i18next.t( "Scientific-Name", { lang: "ru" } )
|
||||
);
|
||||
@@ -77,7 +71,6 @@ describe( "LanguageSettings", ( ) => {
|
||||
|
||||
it( "allows change to Swedish and requests remote locale change", async ( ) => {
|
||||
renderAppWithComponent( <Settings /> );
|
||||
await toggleAdvancedMode( );
|
||||
const changeLocaleButton = await screen.findByText(
|
||||
i18next.t( "CHANGE-APP-LANGUAGE", { lang: "ru" } )
|
||||
);
|
||||
|
||||
@@ -138,9 +138,9 @@ const displayItemByText = text => {
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -358,9 +358,9 @@ describe( "MyObservations", ( ) => {
|
||||
it( "displays observation status in list view in advanced mode", async () => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
const realm = global.mockRealms[__filename];
|
||||
expect( realm.objects( "Observation" ).length ).toBeGreaterThan( 0 );
|
||||
|
||||
@@ -47,9 +47,9 @@ const mockUser = factory( "LocalUser", {
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
@@ -87,9 +87,9 @@ beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: true,
|
||||
shownOnce: {}
|
||||
},
|
||||
isAdvancedUser: false
|
||||
shownOnce: {},
|
||||
isAllAddObsOptionsMode: false
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "@testing-library/react-native";
|
||||
import initI18next from "i18n/initI18next";
|
||||
import inatjs from "inaturalistjs";
|
||||
import { SCREEN_AFTER_PHOTO_EVIDENCE } from "stores/createLayoutSlice.ts";
|
||||
import useStore from "stores/useStore";
|
||||
import factory, { makeResponse } from "tests/factory";
|
||||
import { renderApp } from "tests/helpers/render";
|
||||
@@ -83,9 +84,10 @@ const topSuggestion = {
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: true,
|
||||
screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.OBS_EDIT
|
||||
}
|
||||
} );
|
||||
inatjs.computervision.score_image.mockResolvedValue( makeResponse( [topSuggestion] ) );
|
||||
} );
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import initI18next from "i18n/initI18next";
|
||||
import inatjs from "inaturalistjs";
|
||||
import * as ImagePicker from "react-native-image-picker";
|
||||
import { SCREEN_AFTER_PHOTO_EVIDENCE } from "stores/createLayoutSlice.ts";
|
||||
import useStore from "stores/useStore";
|
||||
import factory, { makeResponse } from "tests/factory";
|
||||
import faker from "tests/helpers/faker";
|
||||
@@ -91,9 +92,9 @@ beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false,
|
||||
isAdvancedSuggestionsMode: true
|
||||
},
|
||||
isAdvancedUser: true
|
||||
screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.SUGGESTIONS,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
inatjs.computervision.score_image.mockResolvedValue( makeResponse( [topSuggestion] ) );
|
||||
} );
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import * as usePredictions from "components/Camera/AICamera/hooks/usePredictions.ts";
|
||||
import inatjs from "inaturalistjs";
|
||||
import * as useLocationPermission from "sharedHooks/useLocationPermission.tsx";
|
||||
import { SCREEN_AFTER_PHOTO_EVIDENCE } from "stores/createLayoutSlice.ts";
|
||||
import useStore from "stores/useStore";
|
||||
import factory, { makeResponse } from "tests/factory";
|
||||
import { renderAppWithObservations } from "tests/helpers/render";
|
||||
@@ -207,9 +208,9 @@ const setupAppWithSignedInUser = async hasLocation => {
|
||||
currentObservation: observations[0],
|
||||
layout: {
|
||||
isDefaultMode: false,
|
||||
isAdvancedSuggestionsMode: true
|
||||
},
|
||||
isAdvancedUser: true
|
||||
screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.OBS_EDIT,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
await renderAppWithObservations( observations, __filename );
|
||||
return { observations };
|
||||
|
||||
@@ -85,9 +85,8 @@ beforeEach( async ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false,
|
||||
isAdvancedSuggestionsMode: false
|
||||
},
|
||||
isAdvancedUser: true
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
inatjs.computervision.score_image.mockResolvedValue( makeResponse( [topSuggestion] ) );
|
||||
} );
|
||||
@@ -138,14 +137,6 @@ describe( "AICamera navigation with advanced user layout", ( ) => {
|
||||
|
||||
describe( "to Suggestions", ( ) => {
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false,
|
||||
isAdvancedSuggestionsMode: true
|
||||
},
|
||||
isAdvancedUser: true
|
||||
} );
|
||||
|
||||
const mockWatchPosition = jest.fn( ( success, _error, _options ) => success( {
|
||||
coords: {
|
||||
latitude: 56,
|
||||
|
||||
@@ -82,7 +82,11 @@ describe( "AddObsButton", ( ) => {
|
||||
|
||||
describe( "with advanced user layout", ( ) => {
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( { isAdvancedUser: true } );
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
it( "opens AddObsModal", async ( ) => {
|
||||
@@ -117,9 +121,9 @@ describe( "with advanced user layout", ( ) => {
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: false
|
||||
},
|
||||
isAdvancedUser: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
@@ -89,9 +89,9 @@ beforeAll( async () => {
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
@@ -153,7 +153,11 @@ describe( "MediaViewer navigation", ( ) => {
|
||||
}
|
||||
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( { isAdvancedUser: true } );
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
it( "should show a photo when tapped", async ( ) => {
|
||||
|
||||
@@ -86,9 +86,9 @@ const actor = userEvent.setup( );
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
inatjs.computervision.score_image.mockResolvedValue( makeResponse( [topSuggestion] ) );
|
||||
} );
|
||||
|
||||
@@ -108,9 +108,9 @@ const uploadObsEditObservation = async options => {
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "@testing-library/react-native";
|
||||
import initI18next from "i18n/initI18next";
|
||||
import * as rnImagePicker from "react-native-image-picker";
|
||||
import { SCREEN_AFTER_PHOTO_EVIDENCE } from "stores/createLayoutSlice.ts";
|
||||
import useStore from "stores/useStore";
|
||||
import faker from "tests/helpers/faker";
|
||||
import { renderApp } from "tests/helpers/render";
|
||||
@@ -73,8 +74,10 @@ describe( "PhotoLibrary navigation", ( ) => {
|
||||
global.withAnimatedTimeTravelEnabled( );
|
||||
beforeEach( ( ) => {
|
||||
useStore.setState( {
|
||||
isAdvancedUser: true,
|
||||
layout: { isAdvancedSuggestionsMode: false }
|
||||
layout: {
|
||||
screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.OBS_EDIT,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -112,8 +115,10 @@ describe( "PhotoLibrary navigation when suggestions screen is preferred next scr
|
||||
global.withAnimatedTimeTravelEnabled();
|
||||
beforeEach( () => {
|
||||
useStore.setState( {
|
||||
isAdvancedUser: true,
|
||||
layout: { isAdvancedSuggestionsMode: true }
|
||||
layout: {
|
||||
screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.SUGGESTIONS,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
it( "advances to Suggestions when one photo is selected", async () => {
|
||||
|
||||
@@ -37,7 +37,11 @@ afterAll( uniqueRealmAfterAll );
|
||||
|
||||
beforeAll( async () => {
|
||||
await initI18next();
|
||||
useStore.setState( { isAdvancedUser: true } );
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( "SoundRecorder navigation", ( ) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
within
|
||||
} from "@testing-library/react-native";
|
||||
import initI18next from "i18n/initI18next";
|
||||
import { SCREEN_AFTER_PHOTO_EVIDENCE } from "stores/createLayoutSlice.ts";
|
||||
import useStore from "stores/useStore";
|
||||
import { renderApp } from "tests/helpers/render";
|
||||
import setupUniqueRealm from "tests/helpers/uniqueRealm";
|
||||
@@ -38,10 +39,14 @@ afterAll( uniqueRealmAfterAll );
|
||||
|
||||
beforeAll( async () => {
|
||||
await initI18next();
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
beforeEach( ( ) => useStore.setState( { isAdvancedUser: true } ) );
|
||||
|
||||
const actor = userEvent.setup( );
|
||||
|
||||
const navigateToCamera = async ( ) => {
|
||||
@@ -60,8 +65,11 @@ describe( "StandardCamera navigation with advanced user layout", ( ) => {
|
||||
global.withAnimatedTimeTravelEnabled( );
|
||||
beforeEach( () => {
|
||||
useStore.setState( {
|
||||
isAdvancedUser: true,
|
||||
layout: { isAdvancedSuggestionsMode: false }
|
||||
layout: {
|
||||
isDefaultMode: false,
|
||||
isAllAddObsOptionsMode: true,
|
||||
screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.OBS_EDIT
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -103,7 +111,11 @@ describe( "StandardCamera navigation with advanced user layout", ( ) => {
|
||||
describe( "when navigating to Suggestions", ( ) => {
|
||||
beforeEach( () => {
|
||||
useStore.setState( {
|
||||
layout: { isAdvancedSuggestionsMode: true }
|
||||
layout: {
|
||||
isDefaultMode: false,
|
||||
screenAfterPhotoEvidence: SCREEN_AFTER_PHOTO_EVIDENCE.SUGGESTIONS,
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
@@ -240,15 +240,14 @@ describe( "Suggestions", ( ) => {
|
||||
// } );
|
||||
} );
|
||||
|
||||
describe( "when reached from Camera directly", ( ) => {
|
||||
describe( "when reached from AI Camera directly", ( ) => {
|
||||
beforeEach( async ( ) => {
|
||||
await signIn( mockUser, { realm: global.mockRealms[__filename] } );
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isDefaultMode: false,
|
||||
isAdvancedSuggestionsMode: true
|
||||
},
|
||||
isAdvancedUser: true
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
inatjs.computervision.score_image
|
||||
.mockResolvedValue( makeResponse( [topSuggestion] ) );
|
||||
|
||||
@@ -80,7 +80,11 @@ describe( "CustomTabBar", () => {
|
||||
|
||||
describe( "CustomTabBar with advanced user layout", () => {
|
||||
beforeAll( ( ) => {
|
||||
useStore.setState( { isAdvancedUser: true } );
|
||||
useStore.setState( {
|
||||
layout: {
|
||||
isAllAddObsOptionsMode: true
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
afterAll( ( ) => {
|
||||
|
||||
@@ -32,7 +32,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
numUnuploadedObservations: 1,
|
||||
uploadStatus: UPLOAD_PENDING,
|
||||
syncingStatus: MANUAL_SYNC_IN_PROGRESS
|
||||
@@ -48,7 +47,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
uploadStatus: UPLOAD_PENDING,
|
||||
syncingStatus: SYNC_PENDING
|
||||
} );
|
||||
@@ -68,7 +66,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
initialNumObservationsInQueue: 1,
|
||||
numUploadsAttempted: 1,
|
||||
uploadStatus: UPLOAD_IN_PROGRESS,
|
||||
@@ -86,7 +83,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
numUploadsAttempted,
|
||||
uploadStatus: UPLOAD_COMPLETE,
|
||||
syncingStatus: SYNC_PENDING,
|
||||
@@ -103,7 +99,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
uploadStatus: UPLOAD_PENDING,
|
||||
syncingStatus: SYNC_PENDING
|
||||
} );
|
||||
@@ -123,7 +118,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
uploadStatus: UPLOAD_IN_PROGRESS,
|
||||
numUploadsAttempted: 2,
|
||||
syncingStatus: SYNC_PENDING,
|
||||
@@ -141,7 +135,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
numUploadsAttempted,
|
||||
uploadStatus: UPLOAD_COMPLETE,
|
||||
syncingStatus: SYNC_PENDING,
|
||||
@@ -174,7 +167,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
...deletionStore,
|
||||
currentDeleteCount: 1,
|
||||
deleteQueue: [{}],
|
||||
@@ -192,7 +184,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
...deletionStore,
|
||||
deleteError,
|
||||
initialNumDeletionsInQueue: 2
|
||||
@@ -212,7 +203,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
uploadStatus: UPLOAD_PENDING,
|
||||
syncingStatus: SYNC_PENDING,
|
||||
numOfUserObservations: 1
|
||||
@@ -234,7 +224,6 @@ describe( "SimpleUploadBannerContainer", () => {
|
||||
layout: {
|
||||
isDefaultMode: false
|
||||
},
|
||||
isAdvancedUser: true,
|
||||
uploadStatus: UPLOAD_PENDING,
|
||||
syncingStatus: SYNC_PENDING
|
||||
} );
|
||||
|
||||
@@ -26,10 +26,9 @@ jest.mock( "@react-navigation/native", ( ) => {
|
||||
|
||||
const initialStoreState = useStore.getState( );
|
||||
|
||||
const toggleAdvancedMode = async ( ) => {
|
||||
const advancedRadioButton = await screen
|
||||
.findByText( /Advanced/ );
|
||||
fireEvent.press( advancedRadioButton );
|
||||
const toggleAdvancedSwitch = async ( ) => {
|
||||
const advancedSwitch = await screen.findByTestId( "advanced-interface-switch.switch" );
|
||||
fireEvent.press( advancedSwitch );
|
||||
};
|
||||
|
||||
beforeAll( async ( ) => {
|
||||
@@ -52,9 +51,9 @@ beforeEach( ( ) => {
|
||||
} );
|
||||
|
||||
describe( "Settings", ( ) => {
|
||||
it( "should toggle the green observation button", async ( ) => {
|
||||
it( "should toggle the green observation button from all options -> AICamera", async ( ) => {
|
||||
renderComponent( <Settings /> );
|
||||
await toggleAdvancedMode( );
|
||||
await toggleAdvancedSwitch( );
|
||||
const allObsOptions = await screen.findByLabelText( /All observation options/ );
|
||||
expect( allObsOptions ).toHaveProp( "accessibilityState", expect.objectContaining( {
|
||||
checked: true
|
||||
@@ -106,7 +105,6 @@ describe( "Settings", ( ) => {
|
||||
|
||||
test( "should change language immediately via language picker via online results", async ( ) => {
|
||||
renderComponent( <Settings /> );
|
||||
await toggleAdvancedMode( );
|
||||
const changeLanguageButton = await screen.findByText( /CHANGE APP LANGUAGE/ );
|
||||
fireEvent.press( changeLanguageButton );
|
||||
const picker = await screen.findByTestId( "ReactNativePicker" );
|
||||
|
||||
Reference in New Issue
Block a user