From e04a183f35563c284f7389cc14ba6346f95f8cb1 Mon Sep 17 00:00:00 2001 From: Abbey Campbell Date: Fri, 7 Nov 2025 18:29:23 -0800 Subject: [PATCH 01/67] consider incomplete observations uploadable for logged out users --- src/components/MyObservations/MyObservationsSimple.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MyObservations/MyObservationsSimple.tsx b/src/components/MyObservations/MyObservationsSimple.tsx index a541d407c..8a5475933 100644 --- a/src/components/MyObservations/MyObservationsSimple.tsx +++ b/src/components/MyObservations/MyObservationsSimple.tsx @@ -205,7 +205,8 @@ const MyObservationsSimple = ( { numUnuploadedObservations > 0 && numUnuploadedObsMissingBasics > 0 ), [numUnuploadedObservations, numUnuploadedObsMissingBasics] ); - const numUploadableObservations = isDefaultMode + // if user is not logged in, we'll consider all obs 'uploadable' to shepherd people to login flow + const numUploadableObservations = isDefaultMode && !!currentUser ? numUnuploadedObservations - numUnuploadedObsMissingBasics : numUnuploadedObservations; From 3c31bc228a1d91bc9e13542d0fe8c23cd0ed21b7 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Mon, 10 Nov 2025 13:40:11 -0500 Subject: [PATCH 02/67] Migrate `src/api/translations` to TypeScript. --- src/api/error.ts | 2 +- src/api/translations.js | 20 -------------------- src/api/translations.ts | 20 ++++++++++++++++++++ 3 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 src/api/translations.js create mode 100644 src/api/translations.ts diff --git a/src/api/error.ts b/src/api/error.ts index 4648d84ab..e8cf77031 100644 --- a/src/api/error.ts +++ b/src/api/error.ts @@ -68,7 +68,7 @@ interface HandleErrorOptions { onApiError?: ( error: INatApiError ) => void; } -interface ErrorWithResponse { +export interface ErrorWithResponse { response?: { status: number; url: string; diff --git a/src/api/translations.js b/src/api/translations.js deleted file mode 100644 index 145daffcb..000000000 --- a/src/api/translations.js +++ /dev/null @@ -1,20 +0,0 @@ -// @flow - -import inatjs from "inaturalistjs"; - -import handleError from "./error"; - -const fetchAvailableLocales = async ( - params: Object = {}, - opts: Object = {} -): Promise => { - try { - const response = await inatjs.translations.locales( params, opts ); - if ( !response ) { return null; } - return response?.results; - } catch ( e ) { - return handleError( e, { context: { functionName: "fetchAvailableLocales", opts } } ); - } -}; - -export default fetchAvailableLocales; diff --git a/src/api/translations.ts b/src/api/translations.ts new file mode 100644 index 000000000..4c8bce14c --- /dev/null +++ b/src/api/translations.ts @@ -0,0 +1,20 @@ +import handleError, { ErrorWithResponse, INatApiError } from "api/error"; +import inatjs from "inaturalistjs"; + +const fetchAvailableLocales = async ( + params: Record = {}, + opts: Record = {} +): Promise | null | ErrorWithResponse | INatApiError> => { + try { + const response = await inatjs.translations.locales( params, opts ); + if ( !response ) { return null; } + return response?.results; + } catch ( e ) { + return handleError( + e as ErrorWithResponse, + { context: { functionName: "fetchAvailableLocales", opts } } + ); + } +}; + +export default fetchAvailableLocales; From e87de2c7c4b806976e05b4d81d609074a62f092e Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Mon, 10 Nov 2025 13:53:56 -0500 Subject: [PATCH 03/67] Simplify `name` property setting on API error classes. --- src/api/error.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/api/error.ts b/src/api/error.ts index 4648d84ab..e2db52330 100644 --- a/src/api/error.ts +++ b/src/api/error.ts @@ -18,15 +18,12 @@ export class INatApiError extends Error { context?: Record | null ) { super( JSON.stringify( json ) ); + this.name = "INatApiError"; this.json = json; this.status = status || Number( json.status ); this.context = context || null; } } -// https://wbinnssmith.com/blog/subclassing-error-in-modern-javascript/ -Object.defineProperty( INatApiError.prototype, "name", { - value: "INatApiError" -} ); export class INatApiUnauthorizedError extends INatApiError { constructor( context?: Record ) { @@ -36,12 +33,9 @@ export class INatApiUnauthorizedError extends INatApiError { context }; super( errorJson, 401, context ); + this.name = "INatApiUnauthorizedError"; } } -// https://wbinnssmith.com/blog/subclassing-error-in-modern-javascript/ -Object.defineProperty( INatApiUnauthorizedError.prototype, "name", { - value: "INatApiUnauthorizedError" -} ); export class INatApiTooManyRequestsError extends INatApiError { constructor( context?: Record ) { @@ -51,13 +45,10 @@ export class INatApiTooManyRequestsError extends INatApiError { context }; super( errorJson, 429, context ); + this.name = "INatApiTooManyRequestsError"; } } -Object.defineProperty( INatApiTooManyRequestsError.prototype, "name", { - value: "INatApiTooManyRequestsError" -} ); - interface HandleErrorOptions { queryKey?: unknown[]; failureCount?: number; From 4c8d4002dfda2c7dc016fdb1c98b9f2aa38a119d Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Mon, 10 Nov 2025 16:32:07 -0500 Subject: [PATCH 04/67] Introduce a type declaration file for Tailwind config colors. --- src/styles/tailwindColors.d.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/styles/tailwindColors.d.ts diff --git a/src/styles/tailwindColors.d.ts b/src/styles/tailwindColors.d.ts new file mode 100644 index 000000000..b95a4265f --- /dev/null +++ b/src/styles/tailwindColors.d.ts @@ -0,0 +1,30 @@ +declare module "styles/tailwindColors" { + export interface TailwindColors { + accessibleGreen: string; + black: string; + darkGray: string; + darkGrayDisabled: string; + darkModeGray: string; + inatGreen: string; + inatGreenDisabled: string; + inatGreenDisabledDark: string; + lightGray: string; + mediumGray: string; + mediumGrayGhost: string; + warningRed: string; + warningRedDisabled: string; + warningYellow: string; + white: string; + yellow: string; + red: string; + green: string; + blue: string; + deepPink: string; + deeppink: string; + DeepPink: string; + orange: string; + } + + const colors: TailwindColors; + export default colors; +} From e081b3a41403673cf6009d928dcec2833237b2d6 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Mon, 10 Nov 2025 16:38:23 -0500 Subject: [PATCH 05/67] Replace usages of `React.Node` with `React.ReactNode`. The React TypeScript types define the latter, but not the former. --- src/components/MyObservations/LoginBanner.tsx | 2 +- .../SharedComponents/FlashList/FlashListEmptyWrapper.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MyObservations/LoginBanner.tsx b/src/components/MyObservations/LoginBanner.tsx index 0ab2f4c51..646f83137 100644 --- a/src/components/MyObservations/LoginBanner.tsx +++ b/src/components/MyObservations/LoginBanner.tsx @@ -23,7 +23,7 @@ type Props = { const LoginBanner = ( { currentUser -}: Props ): React.Node => { +}: Props ): React.ReactNode => { const { t } = useTranslation( ); const navigation = useNavigation(); const loginBannerDismissed = useStore( state => state.layout.loginBannerDismissed ); diff --git a/src/components/SharedComponents/FlashList/FlashListEmptyWrapper.tsx b/src/components/SharedComponents/FlashList/FlashListEmptyWrapper.tsx index cade359b0..49c69d10e 100644 --- a/src/components/SharedComponents/FlashList/FlashListEmptyWrapper.tsx +++ b/src/components/SharedComponents/FlashList/FlashListEmptyWrapper.tsx @@ -7,7 +7,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; const FOOTER_HEIGHT = 77; interface Props { - children: React.Node; + children: React.ReactNode; containerClassName?: string; headerHeight: number; emptyItemHeight: number; From ad1f0da4332f56575465b7229cdb2f05488f07f0 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Mon, 10 Nov 2025 16:45:24 -0500 Subject: [PATCH 06/67] Remove unnecessary type casting in `src/sharedHelpers/taxon.ts`. --- src/sharedHelpers/taxon.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sharedHelpers/taxon.ts b/src/sharedHelpers/taxon.ts index 86598c18f..ee04a8c39 100644 --- a/src/sharedHelpers/taxon.ts +++ b/src/sharedHelpers/taxon.ts @@ -113,7 +113,7 @@ interface TaxonDisplayData { scientificName?: string; } -export const generateTaxonPieces = ( taxon: Taxon ) => { +export const generateTaxonPieces = ( taxon: Taxon ): TaxonDisplayData => { const taxonDisplayData: Partial = {}; if ( taxon.rank ) taxonDisplayData.rank = capitalize( taxon.rank ); @@ -151,7 +151,7 @@ export const generateTaxonPieces = ( taxon: Taxon ) => { taxonDisplayData.scientificNamePieces = scientificNamePieces; taxonDisplayData.scientificName = scientificNamePieces?.join( " " ); - return taxonDisplayData as TaxonDisplayData; + return taxonDisplayData; }; interface User { From 6fe7e4ed24c2b9b985f65c3d90c7c22765ad8085 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 11 Nov 2025 01:02:14 -0500 Subject: [PATCH 07/67] Migrate `src/sharedHooks/useDebugMode` to TypeScript. --- src/sharedHooks/{useDebugMode.js => useDebugMode.ts} | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename src/sharedHooks/{useDebugMode.js => useDebugMode.ts} (90%) diff --git a/src/sharedHooks/useDebugMode.js b/src/sharedHooks/useDebugMode.ts similarity index 90% rename from src/sharedHooks/useDebugMode.js rename to src/sharedHooks/useDebugMode.ts index 35d2fbbe3..fe2ae6152 100644 --- a/src/sharedHooks/useDebugMode.js +++ b/src/sharedHooks/useDebugMode.ts @@ -1,11 +1,9 @@ -// @flow - import { useCallback, useEffect, useState } from "react"; import { zustandStorage } from "stores/useStore"; const DEBUG_MODE = "debugMode"; -const useDebugMode = ( ): { isDebug: boolean, toggleDebug: Function } => { +const useDebugMode = ( ): { isDebug: boolean, toggleDebug: () => void } => { const [isDebug, setDebug] = useState( false ); useEffect( ( ) => { From 89d5d258c2c62edd5887f30b2f3af3279f13660d Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 11 Nov 2025 01:05:28 -0500 Subject: [PATCH 08/67] Improve return typing of `src/sharedHooks/useAuthenticatedQuery` function. --- src/sharedHooks/useAuthenticatedQuery.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sharedHooks/useAuthenticatedQuery.ts b/src/sharedHooks/useAuthenticatedQuery.ts index ff4364bb2..e0f230834 100644 --- a/src/sharedHooks/useAuthenticatedQuery.ts +++ b/src/sharedHooks/useAuthenticatedQuery.ts @@ -11,13 +11,13 @@ interface QueryOptions { retry?: boolean; } -type QueryFunction = ( options: { api_token: string | null } ) => Promise; +type QueryFunction = ( options: { api_token: string | null } ) => Promise; // Should work like React Query's useQuery except it calls the queryFunction // with an object that includes the JWT -const useAuthenticatedQuery = ( +const useAuthenticatedQuery = ( queryKey: string[], - queryFunction: QueryFunction, + queryFunction: QueryFunction, queryOptions: QueryOptions = {} ) => { const [userLoggedIn, setUserLoggedIn] = useState( LOGGED_IN_UNKNOWN ); From a085434ae35fb8353a74bc9f3f8d218e4f9555a2 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 11 Nov 2025 01:17:47 -0500 Subject: [PATCH 09/67] Migrate `src/components/SharedComponents/ObsDetails/ContentWithIcon` to TypeScript. --- .../ObsDetails/{ContentWithIcon.js => ContentWithIcon.tsx} | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename src/components/SharedComponents/ObsDetails/{ContentWithIcon.js => ContentWithIcon.tsx} (88%) diff --git a/src/components/SharedComponents/ObsDetails/ContentWithIcon.js b/src/components/SharedComponents/ObsDetails/ContentWithIcon.tsx similarity index 88% rename from src/components/SharedComponents/ObsDetails/ContentWithIcon.js rename to src/components/SharedComponents/ObsDetails/ContentWithIcon.tsx index ec6d6dd86..9c7f10f91 100644 --- a/src/components/SharedComponents/ObsDetails/ContentWithIcon.js +++ b/src/components/SharedComponents/ObsDetails/ContentWithIcon.tsx @@ -1,4 +1,3 @@ -// @flow import classNames from "classnames"; import { INatIcon } from "components/SharedComponents"; import { View } from "components/styledComponents"; @@ -8,8 +7,7 @@ type Props = { icon: string, size?: number, classNameMargin?: string, - // $FlowIgnore - children: unknown + children: React.ReactNode } const ContentWithIcon = ( { @@ -17,7 +15,7 @@ const ContentWithIcon = ( { size, classNameMargin, children -}: Props ): React.Node => ( +}: Props ) => ( Date: Tue, 11 Nov 2025 01:21:04 -0500 Subject: [PATCH 10/67] Fix type error in `src/components/SharedComponents/Buttons/GradientButton.tsx`. --- src/components/SharedComponents/Buttons/GradientButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SharedComponents/Buttons/GradientButton.tsx b/src/components/SharedComponents/Buttons/GradientButton.tsx index 587c882dd..00655c3e9 100644 --- a/src/components/SharedComponents/Buttons/GradientButton.tsx +++ b/src/components/SharedComponents/Buttons/GradientButton.tsx @@ -25,9 +25,9 @@ const GradientButton = ( { iconName, iconSize }: Props ) => { - const handleLongPress = _event => { + const handleLongPress = ( event: GestureResponderEvent ) => { if ( onLongPress ) { - onLongPress( _event ); + onLongPress( event ); } }; From d16d54719eca8ad6be9df19c769f68b70477ca89 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 11 Nov 2025 13:05:46 -0500 Subject: [PATCH 11/67] Migrate `useAuthenticatedMutation` to TypeScript --- ...utation.js => useAuthenticatedMutation.ts} | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) rename src/sharedHooks/{useAuthenticatedMutation.js => useAuthenticatedMutation.ts} (76%) diff --git a/src/sharedHooks/useAuthenticatedMutation.js b/src/sharedHooks/useAuthenticatedMutation.ts similarity index 76% rename from src/sharedHooks/useAuthenticatedMutation.js rename to src/sharedHooks/useAuthenticatedMutation.ts index 02774e098..95eed044e 100644 --- a/src/sharedHooks/useAuthenticatedMutation.js +++ b/src/sharedHooks/useAuthenticatedMutation.ts @@ -1,6 +1,4 @@ -// @flow - -import { useMutation } from "@tanstack/react-query"; +import { MutationKey, useMutation } from "@tanstack/react-query"; import handleError from "api/error"; import { getJWT } from "components/LoginSignUp/AuthenticationService"; @@ -8,12 +6,26 @@ import { log } from "../../react-native-logs.config"; const logger = log.extend( "useAuthenticatedMutation" ); +interface MutationOptions { + mutationKey?: MutationKey; + throwOnError?: boolean; +} + +type MutationFunction = ( + params: unknown, options: { api_token: string | null } +) => Promise; + +// At the time of writing, there is no type representing the response type of API errors, +// so for the time being, we will use `any` here. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type MutationError = any; + // Should work like React Query's useMutation except it calls the queryFunction // with an object that includes the JWT -const useAuthenticatedMutation = ( - mutationFunction: Function, - mutationOptions: Object = {} -): Object => useMutation( { +const useAuthenticatedMutation = ( + mutationFunction: MutationFunction, + mutationOptions: MutationOptions = {} +) => useMutation( { mutationFn: async params => { // Note, getJWTToken() takes care of fetching a new token if the existing // one is expired. We *could* store the token in state with useState if From 87f1b9f21f7b1deaa76b619a334140d4dcb393e3 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 11 Nov 2025 13:56:59 -0500 Subject: [PATCH 12/67] Resolve only type error in `src/sharedHelpers/fetchPlaceName.ts`. --- src/sharedHelpers/fetchPlaceName.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sharedHelpers/fetchPlaceName.ts b/src/sharedHelpers/fetchPlaceName.ts index b809f6f61..f813daf8f 100644 --- a/src/sharedHelpers/fetchPlaceName.ts +++ b/src/sharedHelpers/fetchPlaceName.ts @@ -44,7 +44,7 @@ const fetchPlaceName = async ( lat?: number, lng?: number ): Promise { + const timeoutPromise: Promise = new Promise( ( _, reject ) => { setTimeout( ( ) => reject( new Error( TIMEOUT_ERROR_MESSAGE ) ), GEOCODER_TIMEOUT ); } ); From 9c7e09e0f10c036d39d6d4a8b7153d11f97d4325 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 11 Nov 2025 14:08:51 -0500 Subject: [PATCH 13/67] Fix outstanding type errors in `src/sharedHelpers/geolocationWrapper.ts`. --- src/sharedHelpers/geolocationWrapper.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/sharedHelpers/geolocationWrapper.ts b/src/sharedHelpers/geolocationWrapper.ts index 011fdbbb9..576c0cee6 100644 --- a/src/sharedHelpers/geolocationWrapper.ts +++ b/src/sharedHelpers/geolocationWrapper.ts @@ -3,6 +3,7 @@ import Geolocation, { GeolocationError, + GeolocationOptions, GeolocationResponse } from "@react-native-community/geolocation"; import { @@ -18,11 +19,7 @@ import { export function getCurrentPosition( success: ( position: GeolocationResponse ) => void, error?: ( error: GeolocationError ) => void, - options?: { - timeout?: number; - maximumAge?: number; - enableHighAccuracy?: boolean; - } + options?: GeolocationOptions ) { return Geolocation.getCurrentPosition( success, error, options ); } @@ -66,7 +63,7 @@ export const lowAccuracyOptions = { } as const; export const getCurrentPositionWithOptions = ( - options + options: GeolocationOptions ): Promise => new Promise( ( resolve, reject ) => { getCurrentPosition( resolve, reject, options ); From fe36ce91153249440cf768b1465ed6565b58deb5 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 11 Nov 2025 14:19:48 -0500 Subject: [PATCH 14/67] Fix only type error in `src/sharedHelpers/sentinelFiles.ts`. --- src/sharedHelpers/sentinelFiles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sharedHelpers/sentinelFiles.ts b/src/sharedHelpers/sentinelFiles.ts index a23833a64..9dc3028d4 100644 --- a/src/sharedHelpers/sentinelFiles.ts +++ b/src/sharedHelpers/sentinelFiles.ts @@ -6,7 +6,7 @@ import { sentinelFilePath } from "../appConstants/paths"; const logger = log.extend( "sentinelFiles" ); -const accessFullFilePath = fileName => `${sentinelFilePath}/${fileName}`; +const accessFullFilePath = ( fileName: string ) => `${sentinelFilePath}/${fileName}`; const generateSentinelFileName = ( screenName: string ): string => { const timestamp = new Date().getTime(); From 588e7f592c77f097e6418bb960fa74e52d7c09cb Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 11 Nov 2025 14:34:20 -0500 Subject: [PATCH 15/67] Remove unused `logStage` option on `useWatchPosition` function. --- src/sharedHooks/useWatchPosition.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sharedHooks/useWatchPosition.ts b/src/sharedHooks/useWatchPosition.ts index 50e43d681..b0504e271 100644 --- a/src/sharedHooks/useWatchPosition.ts +++ b/src/sharedHooks/useWatchPosition.ts @@ -35,7 +35,6 @@ const useWatchPosition = ( options: { const [userLocation, setUserLocation] = useState( null ); const { shouldFetchLocation } = options; const [hasFocus, setHasFocus] = useState( true ); - const logStage = options?.logStage; const shouldStartWatch = shouldFetchLocation && subscriptionId === null @@ -113,7 +112,7 @@ const useWatchPosition = ( options: { setHasFocus( false ); } ); return unsubscribe; - }, [navigation, stopWatch, subscriptionId, logStage] ); + }, [navigation, stopWatch, subscriptionId] ); // Listen for focus. We only want to fetch location when this screen has focus. useEffect( ( ) => { From dee5a1a4e34b051c25129891a64d5d8385cdb440 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Tue, 11 Nov 2025 15:04:43 -0500 Subject: [PATCH 16/67] Migrate `src/navigation/BottomTabNavigator/index` to TypeScript. --- src/navigation/BottomTabNavigator/{index.js => index.tsx} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/navigation/BottomTabNavigator/{index.js => index.tsx} (88%) diff --git a/src/navigation/BottomTabNavigator/index.js b/src/navigation/BottomTabNavigator/index.tsx similarity index 88% rename from src/navigation/BottomTabNavigator/index.js rename to src/navigation/BottomTabNavigator/index.tsx index 95e14dc4f..0a6e49358 100644 --- a/src/navigation/BottomTabNavigator/index.js +++ b/src/navigation/BottomTabNavigator/index.tsx @@ -1,4 +1,4 @@ -import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; +import { BottomTabBarProps, createBottomTabNavigator } from "@react-navigation/bottom-tabs"; import Mortal from "components/SharedComponents/Mortal"; import TabStackNavigator, { SCREEN_NAME_NOTIFICATIONS, @@ -14,7 +14,7 @@ const Tab = createBottomTabNavigator( ); /* eslint-disable react/jsx-props-no-spreading */ const BottomTabs = ( ) => { - const renderTabBar = props => ; + const renderTabBar = ( props: BottomTabBarProps ) => ; // DEVELOPERS: do you need to add any screens here? All the rest of our screens live in // NoBottomTabStackNavigator, TabStackNavigator, or LoginStackNavigator From 6a603656f95d4c3cb50fcd7efd1ce68b78c9c409 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Wed, 12 Nov 2025 00:19:19 -0500 Subject: [PATCH 17/67] Migrate `src/components/LocationPicker/Footer` to TypeScript. --- .../LocationPicker/{Footer.js => Footer.tsx} | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) rename src/components/LocationPicker/{Footer.js => Footer.tsx} (74%) diff --git a/src/components/LocationPicker/Footer.js b/src/components/LocationPicker/Footer.tsx similarity index 74% rename from src/components/LocationPicker/Footer.js rename to src/components/LocationPicker/Footer.tsx index 083fa2d61..c0749fcca 100644 --- a/src/components/LocationPicker/Footer.js +++ b/src/components/LocationPicker/Footer.tsx @@ -1,16 +1,14 @@ -// @flow - import { Button } from "components/SharedComponents"; import { View } from "components/styledComponents"; -import type { Node } from "react"; import React from "react"; +import { GestureResponderEvent } from "react-native"; import useTranslation from "sharedHooks/useTranslation"; -type Props = { - handleSave: Function -}; +interface Props { + handleSave: ( _event?: GestureResponderEvent ) => void; +} -const Footer = ( { handleSave }: Props ): Node => { +const Footer = ( { handleSave }: Props ) => { const { t } = useTranslation( ); return ( From 1978d86f589771b970960e255680720f15696468 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Wed, 12 Nov 2025 00:20:10 -0500 Subject: [PATCH 18/67] Migrate `src/components/LoginSignUp/Header` to TypeScript. --- src/components/LoginSignUp/{Header.js => Header.tsx} | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) rename src/components/LoginSignUp/{Header.js => Header.tsx} (73%) diff --git a/src/components/LoginSignUp/Header.js b/src/components/LoginSignUp/Header.tsx similarity index 73% rename from src/components/LoginSignUp/Header.js rename to src/components/LoginSignUp/Header.tsx index 6b5b75584..8fd868c84 100644 --- a/src/components/LoginSignUp/Header.js +++ b/src/components/LoginSignUp/Header.tsx @@ -1,21 +1,17 @@ -// @flow - import { Body1 } from "components/SharedComponents"; import { View } from "components/styledComponents"; import INaturalistLogo from "images/svg/inaturalist-white.svg"; -import type { Node } from "react"; import React from "react"; -type Props = { - headerText?: string, - hideHeader?: boolean +interface Props { + headerText?: string; + hideHeader?: boolean; } -const Header = ( { headerText, hideHeader }: Props ): Node => { +const Header = ( { headerText, hideHeader }: Props ) => { if ( hideHeader ) { return null; } const renderLogo = ( ) => ( - // $FlowIgnore[not-a-component] ); return ( From 609f1fa26a1838b120a8511a5aec5bf19ab88730 Mon Sep 17 00:00:00 2001 From: Johannes Klein Date: Wed, 12 Nov 2025 09:35:10 +0100 Subject: [PATCH 19/67] v1.0.11+185 --- android/app/build.gradle | 2 +- fastlane/metadata/android/en-US/changelogs/185.txt | 3 +++ ios/iNaturalistReactNative.xcodeproj/project.pbxproj | 8 ++++---- ios/iNaturalistReactNative/Info.plist | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/185.txt diff --git a/android/app/build.gradle b/android/app/build.gradle index e37317eae..3477c4f0a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,7 +106,7 @@ android { applicationId "org.inaturalist.iNaturalistMobile" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 184 + versionCode 185 versionName "1.0.11" setProperty("archivesBaseName", applicationId + "-v" + versionName + "+" + versionCode) manifestPlaceholders = [ GMAPS_API_KEY:project.env.get("GMAPS_API_KEY") ] diff --git a/fastlane/metadata/android/en-US/changelogs/185.txt b/fastlane/metadata/android/en-US/changelogs/185.txt new file mode 100644 index 000000000..26ede634a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/185.txt @@ -0,0 +1,3 @@ +FIXED +* Upload status text should wrap +* "Top id suggestion" on the suggestions screen should show gray checkmark diff --git a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj index 9465488fb..93398c070 100644 --- a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj +++ b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj @@ -548,7 +548,7 @@ CODE_SIGN_ENTITLEMENTS = iNaturalistReactNative/iNaturalistReactNative.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; - CURRENT_PROJECT_VERSION = 184; + CURRENT_PROJECT_VERSION = 185; DEVELOPMENT_TEAM = N5J7L4P93Z; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; @@ -675,7 +675,7 @@ CODE_SIGN_ENTITLEMENTS = iNaturalistReactNative/iNaturalistReactNativeRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; - CURRENT_PROJECT_VERSION = 184; + CURRENT_PROJECT_VERSION = 185; DEVELOPMENT_TEAM = N5J7L4P93Z; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; HEADER_SEARCH_PATHS = ( @@ -988,7 +988,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "iNaturalistReactNative-ShareExtension/iNaturalistReactNative-ShareExtension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 184; + CURRENT_PROJECT_VERSION = 185; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = N5J7L4P93Z; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; @@ -1033,7 +1033,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 184; + CURRENT_PROJECT_VERSION = 185; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = N5J7L4P93Z; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; diff --git a/ios/iNaturalistReactNative/Info.plist b/ios/iNaturalistReactNative/Info.plist index 4ef3dd85d..4615d4349 100644 --- a/ios/iNaturalistReactNative/Info.plist +++ b/ios/iNaturalistReactNative/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 184 + 185 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes From 56a9793371e7f472b23eea29ef44352ab0ecfb09 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Wed, 12 Nov 2025 12:21:39 -0500 Subject: [PATCH 20/67] Migrate `src/components/Match/EmptyMapSection` to TypeScript. --- .../Match/{EmptyMapSection.js => EmptyMapSection.tsx} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/components/Match/{EmptyMapSection.js => EmptyMapSection.tsx} (93%) diff --git a/src/components/Match/EmptyMapSection.js b/src/components/Match/EmptyMapSection.tsx similarity index 93% rename from src/components/Match/EmptyMapSection.js rename to src/components/Match/EmptyMapSection.tsx index 31376beab..30b7ffcc5 100644 --- a/src/components/Match/EmptyMapSection.js +++ b/src/components/Match/EmptyMapSection.tsx @@ -7,9 +7,9 @@ import React from "react"; import { View } from "react-native"; import { useTranslation } from "sharedHooks"; -type Props = { - isFetchingLocation: boolean, - handleAddLocationPressed: ( ) => void +interface Props { + isFetchingLocation: boolean; + handleAddLocationPressed: ( ) => void; } const EmptyMapSection = ( { From c6bd05edb1d6b5be609608e3af0663f7b083cb87 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Wed, 12 Nov 2025 12:32:37 -0500 Subject: [PATCH 21/67] Migrate `tryToReplaceWithLocalTaxon` to TypeScript. --- ...aceWithLocalTaxon.js => tryToReplaceWithLocalTaxon.ts} | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) rename src/components/Match/helpers/{tryToReplaceWithLocalTaxon.js => tryToReplaceWithLocalTaxon.ts} (61%) diff --git a/src/components/Match/helpers/tryToReplaceWithLocalTaxon.js b/src/components/Match/helpers/tryToReplaceWithLocalTaxon.ts similarity index 61% rename from src/components/Match/helpers/tryToReplaceWithLocalTaxon.js rename to src/components/Match/helpers/tryToReplaceWithLocalTaxon.ts index ac1652561..3454cbab2 100644 --- a/src/components/Match/helpers/tryToReplaceWithLocalTaxon.js +++ b/src/components/Match/helpers/tryToReplaceWithLocalTaxon.ts @@ -1,4 +1,10 @@ -const tryToReplaceWithLocalTaxon = ( localTaxa, suggestion ) => { +import Taxon from "realmModels/Taxon"; +import type { RealmTaxon } from "realmModels/types"; + +const tryToReplaceWithLocalTaxon = ( + localTaxa: ( Taxon & RealmTaxon )[], + suggestion: { taxon: { id: number } } +) => { const localTaxon = localTaxa.find( local => local.id === suggestion.taxon.id ); if ( localTaxon ) { From de666803e70ef02d532cde1f7990fe96708d951f Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Wed, 12 Nov 2025 13:58:08 -0500 Subject: [PATCH 22/67] Fix issue where component does not return `Element | null`. --- .../SharedComponents/Map/CurrentLocationButton.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/SharedComponents/Map/CurrentLocationButton.tsx b/src/components/SharedComponents/Map/CurrentLocationButton.tsx index d8e7e104e..fee8d4ceb 100644 --- a/src/components/SharedComponents/Map/CurrentLocationButton.tsx +++ b/src/components/SharedComponents/Map/CurrentLocationButton.tsx @@ -20,7 +20,10 @@ const CurrentLocationButton = ( { renderPermissionsGate }: Props ) => { const { t } = useTranslation( ); - return showCurrentLocationButton && ( + if ( !showCurrentLocationButton ) { + return null; + } + return ( <> Date: Thu, 13 Nov 2025 16:43:41 -0600 Subject: [PATCH 23/67] typescriptify Developer.js and refactor size calculation (#3176) * typescriptify Developer.js and refactor size calculation * remove logs * fix typography component types / spacing, clean up naming * fix type annotation, unused export, and variable name --- .../Developer/{Developer.js => Developer.tsx} | 124 +++++++++++------- src/components/Developer/hooks/useAppSize.ts | 110 +++++++++------- 2 files changed, 141 insertions(+), 93 deletions(-) rename src/components/Developer/{Developer.js => Developer.tsx} (70%) diff --git a/src/components/Developer/Developer.js b/src/components/Developer/Developer.tsx similarity index 70% rename from src/components/Developer/Developer.js rename to src/components/Developer/Developer.tsx index f02009675..d89004183 100644 --- a/src/components/Developer/Developer.js +++ b/src/components/Developer/Developer.tsx @@ -10,8 +10,7 @@ import { } from "components/SharedComponents"; import { fontMonoClass, View } from "components/styledComponents"; import { t } from "i18next"; -import type { Node } from "react"; -import React, { useCallback } from "react"; +import React, { type PropsWithChildren } from "react"; import { I18nManager, Platform, Text } from "react-native"; import Config from "react-native-config"; import RNFS from "react-native-fs"; @@ -19,13 +18,30 @@ import RNRestart from "react-native-restart"; import useLogs from "sharedHooks/useLogs"; import useAppSize, { + DirectoryEntrySize, formatAppSizeString, formatSizeUnits, getTotalDirectorySize } from "./hooks/useAppSize"; -const H1 = ( { children } ) => {children}; -const H2 = ( { children } ) => {children}; -const P = ( { children } ) => {children}; -const CODE = ( { children, optionalClassName } ) => ( +const H1 = ( { children }: PropsWithChildren ) => ( + + {children} + +); +const H2 = ( { children }: PropsWithChildren ) => ( + + {children} + +); +const P = ( { children }: PropsWithChildren ) => ( + + {children} + +); + +interface CODEProps extends PropsWithChildren { + optionalClassName?: string; +} +const CODE = ( { children, optionalClassName }: CODEProps ) => ( ( ); -const modelFileName: string = Platform.select( { +const modelFileName = Platform.select( { ios: Config.IOS_MODEL_FILE_NAME, android: Config.ANDROID_MODEL_FILE_NAME } ); @@ -49,48 +65,66 @@ const geomodelFileName = Platform.select( { ios: Config.IOS_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 + } +); + +interface DirectorySizesProps { + directoryName: string; + directoryEntrySizes: DirectoryEntrySize[] +} /* eslint-disable i18next/no-literal-string */ -const Developer = (): Node => { - const fileSizes = useAppSize( ); - - const boldClassname = ( line, isDirectory = false ) => classnames( - { - "text-red font-bold": line.includes( "MB" ), - "text-blue": isDirectory - } +const DirectoryFileSizes = ( { directoryName, directoryEntrySizes }: DirectorySizesProps ) => { + const totalDirectorySize = formatSizeUnits( getTotalDirectorySize( directoryEntrySizes ) ); + return ( + +

