Compare commits

...

1 Commits

Author SHA1 Message Date
Fredrik Burmester
6c6a0f69ae wip 2024-08-21 09:13:17 +02:00
5 changed files with 161 additions and 130 deletions

View File

@@ -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}

View File

@@ -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}`);
}}
>

View File

@@ -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}

View File

@@ -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>
);
};

View File

@@ -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]]);