diff --git a/app/(auth)/(tabs)/(home)/downloads.tsx b/app/(auth)/(tabs)/(home)/downloads.tsx
index ab61ad8d..02109e99 100644
--- a/app/(auth)/(tabs)/(home)/downloads.tsx
+++ b/app/(auth)/(tabs)/(home)/downloads.tsx
@@ -6,32 +6,42 @@ import { DownloadedItem, useDownload } from "@/providers/DownloadProvider";
import { queueAtom } from "@/utils/atoms/queue";
import { useSettings } from "@/utils/atoms/settings";
import { Ionicons } from "@expo/vector-icons";
-import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
-import { router } from "expo-router";
+import { useRouter } from "expo-router";
import { useAtom } from "jotai";
import { useMemo } from "react";
-import { ScrollView, TouchableOpacity, View } from "react-native";
+import { Alert, ScrollView, TouchableOpacity, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
-const downloads: React.FC = () => {
+export default function page() {
const [queue, setQueue] = useAtom(queueAtom);
const { removeProcess, downloadedFiles } = useDownload();
-
+ const router = useRouter();
const [settings] = useSettings();
- const movies = useMemo(
- () => downloadedFiles?.filter((f) => f.item.Type === "Movie") || [],
- [downloadedFiles]
- );
+ const movies = useMemo(() => {
+ try {
+ return downloadedFiles?.filter((f) => f.item.Type === "Movie") || [];
+ } catch {
+ migration_20241124();
+ return [];
+ }
+ }, [downloadedFiles]);
const groupedBySeries = useMemo(() => {
- const episodes = downloadedFiles?.filter((f) => f.item.Type === "Episode");
- const series: { [key: string]: DownloadedItem[] } = {};
- episodes?.forEach((e) => {
- if (!series[e.item.SeriesName!]) series[e.item.SeriesName!] = [];
- series[e.item.SeriesName!].push(e);
- });
- return Object.values(series);
+ try {
+ const episodes = downloadedFiles?.filter(
+ (f) => f.item.Type === "Episode"
+ );
+ const series: { [key: string]: DownloadedItem[] } = {};
+ episodes?.forEach((e) => {
+ if (!series[e.item.SeriesName!]) series[e.item.SeriesName!] = [];
+ series[e.item.SeriesName!].push(e);
+ });
+ return Object.values(series);
+ } catch {
+ migration_20241124();
+ return [];
+ }
}, [downloadedFiles]);
const insets = useSafeAreaInsets();
@@ -121,6 +131,24 @@ const downloads: React.FC = () => {
);
-};
+}
-export default downloads;
+function migration_20241124() {
+ const router = useRouter();
+ const { deleteAllFiles } = useDownload();
+ Alert.alert(
+ "New app version requires re-download",
+ "The new update reqires content to be downloaded again. Please remove all downloaded content and try again.",
+ [
+ {
+ text: "Back",
+ onPress: () => router.back(),
+ },
+ {
+ text: "Delete",
+ style: "destructive",
+ onPress: async () => await deleteAllFiles(),
+ },
+ ]
+ );
+}
diff --git a/app/(auth)/player/player.tsx b/app/(auth)/player/player.tsx
index 4666209e..48fd9fff 100644
--- a/app/(auth)/player/player.tsx
+++ b/app/(auth)/player/player.tsx
@@ -119,14 +119,14 @@ export default function page() {
queryFn: async () => {
console.log("Offline:", offline);
if (offline) {
- const item = await getDownloadedItem(itemId);
- if (!item?.mediaSource) return null;
+ const data = await getDownloadedItem(itemId);
+ if (!data?.mediaSource) return null;
- const url = await getDownloadedFileUrl(item.item.Id!);
+ const url = await getDownloadedFileUrl(data.item.Id!);
if (item)
return {
- mediaSource: item.mediaSource,
+ mediaSource: data.mediaSource,
url,
sessionId: undefined,
};
@@ -165,13 +165,13 @@ export default function page() {
const togglePlay = useCallback(
async (ms: number) => {
- if (!api || !stream) return;
+ if (!api) return;
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
if (isPlaying) {
await videoRef.current?.pause();
- if (!offline) {
+ if (!offline && stream) {
await getPlaystateApi(api).onPlaybackProgress({
itemId: item?.Id!,
audioStreamIndex: audioIndex ? audioIndex : undefined,
@@ -189,7 +189,7 @@ export default function page() {
console.log("Actually marked as paused");
} else {
videoRef.current?.play();
- if (!offline) {
+ if (!offline && stream) {
await getPlaystateApi(api).onPlaybackProgress({
itemId: item?.Id!,
audioStreamIndex: audioIndex ? audioIndex : undefined,
@@ -235,6 +235,7 @@ export default function page() {
const reportPlaybackStopped = useCallback(async () => {
if (offline) return;
+
const currentTimeInTicks = msToTicks(progress.value);
await getPlaystateApi(api!).onPlaybackStopped({
itemId: item?.Id!,
@@ -245,9 +246,10 @@ export default function page() {
}, [api, item, mediaSourceId, stream]);
const reportPlaybackStart = useCallback(async () => {
- if (!api || !stream) return;
if (offline) return;
- await getPlaystateApi(api).onPlaybackStart({
+
+ if (!stream) return;
+ await getPlaystateApi(api!).onPlaybackStart({
itemId: item?.Id!,
audioStreamIndex: audioIndex ? audioIndex : undefined,
subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
@@ -261,7 +263,6 @@ export default function page() {
async (data: ProgressUpdatePayload) => {
if (isSeeking.value === true) return;
if (isPlaybackStopped === true) return;
- if (!item?.Id || !api || !stream) return;
const { currentTime } = data.nativeEvent;
@@ -275,7 +276,9 @@ export default function page() {
const currentTimeInTicks = msToTicks(currentTime);
- await getPlaystateApi(api).onPlaybackProgress({
+ if (!item?.Id || !stream) return;
+
+ await getPlaystateApi(api!).onPlaybackProgress({
itemId: item.Id,
audioStreamIndex: audioIndex ? audioIndex : undefined,
subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
@@ -343,21 +346,20 @@ export default function page() {
);
- if (!stream || !item) return null;
-
// Preselection of audio and subtitle tracks.
let initOptions = ["--sub-text-scale=60"];
let externalTrack = { name: "", DeliveryUrl: "" };
const allSubs =
- stream.mediaSource.MediaStreams?.filter((sub) => sub.Type === "Subtitle") ||
- [];
+ stream?.mediaSource.MediaStreams?.filter(
+ (sub) => sub.Type === "Subtitle"
+ ) || [];
const chosenSubtitleTrack = allSubs.find(
(sub) => sub.Index === subtitleIndex
);
const allAudio =
- stream.mediaSource.MediaStreams?.filter(
+ stream?.mediaSource.MediaStreams?.filter(
(audio) => audio.Type === "Audio"
) || [];
const chosenAudioTrack = allAudio.find((audio) => audio.Index === audioIndex);
@@ -375,9 +377,8 @@ export default function page() {
};
}
- if (!chosenAudioTrack) throw new Error("No audio track found");
-
- initOptions.push(`--audio-track=${allAudio.indexOf(chosenAudioTrack)}`);
+ if (chosenAudioTrack)
+ initOptions.push(`--audio-track=${allAudio.indexOf(chosenAudioTrack)}`);
} else {
// Transcoded playback CASE
if (chosenSubtitleTrack?.DeliveryMethod === "Hls") {
@@ -388,6 +389,15 @@ export default function page() {
}
}
+ if (!item || !stream)
+ return (
+
+
+
+
+
+ );
+
return (
{
const now = Date.now();
- const settingsData = await AsyncStorage.getItem("settings");
+ const settingsData = storage.getString("settings");
if (!settingsData) return BackgroundFetch.BackgroundFetchResult.NoData;
@@ -96,8 +96,8 @@ TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
if (!settings?.autoDownload || !url)
return BackgroundFetch.BackgroundFetchResult.NoData;
- const token = await getTokenFromStoraage();
- const deviceId = await getOrSetDeviceId();
+ const token = getTokenFromStorage();
+ const deviceId = getOrSetDeviceId();
const baseDirectory = FileSystem.documentDirectory;
if (!token || !deviceId || !baseDirectory)
@@ -177,7 +177,7 @@ TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
const checkAndRequestPermissions = async () => {
try {
- const hasAskedBefore = await AsyncStorage.getItem(
+ const hasAskedBefore = storage.getString(
"hasAskedForNotificationPermission"
);
@@ -192,7 +192,7 @@ const checkAndRequestPermissions = async () => {
console.log("Notification permissions denied.");
}
- await AsyncStorage.setItem("hasAskedForNotificationPermission", "true");
+ storage.set("hasAskedForNotificationPermission", "true");
} else {
console.log("Already asked for notification permissions before.");
}
@@ -365,9 +365,9 @@ function Layout() {
);
}
-async function saveDownloadedItemInfo(item: BaseItemDto) {
+function saveDownloadedItemInfo(item: BaseItemDto) {
try {
- const downloadedItems = await AsyncStorage.getItem("downloadedItems");
+ const downloadedItems = storage.getString("downloadedItems");
let items: BaseItemDto[] = downloadedItems
? JSON.parse(downloadedItems)
: [];
@@ -379,7 +379,7 @@ async function saveDownloadedItemInfo(item: BaseItemDto) {
items.push(item);
}
- await AsyncStorage.setItem("downloadedItems", JSON.stringify(items));
+ storage.set("downloadedItems", JSON.stringify(items));
} catch (error) {
writeToLog("ERROR", "Failed to save downloaded item information:", error);
console.error("Failed to save downloaded item information:", error);
diff --git a/hooks/useImageStorage.ts b/hooks/useImageStorage.ts
index 56b44ba0..f379de1c 100644
--- a/hooks/useImageStorage.ts
+++ b/hooks/useImageStorage.ts
@@ -1,12 +1,10 @@
-import { useState, useCallback } from "react";
-import AsyncStorage from "@react-native-async-storage/async-storage";
-import * as FileSystem from "expo-file-system";
import { storage } from "@/utils/mmkv";
+import { useCallback } from "react";
const useImageStorage = () => {
const saveBase64Image = useCallback(async (base64: string, key: string) => {
try {
- // Save the base64 string to AsyncStorage
+ // Save the base64 string to storage
storage.set(key, base64);
} catch (error) {
console.error("Error saving image:", error);
@@ -69,7 +67,7 @@ const useImageStorage = () => {
const loadImage = useCallback(async (key: string) => {
try {
- // Retrieve the base64 string from AsyncStorage
+ // Retrieve the base64 string from storage
const base64Image = storage.getString(key);
if (base64Image !== null) {
// Set the loaded image state
diff --git a/hooks/useRemuxHlsToMp4.ts b/hooks/useRemuxHlsToMp4.ts
index ffba7c7c..d2e5388b 100644
--- a/hooks/useRemuxHlsToMp4.ts
+++ b/hooks/useRemuxHlsToMp4.ts
@@ -1,21 +1,19 @@
-import { useCallback } from "react";
-import { useAtom, useAtomValue } from "jotai";
-import AsyncStorage from "@react-native-async-storage/async-storage";
-import * as FileSystem from "expo-file-system";
-import { FFmpegKit, FFmpegKitConfig } from "ffmpeg-kit-react-native";
+import { useDownload } from "@/providers/DownloadProvider";
+import { apiAtom } from "@/providers/JellyfinProvider";
+import { getItemImage } from "@/utils/getItemImage";
+import { writeToLog } from "@/utils/log";
import {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client/models";
-import { writeToLog } from "@/utils/log";
import { useQueryClient } from "@tanstack/react-query";
-import { toast } from "sonner-native";
-import { useDownload } from "@/providers/DownloadProvider";
+import * as FileSystem from "expo-file-system";
import { useRouter } from "expo-router";
-import { JobStatus } from "@/utils/optimize-server";
+import { FFmpegKit, FFmpegKitConfig } from "ffmpeg-kit-react-native";
+import { useAtomValue } from "jotai";
+import { useCallback } from "react";
+import { toast } from "sonner-native";
import useImageStorage from "./useImageStorage";
-import { getItemImage } from "@/utils/getItemImage";
-import { apiAtom } from "@/providers/JellyfinProvider";
/**
* Custom hook for remuxing HLS to MP4 using FFmpeg.
diff --git a/providers/DownloadProvider.tsx b/providers/DownloadProvider.tsx
index a8dbf663..b5b84c1b 100644
--- a/providers/DownloadProvider.tsx
+++ b/providers/DownloadProvider.tsx
@@ -19,7 +19,7 @@ import {
download,
setConfig,
} from "@kesha-antonov/react-native-background-downloader";
-import AsyncStorage from "@react-native-async-storage/async-storage";
+import MMKV from "react-native-mmkv";
import {
focusManager,
QueryClient,
@@ -45,6 +45,7 @@ import { apiAtom } from "./JellyfinProvider";
import * as Notifications from "expo-notifications";
import { getItemImage } from "@/utils/getItemImage";
import useImageStorage from "@/hooks/useImageStorage";
+import { storage } from "@/utils/mmkv";
export type DownloadedItem = {
item: Partial;
@@ -109,7 +110,6 @@ function useDownloadProvider() {
url,
});
- // Local downloading processes that are still valid
const downloadingProcesses = processes
.filter((p) => p.status === "downloading")
.filter((p) => jobs.some((j) => j.id === p.id));
@@ -120,8 +120,6 @@ function useDownloadProvider() {
setProcesses([...updatedProcesses, ...downloadingProcesses]);
- // Go though new jobs and compare them to old jobs
- // if new job is now completed, start download.
for (let job of jobs) {
const process = processes.find((p) => p.id === job.id);
if (
@@ -314,7 +312,6 @@ function useDownloadProvider() {
const fileExtension = mediaSource.TranscodingContainer;
const deviceId = await getOrSetDeviceId();
- // Save poster to disk
const itemImage = getItemImage({
item,
api,
@@ -324,7 +321,6 @@ function useDownloadProvider() {
});
await saveImage(item.Id, itemImage?.uri);
- // POST to start optimization job on the server
const response = await axios.post(
settings?.optimizedVersionsServerUrl + "optimize-version",
{
@@ -391,7 +387,7 @@ function useDownloadProvider() {
const deleteAllFiles = async (): Promise => {
try {
await deleteLocalFiles();
- await removeDownloadedItemsFromStorage();
+ removeDownloadedItemsFromStorage();
await cancelAllServerJobs();
queryClient.invalidateQueries({ queryKey: ["downloadedItems"] });
toast.success("All files, folders, and jobs deleted successfully");
@@ -421,12 +417,12 @@ function useDownloadProvider() {
}
};
- const removeDownloadedItemsFromStorage = async (): Promise => {
+ const removeDownloadedItemsFromStorage = (): void => {
try {
- await AsyncStorage.removeItem("downloadedItems");
+ storage.delete("downloadedItems");
} catch (error) {
console.error(
- "Failed to remove downloadedItems from AsyncStorage:",
+ "Failed to remove downloadedItems from storage:",
error
);
throw error;
@@ -482,27 +478,25 @@ function useDownloadProvider() {
}
}
- const downloadedItems = await AsyncStorage.getItem("downloadedItems");
+ const downloadedItems = storage.getString("downloadedItems");
if (downloadedItems) {
let items = JSON.parse(downloadedItems);
items = items.filter((item: any) => item.Id !== id);
- await AsyncStorage.setItem("downloadedItems", JSON.stringify(items));
+ storage.set("downloadedItems", JSON.stringify(items));
}
queryClient.invalidateQueries({ queryKey: ["downloadedItems"] });
} catch (error) {
console.error(
- `Failed to delete file and AsyncStorage entry for ID ${id}:`,
+ `Failed to delete file and storage entry for ID ${id}:`,
error
);
}
};
- async function getDownloadedItem(
- itemId: string
- ): Promise {
+ function getDownloadedItem(itemId: string): DownloadedItem | null {
try {
- const downloadedItems = await AsyncStorage.getItem("downloadedItems");
+ const downloadedItems = storage.getString("downloadedItems");
if (downloadedItems) {
const items: DownloadedItem[] = JSON.parse(downloadedItems);
const item = items.find((i) => i.item.Id === itemId);
@@ -515,9 +509,9 @@ function useDownloadProvider() {
}
}
- async function getAllDownloadedItems(): Promise {
+ function getAllDownloadedItems(): DownloadedItem[] {
try {
- const downloadedItems = await AsyncStorage.getItem("downloadedItems");
+ const downloadedItems = storage.getString("downloadedItems");
if (downloadedItems) {
return JSON.parse(downloadedItems) as DownloadedItem[];
} else {
@@ -529,9 +523,9 @@ function useDownloadProvider() {
}
}
- async function saveDownloadedItemInfo(item: BaseItemDto) {
+ function saveDownloadedItemInfo(item: BaseItemDto) {
try {
- const downloadedItems = await AsyncStorage.getItem("downloadedItems");
+ const downloadedItems = storage.getString("downloadedItems");
let items: DownloadedItem[] = downloadedItems
? JSON.parse(downloadedItems)
: [];
@@ -555,8 +549,8 @@ function useDownloadProvider() {
deleteDownloadItemInfoFromDiskTmp(item.Id!);
- await AsyncStorage.setItem("downloadedItems", JSON.stringify(items));
- await queryClient.invalidateQueries({ queryKey: ["downloadedItems"] });
+ storage.set("downloadedItems", JSON.stringify(items));
+ queryClient.invalidateQueries({ queryKey: ["downloadedItems"] });
refetch();
} catch (error) {
console.error(
diff --git a/providers/JellyfinProvider.tsx b/providers/JellyfinProvider.tsx
index a6af0322..03f32f89 100644
--- a/providers/JellyfinProvider.tsx
+++ b/providers/JellyfinProvider.tsx
@@ -2,7 +2,6 @@ import { useInterval } from "@/hooks/useInterval";
import { Api, Jellyfin } from "@jellyfin/sdk";
import { UserDto } from "@jellyfin/sdk/lib/generated-client/models";
import { getUserApi } from "@jellyfin/sdk/lib/utils/api";
-import AsyncStorage from "@react-native-async-storage/async-storage";
import { useMutation, useQuery } from "@tanstack/react-query";
import axios, { AxiosError } from "axios";
import { router, useSegments } from "expo-router";
@@ -18,6 +17,8 @@ import React, {
} from "react";
import { Platform } from "react-native";
import uuid from "react-native-uuid";
+import MMKV from "react-native-mmkv";
+import { storage } from "@/utils/mmkv";
interface Server {
address: string;
@@ -48,7 +49,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
useEffect(() => {
(async () => {
- const id = await getOrSetDeviceId();
+ const id = getOrSetDeviceId();
setJellyfin(
() =>
new Jellyfin({
@@ -138,8 +139,8 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
const { AccessToken, User } = authResponse.data;
api.accessToken = AccessToken;
setUser(User);
- await AsyncStorage.setItem("token", AccessToken);
- await AsyncStorage.setItem("user", JSON.stringify(User));
+ storage.set("token", AccessToken);
+ storage.set("user", JSON.stringify(User));
return true;
}
}
@@ -172,7 +173,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
if (!apiInstance?.basePath) throw new Error("Failed to connect");
setApi(apiInstance);
- await AsyncStorage.setItem("serverUrl", server.address);
+ storage.set("serverUrl", server.address);
},
onError: (error) => {
console.error("Failed to set server:", error);
@@ -181,7 +182,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
const removeServerMutation = useMutation({
mutationFn: async () => {
- await AsyncStorage.removeItem("serverUrl");
+ storage.delete("serverUrl");
setApi(null);
},
onError: (error) => {
@@ -204,9 +205,9 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
if (auth.data.AccessToken && auth.data.User) {
setUser(auth.data.User);
- await AsyncStorage.setItem("user", JSON.stringify(auth.data.User));
+ storage.set("user", JSON.stringify(auth.data.User));
setApi(jellyfin.createApi(api?.basePath, auth.data?.AccessToken));
- await AsyncStorage.setItem("token", auth.data?.AccessToken);
+ storage.set("token", auth.data?.AccessToken);
}
} catch (error) {
if (axios.isAxiosError(error)) {
@@ -241,7 +242,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
const logoutMutation = useMutation({
mutationFn: async () => {
- await AsyncStorage.removeItem("token");
+ storage.delete("token");
setUser(null);
},
onError: (error) => {
@@ -258,13 +259,10 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
],
queryFn: async () => {
try {
- const token = await getTokenFromStoraage();
- const serverUrl = await getServerUrlFromStorage();
- const user = JSON.parse(
- (await getUserFromStorage()) as string
- ) as UserDto;
-
- if (serverUrl && token && user.Id && jellyfin) {
+ const token = getTokenFromStorage();
+ const serverUrl = getServerUrlFromStorage();
+ const user = getUserFromStorage();
+ if (serverUrl && token && user?.Id && jellyfin) {
const apiInstance = jellyfin.createApi(serverUrl, token);
setApi(apiInstance);
setUser(user);
@@ -273,6 +271,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
return true;
} catch (e) {
console.error(e);
+ return false;
}
},
staleTime: 0,
@@ -321,24 +320,32 @@ function useProtectedRoute(user: UserDto | null, loading = false) {
}, [user, segments, loading]);
}
-export async function getTokenFromStoraage() {
- return await AsyncStorage.getItem("token");
+export function getTokenFromStorage(): string | null {
+ return storage.getString("token") || null;
}
-export async function getUserFromStorage() {
- return await AsyncStorage.getItem("user");
+export function getUserFromStorage(): UserDto | null {
+ const userStr = storage.getString("user");
+ if (userStr) {
+ try {
+ return JSON.parse(userStr) as UserDto;
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ return null;
}
-export async function getServerUrlFromStorage() {
- return await AsyncStorage.getItem("serverUrl");
+export function getServerUrlFromStorage(): string | null {
+ return storage.getString("serverUrl") || null;
}
-export async function getOrSetDeviceId() {
- let deviceId = await AsyncStorage.getItem("deviceId");
+export function getOrSetDeviceId(): string {
+ let deviceId = storage.getString("deviceId");
if (!deviceId) {
deviceId = uuid.v4() as string;
- await AsyncStorage.setItem("deviceId", deviceId);
+ storage.set("deviceId", deviceId);
}
return deviceId;
diff --git a/utils/atoms/filters.ts b/utils/atoms/filters.ts
index 9d758e26..e2c9e60c 100644
--- a/utils/atoms/filters.ts
+++ b/utils/atoms/filters.ts
@@ -1,6 +1,6 @@
-import AsyncStorage from "@react-native-async-storage/async-storage";
import { atom } from "jotai";
-import { atomWithStorage, createJSONStorage } from "jotai/utils";
+import { atomWithStorage } from "jotai/utils";
+import { storage } from "../mmkv";
export enum SortByOption {
Default = "Default",
@@ -68,9 +68,6 @@ export const sortOrderAtom = atom([
SortOrderOption.Ascending,
]);
-/**
- * Sort preferences with persistence
- */
export interface SortPreference {
[libraryId: string]: SortByOption;
}
@@ -86,15 +83,15 @@ export const sortByPreferenceAtom = atomWithStorage(
"sortByPreference",
defaultSortPreference,
{
- getItem: async (key) => {
- const value = await AsyncStorage.getItem(key);
+ getItem: (key) => {
+ const value = storage.getString(key);
return value ? JSON.parse(value) : null;
},
- setItem: async (key, value) => {
- await AsyncStorage.setItem(key, JSON.stringify(value));
+ setItem: (key, value) => {
+ storage.set(key, JSON.stringify(value));
},
- removeItem: async (key) => {
- await AsyncStorage.removeItem(key);
+ removeItem: (key) => {
+ storage.delete(key);
},
}
);
@@ -103,20 +100,19 @@ export const sortOrderPreferenceAtom = atomWithStorage(
"sortOrderPreference",
defaultSortOrderPreference,
{
- getItem: async (key) => {
- const value = await AsyncStorage.getItem(key);
+ getItem: (key) => {
+ const value = storage.getString(key);
return value ? JSON.parse(value) : null;
},
- setItem: async (key, value) => {
- await AsyncStorage.setItem(key, JSON.stringify(value));
+ setItem: (key, value) => {
+ storage.set(key, JSON.stringify(value));
},
- removeItem: async (key) => {
- await AsyncStorage.removeItem(key);
+ removeItem: (key) => {
+ storage.delete(key);
},
}
);
-// Helper functions to get and set sort preferences
export const getSortByPreference = (
libraryId: string,
preferences: SortPreference
@@ -130,4 +126,3 @@ export const getSortOrderPreference = (
) => {
return preferences?.[libraryId] || null;
};
-
diff --git a/utils/atoms/queue.ts b/utils/atoms/queue.ts
index 2950d55a..8bd45ffa 100644
--- a/utils/atoms/queue.ts
+++ b/utils/atoms/queue.ts
@@ -1,7 +1,5 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
-import AsyncStorage from "@react-native-async-storage/async-storage";
import { atom, useAtom } from "jotai";
-import { atomWithStorage } from "jotai/utils";
import { useEffect } from "react";
export interface Job {
diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts
index 584f9ed8..5d47c3dd 100644
--- a/utils/atoms/settings.ts
+++ b/utils/atoms/settings.ts
@@ -1,7 +1,7 @@
import { atom, useAtom } from "jotai";
-import AsyncStorage from "@react-native-async-storage/async-storage";
import { useEffect } from "react";
import * as ScreenOrientation from "expo-screen-orientation";
+import { storage } from "../mmkv";
export type DownloadQuality = "original" | "high" | "low";
@@ -75,15 +75,8 @@ export type Settings = {
downloadMethod: "optimized" | "remux";
autoDownload: boolean;
};
-/**
- *
- * The settings atom is a Jotai atom that stores the user's settings.
- * It is initialized with a default value of null, which indicates that the settings have not been loaded yet.
- * The settings are loaded from AsyncStorage when the atom is read for the first time.
- *
- */
-const loadSettings = async (): Promise => {
+const loadSettings = (): Settings => {
const defaultValues: Settings = {
autoRotate: true,
forceLandscapeInVideoPlayer: false,
@@ -113,7 +106,7 @@ const loadSettings = async (): Promise => {
};
try {
- const jsonValue = await AsyncStorage.getItem("settings");
+ const jsonValue = storage.getString("settings");
const loadedValues: Partial =
jsonValue != null ? JSON.parse(jsonValue) : {};
@@ -124,30 +117,28 @@ const loadSettings = async (): Promise => {
}
};
-// Utility function to save settings to AsyncStorage
-const saveSettings = async (settings: Settings) => {
+const saveSettings = (settings: Settings) => {
const jsonValue = JSON.stringify(settings);
- await AsyncStorage.setItem("settings", jsonValue);
+ storage.set("settings", jsonValue);
};
-// Create an atom to store the settings in memory
export const settingsAtom = atom(null);
-// A hook to manage settings, loading them on initial mount and providing a way to update them
export const useSettings = () => {
const [settings, setSettings] = useAtom(settingsAtom);
useEffect(() => {
if (settings === null) {
- loadSettings().then(setSettings);
+ const loadedSettings = loadSettings();
+ setSettings(loadedSettings);
}
}, [settings, setSettings]);
- const updateSettings = async (update: Partial) => {
+ const updateSettings = (update: Partial) => {
if (settings) {
const newSettings = { ...settings, ...update };
setSettings(newSettings);
- await saveSettings(newSettings);
+ saveSettings(newSettings);
}
};
diff --git a/utils/device.ts b/utils/device.ts
index 3968b02f..29a988a5 100644
--- a/utils/device.ts
+++ b/utils/device.ts
@@ -1,19 +1,19 @@
-import AsyncStorage from "@react-native-async-storage/async-storage";
import uuid from "react-native-uuid";
+import { storage } from "./mmkv";
-export const getOrSetDeviceId = async () => {
- let deviceId = await AsyncStorage.getItem("deviceId");
+export const getOrSetDeviceId = () => {
+ let deviceId = storage.getString("deviceId");
if (!deviceId) {
deviceId = uuid.v4() as string;
- await AsyncStorage.setItem("deviceId", deviceId);
+ storage.set("deviceId", deviceId);
}
return deviceId;
};
-export const getDeviceId = async () => {
- let deviceId = await AsyncStorage.getItem("deviceId");
+export const getDeviceId = () => {
+ let deviceId = storage.getString("deviceId");
return deviceId || null;
};
diff --git a/utils/log.ts b/utils/log.ts
index 0f8af54a..0072c82c 100644
--- a/utils/log.ts
+++ b/utils/log.ts
@@ -1,5 +1,5 @@
-import AsyncStorage from "@react-native-async-storage/async-storage";
import { atomWithStorage, createJSONStorage } from "jotai/utils";
+import { storage } from "./mmkv";
type LogLevel = "INFO" | "WARN" | "ERROR";
@@ -10,14 +10,14 @@ interface LogEntry {
data?: any;
}
-const asyncStorage = createJSONStorage(() => AsyncStorage);
-const logsAtom = atomWithStorage("logs", [], asyncStorage);
+const mmkvStorage = createJSONStorage(() => ({
+ getItem: (key: string) => storage.getString(key) || null,
+ setItem: (key: string, value: string) => storage.set(key, value),
+ removeItem: (key: string) => storage.delete(key),
+}));
+const logsAtom = atomWithStorage("logs", [], mmkvStorage);
-export const writeToLog = async (
- level: LogLevel,
- message: string,
- data?: any
-) => {
+export const writeToLog = (level: LogLevel, message: string, data?: any) => {
const newEntry: LogEntry = {
timestamp: new Date().toISOString(),
level: level,
@@ -25,23 +25,23 @@ export const writeToLog = async (
data: data,
};
- const currentLogs = await AsyncStorage.getItem("logs");
+ const currentLogs = storage.getString("logs");
const logs: LogEntry[] = currentLogs ? JSON.parse(currentLogs) : [];
logs.push(newEntry);
const maxLogs = 100;
const recentLogs = logs.slice(Math.max(logs.length - maxLogs, 0));
- await AsyncStorage.setItem("logs", JSON.stringify(recentLogs));
+ storage.set("logs", JSON.stringify(recentLogs));
};
-export const readFromLog = async (): Promise => {
- const logs = await AsyncStorage.getItem("logs");
+export const readFromLog = (): LogEntry[] => {
+ const logs = storage.getString("logs");
return logs ? JSON.parse(logs) : [];
};
-export const clearLogs = async () => {
- await AsyncStorage.removeItem("logs");
+export const clearLogs = () => {
+ storage.delete("logs");
};
export default logsAtom;