forked from Ninjalama/streamyfin_mirror
Compare commits
1 Commits
feat/nativ
...
fix/save-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c6a0f69ae |
@@ -1,4 +1,3 @@
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
|
||||
import { FilterButton } from "@/components/filters/FilterButton";
|
||||
import { ResetFiltersButton } from "@/components/filters/ResetFiltersButton";
|
||||
@@ -7,9 +6,10 @@ import { Loader } from "@/components/Loader";
|
||||
import MoviePoster from "@/components/posters/MoviePoster";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import {
|
||||
currentCollectionIdAtom,
|
||||
genreFilterAtom,
|
||||
sortByAtom,
|
||||
sortOptions,
|
||||
sortByOptions,
|
||||
sortOrderAtom,
|
||||
sortOrderOptions,
|
||||
tagsFilterAtom,
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
getUserLibraryApi,
|
||||
} from "@jellyfin/sdk/lib/utils/api";
|
||||
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
||||
import { Stack, useLocalSearchParams, useNavigation } from "expo-router";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useAtom } from "jotai";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import { NativeScrollEvent, ScrollView, View } from "react-native";
|
||||
@@ -46,9 +46,14 @@ const page: React.FC = () => {
|
||||
const searchParams = useLocalSearchParams();
|
||||
const { libraryId } = searchParams as { libraryId: string };
|
||||
|
||||
const [, setCurrentCollectionId] = useAtom(currentCollectionIdAtom);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentCollectionId(libraryId);
|
||||
}, [libraryId]);
|
||||
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
const navigation = useNavigation();
|
||||
|
||||
const [selectedGenres, setSelectedGenres] = useAtom(genreFilterAtom);
|
||||
const [selectedYears, setSelectedYears] = useAtom(yearFilterAtom);
|
||||
@@ -204,7 +209,7 @@ const page: React.FC = () => {
|
||||
});
|
||||
return response.data.Genres || [];
|
||||
}}
|
||||
set={setSelectedGenres}
|
||||
set={(value) => setSelectedGenres(value, libraryId)}
|
||||
values={selectedGenres}
|
||||
title="Genres"
|
||||
renderItemLabel={(item) => item.toString()}
|
||||
@@ -226,7 +231,7 @@ const page: React.FC = () => {
|
||||
});
|
||||
return response.data.Tags || [];
|
||||
}}
|
||||
set={setSelectedTags}
|
||||
set={(value) => setSelectedTags(value, libraryId)}
|
||||
values={selectedTags}
|
||||
title="Tags"
|
||||
renderItemLabel={(item) => item.toString()}
|
||||
@@ -252,7 +257,7 @@ const page: React.FC = () => {
|
||||
) || []
|
||||
);
|
||||
}}
|
||||
set={setSelectedYears}
|
||||
set={(value) => setSelectedYears(value, libraryId)}
|
||||
values={selectedYears}
|
||||
title="Years"
|
||||
renderItemLabel={(item) => item.toString()}
|
||||
@@ -265,9 +270,9 @@ const page: React.FC = () => {
|
||||
collectionId={libraryId}
|
||||
queryKey="sortByFilter"
|
||||
queryFn={async () => {
|
||||
return sortOptions;
|
||||
return sortByOptions;
|
||||
}}
|
||||
set={setSortBy}
|
||||
set={(value) => setSortBy(value, libraryId)}
|
||||
values={sortBy}
|
||||
title="Sort by"
|
||||
renderItemLabel={(item) => item.value}
|
||||
@@ -285,7 +290,7 @@ const page: React.FC = () => {
|
||||
queryFn={async () => {
|
||||
return sortOrderOptions;
|
||||
}}
|
||||
set={setSortOrder}
|
||||
set={(value) => setSortOrder(value, libraryId)}
|
||||
values={sortOrder}
|
||||
title="Order by"
|
||||
renderItemLabel={(item) => item.value}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { Loader } from "@/components/Loader";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { currentCollectionIdAtom } from "@/utils/atoms/filters";
|
||||
import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl";
|
||||
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { getUserViewsApi } from "@jellyfin/sdk/lib/utils/api";
|
||||
@@ -66,6 +67,10 @@ const LibraryItemCard: React.FC<Props> = ({ library }) => {
|
||||
|
||||
const [api] = useAtom(apiAtom);
|
||||
|
||||
const [currentCollection, setCurrentCollection] = useAtom(
|
||||
currentCollectionIdAtom
|
||||
);
|
||||
|
||||
const url = useMemo(
|
||||
() =>
|
||||
getPrimaryImageUrl({
|
||||
@@ -80,6 +85,8 @@ const LibraryItemCard: React.FC<Props> = ({ library }) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (!library.Id) return;
|
||||
setCurrentCollection(library.Id);
|
||||
router.push(`/libraries/${library.Id}`);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -7,9 +7,10 @@ import { Loader } from "@/components/Loader";
|
||||
import MoviePoster from "@/components/posters/MoviePoster";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import {
|
||||
currentCollectionIdAtom,
|
||||
genreFilterAtom,
|
||||
sortByAtom,
|
||||
sortOptions,
|
||||
sortByOptions,
|
||||
sortOrderAtom,
|
||||
sortOrderOptions,
|
||||
tagsFilterAtom,
|
||||
@@ -55,20 +56,29 @@ const page: React.FC = () => {
|
||||
const [selectedTags, setSelectedTags] = useAtom(tagsFilterAtom);
|
||||
const [sortBy, setSortBy] = useAtom(sortByAtom);
|
||||
const [sortOrder, setSortOrder] = useAtom(sortOrderAtom);
|
||||
const [currentCollection, setCurrentCollection] = useAtom(
|
||||
currentCollectionIdAtom
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSortBy([
|
||||
{
|
||||
key: "PremiereDate",
|
||||
value: "Premiere Date",
|
||||
},
|
||||
]);
|
||||
setSortOrder([
|
||||
{
|
||||
key: "Ascending",
|
||||
value: "Ascending",
|
||||
},
|
||||
]);
|
||||
setSortBy(
|
||||
[
|
||||
{
|
||||
key: "PremiereDate",
|
||||
value: "Premiere Date",
|
||||
},
|
||||
],
|
||||
collectionId
|
||||
);
|
||||
setSortOrder(
|
||||
[
|
||||
{
|
||||
key: "Ascending",
|
||||
value: "Ascending",
|
||||
},
|
||||
],
|
||||
collectionId
|
||||
);
|
||||
}, []);
|
||||
|
||||
const { data: collection } = useQuery({
|
||||
@@ -208,7 +218,7 @@ const page: React.FC = () => {
|
||||
});
|
||||
return response.data.Genres || [];
|
||||
}}
|
||||
set={setSelectedGenres}
|
||||
set={(value) => setSelectedGenres(value, collectionId)}
|
||||
values={selectedGenres}
|
||||
title="Genres"
|
||||
renderItemLabel={(item) => item.toString()}
|
||||
@@ -230,7 +240,7 @@ const page: React.FC = () => {
|
||||
});
|
||||
return response.data.Tags || [];
|
||||
}}
|
||||
set={setSelectedTags}
|
||||
set={(value) => setSelectedTags(value, collectionId)}
|
||||
values={selectedTags}
|
||||
title="Tags"
|
||||
renderItemLabel={(item) => item.toString()}
|
||||
@@ -256,7 +266,7 @@ const page: React.FC = () => {
|
||||
) || []
|
||||
);
|
||||
}}
|
||||
set={setSelectedYears}
|
||||
set={(value) => setSelectedYears(value, collectionId)}
|
||||
values={selectedYears}
|
||||
title="Years"
|
||||
renderItemLabel={(item) => item.toString()}
|
||||
@@ -269,9 +279,9 @@ const page: React.FC = () => {
|
||||
collectionId={collectionId}
|
||||
queryKey="sortByFilter"
|
||||
queryFn={async () => {
|
||||
return sortOptions;
|
||||
return sortByOptions;
|
||||
}}
|
||||
set={setSortBy}
|
||||
set={(value) => setSortBy(value, collectionId)}
|
||||
values={sortBy}
|
||||
title="Sort by"
|
||||
renderItemLabel={(item) => item.value}
|
||||
@@ -289,7 +299,7 @@ const page: React.FC = () => {
|
||||
queryFn={async () => {
|
||||
return sortOrderOptions;
|
||||
}}
|
||||
set={setSortOrder}
|
||||
set={(value) => setSortOrder(value, collectionId)}
|
||||
values={sortOrder}
|
||||
title="Order by"
|
||||
renderItemLabel={(item) => item.value}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||
import { Text } from "@/components/common/Text";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtom } from "jotai";
|
||||
import { TouchableOpacity, View, ViewProps } from "react-native";
|
||||
import {
|
||||
sortByAtom,
|
||||
sortOptions,
|
||||
sortOrderAtom,
|
||||
sortOrderOptions,
|
||||
} from "@/utils/atoms/filters";
|
||||
|
||||
interface Props extends ViewProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const SortButton: React.FC<Props> = ({ title, ...props }) => {
|
||||
const [api] = useAtom(apiAtom);
|
||||
const [user] = useAtom(userAtom);
|
||||
|
||||
const [sortBy, setSortBy] = useAtom(sortByAtom);
|
||||
const [sortOrder, setSortOrder] = useAtom(sortOrderAtom);
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
<TouchableOpacity>
|
||||
<View
|
||||
className={`
|
||||
px-3 py-2 rounded-full flex flex-row items-center space-x-2 bg-neutral-900
|
||||
`}
|
||||
{...props}
|
||||
>
|
||||
<Text>Sort by</Text>
|
||||
<Ionicons
|
||||
name="filter"
|
||||
size={16}
|
||||
color="white"
|
||||
style={{ opacity: 0.5 }}
|
||||
/>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
loop={true}
|
||||
side="bottom"
|
||||
align="start"
|
||||
alignOffset={0}
|
||||
avoidCollisions={true}
|
||||
collisionPadding={8}
|
||||
sideOffset={8}
|
||||
>
|
||||
{sortOptions?.map((g) => (
|
||||
<DropdownMenu.CheckboxItem
|
||||
value={sortBy.key === g.key ? "on" : "off"}
|
||||
onValueChange={(next, previous) => {
|
||||
if (next === "on") {
|
||||
setSortBy(g);
|
||||
} else {
|
||||
setSortBy(sortOptions[0]);
|
||||
}
|
||||
}}
|
||||
key={g.key}
|
||||
textValue={g.value}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator />
|
||||
</DropdownMenu.CheckboxItem>
|
||||
))}
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
{sortOrderOptions.map((g) => (
|
||||
<DropdownMenu.CheckboxItem
|
||||
value={sortOrder.key === g.key ? "on" : "off"}
|
||||
onValueChange={(next, previous) => {
|
||||
if (next === "on") {
|
||||
setSortOrder(g);
|
||||
} else {
|
||||
setSortOrder(sortOrderOptions[0]);
|
||||
}
|
||||
}}
|
||||
key={g.key}
|
||||
textValue={g.value}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator />
|
||||
</DropdownMenu.CheckboxItem>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
};
|
||||
@@ -5,8 +5,9 @@ import {
|
||||
SortOrder,
|
||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
|
||||
export const sortOptions: {
|
||||
export const sortByOptions: {
|
||||
key: ItemSortBy;
|
||||
value: string;
|
||||
}[] = [
|
||||
@@ -38,10 +39,111 @@ export const sortOrderOptions: {
|
||||
{ key: "Descending", value: "Descending" },
|
||||
];
|
||||
|
||||
export const genreFilterAtom = atom<string[]>([]);
|
||||
export const tagsFilterAtom = atom<string[]>([]);
|
||||
export const yearFilterAtom = atom<string[]>([]);
|
||||
export const sortByAtom = atom<[typeof sortOptions][number]>([sortOptions[0]]);
|
||||
export const sortOrderAtom = atom<[typeof sortOrderOptions][number]>([
|
||||
sortOrderOptions[0],
|
||||
]);
|
||||
// Define the keys for our preferences
|
||||
type PreferenceKey =
|
||||
| "genreFilter"
|
||||
| "tagsFilter"
|
||||
| "yearFilter"
|
||||
| "sortBy"
|
||||
| "sortOrder";
|
||||
|
||||
// Define the type for a single collection's preferences
|
||||
type CollectionPreference = {
|
||||
genreFilter: string[];
|
||||
tagsFilter: string[];
|
||||
yearFilter: string[];
|
||||
sortBy: [typeof sortByOptions][number];
|
||||
sortOrder: [typeof sortOrderOptions][number];
|
||||
};
|
||||
|
||||
// Define the type for all sort preferences
|
||||
type SortPreference = {
|
||||
[collectionId: string]: CollectionPreference;
|
||||
};
|
||||
|
||||
// Create a base atom with storage
|
||||
const baseSortPreferenceAtom = atomWithStorage<SortPreference>(
|
||||
"sortPreferences",
|
||||
{}
|
||||
);
|
||||
|
||||
// Create a derived atom with logging
|
||||
export const sortPreferenceAtom = atom(
|
||||
(get) => {
|
||||
const value = get(baseSortPreferenceAtom);
|
||||
console.log("Getting sortPreferences:", value);
|
||||
return value;
|
||||
},
|
||||
(get, set, newValue: SortPreference) => {
|
||||
console.log("Setting sortPreferences:", newValue);
|
||||
set(baseSortPreferenceAtom, newValue);
|
||||
}
|
||||
);
|
||||
|
||||
export const currentCollectionIdAtom = atomWithStorage<string | null>(
|
||||
"currentCollectionId",
|
||||
null
|
||||
);
|
||||
|
||||
// Helper function to create an atom with custom getter and setter
|
||||
const createFilterAtom = <T extends CollectionPreference[PreferenceKey]>(
|
||||
key: PreferenceKey,
|
||||
initialValue: T
|
||||
) => {
|
||||
const baseAtom = atom<T>(initialValue);
|
||||
|
||||
return atom(
|
||||
(get): T => {
|
||||
const preferences = get(sortPreferenceAtom);
|
||||
const currentCollectionId = get(currentCollectionIdAtom);
|
||||
if (currentCollectionId && preferences[currentCollectionId]) {
|
||||
const preferenceValue = preferences[currentCollectionId][key];
|
||||
|
||||
// Ensure the returned value matches the expected type T
|
||||
if (Array.isArray(initialValue) && Array.isArray(preferenceValue)) {
|
||||
return preferenceValue as T;
|
||||
} else if (
|
||||
typeof initialValue === "object" &&
|
||||
typeof preferenceValue === "object"
|
||||
) {
|
||||
return preferenceValue as T;
|
||||
} else if (typeof initialValue === typeof preferenceValue) {
|
||||
return preferenceValue as T;
|
||||
}
|
||||
}
|
||||
return get(baseAtom);
|
||||
},
|
||||
(get, set, newValue: T, collectionId: string) => {
|
||||
set(baseAtom, newValue);
|
||||
const preferences = get(sortPreferenceAtom);
|
||||
console.log("Set", preferences);
|
||||
set(sortPreferenceAtom, {
|
||||
...preferences,
|
||||
[collectionId]: {
|
||||
...preferences[collectionId],
|
||||
[key]: newValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
type SortByOption = ItemSortBy | { key: ItemSortBy; value: string };
|
||||
type SortOrderOption = SortOrder | { key: SortOrder; value: string };
|
||||
|
||||
function getSortKey(
|
||||
option: SortByOption | SortOrderOption
|
||||
): ItemSortBy | SortOrder {
|
||||
return typeof option === "string" ? option : option.key;
|
||||
}
|
||||
|
||||
export const genreFilterAtom = createFilterAtom<string[]>("genreFilter", []);
|
||||
export const tagsFilterAtom = createFilterAtom<string[]>("tagsFilter", []);
|
||||
export const yearFilterAtom = createFilterAtom<string[]>("yearFilter", []);
|
||||
export const sortByAtom = createFilterAtom<[typeof sortByOptions][number]>(
|
||||
"sortBy",
|
||||
[sortByOptions[0]]
|
||||
);
|
||||
export const sortOrderAtom = createFilterAtom<
|
||||
[typeof sortOrderOptions][number]
|
||||
>("sortOrder", [sortOrderOptions[0]]);
|
||||
|
||||
Reference in New Issue
Block a user