diff --git a/apps/mobile/package.json b/apps/mobile/package.json index b63c4ccbb..7d9c4f47b 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -31,7 +31,9 @@ "@react-navigation/drawer": "7.1.1", "@react-navigation/native": "7.0.14", "@shopify/flash-list": "1.7.3", + "@tanstack/query-sync-storage-persister": "5.66.4", "@tanstack/react-query": "5.66.7", + "@tanstack/react-query-persist-client": "5.66.7", "better-auth": "1.1.18", "cookie-es": "1.2.2", "dayjs": "1.11.13", diff --git a/apps/mobile/src/initialize/hydrate.ts b/apps/mobile/src/initialize/hydrate.ts index de93760db..ae154f633 100644 --- a/apps/mobile/src/initialize/hydrate.ts +++ b/apps/mobile/src/initialize/hydrate.ts @@ -1,2 +1,21 @@ +import { persistQueryClient } from "@tanstack/react-query-persist-client" + +import { kvStoragePersister, queryClient } from "../lib/query-client" + export { hydrateDatabaseToStore } from "../services" export const hydrateSettings = () => {} +export const hydrateQueryClient = () => { + persistQueryClient({ + queryClient, + persister: kvStoragePersister, + dehydrateOptions: { + shouldDehydrateQuery(query) { + return query.queryKey.includes("cache") + }, + shouldDehydrateMutation() { + return false + }, + }, + maxAge: 1000 * 60 * 60 * 24 * 1, + }) +} diff --git a/apps/mobile/src/initialize/index.ts b/apps/mobile/src/initialize/index.ts index f8d800919..97fc96bdf 100644 --- a/apps/mobile/src/initialize/index.ts +++ b/apps/mobile/src/initialize/index.ts @@ -1,6 +1,6 @@ import { initializeDb } from "../database" import { initializeDayjs } from "./dayjs" -import { hydrateDatabaseToStore, hydrateSettings } from "./hydrate" +import { hydrateDatabaseToStore, hydrateQueryClient, hydrateSettings } from "./hydrate" import { migrateDatabase } from "./migration" import { initializePlayer } from "./player" /* eslint-disable no-console */ @@ -14,6 +14,7 @@ export const initializeApp = async () => { await apm("hydrateSettings", hydrateSettings) await apm("hydrateDatabaseToStore", hydrateDatabaseToStore) + await apm("hydrateQueryClient", hydrateQueryClient) const loadingTime = Date.now() - now console.log(`Initialize done,`, `${loadingTime}ms`) diff --git a/apps/mobile/src/lib/query-client.ts b/apps/mobile/src/lib/query-client.ts index 944dbea12..ff404d56f 100644 --- a/apps/mobile/src/lib/query-client.ts +++ b/apps/mobile/src/lib/query-client.ts @@ -1,3 +1,20 @@ +import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister" import { QueryClient } from "@tanstack/react-query" -export const queryClient = new QueryClient() +import { kv } from "./kv" + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + gcTime: 1000 * 60 * 60 * 24, + }, + }, +}) + +export const kvStoragePersister = createSyncStoragePersister({ + storage: { + getItem: (key: string) => kv.getSync(key), + setItem: (key: string, value: string) => kv.setSync(key, value), + removeItem: (key: string) => kv.delete(key), + }, +}) diff --git a/apps/mobile/src/modules/discover/Recommendations.tsx b/apps/mobile/src/modules/discover/Recommendations.tsx index fd0ff70ff..99b86046e 100644 --- a/apps/mobile/src/modules/discover/Recommendations.tsx +++ b/apps/mobile/src/modules/discover/Recommendations.tsx @@ -108,7 +108,7 @@ const Tab: TabComponent = ({ tab, isSelected, ...rest }) => { const tabHeight = useBottomTabBarHeight() const { data, isLoading } = useQuery({ - queryKey: ["rsshub-popular", tab.value], + queryKey: ["rsshub-popular", "cache", tab.value], queryFn: () => fetchRsshubPopular(tab.value, "all").then((res) => res.data), }) const keys = useMemo(() => { diff --git a/apps/mobile/src/morph/hono.ts b/apps/mobile/src/morph/hono.ts index 1a34cbef4..37325f293 100644 --- a/apps/mobile/src/morph/hono.ts +++ b/apps/mobile/src/morph/hono.ts @@ -180,6 +180,7 @@ class Morph { // TODO: handle inboxHandle inboxHandle: "", read: false, + sources: null, } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 794f4feaf..18283f3ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -470,9 +470,15 @@ importers: '@shopify/flash-list': specifier: 1.7.3 version: 1.7.3(@babel/runtime@7.26.9)(react-native@0.77.0(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1) + '@tanstack/query-sync-storage-persister': + specifier: 5.66.4 + version: 5.66.4 '@tanstack/react-query': specifier: 5.66.7 version: 5.66.7(react@18.3.1) + '@tanstack/react-query-persist-client': + specifier: 5.66.7 + version: 5.66.7(@tanstack/react-query@5.66.7(react@18.3.1))(react@18.3.1) better-auth: specifier: 1.1.18 version: 1.1.18 @@ -3998,8 +4004,8 @@ packages: '@radix-ui/react-avatar@1.1.3': resolution: {integrity: sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==} peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -4064,7 +4070,7 @@ packages: '@radix-ui/react-context@1.1.1': resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} peerDependencies: - '@types/react': '*' + '@types/react': npm:@types/react@^18.3.12 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -4095,8 +4101,8 @@ packages: '@radix-ui/react-dismissable-layer@1.1.5': resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==} peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -4121,7 +4127,7 @@ packages: '@radix-ui/react-focus-guards@1.1.1': resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: - '@types/react': '*' + '@types/react': npm:@types/react@^18.3.12 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -4178,8 +4184,8 @@ packages: '@radix-ui/react-menu@2.1.6': resolution: {integrity: sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==} peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -4217,8 +4223,8 @@ packages: '@radix-ui/react-popper@1.2.2': resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==} peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -4230,8 +4236,8 @@ packages: '@radix-ui/react-portal@1.1.4': resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==} peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -4282,8 +4288,8 @@ packages: '@radix-ui/react-roving-focus@1.1.2': resolution: {integrity: sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==} peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -4400,7 +4406,7 @@ packages: '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: - '@types/react': '*' + '@types/react': npm:@types/react@^18.3.12 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -4409,7 +4415,7 @@ packages: '@radix-ui/react-use-controllable-state@1.1.0': resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: - '@types/react': '*' + '@types/react': npm:@types/react@^18.3.12 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -4463,8 +4469,8 @@ packages: '@radix-ui/react-visually-hidden@1.1.2': resolution: {integrity: sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==} peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -4546,7 +4552,7 @@ packages: '@react-native-menu/menu@1.2.2': resolution: {integrity: sha512-Uk65PAhwNkCVBAqJu5t2H9biV+m0JLwJc7m3v2X2A/W8SFJmUqYabBsLH4fOWKI3a7kkR9QDT6HruliIKSfM8w==} peerDependencies: - react: '*' + react: 18.3.1 react-native: '*' '@react-native-picker/picker@2.11.0': @@ -8483,7 +8489,7 @@ packages: resolution: {integrity: sha512-LUnfrddmee1xLOkyG2NN1l9xQbtvMX3fbM1brEGHg0SKSndvjod3FQdhTzZEYAariqW2RSxQR8v1IsheIoLQXg==} peerDependencies: expo: '*' - react-native: npm:react-native@0.77.0 + react-native: '*' expo-module-scripts@4.0.4: resolution: {integrity: sha512-ytFufVi7HTFLxxf8fbtz3DJuegq3489WPk9pz8Yqm3tYfZ+6/yufPYxLvk6N8b4yBW05oNr/Cqp7MWLpm0xPcA==} @@ -12419,7 +12425,7 @@ packages: resolution: {integrity: sha512-sc5qgX4YtM6IxjtosjgQLdsal120MvU+YWs0F2MdgQWijps22AXLDCUoBnZZ8vxVhVyJ2WnnIPrmtVBvVJjSuQ==} engines: {node: '>= 18.0.0'} peerDependencies: - react: '*' + react: 18.3.1 react-native: '*' react-native-gesture-handler: '>=2.0.0' react-native-reanimated: '>=3.0.0' @@ -12539,7 +12545,7 @@ packages: resolution: {integrity: sha512-9PPREdwH3tMR8otUK1voiFw9AFNtC37Del2fZwbCMBKjOyClC+f3Fwp7KqUouP0A5c3zjEc3T8TetBGQGPQE9A==} peerDependencies: react: '*' - react-native: '*' + react-native: npm:react-native@0.77.0 react-native-gesture-handler: '*' react-native-reanimated: '*' @@ -12553,7 +12559,7 @@ packages: resolution: {integrity: sha512-E5N/eK/+HtAVJUAzXpm1cWz8ROheV9jb0TI6h2bM+333U+DWibTTnT2T1122FkCoXLXIYavtm2FR2if+5jH8cA==} peerDependencies: react: '>=16.8.6' - react-native: '>=0.60.0-rc.2' + react-native: npm:react-native@0.77.0 react-native-windows: '>=0.63.0' shaka-player: ^4.7.9 peerDependenciesMeta: @@ -12573,7 +12579,7 @@ packages: react-native-volume-manager@2.0.8: resolution: {integrity: sha512-aZM47/mYkdQ4CbXpKYO6Ajiczv7fxbQXZ9c0H8gRuQUaS3OCz/MZABer6o9aDWq0KMNsQ7q7GVFLRPnSSeeMmw==} peerDependencies: - react: '*' + react: 18.3.1 react-native: '*' react-native-web@0.19.13: