From 12e1c840d6df2188724f3140f73465c6e0f7e97c Mon Sep 17 00:00:00 2001
From: sepeterson <10458078+sepeterson@users.noreply.github.com>
Date: Thu, 20 Nov 2025 09:06:42 -0600
Subject: [PATCH 01/35] MOB-512 saved match UI
---
.../ObsDetailsDefaultMode/SavedMatch.tsx | 94 +++++++++++++++++++
1 file changed, 94 insertions(+)
create mode 100644 src/components/ObsDetailsDefaultMode/SavedMatch.tsx
diff --git a/src/components/ObsDetailsDefaultMode/SavedMatch.tsx b/src/components/ObsDetailsDefaultMode/SavedMatch.tsx
new file mode 100644
index 000000000..d3c1e919f
--- /dev/null
+++ b/src/components/ObsDetailsDefaultMode/SavedMatch.tsx
@@ -0,0 +1,94 @@
+import { useNetInfo } from "@react-native-community/netinfo";
+import EmptyMapSection from "components/Match/EmptyMapSection";
+import MatchHeader from "components/Match/MatchHeader";
+import PhotosSection from "components/Match/PhotosSection";
+import LocationSection from "components/ObsDetailsDefaultMode/LocationSection/LocationSection";
+import MapSection from "components/ObsDetailsDefaultMode/MapSection/MapSection";
+import { Button, ScrollViewWrapper } from "components/SharedComponents";
+import { View } from "components/styledComponents";
+import _ from "lodash";
+import React from "react";
+import type { RealmObservation } from "realmModels/types";
+import { useTranslation } from "sharedHooks";
+
+const cardClassTop
+ = "rounded-t-2xl border-lightGray border-[2px] py-[18px] px-5 border-b-0 -mb-0.5";
+const cardClassBottom
+ = "rounded-b-2xl border-lightGray border-[2px] pb-3 border-t-0 -mt-0.5 mb-[30px]";
+
+type Props = {
+ observation: RealmObservation,
+ navToTaxonDetails: ( ) => void,
+ isFetchingLocation: boolean,
+ handleAddLocationPressed: ( ) => void,
+}
+
+const SavedMatch = ( {
+ observation,
+ navToTaxonDetails,
+ isFetchingLocation,
+ handleAddLocationPressed
+}: Props ) => {
+ const { t } = useTranslation( );
+ const { isConnected } = useNetInfo( );
+
+ const latitude = observation?.privateLatitude || observation?.latitude;
+ const { taxon } = observation;
+
+ return (
+
+
+
+
+
+
+ {!latitude
+ ? (
+
+ )
+ : (
+
+ )}
+
+
+ {
+ isConnected && (
+
+ )
+ }
+ {!latitude && (
+
+ )}
+
+ );
+};
+
+export default SavedMatch;
From 2d8d4d45d79dfda6cf5ad25656c9e8f3c5ba9e74 Mon Sep 17 00:00:00 2001
From: sepeterson <10458078+sepeterson@users.noreply.github.com>
Date: Thu, 20 Nov 2025 11:47:04 -0600
Subject: [PATCH 02/35] MOB-512 add wrapper to select obs screen ui
---
.../ObsDetailsDefaultModeContainer.js | 161 ++++++++++++------
1 file changed, 111 insertions(+), 50 deletions(-)
diff --git a/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js b/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js
index baae00aea..fc60d7d04 100644
--- a/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js
+++ b/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js
@@ -31,6 +31,7 @@ import useStore from "stores/useStore";
import useMarkViewedMutation from "./hooks/useMarkViewedMutation";
import ObsDetailsDefaultMode from "./ObsDetailsDefaultMode";
+import SavedMatch from "./SavedMatch";
const { useRealm } = RealmContext;
@@ -109,19 +110,30 @@ const reducer = ( state, action ) => {
}
};
-const ObsDetailsDefaultModeContainer = ( ): Node => {
+const ObsDetailsDefaultModeContainer = ( props ): Node => {
const setObservations = useStore( state => state.setObservations );
- const currentUser = useCurrentUser( );
- const { params } = useRoute();
- const {
- targetActivityItemID,
- uuid
- } = params;
const navigation = useNavigation( );
const realm = useRealm( );
- const { isConnected } = useNetInfo( );
const [state, dispatch] = useReducer( reducer, initialState );
- const [remoteObsWasDeleted, setRemoteObsWasDeleted] = useState( false );
+
+ const {
+ observation,
+ targetActivityItemID,
+ uuid,
+ localObservation,
+ markViewedLocally,
+ markDeletedLocally,
+ remoteObservation,
+ setRemoteObsWasDeleted,
+ fetchRemoteObservationError,
+ currentUser,
+ belongsToCurrentUser,
+ isRefetching,
+ refetchRemoteObservation,
+ isConnected,
+ isSimpleMode,
+ remoteObsWasDeleted
+ } = props;
const {
activityItems,
@@ -133,26 +145,6 @@ const ObsDetailsDefaultModeContainer = ( ): Node => {
} = state;
const queryClient = useQueryClient( );
- const {
- localObservation,
- markDeletedLocally,
- markViewedLocally
- } = useLocalObservation( uuid );
- const wasSynced = localObservation && localObservation?.wasSynced();
-
- const fetchRemoteObservationEnabled = !!(
- !remoteObsWasDeleted
- && ( !localObservation || localObservation?.wasSynced( ) )
- && isConnected
- );
-
- const {
- remoteObservation,
- refetchRemoteObservation,
- isRefetching,
- fetchRemoteObservationError
- } = useRemoteObservation( uuid, fetchRemoteObservationEnabled );
-
useMarkViewedMutation( localObservation, markViewedLocally, remoteObservation );
// If we tried to get a remote observation but it no longer exists, the user
@@ -160,7 +152,8 @@ const ObsDetailsDefaultModeContainer = ( ): Node => {
// copy of this observation
useEffect( ( ) => {
setRemoteObsWasDeleted( fetchRemoteObservationError?.status === 404 );
- }, [fetchRemoteObservationError?.status] );
+ }, [fetchRemoteObservationError?.status, setRemoteObsWasDeleted] );
+
const confirmRemoteObsWasDeleted = useCallback( ( ) => {
if ( localObservation ) {
markDeletedLocally( );
@@ -172,27 +165,10 @@ const ObsDetailsDefaultModeContainer = ( ): Node => {
navigation
] );
- const observation = localObservation || Observation.mapApiToRealm( remoteObservation );
+ const wasSynced = localObservation && localObservation?.wasSynced();
+
const hasPhotos = observation?.observationPhotos?.length > 0;
- // In theory the only situation in which an observation would not have a
- // user is when a user is not signed but has made a new observation in the
- // app. Also in theory that user should not be able to get to ObsDetail for
- // those observations, just ObsEdit. But.... let's be safe.
- const belongsToCurrentUser = (
- observation?.user?.id === currentUser?.id
- || ( !observation?.user && !observation?.id )
- );
-
- const isSimpleMode = useMemo( () => (
- // Simple mode applies only when:
- // 1. It's the current user's observation (or an observation being created)
- // 2. AND the observation hasn't been synced yet
- ( belongsToCurrentUser || !observation?.user )
- && localObservation
- && !localObservation.wasSynced()
- ), [belongsToCurrentUser, localObservation, observation?.user] );
-
const { data: subscriptions, refetch: refetchSubscriptions } = useAuthenticatedQuery(
[
"fetchSubscriptions"
@@ -385,4 +361,89 @@ const ObsDetailsDefaultModeContainer = ( ): Node => {
);
};
-export default ObsDetailsDefaultModeContainer;
+const ObsDetailsDefaultModeScreenWrapper = () => {
+ const { params } = useRoute();
+ const {
+ targetActivityItemID,
+ uuid
+ } = params;
+ const currentUser = useCurrentUser( );
+ const { isConnected } = useNetInfo( );
+
+ const [remoteObsWasDeleted, setRemoteObsWasDeleted] = useState( false );
+
+ const {
+ localObservation,
+ markDeletedLocally,
+ markViewedLocally
+ } = useLocalObservation( uuid );
+
+ const fetchRemoteObservationEnabled = !!(
+ !remoteObsWasDeleted
+ && ( !localObservation || localObservation?.wasSynced( ) )
+ && isConnected
+ );
+
+ const {
+ remoteObservation,
+ refetchRemoteObservation,
+ isRefetching,
+ fetchRemoteObservationError
+ } = useRemoteObservation( uuid, fetchRemoteObservationEnabled );
+
+ const observation = localObservation || Observation.mapApiToRealm( remoteObservation );
+
+ // In theory the only situation in which an observation would not have a
+ // user is when a user is not signed but has made a new observation in the
+ // app. Also in theory that user should not be able to get to ObsDetail for
+ // those observations, just ObsEdit. But.... let's be safe.
+ const belongsToCurrentUser = (
+ observation?.user?.id === currentUser?.id
+ || ( !observation?.user && !observation?.id )
+ );
+
+ const showSavedMatch = useMemo( () => (
+ // Simple mode applies only when:
+ // 1. It's the current user's observation (or an observation being created)
+ // 2. AND the observation hasn't been synced yet
+ ( belongsToCurrentUser || !observation?.user )
+ && localObservation
+ && !localObservation.wasSynced()
+ ), [belongsToCurrentUser, localObservation, observation?.user] );
+
+ if ( showSavedMatch ) {
+ return (
+ // todo add edit pencil
+ {}}
+ isFetchingLocation={false}
+ handleAddLocationPressed={() => {}}
+ />
+ );
+ }
+
+ return (
+
+ );
+};
+
+// todo update stack navigator name for this import
+export default ObsDetailsDefaultModeScreenWrapper;
From 7de330b51f656874dffff2cfb60e658dbac7fa8b Mon Sep 17 00:00:00 2001
From: sepeterson <10458078+sepeterson@users.noreply.github.com>
Date: Thu, 20 Nov 2025 13:44:18 -0600
Subject: [PATCH 03/35] MOB-512 use wrapper in navigator
---
.../ObsDetailsDefaultModeContainer.js | 101 +-----------------
.../ObsDetailsDefaultModeScreensWrapper.tsx | 98 +++++++++++++++++
.../{ => SavedMatch}/SavedMatch.tsx | 0
.../SavedMatch/SavedMatchContainer.tsx | 0
.../StackNavigators/TabStackNavigator.js | 10 +-
5 files changed, 107 insertions(+), 102 deletions(-)
create mode 100644 src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeScreensWrapper.tsx
rename src/components/ObsDetailsDefaultMode/{ => SavedMatch}/SavedMatch.tsx (100%)
create mode 100644 src/components/ObsDetailsDefaultMode/SavedMatch/SavedMatchContainer.tsx
diff --git a/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js b/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js
index fc60d7d04..8da0aaf2b 100644
--- a/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js
+++ b/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js
@@ -1,8 +1,5 @@
// @flow
-import {
- useNetInfo
-} from "@react-native-community/netinfo";
-import { useFocusEffect, useNavigation, useRoute } from "@react-navigation/native";
+import { useFocusEffect, useNavigation } from "@react-navigation/native";
import { useQueryClient } from "@tanstack/react-query";
import { fetchSubscriptions } from "api/observations";
import IdentificationSheets from "components/ObsDetailsDefaultMode/IdentificationSheets";
@@ -11,27 +8,22 @@ import type { Node } from "react";
import React, {
useCallback,
useEffect,
- useMemo,
- useReducer,
- useState
+ useReducer
} from "react";
import { LogBox } from "react-native";
import Observation from "realmModels/Observation";
import safeRealmWrite from "sharedHelpers/safeRealmWrite";
import {
useAuthenticatedQuery,
- useCurrentUser,
- useLocalObservation,
useObservationsUpdates
} from "sharedHooks";
-import useRemoteObservation, {
+import {
fetchRemoteObservationKey
} from "sharedHooks/useRemoteObservation";
import useStore from "stores/useStore";
import useMarkViewedMutation from "./hooks/useMarkViewedMutation";
import ObsDetailsDefaultMode from "./ObsDetailsDefaultMode";
-import SavedMatch from "./SavedMatch";
const { useRealm } = RealmContext;
@@ -361,89 +353,4 @@ const ObsDetailsDefaultModeContainer = ( props ): Node => {
);
};
-const ObsDetailsDefaultModeScreenWrapper = () => {
- const { params } = useRoute();
- const {
- targetActivityItemID,
- uuid
- } = params;
- const currentUser = useCurrentUser( );
- const { isConnected } = useNetInfo( );
-
- const [remoteObsWasDeleted, setRemoteObsWasDeleted] = useState( false );
-
- const {
- localObservation,
- markDeletedLocally,
- markViewedLocally
- } = useLocalObservation( uuid );
-
- const fetchRemoteObservationEnabled = !!(
- !remoteObsWasDeleted
- && ( !localObservation || localObservation?.wasSynced( ) )
- && isConnected
- );
-
- const {
- remoteObservation,
- refetchRemoteObservation,
- isRefetching,
- fetchRemoteObservationError
- } = useRemoteObservation( uuid, fetchRemoteObservationEnabled );
-
- const observation = localObservation || Observation.mapApiToRealm( remoteObservation );
-
- // In theory the only situation in which an observation would not have a
- // user is when a user is not signed but has made a new observation in the
- // app. Also in theory that user should not be able to get to ObsDetail for
- // those observations, just ObsEdit. But.... let's be safe.
- const belongsToCurrentUser = (
- observation?.user?.id === currentUser?.id
- || ( !observation?.user && !observation?.id )
- );
-
- const showSavedMatch = useMemo( () => (
- // Simple mode applies only when:
- // 1. It's the current user's observation (or an observation being created)
- // 2. AND the observation hasn't been synced yet
- ( belongsToCurrentUser || !observation?.user )
- && localObservation
- && !localObservation.wasSynced()
- ), [belongsToCurrentUser, localObservation, observation?.user] );
-
- if ( showSavedMatch ) {
- return (
- // todo add edit pencil
- {}}
- isFetchingLocation={false}
- handleAddLocationPressed={() => {}}
- />
- );
- }
-
- return (
-
- );
-};
-
-// todo update stack navigator name for this import
-export default ObsDetailsDefaultModeScreenWrapper;
+export default ObsDetailsDefaultModeContainer;
diff --git a/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeScreensWrapper.tsx b/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeScreensWrapper.tsx
new file mode 100644
index 000000000..7682c384c
--- /dev/null
+++ b/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeScreensWrapper.tsx
@@ -0,0 +1,98 @@
+import { useNetInfo } from "@react-native-community/netinfo";
+import { useRoute } from "@react-navigation/native";
+import ObsDetailsDefaultModeContainer
+ from "components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer";
+import SavedMatch from "components/ObsDetailsDefaultMode/SavedMatch/SavedMatch";
+import React, { useMemo, useState } from "react";
+import Observation from "realmModels/Observation";
+import {
+ useCurrentUser,
+ useLocalObservation,
+ useRemoteObservation
+} from "sharedHooks";
+
+const ObsDetailsDefaultModeScreensWrapper = () => {
+ const { params } = useRoute();
+ const {
+ targetActivityItemID,
+ uuid
+ } = params;
+ const currentUser = useCurrentUser( );
+ const { isConnected } = useNetInfo( );
+
+ const [remoteObsWasDeleted, setRemoteObsWasDeleted] = useState( false );
+
+ const {
+ localObservation,
+ markDeletedLocally,
+ markViewedLocally
+ } = useLocalObservation( uuid );
+
+ const fetchRemoteObservationEnabled = !!(
+ !remoteObsWasDeleted
+ && ( !localObservation || localObservation?.wasSynced( ) )
+ && isConnected
+ );
+
+ const {
+ remoteObservation,
+ refetchRemoteObservation,
+ isRefetching,
+ fetchRemoteObservationError
+ } = useRemoteObservation( uuid, fetchRemoteObservationEnabled );
+
+ const observation = localObservation || Observation.mapApiToRealm( remoteObservation );
+
+ // In theory the only situation in which an observation would not have a
+ // user is when a user is not signed but has made a new observation in the
+ // app. Also in theory that user should not be able to get to ObsDetail for
+ // those observations, just ObsEdit. But.... let's be safe.
+ const belongsToCurrentUser = (
+ observation?.user?.id === currentUser?.id
+ || ( !observation?.user && !observation?.id )
+ );
+
+ const showSavedMatch = useMemo( () => (
+ // Simple mode applies only when:
+ // 1. It's the current user's observation (or an observation being created)
+ // 2. AND the observation hasn't been synced yet
+ ( belongsToCurrentUser || !observation?.user )
+ && localObservation
+ && !localObservation.wasSynced()
+ ), [belongsToCurrentUser, localObservation, observation?.user] );
+
+ if ( showSavedMatch ) {
+ return (
+ // todo add edit pencil
+ {}}
+ isFetchingLocation={false}
+ handleAddLocationPressed={() => {}}
+ />
+ );
+ }
+
+ return (
+
+ );
+};
+
+export default ObsDetailsDefaultModeScreensWrapper;
diff --git a/src/components/ObsDetailsDefaultMode/SavedMatch.tsx b/src/components/ObsDetailsDefaultMode/SavedMatch/SavedMatch.tsx
similarity index 100%
rename from src/components/ObsDetailsDefaultMode/SavedMatch.tsx
rename to src/components/ObsDetailsDefaultMode/SavedMatch/SavedMatch.tsx
diff --git a/src/components/ObsDetailsDefaultMode/SavedMatch/SavedMatchContainer.tsx b/src/components/ObsDetailsDefaultMode/SavedMatch/SavedMatchContainer.tsx
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/navigation/StackNavigators/TabStackNavigator.js b/src/navigation/StackNavigators/TabStackNavigator.js
index 8887dbbad..c8b466198 100644
--- a/src/navigation/StackNavigators/TabStackNavigator.js
+++ b/src/navigation/StackNavigators/TabStackNavigator.js
@@ -19,8 +19,8 @@ import MyObservationsContainer from "components/MyObservations/MyObservationsCon
import Notifications from "components/Notifications/Notifications";
import DQAContainer from "components/ObsDetails/DQAContainer";
import ObsDetailsContainer from "components/ObsDetails/ObsDetailsContainer";
-import ObsDetailsDefaultModeContainer
- from "components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer";
+import ObsDetailsDefaultModeScreensWrapper
+ from "components/ObsDetailsDefaultMode/ObsDetailsDefaultModeScreensWrapper";
import ProjectDetailsContainer from "components/ProjectDetails/ProjectDetailsContainer";
import ProjectMembers from "components/ProjectDetails/ProjectMembers";
import ProjectRequirements from "components/ProjectDetails/ProjectRequirements";
@@ -129,8 +129,8 @@ const FadeInRootExplore = ( ) => fadeInComponent( );
const FadeInMyObservations = ( ) => fadeInComponent( );
const FadeInUserProfile = ( ) => fadeInComponent( );
const FadeInExploreContainer = ( ) => fadeInComponent( );
-const FadeInObsDetailsDefaultModeContainer = ( ) => fadeInComponent(
-
+const FadeInObsDetailsDefaultModeScreensWrapper = ( ) => fadeInComponent(
+
);
const FadeInObsDetailsContainer = ( ) => fadeInComponent(
@@ -226,7 +226,7 @@ const TabStackNavigator = ( { route }: TabStackNavigatorProps ): Node => {
? (
)
From 783d2fa564ec464ebb901d28c06608f064c1a09a Mon Sep 17 00:00:00 2001
From: sepeterson <10458078+sepeterson@users.noreply.github.com>
Date: Thu, 20 Nov 2025 14:01:02 -0600
Subject: [PATCH 04/35] MOB-512: flow type to obs container props
---
.../ObsDetailsDefaultModeContainer.js | 25 ++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js b/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js
index 8da0aaf2b..37610b6d4 100644
--- a/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js
+++ b/src/components/ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js
@@ -102,7 +102,26 @@ const reducer = ( state, action ) => {
}
};
-const ObsDetailsDefaultModeContainer = ( props ): Node => {
+type Props = {
+ belongsToCurrentUser: boolean,
+ currentUser: ?Object,
+ fetchRemoteObservationError: ?Object,
+ isConnected: boolean,
+ isRefetching: boolean,
+ isSimpleMode: boolean,
+ localObservation: ?Object,
+ markDeletedLocally: Function,
+ markViewedLocally: Function,
+ observation: Object,
+ refetchRemoteObservation: Function,
+ remoteObservation: ?Object,
+ remoteObsWasDeleted: boolean,
+ setRemoteObsWasDeleted: Function,
+ targetActivityItemID: number,
+ uuid: string
+}
+
+const ObsDetailsDefaultModeContainer = ( props: Props ): Node => {
const setObservations = useStore( state => state.setObservations );
const navigation = useNavigation( );
const realm = useRealm( );
@@ -157,7 +176,7 @@ const ObsDetailsDefaultModeContainer = ( props ): Node => {
navigation
] );
- const wasSynced = localObservation && localObservation?.wasSynced();
+ const wasSynced = !!( localObservation && localObservation?.wasSynced() );
const hasPhotos = observation?.observationPhotos?.length > 0;
@@ -293,7 +312,7 @@ const ObsDetailsDefaultModeContainer = ( props ): Node => {
const localComments = localObservation?.comments;
const newComment = data[0];
newComment.user = currentUser;
- localComments.push( newComment );
+ localComments?.push( newComment );
}, "setting local comment in ObsDetailsContainer" );
const updatedLocalObservation = realm.objectForPrimaryKey( "Observation", uuid );
dispatch( { type: "ADD_ACTIVITY_ITEM", observationShown: updatedLocalObservation } );
From ccbfc6c4fe2f097ef960413d300f9ee1470ab0e2 Mon Sep 17 00:00:00 2001
From: sepeterson <10458078+sepeterson@users.noreply.github.com>
Date: Thu, 20 Nov 2025 15:03:18 -0600
Subject: [PATCH 05/35] MOB-512: maybe types for targetItemID
---
.../ObsDetailsDefaultMode/CommunitySection/CommunitySection.js | 2 +-
src/components/ObsDetailsDefaultMode/ObsDetailsDefaultMode.js | 2 +-
.../ObsDetailsDefaultMode/ObsDetailsDefaultModeContainer.js | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/ObsDetailsDefaultMode/CommunitySection/CommunitySection.js b/src/components/ObsDetailsDefaultMode/CommunitySection/CommunitySection.js
index d7a3d087e..7554d05a6 100644
--- a/src/components/ObsDetailsDefaultMode/CommunitySection/CommunitySection.js
+++ b/src/components/ObsDetailsDefaultMode/CommunitySection/CommunitySection.js
@@ -14,7 +14,7 @@ type Props = {
activityItems: Array