mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
MOB-1077: merge main
This commit is contained in:
38
.detoxrc.js
38
.detoxrc.js
@@ -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",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
65
.eslintrc.js
65
.eslintrc.js
@@ -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"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,10 +9,10 @@ export default ( {
|
||||
_compressFormat,
|
||||
_quality,
|
||||
_rotation,
|
||||
outputPath
|
||||
outputPath,
|
||||
) => {
|
||||
const filename = mockNodePath.basename( path );
|
||||
return { uri: mockNodePath.join( outputPath, filename ) };
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
} );
|
||||
|
||||
@@ -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( ) ),
|
||||
};
|
||||
|
||||
@@ -3,6 +3,6 @@ export default ( {
|
||||
watchPosition: jest.fn( ( ) => 0 ),
|
||||
clearWatch: jest.fn( ),
|
||||
setRNConfiguration: jest.fn( ( ) => ( {
|
||||
skipPermissionRequests: true
|
||||
} ) )
|
||||
skipPermissionRequests: true,
|
||||
} ) ),
|
||||
} );
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
} );
|
||||
|
||||
@@ -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( ) );
|
||||
} )
|
||||
} ),
|
||||
};
|
||||
|
||||
@@ -3,6 +3,6 @@ export default ( {
|
||||
`Somewhere near ${coord.lat}, ${coord.lng}`,
|
||||
"Somewhere",
|
||||
"Somewheria",
|
||||
"SW"
|
||||
] )
|
||||
"SW",
|
||||
] ),
|
||||
} );
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
module.exports = {
|
||||
KeyboardAwareScrollView: jest
|
||||
.fn( )
|
||||
.mockImplementation( ( { children } ) => children )
|
||||
.mockImplementation( ( { children } ) => children ),
|
||||
};
|
||||
|
||||
@@ -2,5 +2,5 @@ export const useDeviceOrientationChange = jest.fn();
|
||||
|
||||
export default ( {
|
||||
lockToPortrait: jest.fn( ),
|
||||
unlockAllOrientations: jest.fn( )
|
||||
unlockAllOrientations: jest.fn( ),
|
||||
} );
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const actualPaper = jest.requireActual( "react-native-paper" );
|
||||
module.exports = {
|
||||
...actualPaper,
|
||||
Portal: ( { children } ) => children
|
||||
Portal: ( { children } ) => children,
|
||||
};
|
||||
|
||||
@@ -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 } ),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const ShareMenu = {
|
||||
__reset: ( ) => {
|
||||
mockShareData = null;
|
||||
mockListeners = [];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default ShareMenu;
|
||||
|
||||
@@ -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( );
|
||||
|
||||
@@ -2,6 +2,6 @@ export const useSharedValue = jest.fn();
|
||||
export const Worklets = {
|
||||
createRunOnJS: jest.fn(),
|
||||
defaultContext: {
|
||||
createRunAsync: jest.fn()
|
||||
}
|
||||
createRunAsync: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
@@ -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"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
},
|
||||
);
|
||||
} );
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
by,
|
||||
element,
|
||||
waitFor
|
||||
waitFor,
|
||||
} from "detox";
|
||||
|
||||
const VISIBILITY_TIMEOUT = 10_000;
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
by, element, expect, waitFor
|
||||
by, element, expect, waitFor,
|
||||
} from "detox";
|
||||
import Config from "react-native-config-node";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
by, element, waitFor
|
||||
by, element, waitFor,
|
||||
} from "detox";
|
||||
|
||||
const TIMEOUT = 10_000;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
device,
|
||||
element,
|
||||
expect,
|
||||
waitFor
|
||||
waitFor,
|
||||
} from "detox";
|
||||
|
||||
import { iNatE2eAfterEach, iNatE2eBeforeAll, iNatE2eBeforeEach } from "./helpers";
|
||||
|
||||
22
index.js
22
index.js
@@ -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 = ( ) => {
|
||||
|
||||
@@ -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}]
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module.exports = {
|
||||
assets: ["./assets/fonts/"]
|
||||
assets: ["./assets/fonts/"],
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> => {
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 } },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -16,5 +16,5 @@ export const CHUCKS_PAD = {
|
||||
altitude: 120.0234,
|
||||
altitudeAccuracy: 2.123,
|
||||
heading: null,
|
||||
speed: null
|
||||
speed: null,
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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( );
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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( );
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -78,7 +78,7 @@ const AICameraButtons = ( {
|
||||
zoomTextValue,
|
||||
useLocation,
|
||||
toggleLocation,
|
||||
deleteSentinelFile
|
||||
deleteSentinelFile,
|
||||
}: Props ) => {
|
||||
const { isDefaultMode } = useLayoutPrefs();
|
||||
if ( isTablet ) {
|
||||
|
||||
@@ -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 )}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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] );
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
|
||||
const CameraFlip = ( {
|
||||
flipCamera,
|
||||
cameraFlipClasses
|
||||
cameraFlipClasses,
|
||||
}: Props ) => {
|
||||
const { t } = useTranslation( );
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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( );
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ interface Props {
|
||||
const Location = ( {
|
||||
rotatableAnimatedStyle,
|
||||
toggleLocation,
|
||||
useLocation
|
||||
useLocation,
|
||||
}: Props ) => {
|
||||
const { t } = useTranslation( );
|
||||
|
||||
|
||||
@@ -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" )}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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" );
|
||||
}
|
||||
},
|
||||
} )}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" ) && (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -24,7 +24,7 @@ const CameraNavButtons = ( {
|
||||
handleClose,
|
||||
photosTaken,
|
||||
rotatableAnimatedStyle,
|
||||
takePhoto
|
||||
takePhoto,
|
||||
}: Props ): Node => {
|
||||
const takePhotoButton = useMemo( ( ) => (
|
||||
<TakePhoto
|
||||
|
||||
@@ -41,7 +41,7 @@ const CameraOptionsButtons = ( {
|
||||
takePhotoOptions,
|
||||
handleZoomButtonPress,
|
||||
zoomTextValue,
|
||||
showZoomButton
|
||||
showZoomButton,
|
||||
}: Props ): Node => {
|
||||
const renderPhoneCameraOptions = () => (
|
||||
<>
|
||||
|
||||
@@ -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( );
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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" )}
|
||||
|
||||
@@ -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 ) => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -41,5 +41,5 @@ export {
|
||||
handleCaptureError,
|
||||
handleClassifierError,
|
||||
handleDeviceNotSupported,
|
||||
handleLog
|
||||
handleLog,
|
||||
};
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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( ( ) => {
|
||||
|
||||
@@ -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" } );
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user