MOB-1077: merge main

This commit is contained in:
sepeterson
2025-12-23 09:27:50 -06:00
779 changed files with 5752 additions and 5739 deletions

View File

@@ -11,12 +11,12 @@ module.exports = {
$0: "jest",
args: {
config: "e2e/jest.config.js",
_: ["e2e"]
_: ["e2e"],
},
jest: {
setupTimeout: 900000,
teardownTimeout: 900000
}
teardownTimeout: 900000,
},
},
apps: {
"ios.debug": {
@@ -25,7 +25,7 @@ module.exports = {
"ios/build/Build/Products/Debug-iphonesimulator/iNaturalistReactNative.app",
build:
/* eslint-disable-next-line max-len */
"xcodebuild -workspace ios/iNaturalistReactNative.xcworkspace -scheme iNaturalistReactNative -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build"
"xcodebuild -workspace ios/iNaturalistReactNative.xcworkspace -scheme iNaturalistReactNative -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
},
"ios.release": {
type: "ios.app",
@@ -33,7 +33,7 @@ module.exports = {
"ios/build/Build/Products/Release-iphonesimulator/iNaturalistReactNative.app",
build:
/* eslint-disable-next-line max-len */
"xcodebuild -workspace ios/iNaturalistReactNative.xcworkspace -scheme iNaturalistReactNative -configuration Release -sdk iphonesimulator -derivedDataPath ios/build"
"xcodebuild -workspace ios/iNaturalistReactNative.xcworkspace -scheme iNaturalistReactNative -configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
},
"android.debug": {
type: "android.apk",
@@ -41,7 +41,7 @@ module.exports = {
/* eslint-disable-next-line max-len */
testBinaryPath: `android/app/build/outputs/apk/androidTest/debug/${apkFilenamePrefix}-debug-androidTest.apk`,
build:
"(cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug)"
"(cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug)",
},
"android.release": {
type: "android.apk",
@@ -49,40 +49,40 @@ module.exports = {
/* eslint-disable-next-line max-len */
testBinaryPath: `android/app/build/outputs/apk/androidTest/release/${apkFilenamePrefix}-release-androidTest.apk`,
build:
"(cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release)"
}
"(cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release)",
},
},
devices: {
simulator: {
type: "ios.simulator",
device: {
type: "iPhone 16 Pro",
os: "iOS 18.6"
}
os: "iOS 18.6",
},
},
emulator: {
type: "android.emulator",
device: {
avdName: "Pixel_5_API_31_AOSP"
}
}
avdName: "Pixel_5_API_31_AOSP",
},
},
},
configurations: {
"ios.debug": {
device: "simulator",
app: "ios.debug"
app: "ios.debug",
},
"ios.release": {
device: "simulator",
app: "ios.release"
app: "ios.release",
},
"android.debug": {
device: "emulator",
app: "android.debug"
app: "android.debug",
},
"android.release": {
device: "emulator",
app: "android.release"
}
}
app: "android.release",
},
},
};

View File

@@ -4,15 +4,15 @@ module.exports = {
parserOptions: {
requireConfigFile: false,
babelOptions: {
presets: ["@babel/preset-react"]
}
presets: ["@babel/preset-react"],
},
},
extends: [
"airbnb",
"plugin:i18next/recommended",
"plugin:@tanstack/eslint-plugin-query/recommended",
"plugin:react-native-a11y/ios",
"plugin:@typescript-eslint/recommended"
"plugin:@typescript-eslint/recommended",
],
plugins: [
"module-resolver",
@@ -21,11 +21,13 @@ module.exports = {
"simple-import-sort",
"@tanstack/query",
"@typescript-eslint",
"@stylistic"
"@stylistic",
],
rules: {
"arrow-parens": [2, "as-needed"],
"comma-dangle": [2, "never"],
// enforces trailing comma in objects / arrays except inline: no [1,2,3,]
// functionally, this improves git deltas when the last items of {}/[] are changed
"comma-dangle": ["error", "always-multiline"],
"consistent-return": [2, { treatUndefinedAsUnspecified: true }],
"func-names": 0,
"global-require": 0,
@@ -34,9 +36,9 @@ module.exports = {
{
words: {
// Minor change to the default to disallow all-caps string literals as well
exclude: ["[0-9!-/:-@[-`{-~]+"]
}
}
exclude: ["[0-9!-/:-@[-`{-~]+"],
},
},
],
// The AirBNB approach at
// https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/rules/imports.js#L71
@@ -54,7 +56,7 @@ module.exports = {
jsx: "never",
json: "always",
ts: "never",
tsx: "never"
tsx: "never",
}],
indent: ["error", 2, { SwitchCase: 1 }],
"max-len": [
@@ -66,8 +68,8 @@ module.exports = {
ignoreComments: false,
ignoreRegExpLiterals: true,
ignoreStrings: false,
ignoreTemplateLiterals: false
}
ignoreTemplateLiterals: false,
},
],
"no-alert": 0,
"no-underscore-dangle": 0,
@@ -92,7 +94,7 @@ module.exports = {
"react/jsx-filename-extension": 0,
"react/function-component-definition": [
2,
{ namedComponents: "arrow-function" }
{ namedComponents: "arrow-function" },
],
"react/require-default-props": 0,
@@ -127,6 +129,9 @@ module.exports = {
// https://eslint.org/docs/latest/rules/no-undef#handled_by_typescript
"no-undef": "error",
"@typescript-eslint/array-type": ["error", {
default: "array",
}],
"@typescript-eslint/no-unused-vars": [
"error",
{
@@ -138,18 +143,18 @@ module.exports = {
ignoreRestSiblings: true,
caughtErrors: "all",
// needed a special case for catch blocks that use _ to define an unused error
caughtErrorsIgnorePattern: "^_"
}
caughtErrorsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-require-imports": ["error", {
allow: ["\\.(png|jpg|jpeg|gif|svg)$"]
allow: ["\\.(png|jpg|jpeg|gif|svg)$"],
}],
"@typescript-eslint/no-unsafe-function-type": 1,
"@typescript-eslint/consistent-type-imports": ["error", {
fixStyle: "separate-type-imports"
fixStyle: "separate-type-imports",
}],
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
"@stylistic/member-delimiter-style": "error"
"@stylistic/member-delimiter-style": "error",
},
ignorePatterns: ["!.detoxrc.js", "/coverage/*", "/vendor/*", "**/flow-typed"],
settings: {
@@ -157,9 +162,9 @@ module.exports = {
"babel-module": { allowExistingDirectories: true },
node: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
moduleDirectory: ["node_modules", "src"]
}
}
moduleDirectory: ["node_modules", "src"],
},
},
},
overrides: [
{
@@ -170,29 +175,29 @@ module.exports = {
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/consistent-type-imports": "off",
"import/consistent-type-specifier-style": "off",
"@stylistic/member-delimiter-style": "off"
}
"@stylistic/member-delimiter-style": "off",
},
},
{
files: ["*.test.js", "*.test.tsx"],
rules: {
"react/jsx-props-no-spreading": "off"
}
"react/jsx-props-no-spreading": "off",
},
},
{
files: ["**/__mocks__/**/*", "**/*mock*", "**/*.mock.*"],
rules: {
"@typescript-eslint/no-require-imports": "off"
}
"@typescript-eslint/no-require-imports": "off",
},
},
{
files: ["tests/**/*", "__mocks__/**/*", "e2e/**/*"],
env: { jest: true }
env: { jest: true },
},
{
files: ["tests/**/*"],
plugins: ["testing-library"],
extends: ["plugin:testing-library/react"]
}
]
extends: ["plugin:testing-library/react"],
},
],
};

View File

@@ -9,10 +9,10 @@ export default ( {
_compressFormat,
_quality,
_rotation,
outputPath
outputPath,
) => {
const filename = mockNodePath.basename( path );
return { uri: mockNodePath.join( outputPath, filename ) };
}
)
},
),
} );

View File

@@ -6,7 +6,7 @@ export const CameraRoll = {
resolve( {
page_info: {
end_cursor: jest.fn( ),
has_next_page: false
has_next_page: false,
},
edges: [
{
@@ -20,16 +20,16 @@ export const CameraRoll = {
width: 1080,
fileSize: 123456,
playableDuration: NaN,
orientation: 1
}
}
}
]
orientation: 1,
},
},
},
],
} );
} ) ),
getAlbums: jest.fn( ( ) => ( {
// Expecting album titles as keys and photo counts as values
// "My Amazing album": 12
} ) ),
save: jest.fn( ( _uri, _options = {} ) => mockFaker.system.filePath( ) )
save: jest.fn( ( _uri, _options = {} ) => mockFaker.system.filePath( ) ),
};

View File

@@ -3,6 +3,6 @@ export default ( {
watchPosition: jest.fn( ( ) => 0 ),
clearWatch: jest.fn( ),
setRNConfiguration: jest.fn( ( ) => ( {
skipPermissionRequests: true
} ) )
skipPermissionRequests: true,
} ) ),
} );

View File

@@ -13,11 +13,11 @@ const useNavigation = ( ) => ( {
addListener: jest.fn( ),
canGoBack: jest.fn( ( ) => true ),
goBack: jest.fn( ),
setOptions: jest.fn( )
setOptions: jest.fn( ),
} );
module.exports = {
...actualNav,
useNavigation,
useRoute
useRoute,
};

View File

@@ -2,5 +2,5 @@ export default ( {
OAUTH_CLIENT_ID: process.env.OAUTH_CLIENT_ID,
OAUTH_CLIENT_SECRET: process.env.OAUTH_CLIENT_SECRET,
JWT_ANONYMOUS_API_SECRET: process.env.JWT_ANONYMOUS_API_SECRET,
API_URL: process.env.API_URL
API_URL: process.env.API_URL,
} );

View File

@@ -9,15 +9,15 @@ module.exports = {
copyFile: async ( ) => "testdata",
copyAssetsFileIOS: async ( ) => "testdata",
stat: jest.fn( ( ) => ( {
mtime: new Date()
mtime: new Date(),
} ) ),
readFile: jest.fn( ( ) => "testdata" ),
readDir: jest.fn( async ( ) => ( [
{
ctime: new Date(),
mtime: new Date(),
name: "testdata"
}
name: "testdata",
},
] ) ),
writeFile: jest.fn( async ( filePath, contents, _encoding ) => {
mockFs.writeFile( filePath, contents, jest.fn( ) );
@@ -29,5 +29,5 @@ module.exports = {
if ( !path ) return;
if ( typeof ( path ) !== "string" ) return;
mockFs.unlink( path, jest.fn( ) );
} )
} ),
};

View File

@@ -3,6 +3,6 @@ export default ( {
`Somewhere near ${coord.lat}, ${coord.lng}`,
"Somewhere",
"Somewheria",
"SW"
] )
"SW",
] ),
} );

View File

@@ -3,5 +3,5 @@
module.exports = {
KeyboardAwareScrollView: jest
.fn( )
.mockImplementation( ( { children } ) => children )
.mockImplementation( ( { children } ) => children ),
};

View File

@@ -2,5 +2,5 @@ export const useDeviceOrientationChange = jest.fn();
export default ( {
lockToPortrait: jest.fn( ),
unlockAllOrientations: jest.fn( )
unlockAllOrientations: jest.fn( ),
} );

View File

@@ -1,5 +1,5 @@
const actualPaper = jest.requireActual( "react-native-paper" );
module.exports = {
...actualPaper,
Portal: ( { children } ) => children
Portal: ( { children } ) => children,
};

View File

@@ -54,7 +54,7 @@ class RNSInfo {
if ( service?.size ) {
mappedValues = Array.from( service.entries() ).map(
( [key, value] ) => ( { key, value, service: serviceName } )
( [key, value] ) => ( { key, value, service: serviceName } ),
);
}

View File

@@ -25,7 +25,7 @@ const ShareMenu = {
__reset: ( ) => {
mockShareData = null;
mockListeners = [];
}
},
};
export default ShareMenu;

View File

@@ -3,7 +3,7 @@ import {
mockSortDevices,
mockUseCameraDevice,
mockUseCameraDevices,
mockUseCameraFormat
mockUseCameraFormat,
} from "tests/vision-camera/vision-camera";
export const Camera = mockCamera;
@@ -12,6 +12,6 @@ export const useCameraDevice = mockUseCameraDevice;
export const useCameraDevices = mockUseCameraDevices;
export const useCameraFormat = mockUseCameraFormat;
export const VisionCameraProxy = {
initFrameProcessorPlugin: jest.fn( )
initFrameProcessorPlugin: jest.fn( ),
};
export const useFrameProcessor = jest.fn( );

View File

@@ -2,6 +2,6 @@ export const useSharedValue = jest.fn();
export const Worklets = {
createRunOnJS: jest.fn(),
defaultContext: {
createRunAsync: jest.fn()
}
createRunAsync: jest.fn(),
},
};

View File

@@ -1,21 +1,21 @@
export const getPredictionsForImage = jest.fn( () => Promise.resolve( {
predictions: [],
commonAncestor: undefined
commonAncestor: undefined,
} ) );
export const getPredictionsForLocation = jest.fn( () => Promise.resolve( { predictions: [] } ) );
export const removeLogListener = jest.fn( );
export const resetStoredResults = jest.fn( );
export const getCellLocation = jest.fn( location => ( {
...location,
elevation: 12
elevation: 12,
} ) );
export const MODE = {
BEST_BRANCH: "BEST_BRANCH",
COMMON_ANCESTOR: "COMMON_ANCESTOR"
COMMON_ANCESTOR: "COMMON_ANCESTOR",
};
export const COMMON_ANCESTOR_RANK_TYPE = {
MAJOR: "major",
UNRESTRICTED: "unrestricted"
UNRESTRICTED: "unrestricted",
};

View File

@@ -23,20 +23,20 @@ module.exports = {
stores: "./src/stores",
styles: "./src/styles",
tests: "./tests",
uploaders: "./src/uploaders"
}
uploaders: "./src/uploaders",
},
}],
// Reanimated plugin has to be listed last https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation/
[
"react-native-reanimated/plugin",
{
processNestedWorklets: true
}
]
processNestedWorklets: true,
},
],
],
env: {
production: {
plugins: ["react-native-paper/babel", "transform-remove-console"]
}
}
plugins: ["react-native-paper/babel", "transform-remove-console"],
},
},
};

View File

@@ -1,5 +1,5 @@
import {
by, device, element, waitFor
by, device, element, waitFor,
} from "detox";
import { iNatE2eAfterEach, iNatE2eBeforeAll, iNatE2eBeforeEach } from "./helpers";
@@ -46,7 +46,7 @@ describe( "AICamera", () => {
const otherSuggestionsTitle = element( by.text( "OTHER SUGGESTIONS" ) );
await waitFor( otherSuggestionsTitle ).toBeVisible( ).withTimeout( 30_000 );
const firstSuggestion = element( by.id( /SuggestionsList\.taxa\..*/ ) ).atIndex(
0
0,
);
await waitFor( firstSuggestion ).toBeVisible().withTimeout( TIMEOUT );
const suggestionAttributes = await firstSuggestion.getAttributes();
@@ -64,9 +64,9 @@ describe( "AICamera", () => {
// Check that the display taxon name is visible
const displayTaxonName = element( by.id( `display-taxon-name.${taxonID}` ) ).atIndex(
0
0,
);
await waitFor( displayTaxonName ).toBeVisible().withTimeout( TIMEOUT );
}
},
);
} );

View File

@@ -12,8 +12,8 @@ export async function iNatE2eBeforeAll( device ) {
location: "always",
camera: "YES",
medialibrary: "YES",
photos: "YES"
}
photos: "YES",
},
} );
}
}
@@ -31,8 +31,8 @@ export async function iNatE2eBeforeEach( device ) {
location: "always",
camera: "YES",
medialibrary: "YES",
photos: "YES"
}
photos: "YES",
},
};
try {
await device.launchApp( launchAppOptions );
@@ -46,15 +46,15 @@ export async function iNatE2eBeforeEach( device ) {
// disable password autofill
execSync(
// eslint-disable-next-line max-len
`plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/UserSettings.plist`
`plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/UserSettings.plist`,
);
execSync(
// eslint-disable-next-line max-len
`plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Library/UserConfigurationProfiles/EffectiveUserSettings.plist`
`plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Library/UserConfigurationProfiles/EffectiveUserSettings.plist`,
);
execSync(
// eslint-disable-next-line max-len
`plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Library/UserConfigurationProfiles/PublicInfo/PublicEffectiveUserSettings.plist`
`plutil -replace restrictedBool.allowPasswordAutoFill.value -bool NO ~/Library/Developer/CoreSimulator/Devices/${device.id}/data/Library/UserConfigurationProfiles/PublicInfo/PublicEffectiveUserSettings.plist`,
);
}
@@ -131,7 +131,7 @@ export async function iNatE2eAfterEach( device ) {
} catch ( detoxError ) {
console.log(
"Detox terminateApp failed, falling back to manual termination:",
detoxError.message
detoxError.message,
);
}
}

View File

@@ -7,5 +7,5 @@ module.exports = {
reporters: ["detox/runners/jest/reporter"],
globalSetup: "detox/runners/jest/globalSetup",
globalTeardown: "detox/runners/jest/globalTeardown",
testEnvironment: "detox/runners/jest/testEnvironment"
testEnvironment: "detox/runners/jest/testEnvironment",
};

View File

@@ -1,7 +1,7 @@
import {
by,
element,
waitFor
waitFor,
} from "detox";
const VISIBILITY_TIMEOUT = 10_000;

View File

