Skip to content

Commit

Permalink
refactor(preferences): allow to perform partial key update via update…
Browse files Browse the repository at this point in the history
… callback

Refs #375
  • Loading branch information
lpezzolla committed Nov 23, 2023
1 parent 94927ee commit 7dbf2dc
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 29 deletions.
29 changes: 23 additions & 6 deletions src/core/contexts/PreferencesContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export type CoursesPreferences = {
[courseId: number | string]: CoursePreferencesProps;
};

export interface PreferencesContextBase {
lastInstalledVersion: string | null;
export interface EditablePreferences {
lastInstalledVersion?: string;
username: string;
campusId?: string;
colorScheme: 'light' | 'dark' | 'system';
Expand Down Expand Up @@ -76,10 +76,27 @@ export interface PreferencesContextBase {
};
}

export interface PreferencesContextProps extends PreferencesContextBase {
updatePreference: <T extends PreferenceKey>(
key: T,
value: PreferencesContextBase[T],
/**
* A callback that receives the previous value of the preference and returns the next one
*/
type UpdatePreferenceCallback<
K extends PreferenceKey,
V extends EditablePreferences[K],
> = (prev: V | undefined) => V | undefined;

/**
* The value of a preference can be:
* - a value of the type of the preference
* - a callback that receives the previous value of the preference and returns the next one
*/
export type UpdatePreferenceValue<K extends PreferenceKey> =
| EditablePreferences[K]
| UpdatePreferenceCallback<K, EditablePreferences[K]>;

export interface PreferencesContextProps extends EditablePreferences {
updatePreference: <K extends PreferenceKey>(
key: K,
value: UpdatePreferenceValue<K>,
) => void;
}

Expand Down
56 changes: 38 additions & 18 deletions src/core/providers/PreferencesProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { PropsWithChildren, useEffect, useRef, useState } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

import {
EditablePreferences,
PreferenceKey,
PreferencesContext,
PreferencesContextProps,
UpdatePreferenceValue,
editablePreferenceKeys,
objectPreferenceKeys,
} from '../contexts/PreferencesContext';
Expand All @@ -15,7 +17,7 @@ export const PreferencesProvider = ({ children }: PropsWithChildren) => {
const deviceLanguage = useDeviceLanguage();
const [preferencesContext, setPreferencesContext] =
useState<PreferencesContextProps>({
lastInstalledVersion: null,
lastInstalledVersion: undefined,
username: '',
colorScheme: 'system',
courses: {},
Expand All @@ -38,35 +40,52 @@ export const PreferencesProvider = ({ children }: PropsWithChildren) => {

const preferencesInitialized = useRef<boolean>(false);

const updatePreference = (key: PreferenceKey, value: unknown) => {
const stringKey = key.toString();
if (value === null) {
AsyncStorage.removeItem(stringKey).then(() =>
setPreferencesContext(oldP => ({
...oldP,
[stringKey]: value,
})),
);
} else {
// Initialize preferences from AsyncStorage
useEffect(() => {
const updatePreference = <K extends PreferenceKey>(
key: K,
value: UpdatePreferenceValue<K>,
) => {
const stringKey = key.toString();

// if value is undefined, remove the preference
if (value === undefined) {
AsyncStorage.removeItem(stringKey).then(() =>
setPreferencesContext(oldP => ({
...oldP,
[stringKey]: value,
})),
);

return;
}

let storageValue: string;
let nextValue: EditablePreferences[PreferenceKey];

// if value is a callback, call it with the current value
if (typeof value === 'function') {
const currentValue = preferencesContext[key];
nextValue = value(currentValue);
} else {
nextValue = value;
}

// if value is an object, stringify it
if (objectPreferenceKeys.includes(key)) {
storageValue = JSON.stringify(value);
storageValue = JSON.stringify(nextValue);
} else {
storageValue = value as string;
storageValue = nextValue as string;
}

AsyncStorage.setItem(stringKey, storageValue).then(() =>
setPreferencesContext(oldP => ({
...oldP,
[stringKey]: value,
[stringKey]: nextValue,
})),
);
}
};
};

// Initialize preferences from AsyncStorage
useEffect(() => {
AsyncStorage.multiGet(editablePreferenceKeys).then(storagePreferences => {
const preferences: Partial<PreferencesContextProps> = {
updatePreference,
Expand All @@ -90,6 +109,7 @@ export const PreferencesProvider = ({ children }: PropsWithChildren) => {
return { ...oldP, ...preferences };
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps -- only on mount
}, []);

// Preferences are loaded
Expand Down
4 changes: 2 additions & 2 deletions src/features/courses/screens/CourseIconPickerScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack';

import { BottomBarSpacer } from '../../../core/components/BottomBarSpacer';
import {
PreferencesContextBase,
EditablePreferences,
usePreferencesContext,
} from '../../../core/contexts/PreferencesContext';
import { useSafeAreaSpacing } from '../../../core/hooks/useSafeAreaSpacing';
Expand Down Expand Up @@ -96,7 +96,7 @@ export const CourseIconPickerScreen = ({ navigation, route }: Props) => {
...coursePrefs,
icon: null,
},
} as PreferencesContextBase['courses']);
} as EditablePreferences['courses']);
navigation.goBack();
}}
/>
Expand Down
6 changes: 3 additions & 3 deletions src/features/user/screens/SettingsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { version } from '../../../../package.json';
import { BottomBarSpacer } from '../../../core/components/BottomBarSpacer';
import { useFeedbackContext } from '../../../core/contexts/FeedbackContext';
import {
PreferencesContextBase,
EditablePreferences,
usePreferencesContext,
} from '../../../core/contexts/PreferencesContext';
import { useConfirmationDialog } from '../../../core/hooks/useConfirmationDialog';
Expand Down Expand Up @@ -178,7 +178,7 @@ const VisualizationListItem = () => {
onPressAction={({ nativeEvent: { event } }) => {
updatePreference(
'colorScheme',
event as PreferencesContextBase['colorScheme'],
event as EditablePreferences['colorScheme'],
);
}}
>
Expand Down Expand Up @@ -247,7 +247,7 @@ const Notifications = () => {
updatePreference('notifications', {
...notifications,
[notificationType]: value,
} as PreferencesContextBase['notifications']);
} as EditablePreferences['notifications']);
};

return (
Expand Down

0 comments on commit 7dbf2dc

Please sign in to comment.