From 1889aa1d90a408d583f54227623ece863f94153c Mon Sep 17 00:00:00 2001 From: Cindy Green Date: Mon, 9 Dec 2024 18:07:52 -0500 Subject: [PATCH 1/3] Adds new hook to use backend functions for getting and setting the media sync settings. --- .../persistedState/usePersistedSettings.ts | 6 +-- src/frontend/hooks/server/mediaSync.ts | 40 +++++++++++++++++++ .../ProjectSettings/MediaSyncSettings.tsx | 33 +++++++-------- .../Settings/ProjectSettings/index.tsx | 18 ++++----- 4 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 src/frontend/hooks/server/mediaSync.ts diff --git a/src/frontend/hooks/persistedState/usePersistedSettings.ts b/src/frontend/hooks/persistedState/usePersistedSettings.ts index 260b07978..fcf7f2900 100644 --- a/src/frontend/hooks/persistedState/usePersistedSettings.ts +++ b/src/frontend/hooks/persistedState/usePersistedSettings.ts @@ -1,29 +1,25 @@ import {type StateCreator} from 'zustand'; import {createPersistedState} from './createPersistedState'; -import {CoordinateFormat, MediaSyncSetting} from '../../sharedTypes'; +import {CoordinateFormat} from '../../sharedTypes'; type SettingsSlice = { coordinateFormat: CoordinateFormat; manualCoordinateEntryFormat: CoordinateFormat; - mediaSyncSetting: MediaSyncSetting; actions: { setCoordinateFormat: (coordinateFormat: CoordinateFormat) => void; setManualCoordinateEntryFormat: ( coordinateFormat: CoordinateFormat, ) => void; - setMediaSyncSetting: (mediaSyncSetting: MediaSyncSetting) => void; }; }; const settingsSlice: StateCreator = (set, get) => ({ coordinateFormat: 'utm', manualCoordinateEntryFormat: 'utm', - mediaSyncSetting: 'everything', actions: { setCoordinateFormat: coordinateFormat => set({coordinateFormat}), setManualCoordinateEntryFormat: coordinateFormat => set({manualCoordinateEntryFormat: coordinateFormat}), - setMediaSyncSetting: mediaSyncSetting => set({mediaSyncSetting}), }, }); diff --git a/src/frontend/hooks/server/mediaSync.ts b/src/frontend/hooks/server/mediaSync.ts new file mode 100644 index 000000000..a2b1fedd0 --- /dev/null +++ b/src/frontend/hooks/server/mediaSync.ts @@ -0,0 +1,40 @@ +import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'; +import {useApi} from '../../contexts/ApiContext'; +import {MediaSyncSetting} from '../../sharedTypes'; + +export const MEDIA_SYNC_SETTING_KEY = 'media_sync_setting'; + +export function convertMediaSyncSetting(isArchive: boolean): MediaSyncSetting { + return isArchive ? 'everything' : 'previews'; +} + +export function isArchiveDevice(value: MediaSyncSetting): boolean { + return value === 'everything'; +} + +export function useGetMediaSyncSetting() { + const api = useApi(); + + return useQuery({ + queryKey: [MEDIA_SYNC_SETTING_KEY], + queryFn: async () => { + const isArchive = await api.getIsArchiveDevice(); + return convertMediaSyncSetting(isArchive); + }, + }); +} + +export function useSetMediaSyncSetting() { + const api = useApi(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (newSetting: MediaSyncSetting) => { + const isArchive = isArchiveDevice(newSetting); + await api.setIsArchiveDevice(isArchive); + }, + onSuccess: () => { + queryClient.invalidateQueries({queryKey: [MEDIA_SYNC_SETTING_KEY]}); + }, + }); +} diff --git a/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx b/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx index 1f34b7c62..9843e9152 100644 --- a/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx +++ b/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx @@ -3,16 +3,16 @@ import {ScrollView, StyleSheet} from 'react-native'; import {useIntl, defineMessages} from 'react-intl'; import {SelectOne} from '../../../sharedComponents/SelectOne'; import {SYNC_BACKGROUND} from '../../../lib/styles'; -import { - usePersistedSettings, - usePersistedSettingsAction, -} from '../../../hooks/persistedState/usePersistedSettings'; import {MediaSyncActionSheetContent} from './MediaSyncActionSheetContent'; import { useBottomSheetModal, BottomSheetModal, } from '../../../sharedComponents/BottomSheetModal'; import {MediaSyncSetting} from '../../../sharedTypes'; +import { + useGetMediaSyncSetting, + useSetMediaSyncSetting, +} from '../../../hooks/server/mediaSync'; const m = defineMessages({ syncSettingsTitle: { @@ -71,14 +71,17 @@ const m = defineMessages({ export const MediaSyncSettings = () => { const {formatMessage: t} = useIntl(); - const mediaSyncSetting = usePersistedSettings( - store => store.mediaSyncSetting, - ); - const {setMediaSyncSetting} = usePersistedSettingsAction(); + const {data: mediaSyncSetting, isLoading} = useGetMediaSyncSetting(); + const {mutate: setMediaSyncSetting} = useSetMediaSyncSetting(); - const [modalType, setModalType] = React.useState( - () => mediaSyncSetting, - ); + const [modalType, setModalType] = + React.useState('everything'); + + React.useEffect(() => { + if (!isLoading && mediaSyncSetting) { + setModalType(mediaSyncSetting); + } + }, [mediaSyncSetting]); const {isOpen, openSheet, closeSheet, sheetRef} = useBottomSheetModal({ openOnMount: false, @@ -97,7 +100,7 @@ export const MediaSyncSettings = () => { const options: { value: MediaSyncSetting; label: string; - hint?: React.ReactNode; + hint: React.ReactNode; }[] = [ { value: 'previews', @@ -120,7 +123,7 @@ export const MediaSyncSettings = () => { return ( { : t(m.syncEverything) } confirmAction={handleConfirm} - onDismiss={() => { - closeSheet(); - }} + onDismiss={closeSheet} /> diff --git a/src/frontend/screens/Settings/ProjectSettings/index.tsx b/src/frontend/screens/Settings/ProjectSettings/index.tsx index e8e7241cb..0a9e0931c 100644 --- a/src/frontend/screens/Settings/ProjectSettings/index.tsx +++ b/src/frontend/screens/Settings/ProjectSettings/index.tsx @@ -98,17 +98,13 @@ export const ProjectSettings: NativeNavigationComponent<'ProjectSettings'> = ({ }, ] : []), - ...(process.env.EXPO_PUBLIC_FEATURE_MEDIA_MANAGER - ? [ - { - onPress: () => { - navigation.navigate('MediaSyncSettings'); - }, - primaryText: formatMessage(m.mediaSyncSettings), - testID: 'AIN.sync-list-item', - }, - ] - : []), + { + onPress: () => { + navigation.navigate('MediaSyncSettings'); + }, + primaryText: formatMessage(m.mediaSyncSettings), + testID: 'MAIN.sync-list-item', + }, ]; return ; From 27ea8e57fb4c1d89b551365603652636af77b427 Mon Sep 17 00:00:00 2001 From: Cindy Green Date: Tue, 10 Dec 2024 10:44:37 -0500 Subject: [PATCH 2/3] Uses react suspense. Makes sure state is only reflective of user's temporary choice, not data from server. --- src/frontend/hooks/server/mediaSync.ts | 26 ++++++++++++-- .../ProjectSettings/MediaSyncSettings.tsx | 34 +++++++++---------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/frontend/hooks/server/mediaSync.ts b/src/frontend/hooks/server/mediaSync.ts index a2b1fedd0..de9721101 100644 --- a/src/frontend/hooks/server/mediaSync.ts +++ b/src/frontend/hooks/server/mediaSync.ts @@ -1,4 +1,8 @@ -import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'; +import { + useMutation, + useQueryClient, + useSuspenseQuery, +} from '@tanstack/react-query'; import {useApi} from '../../contexts/ApiContext'; import {MediaSyncSetting} from '../../sharedTypes'; @@ -15,7 +19,7 @@ export function isArchiveDevice(value: MediaSyncSetting): boolean { export function useGetMediaSyncSetting() { const api = useApi(); - return useQuery({ + return useSuspenseQuery({ queryKey: [MEDIA_SYNC_SETTING_KEY], queryFn: async () => { const isArchive = await api.getIsArchiveDevice(); @@ -33,7 +37,23 @@ export function useSetMediaSyncSetting() { const isArchive = isArchiveDevice(newSetting); await api.setIsArchiveDevice(isArchive); }, - onSuccess: () => { + onMutate: async newSetting => { + await queryClient.cancelQueries({queryKey: [MEDIA_SYNC_SETTING_KEY]}); + const previousSetting = queryClient.getQueryData([ + MEDIA_SYNC_SETTING_KEY, + ]); + queryClient.setQueryData([MEDIA_SYNC_SETTING_KEY], newSetting); + return {previousSetting}; + }, + onError: (err, newSetting, context) => { + if (context?.previousSetting) { + queryClient.setQueryData( + [MEDIA_SYNC_SETTING_KEY], + context.previousSetting, + ); + } + }, + onSettled: () => { queryClient.invalidateQueries({queryKey: [MEDIA_SYNC_SETTING_KEY]}); }, }); diff --git a/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx b/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx index 9843e9152..f4ad7665f 100644 --- a/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx +++ b/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx @@ -71,29 +71,29 @@ const m = defineMessages({ export const MediaSyncSettings = () => { const {formatMessage: t} = useIntl(); - const {data: mediaSyncSetting, isLoading} = useGetMediaSyncSetting(); + const {data: mediaSyncSetting} = useGetMediaSyncSetting(); const {mutate: setMediaSyncSetting} = useSetMediaSyncSetting(); - - const [modalType, setModalType] = - React.useState('everything'); - - React.useEffect(() => { - if (!isLoading && mediaSyncSetting) { - setModalType(mediaSyncSetting); - } - }, [mediaSyncSetting]); + const [possibleSetting, setPossibleSetting] = + React.useState(null); const {isOpen, openSheet, closeSheet, sheetRef} = useBottomSheetModal({ openOnMount: false, }); const handleOptionChange = (value: MediaSyncSetting) => { - setModalType(value); + setPossibleSetting(value); openSheet(); }; const handleConfirm = () => { - setMediaSyncSetting(modalType); + if (possibleSetting) { + setMediaSyncSetting(possibleSetting); + } + closeSheet(); + }; + + const handleDismiss = () => { + setPossibleSetting(null); closeSheet(); }; @@ -123,7 +123,7 @@ export const MediaSyncSettings = () => { return ( { {t(m.syncPreviewsDescriptionBottomSheet)} {'\n\n'} @@ -148,12 +148,12 @@ export const MediaSyncSettings = () => { ) } confirmActionText={ - modalType === 'previews' + possibleSetting === 'previews' ? t(m.syncPreviewsBottomSheetConfirm) : t(m.syncEverything) } confirmAction={handleConfirm} - onDismiss={closeSheet} + onDismiss={handleDismiss} /> From 461471e85f5be2f1b994576aef6d36d91547ca81 Mon Sep 17 00:00:00 2001 From: Cindy Green Date: Wed, 11 Dec 2024 10:04:58 -0500 Subject: [PATCH 3/3] Uses optimistic updates via the ui. --- src/frontend/hooks/server/mediaSync.ts | 24 ++++--------------- .../ProjectSettings/MediaSyncSettings.tsx | 8 +++++-- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/frontend/hooks/server/mediaSync.ts b/src/frontend/hooks/server/mediaSync.ts index de9721101..e665e07bd 100644 --- a/src/frontend/hooks/server/mediaSync.ts +++ b/src/frontend/hooks/server/mediaSync.ts @@ -35,26 +35,12 @@ export function useSetMediaSyncSetting() { return useMutation({ mutationFn: async (newSetting: MediaSyncSetting) => { const isArchive = isArchiveDevice(newSetting); - await api.setIsArchiveDevice(isArchive); + return api.setIsArchiveDevice(isArchive); }, - onMutate: async newSetting => { - await queryClient.cancelQueries({queryKey: [MEDIA_SYNC_SETTING_KEY]}); - const previousSetting = queryClient.getQueryData([ - MEDIA_SYNC_SETTING_KEY, - ]); - queryClient.setQueryData([MEDIA_SYNC_SETTING_KEY], newSetting); - return {previousSetting}; - }, - onError: (err, newSetting, context) => { - if (context?.previousSetting) { - queryClient.setQueryData( - [MEDIA_SYNC_SETTING_KEY], - context.previousSetting, - ); - } - }, - onSettled: () => { - queryClient.invalidateQueries({queryKey: [MEDIA_SYNC_SETTING_KEY]}); + onSettled: async () => { + return await queryClient.invalidateQueries({ + queryKey: [MEDIA_SYNC_SETTING_KEY], + }); }, }); } diff --git a/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx b/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx index f4ad7665f..a79136512 100644 --- a/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx +++ b/src/frontend/screens/Settings/ProjectSettings/MediaSyncSettings.tsx @@ -72,7 +72,11 @@ const m = defineMessages({ export const MediaSyncSettings = () => { const {formatMessage: t} = useIntl(); const {data: mediaSyncSetting} = useGetMediaSyncSetting(); - const {mutate: setMediaSyncSetting} = useSetMediaSyncSetting(); + const { + mutate: setMediaSyncSetting, + variables, + isPending, + } = useSetMediaSyncSetting(); const [possibleSetting, setPossibleSetting] = React.useState(null); @@ -123,7 +127,7 @@ export const MediaSyncSettings = () => { return (