mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
complete migration to mmkv
This commit is contained in:
parent
e5e77508b8
commit
49b814a36d
54 changed files with 621 additions and 459 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -73,3 +73,4 @@ SDK54_UPGRADE_SUMMARY.md
|
|||
build-and-publish-app-releases.sh
|
||||
bottomnav.md
|
||||
/TrailerServices
|
||||
mmkv.md
|
||||
|
|
|
|||
4
App.tsx
4
App.tsx
|
|
@ -34,13 +34,13 @@ import UpdatePopup from './src/components/UpdatePopup';
|
|||
import MajorUpdateOverlay from './src/components/MajorUpdateOverlay';
|
||||
import { useGithubMajorUpdate } from './src/hooks/useGithubMajorUpdate';
|
||||
import { useUpdatePopup } from './src/hooks/useUpdatePopup';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import * as Sentry from '@sentry/react-native';
|
||||
import UpdateService from './src/services/updateService';
|
||||
import { memoryMonitorService } from './src/services/memoryMonitorService';
|
||||
import { aiService } from './src/services/aiService';
|
||||
import { AccountProvider, useAccount } from './src/contexts/AccountContext';
|
||||
import { ToastProvider } from './src/contexts/ToastContext';
|
||||
import { mmkvStorage } from './src/services/mmkvStorage';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
||||
|
|
@ -104,7 +104,7 @@ const ThemedApp = () => {
|
|||
const initializeApp = async () => {
|
||||
try {
|
||||
// Check onboarding status
|
||||
const onboardingCompleted = await AsyncStorage.getItem('hasCompletedOnboarding');
|
||||
const onboardingCompleted = await mmkvStorage.getItem('hasCompletedOnboarding');
|
||||
setHasCompletedOnboarding(onboardingCompleted === 'true');
|
||||
|
||||
// Initialize update service
|
||||
|
|
|
|||
|
|
@ -297,7 +297,6 @@
|
|||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoLocalization/ExpoLocalization_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/KSPlayer/KSPlayer_KSPlayer.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
|
||||
|
|
@ -340,7 +339,6 @@
|
|||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoLocalization_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/KSPlayer_KSPlayer.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
|
||||
|
|
@ -461,7 +459,7 @@
|
|||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||
PRODUCT_NAME = "Nuvio";
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -492,8 +490,8 @@
|
|||
"-lc++",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app";
|
||||
PRODUCT_NAME = "Nuvio";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
|
|
|||
|
|
@ -404,7 +404,56 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- MMKVCore (2.2.4)
|
||||
- MobileVLCKit (3.6.1b1)
|
||||
- NitroMmkv (4.0.0):
|
||||
- hermes-engine
|
||||
- MMKVCore (= 2.2.4)
|
||||
- NitroModules
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- React-Core-prebuilt
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- NitroModules (0.31.2):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- React-Core-prebuilt
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RCTDeprecation (0.81.4)
|
||||
- RCTRequired (0.81.4)
|
||||
- RCTTypeSafety (0.81.4):
|
||||
|
|
@ -2292,28 +2341,6 @@ PODS:
|
|||
- React-utils (= 0.81.4)
|
||||
- ReactNativeDependencies
|
||||
- ReactNativeDependencies (0.81.4)
|
||||
- RNCAsyncStorage (2.2.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-Core-prebuilt
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNCPicker (2.11.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
|
|
@ -2725,6 +2752,8 @@ DEPENDENCIES:
|
|||
- KSPlayer (from `https://github.com/kingslay/KSPlayer.git`, branch `main`)
|
||||
- Libass (from `https://github.com/kingslay/FFmpegKit.git`, branch `main`)
|
||||
- lottie-react-native (from `../node_modules/lottie-react-native`)
|
||||
- NitroMmkv (from `../node_modules/react-native-mmkv`)
|
||||
- NitroModules (from `../node_modules/react-native-nitro-modules`)
|
||||
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
|
||||
- RCTRequired (from `../node_modules/react-native/Libraries/Required`)
|
||||
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
|
||||
|
|
@ -2798,7 +2827,6 @@ DEPENDENCIES:
|
|||
- ReactCodegen (from `build/generated/ios`)
|
||||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||
- ReactNativeDependencies (from `../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`)
|
||||
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
|
||||
- "RNCPicker (from `../node_modules/@react-native-picker/picker`)"
|
||||
- "RNFastImage (from `../node_modules/@d11/react-native-fast-image`)"
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
|
|
@ -2816,6 +2844,7 @@ SPEC REPOS:
|
|||
- libdav1d
|
||||
- libwebp
|
||||
- lottie-ios
|
||||
- MMKVCore
|
||||
- MobileVLCKit
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
|
|
@ -2916,6 +2945,10 @@ EXTERNAL SOURCES:
|
|||
:git: https://github.com/kingslay/FFmpegKit.git
|
||||
lottie-react-native:
|
||||
:path: "../node_modules/lottie-react-native"
|
||||
NitroMmkv:
|
||||
:path: "../node_modules/react-native-mmkv"
|
||||
NitroModules:
|
||||
:path: "../node_modules/react-native-nitro-modules"
|
||||
RCTDeprecation:
|
||||
:path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation"
|
||||
RCTRequired:
|
||||
|
|
@ -3060,8 +3093,6 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native/ReactCommon"
|
||||
ReactNativeDependencies:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec"
|
||||
RNCAsyncStorage:
|
||||
:path: "../node_modules/@react-native-async-storage/async-storage"
|
||||
RNCPicker:
|
||||
:path: "../node_modules/@react-native-picker/picker"
|
||||
RNFastImage:
|
||||
|
|
@ -3145,7 +3176,10 @@ SPEC CHECKSUMS:
|
|||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
|
||||
lottie-react-native: cbe3d931a7c24f7891a8e8032c2bb9b2373c4b9c
|
||||
MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df
|
||||
MobileVLCKit: 2d9c7c373393ae43086aeeff890bf0b1afc15c5c
|
||||
NitroMmkv: 7fe66a61d5acab6516098a64f42af575595e7566
|
||||
NitroModules: 8c4eca403e6f45f474608d24cd11ab664ed2961c
|
||||
RCTDeprecation: 7487d6dda857ccd4cb3dd6ecfccdc3170e85dcbc
|
||||
RCTRequired: 54128b7df8be566881d48c7234724a78cb9b6157
|
||||
RCTTypeSafety: d2b07797a79e45d7b19e1cd2f53c79ab419fe217
|
||||
|
|
@ -3219,7 +3253,6 @@ SPEC CHECKSUMS:
|
|||
ReactCodegen: a15ad48730e9fb2a51a4c9f61fe1ed253dfcf10f
|
||||
ReactCommon: 149b6c05126f2e99f2ed0d3c63539369546f8cae
|
||||
ReactNativeDependencies: ed6d1e64802b150399f04f1d5728ec16b437251e
|
||||
RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4
|
||||
RNCPicker: a7170edbcbf8288de8edb2502e08e7fc757fa755
|
||||
RNFastImage: 42a769cd260a7686b1db32a9f7d754333bad4e77
|
||||
RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3
|
||||
|
|
|
|||
59
package-lock.json
generated
59
package-lock.json
generated
|
|
@ -19,7 +19,6 @@
|
|||
"@gorhom/bottom-sheet": "^5.2.6",
|
||||
"@legendapp/list": "^2.0.13",
|
||||
"@lottiefiles/dotlottie-react": "^0.6.5",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-community/blur": "^4.4.1",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-community/slider": "5.0.1",
|
||||
|
|
@ -74,6 +73,8 @@
|
|||
"react-native-image-colors": "^2.5.0",
|
||||
"react-native-immersive-mode": "^2.0.2",
|
||||
"react-native-markdown-display": "^7.0.2",
|
||||
"react-native-mmkv": "^4.0.0",
|
||||
"react-native-nitro-modules": "^0.31.2",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "^4.1.1",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
|
|
@ -2884,18 +2885,6 @@
|
|||
"integrity": "sha512-hxLL8kZNHH098geedcxCz8y6xojkNYbmJEW+1vFXsmPcExyCXIUUJ/34X6xa9GcprKxd0Wsx3vfJQLQX4iVPhw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-native-async-storage/async-storage": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
|
||||
"integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"merge-options": "^3.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-native": "^0.0.0-0 || >=0.65 <1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-community/blur": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-4.4.1.tgz",
|
||||
|
|
@ -7936,15 +7925,6 @@
|
|||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-obj": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
|
||||
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||
|
|
@ -9007,18 +8987,6 @@
|
|||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/merge-options": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
|
||||
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-plain-obj": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
|
|
@ -10850,6 +10818,29 @@
|
|||
"react-native": ">=0.50.4"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-mmkv": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-mmkv/-/react-native-mmkv-4.0.0.tgz",
|
||||
"integrity": "sha512-Osoy8as2ZLzO1TTsKxc4tX14Qk19qRVMWnS4ZVBwxie9Re5cjt7rqlpDkJczK3H/y3z70EQ6rmKI/cNMCLGAYQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-nitro-modules": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-nitro-modules": {
|
||||
"version": "0.31.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-nitro-modules/-/react-native-nitro-modules-0.31.2.tgz",
|
||||
"integrity": "sha512-UTG1kfLq5XDlR1OkhrNLNMbU6EvxIdLp4RUWT4PY8eBKyykOq8/kjc3fYMYxVEUZG+mrujRSMNXKeqHfiMUhOQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-paper": {
|
||||
"version": "5.14.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.14.5.tgz",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
"@gorhom/bottom-sheet": "^5.2.6",
|
||||
"@legendapp/list": "^2.0.13",
|
||||
"@lottiefiles/dotlottie-react": "^0.6.5",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-community/blur": "^4.4.1",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-community/slider": "5.0.1",
|
||||
|
|
@ -74,6 +73,8 @@
|
|||
"react-native-image-colors": "^2.5.0",
|
||||
"react-native-immersive-mode": "^2.0.2",
|
||||
"react-native-markdown-display": "^7.0.2",
|
||||
"react-native-mmkv": "^4.0.0",
|
||||
"react-native-nitro-modules": "^0.31.2",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "^4.1.1",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { useTheme } from '../../contexts/ThemeContext';
|
|||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { catalogService, StreamingContent } from '../../services/catalogService';
|
||||
import { DropUpMenu } from './DropUpMenu';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../../services/mmkvStorage';
|
||||
import { storageService } from '../../services/storageService';
|
||||
import { TraktService } from '../../services/traktService';
|
||||
import { useTraktContext } from '../../contexts/TraktContext';
|
||||
|
|
@ -97,7 +97,7 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe
|
|||
// Load watched state from AsyncStorage when item changes
|
||||
useEffect(() => {
|
||||
const updateWatched = () => {
|
||||
AsyncStorage.getItem(`watched:${item.type}:${item.id}`).then(val => setIsWatched(val === 'true'));
|
||||
mmkvStorage.getItem(`watched:${item.type}:${item.id}`).then((val: string | null) => setIsWatched(val === 'true'));
|
||||
};
|
||||
updateWatched();
|
||||
const sub = DeviceEventEmitter.addListener('watchedStatusChanged', updateWatched);
|
||||
|
|
@ -163,7 +163,7 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe
|
|||
const targetWatched = !isWatched;
|
||||
setIsWatched(targetWatched);
|
||||
try {
|
||||
await AsyncStorage.setItem(`watched:${item.type}:${item.id}`, targetWatched ? 'true' : 'false');
|
||||
await mmkvStorage.setItem(`watched:${item.type}:${item.id}`, targetWatched ? 'true' : 'false');
|
||||
} catch {}
|
||||
showInfo(targetWatched ? 'Marked as Watched' : 'Marked as Unwatched', targetWatched ? 'Item marked as watched' : 'Item marked as unwatched');
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'
|
|||
import { View, Text, StyleSheet, ActivityIndicator, Image, Animated, Dimensions } from 'react-native';
|
||||
import { useTheme } from '../../contexts/ThemeContext';
|
||||
import { useMDBListRatings } from '../../hooks/useMDBListRatings';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../../services/mmkvStorage';
|
||||
import { isMDBListEnabled, RATING_PROVIDERS_STORAGE_KEY } from '../../screens/MDBListSettingsScreen';
|
||||
|
||||
// Import SVG icons
|
||||
|
|
@ -124,7 +124,7 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
|
|||
|
||||
const loadProviderSettings = async () => {
|
||||
try {
|
||||
const savedSettings = await AsyncStorage.getItem(RATING_PROVIDERS_STORAGE_KEY);
|
||||
const savedSettings = await mmkvStorage.getItem(RATING_PROVIDERS_STORAGE_KEY);
|
||||
if (savedSettings) {
|
||||
setEnabledProviders(JSON.parse(savedSettings));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { useFocusEffect } from '@react-navigation/native';
|
|||
import Animated, { FadeIn, FadeOut, SlideInRight, SlideOutLeft } from 'react-native-reanimated';
|
||||
import { TraktService } from '../../services/traktService';
|
||||
import { logger } from '../../utils/logger';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../../services/mmkvStorage';
|
||||
|
||||
// Enhanced responsive breakpoints for Seasons Section
|
||||
const BREAKPOINTS = {
|
||||
|
|
@ -186,7 +186,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
useEffect(() => {
|
||||
const loadViewModePreference = async () => {
|
||||
try {
|
||||
const savedMode = await AsyncStorage.getItem('global_season_view_mode');
|
||||
const savedMode = await mmkvStorage.getItem('global_season_view_mode');
|
||||
if (savedMode === 'text' || savedMode === 'posters') {
|
||||
setSeasonViewMode(savedMode);
|
||||
if (__DEV__) console.log('[SeriesContent] Loaded global view mode:', savedMode);
|
||||
|
|
@ -215,7 +215,7 @@ export const SeriesContent: React.FC<SeriesContentProps> = ({
|
|||
// Update view mode without animations
|
||||
const updateViewMode = (newMode: 'posters' | 'text') => {
|
||||
setSeasonViewMode(newMode);
|
||||
AsyncStorage.setItem('global_season_view_mode', newMode).catch(error => {
|
||||
mmkvStorage.setItem('global_season_view_mode', newMode).catch((error: any) => {
|
||||
if (__DEV__) console.log('[SeriesContent] Error saving global view mode preference:', error);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import RNImmersiveMode from 'react-native-immersive-mode';
|
|||
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import { storageService } from '../../services/storageService';
|
||||
import { logger } from '../../utils/logger';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../../services/mmkvStorage';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { useTraktAutosync } from '../../hooks/useTraktAutosync';
|
||||
|
|
@ -235,7 +235,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
// Load speed settings from storage
|
||||
const loadSpeedSettings = useCallback(async () => {
|
||||
try {
|
||||
const saved = await AsyncStorage.getItem(SPEED_SETTINGS_KEY);
|
||||
const saved = await mmkvStorage.getItem(SPEED_SETTINGS_KEY);
|
||||
if (saved) {
|
||||
const settings = JSON.parse(saved);
|
||||
if (typeof settings.holdToSpeedEnabled === 'boolean') {
|
||||
|
|
@ -257,7 +257,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
holdToSpeedEnabled,
|
||||
holdToSpeedValue,
|
||||
};
|
||||
await AsyncStorage.setItem(SPEED_SETTINGS_KEY, JSON.stringify(settings));
|
||||
await mmkvStorage.setItem(SPEED_SETTINGS_KEY, JSON.stringify(settings));
|
||||
} catch (error) {
|
||||
logger.warn('[AndroidVideoPlayer] Error saving speed settings:', error);
|
||||
}
|
||||
|
|
@ -2278,7 +2278,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
return;
|
||||
}
|
||||
// One-time migrate legacy key if present
|
||||
const legacy = await AsyncStorage.getItem(SUBTITLE_SIZE_KEY);
|
||||
const legacy = await mmkvStorage.getItem(SUBTITLE_SIZE_KEY);
|
||||
if (legacy) {
|
||||
const migrated = parseInt(legacy, 10);
|
||||
if (!Number.isNaN(migrated) && migrated > 0) {
|
||||
|
|
@ -2288,7 +2288,7 @@ const AndroidVideoPlayer: React.FC = () => {
|
|||
await storageService.saveSubtitleSettings(merged);
|
||||
} catch {}
|
||||
}
|
||||
try { await AsyncStorage.removeItem(SUBTITLE_SIZE_KEY); } catch {}
|
||||
try { await mmkvStorage.removeItem(SUBTITLE_SIZE_KEY); } catch {}
|
||||
return;
|
||||
}
|
||||
// If no saved settings, use responsive default
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import RNImmersiveMode from 'react-native-immersive-mode';
|
|||
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import { storageService } from '../../services/storageService';
|
||||
import { logger } from '../../utils/logger';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../../services/mmkvStorage';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import Slider from '@react-native-community/slider';
|
||||
|
|
@ -1616,7 +1616,7 @@ const KSPlayerCore: React.FC = () => {
|
|||
return;
|
||||
}
|
||||
// One-time migrate legacy key if present
|
||||
const legacy = await AsyncStorage.getItem(SUBTITLE_SIZE_KEY);
|
||||
const legacy = await mmkvStorage.getItem(SUBTITLE_SIZE_KEY);
|
||||
if (legacy) {
|
||||
const migrated = parseInt(legacy, 10);
|
||||
if (!Number.isNaN(migrated) && migrated > 0) {
|
||||
|
|
@ -1626,7 +1626,7 @@ const KSPlayerCore: React.FC = () => {
|
|||
await storageService.saveSubtitleSettings(merged);
|
||||
} catch {}
|
||||
}
|
||||
try { await AsyncStorage.removeItem(SUBTITLE_SIZE_KEY); } catch {}
|
||||
try { await mmkvStorage.removeItem(SUBTITLE_SIZE_KEY); } catch {}
|
||||
return;
|
||||
}
|
||||
// If no saved settings, use responsive default
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AppState } from 'react-native';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { notificationService } from '../services/notificationService';
|
||||
|
||||
export type DownloadStatus = 'downloading' | 'completed' | 'paused' | 'error' | 'queued';
|
||||
|
|
@ -142,7 +142,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
|||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
||||
const raw = await mmkvStorage.getItem(STORAGE_KEY);
|
||||
if (raw) {
|
||||
const list = JSON.parse(raw) as Array<Partial<DownloadItem>>;
|
||||
// Mark any in-progress as paused on restore (cannot resume across sessions reliably)
|
||||
|
|
@ -216,7 +216,7 @@ export const DownloadsProvider: React.FC<{ children: React.ReactNode }> = ({ chi
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(downloads)).catch(() => {});
|
||||
mmkvStorage.setItem(STORAGE_KEY, JSON.stringify(downloads)).catch(() => {});
|
||||
}, [downloads]);
|
||||
|
||||
const updateDownload = useCallback((id: string, updater: (d: DownloadItem) => DownloadItem) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { createContext, useState, useContext, useEffect, ReactNode } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { settingsEmitter } from '../hooks/useSettings';
|
||||
import { colors as defaultColors } from '../styles/colors';
|
||||
|
||||
|
|
@ -168,11 +168,11 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
useEffect(() => {
|
||||
const loadThemes = async () => {
|
||||
try {
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const appSettingsJson = await AsyncStorage.getItem(`@user:${scope}:app_settings`);
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const appSettingsJson = await mmkvStorage.getItem(`@user:${scope}:app_settings`);
|
||||
const appSettings = appSettingsJson ? JSON.parse(appSettingsJson) : {};
|
||||
const savedThemeId = appSettings.themeId || (await AsyncStorage.getItem(CURRENT_THEME_KEY));
|
||||
const customThemesJson = appSettings.customThemes ? JSON.stringify(appSettings.customThemes) : await AsyncStorage.getItem(CUSTOM_THEMES_KEY);
|
||||
const savedThemeId = appSettings.themeId || (await mmkvStorage.getItem(CURRENT_THEME_KEY));
|
||||
const customThemesJson = appSettings.customThemes ? JSON.stringify(appSettings.customThemes) : await mmkvStorage.getItem(CUSTOM_THEMES_KEY);
|
||||
const customThemes = customThemesJson ? JSON.parse(customThemesJson) : [];
|
||||
const allThemes = [...DEFAULT_THEMES, ...customThemes];
|
||||
setAvailableThemes(allThemes);
|
||||
|
|
@ -195,13 +195,13 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
if (theme) {
|
||||
setCurrentThemeState(theme);
|
||||
// Persist into scoped app_settings and legacy key for backward compat
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const key = `@user:${scope}:app_settings`;
|
||||
let settings = {} as any;
|
||||
try { settings = JSON.parse((await AsyncStorage.getItem(key)) || '{}'); } catch {}
|
||||
try { settings = JSON.parse((await mmkvStorage.getItem(key)) || '{}'); } catch {}
|
||||
settings.themeId = themeId;
|
||||
await AsyncStorage.setItem(key, JSON.stringify(settings));
|
||||
await AsyncStorage.setItem(CURRENT_THEME_KEY, themeId);
|
||||
await mmkvStorage.setItem(key, JSON.stringify(settings));
|
||||
await mmkvStorage.setItem(CURRENT_THEME_KEY, themeId);
|
||||
// Do not emit global settings sync for themes (sync on app restart only)
|
||||
}
|
||||
};
|
||||
|
|
@ -225,20 +225,20 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
const updatedAllThemes = [...DEFAULT_THEMES, ...updatedCustomThemes];
|
||||
|
||||
// Save to storage (scoped app_settings + legacy key)
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const key = `@user:${scope}:app_settings`;
|
||||
let settings = {} as any;
|
||||
try { settings = JSON.parse((await AsyncStorage.getItem(key)) || '{}'); } catch {}
|
||||
try { settings = JSON.parse((await mmkvStorage.getItem(key)) || '{}'); } catch {}
|
||||
settings.customThemes = updatedCustomThemes;
|
||||
await AsyncStorage.setItem(key, JSON.stringify(settings));
|
||||
await AsyncStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(updatedCustomThemes));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(settings));
|
||||
await mmkvStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(updatedCustomThemes));
|
||||
|
||||
// Update state
|
||||
setAvailableThemes(updatedAllThemes);
|
||||
|
||||
// Set as current theme
|
||||
setCurrentThemeState(newTheme);
|
||||
await AsyncStorage.setItem(CURRENT_THEME_KEY, id);
|
||||
await mmkvStorage.setItem(CURRENT_THEME_KEY, id);
|
||||
// Do not emit global settings sync for themes
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Failed to add custom theme:', error);
|
||||
|
|
@ -262,13 +262,13 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
const updatedAllThemes = [...DEFAULT_THEMES, ...updatedCustomThemes];
|
||||
|
||||
// Save to storage (scoped app_settings + legacy key)
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const key = `@user:${scope}:app_settings`;
|
||||
let settings = {} as any;
|
||||
try { settings = JSON.parse((await AsyncStorage.getItem(key)) || '{}'); } catch {}
|
||||
try { settings = JSON.parse((await mmkvStorage.getItem(key)) || '{}'); } catch {}
|
||||
settings.customThemes = updatedCustomThemes;
|
||||
await AsyncStorage.setItem(key, JSON.stringify(settings));
|
||||
await AsyncStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(updatedCustomThemes));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(settings));
|
||||
await mmkvStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(updatedCustomThemes));
|
||||
|
||||
// Update state
|
||||
setAvailableThemes(updatedAllThemes);
|
||||
|
|
@ -298,13 +298,13 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
const updatedAllThemes = [...DEFAULT_THEMES, ...customThemes];
|
||||
|
||||
// Save to storage (scoped app_settings + legacy key)
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const key = `@user:${scope}:app_settings`;
|
||||
let settings = {} as any;
|
||||
try { settings = JSON.parse((await AsyncStorage.getItem(key)) || '{}'); } catch {}
|
||||
try { settings = JSON.parse((await mmkvStorage.getItem(key)) || '{}'); } catch {}
|
||||
settings.customThemes = customThemes;
|
||||
await AsyncStorage.setItem(key, JSON.stringify(settings));
|
||||
await AsyncStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(customThemes));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(settings));
|
||||
await mmkvStorage.setItem(CUSTOM_THEMES_KEY, JSON.stringify(customThemes));
|
||||
|
||||
// Update state
|
||||
setAvailableThemes(updatedAllThemes);
|
||||
|
|
@ -312,7 +312,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
|||
// Reset to default theme if current theme was deleted
|
||||
if (currentTheme.id === themeId) {
|
||||
setCurrentThemeState(DEFAULT_THEMES[0]);
|
||||
await AsyncStorage.setItem(CURRENT_THEME_KEY, DEFAULT_THEMES[0].id);
|
||||
await mmkvStorage.setItem(CURRENT_THEME_KEY, DEFAULT_THEMES[0].id);
|
||||
}
|
||||
// Do not emit global settings sync for themes
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
const CATALOG_CUSTOM_NAMES_KEY = 'catalog_custom_names';
|
||||
|
|
@ -27,7 +27,7 @@ export function useCustomCatalogNames() {
|
|||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const savedCustomNamesJson = await AsyncStorage.getItem(CATALOG_CUSTOM_NAMES_KEY);
|
||||
const savedCustomNamesJson = await mmkvStorage.getItem(CATALOG_CUSTOM_NAMES_KEY);
|
||||
const loadedNames = savedCustomNamesJson ? JSON.parse(savedCustomNamesJson) : {};
|
||||
setCustomNames(loadedNames);
|
||||
// Update cache
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { AppState } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { StreamingContent, catalogService } from '../services/catalogService';
|
||||
import { tmdbService } from '../services/tmdbService';
|
||||
import { logger } from '../utils/logger';
|
||||
|
|
@ -241,7 +241,7 @@ export function useFeaturedContent() {
|
|||
// Safety guard: if nothing came back within a reasonable time, stop loading
|
||||
if (!formattedContent || formattedContent.length === 0) {
|
||||
// Fall back to any cached featured item so UI can render something
|
||||
const cachedJson = await AsyncStorage.getItem(STORAGE_KEY).catch(() => null);
|
||||
const cachedJson = await mmkvStorage.getItem(STORAGE_KEY).catch(() => null);
|
||||
if (cachedJson) {
|
||||
try {
|
||||
const parsed = JSON.parse(cachedJson);
|
||||
|
|
@ -270,7 +270,7 @@ export function useFeaturedContent() {
|
|||
// Persist cache for fast startup (skipped when cache disabled)
|
||||
if (!DISABLE_CACHE) {
|
||||
try {
|
||||
await AsyncStorage.setItem(
|
||||
await mmkvStorage.setItem(
|
||||
STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
ts: now,
|
||||
|
|
@ -285,7 +285,7 @@ export function useFeaturedContent() {
|
|||
setFeaturedContent(null);
|
||||
// Clear persisted cache on empty (skipped when cache disabled)
|
||||
if (!DISABLE_CACHE) {
|
||||
try { await AsyncStorage.removeItem(STORAGE_KEY); } catch {}
|
||||
try { await mmkvStorage.removeItem(STORAGE_KEY); } catch {}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -310,7 +310,7 @@ export function useFeaturedContent() {
|
|||
let cancelled = false;
|
||||
(async () => {
|
||||
try {
|
||||
const json = await AsyncStorage.getItem(STORAGE_KEY);
|
||||
const json = await mmkvStorage.getItem(STORAGE_KEY);
|
||||
if (!json) return;
|
||||
const parsed = JSON.parse(json);
|
||||
if (cancelled) return;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useCallback, useEffect, useState } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import * as Updates from 'expo-updates';
|
||||
import { getDisplayedAppVersion } from '../utils/version';
|
||||
import { fetchLatestGithubRelease, isAnyUpgrade } from '../services/githubReleaseService';
|
||||
|
|
@ -29,7 +29,7 @@ export function useGithubMajorUpdate(): MajorUpdateData {
|
|||
const info = await fetchLatestGithubRelease();
|
||||
if (!info?.tag_name) return;
|
||||
|
||||
const dismissed = await AsyncStorage.getItem(DISMISSED_KEY);
|
||||
const dismissed = await mmkvStorage.getItem(DISMISSED_KEY);
|
||||
if (dismissed === info.tag_name) return;
|
||||
|
||||
// "Later" is session-only now, no persisted snooze
|
||||
|
|
@ -51,7 +51,7 @@ export function useGithubMajorUpdate(): MajorUpdateData {
|
|||
}, [check]);
|
||||
|
||||
const onDismiss = useCallback(async () => {
|
||||
if (latestTag) await AsyncStorage.setItem(DISMISSED_KEY, latestTag);
|
||||
if (latestTag) await mmkvStorage.setItem(DISMISSED_KEY, latestTag);
|
||||
setVisible(false);
|
||||
}, [latestTag]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { StreamingContent } from '../services/catalogService';
|
||||
import { catalogService } from '../services/catalogService';
|
||||
|
||||
|
|
@ -13,14 +13,14 @@ export const useLibrary = () => {
|
|||
const loadLibraryItems = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const scopedKey = `@user:${scope}:stremio-library`;
|
||||
let storedItems = await AsyncStorage.getItem(scopedKey);
|
||||
let storedItems = await mmkvStorage.getItem(scopedKey);
|
||||
if (!storedItems) {
|
||||
// migrate legacy into scoped
|
||||
const legacy = await AsyncStorage.getItem(LEGACY_LIBRARY_STORAGE_KEY);
|
||||
const legacy = await mmkvStorage.getItem(LEGACY_LIBRARY_STORAGE_KEY);
|
||||
if (legacy) {
|
||||
await AsyncStorage.setItem(scopedKey, legacy);
|
||||
await mmkvStorage.setItem(scopedKey, legacy);
|
||||
storedItems = legacy;
|
||||
}
|
||||
}
|
||||
|
|
@ -50,11 +50,11 @@ export const useLibrary = () => {
|
|||
acc[`${item.type}:${item.id}`] = item;
|
||||
return acc;
|
||||
}, {} as Record<string, StreamingContent>);
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const scopedKey = `@user:${scope}:stremio-library`;
|
||||
await AsyncStorage.setItem(scopedKey, JSON.stringify(itemsObject));
|
||||
await mmkvStorage.setItem(scopedKey, JSON.stringify(itemsObject));
|
||||
// keep legacy for backward-compat
|
||||
await AsyncStorage.setItem(LEGACY_LIBRARY_STORAGE_KEY, JSON.stringify(itemsObject));
|
||||
await mmkvStorage.setItem(LEGACY_LIBRARY_STORAGE_KEY, JSON.stringify(itemsObject));
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error saving library items:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { Cast, Episode, GroupedEpisodes, GroupedStreams } from '../types/metadat
|
|||
import { TMDBService } from '../services/tmdbService';
|
||||
import { logger } from '../utils/logger';
|
||||
import { usePersistentSeasons } from './usePersistentSeasons';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { Stream } from '../types/metadata';
|
||||
import { storageService } from '../services/storageService';
|
||||
import { useSettings } from './useSettings';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { logger } from '../utils/logger';
|
|||
import { TMDBService } from '../services/tmdbService';
|
||||
import { isTmdbUrl } from '../utils/logoUtils';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
|
||||
// Cache for image availability checks
|
||||
const imageAvailabilityCache: Record<string, boolean> = {};
|
||||
|
|
@ -17,7 +17,7 @@ const checkImageAvailability = async (url: string): Promise<boolean> => {
|
|||
|
||||
// Check AsyncStorage cache
|
||||
try {
|
||||
const cachedResult = await AsyncStorage.getItem(`image_available:${url}`);
|
||||
const cachedResult = await mmkvStorage.getItem(`image_available:${url}`);
|
||||
if (cachedResult !== null) {
|
||||
const isAvailable = cachedResult === 'true';
|
||||
imageAvailabilityCache[url] = isAvailable;
|
||||
|
|
@ -35,7 +35,7 @@ const checkImageAvailability = async (url: string): Promise<boolean> => {
|
|||
// Update caches
|
||||
imageAvailabilityCache[url] = isAvailable;
|
||||
try {
|
||||
await AsyncStorage.setItem(`image_available:${url}`, isAvailable ? 'true' : 'false');
|
||||
await mmkvStorage.setItem(`image_available:${url}`, isAvailable ? 'true' : 'false');
|
||||
} catch (error) {
|
||||
// Ignore AsyncStorage errors
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
const SEASONS_STORAGE_KEY = 'selected_seasons';
|
||||
|
|
@ -27,7 +27,7 @@ export function usePersistentSeasons() {
|
|||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const savedSeasonsJson = await AsyncStorage.getItem(SEASONS_STORAGE_KEY);
|
||||
const savedSeasonsJson = await mmkvStorage.getItem(SEASONS_STORAGE_KEY);
|
||||
const loadedSeasons = savedSeasonsJson ? JSON.parse(savedSeasonsJson) : {};
|
||||
setSelectedSeasons(loadedSeasons);
|
||||
// Update cache
|
||||
|
|
@ -63,7 +63,7 @@ export function usePersistentSeasons() {
|
|||
setSelectedSeasons(updatedSeasons);
|
||||
|
||||
// Save to AsyncStorage
|
||||
await AsyncStorage.setItem(SEASONS_STORAGE_KEY, JSON.stringify(updatedSeasons));
|
||||
await mmkvStorage.setItem(SEASONS_STORAGE_KEY, JSON.stringify(updatedSeasons));
|
||||
} catch (error) {
|
||||
logger.error('Failed to save selected season:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
|
||||
// Simple event emitter for settings changes
|
||||
class SettingsEventEmitter {
|
||||
|
|
@ -168,11 +168,11 @@ export const useSettings = () => {
|
|||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const scopedKey = `@user:${scope}:${SETTINGS_STORAGE_KEY}`;
|
||||
const [scopedJson, legacyJson] = await Promise.all([
|
||||
AsyncStorage.getItem(scopedKey),
|
||||
AsyncStorage.getItem(SETTINGS_STORAGE_KEY),
|
||||
mmkvStorage.getItem(scopedKey),
|
||||
mmkvStorage.getItem(SETTINGS_STORAGE_KEY),
|
||||
]);
|
||||
const parsedScoped = scopedJson ? JSON.parse(scopedJson) : null;
|
||||
const parsedLegacy = legacyJson ? JSON.parse(legacyJson) : null;
|
||||
|
|
@ -182,10 +182,10 @@ export const useSettings = () => {
|
|||
// Fallback: scan any existing user-scoped settings if current scope not set yet
|
||||
if (!merged) {
|
||||
try {
|
||||
const allKeys = await AsyncStorage.getAllKeys();
|
||||
const allKeys = await mmkvStorage.getAllKeys();
|
||||
const candidateKeys = (allKeys || []).filter(k => k.endsWith(`:${SETTINGS_STORAGE_KEY}`));
|
||||
if (candidateKeys.length > 0) {
|
||||
const pairs = await AsyncStorage.multiGet(candidateKeys);
|
||||
const pairs = await mmkvStorage.multiGet(candidateKeys);
|
||||
for (const [, value] of pairs) {
|
||||
if (value) {
|
||||
try {
|
||||
|
|
@ -221,15 +221,15 @@ export const useSettings = () => {
|
|||
) => {
|
||||
const newSettings = { ...settings, [key]: value };
|
||||
try {
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scopedKey = `@user:${scope}:${SETTINGS_STORAGE_KEY}`;
|
||||
// Write to both scoped key (multi-user aware) and legacy key for backward compatibility
|
||||
await Promise.all([
|
||||
AsyncStorage.setItem(scopedKey, JSON.stringify(newSettings)),
|
||||
AsyncStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(newSettings)),
|
||||
]);
|
||||
// Ensure a current scope exists to avoid future loads missing the chosen scope
|
||||
await AsyncStorage.setItem('@user:current', scope);
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const scopedKey = `@user:${scope}:${SETTINGS_STORAGE_KEY}`;
|
||||
// Write to both scoped key (multi-user aware) and legacy key for backward compatibility
|
||||
await Promise.all([
|
||||
mmkvStorage.setItem(scopedKey, JSON.stringify(newSettings)),
|
||||
mmkvStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(newSettings)),
|
||||
]);
|
||||
// Ensure a current scope exists to avoid future loads missing the chosen scope
|
||||
await mmkvStorage.setItem('@user:current', scope);
|
||||
setSettings(newSettings);
|
||||
if (__DEV__) console.log(`Setting updated: ${key}`, value);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useTraktIntegration } from './useTraktIntegration';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
|
|
@ -35,9 +35,9 @@ export function useTraktAutosyncSettings() {
|
|||
try {
|
||||
setIsLoading(true);
|
||||
const [enabled, frequency, threshold] = await Promise.all([
|
||||
AsyncStorage.getItem(TRAKT_AUTOSYNC_ENABLED_KEY),
|
||||
AsyncStorage.getItem(TRAKT_SYNC_FREQUENCY_KEY),
|
||||
AsyncStorage.getItem(TRAKT_COMPLETION_THRESHOLD_KEY)
|
||||
mmkvStorage.getItem(TRAKT_AUTOSYNC_ENABLED_KEY),
|
||||
mmkvStorage.getItem(TRAKT_SYNC_FREQUENCY_KEY),
|
||||
mmkvStorage.getItem(TRAKT_COMPLETION_THRESHOLD_KEY)
|
||||
]);
|
||||
|
||||
setSettings({
|
||||
|
|
@ -56,7 +56,7 @@ export function useTraktAutosyncSettings() {
|
|||
// Save individual setting
|
||||
const saveSetting = useCallback(async (key: string, value: any) => {
|
||||
try {
|
||||
await AsyncStorage.setItem(key, JSON.stringify(value));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
logger.error('[useTraktAutosyncSettings] Error saving setting:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
|
|||
import { Platform } from 'react-native';
|
||||
import { toastService } from '../services/toastService';
|
||||
import UpdateService, { UpdateInfo } from '../services/updateService';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
|
||||
interface UseUpdatePopupReturn {
|
||||
showUpdatePopup: boolean;
|
||||
|
|
@ -30,7 +30,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
try {
|
||||
|
||||
// Check if user has dismissed the popup for this version
|
||||
const dismissedVersion = await AsyncStorage.getItem(UPDATE_POPUP_STORAGE_KEY);
|
||||
const dismissedVersion = await mmkvStorage.getItem(UPDATE_POPUP_STORAGE_KEY);
|
||||
const currentVersion = updateInfo.manifest?.id;
|
||||
|
||||
if (dismissedVersion === currentVersion && !forceCheck) {
|
||||
|
|
@ -38,7 +38,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
}
|
||||
|
||||
// Check if user chose "later" recently (within 6 hours)
|
||||
const updateLaterTimestamp = await AsyncStorage.getItem(UPDATE_LATER_STORAGE_KEY);
|
||||
const updateLaterTimestamp = await mmkvStorage.getItem(UPDATE_LATER_STORAGE_KEY);
|
||||
if (updateLaterTimestamp && !forceCheck) {
|
||||
const laterTime = parseInt(updateLaterTimestamp);
|
||||
const now = Date.now();
|
||||
|
|
@ -91,7 +91,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
const handleUpdateLater = useCallback(async () => {
|
||||
try {
|
||||
// Store timestamp when user chose "later"
|
||||
await AsyncStorage.setItem(UPDATE_LATER_STORAGE_KEY, Date.now().toString());
|
||||
await mmkvStorage.setItem(UPDATE_LATER_STORAGE_KEY, Date.now().toString());
|
||||
setShowUpdatePopup(false);
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error storing update later preference:', error);
|
||||
|
|
@ -104,7 +104,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
// Store the current version ID so we don't show popup again for this version
|
||||
const currentVersion = updateInfo.manifest?.id;
|
||||
if (currentVersion) {
|
||||
await AsyncStorage.setItem(UPDATE_POPUP_STORAGE_KEY, currentVersion);
|
||||
await mmkvStorage.setItem(UPDATE_POPUP_STORAGE_KEY, currentVersion);
|
||||
}
|
||||
setShowUpdatePopup(false);
|
||||
} catch (error) {
|
||||
|
|
@ -150,7 +150,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
// Check if user hasn't dismissed this version
|
||||
(async () => {
|
||||
try {
|
||||
const dismissedVersion = await AsyncStorage.getItem(UPDATE_POPUP_STORAGE_KEY);
|
||||
const dismissedVersion = await mmkvStorage.getItem(UPDATE_POPUP_STORAGE_KEY);
|
||||
const currentVersion = updateInfo.manifest?.id;
|
||||
|
||||
if (dismissedVersion !== currentVersion) {
|
||||
|
|
@ -175,7 +175,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
const timer = setTimeout(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const lastCheckTs = await AsyncStorage.getItem(UPDATE_LAST_CHECK_TS_KEY);
|
||||
const lastCheckTs = await mmkvStorage.getItem(UPDATE_LAST_CHECK_TS_KEY);
|
||||
const last = lastCheckTs ? parseInt(lastCheckTs, 10) : 0;
|
||||
const now = Date.now();
|
||||
const sixHours = 6 * 60 * 60 * 1000; // Reduced from 24 hours
|
||||
|
|
@ -183,7 +183,7 @@ export const useUpdatePopup = (): UseUpdatePopupReturn => {
|
|||
return; // Throttle: only auto-check once per 6h
|
||||
}
|
||||
await checkForUpdates();
|
||||
await AsyncStorage.setItem(UPDATE_LAST_CHECK_TS_KEY, String(now));
|
||||
await mmkvStorage.setItem(UPDATE_LAST_CHECK_TS_KEY, String(now));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { NavigationContainer, DefaultTheme as NavigationDefaultTheme, DarkTheme
|
|||
import { createNativeStackNavigator, NativeStackNavigationOptions, NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { createBottomTabNavigator, BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
|
||||
import { useColorScheme, Platform, Animated, StatusBar, TouchableOpacity, View, Text, AppState, Easing, Dimensions } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { PaperProvider, MD3DarkTheme, MD3LightTheme, adaptNavigationTheme } from 'react-native-paper';
|
||||
import type { MD3Theme } from 'react-native-paper';
|
||||
import type { BottomTabBarProps } from '@react-navigation/bottom-tabs';
|
||||
|
|
@ -524,7 +524,7 @@ const MainTabs = () => {
|
|||
let mounted = true;
|
||||
const load = async () => {
|
||||
try {
|
||||
const flag = await AsyncStorage.getItem('@update_badge_pending');
|
||||
const flag = await mmkvStorage.getItem('@update_badge_pending');
|
||||
if (mounted) setHasUpdateBadge(flag === 'true');
|
||||
} catch {}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
Switch,
|
||||
} from 'react-native';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
|
|
@ -80,7 +80,7 @@ const AISettingsScreen: React.FC = () => {
|
|||
|
||||
const loadApiKey = async () => {
|
||||
try {
|
||||
const savedKey = await AsyncStorage.getItem('openrouter_api_key');
|
||||
const savedKey = await mmkvStorage.getItem('openrouter_api_key');
|
||||
if (savedKey) {
|
||||
setApiKey(savedKey);
|
||||
setIsKeySet(true);
|
||||
|
|
@ -103,7 +103,7 @@ const AISettingsScreen: React.FC = () => {
|
|||
|
||||
setLoading(true);
|
||||
try {
|
||||
await AsyncStorage.setItem('openrouter_api_key', apiKey.trim());
|
||||
await mmkvStorage.setItem('openrouter_api_key', apiKey.trim());
|
||||
setIsKeySet(true);
|
||||
openAlert('Success', 'OpenRouter API key saved successfully!');
|
||||
} catch (error) {
|
||||
|
|
@ -124,7 +124,7 @@ const AISettingsScreen: React.FC = () => {
|
|||
label: 'Remove',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await AsyncStorage.removeItem('openrouter_api_key');
|
||||
await mmkvStorage.removeItem('openrouter_api_key');
|
||||
setApiKey('');
|
||||
setIsKeySet(false);
|
||||
openAlert('Success', 'API key removed successfully');
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import { useNavigation } from '@react-navigation/native';
|
|||
import { NavigationProp } from '@react-navigation/native';
|
||||
import { RootStackParamList } from '../navigation/AppNavigator';
|
||||
import { logger } from '../utils/logger';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { BlurView as ExpoBlurView } from 'expo-blur';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
|
|
@ -671,7 +671,7 @@ const AddonsScreen = () => {
|
|||
});
|
||||
|
||||
// Get catalog settings to determine enabled count
|
||||
const catalogSettingsJson = await AsyncStorage.getItem('catalog_settings');
|
||||
const catalogSettingsJson = await mmkvStorage.getItem('catalog_settings');
|
||||
if (catalogSettingsJson) {
|
||||
const catalogSettings = JSON.parse(catalogSettingsJson);
|
||||
const disabledCount = Object.entries(catalogSettings)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { View, TextInput, Text, TouchableOpacity, StyleSheet, ActivityIndicator, SafeAreaView, KeyboardAvoidingView, Platform, Dimensions, Animated, Easing, Keyboard } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
|
|
@ -196,7 +196,7 @@ const AuthScreen: React.FC = () => {
|
|||
|
||||
const handleSkipAuth = async () => {
|
||||
try {
|
||||
await AsyncStorage.setItem('showLoginHintToastOnce', 'true');
|
||||
await mmkvStorage.setItem('showLoginHintToastOnce', 'true');
|
||||
} catch {}
|
||||
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' as never }] } as any);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ if (Platform.OS === 'ios') {
|
|||
}
|
||||
import { logger } from '../utils/logger';
|
||||
import { useCustomCatalogNames } from '../hooks/useCustomCatalogNames';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { catalogService, DataSource, StreamingContent } from '../services/catalogService';
|
||||
import { tmdbService } from '../services/tmdbService';
|
||||
|
||||
|
|
@ -262,7 +262,7 @@ const CatalogScreen: React.FC<CatalogScreenProps> = ({ route, navigation }) => {
|
|||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const pref = await AsyncStorage.getItem('catalog_mobile_columns');
|
||||
const pref = await mmkvStorage.getItem('catalog_mobile_columns');
|
||||
if (pref === '2') setMobileColumnsPref(2);
|
||||
else if (pref === '3') setMobileColumnsPref(3);
|
||||
else setMobileColumnsPref('auto');
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
Pressable,
|
||||
Button,
|
||||
} from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { stremioService } from '../services/stremioService';
|
||||
|
|
@ -294,11 +294,11 @@ const CatalogSettingsScreen = () => {
|
|||
const availableCatalogs: CatalogSetting[] = [];
|
||||
|
||||
// Get saved enable/disable settings
|
||||
const savedSettingsJson = await AsyncStorage.getItem(CATALOG_SETTINGS_KEY);
|
||||
const savedSettingsJson = await mmkvStorage.getItem(CATALOG_SETTINGS_KEY);
|
||||
const savedEnabledSettings: { [key: string]: boolean } = savedSettingsJson ? JSON.parse(savedSettingsJson) : {};
|
||||
|
||||
// Get saved custom names
|
||||
const savedCustomNamesJson = await AsyncStorage.getItem(CATALOG_CUSTOM_NAMES_KEY);
|
||||
const savedCustomNamesJson = await mmkvStorage.getItem(CATALOG_CUSTOM_NAMES_KEY);
|
||||
const savedCustomNames: { [key: string]: string } = savedCustomNamesJson ? JSON.parse(savedCustomNamesJson) : {};
|
||||
|
||||
// Process each addon's catalogs
|
||||
|
|
@ -371,7 +371,7 @@ const CatalogSettingsScreen = () => {
|
|||
|
||||
// Load mobile columns preference (phones only)
|
||||
try {
|
||||
const pref = await AsyncStorage.getItem(CATALOG_MOBILE_COLUMNS_KEY);
|
||||
const pref = await mmkvStorage.getItem(CATALOG_MOBILE_COLUMNS_KEY);
|
||||
if (pref === '2') setMobileColumns(2);
|
||||
else if (pref === '3') setMobileColumns(3);
|
||||
else setMobileColumns('auto');
|
||||
|
|
@ -395,7 +395,7 @@ const CatalogSettingsScreen = () => {
|
|||
const key = `${setting.addonId}:${setting.type}:${setting.catalogId}`;
|
||||
settingsObj[key] = setting.enabled;
|
||||
});
|
||||
await AsyncStorage.setItem(CATALOG_SETTINGS_KEY, JSON.stringify(settingsObj));
|
||||
await mmkvStorage.setItem(CATALOG_SETTINGS_KEY, JSON.stringify(settingsObj));
|
||||
|
||||
// Small delay to ensure AsyncStorage has fully persisted before triggering refresh
|
||||
setTimeout(() => {
|
||||
|
|
@ -461,7 +461,7 @@ const CatalogSettingsScreen = () => {
|
|||
const settingKey = `${catalogToRename.addonId}:${catalogToRename.type}:${catalogToRename.catalogId}`;
|
||||
|
||||
try {
|
||||
const savedCustomNamesJson = await AsyncStorage.getItem(CATALOG_CUSTOM_NAMES_KEY);
|
||||
const savedCustomNamesJson = await mmkvStorage.getItem(CATALOG_CUSTOM_NAMES_KEY);
|
||||
const customNames: { [key: string]: string } = savedCustomNamesJson ? JSON.parse(savedCustomNamesJson) : {};
|
||||
|
||||
const trimmedNewName = currentRenameValue.trim();
|
||||
|
|
@ -472,7 +472,7 @@ const CatalogSettingsScreen = () => {
|
|||
customNames[settingKey] = trimmedNewName;
|
||||
}
|
||||
|
||||
await AsyncStorage.setItem(CATALOG_CUSTOM_NAMES_KEY, JSON.stringify(customNames));
|
||||
await mmkvStorage.setItem(CATALOG_CUSTOM_NAMES_KEY, JSON.stringify(customNames));
|
||||
// Clear in-memory cache so new name is used immediately
|
||||
try { clearCustomNameCache(); } catch {}
|
||||
|
||||
|
|
@ -550,7 +550,7 @@ const CatalogSettingsScreen = () => {
|
|||
style={[styles.optionChip, mobileColumns === 'auto' && styles.optionChipSelected]}
|
||||
onPress={async () => {
|
||||
try {
|
||||
await AsyncStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, 'auto');
|
||||
await mmkvStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, 'auto');
|
||||
setMobileColumns('auto');
|
||||
} catch {}
|
||||
}}
|
||||
|
|
@ -562,7 +562,7 @@ const CatalogSettingsScreen = () => {
|
|||
style={[styles.optionChip, mobileColumns === 2 && styles.optionChipSelected]}
|
||||
onPress={async () => {
|
||||
try {
|
||||
await AsyncStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, '2');
|
||||
await mmkvStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, '2');
|
||||
setMobileColumns(2);
|
||||
} catch {}
|
||||
}}
|
||||
|
|
@ -574,7 +574,7 @@ const CatalogSettingsScreen = () => {
|
|||
style={[styles.optionChip, mobileColumns === 3 && styles.optionChipSelected]}
|
||||
onPress={async () => {
|
||||
try {
|
||||
await AsyncStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, '3');
|
||||
await mmkvStorage.setItem(CATALOG_MOBILE_COLUMNS_KEY, '3');
|
||||
setMobileColumns(3);
|
||||
} catch {}
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
FlatList,
|
||||
ActivityIndicator
|
||||
} from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NavigationProp } from '@react-navigation/native';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
|
|
@ -108,8 +108,8 @@ const ContributorsScreen: React.FC = () => {
|
|||
// Check cache first (unless refreshing)
|
||||
if (!isRefresh) {
|
||||
try {
|
||||
const cachedData = await AsyncStorage.getItem('github_contributors');
|
||||
const cacheTimestamp = await AsyncStorage.getItem('github_contributors_timestamp');
|
||||
const cachedData = await mmkvStorage.getItem('github_contributors');
|
||||
const cacheTimestamp = await mmkvStorage.getItem('github_contributors_timestamp');
|
||||
const now = Date.now();
|
||||
const ONE_HOUR = 60 * 60 * 1000; // 1 hour cache
|
||||
|
||||
|
|
@ -124,8 +124,8 @@ const ContributorsScreen: React.FC = () => {
|
|||
return;
|
||||
} else {
|
||||
// Remove invalid cache
|
||||
await AsyncStorage.removeItem('github_contributors');
|
||||
await AsyncStorage.removeItem('github_contributors_timestamp');
|
||||
await mmkvStorage.removeItem('github_contributors');
|
||||
await mmkvStorage.removeItem('github_contributors_timestamp');
|
||||
if (__DEV__) console.log('Removed invalid contributors cache');
|
||||
}
|
||||
}
|
||||
|
|
@ -134,8 +134,8 @@ const ContributorsScreen: React.FC = () => {
|
|||
if (__DEV__) console.error('Cache read error:', cacheError);
|
||||
// Remove corrupted cache
|
||||
try {
|
||||
await AsyncStorage.removeItem('github_contributors');
|
||||
await AsyncStorage.removeItem('github_contributors_timestamp');
|
||||
await mmkvStorage.removeItem('github_contributors');
|
||||
await mmkvStorage.removeItem('github_contributors_timestamp');
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,16 +145,16 @@ const ContributorsScreen: React.FC = () => {
|
|||
setContributors(data);
|
||||
// Only cache valid data
|
||||
try {
|
||||
await AsyncStorage.setItem('github_contributors', JSON.stringify(data));
|
||||
await AsyncStorage.setItem('github_contributors_timestamp', Date.now().toString());
|
||||
await mmkvStorage.setItem('github_contributors', JSON.stringify(data));
|
||||
await mmkvStorage.setItem('github_contributors_timestamp', Date.now().toString());
|
||||
} catch (cacheError) {
|
||||
if (__DEV__) console.error('Cache write error:', cacheError);
|
||||
}
|
||||
} else {
|
||||
// Clear any existing cache if we get invalid data
|
||||
try {
|
||||
await AsyncStorage.removeItem('github_contributors');
|
||||
await AsyncStorage.removeItem('github_contributors_timestamp');
|
||||
await mmkvStorage.removeItem('github_contributors');
|
||||
await mmkvStorage.removeItem('github_contributors_timestamp');
|
||||
} catch {}
|
||||
setError('Unable to load contributors. This might be due to GitHub API rate limits.');
|
||||
}
|
||||
|
|
@ -171,12 +171,12 @@ const ContributorsScreen: React.FC = () => {
|
|||
// Clear any invalid cache on mount
|
||||
const clearInvalidCache = async () => {
|
||||
try {
|
||||
const cachedData = await AsyncStorage.getItem('github_contributors');
|
||||
const cachedData = await mmkvStorage.getItem('github_contributors');
|
||||
if (cachedData) {
|
||||
const parsedData = JSON.parse(cachedData);
|
||||
if (!parsedData || !Array.isArray(parsedData) || parsedData.length === 0) {
|
||||
await AsyncStorage.removeItem('github_contributors');
|
||||
await AsyncStorage.removeItem('github_contributors_timestamp');
|
||||
await mmkvStorage.removeItem('github_contributors');
|
||||
await mmkvStorage.removeItem('github_contributors_timestamp');
|
||||
if (__DEV__) console.log('Cleared invalid cache on mount');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ import { useTheme } from '../contexts/ThemeContext';
|
|||
import type { Theme } from '../contexts/ThemeContext';
|
||||
import { useLoading } from '../contexts/LoadingContext';
|
||||
import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
import FirstTimeWelcome from '../components/FirstTimeWelcome';
|
||||
|
|
@ -155,7 +155,7 @@ const HomeScreen = () => {
|
|||
try {
|
||||
const [addons, catalogSettingsJson, addonManifests] = await Promise.all([
|
||||
catalogService.getAllAddons(),
|
||||
AsyncStorage.getItem(CATALOG_SETTINGS_KEY),
|
||||
mmkvStorage.getItem(CATALOG_SETTINGS_KEY),
|
||||
stremioService.getInstalledAddonsAsync()
|
||||
]);
|
||||
|
||||
|
|
@ -347,10 +347,10 @@ const HomeScreen = () => {
|
|||
let hideTimer: any;
|
||||
(async () => {
|
||||
try {
|
||||
const flag = await AsyncStorage.getItem('showLoginHintToastOnce');
|
||||
const flag = await mmkvStorage.getItem('showLoginHintToastOnce');
|
||||
if (flag === 'true') {
|
||||
setHintVisible(true);
|
||||
await AsyncStorage.removeItem('showLoginHintToastOnce');
|
||||
await mmkvStorage.removeItem('showLoginHintToastOnce');
|
||||
hideTimer = setTimeout(() => setHintVisible(false), 2000);
|
||||
// Also show a global toast for consistency across screens
|
||||
// showInfo('Sign In Available', 'You can sign in anytime from Settings → Account');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { DeviceEventEmitter } from 'react-native';
|
||||
import { Share } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
import DropUpMenu from '../components/home/DropUpMenu';
|
||||
import {
|
||||
|
|
@ -289,7 +289,7 @@ const LibraryScreen = () => {
|
|||
traktId: typeof (item as any).traktId === 'number' ? (item as any).traktId : 0,
|
||||
};
|
||||
const key = `watched:${item.type}:${item.id}`;
|
||||
const watched = await AsyncStorage.getItem(key);
|
||||
const watched = await mmkvStorage.getItem(key);
|
||||
return {
|
||||
...libraryItem,
|
||||
watched: watched === 'true'
|
||||
|
|
@ -323,7 +323,7 @@ const LibraryScreen = () => {
|
|||
traktId: typeof (item as any).traktId === 'number' ? (item as any).traktId : 0,
|
||||
};
|
||||
const key = `watched:${item.type}:${item.id}`;
|
||||
const watched = await AsyncStorage.getItem(key);
|
||||
const watched = await mmkvStorage.getItem(key);
|
||||
return {
|
||||
...libraryItem,
|
||||
watched: watched === 'true'
|
||||
|
|
@ -1008,7 +1008,7 @@ const LibraryScreen = () => {
|
|||
// Use AsyncStorage to store watched status by key
|
||||
const key = `watched:${selectedItem.type}:${selectedItem.id}`;
|
||||
const newWatched = !selectedItem.watched;
|
||||
await AsyncStorage.setItem(key, newWatched ? 'true' : 'false');
|
||||
await mmkvStorage.setItem(key, newWatched ? 'true' : 'false');
|
||||
showInfo(newWatched ? 'Marked as Watched' : 'Marked as Unwatched', newWatched ? 'Item marked as watched' : 'Item marked as unwatched');
|
||||
// Instantly update local state
|
||||
setLibraryItems(prev => prev.map(item =>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
import CustomAlert from '../components/CustomAlert';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { logger } from '../utils/logger';
|
||||
import { RATING_PROVIDERS } from '../components/metadata/RatingsSection';
|
||||
|
|
@ -31,7 +31,7 @@ const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
|||
// Function to check if MDBList is enabled
|
||||
export const isMDBListEnabled = async (): Promise<boolean> => {
|
||||
try {
|
||||
const enabledSetting = await AsyncStorage.getItem(MDBLIST_ENABLED_STORAGE_KEY);
|
||||
const enabledSetting = await mmkvStorage.getItem(MDBLIST_ENABLED_STORAGE_KEY);
|
||||
return enabledSetting === 'true';
|
||||
} catch (error) {
|
||||
logger.error('[MDBList] Error checking if MDBList is enabled:', error);
|
||||
|
|
@ -48,7 +48,7 @@ export const getMDBListAPIKey = async (): Promise<string | null> => {
|
|||
return null;
|
||||
}
|
||||
|
||||
return await AsyncStorage.getItem(MDBLIST_API_KEY_STORAGE_KEY);
|
||||
return await mmkvStorage.getItem(MDBLIST_API_KEY_STORAGE_KEY);
|
||||
} catch (error) {
|
||||
logger.error('[MDBList] Error retrieving API key:', error);
|
||||
return null;
|
||||
|
|
@ -388,14 +388,14 @@ const MDBListSettingsScreen = () => {
|
|||
const loadMdbListEnabledSetting = async () => {
|
||||
logger.log('[MDBListSettingsScreen] Loading MDBList enabled setting');
|
||||
try {
|
||||
const savedSetting = await AsyncStorage.getItem(MDBLIST_ENABLED_STORAGE_KEY);
|
||||
const savedSetting = await mmkvStorage.getItem(MDBLIST_ENABLED_STORAGE_KEY);
|
||||
if (savedSetting !== null) {
|
||||
setIsMdbListEnabled(savedSetting === 'true');
|
||||
logger.log('[MDBListSettingsScreen] MDBList enabled setting:', savedSetting === 'true');
|
||||
} else {
|
||||
// Default to disabled if no setting found
|
||||
setIsMdbListEnabled(false);
|
||||
await AsyncStorage.setItem(MDBLIST_ENABLED_STORAGE_KEY, 'false');
|
||||
await mmkvStorage.setItem(MDBLIST_ENABLED_STORAGE_KEY, 'false');
|
||||
logger.log('[MDBListSettingsScreen] MDBList enabled setting not found, defaulting to false');
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -409,7 +409,7 @@ const MDBListSettingsScreen = () => {
|
|||
try {
|
||||
const newValue = !isMdbListEnabled;
|
||||
setIsMdbListEnabled(newValue);
|
||||
await AsyncStorage.setItem(MDBLIST_ENABLED_STORAGE_KEY, newValue.toString());
|
||||
await mmkvStorage.setItem(MDBLIST_ENABLED_STORAGE_KEY, newValue.toString());
|
||||
logger.log('[MDBListSettingsScreen] MDBList enabled set to:', newValue);
|
||||
} catch (error) {
|
||||
logger.error('[MDBListSettingsScreen] Failed to save MDBList enabled setting:', error);
|
||||
|
|
@ -419,7 +419,7 @@ const MDBListSettingsScreen = () => {
|
|||
const loadApiKey = async () => {
|
||||
logger.log('[MDBListSettingsScreen] Loading API key from storage');
|
||||
try {
|
||||
const savedKey = await AsyncStorage.getItem(MDBLIST_API_KEY_STORAGE_KEY);
|
||||
const savedKey = await mmkvStorage.getItem(MDBLIST_API_KEY_STORAGE_KEY);
|
||||
logger.log('[MDBListSettingsScreen] API key status:', savedKey ? 'Found' : 'Not found');
|
||||
if (savedKey) {
|
||||
setApiKey(savedKey);
|
||||
|
|
@ -438,7 +438,7 @@ const MDBListSettingsScreen = () => {
|
|||
|
||||
const loadProviderSettings = async () => {
|
||||
try {
|
||||
const savedSettings = await AsyncStorage.getItem(RATING_PROVIDERS_STORAGE_KEY);
|
||||
const savedSettings = await mmkvStorage.getItem(RATING_PROVIDERS_STORAGE_KEY);
|
||||
if (savedSettings) {
|
||||
setEnabledProviders(JSON.parse(savedSettings));
|
||||
} else {
|
||||
|
|
@ -448,7 +448,7 @@ const MDBListSettingsScreen = () => {
|
|||
return acc;
|
||||
}, {} as Record<string, boolean>);
|
||||
setEnabledProviders(defaultSettings);
|
||||
await AsyncStorage.setItem(RATING_PROVIDERS_STORAGE_KEY, JSON.stringify(defaultSettings));
|
||||
await mmkvStorage.setItem(RATING_PROVIDERS_STORAGE_KEY, JSON.stringify(defaultSettings));
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[MDBListSettingsScreen] Failed to load provider settings:', error);
|
||||
|
|
@ -462,7 +462,7 @@ const MDBListSettingsScreen = () => {
|
|||
[providerId]: !enabledProviders[providerId]
|
||||
};
|
||||
setEnabledProviders(newSettings);
|
||||
await AsyncStorage.setItem(RATING_PROVIDERS_STORAGE_KEY, JSON.stringify(newSettings));
|
||||
await mmkvStorage.setItem(RATING_PROVIDERS_STORAGE_KEY, JSON.stringify(newSettings));
|
||||
} catch (error) {
|
||||
logger.error('[MDBListSettingsScreen] Failed to save provider settings:', error);
|
||||
}
|
||||
|
|
@ -481,7 +481,7 @@ const MDBListSettingsScreen = () => {
|
|||
}
|
||||
|
||||
logger.log('[MDBListSettingsScreen] Saving API key');
|
||||
await AsyncStorage.setItem(MDBLIST_API_KEY_STORAGE_KEY, trimmedKey);
|
||||
await mmkvStorage.setItem(MDBLIST_API_KEY_STORAGE_KEY, trimmedKey);
|
||||
setIsKeySet(true);
|
||||
setTestResult({ success: true, message: 'API key saved successfully.' });
|
||||
logger.log('[MDBListSettingsScreen] API key saved successfully');
|
||||
|
|
@ -506,7 +506,7 @@ const MDBListSettingsScreen = () => {
|
|||
onPress: async () => {
|
||||
logger.log('[MDBListSettingsScreen] Proceeding with API key clear');
|
||||
try {
|
||||
await AsyncStorage.removeItem(MDBLIST_API_KEY_STORAGE_KEY);
|
||||
await mmkvStorage.removeItem(MDBLIST_API_KEY_STORAGE_KEY);
|
||||
setApiKey('');
|
||||
setIsKeySet(false);
|
||||
setTestResult(null);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import Animated, {
|
|||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { NavigationProp, useNavigation } from '@react-navigation/native';
|
||||
import { RootStackParamList } from '../navigation/AppNavigator';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
|
||||
const { width, height } = Dimensions.get('window');
|
||||
|
||||
|
|
@ -125,8 +125,8 @@ const OnboardingScreen = () => {
|
|||
// Skip login: proceed to app and show a one-time hint toast
|
||||
(async () => {
|
||||
try {
|
||||
await AsyncStorage.setItem('hasCompletedOnboarding', 'true');
|
||||
await AsyncStorage.setItem('showLoginHintToastOnce', 'true');
|
||||
await mmkvStorage.setItem('hasCompletedOnboarding', 'true');
|
||||
await mmkvStorage.setItem('showLoginHintToastOnce', 'true');
|
||||
} catch {}
|
||||
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] });
|
||||
})();
|
||||
|
|
@ -134,7 +134,7 @@ const OnboardingScreen = () => {
|
|||
|
||||
const handleGetStarted = async () => {
|
||||
try {
|
||||
await AsyncStorage.setItem('hasCompletedOnboarding', 'true');
|
||||
await mmkvStorage.setItem('hasCompletedOnboarding', 'true');
|
||||
// After onboarding, go directly to main app
|
||||
navigation.reset({ index: 0, routes: [{ name: 'MainTabs' }] });
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { useNavigation } from '@react-navigation/native';
|
|||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useTraktContext } from '../contexts/TraktContext';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
|
||||
const ANDROID_STATUSBAR_HEIGHT = StatusBar.currentHeight || 0;
|
||||
|
|
@ -60,7 +60,7 @@ const ProfilesScreen: React.FC = () => {
|
|||
const loadProfiles = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const storedProfiles = await AsyncStorage.getItem(PROFILE_STORAGE_KEY);
|
||||
const storedProfiles = await mmkvStorage.getItem(PROFILE_STORAGE_KEY);
|
||||
if (storedProfiles) {
|
||||
setProfiles(JSON.parse(storedProfiles));
|
||||
} else {
|
||||
|
|
@ -72,7 +72,7 @@ const ProfilesScreen: React.FC = () => {
|
|||
createdAt: new Date().getTime()
|
||||
};
|
||||
setProfiles([defaultProfile]);
|
||||
await AsyncStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify([defaultProfile]));
|
||||
await mmkvStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify([defaultProfile]));
|
||||
}
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error loading profiles:', error);
|
||||
|
|
@ -99,7 +99,7 @@ const ProfilesScreen: React.FC = () => {
|
|||
// Save profiles to AsyncStorage
|
||||
const saveProfiles = useCallback(async (updatedProfiles: Profile[]) => {
|
||||
try {
|
||||
await AsyncStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify(updatedProfiles));
|
||||
await mmkvStorage.setItem(PROFILE_STORAGE_KEY, JSON.stringify(updatedProfiles));
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Error saving profiles:', error);
|
||||
openAlert('Error', 'Failed to save profiles');
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import FastImage from '@d11/react-native-fast-image';
|
|||
import debounce from 'lodash/debounce';
|
||||
import { DropUpMenu } from '../components/home/DropUpMenu';
|
||||
import { DeviceEventEmitter, Share } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import Animated, {
|
||||
FadeIn,
|
||||
FadeOut,
|
||||
|
|
@ -250,7 +250,7 @@ const SearchScreen = () => {
|
|||
const found = items.find((libItem: any) => libItem.id === selectedItem.id && libItem.type === selectedItem.type);
|
||||
setIsSaved(!!found);
|
||||
// Check watched status
|
||||
const val = await AsyncStorage.getItem(`watched:${selectedItem.type}:${selectedItem.id}`);
|
||||
const val = await mmkvStorage.getItem(`watched:${selectedItem.type}:${selectedItem.id}`);
|
||||
setIsWatched(val === 'true');
|
||||
})();
|
||||
}, [selectedItem]);
|
||||
|
|
@ -349,7 +349,7 @@ const SearchScreen = () => {
|
|||
|
||||
const loadRecentSearches = async () => {
|
||||
try {
|
||||
const savedSearches = await AsyncStorage.getItem(RECENT_SEARCHES_KEY);
|
||||
const savedSearches = await mmkvStorage.getItem(RECENT_SEARCHES_KEY);
|
||||
if (savedSearches) {
|
||||
setRecentSearches(JSON.parse(savedSearches));
|
||||
}
|
||||
|
|
@ -367,7 +367,7 @@ const SearchScreen = () => {
|
|||
].slice(0, MAX_RECENT_SEARCHES);
|
||||
|
||||
// Save to AsyncStorage
|
||||
AsyncStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(newRecentSearches));
|
||||
mmkvStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(newRecentSearches));
|
||||
|
||||
return newRecentSearches;
|
||||
});
|
||||
|
|
@ -533,7 +533,7 @@ const SearchScreen = () => {
|
|||
const newRecentSearches = [...recentSearches];
|
||||
newRecentSearches.splice(index, 1);
|
||||
setRecentSearches(newRecentSearches);
|
||||
AsyncStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(newRecentSearches));
|
||||
mmkvStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(newRecentSearches));
|
||||
}}
|
||||
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
||||
style={styles.recentSearchDeleteButton}
|
||||
|
|
@ -558,7 +558,7 @@ const SearchScreen = () => {
|
|||
const [watched, setWatched] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
const updateWatched = () => {
|
||||
AsyncStorage.getItem(`watched:${item.type}:${item.id}`).then(val => setWatched(val === 'true'));
|
||||
mmkvStorage.getItem(`watched:${item.type}:${item.id}`).then(val => setWatched(val === 'true'));
|
||||
};
|
||||
updateWatched();
|
||||
const sub = DeviceEventEmitter.addListener('watchedStatusChanged', updateWatched);
|
||||
|
|
@ -977,7 +977,7 @@ const SearchScreen = () => {
|
|||
case 'watched': {
|
||||
const key = `watched:${selectedItem.type}:${selectedItem.id}`;
|
||||
const newWatched = !isWatched;
|
||||
await AsyncStorage.setItem(key, newWatched ? 'true' : 'false');
|
||||
await mmkvStorage.setItem(key, newWatched ? 'true' : 'false');
|
||||
setIsWatched(newWatched);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
Linking,
|
||||
Clipboard
|
||||
} from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { NavigationProp } from '@react-navigation/native';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
|
|
@ -271,7 +271,7 @@ const SettingsScreen: React.FC = () => {
|
|||
let mounted = true;
|
||||
(async () => {
|
||||
try {
|
||||
const flag = await AsyncStorage.getItem('@update_badge_pending');
|
||||
const flag = await mmkvStorage.getItem('@update_badge_pending');
|
||||
if (mounted) setHasUpdateBadge(flag === 'true');
|
||||
} catch {}
|
||||
})();
|
||||
|
|
@ -329,7 +329,7 @@ const SettingsScreen: React.FC = () => {
|
|||
});
|
||||
|
||||
// Load saved catalog settings
|
||||
const catalogSettingsJson = await AsyncStorage.getItem('catalog_settings');
|
||||
const catalogSettingsJson = await mmkvStorage.getItem('catalog_settings');
|
||||
if (catalogSettingsJson) {
|
||||
const catalogSettings = JSON.parse(catalogSettingsJson);
|
||||
// Filter out _lastUpdate key and count only explicitly disabled catalogs
|
||||
|
|
@ -344,11 +344,11 @@ const SettingsScreen: React.FC = () => {
|
|||
}
|
||||
|
||||
// Check MDBList API key status
|
||||
const mdblistKey = await AsyncStorage.getItem('mdblist_api_key');
|
||||
const mdblistKey = await mmkvStorage.getItem('mdblist_api_key');
|
||||
setMdblistKeySet(!!mdblistKey);
|
||||
|
||||
// Check OpenRouter API key status
|
||||
const openRouterKey = await AsyncStorage.getItem('openrouter_api_key');
|
||||
const openRouterKey = await mmkvStorage.getItem('openrouter_api_key');
|
||||
setOpenRouterKeySet(!!openRouterKey);
|
||||
|
||||
// Load GitHub total downloads (initial load only, polling happens in useEffect)
|
||||
|
|
@ -459,7 +459,7 @@ const SettingsScreen: React.FC = () => {
|
|||
label: 'Clear',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await AsyncStorage.removeItem('mdblist_cache');
|
||||
await mmkvStorage.removeItem('mdblist_cache');
|
||||
openAlert('Success', 'MDBList cache has been cleared.');
|
||||
} catch (error) {
|
||||
openAlert('Error', 'Could not clear MDBList cache.');
|
||||
|
|
@ -746,7 +746,7 @@ const SettingsScreen: React.FC = () => {
|
|||
icon="refresh-ccw"
|
||||
onPress={async () => {
|
||||
try {
|
||||
await AsyncStorage.removeItem('hasCompletedOnboarding');
|
||||
await mmkvStorage.removeItem('hasCompletedOnboarding');
|
||||
openAlert('Success', 'Onboarding has been reset. Restart the app to see the onboarding flow.');
|
||||
} catch (error) {
|
||||
openAlert('Error', 'Failed to reset onboarding.');
|
||||
|
|
@ -768,7 +768,7 @@ const SettingsScreen: React.FC = () => {
|
|||
label: 'Clear',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await AsyncStorage.clear();
|
||||
await mmkvStorage.clear();
|
||||
openAlert('Success', 'All data cleared. Please restart the app.');
|
||||
} catch (error) {
|
||||
openAlert('Error', 'Failed to clear data.');
|
||||
|
|
@ -823,7 +823,7 @@ const SettingsScreen: React.FC = () => {
|
|||
badge={Platform.OS === 'android' && hasUpdateBadge ? 1 : undefined}
|
||||
onPress={async () => {
|
||||
if (Platform.OS === 'android') {
|
||||
try { await AsyncStorage.removeItem('@update_badge_pending'); } catch {}
|
||||
try { await mmkvStorage.removeItem('@update_badge_pending'); } catch {}
|
||||
setHasUpdateBadge(false);
|
||||
}
|
||||
navigation.navigate('Update');
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from 'react-native';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import FastImage from '@d11/react-native-fast-image';
|
||||
import { tmdbService } from '../services/tmdbService';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
|
|
@ -124,8 +124,8 @@ const TMDBSettingsScreen = () => {
|
|||
logger.log('[TMDBSettingsScreen] Loading settings from storage');
|
||||
try {
|
||||
const [savedKey, savedUseCustomKey] = await Promise.all([
|
||||
AsyncStorage.getItem(TMDB_API_KEY_STORAGE_KEY),
|
||||
AsyncStorage.getItem(USE_CUSTOM_TMDB_API_KEY)
|
||||
mmkvStorage.getItem(TMDB_API_KEY_STORAGE_KEY),
|
||||
mmkvStorage.getItem(USE_CUSTOM_TMDB_API_KEY)
|
||||
]);
|
||||
|
||||
logger.log('[TMDBSettingsScreen] API key status:', savedKey ? 'Found' : 'Not found');
|
||||
|
|
@ -164,8 +164,8 @@ const TMDBSettingsScreen = () => {
|
|||
// Test the API key to make sure it works
|
||||
if (await testApiKey(trimmedKey)) {
|
||||
logger.log('[TMDBSettingsScreen] API key test successful, saving key');
|
||||
await AsyncStorage.setItem(TMDB_API_KEY_STORAGE_KEY, trimmedKey);
|
||||
await AsyncStorage.setItem(USE_CUSTOM_TMDB_API_KEY, 'true');
|
||||
await mmkvStorage.setItem(TMDB_API_KEY_STORAGE_KEY, trimmedKey);
|
||||
await mmkvStorage.setItem(USE_CUSTOM_TMDB_API_KEY, 'true');
|
||||
setIsKeySet(true);
|
||||
setUseCustomKey(true);
|
||||
setTestResult({ success: true, message: 'API key verified and saved successfully.' });
|
||||
|
|
@ -217,8 +217,8 @@ const TMDBSettingsScreen = () => {
|
|||
onPress: async () => {
|
||||
logger.log('[TMDBSettingsScreen] Proceeding with API key clear');
|
||||
try {
|
||||
await AsyncStorage.removeItem(TMDB_API_KEY_STORAGE_KEY);
|
||||
await AsyncStorage.setItem(USE_CUSTOM_TMDB_API_KEY, 'false');
|
||||
await mmkvStorage.removeItem(TMDB_API_KEY_STORAGE_KEY);
|
||||
await mmkvStorage.setItem(USE_CUSTOM_TMDB_API_KEY, 'false');
|
||||
setApiKey('');
|
||||
setIsKeySet(false);
|
||||
setUseCustomKey(false);
|
||||
|
|
@ -237,7 +237,7 @@ const TMDBSettingsScreen = () => {
|
|||
const toggleUseCustomKey = async (value: boolean) => {
|
||||
logger.log('[TMDBSettingsScreen] Toggle use custom key:', value);
|
||||
try {
|
||||
await AsyncStorage.setItem(USE_CUSTOM_TMDB_API_KEY, value ? 'true' : 'false');
|
||||
await mmkvStorage.setItem(USE_CUSTOM_TMDB_API_KEY, value ? 'true' : 'false');
|
||||
setUseCustomKey(value);
|
||||
|
||||
if (!value) {
|
||||
|
|
@ -370,7 +370,7 @@ const TMDBSettingsScreen = () => {
|
|||
const handleShowSelect = (show: typeof EXAMPLE_SHOWS[0]) => {
|
||||
setSelectedShow(show);
|
||||
try {
|
||||
AsyncStorage.setItem('tmdb_settings_selected_show', show.imdbId);
|
||||
mmkvStorage.setItem('tmdb_settings_selected_show', show.imdbId);
|
||||
} catch (e) {
|
||||
if (__DEV__) console.error('Error saving selected show:', e);
|
||||
}
|
||||
|
|
@ -420,7 +420,7 @@ const TMDBSettingsScreen = () => {
|
|||
useEffect(() => {
|
||||
const loadSelectedShow = async () => {
|
||||
try {
|
||||
const savedShowId = await AsyncStorage.getItem('tmdb_settings_selected_show');
|
||||
const savedShowId = await mmkvStorage.getItem('tmdb_settings_selected_show');
|
||||
if (savedShowId) {
|
||||
const foundShow = EXAMPLE_SHOWS.find(show => show.imdbId === savedShowId);
|
||||
if (foundShow) {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { useTheme } from '../contexts/ThemeContext';
|
|||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import UpdateService from '../services/updateService';
|
||||
import CustomAlert from '../components/CustomAlert';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { useGithubMajorUpdate } from '../hooks/useGithubMajorUpdate';
|
||||
import { getDisplayedAppVersion } from '../utils/version';
|
||||
import { isAnyUpgrade } from '../services/githubReleaseService';
|
||||
|
|
@ -146,7 +146,7 @@ const UpdateScreen: React.FC = () => {
|
|||
if (Platform.OS === 'android') {
|
||||
// ensure badge clears when entering this screen
|
||||
(async () => {
|
||||
try { await AsyncStorage.removeItem('@update_badge_pending'); } catch {}
|
||||
try { await mmkvStorage.removeItem('@update_badge_pending'); } catch {}
|
||||
})();
|
||||
}
|
||||
checkForUpdates();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
|
||||
export type AuthUser = {
|
||||
id: string;
|
||||
|
|
@ -30,13 +30,13 @@ class AccountService {
|
|||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
await AsyncStorage.removeItem(USER_DATA_KEY);
|
||||
await AsyncStorage.setItem(USER_SCOPE_KEY, 'local');
|
||||
await mmkvStorage.removeItem(USER_DATA_KEY);
|
||||
await mmkvStorage.setItem(USER_SCOPE_KEY, 'local');
|
||||
}
|
||||
|
||||
async getCurrentUser(): Promise<AuthUser | null> {
|
||||
try {
|
||||
const userData = await AsyncStorage.getItem(USER_DATA_KEY);
|
||||
const userData = await mmkvStorage.getItem(USER_DATA_KEY);
|
||||
if (!userData) return null;
|
||||
return JSON.parse(userData);
|
||||
} catch {
|
||||
|
|
@ -50,7 +50,7 @@ class AccountService {
|
|||
if (!currentUser) return 'Not authenticated';
|
||||
|
||||
const updatedUser = { ...currentUser, ...partial };
|
||||
await AsyncStorage.setItem(USER_DATA_KEY, JSON.stringify(updatedUser));
|
||||
await mmkvStorage.setItem(USER_DATA_KEY, JSON.stringify(updatedUser));
|
||||
return null;
|
||||
} catch {
|
||||
return 'Failed to update profile';
|
||||
|
|
@ -61,8 +61,8 @@ class AccountService {
|
|||
const user = await this.getCurrentUser();
|
||||
if (user?.id) return user.id;
|
||||
// Guest scope
|
||||
const scope = (await AsyncStorage.getItem(USER_SCOPE_KEY)) || 'local';
|
||||
if (!scope) await AsyncStorage.setItem(USER_SCOPE_KEY, 'local');
|
||||
const scope = (await mmkvStorage.getItem(USER_SCOPE_KEY)) || 'local';
|
||||
if (!scope) await mmkvStorage.setItem(USER_SCOPE_KEY, 'local');
|
||||
return scope || 'local';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
|
|
@ -112,7 +112,7 @@ class AIService {
|
|||
|
||||
async initialize(): Promise<boolean> {
|
||||
try {
|
||||
this.apiKey = await AsyncStorage.getItem('openrouter_api_key');
|
||||
this.apiKey = await mmkvStorage.getItem('openrouter_api_key');
|
||||
return !!this.apiKey;
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('Failed to initialize AI service:', error);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { Platform } from 'react-native';
|
||||
import { logger } from '../utils/logger';
|
||||
|
|
@ -373,7 +373,7 @@ export class BackupService {
|
|||
// Private helper methods for data collection
|
||||
private async getUserScope(): Promise<string> {
|
||||
try {
|
||||
const scope = await AsyncStorage.getItem('@user:current');
|
||||
const scope = await mmkvStorage.getItem('@user:current');
|
||||
return scope || 'local';
|
||||
} catch {
|
||||
return 'local';
|
||||
|
|
@ -384,7 +384,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:app_settings`;
|
||||
const settingsJson = await AsyncStorage.getItem(scopedKey);
|
||||
const settingsJson = await mmkvStorage.getItem(scopedKey);
|
||||
return settingsJson ? JSON.parse(settingsJson) : DEFAULT_SETTINGS;
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get settings:', error);
|
||||
|
|
@ -396,7 +396,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:stremio-library`;
|
||||
const libraryJson = await AsyncStorage.getItem(scopedKey);
|
||||
const libraryJson = await mmkvStorage.getItem(scopedKey);
|
||||
if (libraryJson) {
|
||||
const parsed = JSON.parse(libraryJson);
|
||||
return Array.isArray(parsed) ? parsed : Object.values(parsed);
|
||||
|
|
@ -411,14 +411,14 @@ export class BackupService {
|
|||
private async getWatchProgress(): Promise<Record<string, any>> {
|
||||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const allKeys = await AsyncStorage.getAllKeys();
|
||||
const allKeys = await mmkvStorage.getAllKeys();
|
||||
const watchProgressKeys = allKeys.filter(key =>
|
||||
key.startsWith(`@user:${scope}:@watch_progress:`)
|
||||
);
|
||||
|
||||
const watchProgress: Record<string, any> = {};
|
||||
if (watchProgressKeys.length > 0) {
|
||||
const pairs = await AsyncStorage.multiGet(watchProgressKeys);
|
||||
const pairs = await mmkvStorage.multiGet(watchProgressKeys);
|
||||
for (const [key, value] of pairs) {
|
||||
if (value) {
|
||||
watchProgress[key] = JSON.parse(value);
|
||||
|
|
@ -436,7 +436,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:stremio-addons`;
|
||||
const addonsJson = await AsyncStorage.getItem(scopedKey);
|
||||
const addonsJson = await mmkvStorage.getItem(scopedKey);
|
||||
return addonsJson ? JSON.parse(addonsJson) : [];
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get addons:', error);
|
||||
|
|
@ -446,7 +446,7 @@ export class BackupService {
|
|||
|
||||
private async getDownloads(): Promise<DownloadItem[]> {
|
||||
try {
|
||||
const downloadsJson = await AsyncStorage.getItem('downloads_state_v1');
|
||||
const downloadsJson = await mmkvStorage.getItem('downloads_state_v1');
|
||||
return downloadsJson ? JSON.parse(downloadsJson) : [];
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get downloads:', error);
|
||||
|
|
@ -458,11 +458,11 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:@subtitle_settings`;
|
||||
const subtitlesJson = await AsyncStorage.getItem(scopedKey);
|
||||
const subtitlesJson = await mmkvStorage.getItem(scopedKey);
|
||||
let subtitleSettings = subtitlesJson ? JSON.parse(subtitlesJson) : {};
|
||||
|
||||
// Also check for legacy subtitle size preference
|
||||
const legacySubtitleSize = await AsyncStorage.getItem('@subtitle_size_preference');
|
||||
const legacySubtitleSize = await mmkvStorage.getItem('@subtitle_size_preference');
|
||||
if (legacySubtitleSize && !subtitleSettings.subtitleSize) {
|
||||
const legacySize = parseInt(legacySubtitleSize, 10);
|
||||
if (!Number.isNaN(legacySize) && legacySize > 0) {
|
||||
|
|
@ -481,7 +481,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:@wp_tombstones`;
|
||||
const tombstonesJson = await AsyncStorage.getItem(scopedKey);
|
||||
const tombstonesJson = await mmkvStorage.getItem(scopedKey);
|
||||
return tombstonesJson ? JSON.parse(tombstonesJson) : {};
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get tombstones:', error);
|
||||
|
|
@ -493,7 +493,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:@continue_watching_removed`;
|
||||
const removedJson = await AsyncStorage.getItem(scopedKey);
|
||||
const removedJson = await mmkvStorage.getItem(scopedKey);
|
||||
return removedJson ? JSON.parse(removedJson) : {};
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get continue watching removed:', error);
|
||||
|
|
@ -504,14 +504,14 @@ export class BackupService {
|
|||
private async getContentDuration(): Promise<Record<string, number>> {
|
||||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const allKeys = await AsyncStorage.getAllKeys();
|
||||
const allKeys = await mmkvStorage.getAllKeys();
|
||||
const durationKeys = allKeys.filter(key =>
|
||||
key.startsWith(`@user:${scope}:@content_duration:`)
|
||||
);
|
||||
|
||||
const contentDuration: Record<string, number> = {};
|
||||
if (durationKeys.length > 0) {
|
||||
const pairs = await AsyncStorage.multiGet(durationKeys);
|
||||
const pairs = await mmkvStorage.multiGet(durationKeys);
|
||||
for (const [key, value] of pairs) {
|
||||
if (value) {
|
||||
contentDuration[key] = JSON.parse(value);
|
||||
|
|
@ -527,7 +527,7 @@ export class BackupService {
|
|||
|
||||
private async getSyncQueue(): Promise<any[]> {
|
||||
try {
|
||||
const syncQueueJson = await AsyncStorage.getItem('@sync_queue');
|
||||
const syncQueueJson = await mmkvStorage.getItem('@sync_queue');
|
||||
return syncQueueJson ? JSON.parse(syncQueueJson) : [];
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get sync queue:', error);
|
||||
|
|
@ -538,7 +538,7 @@ export class BackupService {
|
|||
private async getTraktSettings(): Promise<any> {
|
||||
try {
|
||||
// Get general Trakt settings
|
||||
const traktSettingsJson = await AsyncStorage.getItem('trakt_settings');
|
||||
const traktSettingsJson = await mmkvStorage.getItem('trakt_settings');
|
||||
const traktSettings = traktSettingsJson ? JSON.parse(traktSettingsJson) : {};
|
||||
|
||||
// Get authentication tokens
|
||||
|
|
@ -550,12 +550,12 @@ export class BackupService {
|
|||
syncFrequency,
|
||||
completionThreshold
|
||||
] = await Promise.all([
|
||||
AsyncStorage.getItem('trakt_access_token'),
|
||||
AsyncStorage.getItem('trakt_refresh_token'),
|
||||
AsyncStorage.getItem('trakt_token_expiry'),
|
||||
AsyncStorage.getItem('trakt_autosync_enabled'),
|
||||
AsyncStorage.getItem('trakt_sync_frequency'),
|
||||
AsyncStorage.getItem('trakt_completion_threshold')
|
||||
mmkvStorage.getItem('trakt_access_token'),
|
||||
mmkvStorage.getItem('trakt_refresh_token'),
|
||||
mmkvStorage.getItem('trakt_token_expiry'),
|
||||
mmkvStorage.getItem('trakt_autosync_enabled'),
|
||||
mmkvStorage.getItem('trakt_sync_frequency'),
|
||||
mmkvStorage.getItem('trakt_completion_threshold')
|
||||
]);
|
||||
|
||||
return {
|
||||
|
|
@ -583,21 +583,21 @@ export class BackupService {
|
|||
private async getLocalScrapers(): Promise<any> {
|
||||
try {
|
||||
// Get main scraper configurations
|
||||
const localScrapersJson = await AsyncStorage.getItem('local-scrapers');
|
||||
const localScrapersJson = await mmkvStorage.getItem('local-scrapers');
|
||||
|
||||
// Get repository settings
|
||||
const repoUrl = await AsyncStorage.getItem('scraper-repository-url');
|
||||
const repositories = await AsyncStorage.getItem('scraper-repositories');
|
||||
const currentRepo = await AsyncStorage.getItem('current-repository-id');
|
||||
const scraperSettings = await AsyncStorage.getItem('scraper-settings');
|
||||
const repoUrl = await mmkvStorage.getItem('scraper-repository-url');
|
||||
const repositories = await mmkvStorage.getItem('scraper-repositories');
|
||||
const currentRepo = await mmkvStorage.getItem('current-repository-id');
|
||||
const scraperSettings = await mmkvStorage.getItem('scraper-settings');
|
||||
|
||||
// Get all scraper code cache keys
|
||||
const allKeys = await AsyncStorage.getAllKeys();
|
||||
const allKeys = await mmkvStorage.getAllKeys();
|
||||
const scraperCodeKeys = allKeys.filter(key => key.startsWith('scraper-code-'));
|
||||
const scraperCode: Record<string, string> = {};
|
||||
|
||||
if (scraperCodeKeys.length > 0) {
|
||||
const codePairs = await AsyncStorage.multiGet(scraperCodeKeys);
|
||||
const codePairs = await mmkvStorage.multiGet(scraperCodeKeys);
|
||||
for (const [key, value] of codePairs) {
|
||||
if (value) {
|
||||
scraperCode[key] = value;
|
||||
|
|
@ -622,8 +622,8 @@ export class BackupService {
|
|||
private async getApiKeys(): Promise<{ mdblistApiKey?: string; openRouterApiKey?: string }> {
|
||||
try {
|
||||
const [mdblistKey, openRouterKey] = await Promise.all([
|
||||
AsyncStorage.getItem('mdblist_api_key'),
|
||||
AsyncStorage.getItem('openrouter_api_key')
|
||||
mmkvStorage.getItem('mdblist_api_key'),
|
||||
mmkvStorage.getItem('openrouter_api_key')
|
||||
]);
|
||||
|
||||
return {
|
||||
|
|
@ -638,7 +638,7 @@ export class BackupService {
|
|||
|
||||
private async getCatalogSettings(): Promise<any> {
|
||||
try {
|
||||
const catalogSettingsJson = await AsyncStorage.getItem('catalog_settings');
|
||||
const catalogSettingsJson = await mmkvStorage.getItem('catalog_settings');
|
||||
return catalogSettingsJson ? JSON.parse(catalogSettingsJson) : null;
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get catalog settings:', error);
|
||||
|
|
@ -653,9 +653,9 @@ export class BackupService {
|
|||
|
||||
// Try scoped key first, then legacy keys
|
||||
const [scopedOrder, legacyOrder, localOrder] = await Promise.all([
|
||||
AsyncStorage.getItem(scopedKey),
|
||||
AsyncStorage.getItem('stremio-addon-order'),
|
||||
AsyncStorage.getItem('@user:local:stremio-addon-order')
|
||||
mmkvStorage.getItem(scopedKey),
|
||||
mmkvStorage.getItem('stremio-addon-order'),
|
||||
mmkvStorage.getItem('@user:local:stremio-addon-order')
|
||||
]);
|
||||
|
||||
const orderJson = scopedOrder || legacyOrder || localOrder;
|
||||
|
|
@ -668,7 +668,7 @@ export class BackupService {
|
|||
|
||||
private async getRemovedAddons(): Promise<string[]> {
|
||||
try {
|
||||
const removedAddonsJson = await AsyncStorage.getItem('user_removed_addons');
|
||||
const removedAddonsJson = await mmkvStorage.getItem('user_removed_addons');
|
||||
return removedAddonsJson ? JSON.parse(removedAddonsJson) : [];
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get removed addons:', error);
|
||||
|
|
@ -678,7 +678,7 @@ export class BackupService {
|
|||
|
||||
private async getGlobalSeasonViewMode(): Promise<string | undefined> {
|
||||
try {
|
||||
const mode = await AsyncStorage.getItem('global_season_view_mode');
|
||||
const mode = await mmkvStorage.getItem('global_season_view_mode');
|
||||
return mode || undefined;
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get global season view mode:', error);
|
||||
|
|
@ -688,7 +688,7 @@ export class BackupService {
|
|||
|
||||
private async getHasCompletedOnboarding(): Promise<boolean | undefined> {
|
||||
try {
|
||||
const value = await AsyncStorage.getItem('hasCompletedOnboarding');
|
||||
const value = await mmkvStorage.getItem('hasCompletedOnboarding');
|
||||
return value === 'true' ? true : value === 'false' ? false : undefined;
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get has completed onboarding:', error);
|
||||
|
|
@ -698,7 +698,7 @@ export class BackupService {
|
|||
|
||||
private async getShowLoginHintToastOnce(): Promise<boolean | undefined> {
|
||||
try {
|
||||
const value = await AsyncStorage.getItem('showLoginHintToastOnce');
|
||||
const value = await mmkvStorage.getItem('showLoginHintToastOnce');
|
||||
return value === 'true' ? true : value === 'false' ? false : undefined;
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get show login hint toast once:', error);
|
||||
|
|
@ -711,7 +711,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:app_settings`;
|
||||
await AsyncStorage.setItem(scopedKey, JSON.stringify(settings));
|
||||
await mmkvStorage.setItem(scopedKey, JSON.stringify(settings));
|
||||
logger.info('[BackupService] Settings restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore settings:', error);
|
||||
|
|
@ -722,7 +722,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:stremio-library`;
|
||||
await AsyncStorage.setItem(scopedKey, JSON.stringify(library));
|
||||
await mmkvStorage.setItem(scopedKey, JSON.stringify(library));
|
||||
logger.info('[BackupService] Library restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore library:', error);
|
||||
|
|
@ -732,7 +732,7 @@ export class BackupService {
|
|||
private async restoreWatchProgress(watchProgress: Record<string, any>): Promise<void> {
|
||||
try {
|
||||
const pairs: [string, string][] = Object.entries(watchProgress).map(([key, value]) => [key, JSON.stringify(value)]);
|
||||
await AsyncStorage.multiSet(pairs);
|
||||
await mmkvStorage.multiSet(pairs);
|
||||
logger.info('[BackupService] Watch progress restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore watch progress:', error);
|
||||
|
|
@ -743,7 +743,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:stremio-addons`;
|
||||
await AsyncStorage.setItem(scopedKey, JSON.stringify(addons));
|
||||
await mmkvStorage.setItem(scopedKey, JSON.stringify(addons));
|
||||
logger.info('[BackupService] Addons restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore addons:', error);
|
||||
|
|
@ -752,7 +752,7 @@ export class BackupService {
|
|||
|
||||
private async restoreDownloads(downloads: DownloadItem[]): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem('downloads_state_v1', JSON.stringify(downloads));
|
||||
await mmkvStorage.setItem('downloads_state_v1', JSON.stringify(downloads));
|
||||
logger.info('[BackupService] Downloads restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore downloads:', error);
|
||||
|
|
@ -763,11 +763,11 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:@subtitle_settings`;
|
||||
await AsyncStorage.setItem(scopedKey, JSON.stringify(subtitles));
|
||||
await mmkvStorage.setItem(scopedKey, JSON.stringify(subtitles));
|
||||
|
||||
// Also restore legacy subtitle size preference for backward compatibility
|
||||
if (subtitles && typeof subtitles.subtitleSize === 'number') {
|
||||
await AsyncStorage.setItem('@subtitle_size_preference', subtitles.subtitleSize.toString());
|
||||
await mmkvStorage.setItem('@subtitle_size_preference', subtitles.subtitleSize.toString());
|
||||
}
|
||||
|
||||
logger.info('[BackupService] Subtitle settings restored');
|
||||
|
|
@ -780,7 +780,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:@wp_tombstones`;
|
||||
await AsyncStorage.setItem(scopedKey, JSON.stringify(tombstones));
|
||||
await mmkvStorage.setItem(scopedKey, JSON.stringify(tombstones));
|
||||
logger.info('[BackupService] Tombstones restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore tombstones:', error);
|
||||
|
|
@ -791,7 +791,7 @@ export class BackupService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:@continue_watching_removed`;
|
||||
await AsyncStorage.setItem(scopedKey, JSON.stringify(removed));
|
||||
await mmkvStorage.setItem(scopedKey, JSON.stringify(removed));
|
||||
logger.info('[BackupService] Continue watching removed restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore continue watching removed:', error);
|
||||
|
|
@ -801,7 +801,7 @@ export class BackupService {
|
|||
private async restoreContentDuration(contentDuration: Record<string, number>): Promise<void> {
|
||||
try {
|
||||
const pairs: [string, string][] = Object.entries(contentDuration).map(([key, value]) => [key, JSON.stringify(value)]);
|
||||
await AsyncStorage.multiSet(pairs);
|
||||
await mmkvStorage.multiSet(pairs);
|
||||
logger.info('[BackupService] Content duration restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore content duration:', error);
|
||||
|
|
@ -810,7 +810,7 @@ export class BackupService {
|
|||
|
||||
private async restoreSyncQueue(syncQueue: any[]): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem('@sync_queue', JSON.stringify(syncQueue));
|
||||
await mmkvStorage.setItem('@sync_queue', JSON.stringify(syncQueue));
|
||||
logger.info('[BackupService] Sync queue restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore sync queue:', error);
|
||||
|
|
@ -824,22 +824,22 @@ export class BackupService {
|
|||
const { authentication, autosync, ...generalSettings } = traktSettings;
|
||||
|
||||
// Restore general settings
|
||||
await AsyncStorage.setItem('trakt_settings', JSON.stringify(generalSettings));
|
||||
await mmkvStorage.setItem('trakt_settings', JSON.stringify(generalSettings));
|
||||
|
||||
// Restore authentication tokens if available
|
||||
if (authentication) {
|
||||
const tokenPromises = [];
|
||||
|
||||
if (authentication.accessToken) {
|
||||
tokenPromises.push(AsyncStorage.setItem('trakt_access_token', authentication.accessToken));
|
||||
tokenPromises.push(mmkvStorage.setItem('trakt_access_token', authentication.accessToken));
|
||||
}
|
||||
|
||||
if (authentication.refreshToken) {
|
||||
tokenPromises.push(AsyncStorage.setItem('trakt_refresh_token', authentication.refreshToken));
|
||||
tokenPromises.push(mmkvStorage.setItem('trakt_refresh_token', authentication.refreshToken));
|
||||
}
|
||||
|
||||
if (authentication.tokenExpiry) {
|
||||
tokenPromises.push(AsyncStorage.setItem('trakt_token_expiry', authentication.tokenExpiry.toString()));
|
||||
tokenPromises.push(mmkvStorage.setItem('trakt_token_expiry', authentication.tokenExpiry.toString()));
|
||||
}
|
||||
|
||||
await Promise.all(tokenPromises);
|
||||
|
|
@ -850,15 +850,15 @@ export class BackupService {
|
|||
const autosyncPromises = [];
|
||||
|
||||
if (autosync.enabled !== undefined) {
|
||||
autosyncPromises.push(AsyncStorage.setItem('trakt_autosync_enabled', JSON.stringify(autosync.enabled)));
|
||||
autosyncPromises.push(mmkvStorage.setItem('trakt_autosync_enabled', JSON.stringify(autosync.enabled)));
|
||||
}
|
||||
|
||||
if (autosync.frequency !== undefined) {
|
||||
autosyncPromises.push(AsyncStorage.setItem('trakt_sync_frequency', autosync.frequency.toString()));
|
||||
autosyncPromises.push(mmkvStorage.setItem('trakt_sync_frequency', autosync.frequency.toString()));
|
||||
}
|
||||
|
||||
if (autosync.completionThreshold !== undefined) {
|
||||
autosyncPromises.push(AsyncStorage.setItem('trakt_completion_threshold', autosync.completionThreshold.toString()));
|
||||
autosyncPromises.push(mmkvStorage.setItem('trakt_completion_threshold', autosync.completionThreshold.toString()));
|
||||
}
|
||||
|
||||
await Promise.all(autosyncPromises);
|
||||
|
|
@ -875,31 +875,31 @@ export class BackupService {
|
|||
try {
|
||||
// Restore main scraper configurations
|
||||
if (localScrapers.scrapers) {
|
||||
await AsyncStorage.setItem('local-scrapers', JSON.stringify(localScrapers.scrapers));
|
||||
await mmkvStorage.setItem('local-scrapers', JSON.stringify(localScrapers.scrapers));
|
||||
}
|
||||
|
||||
// Restore repository settings
|
||||
if (localScrapers.repositoryUrl) {
|
||||
await AsyncStorage.setItem('scraper-repository-url', localScrapers.repositoryUrl);
|
||||
await mmkvStorage.setItem('scraper-repository-url', localScrapers.repositoryUrl);
|
||||
}
|
||||
|
||||
if (localScrapers.repositories) {
|
||||
await AsyncStorage.setItem('scraper-repositories', JSON.stringify(localScrapers.repositories));
|
||||
await mmkvStorage.setItem('scraper-repositories', JSON.stringify(localScrapers.repositories));
|
||||
}
|
||||
|
||||
if (localScrapers.currentRepository) {
|
||||
await AsyncStorage.setItem('current-repository-id', localScrapers.currentRepository);
|
||||
await mmkvStorage.setItem('current-repository-id', localScrapers.currentRepository);
|
||||
}
|
||||
|
||||
if (localScrapers.scraperSettings) {
|
||||
await AsyncStorage.setItem('scraper-settings', JSON.stringify(localScrapers.scraperSettings));
|
||||
await mmkvStorage.setItem('scraper-settings', JSON.stringify(localScrapers.scraperSettings));
|
||||
}
|
||||
|
||||
// Restore scraper code cache
|
||||
if (localScrapers.scraperCode && typeof localScrapers.scraperCode === 'object') {
|
||||
const codePairs: [string, string][] = Object.entries(localScrapers.scraperCode).map(([key, value]) => [key, value as string]);
|
||||
if (codePairs.length > 0) {
|
||||
await AsyncStorage.multiSet(codePairs);
|
||||
await mmkvStorage.multiSet(codePairs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -914,11 +914,11 @@ export class BackupService {
|
|||
const setPromises: Promise<void>[] = [];
|
||||
|
||||
if (apiKeys.mdblistApiKey) {
|
||||
setPromises.push(AsyncStorage.setItem('mdblist_api_key', apiKeys.mdblistApiKey));
|
||||
setPromises.push(mmkvStorage.setItem('mdblist_api_key', apiKeys.mdblistApiKey));
|
||||
}
|
||||
|
||||
if (apiKeys.openRouterApiKey) {
|
||||
setPromises.push(AsyncStorage.setItem('openrouter_api_key', apiKeys.openRouterApiKey));
|
||||
setPromises.push(mmkvStorage.setItem('openrouter_api_key', apiKeys.openRouterApiKey));
|
||||
}
|
||||
|
||||
await Promise.all(setPromises);
|
||||
|
|
@ -930,7 +930,7 @@ export class BackupService {
|
|||
|
||||
private async restoreCatalogSettings(catalogSettings: any): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem('catalog_settings', JSON.stringify(catalogSettings));
|
||||
await mmkvStorage.setItem('catalog_settings', JSON.stringify(catalogSettings));
|
||||
logger.info('[BackupService] Catalog settings restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore catalog settings:', error);
|
||||
|
|
@ -944,8 +944,8 @@ export class BackupService {
|
|||
|
||||
// Restore to both scoped and legacy keys for compatibility
|
||||
await Promise.all([
|
||||
AsyncStorage.setItem(scopedKey, JSON.stringify(addonOrder)),
|
||||
AsyncStorage.setItem('stremio-addon-order', JSON.stringify(addonOrder))
|
||||
mmkvStorage.setItem(scopedKey, JSON.stringify(addonOrder)),
|
||||
mmkvStorage.setItem('stremio-addon-order', JSON.stringify(addonOrder))
|
||||
]);
|
||||
|
||||
logger.info('[BackupService] Addon order restored');
|
||||
|
|
@ -956,7 +956,7 @@ export class BackupService {
|
|||
|
||||
private async restoreRemovedAddons(removedAddons: string[]): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem('user_removed_addons', JSON.stringify(removedAddons));
|
||||
await mmkvStorage.setItem('user_removed_addons', JSON.stringify(removedAddons));
|
||||
logger.info('[BackupService] Removed addons restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore removed addons:', error);
|
||||
|
|
@ -965,7 +965,7 @@ export class BackupService {
|
|||
|
||||
private async restoreGlobalSeasonViewMode(mode: string): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem('global_season_view_mode', mode);
|
||||
await mmkvStorage.setItem('global_season_view_mode', mode);
|
||||
logger.info('[BackupService] Global season view mode restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore global season view mode:', error);
|
||||
|
|
@ -974,7 +974,7 @@ export class BackupService {
|
|||
|
||||
private async restoreHasCompletedOnboarding(value: boolean): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem('hasCompletedOnboarding', value.toString());
|
||||
await mmkvStorage.setItem('hasCompletedOnboarding', value.toString());
|
||||
logger.info('[BackupService] Has completed onboarding restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore has completed onboarding:', error);
|
||||
|
|
@ -983,7 +983,7 @@ export class BackupService {
|
|||
|
||||
private async restoreShowLoginHintToastOnce(value: boolean): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem('showLoginHintToastOnce', value.toString());
|
||||
await mmkvStorage.setItem('showLoginHintToastOnce', value.toString());
|
||||
logger.info('[BackupService] Show login hint toast once restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore show login hint toast once:', error);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { stremioService, Meta, Manifest } from './stremioService';
|
||||
import { notificationService } from './notificationService';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import axios from 'axios';
|
||||
import { TMDBService } from './tmdbService';
|
||||
import { logger } from '../utils/logger';
|
||||
|
|
@ -164,9 +164,9 @@ class CatalogService {
|
|||
|
||||
private async initializeScope(): Promise<void> {
|
||||
try {
|
||||
const currentScope = await AsyncStorage.getItem('@user:current');
|
||||
const currentScope = await mmkvStorage.getItem('@user:current');
|
||||
if (!currentScope) {
|
||||
await AsyncStorage.setItem('@user:current', 'local');
|
||||
await mmkvStorage.setItem('@user:current', 'local');
|
||||
logger.log('[CatalogService] Initialized @user:current scope to "local"');
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -183,14 +183,14 @@ class CatalogService {
|
|||
|
||||
private async loadLibrary(): Promise<void> {
|
||||
try {
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const scopedKey = `@user:${scope}:stremio-library`;
|
||||
let storedLibrary = (await AsyncStorage.getItem(scopedKey));
|
||||
let storedLibrary = (await mmkvStorage.getItem(scopedKey));
|
||||
if (!storedLibrary) {
|
||||
// Fallback: read legacy and migrate into scoped
|
||||
storedLibrary = await AsyncStorage.getItem(this.LEGACY_LIBRARY_KEY);
|
||||
storedLibrary = await mmkvStorage.getItem(this.LEGACY_LIBRARY_KEY);
|
||||
if (storedLibrary) {
|
||||
await AsyncStorage.setItem(scopedKey, storedLibrary);
|
||||
await mmkvStorage.setItem(scopedKey, storedLibrary);
|
||||
}
|
||||
}
|
||||
if (storedLibrary) {
|
||||
|
|
@ -201,7 +201,7 @@ class CatalogService {
|
|||
this.library = {};
|
||||
}
|
||||
// Ensure @user:current is set to prevent future scope issues
|
||||
await AsyncStorage.setItem('@user:current', scope);
|
||||
await mmkvStorage.setItem('@user:current', scope);
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to load library:', error);
|
||||
this.library = {};
|
||||
|
|
@ -210,11 +210,11 @@ class CatalogService {
|
|||
|
||||
private async saveLibrary(): Promise<void> {
|
||||
try {
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const scopedKey = `@user:${scope}:stremio-library`;
|
||||
const libraryData = JSON.stringify(this.library);
|
||||
await AsyncStorage.setItem(scopedKey, libraryData);
|
||||
await AsyncStorage.setItem(this.LEGACY_LIBRARY_KEY, libraryData);
|
||||
await mmkvStorage.setItem(scopedKey, libraryData);
|
||||
await mmkvStorage.setItem(this.LEGACY_LIBRARY_KEY, libraryData);
|
||||
logger.log(`[CatalogService] Library saved successfully with ${Object.keys(this.library).length} items to scope: ${scope}`);
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to save library:', error);
|
||||
|
|
@ -223,7 +223,7 @@ class CatalogService {
|
|||
|
||||
private async loadRecentContent(): Promise<void> {
|
||||
try {
|
||||
const storedRecentContent = await AsyncStorage.getItem(this.RECENT_CONTENT_KEY);
|
||||
const storedRecentContent = await mmkvStorage.getItem(this.RECENT_CONTENT_KEY);
|
||||
if (storedRecentContent) {
|
||||
this.recentContent = JSON.parse(storedRecentContent);
|
||||
}
|
||||
|
|
@ -234,7 +234,7 @@ class CatalogService {
|
|||
|
||||
private async saveRecentContent(): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem(this.RECENT_CONTENT_KEY, JSON.stringify(this.recentContent));
|
||||
await mmkvStorage.setItem(this.RECENT_CONTENT_KEY, JSON.stringify(this.recentContent));
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to save recent content:', error);
|
||||
}
|
||||
|
|
@ -265,7 +265,7 @@ class CatalogService {
|
|||
const addons = await this.getAllAddons();
|
||||
|
||||
// Load enabled/disabled settings
|
||||
const catalogSettingsJson = await AsyncStorage.getItem(CATALOG_SETTINGS_KEY);
|
||||
const catalogSettingsJson = await mmkvStorage.getItem(CATALOG_SETTINGS_KEY);
|
||||
const catalogSettings = catalogSettingsJson ? JSON.parse(catalogSettingsJson) : {};
|
||||
|
||||
// Create an array of promises for all catalog fetches
|
||||
|
|
@ -546,7 +546,7 @@ class CatalogService {
|
|||
*/
|
||||
async getDataSourcePreference(): Promise<DataSource> {
|
||||
try {
|
||||
const dataSource = await AsyncStorage.getItem(DATA_SOURCE_KEY);
|
||||
const dataSource = await mmkvStorage.getItem(DATA_SOURCE_KEY);
|
||||
return dataSource as DataSource || DataSource.STREMIO_ADDONS;
|
||||
} catch (error) {
|
||||
logger.error('Failed to get data source preference:', error);
|
||||
|
|
@ -559,7 +559,7 @@ class CatalogService {
|
|||
*/
|
||||
async setDataSourcePreference(dataSource: DataSource): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem(DATA_SOURCE_KEY, dataSource);
|
||||
await mmkvStorage.setItem(DATA_SOURCE_KEY, dataSource);
|
||||
} catch (error) {
|
||||
logger.error('Failed to set data source preference:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import axios from 'axios';
|
||||
import { Platform } from 'react-native';
|
||||
import { logger } from '../utils/logger';
|
||||
|
|
@ -101,13 +101,13 @@ class LocalScraperService {
|
|||
|
||||
try {
|
||||
// Load repositories
|
||||
const repositoriesData = await AsyncStorage.getItem(this.REPOSITORIES_KEY);
|
||||
const repositoriesData = await mmkvStorage.getItem(this.REPOSITORIES_KEY);
|
||||
if (repositoriesData) {
|
||||
const repos = JSON.parse(repositoriesData);
|
||||
this.repositories = new Map(Object.entries(repos));
|
||||
} else {
|
||||
// Migrate from old single repository format or create default tapframe repository
|
||||
const storedRepoUrl = await AsyncStorage.getItem(this.REPOSITORY_KEY);
|
||||
const storedRepoUrl = await mmkvStorage.getItem(this.REPOSITORY_KEY);
|
||||
if (storedRepoUrl) {
|
||||
const defaultRepo: RepositoryInfo = {
|
||||
id: 'default',
|
||||
|
|
@ -127,7 +127,7 @@ class LocalScraperService {
|
|||
}
|
||||
|
||||
// Load current repository
|
||||
const currentRepoId = await AsyncStorage.getItem('current-repository-id');
|
||||
const currentRepoId = await mmkvStorage.getItem('current-repository-id');
|
||||
if (currentRepoId && this.repositories.has(currentRepoId)) {
|
||||
this.currentRepositoryId = currentRepoId;
|
||||
const currentRepo = this.repositories.get(currentRepoId)!;
|
||||
|
|
@ -142,7 +142,7 @@ class LocalScraperService {
|
|||
}
|
||||
|
||||
// Load installed scrapers
|
||||
const storedScrapers = await AsyncStorage.getItem(this.STORAGE_KEY);
|
||||
const storedScrapers = await mmkvStorage.getItem(this.STORAGE_KEY);
|
||||
if (storedScrapers) {
|
||||
const scrapers: ScraperInfo[] = JSON.parse(storedScrapers);
|
||||
const validScrapers: ScraperInfo[] = [];
|
||||
|
|
@ -194,14 +194,14 @@ class LocalScraperService {
|
|||
// Save cleaned scrapers back to storage if any were filtered out
|
||||
if (validScrapers.length !== scrapers.length) {
|
||||
logger.log('[LocalScraperService] Cleaned up invalid scrapers, saving valid ones');
|
||||
await AsyncStorage.setItem(this.STORAGE_KEY, JSON.stringify(validScrapers));
|
||||
await mmkvStorage.setItem(this.STORAGE_KEY, JSON.stringify(validScrapers));
|
||||
|
||||
// Clean up cached code for removed scrapers
|
||||
const validScraperIds = new Set(validScrapers.map(s => s.id));
|
||||
const removedScrapers = scrapers.filter(s => s.id && !validScraperIds.has(s.id));
|
||||
for (const removedScraper of removedScrapers) {
|
||||
try {
|
||||
await AsyncStorage.removeItem(`scraper-code-${removedScraper.id}`);
|
||||
await mmkvStorage.removeItem(`scraper-code-${removedScraper.id}`);
|
||||
logger.log('[LocalScraperService] Removed cached code for invalid scraper:', removedScraper.id);
|
||||
} catch (error) {
|
||||
logger.error('[LocalScraperService] Failed to remove cached code for', removedScraper.id, ':', error);
|
||||
|
|
@ -244,7 +244,7 @@ class LocalScraperService {
|
|||
// Set repository URL
|
||||
async setRepositoryUrl(url: string): Promise<void> {
|
||||
this.repositoryUrl = url;
|
||||
await AsyncStorage.setItem(this.REPOSITORY_KEY, url);
|
||||
await mmkvStorage.setItem(this.REPOSITORY_KEY, url);
|
||||
logger.log('[LocalScraperService] Repository URL set to:', url);
|
||||
}
|
||||
|
||||
|
|
@ -328,7 +328,7 @@ class LocalScraperService {
|
|||
} else {
|
||||
// No repositories left, clear current repository
|
||||
this.currentRepositoryId = '';
|
||||
await AsyncStorage.removeItem('current-repository-id');
|
||||
await mmkvStorage.removeItem('current-repository-id');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -340,7 +340,7 @@ class LocalScraperService {
|
|||
for (const scraperId of scrapersToRemove) {
|
||||
this.installedScrapers.delete(scraperId);
|
||||
this.scraperCode.delete(scraperId);
|
||||
await AsyncStorage.removeItem(`scraper-code-${scraperId}`);
|
||||
await mmkvStorage.removeItem(`scraper-code-${scraperId}`);
|
||||
}
|
||||
|
||||
this.repositories.delete(id);
|
||||
|
|
@ -360,7 +360,7 @@ class LocalScraperService {
|
|||
this.repositoryUrl = repo.url;
|
||||
this.repositoryName = repo.name;
|
||||
|
||||
await AsyncStorage.setItem('current-repository-id', id);
|
||||
await mmkvStorage.setItem('current-repository-id', id);
|
||||
|
||||
// Refresh the repository to get its scrapers
|
||||
try {
|
||||
|
|
@ -450,7 +450,7 @@ class LocalScraperService {
|
|||
|
||||
private async saveRepositories(): Promise<void> {
|
||||
const reposObject = Object.fromEntries(this.repositories);
|
||||
await AsyncStorage.setItem(this.REPOSITORIES_KEY, JSON.stringify(reposObject));
|
||||
await mmkvStorage.setItem(this.REPOSITORIES_KEY, JSON.stringify(reposObject));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -504,7 +504,7 @@ class LocalScraperService {
|
|||
const scraper = this.installedScrapers.get(scraperId);
|
||||
if (scraper && scraper.repositoryId === this.currentRepositoryId) {
|
||||
this.scraperCode.delete(scraperId);
|
||||
await AsyncStorage.removeItem(`scraper-code-${scraperId}`);
|
||||
await mmkvStorage.removeItem(`scraper-code-${scraperId}`);
|
||||
logger.log('[LocalScraperService] Cleared cached code for scraper:', scraper.name);
|
||||
}
|
||||
}
|
||||
|
|
@ -551,7 +551,7 @@ class LocalScraperService {
|
|||
this.installedScrapers.delete(scraperId);
|
||||
this.scraperCode.delete(scraperId);
|
||||
// Remove from AsyncStorage cache
|
||||
await AsyncStorage.removeItem(`scraper-code-${scraperId}`);
|
||||
await mmkvStorage.removeItem(`scraper-code-${scraperId}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -571,7 +571,7 @@ class LocalScraperService {
|
|||
logger.log('[LocalScraperService] Removing platform-incompatible scraper:', scraperInfo.name);
|
||||
this.installedScrapers.delete(scraperInfo.id);
|
||||
this.scraperCode.delete(scraperInfo.id);
|
||||
await AsyncStorage.removeItem(`scraper-code-${scraperInfo.id}`);
|
||||
await mmkvStorage.removeItem(`scraper-code-${scraperInfo.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -675,7 +675,7 @@ class LocalScraperService {
|
|||
// Cache scraper code locally
|
||||
private async cacheScraperCode(scraperId: string, code: string): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem(`scraper-code-${scraperId}`, code);
|
||||
await mmkvStorage.setItem(`scraper-code-${scraperId}`, code);
|
||||
} catch (error) {
|
||||
logger.error('[LocalScraperService] Failed to cache scraper code:', error);
|
||||
}
|
||||
|
|
@ -685,7 +685,7 @@ class LocalScraperService {
|
|||
private async loadScraperCode(): Promise<void> {
|
||||
for (const [scraperId] of this.installedScrapers) {
|
||||
try {
|
||||
const cachedCode = await AsyncStorage.getItem(`scraper-code-${scraperId}`);
|
||||
const cachedCode = await mmkvStorage.getItem(`scraper-code-${scraperId}`);
|
||||
if (cachedCode) {
|
||||
this.scraperCode.set(scraperId, cachedCode);
|
||||
}
|
||||
|
|
@ -699,7 +699,7 @@ class LocalScraperService {
|
|||
private async saveInstalledScrapers(): Promise<void> {
|
||||
try {
|
||||
const scrapers = Array.from(this.installedScrapers.values());
|
||||
await AsyncStorage.setItem(this.STORAGE_KEY, JSON.stringify(scrapers));
|
||||
await mmkvStorage.setItem(this.STORAGE_KEY, JSON.stringify(scrapers));
|
||||
} catch (error) {
|
||||
logger.error('[LocalScraperService] Failed to save scrapers:', error);
|
||||
}
|
||||
|
|
@ -716,7 +716,7 @@ class LocalScraperService {
|
|||
await this.ensureInitialized();
|
||||
try {
|
||||
if (!this.scraperSettingsCache) {
|
||||
const raw = await AsyncStorage.getItem(this.SCRAPER_SETTINGS_KEY);
|
||||
const raw = await mmkvStorage.getItem(this.SCRAPER_SETTINGS_KEY);
|
||||
this.scraperSettingsCache = raw ? JSON.parse(raw) : {};
|
||||
}
|
||||
const cache = this.scraperSettingsCache || {};
|
||||
|
|
@ -731,13 +731,13 @@ class LocalScraperService {
|
|||
await this.ensureInitialized();
|
||||
try {
|
||||
if (!this.scraperSettingsCache) {
|
||||
const raw = await AsyncStorage.getItem(this.SCRAPER_SETTINGS_KEY);
|
||||
const raw = await mmkvStorage.getItem(this.SCRAPER_SETTINGS_KEY);
|
||||
this.scraperSettingsCache = raw ? JSON.parse(raw) : {};
|
||||
}
|
||||
const cache = this.scraperSettingsCache || {};
|
||||
cache[scraperId] = settings || {};
|
||||
this.scraperSettingsCache = cache;
|
||||
await AsyncStorage.setItem(this.SCRAPER_SETTINGS_KEY, JSON.stringify(cache));
|
||||
await mmkvStorage.setItem(this.SCRAPER_SETTINGS_KEY, JSON.stringify(cache));
|
||||
} catch (error) {
|
||||
logger.error('[LocalScraperService] Failed to set scraper settings for', scraperId, error);
|
||||
}
|
||||
|
|
@ -1000,12 +1000,12 @@ class LocalScraperService {
|
|||
// This is a simplified sandbox - in production, you'd want more security
|
||||
try {
|
||||
// Get URL validation setting from AsyncStorage
|
||||
const settingsData = await AsyncStorage.getItem('app_settings');
|
||||
const settingsData = await mmkvStorage.getItem('app_settings');
|
||||
const settings = settingsData ? JSON.parse(settingsData) : {};
|
||||
const urlValidationEnabled = settings.enableScraperUrlValidation ?? true;
|
||||
|
||||
// Load per-scraper settings for this run
|
||||
const allScraperSettingsRaw = await AsyncStorage.getItem(this.SCRAPER_SETTINGS_KEY);
|
||||
const allScraperSettingsRaw = await mmkvStorage.getItem(this.SCRAPER_SETTINGS_KEY);
|
||||
const allScraperSettings = allScraperSettingsRaw ? JSON.parse(allScraperSettingsRaw) : {};
|
||||
const perScraperSettings = (params && params.scraperId && allScraperSettings[params.scraperId]) ? allScraperSettings[params.scraperId] : (params?.settings || {});
|
||||
|
||||
|
|
@ -1320,12 +1320,12 @@ class LocalScraperService {
|
|||
this.scraperCode.clear();
|
||||
|
||||
// Clear from storage
|
||||
await AsyncStorage.removeItem(this.STORAGE_KEY);
|
||||
await mmkvStorage.removeItem(this.STORAGE_KEY);
|
||||
|
||||
// Clear cached code
|
||||
const keys = await AsyncStorage.getAllKeys();
|
||||
const keys = await mmkvStorage.getAllKeys();
|
||||
const scraperCodeKeys = keys.filter(key => key.startsWith('scraper-code-'));
|
||||
await AsyncStorage.multiRemove(scraperCodeKeys);
|
||||
await mmkvStorage.multiRemove(scraperCodeKeys);
|
||||
|
||||
logger.log('[LocalScraperService] All scrapers cleared');
|
||||
}
|
||||
|
|
@ -1383,9 +1383,9 @@ class LocalScraperService {
|
|||
}
|
||||
|
||||
// Get user settings from AsyncStorage (scoped with fallback)
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scopedSettingsJson = await AsyncStorage.getItem(`@user:${scope}:app_settings`);
|
||||
const legacySettingsJson = await AsyncStorage.getItem('app_settings');
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const scopedSettingsJson = await mmkvStorage.getItem(`@user:${scope}:app_settings`);
|
||||
const legacySettingsJson = await mmkvStorage.getItem('app_settings');
|
||||
const settingsData = scopedSettingsJson || legacySettingsJson;
|
||||
const settings = settingsData ? JSON.parse(settingsData) : {};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import { logger } from '../utils/logger';
|
||||
import {
|
||||
MDBLIST_API_KEY_STORAGE_KEY,
|
||||
|
|
@ -38,7 +38,7 @@ export class MDBListService {
|
|||
async initialize(): Promise<void> {
|
||||
try {
|
||||
// First check if MDBList is enabled
|
||||
const enabledSetting = await AsyncStorage.getItem(MDBLIST_ENABLED_STORAGE_KEY);
|
||||
const enabledSetting = await mmkvStorage.getItem(MDBLIST_ENABLED_STORAGE_KEY);
|
||||
const wasEnabled = this.enabled;
|
||||
this.enabled = enabledSetting === null || enabledSetting === 'true';
|
||||
logger.log('[MDBListService] MDBList enabled:', this.enabled);
|
||||
|
|
@ -55,7 +55,7 @@ export class MDBListService {
|
|||
return;
|
||||
}
|
||||
|
||||
const newApiKey = await AsyncStorage.getItem(MDBLIST_API_KEY_STORAGE_KEY);
|
||||
const newApiKey = await mmkvStorage.getItem(MDBLIST_API_KEY_STORAGE_KEY);
|
||||
// Reset error counter when API key changes
|
||||
if (newApiKey !== this.apiKey) {
|
||||
this.apiKeyErrorCount = 0;
|
||||
|
|
@ -91,7 +91,7 @@ export class MDBListService {
|
|||
if (!this.enabled) {
|
||||
// Try to refresh enabled status in case it was changed
|
||||
try {
|
||||
const enabledSetting = await AsyncStorage.getItem(MDBLIST_ENABLED_STORAGE_KEY);
|
||||
const enabledSetting = await mmkvStorage.getItem(MDBLIST_ENABLED_STORAGE_KEY);
|
||||
this.enabled = enabledSetting === null || enabledSetting === 'true';
|
||||
} catch (error) {
|
||||
// Ignore error and keep current state
|
||||
|
|
|
|||
138
src/services/mmkvStorage.ts
Normal file
138
src/services/mmkvStorage.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import { createMMKV } from 'react-native-mmkv';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
class MMKVStorage {
|
||||
private static instance: MMKVStorage;
|
||||
private storage = createMMKV();
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): MMKVStorage {
|
||||
if (!MMKVStorage.instance) {
|
||||
MMKVStorage.instance = new MMKVStorage();
|
||||
}
|
||||
return MMKVStorage.instance;
|
||||
}
|
||||
|
||||
// AsyncStorage-compatible API
|
||||
async getItem(key: string): Promise<string | null> {
|
||||
try {
|
||||
const value = this.storage.getString(key);
|
||||
return value ?? null;
|
||||
} catch (error) {
|
||||
logger.error(`[MMKVStorage] Error getting item ${key}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async setItem(key: string, value: string): Promise<void> {
|
||||
try {
|
||||
this.storage.set(key, value);
|
||||
} catch (error) {
|
||||
logger.error(`[MMKVStorage] Error setting item ${key}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async removeItem(key: string): Promise<void> {
|
||||
try {
|
||||
// MMKV V4 uses 'remove' method, not 'delete'
|
||||
if (this.storage.contains(key)) {
|
||||
this.storage.remove(key);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[MMKVStorage] Error removing item ${key}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllKeys(): Promise<string[]> {
|
||||
try {
|
||||
const keys = this.storage.getAllKeys();
|
||||
return Array.from(keys) as string[];
|
||||
} catch (error) {
|
||||
logger.error('[MMKVStorage] Error getting all keys:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async multiGet(keys: string[]): Promise<[string, string | null][]> {
|
||||
try {
|
||||
const results: [string, string | null][] = [];
|
||||
for (const key of keys) {
|
||||
const value = this.storage.getString(key);
|
||||
results.push([key, value ?? null]);
|
||||
}
|
||||
return results;
|
||||
} catch (error) {
|
||||
logger.error('[MMKVStorage] Error in multiGet:', error);
|
||||
return keys.map(key => [key, null] as [string, string | null]);
|
||||
}
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
try {
|
||||
this.storage.clearAll();
|
||||
} catch (error) {
|
||||
logger.error('[MMKVStorage] Error clearing storage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Direct MMKV access methods (for performance-critical operations)
|
||||
getString(key: string): string | undefined {
|
||||
return this.storage.getString(key);
|
||||
}
|
||||
|
||||
setString(key: string, value: string): void {
|
||||
this.storage.set(key, value);
|
||||
}
|
||||
|
||||
getNumber(key: string): number | undefined {
|
||||
return this.storage.getNumber(key);
|
||||
}
|
||||
|
||||
setNumber(key: string, value: number): void {
|
||||
this.storage.set(key, value);
|
||||
}
|
||||
|
||||
getBoolean(key: string): boolean | undefined {
|
||||
return this.storage.getBoolean(key);
|
||||
}
|
||||
|
||||
setBoolean(key: string, value: boolean): void {
|
||||
this.storage.set(key, value);
|
||||
}
|
||||
|
||||
contains(key: string): boolean {
|
||||
return this.storage.contains(key);
|
||||
}
|
||||
|
||||
delete(key: string): void {
|
||||
if (this.storage.contains(key)) {
|
||||
this.storage.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Additional AsyncStorage-compatible methods
|
||||
async multiSet(keyValuePairs: [string, string][]): Promise<void> {
|
||||
try {
|
||||
for (const [key, value] of keyValuePairs) {
|
||||
this.storage.set(key, value);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[MMKVStorage] Error in multiSet:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async multiRemove(keys: string[]): Promise<void> {
|
||||
try {
|
||||
for (const key of keys) {
|
||||
if (this.storage.contains(key)) {
|
||||
this.storage.remove(key);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[MMKVStorage] Error in multiRemove:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const mmkvStorage = MMKVStorage.getInstance();
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import * as Notifications from 'expo-notifications';
|
||||
import { Platform, AppState, AppStateStatus } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import { parseISO, differenceInHours, isToday, addDays, isAfter, startOfToday } from 'date-fns';
|
||||
import { stremioService } from './stremioService';
|
||||
import { catalogService } from './catalogService';
|
||||
|
|
@ -101,7 +101,7 @@ class NotificationService {
|
|||
|
||||
private async loadSettings(): Promise<void> {
|
||||
try {
|
||||
const storedSettings = await AsyncStorage.getItem(NOTIFICATION_SETTINGS_KEY);
|
||||
const storedSettings = await mmkvStorage.getItem(NOTIFICATION_SETTINGS_KEY);
|
||||
|
||||
if (storedSettings) {
|
||||
this.settings = { ...DEFAULT_NOTIFICATION_SETTINGS, ...JSON.parse(storedSettings) };
|
||||
|
|
@ -113,7 +113,7 @@ class NotificationService {
|
|||
|
||||
private async saveSettings(): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem(NOTIFICATION_SETTINGS_KEY, JSON.stringify(this.settings));
|
||||
await mmkvStorage.setItem(NOTIFICATION_SETTINGS_KEY, JSON.stringify(this.settings));
|
||||
} catch (error) {
|
||||
logger.error('Error saving notification settings:', error);
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ class NotificationService {
|
|||
|
||||
private async loadScheduledNotifications(): Promise<void> {
|
||||
try {
|
||||
const storedNotifications = await AsyncStorage.getItem(NOTIFICATION_STORAGE_KEY);
|
||||
const storedNotifications = await mmkvStorage.getItem(NOTIFICATION_STORAGE_KEY);
|
||||
|
||||
if (storedNotifications) {
|
||||
this.scheduledNotifications = JSON.parse(storedNotifications);
|
||||
|
|
@ -133,7 +133,7 @@ class NotificationService {
|
|||
|
||||
private async saveScheduledNotifications(): Promise<void> {
|
||||
try {
|
||||
await AsyncStorage.setItem(NOTIFICATION_STORAGE_KEY, JSON.stringify(this.scheduledNotifications));
|
||||
await mmkvStorage.setItem(NOTIFICATION_STORAGE_KEY, JSON.stringify(this.scheduledNotifications));
|
||||
} catch (error) {
|
||||
logger.error('Error saving scheduled notifications:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
// Define the structure of cached data
|
||||
|
|
@ -32,7 +32,7 @@ class RobustCalendarCache {
|
|||
|
||||
private async getCachedData<T>(key: string, libraryItems: any[], traktCollections: TraktCollections): Promise<T | null> {
|
||||
try {
|
||||
const storedCache = await AsyncStorage.getItem(key);
|
||||
const storedCache = await mmkvStorage.getItem(key);
|
||||
if (!storedCache) return null;
|
||||
|
||||
const cache: CachedData<T> = JSON.parse(storedCache);
|
||||
|
|
@ -73,7 +73,7 @@ class RobustCalendarCache {
|
|||
logger.log(`[Cache] Saving successful data to cache for key ${key}`);
|
||||
}
|
||||
|
||||
await AsyncStorage.setItem(key, JSON.stringify(cache));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(cache));
|
||||
} catch (error) {
|
||||
logger.error(`[Cache] Error setting cached data for key ${key}:`, error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
interface WatchProgress {
|
||||
|
|
@ -35,7 +35,7 @@ class StorageService {
|
|||
|
||||
private async getUserScope(): Promise<string> {
|
||||
try {
|
||||
const scope = await AsyncStorage.getItem('@user:current');
|
||||
const scope = await mmkvStorage.getItem('@user:current');
|
||||
return scope || 'local';
|
||||
} catch {
|
||||
return 'local';
|
||||
|
|
@ -79,10 +79,10 @@ class StorageService {
|
|||
): Promise<void> {
|
||||
try {
|
||||
const key = await this.getTombstonesKeyScoped();
|
||||
const json = (await AsyncStorage.getItem(key)) || '{}';
|
||||
const json = (await mmkvStorage.getItem(key)) || '{}';
|
||||
const map = JSON.parse(json) as Record<string, number>;
|
||||
map[this.buildWpKeyString(id, type, episodeId)] = deletedAtMs || Date.now();
|
||||
await AsyncStorage.setItem(key, JSON.stringify(map));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(map));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
|
@ -93,12 +93,12 @@ class StorageService {
|
|||
): Promise<void> {
|
||||
try {
|
||||
const key = await this.getTombstonesKeyScoped();
|
||||
const json = (await AsyncStorage.getItem(key)) || '{}';
|
||||
const json = (await mmkvStorage.getItem(key)) || '{}';
|
||||
const map = JSON.parse(json) as Record<string, number>;
|
||||
const k = this.buildWpKeyString(id, type, episodeId);
|
||||
if (map[k] != null) {
|
||||
delete map[k];
|
||||
await AsyncStorage.setItem(key, JSON.stringify(map));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(map));
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
|
@ -106,7 +106,7 @@ class StorageService {
|
|||
public async getWatchProgressTombstones(): Promise<Record<string, number>> {
|
||||
try {
|
||||
const key = await this.getTombstonesKeyScoped();
|
||||
const json = (await AsyncStorage.getItem(key)) || '{}';
|
||||
const json = (await mmkvStorage.getItem(key)) || '{}';
|
||||
return JSON.parse(json) as Record<string, number>;
|
||||
} catch {
|
||||
return {};
|
||||
|
|
@ -120,10 +120,10 @@ class StorageService {
|
|||
): Promise<void> {
|
||||
try {
|
||||
const key = await this.getContinueWatchingRemovedKeyScoped();
|
||||
const json = (await AsyncStorage.getItem(key)) || '{}';
|
||||
const json = (await mmkvStorage.getItem(key)) || '{}';
|
||||
const map = JSON.parse(json) as Record<string, number>;
|
||||
map[this.buildWpKeyString(id, type)] = removedAtMs || Date.now();
|
||||
await AsyncStorage.setItem(key, JSON.stringify(map));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(map));
|
||||
} catch (error) {
|
||||
logger.error('Error adding continue watching removed item:', error);
|
||||
}
|
||||
|
|
@ -135,12 +135,12 @@ class StorageService {
|
|||
): Promise<void> {
|
||||
try {
|
||||
const key = await this.getContinueWatchingRemovedKeyScoped();
|
||||
const json = (await AsyncStorage.getItem(key)) || '{}';
|
||||
const json = (await mmkvStorage.getItem(key)) || '{}';
|
||||
const map = JSON.parse(json) as Record<string, number>;
|
||||
const k = this.buildWpKeyString(id, type);
|
||||
if (map[k] != null) {
|
||||
delete map[k];
|
||||
await AsyncStorage.setItem(key, JSON.stringify(map));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(map));
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error removing continue watching removed item:', error);
|
||||
|
|
@ -150,7 +150,7 @@ class StorageService {
|
|||
public async getContinueWatchingRemoved(): Promise<Record<string, number>> {
|
||||
try {
|
||||
const key = await this.getContinueWatchingRemovedKeyScoped();
|
||||
const json = (await AsyncStorage.getItem(key)) || '{}';
|
||||
const json = (await mmkvStorage.getItem(key)) || '{}';
|
||||
return JSON.parse(json) as Record<string, number>;
|
||||
} catch (error) {
|
||||
logger.error('Error getting continue watching removed items:', error);
|
||||
|
|
@ -176,7 +176,7 @@ class StorageService {
|
|||
): Promise<void> {
|
||||
try {
|
||||
const key = await this.getContentDurationKeyScoped(id, type, episodeId);
|
||||
await AsyncStorage.setItem(key, duration.toString());
|
||||
await mmkvStorage.setItem(key, duration.toString());
|
||||
} catch (error) {
|
||||
logger.error('Error setting content duration:', error);
|
||||
}
|
||||
|
|
@ -189,7 +189,7 @@ class StorageService {
|
|||
): Promise<number | null> {
|
||||
try {
|
||||
const key = await this.getContentDurationKeyScoped(id, type, episodeId);
|
||||
const data = await AsyncStorage.getItem(key);
|
||||
const data = await mmkvStorage.getItem(key);
|
||||
return data ? parseFloat(data) : null;
|
||||
} catch (error) {
|
||||
logger.error('Error getting content duration:', error);
|
||||
|
|
@ -262,7 +262,7 @@ class StorageService {
|
|||
? progress.lastUpdated
|
||||
: Date.now();
|
||||
const updated = { ...progress, lastUpdated: timestamp };
|
||||
await AsyncStorage.setItem(key, JSON.stringify(updated));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(updated));
|
||||
|
||||
// Notify subscribers; allow forcing immediate notification
|
||||
if (options?.forceNotify) {
|
||||
|
|
@ -332,7 +332,7 @@ class StorageService {
|
|||
): Promise<WatchProgress | null> {
|
||||
try {
|
||||
const key = await this.getWatchProgressKeyScoped(id, type, episodeId);
|
||||
const data = await AsyncStorage.getItem(key);
|
||||
const data = await mmkvStorage.getItem(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
logger.error('Error getting watch progress:', error);
|
||||
|
|
@ -347,7 +347,7 @@ class StorageService {
|
|||
): Promise<void> {
|
||||
try {
|
||||
const key = await this.getWatchProgressKeyScoped(id, type, episodeId);
|
||||
await AsyncStorage.removeItem(key);
|
||||
await mmkvStorage.removeItem(key);
|
||||
await this.addWatchProgressTombstone(id, type, episodeId);
|
||||
// Notify subscribers
|
||||
this.notifyWatchProgressSubscribers();
|
||||
|
|
@ -362,9 +362,9 @@ class StorageService {
|
|||
try {
|
||||
const scope = await this.getUserScope();
|
||||
const prefix = `@user:${scope}:${this.WATCH_PROGRESS_KEY}`;
|
||||
const keys = await AsyncStorage.getAllKeys();
|
||||
const keys = await mmkvStorage.getAllKeys();
|
||||
const watchProgressKeys = keys.filter(key => key.startsWith(prefix));
|
||||
const pairs = await AsyncStorage.multiGet(watchProgressKeys);
|
||||
const pairs = await mmkvStorage.multiGet(watchProgressKeys);
|
||||
return pairs.reduce((acc, [key, value]) => {
|
||||
if (value) {
|
||||
acc[key.replace(prefix, '')] = JSON.parse(value);
|
||||
|
|
@ -644,7 +644,7 @@ class StorageService {
|
|||
public async saveSubtitleSettings(settings: Record<string, any>): Promise<void> {
|
||||
try {
|
||||
const key = await this.getSubtitleSettingsKeyScoped();
|
||||
await AsyncStorage.setItem(key, JSON.stringify(settings));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(settings));
|
||||
} catch (error) {
|
||||
logger.error('Error saving subtitle settings:', error);
|
||||
}
|
||||
|
|
@ -653,7 +653,7 @@ class StorageService {
|
|||
public async getSubtitleSettings(): Promise<Record<string, any> | null> {
|
||||
try {
|
||||
const key = await this.getSubtitleSettingsKeyScoped();
|
||||
const data = await AsyncStorage.getItem(key);
|
||||
const data = await mmkvStorage.getItem(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
logger.error('Error loading subtitle settings:', error);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
export interface CachedStream {
|
||||
|
|
@ -59,7 +59,7 @@ class StreamCacheService {
|
|||
expiresAt: now + ttl
|
||||
};
|
||||
|
||||
await AsyncStorage.setItem(cacheKey, JSON.stringify(cacheEntry));
|
||||
await mmkvStorage.setItem(cacheKey, JSON.stringify(cacheEntry));
|
||||
logger.log(`💾 [StreamCache] Saved stream cache for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
|
||||
logger.log(`💾 [StreamCache] Cache key: ${cacheKey}`);
|
||||
logger.log(`💾 [StreamCache] Stream URL: ${stream.url}`);
|
||||
|
|
@ -78,7 +78,7 @@ class StreamCacheService {
|
|||
const cacheKey = this.getCacheKey(id, type, episodeId);
|
||||
logger.log(`🔍 [StreamCache] Looking for cached stream with key: ${cacheKey}`);
|
||||
|
||||
const cachedData = await AsyncStorage.getItem(cacheKey);
|
||||
const cachedData = await mmkvStorage.getItem(cacheKey);
|
||||
|
||||
if (!cachedData) {
|
||||
logger.log(`❌ [StreamCache] No cached data found for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
|
||||
|
|
@ -116,7 +116,7 @@ class StreamCacheService {
|
|||
async removeCachedStream(id: string, type: string, episodeId?: string): Promise<void> {
|
||||
try {
|
||||
const cacheKey = this.getCacheKey(id, type, episodeId);
|
||||
await AsyncStorage.removeItem(cacheKey);
|
||||
await mmkvStorage.removeItem(cacheKey);
|
||||
logger.log(`🗑️ [StreamCache] Removed cached stream for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
|
||||
} catch (error) {
|
||||
logger.warn('[StreamCache] Failed to remove cached stream:', error);
|
||||
|
|
@ -128,11 +128,11 @@ class StreamCacheService {
|
|||
*/
|
||||
async clearAllCachedStreams(): Promise<void> {
|
||||
try {
|
||||
const allKeys = await AsyncStorage.getAllKeys();
|
||||
const allKeys = await mmkvStorage.getAllKeys();
|
||||
const cacheKeys = allKeys.filter(key => key.startsWith(CACHE_KEY_PREFIX));
|
||||
|
||||
for (const key of cacheKeys) {
|
||||
await AsyncStorage.removeItem(key);
|
||||
await mmkvStorage.removeItem(key);
|
||||
}
|
||||
|
||||
logger.log(`🧹 [StreamCache] Cleared ${cacheKeys.length} cached streams`);
|
||||
|
|
@ -174,7 +174,7 @@ class StreamCacheService {
|
|||
*/
|
||||
async getCacheInfo(): Promise<{ totalCached: number; expiredCount: number; validCount: number }> {
|
||||
try {
|
||||
const allKeys = await AsyncStorage.getAllKeys();
|
||||
const allKeys = await mmkvStorage.getAllKeys();
|
||||
const cacheKeys = allKeys.filter((key: string) => key.startsWith(CACHE_KEY_PREFIX));
|
||||
|
||||
let expiredCount = 0;
|
||||
|
|
@ -183,7 +183,7 @@ class StreamCacheService {
|
|||
|
||||
for (const key of cacheKeys) {
|
||||
try {
|
||||
const cachedData = await AsyncStorage.getItem(key);
|
||||
const cachedData = await mmkvStorage.getItem(key);
|
||||
if (cachedData) {
|
||||
const cacheEntry: StreamCacheEntry = JSON.parse(cachedData);
|
||||
if (now > cacheEntry.expiresAt) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import axios from 'axios';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import { logger } from '../utils/logger';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { localScraperService } from './localScraperService';
|
||||
|
|
@ -321,11 +321,11 @@ class StremioService {
|
|||
if (this.initialized) return;
|
||||
|
||||
try {
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
// Prefer scoped storage, but fall back to legacy keys to preserve older installs
|
||||
let storedAddons = await AsyncStorage.getItem(`@user:${scope}:${this.STORAGE_KEY}`);
|
||||
if (!storedAddons) storedAddons = await AsyncStorage.getItem(this.STORAGE_KEY);
|
||||
if (!storedAddons) storedAddons = await AsyncStorage.getItem(`@user:local:${this.STORAGE_KEY}`);
|
||||
let storedAddons = await mmkvStorage.getItem(`@user:${scope}:${this.STORAGE_KEY}`);
|
||||
if (!storedAddons) storedAddons = await mmkvStorage.getItem(this.STORAGE_KEY);
|
||||
if (!storedAddons) storedAddons = await mmkvStorage.getItem(`@user:local:${this.STORAGE_KEY}`);
|
||||
|
||||
if (storedAddons) {
|
||||
const parsed = JSON.parse(storedAddons);
|
||||
|
|
@ -425,9 +425,9 @@ class StremioService {
|
|||
}
|
||||
|
||||
// Load addon order if exists (scoped first, then legacy, then @user:local for migration safety)
|
||||
let storedOrder = await AsyncStorage.getItem(`@user:${scope}:${this.ADDON_ORDER_KEY}`);
|
||||
if (!storedOrder) storedOrder = await AsyncStorage.getItem(this.ADDON_ORDER_KEY);
|
||||
if (!storedOrder) storedOrder = await AsyncStorage.getItem(`@user:local:${this.ADDON_ORDER_KEY}`);
|
||||
let storedOrder = await mmkvStorage.getItem(`@user:${scope}:${this.ADDON_ORDER_KEY}`);
|
||||
if (!storedOrder) storedOrder = await mmkvStorage.getItem(this.ADDON_ORDER_KEY);
|
||||
if (!storedOrder) storedOrder = await mmkvStorage.getItem(`@user:local:${this.ADDON_ORDER_KEY}`);
|
||||
if (storedOrder) {
|
||||
this.addonOrder = JSON.parse(storedOrder);
|
||||
// Filter out any ids that aren't in installedAddons
|
||||
|
|
@ -507,11 +507,11 @@ class StremioService {
|
|||
private async saveInstalledAddons(): Promise<void> {
|
||||
try {
|
||||
const addonsArray = Array.from(this.installedAddons.values());
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
// Write to both scoped and legacy keys for compatibility
|
||||
await Promise.all([
|
||||
AsyncStorage.setItem(`@user:${scope}:${this.STORAGE_KEY}`, JSON.stringify(addonsArray)),
|
||||
AsyncStorage.setItem(this.STORAGE_KEY, JSON.stringify(addonsArray)),
|
||||
mmkvStorage.setItem(`@user:${scope}:${this.STORAGE_KEY}`, JSON.stringify(addonsArray)),
|
||||
mmkvStorage.setItem(this.STORAGE_KEY, JSON.stringify(addonsArray)),
|
||||
]);
|
||||
} catch (error) {
|
||||
// Continue even if save fails
|
||||
|
|
@ -520,11 +520,11 @@ class StremioService {
|
|||
|
||||
private async saveAddonOrder(): Promise<void> {
|
||||
try {
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
// Write to both scoped and legacy keys for compatibility
|
||||
await Promise.all([
|
||||
AsyncStorage.setItem(`@user:${scope}:${this.ADDON_ORDER_KEY}`, JSON.stringify(this.addonOrder)),
|
||||
AsyncStorage.setItem(this.ADDON_ORDER_KEY, JSON.stringify(this.addonOrder)),
|
||||
mmkvStorage.setItem(`@user:${scope}:${this.ADDON_ORDER_KEY}`, JSON.stringify(this.addonOrder)),
|
||||
mmkvStorage.setItem(this.ADDON_ORDER_KEY, JSON.stringify(this.addonOrder)),
|
||||
]);
|
||||
} catch (error) {
|
||||
// Continue even if save fails
|
||||
|
|
@ -625,7 +625,7 @@ class StremioService {
|
|||
// Check if user has explicitly removed an addon
|
||||
async hasUserRemovedAddon(addonId: string): Promise<boolean> {
|
||||
try {
|
||||
const removedAddons = await AsyncStorage.getItem('user_removed_addons');
|
||||
const removedAddons = await mmkvStorage.getItem('user_removed_addons');
|
||||
if (!removedAddons) return false;
|
||||
const removedList = JSON.parse(removedAddons);
|
||||
return Array.isArray(removedList) && removedList.includes(addonId);
|
||||
|
|
@ -637,13 +637,13 @@ class StremioService {
|
|||
// Mark an addon as removed by user
|
||||
private async markAddonAsRemovedByUser(addonId: string): Promise<void> {
|
||||
try {
|
||||
const removedAddons = await AsyncStorage.getItem('user_removed_addons');
|
||||
const removedAddons = await mmkvStorage.getItem('user_removed_addons');
|
||||
let removedList = removedAddons ? JSON.parse(removedAddons) : [];
|
||||
if (!Array.isArray(removedList)) removedList = [];
|
||||
|
||||
if (!removedList.includes(addonId)) {
|
||||
removedList.push(addonId);
|
||||
await AsyncStorage.setItem('user_removed_addons', JSON.stringify(removedList));
|
||||
await mmkvStorage.setItem('user_removed_addons', JSON.stringify(removedList));
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail - this is not critical functionality
|
||||
|
|
@ -653,14 +653,14 @@ class StremioService {
|
|||
// Remove an addon from the user removed list (allows reinstallation)
|
||||
async unmarkAddonAsRemovedByUser(addonId: string): Promise<void> {
|
||||
try {
|
||||
const removedAddons = await AsyncStorage.getItem('user_removed_addons');
|
||||
const removedAddons = await mmkvStorage.getItem('user_removed_addons');
|
||||
if (!removedAddons) return;
|
||||
|
||||
let removedList = JSON.parse(removedAddons);
|
||||
if (!Array.isArray(removedList)) return;
|
||||
|
||||
const updatedList = removedList.filter(id => id !== addonId);
|
||||
await AsyncStorage.setItem('user_removed_addons', JSON.stringify(updatedList));
|
||||
await mmkvStorage.setItem('user_removed_addons', JSON.stringify(updatedList));
|
||||
} catch (error) {
|
||||
// Silently fail - this is not critical functionality
|
||||
}
|
||||
|
|
@ -669,7 +669,7 @@ class StremioService {
|
|||
// Clean up removed addon from all storage locations
|
||||
private async cleanupRemovedAddonFromStorage(addonId: string): Promise<void> {
|
||||
try {
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
|
||||
// Remove from all possible addon order storage keys
|
||||
const keys = [
|
||||
|
|
@ -679,12 +679,12 @@ class StremioService {
|
|||
];
|
||||
|
||||
for (const key of keys) {
|
||||
const storedOrder = await AsyncStorage.getItem(key);
|
||||
const storedOrder = await mmkvStorage.getItem(key);
|
||||
if (storedOrder) {
|
||||
const order = JSON.parse(storedOrder);
|
||||
if (Array.isArray(order)) {
|
||||
const updatedOrder = order.filter(id => id !== addonId);
|
||||
await AsyncStorage.setItem(key, JSON.stringify(updatedOrder));
|
||||
await mmkvStorage.setItem(key, JSON.stringify(updatedOrder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1054,9 +1054,9 @@ class StremioService {
|
|||
// Check if local scrapers are enabled and execute them first
|
||||
try {
|
||||
// Load settings from AsyncStorage directly (scoped with fallback)
|
||||
const scope = (await AsyncStorage.getItem('@user:current')) || 'local';
|
||||
const settingsJson = (await AsyncStorage.getItem(`@user:${scope}:app_settings`))
|
||||
|| (await AsyncStorage.getItem('app_settings'));
|
||||
const scope = (await mmkvStorage.getItem('@user:current')) || 'local';
|
||||
const settingsJson = (await mmkvStorage.getItem(`@user:${scope}:app_settings`))
|
||||
|| (await mmkvStorage.getItem('app_settings'));
|
||||
const rawSettings = settingsJson ? JSON.parse(settingsJson) : {};
|
||||
const settings: AppSettings = { ...DEFAULT_SETTINGS, ...rawSettings };
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import axios from 'axios';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
|
||||
// TMDB API configuration
|
||||
const DEFAULT_API_KEY = 'd131017ccc6e5462a81c9304d21476de';
|
||||
|
|
@ -124,8 +124,8 @@ export class TMDBService {
|
|||
private async loadApiKey() {
|
||||
try {
|
||||
const [savedKey, savedUseCustomKey] = await Promise.all([
|
||||
AsyncStorage.getItem(TMDB_API_KEY_STORAGE_KEY),
|
||||
AsyncStorage.getItem(USE_CUSTOM_TMDB_API_KEY)
|
||||
mmkvStorage.getItem(TMDB_API_KEY_STORAGE_KEY),
|
||||
mmkvStorage.getItem(USE_CUSTOM_TMDB_API_KEY)
|
||||
]);
|
||||
|
||||
this.useCustomKey = savedUseCustomKey === 'true';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from './mmkvStorage';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
|
|
@ -599,7 +599,7 @@ export class TraktService {
|
|||
*/
|
||||
private async loadCompletionThreshold(): Promise<void> {
|
||||
try {
|
||||
const thresholdStr = await AsyncStorage.getItem('@trakt_completion_threshold');
|
||||
const thresholdStr = await mmkvStorage.getItem('@trakt_completion_threshold');
|
||||
if (thresholdStr) {
|
||||
const threshold = parseInt(thresholdStr, 10);
|
||||
if (!isNaN(threshold) && threshold >= 50 && threshold <= 100) {
|
||||
|
|
@ -681,9 +681,9 @@ export class TraktService {
|
|||
|
||||
try {
|
||||
const [accessToken, refreshToken, tokenExpiry] = await Promise.all([
|
||||
AsyncStorage.getItem(TRAKT_ACCESS_TOKEN_KEY),
|
||||
AsyncStorage.getItem(TRAKT_REFRESH_TOKEN_KEY),
|
||||
AsyncStorage.getItem(TRAKT_TOKEN_EXPIRY_KEY)
|
||||
mmkvStorage.getItem(TRAKT_ACCESS_TOKEN_KEY),
|
||||
mmkvStorage.getItem(TRAKT_REFRESH_TOKEN_KEY),
|
||||
mmkvStorage.getItem(TRAKT_TOKEN_EXPIRY_KEY)
|
||||
]);
|
||||
|
||||
this.accessToken = accessToken;
|
||||
|
|
@ -810,7 +810,7 @@ export class TraktService {
|
|||
this.tokenExpiry = Date.now() + (expiresIn * 1000);
|
||||
|
||||
try {
|
||||
await AsyncStorage.multiSet([
|
||||
await mmkvStorage.multiSet([
|
||||
[TRAKT_ACCESS_TOKEN_KEY, accessToken],
|
||||
[TRAKT_REFRESH_TOKEN_KEY, refreshToken],
|
||||
[TRAKT_TOKEN_EXPIRY_KEY, this.tokenExpiry.toString()]
|
||||
|
|
@ -833,7 +833,7 @@ export class TraktService {
|
|||
this.refreshToken = null;
|
||||
this.tokenExpiry = 0;
|
||||
|
||||
await AsyncStorage.multiRemove([
|
||||
await mmkvStorage.multiRemove([
|
||||
TRAKT_ACCESS_TOKEN_KEY,
|
||||
TRAKT_REFRESH_TOKEN_KEY,
|
||||
TRAKT_TOKEN_EXPIRY_KEY
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { mmkvStorage } from '../services/mmkvStorage';
|
||||
import { logger } from './logger';
|
||||
|
||||
const CATALOG_CUSTOM_NAMES_KEY = 'catalog_custom_names';
|
||||
|
|
@ -17,7 +17,7 @@ async function loadCustomNamesIfNeeded(): Promise<{ [key: string]: string }> {
|
|||
|
||||
try {
|
||||
logger.info('Loading custom catalog names from storage...');
|
||||
const savedCustomNamesJson = await AsyncStorage.getItem(CATALOG_CUSTOM_NAMES_KEY);
|
||||
const savedCustomNamesJson = await mmkvStorage.getItem(CATALOG_CUSTOM_NAMES_KEY);
|
||||
// Assign parsed object or empty object if null/error
|
||||
customNamesCache = savedCustomNamesJson ? JSON.parse(savedCustomNamesJson) : {};
|
||||
cacheTimestamp = now; // Set timestamp only on successful load
|
||||
|
|
|
|||
Loading…
Reference in a new issue