From fdcc4eef14c2b2e96618cd0a8cb1b112ffd78a38 Mon Sep 17 00:00:00 2001 From: Robert Eggl Date: Mon, 9 Dec 2024 12:43:43 +0100 Subject: [PATCH] fix(store): add reset functionality to preferences store and streamline initial state setup --- android/app/build.gradle | 2 +- app.config.json | 2 +- bun.lockb | Bin 644592 -> 644592 bytes src/api/thi-session-handler.ts | 13 ++------ src/app/(flow)/onboarding.tsx | 2 +- src/app/(screens)/profile.tsx | 7 ++++- src/app/(screens)/settings.tsx | 6 ++++ src/app/(tabs)/_layout.tsx | 2 +- src/components/Dashboard/HeaderRight.tsx | 6 ++++ src/hooks/mmkv.ts | 19 ------------ src/hooks/useFlowStore.ts | 11 ++++--- src/hooks/useFoodFilterStore.ts | 31 ++++++++++++++----- src/hooks/usePreferencesStore.ts | 36 +++++++++++++++++------ src/utils/storage.ts | 24 +++++++++++++-- 14 files changed, 102 insertions(+), 59 deletions(-) delete mode 100644 src/hooks/mmkv.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index 5f598722..61cd899c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,7 +91,7 @@ android { applicationId 'app.neuland' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 244 + versionCode 245 versionName "0.11.0" } signingConfigs { diff --git a/app.config.json b/app.config.json index 9d8df04d..9d853117 100644 --- a/app.config.json +++ b/app.config.json @@ -36,7 +36,7 @@ "android": { "package": "app.neuland", "userInterfaceStyle": "automatic", - "versionCode": 244 + "versionCode": 245 }, "sdkVersion": "52.0.0", "experiments": { diff --git a/bun.lockb b/bun.lockb index ac4a0829824bd5be4f1d992c8079d35f70e7d8fa..465674f0f86a532bbf0c9346726f9fa46961f150 100755 GIT binary patch delta 41 ucmeycTK&Un^@bM47N!>F7M2#)Eo^T+87F7M2#)Eo^T+8O__@d9nd9I}mehf9J``)B^x%(-5Qp diff --git a/src/api/thi-session-handler.ts b/src/api/thi-session-handler.ts index ca7ad684..94ef0a87 100644 --- a/src/api/thi-session-handler.ts +++ b/src/api/thi-session-handler.ts @@ -224,18 +224,9 @@ export async function forgetSession(): Promise { SecureStore.deleteItemAsync('password'), ]) - // clear all AsyncStorage data except analytics + // clear the general storage (cache) try { - const keys = storage.getAllKeys() - for (const key of keys) { - if ( - key !== 'analytics' && - key !== 'isOnboardedv1' && - !key.startsWith('isUpdated-') - ) { - storage.delete(key) - } - } + storage.clearAll() } catch (e) { console.error(e) } diff --git a/src/app/(flow)/onboarding.tsx b/src/app/(flow)/onboarding.tsx index 6c2051fd..4d07ae8d 100644 --- a/src/app/(flow)/onboarding.tsx +++ b/src/app/(flow)/onboarding.tsx @@ -77,7 +77,7 @@ export default function OnboardingScreen(): JSX.Element { if (Platform.OS === 'ios') { void Haptics.selectionAsync() } - setOnboarded(true) + setOnboarded() toggleUpdated() setAnalyticsAllowed(true) router.navigate({ diff --git a/src/app/(screens)/profile.tsx b/src/app/(screens)/profile.tsx index 980a06b0..47e58035 100644 --- a/src/app/(screens)/profile.tsx +++ b/src/app/(screens)/profile.tsx @@ -7,6 +7,8 @@ import { DashboardContext, UserKindContext } from '@/components/contexts' import { queryClient } from '@/components/provider' import { USER_STUDENT } from '@/data/constants' import { useRefreshByUser } from '@/hooks' +import { useFoodFilterStore } from '@/hooks/useFoodFilterStore' +import { usePreferencesStore } from '@/hooks/usePreferencesStore' import { type FormListSections } from '@/types/components' import { getPersonalData, networkError, performLogout } from '@/utils/api-utils' import { useQuery } from '@tanstack/react-query' @@ -36,7 +38,8 @@ export default function Profile(): JSX.Element { const { resetOrder } = useContext(DashboardContext) const { t } = useTranslation('settings') const [isLoggingOut, setIsLoggingOut] = React.useState(false) - + const resetPreferences = usePreferencesStore((state) => state.reset) + const resetFood = useFoodFilterStore((state) => state.reset) const { data, error, isLoading, isPaused, isSuccess, refetch, isError } = useQuery({ queryKey: ['personalData'], @@ -109,6 +112,8 @@ export default function Profile(): JSX.Element { style: 'destructive', onPress: () => { setIsLoggingOut(true) + resetFood() + resetPreferences() performLogout(toggleUserKind, resetOrder, queryClient) .catch((e) => { console.log(e) diff --git a/src/app/(screens)/settings.tsx b/src/app/(screens)/settings.tsx index d05cb931..4b205b31 100644 --- a/src/app/(screens)/settings.tsx +++ b/src/app/(screens)/settings.tsx @@ -12,6 +12,8 @@ import { queryClient } from '@/components/provider' import { type UserKindContextType } from '@/contexts/userKind' import { USER_EMPLOYEE, USER_GUEST, USER_STUDENT } from '@/data/constants' import { useRefreshByUser } from '@/hooks' +import { useFoodFilterStore } from '@/hooks/useFoodFilterStore' +import { usePreferencesStore } from '@/hooks/usePreferencesStore' import { type FormListSections } from '@/types/components' import { type MaterialIcon } from '@/types/material-icons' import { @@ -76,6 +78,8 @@ export default function Settings(): JSX.Element { const username = userKind === USER_EMPLOYEE && SecureStore.getItem('username') const { color, randomizeColor } = useRandomColor() + const resetPreferences = usePreferencesStore((state) => state.reset) + const resetFood = useFoodFilterStore((state) => state.reset) useEffect(() => { const { bottomBoundY, topBoundY } = getBounds() @@ -157,6 +161,8 @@ export default function Settings(): JSX.Element { text: t('profile.logout.alert.confirm'), style: 'destructive', onPress: () => { + resetPreferences() + resetFood() performLogout( toggleUserKind, resetOrder, diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx index b9c6f3b9..bad6d71f 100644 --- a/src/app/(tabs)/_layout.tsx +++ b/src/app/(tabs)/_layout.tsx @@ -63,7 +63,7 @@ export default function HomeLayout(): JSX.Element { const [oldAccentColor] = useMMKVString('accentColor') // migration of old settings if (isOnboardedV1 === true) { - setOnboarded(true) + setOnboarded() storage.delete('isOnboardedv1') } if (analyticsV1 === true) { diff --git a/src/components/Dashboard/HeaderRight.tsx b/src/components/Dashboard/HeaderRight.tsx index 7f4599bc..44b7d4c6 100644 --- a/src/components/Dashboard/HeaderRight.tsx +++ b/src/components/Dashboard/HeaderRight.tsx @@ -4,6 +4,8 @@ import { DashboardContext, UserKindContext } from '@/components/contexts' import { queryClient } from '@/components/provider' import { type UserKindContextType } from '@/contexts/userKind' import { USER_EMPLOYEE, USER_GUEST, USER_STUDENT } from '@/data/constants' +import { useFoodFilterStore } from '@/hooks/useFoodFilterStore' +import { usePreferencesStore } from '@/hooks/usePreferencesStore' import { getPersonalData, getUsername, performLogout } from '@/utils/api-utils' import { getContrastColor, getInitials } from '@/utils/ui-utils' import { useQuery } from '@tanstack/react-query' @@ -22,6 +24,8 @@ export const IndexHeaderRight = (): JSX.Element => { const { t } = useTranslation(['navigation', 'settings']) const router = useRouter() const { styles, theme } = useStyles(stylesheet) + const resetPreferences = usePreferencesStore((state) => state.reset) + const resetFood = useFoodFilterStore((state) => state.reset) const { userKind = USER_GUEST } = useContext(UserKindContext) @@ -90,6 +94,8 @@ export const IndexHeaderRight = (): JSX.Element => { text: t('profile.logout.alert.confirm', { ns: 'settings' }), style: 'destructive', onPress: () => { + resetPreferences() + resetFood() performLogout( toggleUserKind, resetOrder, diff --git a/src/hooks/mmkv.ts b/src/hooks/mmkv.ts deleted file mode 100644 index 9fce8f70..00000000 --- a/src/hooks/mmkv.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MMKV } from 'react-native-mmkv' -import { type StateStorage } from 'zustand/middleware' - -export const storage = new MMKV({ - id: 'user-settings-storage', -}) - -export const zustandStorage: StateStorage = { - setItem(name, value) { - storage.set(name, value) - }, - getItem(name) { - const value = storage.getString(name) - return value ?? null - }, - removeItem(name) { - storage.delete(name) - }, -} diff --git a/src/hooks/useFlowStore.ts b/src/hooks/useFlowStore.ts index 5e2d8609..71900db9 100644 --- a/src/hooks/useFlowStore.ts +++ b/src/hooks/useFlowStore.ts @@ -1,13 +1,12 @@ import { convertToMajorMinorPatch } from '@/utils/app-utils' +import { zustandStorage } from '@/utils/storage' import * as Application from 'expo-application' import { create } from 'zustand' import { createJSONStorage, persist } from 'zustand/middleware' -import { zustandStorage } from './mmkv' - interface FlowStore { isOnboarded: boolean | undefined - setOnboarded: (value: boolean) => void + setOnboarded: () => void updatedVersion: string | undefined toggleUpdated: () => void @@ -22,8 +21,8 @@ export const useFlowStore = create()( persist( (set) => ({ isOnboarded: undefined, - setOnboarded: (value: boolean) => { - set({ isOnboarded: value }) + setOnboarded: () => { + set({ isOnboarded: true }) }, updatedVersion: undefined, @@ -39,7 +38,7 @@ export const useFlowStore = create()( }, }), { - name: `flow-store`, + name: 'flow-store', storage: createJSONStorage(() => zustandStorage), } ) diff --git a/src/hooks/useFoodFilterStore.ts b/src/hooks/useFoodFilterStore.ts index a467f37e..99d3221f 100644 --- a/src/hooks/useFoodFilterStore.ts +++ b/src/hooks/useFoodFilterStore.ts @@ -1,9 +1,8 @@ import { type LanguageKey } from '@/localization/i18n' +import { zustandStorage } from '@/utils/storage' import { create } from 'zustand' import { createJSONStorage, persist } from 'zustand/middleware' -import { zustandStorage } from './mmkv' - export type FoodLanguage = LanguageKey | 'default' interface FoodFilterStore { @@ -18,16 +17,30 @@ interface FoodFilterStore { toggleSelectedPreferences: (name: string) => void setShowStatic: (value: boolean) => void toggleFoodLanguage: (language: string) => void + reset: () => void +} + +const initialState: Omit< + FoodFilterStore, + | 'toggleSelectedRestaurant' + | 'toggleSelectedAllergens' + | 'initAllergenSelection' + | 'toggleSelectedPreferences' + | 'setShowStatic' + | 'toggleFoodLanguage' + | 'reset' +> = { + selectedRestaurants: ['IngolstadtMensa', 'Reimanns'], + preferencesSelection: ['veg', 'V'], + allergenSelection: ['not-configured'], + showStatic: undefined, + foodLanguage: 'default', } export const useFoodFilterStore = create()( persist( (set) => ({ - selectedRestaurants: ['IngolstadtMensa', 'Reimanns'], - preferencesSelection: ['veg', 'V'], - allergenSelection: ['not-configured'], - showStatic: undefined, - foodLanguage: 'default', + ...initialState, toggleSelectedRestaurant: (name: string) => { set((state) => { const checked = state.selectedRestaurants.includes(name) @@ -80,6 +93,10 @@ export const useFoodFilterStore = create()( toggleFoodLanguage: (language: string) => { set({ foodLanguage: language as FoodLanguage }) }, + reset: () => { + set({ ...initialState }) + zustandStorage.removeItem('food-filter-store') + }, }), { name: 'food-filter-storage', diff --git a/src/hooks/usePreferencesStore.ts b/src/hooks/usePreferencesStore.ts index 37babf22..4bfaefba 100644 --- a/src/hooks/usePreferencesStore.ts +++ b/src/hooks/usePreferencesStore.ts @@ -1,9 +1,8 @@ import { defaultQuicklinks } from '@/data/constants' +import { zustandStorage } from '@/utils/storage' import { create } from 'zustand' import { createJSONStorage, persist } from 'zustand/middleware' -import { zustandStorage } from './mmkv' - const DEFAULT_ACCENT_COLOR = 'blue' interface PreferencesStore { @@ -21,18 +20,33 @@ interface PreferencesStore { setTimetableMode: (mode: string) => void setSelectedDate: (date: Date) => void addRecentQuicklink: (quicklink: string) => void + reset: () => void +} + +const initialState: Omit< + PreferencesStore, + | 'setAccentColor' + | 'setTheme' + | 'setAppIcon' + | 'addUnlockedAppIcon' + | 'setTimetableMode' + | 'setSelectedDate' + | 'addRecentQuicklink' + | 'reset' +> = { + accentColor: DEFAULT_ACCENT_COLOR, + appIcon: undefined, + theme: 'auto', + unlockedAppIcons: [], + timetableMode: undefined, + selectedDate: new Date(), + recentQuicklinks: defaultQuicklinks, } export const usePreferencesStore = create()( persist( (set) => ({ - accentColor: DEFAULT_ACCENT_COLOR, - theme: 'auto', - appIcon: undefined, - unlockedAppIcons: [], - timetableMode: undefined, - selectedDate: new Date(), - recentQuicklinks: defaultQuicklinks, + ...initialState, setAccentColor: (accentColor: string) => { set({ accentColor }) }, @@ -81,6 +95,10 @@ export const usePreferencesStore = create()( return { recentQuicklinks: finalQuicklinks } }) }, + reset: () => { + set(initialState) + zustandStorage.removeItem('app-storage') + }, }), { name: 'app-storage', diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 49a36f36..c98dbd70 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,10 +1,13 @@ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister' import { MMKV } from 'react-native-mmkv' +import { type StateStorage } from 'zustand/middleware' -export const storage = new MMKV() +export const storage = new MMKV({ + id: 'query-client-storage', +}) const clientStorage = { - setItem: (key: string, value: string | number | boolean | Uint8Array) => { + setItem: (key: string, value: string | number | boolean | ArrayBuffer) => { storage.set(key, value) }, getItem: (key: string) => { @@ -19,3 +22,20 @@ const clientStorage = { export const syncStoragePersister = createSyncStoragePersister({ storage: clientStorage, }) + +export const appStorage = new MMKV({ + id: 'user-settings-storage', +}) + +export const zustandStorage: StateStorage = { + setItem(name, value) { + appStorage.set(name, value) + }, + getItem(name) { + const value = appStorage.getString(name) + return value ?? null + }, + removeItem(name) { + appStorage.delete(name) + }, +}