+ File Sizes: + {" "} + {directoryName} +

+

+ + {`Total Directory Size: ${totalDirectorySize}`} + +

+ {directoryEntrySizes.map( ( { name, size } ) => { + const line = formatAppSizeString( name, size ); + return ( +

+ + {line} + +

+ ); + } )} +
); +}; - const displayFileSizes = useCallback( ( ) => Object.keys( fileSizes ).map( directory => { - const contents = fileSizes[directory]; - if ( !directory || !contents ) { return null; } - const totalDirectorySize = formatSizeUnits( getTotalDirectorySize( contents ) ); - return ( - -

- File Sizes: - {" "} - {directory} -

-

- - {`Total Directory Size: ${totalDirectorySize}`} - -

- {contents.map( ( { name, size } ) => { - const line = formatAppSizeString( name, size ); - return ( -

- - {line} - -

- ); - } )} -
- ); - } ), [fileSizes] ); +const AppFileSizes = () => { + const appSize = useAppSize(); + if ( !appSize ) { + return null; + } + return ( + <> + {Object.entries( appSize ).map( ( [directoryName, directoryEntrySizes] ) => ( + + ) )} + + ); +}; +const Developer = () => { const toggleRTLandLTR = async ( ) => { const { isRTL, forceRTL } = I18nManager; await forceRTL( !isRTL ); @@ -196,7 +230,7 @@ const Developer = (): Node => {

{getUserAgent()}

- {displayFileSizes( )} +

Log file contents