Files
iNaturalistReactNative/src/sharedHooks/useInfiniteNotificationsScroll.ts
Ken-ichi 7f9a272820 feat: show notifications from others (#2491)
* split notifications into tabs
* lots of TypeScript conversion
* feat: resize Heading5 and add Heading6 (closes #2480)
* fix: mark remote observations as viewed from ObsDetails
* feat: show indicator in Notifications tabs if unviewed notifications

Closes #2451
2024-11-27 23:32:31 -08:00

131 lines
3.8 KiB
TypeScript

import { fetchObservationUpdates, fetchRemoteObservations } from "api/observations";
import type {
ApiNotification,
ApiObservation,
ApiObservationsUpdatesParams,
ApiOpts
} from "api/types";
import { flatten } from "lodash";
import { RealmContext } from "providers/contexts.ts";
import type Realm from "realm";
import Observation from "realmModels/Observation";
import { useAuthenticatedInfiniteQuery, useCurrentUser } from "sharedHooks";
const { useRealm } = RealmContext;
// Extends API response with data we need in this app
export interface Notification extends ApiNotification {
resource?: ApiObservation;
viewerOwnsResource?: boolean;
}
interface InfiniteNotificationsScrollResponse {
fetchNextPage: ( ) => void;
isError?: boolean;
isFetching?: boolean;
isInitialLoading?: boolean;
notifications: Notification[];
refetch: ( ) => void;
}
const BASE_PARAMS: ApiObservationsUpdatesParams = {
// observations_by: "owner",
fields: "all", // TODO narrow this down. this has a massive response
per_page: 30,
ttl: -1,
page: 1
};
async function fetchObsByUUIDs(
uuids: string[],
authOptions: ApiOpts,
realm: Realm,
options: {
save?: boolean
} = {}
) {
// TODO convert api/observations to TS
const observations: ApiObservation[] | null = await fetchRemoteObservations(
uuids,
{ fields: Observation.FIELDS },
authOptions
);
if ( options.save ) {
Observation.upsertRemoteObservations( observations, realm );
}
return observations;
}
const useInfiniteNotificationsScroll = (
notificationParams: ApiObservationsUpdatesParams = {}
): InfiniteNotificationsScrollResponse => {
const currentUser = useCurrentUser( );
const realm = useRealm( );
const queryKey = ["useInfiniteNotificationsScroll", JSON.stringify( notificationParams )];
const infQueryResult = useAuthenticatedInfiniteQuery(
queryKey,
async ( { pageParam }: { pageParam: number }, optsWithAuth: ApiOpts ) => {
const params = { ...BASE_PARAMS, ...notificationParams };
if ( pageParam ) {
params.page = pageParam;
} else {
params.page = 1;
}
const response: null | ApiNotification[] = await fetchObservationUpdates(
params,
optsWithAuth
);
// Sometimes updates linger after notifiers that generated them have been deleted
const updatesWithContent = response?.filter(
update => update.comment || update.identification
) || [];
const obsUUIDs = updatesWithContent.map( obsUpdate => obsUpdate.resource_uuid );
if ( obsUUIDs.length > 0 ) {
const observations = await fetchObsByUUIDs(
obsUUIDs,
optsWithAuth,
realm,
{ save: params.observations_by === "owner" }
);
if ( observations ) {
return updatesWithContent.map( ( update: Notification ) => {
const resource = observations.find(
( o: ApiObservation ) => o.uuid === update.resource_uuid
);
update.resource = resource;
update.viewerOwnsResource = resource?.user?.id === currentUser?.id;
return update;
} );
}
}
return updatesWithContent;
},
{
getNextPageParam: ( lastPage, allPages ) => ( lastPage.length > 0
? allPages.length + 1
: undefined ),
enabled: !!( currentUser )
}
);
return {
refetch: infQueryResult.refetch,
isError: infQueryResult.isError,
isFetching: infQueryResult.isFetching,
isInitialLoading: infQueryResult.isInitialLoading,
// Disable fetchNextPage if signed out
fetchNextPage: currentUser
? infQueryResult.fetchNextPage
: ( ) => undefined,
notifications: flatten( infQueryResult?.data?.pages )
};
};
export default useInfiniteNotificationsScroll;