@@ -7,7 +7,7 @@ import { CHUCKS_PAD as sampleObservation } from "../../src/appConstants/e2e";
const apiHost = Config.OAUTH_API_URL;
const testUsernameAllowlist = [
"inaturalist-test"
"inaturalist-test",
];
const userAgent = "iNaturalistRN/e2e";
@@ -15,14 +15,14 @@ const userAgent = "iNaturalistRN/e2e";
inatjs.setConfig( {
apiURL: Config.API_URL,
writeApiURL: Config.API_URL,
userAgent
userAgent,
} );
// programatically dismisses announcements for user and resets to a lone sample observation
// in order to set up consistent testing conditions and remove need to wait for announcements
export default async function resetUserForTesting() {
console.log(
"Test user reset: dismissing announcements and resetting observations..."
"Test user reset: dismissing announcements and resetting observations...",
);
if ( !testUsernameAllowlist.includes( Config.E2E_TEST_USERNAME ) ) {
@@ -35,8 +35,8 @@ export default async function resetUserForTesting() {
const apiClient = create( {
baseURL: apiHost,
headers: {
"User-Agent": userAgent
}
"User-Agent": userAgent,
},
} );
await apiClient.get( "/logout" );
@@ -48,7 +48,7 @@ export default async function resetUserForTesting() {
client_secret: Config.OAUTH_CLIENT_SECRET,
username: Config.E2E_TEST_USERNAME,
password: Config.E2E_TEST_PASSWORD,
locale: "en"
locale: "en",
};
const tokenResponse = await apiClient.post( "/oauth/token", formData );
@@ -59,7 +59,7 @@ export default async function resetUserForTesting() {
const jwtResponse = await apiClient.get( "/users/api_token.json" );
const opts = {
api_token: jwtResponse.data.api_token
api_token: jwtResponse.data.api_token,
};
const announcementSearchParams = {
@@ -68,13 +68,13 @@ export default async function resetUserForTesting() {
per_page: 20,
fields: {
id: true,
dismissible: true
}
dismissible: true,
},
};
const announcementResponse = await inatjs.announcements.search(
announcementSearchParams,
opts
opts,
);
const announcementIdsToDismiss = announcementResponse
@@ -88,7 +88,7 @@ export default async function resetUserForTesting() {
try {
await inatjs.announcements.dismiss(
{ id },
opts
opts,
);
} catch ( _error ) {
console.log( `Could not delete announcement: ${id}. Moving on...` );
@@ -100,9 +100,9 @@ export default async function resetUserForTesting() {
{
headers: {
Authorization: `Bearer ${accessToken}`,
"User-Agent": userAgent
}
}
"User-Agent": userAgent,
},
},
);
const userId = usersEditResponse.data.id;
@@ -131,7 +131,7 @@ export default async function resetUserForTesting() {
try {
await inatjs.observations.delete(
{ uuid },
opts
opts,
);
} catch ( _error ) {
console.log( `Could not delete observation: ${uuid}. Moving on...` );
@@ -142,17 +142,17 @@ export default async function resetUserForTesting() {
const sampleObservationParams = {
observation: {
latitude: sampleObservation.latitude,
longitude: sampleObservation.longitude
}
longitude: sampleObservation.longitude,
},
};
await inatjs.observations.create(
sampleObservationParams,
opts
opts,
);
await apiClient.get( "/logout" );
console.log(
"Test user reset: announcements dismissed and observations reset"
"Test user reset: announcements dismissed and observations reset",
);
}

View File

@@ -1,5 +1,5 @@
import {
by, element, expect, waitFor
by, element, expect, waitFor,
} from "detox";
import Config from "react-native-config-node";

View File

@@ -1,5 +1,5 @@
import {
by, element, waitFor
by, element, waitFor,
} from "detox";
const TIMEOUT = 10_000;

View File

@@ -1,5 +1,5 @@
import {
by, element, expect, waitFor
by, element, expect, waitFor,
} from "detox";
// This needs to be a relative path for the e2e-mock version to be used

View File

@@ -1,5 +1,5 @@
import {
by, device, element, expect, waitFor
by, device, element, expect, waitFor,
} from "detox";
import { iNatE2eAfterEach, iNatE2eBeforeAll, iNatE2eBeforeEach } from "./helpers";
@@ -25,7 +25,7 @@ describe( "Signed in user", () => {
await expect( element( by.id( "observe-without-evidence-button" ) ) ).toBeVisible();
// Observe without evidence
const obsWithoutEvidenceButton = element(
by.id( "observe-without-evidence-button" )
by.id( "observe-without-evidence-button" ),
);
await expect( obsWithoutEvidenceButton ).toBeVisible();
await obsWithoutEvidenceButton.tap();

View File

@@ -3,7 +3,7 @@ import {
device,
element,
expect,
waitFor
waitFor,
} from "detox";
import { iNatE2eAfterEach, iNatE2eBeforeAll, iNatE2eBeforeEach } from "./helpers";

View File

@@ -11,13 +11,13 @@ import "react-native-url-polyfill/auto";
import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
import {
useColorScheme, Alert, AppRegistry, View
useColorScheme, Alert, AppRegistry, View,
} from "react-native";
import { getCurrentRoute } from "navigation/navigationUtils";
import { zustandStorage } from "stores/useStore";
import {
QueryClient,
QueryClientProvider
QueryClientProvider,
} from "@tanstack/react-query";
import App from "components/App";
import ErrorBoundary from "components/ErrorBoundary";
@@ -58,7 +58,7 @@ if (
allRejections: true,
onUnhandled: ( id, error ) => {
logger.error( "Unhandled promise rejection: ", error );
}
},
} );
}
/* eslint-enable no-undef */
@@ -68,7 +68,7 @@ const jsErrorHandler = ( e, isFatal ) => {
Alert.alert(
"Device-storage-full",
"iNaturalist may not be able to save your photos or may crash.",
[{ text: t( "OK" ) }]
[{ text: t( "OK" ) }],
);
}
@@ -104,7 +104,7 @@ setNativeExceptionHandler(
screen: getCurrentRoute()?.name || "",
memoryUsage: await DeviceInfo.getUsedMemory(),
timestamp: new Date().toISOString(),
appVersion: await DeviceInfo.getVersion()
appVersion: await DeviceInfo.getVersion(),
};
// Store crash data for retrieval on next app launch
@@ -117,7 +117,7 @@ setNativeExceptionHandler(
}
},
true, // Force quit the app to prevent zombie states
true // Enable on iOS
true, // Enable on iOS
);
initI18next();
@@ -128,16 +128,16 @@ inatjs.setConfig( {
writeApiURL: Config.API_URL,
userAgent: getUserAgent(),
headers: {
"X-Installation-ID": getInstallID( )
}
"X-Installation-ID": getInstallID( ),
},
} );
const queryClient = new QueryClient( {
defaultOptions: {
queries: {
retry: reactQueryRetry
}
}
retry: reactQueryRetry,
},
},
} );
const AppWithProviders = ( ) => {

View File

@@ -7,27 +7,27 @@ const ignorePatterns = "node_modules/(?!(jest-)?@react-native|react-native|"
const config: Config = {
moduleNameMapper: {
"\\.svg": "<rootDir>/tests/mocks/svgMock.js"
"\\.svg": "<rootDir>/tests/mocks/svgMock.js",
},
preset: "react-native",
setupFiles: [
"./node_modules/react-native-gesture-handler/jestSetup.js",
"./node_modules/@react-native-google-signin/google-signin/jest/build/jest/setup.js",
"<rootDir>/tests/jest.setup.js"
"<rootDir>/tests/jest.setup.js",
],
globalSetup: "<rootDir>/tests/jest.globalSetup.js",
setupFilesAfterEnv: [
"<rootDir>/tests/jest.post-setup.js",
"<rootDir>/tests/realm.setup.js",
"<rootDir>/tests/initI18next.setup.js"
"<rootDir>/tests/initI18next.setup.js",
],
transformIgnorePatterns: [ignorePatterns],
// uncomment the line below to enable verbose logging of test results
// verbose: true,
testPathIgnorePatterns: [
"<rootDir>/tests/integration/broken",
"<rootDir>/tests/integration/navigation/broken"
]
"<rootDir>/tests/integration/navigation/broken",
],
// uncomment reporters below to see which tests are running the slowest in jest
// reporters: [
// ["jest-slow-test-reporter", {"numTests": 8, "warnOnSlowerThan": 300, "color": true}]

View File

@@ -10,7 +10,7 @@
const { getDefaultConfig, mergeConfig } = require( "@react-native/metro-config" );
const {
resolver: { sourceExts, assetExts }
resolver: { sourceExts, assetExts },
} = getDefaultConfig();
const localPackagePaths = [
@@ -25,7 +25,7 @@ const localPackagePaths = [
*/
const config = {
transformer: {
babelTransformerPath: require.resolve( "react-native-svg-transformer/react-native" )
babelTransformerPath: require.resolve( "react-native-svg-transformer/react-native" ),
},
resolver: {
assetExts: assetExts.filter( ext => ext !== "svg" ),
@@ -33,9 +33,9 @@ const config = {
process.env.MOCK_MODE === "e2e"
? ["e2e-mock", ...sourceExts, "svg"]
: [...sourceExts, "svg"],
nodeModulesPaths: [...localPackagePaths]
nodeModulesPaths: [...localPackagePaths],
},
watchFolders: [...localPackagePaths]
watchFolders: [...localPackagePaths],
};
module.exports = mergeConfig( getDefaultConfig( __dirname ), config );

View File

@@ -3,7 +3,7 @@ import RNFS from "react-native-fs";
import {
consoleTransport,
fileAsyncTransport,
logger
logger,
} from "react-native-logs";
const fileName = "inaturalist-rn-log.txt";
@@ -23,19 +23,19 @@ const config = {
transport,
transportOptions: {
FS: RNFS,
fileName
}
fileName,
},
};
const log = logger.createLogger( config );
const logWithoutRemote = logger.createLogger( {
...config,
transport: [consoleTransport, fileAsyncTransport]
transport: [consoleTransport, fileAsyncTransport],
} );
export {
log,
logFilePath,
logWithoutRemote
logWithoutRemote,
};

View File

@@ -1,3 +1,3 @@
module.exports = {
assets: ["./assets/fonts/"]
assets: ["./assets/fonts/"],
};

View File

@@ -28,12 +28,12 @@ const downloadAndroid = async argv => {
"src",
androidFlavor,
"assets",
"camera"
"camera",
);
const androidModel = path.join(
androidDestination,
`${cvModelFilename}.${androidExt}`
`${cvModelFilename}.${androidExt}`,
);
console.log( "Checking android model files..." );
@@ -50,7 +50,7 @@ const downloadAndroid = async argv => {
}
console.log(
`Android model files missing, downloading from '${binariesBaseDir}'...`
`Android model files missing, downloading from '${binariesBaseDir}'...`,
);
await fs.mkdir( androidDestination, { recursive: true } );
@@ -93,7 +93,7 @@ const downloadIOS = async () => {
}
console.log(
`iOS Model files missing, downloading from '${binariesBaseDir}'...`
`iOS Model files missing, downloading from '${binariesBaseDir}'...`,
);
await fs.mkdir( iosDestination, { recursive: true } );
@@ -123,7 +123,7 @@ yargs
.option( "androidFlavor", {
alias: "f",
type: "string",
description: "Android flavor to download model files into"
description: "Android flavor to download model files into",
} )
.command(
"$0",
@@ -133,6 +133,6 @@ yargs
async argv => {
await downloadAndroid( argv );
await downloadIOS();
}
},
)
.help().argv;

View File

@@ -9,19 +9,19 @@ const ANNOUNCEMENTS_FIELDS = {
body: true,
dismissible: true,
start: true,
placement: true
placement: true,
};
const PARAMS = {
fields: ANNOUNCEMENTS_FIELDS,
placement: "mobile"
placement: "mobile",
};
const searchAnnouncements = async ( params: Object = {}, opts: Object = {} ): Promise<?Object> => {
try {
const { results } = await inatjs.announcements.search(
{ ...PARAMS, ...params },
opts
opts,
);
return results;
} catch ( e ) {

View File

@@ -6,12 +6,12 @@ import Comment from "realmModels/Comment";
import handleError from "./error";
const PARAMS = {
fields: Comment.COMMENT_FIELDS
fields: Comment.COMMENT_FIELDS,
};
const createComment = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const { results } = await inatjs.comments.create( { ...PARAMS, ...params }, opts );
@@ -23,7 +23,7 @@ const createComment = async (
const updateComment = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const { results } = await inatjs.comments.update( { ...PARAMS, ...params }, opts );
@@ -35,7 +35,7 @@ const updateComment = async (
const deleteComments = async (
id: number,
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const { results } = await inatjs.comments.delete( { id }, opts );
@@ -48,5 +48,5 @@ const deleteComments = async (
export {
createComment,
deleteComments,
updateComment
updateComment,
};

View File

@@ -9,13 +9,13 @@ const PARAMS = {
fields: {
combined_score: true,
vision_score: true,
taxon: Taxon.LIMITED_TAXON_FIELDS
}
taxon: Taxon.LIMITED_TAXON_FIELDS,
},
};
const scoreImage = async (
params = {},
opts = {}
opts = {},
): Promise<object> => {
try {
return inatjs.computervision.score_image( { ...PARAMS, ...params }, opts );

View File

@@ -15,7 +15,7 @@ export class INatApiError extends Error {
constructor(
json: Record<string, unknown> & { status: string | number },
status?: number,
context?: Record<string, unknown> | null
context?: Record<string, unknown> | null,
) {
super( JSON.stringify( json ) );
this.name = "INatApiError";
@@ -30,7 +30,7 @@ export class INatApiUnauthorizedError extends INatApiError {
const errorJson = {
error: "Unauthorized",
status: 401,
context
context,
};
super( errorJson, 401, context );
this.name = "INatApiUnauthorizedError";
@@ -42,7 +42,7 @@ export class INatApiTooManyRequestsError extends INatApiError {
const errorJson = {
error: "Too Many Requests",
status: 429,
context
context,
};
super( errorJson, 429, context );
this.name = "INatApiTooManyRequestsError";
@@ -65,12 +65,12 @@ export interface ErrorWithResponse {
url: string;
json: () => Promise<{
status: string;
errors: Array<{
errors: {
errorCode: string;
message: string;
from: string | null;
stack: string | null;
}>;
}[];
}>;
};
status?: number;
@@ -82,7 +82,7 @@ export interface ErrorWithResponse {
function createContext(
e: ErrorWithResponse,
options: HandleErrorOptions,
extraContext: Record<string, unknown> | null
extraContext: Record<string, unknown> | null,
) {
const context = {
queryKey: options?.queryKey
@@ -97,19 +97,19 @@ function createContext(
url: e?.response?.url,
routeName: options?.routeName || e?.routeName,
routeParams: options?.routeParams || e?.routeParams,
...( extraContext || {} )
...( extraContext || {} ),
};
// Remove nullish values (null or undefined) from context
return Object.fromEntries(
Object.entries( context ).filter(
( [_, value] ) => value !== null && value !== undefined
)
( [_, value] ) => value !== null && value !== undefined,
),
);
}
async function handleError(
e: ErrorWithResponse,
options: HandleErrorOptions = {}
options: HandleErrorOptions = {},
): Promise<INatApiError | ErrorWithResponse> {
// Get context from options if available
const originalContext = options?.context || null;
@@ -177,7 +177,7 @@ async function handleError(
error,
error.context
? JSON.stringify( error.context )
: "No context"
: "No context",
);
if ( typeof ( options.onApiError ) === "function" ) {
options.onApiError( error );

View File

@@ -6,12 +6,12 @@ import Identification from "realmModels/Identification";
import handleError from "./error";
const PARAMS = {
fields: Identification.ID_FIELDS
fields: Identification.ID_FIELDS,
};
const createIdentification = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const { results } = await inatjs.identifications.create( { ...PARAMS, ...params }, opts );
@@ -23,7 +23,7 @@ const createIdentification = async (
const updateIdentification = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const { results } = await inatjs.identifications.update( { ...PARAMS, ...params }, opts );
@@ -35,5 +35,5 @@ const updateIdentification = async (
export {
createIdentification,
updateIdentification
updateIdentification,
};

View File

@@ -13,8 +13,8 @@ const api = create( {
baseURL: API_HOST,
headers: {
"User-Agent": getUserAgent( ),
"X-Installation-ID": getInstallID( )
}
"X-Installation-ID": getInstallID( ),
},
} );
function isError( error: { message?: string; stack?: string } ) {
@@ -71,16 +71,16 @@ const iNatLogstashTransport: transportFunctionType = async props => {
context: props.extension,
timestamp: new Date().toISOString(),
error_type: errorType,
backtrace
backtrace,
};
try {
await api.post( "/log", formData, {
headers: {
Authorization: [
userToken,
anonymousToken
].flat( ).join( ", " )
}
anonymousToken,
].flat( ).join( ", " ),
},
} );
} catch ( e ) {
const postLogError = e as Error;

View File

@@ -7,11 +7,11 @@ const MESSAGE_FIELDS = {
subject: true,
body: true,
from_user: User.FIELDS,
to_user: User.FIELDS
to_user: User.FIELDS,
};
const PARAMS = {
fields: MESSAGE_FIELDS
fields: MESSAGE_FIELDS,
};
const searchMessages = async ( params: object = {}, opts: object = {} ): Promise<object> => {

View File

@@ -6,7 +6,7 @@ import handleError from "./error";
const deleteRemoteObservationSound = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
) : Promise<?Object> => {
try {
return await inatjs.observation_sounds.delete( params, opts );

View File

@@ -46,13 +46,13 @@ const unfaveObservation = async ( params: Object = {}, opts: Object = {} ): Prom
const fetchRemoteObservation = async (
uuid: string,
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?number> => {
try {
const response = await inatjs.observations.fetch(
uuid,
params,
opts
opts,
);
if ( !response ) { return null; }
const { results } = response;
@@ -66,15 +66,15 @@ const fetchRemoteObservation = async (
};
const fetchRemoteObservations = async (
uuids: Array<string>,
uuids: string[],
params: Object = {},
opts: Object = {}
): Promise<?Array<Object>> => {
opts: Object = {},
): Promise<?Object[]> => {
try {
const response = await inatjs.observations.fetch(
uuids,
params,
opts
opts,
);
if ( !response ) { return null; }
const { results } = response;
@@ -97,7 +97,7 @@ const markAsReviewed = async ( params: Object = {}, opts: Object = {} ): Promise
const markObservationUpdatesViewed = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
return await inatjs.observations.viewedUpdates( params, opts );
@@ -108,7 +108,7 @@ const markObservationUpdatesViewed = async (
const createObservation = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
return await inatjs.observations.create( params, opts );
@@ -119,7 +119,7 @@ const createObservation = async (
const updateObservation = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
return await inatjs.observations.update( params, opts );
@@ -135,7 +135,7 @@ const updateObservation = async (
const createOrUpdateEvidence = async (
apiEndpoint: Function,
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
return await apiEndpoint( params, opts );
@@ -146,7 +146,7 @@ const createOrUpdateEvidence = async (
const fetchObservationUpdates = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const { results } = await inatjs.observations.updates( params, opts );
@@ -158,28 +158,28 @@ const fetchObservationUpdates = async (
const fetchUnviewedObservationUpdatesCount = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<number> => {
try {
const { total_results: updatesCount } = await inatjs.observations.updates( {
...params,
viewed: false,
per_page: 0
per_page: 0,
}, opts );
return updatesCount;
} catch ( e ) {
return handleError( e, {
context: {
functionName: "fetchUnviewedObservationUpdatesCount",
opts
}
opts,
},
} );
}
};
const deleteRemoteObservation = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
) : Promise<?Object> => {
try {
return await inatjs.observations.delete( params, opts );
@@ -206,7 +206,7 @@ const fetchIdentifiers = async ( params: Object = {} ) : Promise<?Object> => {
const fetchSpeciesCounts = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
) : Promise<?Object> => {
try {
return inatjs.observations.speciesCounts( params, opts );
@@ -217,7 +217,7 @@ const fetchSpeciesCounts = async (
const checkForDeletedObservations = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
) : Promise<?Object> => {
try {
return await inatjs.observations.deleted( params, opts );
@@ -228,7 +228,7 @@ const checkForDeletedObservations = async (
const fetchSubscriptions = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
) : Promise<?Object> => {
try {
return inatjs.observations.subscriptions( params, opts );
@@ -239,7 +239,7 @@ const fetchSubscriptions = async (
const createSubscription = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
) : Promise<?Object> => {
try {
return inatjs.observations.subscribe( params, opts );
@@ -267,5 +267,5 @@ export {
markObservationUpdatesViewed,
searchObservations,
unfaveObservation,
updateObservation
updateObservation,
};

View File

@@ -5,9 +5,9 @@ import inatjs from "inaturalistjs";
import handleError from "./error";
const fetchPlace = async (
id: number | Array<number>,
id: number | number[],
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const { results } = await inatjs.places.fetch( id, params, opts );

View File

@@ -7,17 +7,17 @@ import handleError from "./error";
const FIELDS = {
title: true,
icon: true,
project_type: true
project_type: true,
};
const PARAMS = {
fields: FIELDS
fields: FIELDS,
};
const fetchProjects = async (
id: number,
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const { results } = await inatjs.projects.fetch( id, params, opts );
@@ -29,7 +29,7 @@ const fetchProjects = async (
const fetchProjectMembers = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
return await inatjs.projects.members( params, opts );
@@ -40,7 +40,7 @@ const fetchProjectMembers = async (
const fetchProjectPosts = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const response = await inatjs.projects.posts( params, opts );
@@ -92,5 +92,5 @@ export {
fetchProjects,
joinProject,
leaveProject,
searchProjects
searchProjects,
};

View File

@@ -6,7 +6,7 @@ import handleError from "./error";
const setQualityMetric = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const response = await inatjs.observations.setQualityMetric( params, opts );
@@ -18,7 +18,7 @@ const setQualityMetric = async (
const deleteQualityMetric = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const { results } = await inatjs.observations.deleteQualityMetric( params, opts );
@@ -30,7 +30,7 @@ const deleteQualityMetric = async (
const fetchQualityMetrics = async (
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const response = await inatjs.observations.qualityMetrics( params, opts );
@@ -43,5 +43,5 @@ const fetchQualityMetrics = async (
export {
deleteQualityMetric,
fetchQualityMetrics,
setQualityMetric
setQualityMetric,
};

View File

@@ -5,7 +5,7 @@ import inatjs from "inaturalistjs";
import handleError from "./error";
const PARAMS = {
fields: "all"
fields: "all",
};
const fetchRelationships = async ( params: Object = {}, opts: Object = {} ): Promise<?Object> => {
@@ -48,5 +48,5 @@ export {
createRelationships,
deleteRelationships,
fetchRelationships,
updateRelationships
updateRelationships,
};

View File

@@ -8,7 +8,7 @@ import type {
ApiProject,
ApiResponse,
ApiTaxon,
ApiUser
ApiUser,
} from "./types";
interface SearchResponse extends ApiResponse {
@@ -30,13 +30,13 @@ interface SearchParams extends ApiParams {
const PARAMS: ApiParams = {
per_page: 10,
fields: "all"
fields: "all",
};
// Vanilla search wrapper with error handling
const search = async (
params: SearchParams = {},
opts: ApiOpts = {}
opts: ApiOpts = {},
): Promise<null | SearchResponse> => {
let response: SearchResponse;
try {
@@ -53,7 +53,7 @@ const search = async (
// Hits /search AND maps results so it just returns and array of results
const fetchSearchResults = async (
params: SearchParams = {},
opts: ApiOpts = {}
opts: ApiOpts = {},
): Promise<null | ( ApiPlace | ApiProject | ApiTaxon | ApiUser )[]> => {
const response = await search( params, opts );
if ( !response ) { return null; }

View File

@@ -8,31 +8,31 @@ const ANCESTOR_FIELDS = {
name: true,
preferred_common_name: true,
rank: true,
rank_level: true
rank_level: true,
};
const PHOTO_FIELDS = {
id: true,
attribution: true,
license_code: true,
url: true
url: true,
};
// These fields should work with all /taxa endpoints
const FIELDS = {
ancestor_ids: true,
default_photo: {
url: true
url: true,
},
name: true,
preferred_common_name: true,
rank: true,
rank_level: true,
wikipedia_url: true
wikipedia_url: true,
};
const PARAMS = {
fields: FIELDS
fields: FIELDS,
};
function mapTaxonPhotoToLocalSchema( taxonPhoto ) {
@@ -47,9 +47,9 @@ function mapToLocalSchema( taxon ) {
}
async function fetchTaxon(
id: number | Array<number>,
id: number | number[],
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> {
try {
const fetchParams = {
@@ -64,24 +64,24 @@ async function fetchTaxon(
id: true,
place: {
id: true,
display_name: true
}
display_name: true,
},
},
listed_taxa: {
establishment_means: true,
id: true,
list: {
title: true
title: true,
},
place: {
id: true
}
id: true,
},
},
taxon_photos: {
photo: PHOTO_FIELDS
photo: PHOTO_FIELDS,
},
wikipedia_summary: true
}
wikipedia_summary: true,
},
};
const response = await inatjs.taxa.fetch( id, fetchParams, opts );
if ( typeof id === "number" ) {
@@ -108,5 +108,5 @@ async function searchTaxa( params: Object = {}, opts: Object = {} ): Promise<?Ob
export {
fetchTaxon,
searchTaxa
searchTaxa,
};

View File

@@ -4,7 +4,7 @@ import inatjs from "inaturalistjs";
const fetchAvailableLocales = async (
params: Record<string, unknown> = {},
opts: Record<string, unknown> = {}
opts: Record<string, unknown> = {},
): Promise<Record<string, unknown> | null | ErrorWithResponse | INatApiError> => {
try {
const response = await inatjs.translations.locales( params, opts );
@@ -13,7 +13,7 @@ const fetchAvailableLocales = async (
} catch ( e ) {
return handleError(
e as ErrorWithResponse,
{ context: { functionName: "fetchAvailableLocales", opts } }
{ context: { functionName: "fetchAvailableLocales", opts } },
);
}
};

View File

@@ -7,14 +7,14 @@ import {
getDeviceType,
getSystemName,
getSystemVersion,
getVersion
getVersion,
} from "react-native-device-info";
const DETAILS = [
`Build ${getBuildNumber()}`,
`${getSystemName()} ${getSystemVersion()}`,
getDeviceId( ),
getDeviceType( )
getDeviceType( ),
];
async function getOtherDetails( ) {
@@ -32,5 +32,5 @@ function getUserAgent( ) {
export {
DETAILS,
getUserAgent
getUserAgent,
};

View File

@@ -19,16 +19,16 @@ const REMOTE_USER_FIELDS = {
place_id: true,
roles: true,
site: {
name: true
name: true,
},
species_count: true,
updated_at: true,
prefers_common_names: true,
prefers_scientific_name_first: true
prefers_scientific_name_first: true,
};
const REMOTE_USER_PARAMS = {
fields: REMOTE_USER_FIELDS
fields: REMOTE_USER_FIELDS,
};
const fetchUserMe = async ( params: Object = {}, opts: Object = {} ): Promise<?Object> => {
@@ -44,7 +44,7 @@ const fetchUserProjects = async ( params: Object = {}, opts: Object = {} ): Prom
try {
const response = await inatjs.users.projects(
params,
opts
opts,
);
return response?.results;
} catch ( e ) {
@@ -55,14 +55,14 @@ const fetchUserProjects = async ( params: Object = {}, opts: Object = {} ): Prom
const fetchRemoteUser = async (
id: number | string,
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
if ( !id ) return null;
try {
const { results } = await inatjs.users.fetch( id, {
...REMOTE_USER_PARAMS,
...params,
...opts
...opts,
} );
return results[0];
} catch ( e ) {
@@ -71,9 +71,9 @@ const fetchRemoteUser = async (
};
const fetchUsers = async (
ids: Array<number>,
ids: number[],
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
return await inatjs.users.fetch( ids, params, opts );
@@ -85,12 +85,12 @@ const fetchUsers = async (
const blockUser = async (
id: number,
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const response = await inatjs.users.block( { id }, {
...params,
...opts
...opts,
} );
return response;
} catch ( e ) {
@@ -101,12 +101,12 @@ const blockUser = async (
const muteUser = async (
id: number,
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const response = await inatjs.users.mute( { id }, {
...params,
...opts
...opts,
} );
return response;
} catch ( e ) {
@@ -117,12 +117,12 @@ const muteUser = async (
const unblockUser = async (
id: number,
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const response = await inatjs.users.unblock( { id }, {
...params,
...opts
...opts,
} );
return response;
} catch ( e ) {
@@ -133,12 +133,12 @@ const unblockUser = async (
const unmuteUser = async (
id: number,
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
const response = await inatjs.users.unmute( { id }, {
...params,
...opts
...opts,
} );
return response;
} catch ( e ) {
@@ -157,7 +157,7 @@ const updateUsers = async ( params: Object = {}, opts: Object = {} ): Promise<?O
const fetchUserEmailAvailable = async (
email: string,
params: Object = {},
opts: Object = {}
opts: Object = {},
): Promise<?Object> => {
try {
return await inatjs.users.emailAvailable( { email }, { ...params, ...opts } );
@@ -176,5 +176,5 @@ export {
muteUser,
unblockUser,
unmuteUser,
updateUsers
updateUsers,
};

View File

@@ -16,5 +16,5 @@ export const CHUCKS_PAD = {
altitude: 120.0234,
altitudeAccuracy: 2.123,
heading: null,
speed: null
speed: null,
};

View File

@@ -6,7 +6,7 @@ import {
Heading4,
ScrollViewWrapper,
Tabs,
UnderlinedLink
UnderlinedLink,
} from "components/SharedComponents";
import { Image, Pressable, View } from "components/styledComponents";
import { t } from "i18next";
@@ -30,7 +30,7 @@ const About = ( ) => {
navigation.navigate( "FullPageWebView", {
title: t( "TERMS-OF-USE" ),
initialUrl: url,
loggedIn: false
loggedIn: false,
} );
};
@@ -39,7 +39,7 @@ const About = ( ) => {
navigation.navigate( "FullPageWebView", {
title: t( "PRIVACY-POLICY" ),
initialUrl: url,
loggedIn: false
loggedIn: false,
} );
};
@@ -48,7 +48,7 @@ const About = ( ) => {
navigation.navigate( "FullPageWebView", {
title: t( "COMMUNITY-GUIDELINES" ),
initialUrl: url,
loggedIn: false
loggedIn: false,
} );
};
@@ -68,15 +68,15 @@ const About = ( ) => {
text: t( "ABOUT" ),
onPress: () => {
setActiveTab( aboutID );
}
},
},
{
id: teamID,
text: t( "TEAM" ),
onPress: () => {
setActiveTab( teamID );
}
}
},
},
]}
activeId={activeTab}
/>

View File

@@ -1,5 +1,5 @@
import {
Body3, BottomSheet, INatIcon, INatIconButton
Body3, BottomSheet, INatIcon, INatIconButton,
} from "components/SharedComponents";
import { Pressable, View } from "components/styledComponents";
import React, { useMemo } from "react";
@@ -34,7 +34,7 @@ const GREEN_CIRCLE_CLASS = "bg-inatGreen rounded-full h-[36px] w-[36px] mb-2";
const ROW_CLASS = "flex-row justify-center space-x-4 w-full flex-1";
const AddObsBottomSheet = ( {
closeBottomSheet, navAndCloseBottomSheet, hidden
closeBottomSheet, navAndCloseBottomSheet, hidden,
}: Props ) => {
const { t } = useTranslation( );
@@ -47,7 +47,7 @@ const AddObsBottomSheet = ( {
onPress: ( ) => navAndCloseBottomSheet( "Camera", { camera: "AI" } ),
testID: "aicamera-button",
accessibilityLabel: t( "AI-Camera" ),
accessibilityHint: t( "Navigates-to-AI-camera" )
accessibilityHint: t( "Navigates-to-AI-camera" ),
},
standardCamera: {
text: t( "Take-photos" ),
@@ -55,7 +55,7 @@ const AddObsBottomSheet = ( {
onPress: ( ) => navAndCloseBottomSheet( "Camera", { camera: "Standard" } ),
testID: "camera-button",
accessibilityLabel: t( "Camera" ),
accessibilityHint: t( "Navigates-to-camera" )
accessibilityHint: t( "Navigates-to-camera" ),
},
photoLibrary: {
text: t( "Upload-photos" ),
@@ -63,7 +63,7 @@ const AddObsBottomSheet = ( {
onPress: ( ) => navAndCloseBottomSheet( "PhotoLibrary" ),
testID: "import-media-button",
accessibilityLabel: t( "Photo-importer" ),
accessibilityHint: t( "Navigates-to-photo-importer" )
accessibilityHint: t( "Navigates-to-photo-importer" ),
},
soundRecorder: {
text: t( "Record-a-sound" ),
@@ -71,7 +71,7 @@ const AddObsBottomSheet = ( {
onPress: ( ) => navAndCloseBottomSheet( "SoundRecorder" ),
testID: "record-sound-button",
accessibilityLabel: t( "Sound-recorder" ),
accessibilityHint: t( "Navigates-to-sound-recorder" )
accessibilityHint: t( "Navigates-to-sound-recorder" ),
},
noEvidence: {
text: t( "Create-observation-with-no-evidence" ),
@@ -83,22 +83,22 @@ const AddObsBottomSheet = ( {
},
testID: "observe-without-evidence-button",
accessibilityLabel: t( "Observation-with-no-evidence" ),
accessibilityHint: t( "Navigates-to-observation-edit-screen" )
}
accessibilityHint: t( "Navigates-to-observation-edit-screen" ),
},
} ), [
navAndCloseBottomSheet,
prepareObsEdit,
t
t,
] );
const optionRows = AI_CAMERA_SUPPORTED
? [
[obsCreateItems.standardCamera, obsCreateItems.photoLibrary],
[obsCreateItems.soundRecorder, obsCreateItems.aiCamera]
[obsCreateItems.soundRecorder, obsCreateItems.aiCamera],
]
: [
[obsCreateItems.standardCamera],
[obsCreateItems.soundRecorder, obsCreateItems.photoLibrary]
[obsCreateItems.soundRecorder, obsCreateItems.photoLibrary],
];
const renderAddObsIcon = ( {
@@ -107,7 +107,7 @@ const AddObsBottomSheet = ( {
icon,
onPress,
testID,
text
text,
}: ObsCreateItem ) => (
<Pressable
key={testID}

View File

@@ -93,7 +93,7 @@ const AddObsButton = ( ): React.Node => {
justFinishedSignup,
currentUser,
isAllAddObsOptionsMode,
shownOnce
shownOnce,
] );
const dismissTooltip = () => {
@@ -133,13 +133,13 @@ const AddObsButton = ( ): React.Node => {
routes: [
{
name: screen,
params: { ...params, previousScreen: currentRoute }
}
]
}
}
]
} )
params: { ...params, previousScreen: currentRoute },
},
],
},
},
],
} ),
);
closeBottomSheet( );

View File

@@ -22,7 +22,7 @@ const AddObsTooltip = ( { isVisible, dismissTooltip }: Props ) => {
<View
className={classNames(
"border-l-[10px] border-r-[10px] border-x-[#00000000]",
"border-t-[16px] border-t-white mb-2"
"border-t-[16px] border-t-white mb-2",
)}
/>
<GradientButton

View File

@@ -8,7 +8,7 @@ import { log } from "sharedHelpers/logger";
import {
useCurrentUser,
usePerformance,
useShare
useShare,
} from "sharedHooks";
import { isDebugMode } from "sharedHooks/useDebugMode";
@@ -21,7 +21,7 @@ const logger = log.extend( "App" );
type SharedItem = {
mimeType: string,
data: string | Array<string>
data: string | string[]
};
const handleShare = ( navigation, item: ?SharedItem ) => {
@@ -41,7 +41,7 @@ const handleShare = ( navigation, item: ?SharedItem ) => {
// while observations are created
navigation?.navigate( "NoBottomTabStackNavigator", {
screen: "PhotoSharing",
params: { item }
params: { item },
} );
};
@@ -55,7 +55,7 @@ type Props = {
const App = ( { children }: Props ): Node => {
const navigation = useNavigation( );
const { loadTime } = usePerformance( {
screenName: "App"
screenName: "App",
} );
if ( isDebugMode( ) ) {
logger.info( loadTime );
@@ -65,7 +65,7 @@ const App = ( { children }: Props ): Node => {
// for performance reasons
const onShare = useCallback(
item => handleShare( navigation, item ),
[navigation]
[navigation],
);
const currentUser = useCurrentUser( );

View File

@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
import { AppState } from "react-native";
import { log } from "sharedHelpers/logger";
import {
usePerformance
usePerformance,
} from "sharedHooks";
import { isDebugMode } from "sharedHooks/useDebugMode";
@@ -13,7 +13,7 @@ const logger = log.extend( "AppStateListener" );
const AppStateListener = ( ) => {
const { loadTime } = usePerformance( {
screenName: "AppStateListener",
isLoading: false
isLoading: false,
} );
if ( isDebugMode( ) ) {
logger.info( loadTime );

View File

@@ -21,7 +21,7 @@ import {
useDebugMode,
useLayoutPrefs,
usePerformance,
useTranslation
useTranslation,
} from "sharedHooks";
import { isDebugMode } from "sharedHooks/useDebugMode";
import useStore from "stores/useStore";
@@ -32,7 +32,7 @@ import {
handleCaptureError,
handleClassifierError,
handleDeviceNotSupported,
handleLog
handleLog,
} from "../helpers";
import AICameraButtons from "./AICameraButtons";
import FrameProcessorCamera from "./FrameProcessorCamera";
@@ -76,7 +76,7 @@ const AICamera = ( {
takePhotoOptions,
userLocation,
hasLocationPermissions,
requestLocationPermissions
requestLocationPermissions,
}: Props ): Node => {
const navigation = useNavigation( );
const sentinelFileName = useStore( state => state.sentinelFileName );
@@ -92,10 +92,10 @@ const AICamera = ( {
pinchToZoom,
showZoomButton,
zoomTextValue,
resetZoom
resetZoom,
} = useZoom( device );
const {
rotatableAnimatedStyle
rotatableAnimatedStyle,
} = useRotation( );
const {
confidenceThreshold,
@@ -109,7 +109,7 @@ const AICamera = ( {
setConfidenceThreshold,
setFPS,
setNumStoredResults,
setCropRatio
setCropRatio,
} = usePredictions( );
const [inactive, setInactive] = React.useState( false );
const [initialVolume, setInitialVolume] = useState( null );
@@ -149,7 +149,7 @@ const AICamera = ( {
const { t } = useTranslation();
const { loadTime } = usePerformance( {
isLoading: camera?.current !== null
isLoading: camera?.current !== null,
} );
if ( isDebugMode( ) && loadTime ) {
logger.info( loadTime );
@@ -189,7 +189,7 @@ const AICamera = ( {
replaceExisting: true,
inactivateCallback: () => setInactive( true ),
navigateImmediately: true,
visionResult
visionResult,
} );
setHasTakenPhoto( false );
}, [
@@ -198,7 +198,7 @@ const AICamera = ( {
sentinelFileName,
takePhotoAndStoreUri,
result,
hasLocationPermissions
hasLocationPermissions,
] );
useEffect( () => {
@@ -234,8 +234,8 @@ const AICamera = ( {
navigation.navigate( "TabNavigator", {
screen: "ObservationsTab",
params: {
screen: "ObsList"
}
screen: "ObsList",
},
} );
};
@@ -274,7 +274,7 @@ const AICamera = ( {
0.001,
isTablet && isLandscapeMode
? 0.3
: 1
: 1,
]}
className="w-full h-[219px]"
>
@@ -282,7 +282,7 @@ const AICamera = ( {
className={classnames( "self-center", {
"w-[493px]": isTablet,
"w-[346px] top-8": !isTablet,
"top-14": insets.top > 0
"top-14": insets.top > 0,
} )}
>
{showPrediction && result

View File

@@ -78,7 +78,7 @@ const AICameraButtons = ( {
zoomTextValue,
useLocation,
toggleLocation,
deleteSentinelFile
deleteSentinelFile,
}: Props ) => {
const { isDefaultMode } = useLayoutPrefs();
if ( isTablet ) {

View File

@@ -2,16 +2,16 @@ import classnames from "classnames";
import {
Button,
Heading4,
INatIconButton
INatIconButton,
} from "components/SharedComponents";
import {
CIRCLE_SIZE
CIRCLE_SIZE,
} from "components/SharedComponents/Buttons/TransparentCircleButton";
import { Text, View } from "components/styledComponents";
import React, { useState } from "react";
import {
Modal,
Portal
Portal,
} from "react-native-paper";
import type { CameraDeviceFormat } from "react-native-vision-camera";
import { useDebugMode } from "sharedHooks";
@@ -42,7 +42,7 @@ const AIDebugButton = ( {
numStoredResults,
setNumStoredResults,
cropRatio,
setCropRatio
setCropRatio,
}: Props ) => {
const [modalVisible, setModalVisible] = useState( false );
const [slideIndex, setSlideIndex] = useState( 0 );
@@ -57,7 +57,7 @@ const AIDebugButton = ( {
"items-center",
"justify-center",
"rounded-full",
CIRCLE_SIZE
CIRCLE_SIZE,
)}
backgroundColor={colors.deeppink}
onPress={() => setModalVisible( true )}

View File

@@ -2,14 +2,14 @@
import { useNavigation } from "@react-navigation/native";
import CameraView from "components/Camera/CameraView";
import {
useFrameProcessor
useFrameProcessor,
} from "components/Camera/helpers/visionCameraWrapper";
import InatVision from "components/Camera/helpers/visionPluginWrapper";
import type { Node } from "react";
import React, {
useEffect,
useRef,
useState
useState,
} from "react";
import { Platform } from "react-native";
import { Worklets } from "react-native-worklets-core";
@@ -17,7 +17,7 @@ import {
geomodelPath,
modelPath,
modelVersion,
taxonomyPath
taxonomyPath,
} from "sharedHelpers/mlModel";
import { logStage } from "sharedHelpers/sentinelFiles";
import usePatchedRunAsync from "sharedHelpers/visionCameraPatches";
@@ -75,7 +75,7 @@ const FrameProcessorCamera = ( {
inactive,
resetCameraOnFocus,
userLocation,
useLocation
useLocation,
}: Props ): Node => {
const sentinelFileName = useStore( state => state.sentinelFileName );
const { isDefaultMode } = useLayoutPrefs( );
@@ -185,8 +185,8 @@ const FrameProcessorCamera = ( {
location: {
latitude: geoModelCellLocation?.latitude,
longitude: geoModelCellLocation?.longitude,
elevation: geoModelCellLocation?.elevation
}
elevation: geoModelCellLocation?.elevation,
},
} );
const timeAfter = Date.now();
const timeTaken = timeAfter - timeBefore;
@@ -207,8 +207,8 @@ const FrameProcessorCamera = ( {
lastTimestamp,
fps,
useGeomodel,
geoModelCellLocation
]
geoModelCellLocation,
],
);
return (

View File

@@ -21,14 +21,14 @@ const LocationStatus = ( { useLocation, visible, onAnimationEnd }: Props ) => {
Animated.timing( opacity, {
toValue: 1,
duration: 200,
useNativeDriver: true
useNativeDriver: true,
} ),
Animated.delay( 2000 ),
Animated.timing( opacity, {
toValue: 0,
duration: 200,
useNativeDriver: true
} )
useNativeDriver: true,
} ),
] ).start( () => onAnimationEnd() );
}
}, [visible, opacity, onAnimationEnd] );

View File

@@ -2,7 +2,7 @@ import Slider from "@react-native-community/slider";
import {
Body1,
Heading4,
P
P,
} from "components/SharedComponents";
import { View } from "components/styledComponents";
import { round } from "lodash";
@@ -28,7 +28,7 @@ const SliderControl = ( {
min,
max,
precision = 0,
step = 1
step = 1,
}: SliderControlProps ) => (
<P>
{/* eslint-disable-next-line i18next/no-literal-string */}

View File

@@ -42,7 +42,7 @@ const usePredictions = ( ) => {
name: p.name,
rank_level: p.rank_level,
combined_score: p.combined_score,
taxon_id: p.taxon_id
taxon_id: p.taxon_id,
} ) )
.sort( ( a, b ) => a.rank_level - b.rank_level );
const branchIDs = branch.map( t => t.taxon_id );
@@ -56,10 +56,10 @@ const usePredictions = ( ) => {
rank_level: finestPrediction.rank_level,
id: finestPrediction.taxon_id,
name: finestPrediction.name,
iconic_taxon_name: iconicTaxon?.name
iconic_taxon_name: iconicTaxon?.name,
},
combined_score: finestPrediction.combined_score,
timestamp: cvResult.timestamp
timestamp: cvResult.timestamp,
};
}
setResult( prediction );
@@ -78,7 +78,7 @@ const usePredictions = ( ) => {
setConfidenceThreshold,
setFPS,
setNumStoredResults,
setCropRatio
setCropRatio,
};
};

View File

@@ -10,7 +10,7 @@ interface Props {
const CameraFlip = ( {
flipCamera,
cameraFlipClasses
cameraFlipClasses,
}: Props ) => {
const { t } = useTranslation( );

View File

@@ -23,7 +23,7 @@ const Flash = ( {
toggleFlash,
hasFlash,
takePhotoOptions,
flashClassName
flashClassName,
}: Props ) => {
const { t } = useTranslation( );
@@ -49,7 +49,7 @@ const Flash = ( {
className={classnames(
"m-0",
"border-0",
flashClassName
flashClassName,
)}
>
<TransparentCircleButton

View File

@@ -1,5 +1,5 @@
import {
INatIconButton
INatIconButton,
} from "components/SharedComponents";
import React from "react";
import type { GestureResponderEvent } from "react-native";
@@ -13,7 +13,7 @@ interface Props {
const GreenCheckmark = ( {
disabled,
handleCheckmarkPress
handleCheckmarkPress,
}: Props ) => {
const { t } = useTranslation( );

View File

@@ -17,7 +17,7 @@ interface Props {
const Location = ( {
rotatableAnimatedStyle,
toggleLocation,
useLocation
useLocation,
}: Props ) => {
const { t } = useTranslation( );

View File

@@ -19,7 +19,7 @@ const isTablet = DeviceInfo.isTablet();
const PhotoLibraryIcon = ( {
rotatableAnimatedStyle,
deleteSentinelFile,
disabled
disabled,
}: Props ) => {
const { t } = useTranslation( );
const navigation = useNavigation( );
@@ -36,14 +36,14 @@ const PhotoLibraryIcon = ( {
"justify-center",
"border-white",
"border-2",
"rounded"
"rounded",
)}
onPress={() => {
deleteSentinelFile();
navigation.push( "PhotoLibrary", {
cmonBack: true,
lastScreen: "Camera",
fromAICamera: true
fromAICamera: true,
} );
}}
accessibilityLabel={t( "Photo-importer" )}

View File

@@ -1,6 +1,6 @@
import classnames from "classnames";
import {
INatIcon
INatIcon,
} from "components/SharedComponents";
import { Pressable, View } from "components/styledComponents";
import React from "react";
@@ -19,7 +19,7 @@ interface Props {
const TakePhoto = ( {
takePhoto,
disabled,
showPrediction
showPrediction,
}: Props ) => {
const { t } = useTranslation( );
@@ -35,8 +35,8 @@ const TakePhoto = ( {
"justify-center",
"items-center",
{
"opacity-50": disabled
}
"opacity-50": disabled,
},
)}
onPress={takePhoto}
accessibilityLabel={t( "Take-photo" )}
@@ -51,7 +51,7 @@ const TakePhoto = ( {
<View
className={classnames(
borderClass,
"bg-inatGreen items-center justify-center border-accessibleGreen"
"bg-inatGreen items-center justify-center border-accessibleGreen",
)}
>
<INatIcon

View File

@@ -1,7 +1,7 @@
import classnames from "classnames";
import { Body3 } from "components/SharedComponents";
import {
CIRCLE_OPTIONS_CLASSES, CIRCLE_SIZE
CIRCLE_OPTIONS_CLASSES, CIRCLE_SIZE,
} from "components/SharedComponents/Buttons/TransparentCircleButton";
import { Pressable } from "components/styledComponents";
import React from "react";
@@ -23,7 +23,7 @@ const Zoom = ( {
rotatableAnimatedStyle,
handleZoomButtonPress,
zoomClassName,
zoomTextValue
zoomTextValue,
}: Props ) => {
const { t } = useTranslation();

View File

@@ -2,7 +2,7 @@ import { useNavigation, useRoute } from "@react-navigation/native";
import type { Camera } from "components/Camera/helpers/visionCameraWrapper";
import {
useCameraDevice,
useCameraDevices
useCameraDevices,
} from "components/Camera/helpers/visionCameraWrapper";
import { ActivityIndicator } from "components/SharedComponents";
import { View } from "components/styledComponents";
@@ -11,11 +11,11 @@ import React, {
useEffect,
useMemo,
useRef,
useState
useState,
} from "react";
import { Alert, StatusBar } from "react-native";
import type {
TakePhotoOptions
TakePhotoOptions,
} from "react-native-vision-camera";
import fetchAccurateUserLocation from "sharedHelpers/fetchAccurateUserLocation";
import { log } from "sharedHelpers/logger";
@@ -75,7 +75,7 @@ const CameraContainer = ( ) => {
const logStageIfAICamera = useCallback( async (
stageName: string,
stageData: string
stageData: string,
) => {
if ( cameraType !== "AI" ) { return; }
await logStage( sentinelFileName, stageName, stageData );
@@ -89,7 +89,7 @@ const CameraContainer = ( ) => {
const {
hasPermissions: hasLocationPermissions,
renderPermissionsGate: renderLocationPermissionsGate,
requestPermissions: requestLocationPermissions
requestPermissions: requestLocationPermissions,
} = useLocationPermission( );
// we don't want to use this for the observation location because
// a user could be walking with the camera open for a while, so this location
@@ -105,13 +105,13 @@ const CameraContainer = ( ) => {
physicalDevices: [
"ultra-wide-angle-camera",
"wide-angle-camera",
"telephoto-camera"
]
"telephoto-camera",
],
} );
const devices = useCameraDevices( );
const [loadingDevices, setLoadingDevices] = useState( true );
const [timeoutId, setTimeoutId] = useState<undefined | ReturnType<typeof setTimeout> | null>(
undefined
undefined,
);
if ( timeoutId === undefined ) {
setTimeoutId( setTimeout( () => {
@@ -125,7 +125,7 @@ const CameraContainer = ( ) => {
// We had this set to true in Seek but received many reports of it not respecting OS-wide sound
// level and scared away wildlife. So maybe better to just disable it.
enableShutterSound: false,
...( hasFlash && { flash: "off" } as const )
...( hasFlash && { flash: "off" } as const ),
} as const;
const [takePhotoOptions, setTakePhotoOptions] = useState<TakePhotoOptions>( initialPhotoOptions );
const [takingPhoto, setTakingPhoto] = useState( false );
@@ -146,7 +146,7 @@ const CameraContainer = ( ) => {
hasPermissions: hasSavePhotoPermission,
hasBlockedPermissions: hasBlockedSavePhotoPermission,
renderPermissionsGate: renderSavePhotoPermissionGate,
requestPermissions: requestSavePhotoPermission
requestPermissions: requestSavePhotoPermission,
} = useSavePhotoPermission( );
const showPhotoPermissionsGate = !( hasSavePhotoPermission || hasBlockedSavePhotoPermission );
@@ -163,7 +163,7 @@ const CameraContainer = ( ) => {
};
const navigationOptions = useMemo( ( ) => ( {
addPhotoPermissionResult
addPhotoPermissionResult,
} ), [addPhotoPermissionResult] );
const prepareStoreAndNavigate = usePrepareStoreAndNavigate( );
@@ -174,11 +174,11 @@ const CameraContainer = ( ) => {
const handleNavigation = useCallback( async (
newPhotoState: PhotoState,
hasSavePhotoPermission: boolean,
visionResult?: StoredResult | null
visionResult?: StoredResult | null,
) => {
startFirebaseTrace(
FIREBASE_TRACES.AI_CAMERA_TO_MATCH,
{ [FIREBASE_TRACE_ATTRIBUTES.HAS_SAVE_PHOTO_PERMISSION]: `${hasSavePhotoPermission}` }
{ [FIREBASE_TRACE_ATTRIBUTES.HAS_SAVE_PHOTO_PERMISSION]: `${hasSavePhotoPermission}` },
);
// fetch accurate user location, with a fallback to a course location
// at the time the user taps AI shutter or multicapture checkmark
@@ -192,19 +192,19 @@ const CameraContainer = ( ) => {
newPhotoState,
logStageIfAICamera,
deleteStageIfAICamera,
visionResult
visionResult,
} );
}, [
prepareStoreAndNavigate,
navigationOptions,
logStageIfAICamera,
deleteStageIfAICamera,
startFirebaseTrace
startFirebaseTrace,
] );
const handleCheckmarkPress = useCallback( async (
newPhotoState: PhotoState,
visionResult: StoredResult | null
visionResult: StoredResult | null,
) => {
if ( !showPhotoPermissionsGate ) {
await handleNavigation( newPhotoState, true, visionResult );
@@ -216,7 +216,7 @@ const CameraContainer = ( ) => {
handleNavigation,
requestSavePhotoPermission,
showPhotoPermissionsGate,
logStageIfAICamera
logStageIfAICamera,
] );
const toggleFlash = ( ) => {
@@ -224,13 +224,13 @@ const CameraContainer = ( ) => {
...takePhotoOptions,
flash: takePhotoOptions.flash === "on"
? "off"
: "on"
: "on",
} );
};
const updateTakePhotoStore = async (
uri: string,
options?: { replaceExisting?: boolean }
options?: { replaceExisting?: boolean },
): Promise<PhotoState> => {
const replaceExisting = options?.replaceExisting || false;
@@ -252,7 +252,7 @@ const CameraContainer = ( ) => {
const newCameraState = {
cameraUris: [...newCameraUris],
evidenceToAdd: [...newEvidenceToAdd]
evidenceToAdd: [...newEvidenceToAdd],
};
setCameraState( newCameraState );
@@ -316,11 +316,11 @@ const CameraContainer = ( ) => {
if ( !loadingDevices && !device ) {
Alert.alert(
t( "No-Camera-Available" ),
t( "Could-not-find-a-camera-on-this-device" )
t( "Could-not-find-a-camera-on-this-device" ),
);
logger.error(
"Camera started but no device was found. Length of the list of all devices: ",
devices.length
devices.length,
);
navigation.goBack();
return null;
@@ -362,23 +362,23 @@ const CameraContainer = ( ) => {
await logStageIfAICamera( "request_save_photo_permission_complete" );
await handleNavigation( {
cameraUris,
evidenceToAdd
evidenceToAdd,
}, true );
},
onModalHide: async ( ) => {
await logStageIfAICamera( "request_save_photo_permission_complete" );
await handleNavigation( {
cameraUris,
evidenceToAdd
evidenceToAdd,
}, false );
}
},
} )}
{renderLocationPermissionsGate( {
onRequestGranted: ( ) => console.log( "granted in location permission gate" ),
onRequestBlocked: ( ) => console.log( "blocked in location permission gate" ),
onModalHide: async ( ) => {
await logStageIfAICamera( "request_location_permission_complete" );
}
},
} )}
</>
);

View File

@@ -2,33 +2,33 @@ import { useAppState } from "@react-native-community/hooks";
import { useIsFocused } from "@react-navigation/native";
import {
Camera,
useCameraFormat
useCameraFormat,
} from "components/Camera/helpers/visionCameraWrapper";
import useFocusTap from "components/Camera/hooks/useFocusTap";
import React, {
useCallback
useCallback,
} from "react";
import {
Dimensions, Platform, StyleSheet
Dimensions, Platform, StyleSheet,
} from "react-native";
import type {
PanGesture,
PinchGesture
PinchGesture,
} from "react-native-gesture-handler";
import {
Gesture,
GestureDetector
GestureDetector,
} from "react-native-gesture-handler";
import Reanimated from "react-native-reanimated";
import type {
CameraDevice, CameraDeviceFormat, CameraProps, CameraRuntimeError
CameraDevice, CameraDeviceFormat, CameraProps, CameraRuntimeError,
} from "react-native-vision-camera";
import FocusSquare from "./FocusSquare";
const ReanimatedCamera = Reanimated.createAnimatedComponent( Camera );
Reanimated.addWhitelistedNativeProps( {
zoom: true
zoom: true,
} );
interface Props {
@@ -64,11 +64,11 @@ const CameraView = ( {
panToZoom,
pinchToZoom,
resizeMode,
inactive
inactive,
}: Props ) => {
const {
animatedStyle,
tapToFocus
tapToFocus,
} = useFocusTap( cameraRef, device.supportsFocus );
// check if camera page is active
@@ -87,15 +87,15 @@ const CameraView = ( {
{
videoAspectRatio: cameraScreen === "standard"
? standardVideoAspectRatio
: aiVideoAspectRatio
: aiVideoAspectRatio,
},
{
photoAspectRatio: cameraScreen === "standard"
? standardPhotoAspectRatio
: aiPhotoAspectRatio
: aiPhotoAspectRatio,
},
{ photoResolution: "max" },
{ videoResolution: "max" }
{ videoResolution: "max" },
] );
if ( Platform.OS === "android" ) {
console.log( "Android is not using a specific camera format because we never got around to" );
@@ -154,8 +154,8 @@ const CameraView = ( {
onClassifierError,
onDeviceNotSupported,
onCaptureError,
onCameraError
]
onCameraError,
],
);
// Note that overflow-hidden handles what seems to be a bug in android in

View File

@@ -21,7 +21,7 @@ interface Props {
takingPhoto: boolean;
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
takePhotoAndStoreUri: Function;
newPhotoUris: Array<object>;
newPhotoUris: object[];
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
setNewPhotoUris: Function;
takePhotoOptions: object;
@@ -44,7 +44,7 @@ const CameraWithDevice = ( {
takePhotoOptions,
userLocation,
hasLocationPermissions,
requestLocationPermissions
requestLocationPermissions,
}: Props ) => {
const { isLandscapeMode } = useDeviceOrientation( );
const flexDirection = isTablet && isLandscapeMode

View File

@@ -11,7 +11,7 @@ interface Props {
const fade = value => ( {
toValue: value,
duration: 100,
useNativeDriver: true
useNativeDriver: true,
} );
const FadeInOutView = ( { takingPhoto, cameraType }: Props ) => {
@@ -21,7 +21,7 @@ const FadeInOutView = ( { takingPhoto, cameraType }: Props ) => {
if ( takingPhoto ) {
Animated.sequence( [
Animated.timing( fadeAnimation, fade( 1 ) ),
Animated.timing( fadeAnimation, fade( 0 ) )
Animated.timing( fadeAnimation, fade( 0 ) ),
] ).start( );
}
}, [takingPhoto, fadeAnimation] );
@@ -37,7 +37,7 @@ const FadeInOutView = ( { takingPhoto, cameraType }: Props ) => {
height: "100%",
width: "100%",
backgroundColor: colors.black,
opacity: fadeAnimation
opacity: fadeAnimation,
}}
/>
{( takingPhoto && cameraType === "AI" ) && (

View File

@@ -1,5 +1,5 @@
import PermissionGateContainer, {
WRITE_MEDIA_PERMISSIONS
WRITE_MEDIA_PERMISSIONS,
} from "components/SharedComponents/PermissionGateContainer";
import React from "react";
import { useTranslation } from "sharedHooks";
@@ -15,7 +15,7 @@ const SavePhotoPermissionGate = ( {
onPhotoPermissionGranted,
onPhotoPermissionBlocked,
permissionNeeded,
onModalHide
onModalHide,
}: Props ) => {
const { t } = useTranslation( );
return (

View File

@@ -24,7 +24,7 @@ const CameraNavButtons = ( {
handleClose,
photosTaken,
rotatableAnimatedStyle,
takePhoto
takePhoto,
}: Props ): Node => {
const takePhotoButton = useMemo( ( ) => (
<TakePhoto

View File

@@ -41,7 +41,7 @@ const CameraOptionsButtons = ( {
takePhotoOptions,
handleZoomButtonPress,
zoomTextValue,
showZoomButton
showZoomButton,
}: Props ): Node => {
const renderPhoneCameraOptions = () => (
<>

View File

@@ -1,7 +1,7 @@
// @flow
import {
WarningSheet
WarningSheet,
} from "components/SharedComponents";
import type { Node } from "react";
import React from "react";
@@ -16,7 +16,7 @@ type Props = {
const DiscardChangesSheet = ( {
setShowDiscardSheet,
onDiscard,
hidden
hidden,
}: Props ): Node => {
const { t } = useTranslation( );

View File

@@ -3,16 +3,16 @@ import MediaViewerModal from "components/MediaViewer/MediaViewerModal";
import { ActivityIndicator, INatIconButton } from "components/SharedComponents";
import { ImageBackground, Pressable, View } from "components/styledComponents";
import React, {
useCallback, useRef, useState
useCallback, useRef, useState,
} from "react";
import {
FlatList
FlatList,
} from "react-native";
import Modal from "react-native-modal";
import type { SharedValue } from "react-native-reanimated";
import Animated, {
useAnimatedStyle,
withTiming
withTiming,
} from "react-native-reanimated";
import { useTranslation } from "sharedHooks";
import useStore from "stores/useStore";
@@ -37,13 +37,13 @@ const SMALL_PHOTO_CLASSES = [
"rounded-sm",
"w-[42px]",
"h-[42x]",
"mx-[3px]"
"mx-[3px]",
] as const;
const LARGE_PHOTO_CLASSES = [
"rounded-md",
"w-[83px]",
"h-[83px]",
"m-[8.5px]"
"m-[8.5px]",
] as const;
const PhotoCarousel = ( {
@@ -53,7 +53,7 @@ const PhotoCarousel = ( {
isTablet,
rotation,
photoUris,
onDelete
onDelete,
}: Props ) => {
const deletePhotoFromObservation = useStore( state => state.deletePhotoFromObservation );
const { t } = useTranslation( );
@@ -76,10 +76,10 @@ const PhotoCarousel = ( {
{
rotateZ: rotation
? withTiming( `${-1 * rotation.get( )}deg` )
: "0"
}
]
} )
: "0",
},
],
} ),
);
const renderSkeleton = useCallback( ( ) => ( takingPhoto
@@ -88,15 +88,15 @@ const PhotoCarousel = ( {
className={classnames(
"flex",
{
"w-fit h-full": isTablet && isLandscapeMode
"w-fit h-full": isTablet && isLandscapeMode,
},
...IMAGE_CONTAINER_CLASSES
...IMAGE_CONTAINER_CLASSES,
)}
>
<View
className={classnames(
"bg-lightGray justify-center",
...photoClasses
...photoClasses,
)}
>
<ActivityIndicator size={25} />
@@ -107,7 +107,7 @@ const PhotoCarousel = ( {
isTablet,
isLandscapeMode,
photoClasses,
takingPhoto
takingPhoto,
] );
const showDeletePhotoMode = useCallback( ( ) => {
@@ -119,12 +119,12 @@ const PhotoCarousel = ( {
const viewPhotoAtIndex = useCallback( ( index: number ) => {
setTappedPhotoIndex( index );
}, [
setTappedPhotoIndex
setTappedPhotoIndex,
] );
const renderPhotoOrEvidenceButton = useCallback( ( {
item: photoUri,
index
index,
} ) => (
<>
{index === 0 && renderSkeleton( )}
@@ -136,7 +136,7 @@ const PhotoCarousel = ( {
testID="PhotoCarousel.photo"
className={classnames(
"overflow-hidden",
...photoClasses
...photoClasses,
)}
>
<ImageBackground
@@ -145,7 +145,7 @@ const PhotoCarousel = ( {
"w-fit",
"h-full",
"flex",
...IMAGE_CONTAINER_CLASSES
...IMAGE_CONTAINER_CLASSES,
)}
>
{
@@ -196,7 +196,7 @@ const PhotoCarousel = ( {
renderSkeleton,
showDeletePhotoMode,
t,
viewPhotoAtIndex
viewPhotoAtIndex,
] );
const photoPreviewsList = (
@@ -231,7 +231,7 @@ const PhotoCarousel = ( {
height: isTablet && isLandscapeMode
? photoUris.length * ( photoDim + photoGutter ) + photoGutter
: photoDim + ( photoGutter * 2 ),
padding: photoGutter / 2
padding: photoGutter / 2,
};
return (
@@ -246,8 +246,8 @@ const PhotoCarousel = ( {
x: pageX,
y: pageY,
w,
h
} )
h,
} ),
)
}
// Dynamic calculation of these values kind of just doesn't work with tailwind.
@@ -276,7 +276,7 @@ const PhotoCarousel = ( {
position: "absolute",
left: containerPos.x,
top: containerPos.y,
...containerStyle
...containerStyle,
}}
>
{ photoPreviewsList }

View File

@@ -8,7 +8,7 @@ import PhotoCarousel, {
LARGE_PHOTO_DIM,
LARGE_PHOTO_GUTTER,
SMALL_PHOTO_DIM,
SMALL_PHOTO_GUTTER
SMALL_PHOTO_GUTTER,
} from "./PhotoCarousel";
interface Props {
@@ -24,7 +24,7 @@ interface Props {
const STYLE = {
justifyContent: "center",
flex: 0,
flexShrink: 1
flexShrink: 1,
} as const;
const PhotoPreview = ( {
@@ -34,7 +34,7 @@ const PhotoPreview = ( {
onDelete,
photoUris,
rotation,
takingPhoto
takingPhoto,
}: Props ) => {
const { t } = useTranslation( );
const wrapperDim = isLargeScreen
@@ -47,7 +47,7 @@ const PhotoPreview = ( {
"text-white",
"text-center",
"text-xl",
"w-full"
"w-full",
)}
>
{t( "Photos-you-take-will-appear-here" )}
@@ -64,7 +64,7 @@ const PhotoPreview = ( {
"w-[500px]",
"-rotate-90",
"left-[-190px]",
"top-[50%]"
"top-[50%]",
)}
>
{t( "Photos-you-take-will-appear-here" )}

View File

@@ -12,7 +12,7 @@ import type { Node } from "react";
import React, {
useCallback, useEffect,
useMemo,
useState
useState,
} from "react";
import DeviceInfo from "react-native-device-info";
import { Snackbar } from "react-native-paper";
@@ -29,7 +29,7 @@ import {
handleCameraError,
handleCaptureError,
handleClassifierError,
handleDeviceNotSupported
handleDeviceNotSupported,
} from "../helpers";
import CameraNavButtons from "./CameraNavButtons";
import CameraOptionsButtons from "./CameraOptionsButtons";
@@ -53,7 +53,7 @@ type Props = {
takingPhoto: boolean,
takePhotoAndStoreUri: Function,
takePhotoOptions: Object,
newPhotoUris: Array<Object>,
newPhotoUris: Object[],
setNewPhotoUris: Function
};
@@ -68,7 +68,7 @@ const StandardCamera = ( {
takePhotoAndStoreUri,
takePhotoOptions,
newPhotoUris,
setNewPhotoUris
setNewPhotoUris,
}: Props ): Node => {
"use no memo";
@@ -80,17 +80,17 @@ const StandardCamera = ( {
pinchToZoom,
resetZoom,
showZoomButton,
zoomTextValue
zoomTextValue,
} = useZoom( device );
const {
rotatableAnimatedStyle,
rotation
rotation,
} = useRotation( );
const navigation = useNavigation( );
const insets = useSafeAreaInsets();
const { loadTime } = usePerformance( {
isLoading: camera?.current !== null
isLoading: camera?.current !== null,
} );
if ( isDebugMode( ) && loadTime ) {
logger.info( loadTime );
@@ -103,7 +103,7 @@ const StandardCamera = ( {
const totalObsPhotoUris = useMemo(
( ) => [...cameraUris, ...photoLibraryUris].length,
[cameraUris, photoLibraryUris]
[cameraUris, photoLibraryUris],
);
const disallowAddingPhotos = totalObsPhotoUris >= MAX_PHOTOS_ALLOWED;
@@ -120,7 +120,7 @@ const StandardCamera = ( {
const {
handleBackButtonPress,
setShowDiscardSheet,
showDiscardSheet
showDiscardSheet,
} = useBackPress( photosTaken );
useFocusEffect(
@@ -132,7 +132,7 @@ const StandardCamera = ( {
// for this hook
// eslint-disable-next-line react-hooks/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [] )
}, [] ),
);
const deletePhotoByUri = useCallback( async ( photoUri: string ) => {

View File

@@ -2,10 +2,10 @@ import { useFocusEffect, useNavigation } from "@react-navigation/native";
import { getCurrentRoute } from "navigation/navigationUtils";
import {
useCallback,
useState
useState,
} from "react";
import {
BackHandler
BackHandler,
} from "react-native";
import useExitObservationFlow from "sharedHooks/useExitObservationFlow";
@@ -30,7 +30,7 @@ const useBackPress = ( shouldShowDiscardSheet: boolean ) => {
exitObservationFlow,
navigation,
setShowDiscardSheet,
shouldShowDiscardSheet
shouldShowDiscardSheet,
] );
useFocusEffect(
@@ -46,13 +46,13 @@ const useBackPress = ( shouldShowDiscardSheet: boolean ) => {
const backHandler = BackHandler.addEventListener( "hardwareBackPress", onBackPress );
return ( ) => backHandler.remove( );
}, [handleBackButtonPress] )
}, [handleBackButtonPress] ),
);
return {
handleBackButtonPress,
setShowDiscardSheet,
showDiscardSheet
showDiscardSheet,
};
};

View File

@@ -25,7 +25,7 @@ const checkmarkClasses = [
`h-[${CAMERA_BUTTON_DIM}px]`,
`w-[${CAMERA_BUTTON_DIM}px]`,
"justify-center",
"items-center"
"items-center",
].join( " " );
const cameraOptionsClasses = [
@@ -34,7 +34,7 @@ const cameraOptionsClasses = [
"items-center",
"justify-center",
"rounded-full",
`w-[${CAMERA_BUTTON_DIM}px]`
`w-[${CAMERA_BUTTON_DIM}px]`,
].join( " " );
interface Props {
@@ -70,7 +70,7 @@ const CameraButtonPlaceholder = ( { extraClassName }: { extraClassName?: string
// "bg-deeppink",
`w-[${CAMERA_BUTTON_DIM}px]`,
`h-[${CAMERA_BUTTON_DIM}px]`,
extraClassName
extraClassName,
)}
/>
);
@@ -95,7 +95,7 @@ const TabletButtons = ( {
useLocation,
toggleLocation,
isDefaultMode,
deleteSentinelFile
deleteSentinelFile,
}: Props ) => {
const tabletCameraOptionsClasses = [
"absolute",
@@ -104,7 +104,7 @@ const TabletButtons = ( {
"mr-5",
"p-0",
"right-0",
"h-full"
"h-full",
];
return (
@@ -155,7 +155,7 @@ const TabletButtons = ( {
<View
className={classnames(
cameraOptionsClasses,
{ "mt-[25px]": photosTaken }
{ "mt-[25px]": photosTaken },
)}
>
<CloseButton

View File

@@ -41,5 +41,5 @@ export {
handleCaptureError,
handleClassifierError,
handleDeviceNotSupported,
handleLog
handleLog,
};

View File

@@ -1,15 +1,15 @@
import {
rotatedOriginalPhotosPath
rotatedOriginalPhotosPath,
} from "appConstants/paths";
import RNFS from "react-native-fs";
import type {
PhotoFile
PhotoFile,
} from "react-native-vision-camera";
import resizeImage from "sharedHelpers/resizeImage";
import { unlink } from "sharedHelpers/util";
const savePhotoToDocumentsDirectory = async (
cameraPhoto: PhotoFile
cameraPhoto: PhotoFile,
) => {
const path = rotatedOriginalPhotosPath;
await RNFS.mkdir( path );
@@ -20,8 +20,8 @@ const savePhotoToDocumentsDirectory = async (
{
width: cameraPhoto.width,
height: cameraPhoto.height,
outputPath: path
}
outputPath: path,
},
);
// Remove original photo
await unlink( cameraPhoto.path );

View File

@@ -1,11 +1,11 @@
import { CameraRoll } from "@react-native-camera-roll/camera-roll";
import {
permissionResultFromMultiple,
READ_WRITE_MEDIA_PERMISSIONS
READ_WRITE_MEDIA_PERMISSIONS,
} from "components/SharedComponents/PermissionGateContainer";
import { t } from "i18next";
import {
Alert
Alert,
} from "react-native";
import { checkMultiple, RESULTS } from "react-native-permissions";
import { log } from "sharedHelpers/logger";
@@ -24,10 +24,10 @@ const logger = log.extend( "savePhotosToPhotoLibrary" );
// $FlowIgnore
export async function savePhotosToPhotoLibrary(
uris: [string],
location: object
location: object,
) {
const readWritePermissionResult = permissionResultFromMultiple(
await checkMultiple( READ_WRITE_MEDIA_PERMISSIONS )
await checkMultiple( READ_WRITE_MEDIA_PERMISSIONS ),
);
const savedPhotoUris = await uris.reduce(
async ( memo, uri ) => {
@@ -63,7 +63,7 @@ export async function savePhotosToPhotoLibrary(
Alert.alert(
t( "Not-enough-space-left-on-device" ),
t( "Not-enough-space-left-on-device-try-again" ),
[{ text: t( "OK" ) }]
[{ text: t( "OK" ) }],
);
return savedUris;
}
@@ -83,7 +83,7 @@ export async function savePhotosToPhotoLibrary(
// We need the initial value even if we're not using it, otherwise reduce
// will treat the first item in the array as the initial value and not
// call the reducer function on it
Promise.resolve( [] )
Promise.resolve( [] ),
);
return savedPhotoUris;
}

View File

@@ -5,7 +5,7 @@ import {
useCameraDevice,
useCameraDevices,
useCameraFormat,
useFrameProcessor
useFrameProcessor,
} from "react-native-vision-camera";
export {
@@ -13,5 +13,5 @@ export {
useCameraDevice,
useCameraDevices,
useCameraFormat,
useFrameProcessor
useFrameProcessor,
};

View File

@@ -1,9 +1,9 @@
import {
useCallback,
useState
useState,
} from "react";
import {
Alert
Alert,
} from "react-native";
import DeviceInfo from "react-native-device-info";
import { useTranslation } from "sharedHooks";
@@ -22,7 +22,7 @@ const useDeviceStorageFull = ( ) => {
const showStorageFullAlert = useCallback( () => Alert.alert(
t( "Device-storage-full" ),
t( "Device-storage-full-description" ),
[{ text: t( "OK" ) }]
[{ text: t( "OK" ) }],
), [t] );
DeviceInfo.getFreeDiskStorage().then( freeDiskStorage => {
@@ -33,7 +33,7 @@ const useDeviceStorageFull = ( ) => {
return {
deviceStorageFull,
showStorageFullAlert
showStorageFullAlert,
};
};

View File

@@ -1,15 +1,15 @@
import type { Camera } from "components/Camera/helpers/visionCameraWrapper";
import type React from "react";
import {
useCallback, useMemo, useRef, useState
useCallback, useMemo, useRef, useState,
} from "react";
import { Animated } from "react-native";
import type {
GestureStateChangeEvent,
TapGestureHandlerEventPayload
TapGestureHandlerEventPayload,
} from "react-native-gesture-handler";
import {
Gesture
Gesture,
} from "react-native-gesture-handler";
const HALF_SIZE_FOCUS_BOX = 33;
@@ -28,7 +28,7 @@ const useFocusTap = ( cameraRef: React.RefObject<Camera | null>, supportsFocus:
return ( {
left: tappedCoordinates.x - HALF_SIZE_FOCUS_BOX,
top: tappedCoordinates.y - HALF_SIZE_FOCUS_BOX,
opacity: focusOpacity
opacity: focusOpacity,
} );
}, [tappedCoordinates, focusOpacity] );
@@ -46,13 +46,13 @@ const useFocusTap = ( cameraRef: React.RefObject<Camera | null>, supportsFocus:
{
toValue: 0,
duration: 2000,
useNativeDriver: true
}
useNativeDriver: true,
},
).start( );
}, [
cameraRef,
supportsFocus,
focusOpacity
focusOpacity,
] );
const tapToFocus = useMemo( ( ) => Gesture.Tap( )
@@ -62,7 +62,7 @@ const useFocusTap = ( cameraRef: React.RefObject<Camera | null>, supportsFocus:
return {
animatedStyle,
tapToFocus,
tappedCoordinates
tappedCoordinates,
};
};

View File

@@ -1,13 +1,13 @@
import { useNavigation, useRoute } from "@react-navigation/native";
import useDeviceStorageFull from "components/Camera/hooks/useDeviceStorageFull";
import {
useCallback
useCallback,
} from "react";
import Observation from "realmModels/Observation";
import ObservationPhoto from "realmModels/ObservationPhoto";
import fetchPlaceName from "sharedHelpers/fetchPlaceName";
import {
useLayoutPrefs
useLayoutPrefs,
} from "sharedHooks";
import { SCREEN_AFTER_PHOTO_EVIDENCE } from "stores/createLayoutSlice";
import useStore from "stores/useStore";
@@ -39,7 +39,7 @@ const usePrepareStoreAndNavigate = ( ) => {
uris,
addPhotoPermissionResult,
userLocation,
logStageIfAICamera
logStageIfAICamera,
) => {
await logStageIfAICamera( "save_photos_to_photo_library_start" );
if ( addPhotoPermissionResult !== "granted" ) {
@@ -68,7 +68,7 @@ const usePrepareStoreAndNavigate = ( ) => {
deviceStorageFull,
setCameraState,
setSavingPhoto,
showStorageFullAlert
showStorageFullAlert,
] );
const createObsWithCameraPhotos = useCallback( async (
@@ -76,7 +76,7 @@ const usePrepareStoreAndNavigate = ( ) => {
addPhotoPermissionResult,
userLocation,
logStageIfAICamera,
visionResult
visionResult,
) => {
const newObservation = await Observation.new( );
@@ -93,7 +93,7 @@ const usePrepareStoreAndNavigate = ( ) => {
newObservation.observationPhotos = await ObservationPhoto
.createObsPhotosWithPosition( uris, {
position: 0,
local: true
local: true,
} );
if ( !isDefaultMode
&& screenAfterPhotoEvidence === SCREEN_AFTER_PHOTO_EVIDENCE.OBS_EDIT
@@ -105,26 +105,26 @@ const usePrepareStoreAndNavigate = ( ) => {
uris,
addPhotoPermissionResult,
userLocation,
logStageIfAICamera
logStageIfAICamera,
);
}, [
isDefaultMode,
screenAfterPhotoEvidence,
setObservations,
handleSavingToPhotoLibrary
handleSavingToPhotoLibrary,
] );
const updateObsWithCameraPhotos = useCallback( async (
addPhotoPermissionResult,
userLocation,
logStageIfAICamera
logStageIfAICamera,
) => {
const obsPhotos = await ObservationPhoto.createObsPhotosWithPosition(
evidenceToAdd,
{
position: numOfObsPhotos,
local: true
}
local: true,
},
);
const updatedCurrentObservation = Observation
.appendObsPhotos( obsPhotos, currentObservation );
@@ -137,7 +137,7 @@ const usePrepareStoreAndNavigate = ( ) => {
evidenceToAdd,
addPhotoPermissionResult,
userLocation,
logStageIfAICamera
logStageIfAICamera,
);
}, [
evidenceToAdd,
@@ -146,7 +146,7 @@ const usePrepareStoreAndNavigate = ( ) => {
observations,
currentObservationIndex,
updateObservations,
handleSavingToPhotoLibrary
handleSavingToPhotoLibrary,
] );
const prepareStoreAndNavigate = useCallback( async ( {
@@ -155,7 +155,7 @@ const usePrepareStoreAndNavigate = ( ) => {
newPhotoState,
logStageIfAICamera,
deleteStageIfAICamera,
visionResult
visionResult,
} ) => {
if ( userLocation !== null ) {
logStageIfAICamera( "fetch_user_location_complete" );
@@ -175,7 +175,7 @@ const usePrepareStoreAndNavigate = ( ) => {
addPhotoPermissionResult,
userLocation,
logStageIfAICamera,
visionResult
visionResult,
);
await deleteStageIfAICamera( );
setSentinelFileName( null );
@@ -184,13 +184,13 @@ const usePrepareStoreAndNavigate = ( ) => {
if ( isDefaultMode ) {
return navigation.push( "Match", {
entryScreen: "CameraWithDevice",
lastScreen: "CameraWithDevice"
lastScreen: "CameraWithDevice",
} );
}
// Camera navigates based on user settings to Match, Suggestions, or ObsEdit
return navigation.push( screenAfterPhotoEvidence, {
entryScreen: "CameraWithDevice",
lastScreen: "CameraWithDevice"
lastScreen: "CameraWithDevice",
} );
}, [
cameraUris,
@@ -200,7 +200,7 @@ const usePrepareStoreAndNavigate = ( ) => {
navigation,
updateObsWithCameraPhotos,
screenAfterPhotoEvidence,
isDefaultMode
isDefaultMode,
] );
return prepareStoreAndNavigate;

View File

@@ -2,12 +2,12 @@ import { useEffect } from "react";
import {
useAnimatedStyle,
useSharedValue,
withTiming
withTiming,
} from "react-native-reanimated";
import useDeviceOrientation, {
LANDSCAPE_LEFT,
LANDSCAPE_RIGHT,
PORTRAIT_UPSIDE_DOWN
PORTRAIT_UPSIDE_DOWN,
} from "sharedHooks/useDeviceOrientation";
const rotationValue = ( deviceOrientation: string | undefined ) => {
@@ -36,15 +36,15 @@ const useRotation = ( ) => {
() => ( {
transform: [
{
rotateZ: withTiming( `${-1 * ( rotation.get( ) || 0 )}deg` )
}
]
} )
rotateZ: withTiming( `${-1 * ( rotation.get( ) || 0 )}deg` ),
},
],
} ),
);
return {
rotatableAnimatedStyle,
rotation
rotation,
};
};

View File

@@ -1,11 +1,11 @@
import {
permissionResultFromMultiple,
WRITE_MEDIA_PERMISSIONS
WRITE_MEDIA_PERMISSIONS,
} from "components/SharedComponents/PermissionGateContainer";
import React, { useCallback, useEffect, useState } from "react";
import {
checkMultiple,
RESULTS
RESULTS,
} from "react-native-permissions";
import SavePhotoPermissionGate from "../SavePhotoPermissionGate";
@@ -38,7 +38,7 @@ const useSavePhotoPermission = ( ) => {
const {
onPermissionGranted,
onPermissionBlocked,
onModalHide
onModalHide,
} = callbacks || { };
// this prevents infinite rerenders of the SavePhotoPermissionGate component
@@ -74,12 +74,12 @@ const useSavePhotoPermission = ( ) => {
// referential stability
const requestPermissions = useCallback(
( ) => setShowPermissionGate( true ),
[]
[],
);
const checkPermissions = useCallback( async () => {
const permissionsResult = permissionResultFromMultiple(
await checkMultiple( WRITE_MEDIA_PERMISSIONS )
await checkMultiple( WRITE_MEDIA_PERMISSIONS ),
);
if ( permissionsResult === RESULTS.GRANTED ) {
setHasPermissions( true );
@@ -101,7 +101,7 @@ const useSavePhotoPermission = ( ) => {
renderPermissionsGate,
requestPermissions,
hasBlockedPermissions,
checkPermissions
checkPermissions,
};
};

View File

@@ -3,10 +3,10 @@ import {
useCallback,
useEffect,
useMemo,
useState
useState,
} from "react";
import {
Gesture
Gesture,
} from "react-native-gesture-handler";
import {
Extrapolation,
@@ -14,7 +14,7 @@ import {
runOnJS,
useAnimatedProps,
useSharedValue,
withSpring
withSpring,
} from "react-native-reanimated";
import type { CameraDevice, CameraProps } from "react-native-vision-camera";
@@ -98,7 +98,7 @@ const useZoom = ( device: CameraDevice ): object => {
Math.abs( curr - newZoom ) < Math.abs( prev - newZoom )
? curr
: prev );
}
},
);
setZoomTextValue( closestZoomTextValue );
}, [zoomButtonOptions, minZoom] );
@@ -110,7 +110,7 @@ const useZoom = ( device: CameraDevice ): object => {
newValue,
[-1, 0, 1],
[minZoom, startZoom.get( ), maxZoomWithPinch],
Extrapolation.CLAMP
Extrapolation.CLAMP,
);
zoom.set( newZoom );
@@ -118,7 +118,7 @@ const useZoom = ( device: CameraDevice ): object => {
}, [maxZoomWithPinch, minZoom, updateZoomTextValue, startZoom, zoom] );
const animatedProps = useAnimatedProps < CameraProps >(
() => ( { zoom: zoom.get( ) } )
() => ( { zoom: zoom.get( ) } ),
);
const pinchToZoom = useMemo( ( ) => Gesture.Pinch( )
@@ -133,12 +133,12 @@ const useZoom = ( device: CameraDevice ): object => {
e.scale,
[1 - 1 / SCALE_FULL_ZOOM, 1, SCALE_FULL_ZOOM],
[-1, 0, 1],
Extrapolation.CLAMP
Extrapolation.CLAMP,
);
onZoomChange( newValue );
} ), [
onZoomChange,
onZoomStart
onZoomStart,
] );
const yDiff = useSharedValue( 0 );
@@ -159,7 +159,7 @@ const useZoom = ( device: CameraDevice ): object => {
yDiff.get( ),
[PAN_ZOOM_MIN_DISTANCE, 0, PAN_ZOOM_MAX_DISTANCE],
[-1, 0, 1],
Extrapolation.CLAMP
Extrapolation.CLAMP,
) * -1;
onZoomChange( newValue );
} )
@@ -174,7 +174,7 @@ const useZoom = ( device: CameraDevice ): object => {
pinchToZoom,
resetZoom,
showZoomButton: device?.isMultiCam,
zoomTextValue
zoomTextValue,
};
};

View File

@@ -6,7 +6,7 @@ import {
Button,
Heading1,
Heading2,
ScrollViewWrapper
ScrollViewWrapper,
} from "components/SharedComponents";
import { fontMonoClass, View } from "components/styledComponents";
import { t } from "i18next";
@@ -20,7 +20,7 @@ import useLogs from "sharedHooks/useLogs";
import type { DirectoryEntrySize } from "./hooks/useAppSize";
import useAppSize, {
formatAppSizeString, formatSizeUnits, getTotalDirectorySize
formatAppSizeString, formatSizeUnits, getTotalDirectorySize,
} from "./hooks/useAppSize";
const H1 = ( { children }: PropsWithChildren ) => (
@@ -47,7 +47,7 @@ const CODE = ( { children, optionalClassName }: CODEProps ) => (
selectable
className={classnames(
fontMonoClass,
optionalClassName
optionalClassName,
)}
>
{children}
@@ -56,21 +56,21 @@ const CODE = ( { children, optionalClassName }: CODEProps ) => (
const modelFileName = Platform.select( {
ios: Config.IOS_MODEL_FILE_NAME,
android: Config.ANDROID_MODEL_FILE_NAME
android: Config.ANDROID_MODEL_FILE_NAME,
} );
const taxonomyFileName = Platform.select( {
ios: Config.IOS_TAXONOMY_FILE_NAME,
android: Config.ANDROID_TAXONOMY_FILE_NAME
android: Config.ANDROID_TAXONOMY_FILE_NAME,
} );
const geomodelFileName = Platform.select( {
ios: Config.IOS_GEOMODEL_FILE_NAME,
android: Config.ANDROID_GEOMODEL_FILE_NAME
android: Config.ANDROID_GEOMODEL_FILE_NAME,
} );
const boldClassname = ( line: string, isDirectory = false ) => classnames(
{
"text-red font-bold": line.includes( "MB" ),
"text-blue": isDirectory
}
"text-blue": isDirectory,
},
);
interface DirectorySizesProps {
@@ -167,8 +167,8 @@ const Developer = () => {
status: 422,
context: {
routeName: "MyObservations",
timestamp: new Date().toISOString()
}
timestamp: new Date().toISOString(),
},
} );
}}
text="TEST INATAPIERROR"
@@ -178,7 +178,7 @@ const Developer = () => {
onPress={() => {
throw new INatApiTooManyRequestsError( {
routeName: "TaxonDetails",
timestamp: new Date().toISOString()
timestamp: new Date().toISOString(),
} );
}}
text="TEST API TOO MANY REQUESTS ERROR"

View File

@@ -3,18 +3,18 @@ import {
Button,
Heading4,
INatIconButton,
ScrollViewWrapper
ScrollViewWrapper,
} from "components/SharedComponents";
import {
fontMonoClass,
TextInput,
View
View,
} from "components/styledComponents";
import type { Node } from "react";
import React, {
useCallback,
useEffect,
useState
useState,
} from "react";
import { Platform, Text } from "react-native";
import useLogs from "sharedHooks/useLogs";
@@ -47,7 +47,7 @@ const Log = (): Node => {
useEffect(
( ) => navigation.setOptions( { headerRight } ),
[headerRight, navigation]
[headerRight, navigation],
);
useEffect( ( ) => {

View File

@@ -1,16 +1,16 @@
import { useNavigation } from "@react-navigation/native";
import {
Body1,
ViewWrapper
ViewWrapper,
} from "components/SharedComponents";
import {
Pressable
Pressable,
} from "components/styledComponents";
import { sortBy } from "lodash";
import type { Node } from "react";
import React from "react";
import {
FlatList
FlatList,
} from "react-native";
// Note: you need to add here and in UiLibraryItem
@@ -24,7 +24,7 @@ const ITEMS = sortBy( [
{ title: "TaxonResult", component: "TaxonResultDemo" },
{ title: "ObsGridItem", component: "ObsGridItemDemo" },
{ title: "TaxonGridItem", component: "TaxonGridItemDemo" },
{ title: "PivotCards", component: "PivotCardsDemo" }
{ title: "PivotCards", component: "PivotCardsDemo" },
], item => item.title );
ITEMS.push( { title: "Everything Else", component: "Misc" } );

View File

@@ -1,5 +1,5 @@
import {
ActivityIndicator
ActivityIndicator,
} from "components/SharedComponents";
import { View } from "components/styledComponents";
import React from "react";

Some files were not shown because too many files have changed in this diff Show More