diff --git a/.gitignore b/.gitignore index aedc9ceb..4c3672cb 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ build-* *.mp4 build-* Streamyfin.app +package-lock.json /ios /android diff --git a/app/(auth)/(tabs)/(home,libraries,search)/collections/[collectionId].tsx b/app/(auth)/(tabs)/(home,libraries,search)/collections/[collectionId].tsx index b9a657be..b766391e 100644 --- a/app/(auth)/(tabs)/(home,libraries,search)/collections/[collectionId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search)/collections/[collectionId].tsx @@ -8,8 +8,10 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { genreFilterAtom, sortByAtom, + SortByOption, sortOptions, sortOrderAtom, + SortOrderOption, sortOrderOptions, tagsFilterAtom, yearFilterAtom, @@ -17,6 +19,7 @@ import { import { BaseItemDto, BaseItemDtoQueryResult, + ItemSortBy, } from "@jellyfin/sdk/lib/generated-client/models"; import { getFilterApi, @@ -56,21 +59,6 @@ const page: React.FC = () => { const [sortBy, setSortBy] = useAtom(sortByAtom); const [sortOrder, setSortOrder] = useAtom(sortOrderAtom); - useLayoutEffect(() => { - setSortBy([ - { - key: "PremiereDate", - value: "Premiere Date", - }, - ]); - setSortOrder([ - { - key: "Ascending", - value: "Ascending", - }, - ]); - }, []); - const { data: collection } = useQuery({ queryKey: ["collection", collectionId], queryFn: async () => { @@ -88,6 +76,18 @@ const page: React.FC = () => { useEffect(() => { navigation.setOptions({ title: collection?.Name || "" }); + setSortOrder([SortOrderOption.Ascending]); + + if (!collection) return; + + // Convert the DisplayOrder to SortByOption + const displayOrder = collection.DisplayOrder as ItemSortBy; + const sortByOption = displayOrder + ? SortByOption[displayOrder as keyof typeof SortByOption] || + SortByOption.PremiereDate + : SortByOption.PremiereDate; + + setSortBy([sortByOption]); }, [navigation, collection]); const fetchItems = useCallback( @@ -103,8 +103,9 @@ const page: React.FC = () => { parentId: collectionId, limit: 18, startIndex: pageParam, - sortBy: [sortBy[0].key, "SortName", "ProductionYear"], - sortOrder: [sortOrder[0].key], + // Set one ordering at a time. As collections do not work with correctly with multiple. + sortBy: [sortBy[0]], + sortOrder: [sortOrder[0]], fields: [ "ItemCounts", "PrimaryImageAspectRatio", @@ -216,6 +217,13 @@ const page: React.FC = () => { paddingVertical: 16, flexDirection: "row", }} + extraData={[ + selectedGenres, + selectedYears, + selectedTags, + sortBy, + sortOrder, + ]} data={[ { key: "reset", @@ -307,13 +315,15 @@ const page: React.FC = () => { className="mr-1" collectionId={collectionId} queryKey="sortBy" - queryFn={async () => sortOptions} + queryFn={async () => sortOptions.map((s) => s.key)} set={setSortBy} values={sortBy} title="Sort By" - renderItemLabel={(item) => item.value} + renderItemLabel={(item) => + sortOptions.find((i) => i.key === item)?.value || "" + } searchFilter={(item, search) => - item.value.toLowerCase().includes(search.toLowerCase()) + item.toLowerCase().includes(search.toLowerCase()) } /> ), @@ -325,13 +335,15 @@ const page: React.FC = () => { className="mr-1" collectionId={collectionId} queryKey="sortOrder" - queryFn={async () => sortOrderOptions} + queryFn={async () => sortOrderOptions.map((s) => s.key)} set={setSortOrder} values={sortOrder} title="Sort Order" - renderItemLabel={(item) => item.value} + renderItemLabel={(item) => + sortOrderOptions.find((i) => i.key === item)?.value || "" + } searchFilter={(item, search) => - item.value.toLowerCase().includes(search.toLowerCase()) + item.toLowerCase().includes(search.toLowerCase()) } /> ), @@ -369,6 +381,13 @@ const page: React.FC = () => { No results } + extraData={[ + selectedGenres, + selectedYears, + selectedTags, + sortBy, + sortOrder, + ]} contentInsetAdjustmentBehavior="automatic" data={flatData} renderItem={renderItem} diff --git a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx index 77b2e3ff..e021efee 100644 --- a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx +++ b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx @@ -9,25 +9,23 @@ import React, { useMemo, useState, } from "react"; -import { - FlatList, - RefreshControl, - useWindowDimensions, - View, -} from "react-native"; +import { FlatList, useWindowDimensions, View } from "react-native"; import { Text } from "@/components/common/Text"; import { TouchableItemRouter } from "@/components/common/TouchableItemRouter"; import { FilterButton } from "@/components/filters/FilterButton"; import { ResetFiltersButton } from "@/components/filters/ResetFiltersButton"; import { ItemCardText } from "@/components/ItemCardText"; +import { Loader } from "@/components/Loader"; import MoviePoster from "@/components/posters/MoviePoster"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { genreFilterAtom, sortByAtom, + SortByOption, sortOptions, sortOrderAtom, + SortOrderOption, sortOrderOptions, tagsFilterAtom, yearFilterAtom, @@ -35,7 +33,6 @@ import { import { BaseItemDto, BaseItemDtoQueryResult, - BaseItemKind, } from "@jellyfin/sdk/lib/generated-client/models"; import { getFilterApi, @@ -43,7 +40,6 @@ import { getUserLibraryApi, } from "@jellyfin/sdk/lib/utils/api"; import { FlashList } from "@shopify/flash-list"; -import { Loader } from "@/components/Loader"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { orientationAtom } from "@/utils/atoms/orientation"; @@ -55,7 +51,6 @@ const Page = () => { const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); - const navigation = useNavigation(); const { width: screenWidth } = useWindowDimensions(); const [selectedGenres, setSelectedGenres] = useAtom(genreFilterAtom); @@ -75,18 +70,8 @@ const Page = () => { }, [screenWidth, orientation]); useLayoutEffect(() => { - setSortBy([ - { - key: "SortName", - value: "Name", - }, - ]); - setSortOrder([ - { - key: "Ascending", - value: "Ascending", - }, - ]); + setSortBy([SortByOption.SortName]); + setSortOrder([SortOrderOption.Ascending]); }, []); const { data: library, isLoading: isLibraryLoading } = useQuery({ @@ -116,8 +101,8 @@ const Page = () => { parentId: libraryId, limit: 36, startIndex: pageParam, - sortBy: [sortBy[0].key, "SortName", "ProductionYear"], - sortOrder: [sortOrder[0].key], + sortBy: [sortBy[0], "SortName", "ProductionYear"], + sortOrder: [sortOrder[0]], enableImageTypes: ["Primary", "Backdrop", "Banner", "Thumb"], recursive: false, imageTypeLimit: 1, @@ -321,13 +306,15 @@ const Page = () => { className="mr-1" collectionId={libraryId} queryKey="sortBy" - queryFn={async () => sortOptions} + queryFn={async () => sortOptions.map((s) => s.key)} set={setSortBy} values={sortBy} title="Sort By" - renderItemLabel={(item) => item.value} + renderItemLabel={(item) => + sortOptions.find((i) => i.key === item)?.value || "" + } searchFilter={(item, search) => - item.value.toLowerCase().includes(search.toLowerCase()) + item.toLowerCase().includes(search.toLowerCase()) } /> ), @@ -339,13 +326,15 @@ const Page = () => { className="mr-1" collectionId={libraryId} queryKey="sortOrder" - queryFn={async () => sortOrderOptions} + queryFn={async () => sortOrderOptions.map((s) => s.key)} set={setSortOrder} values={sortOrder} title="Sort Order" - renderItemLabel={(item) => item.value} + renderItemLabel={(item) => + sortOrderOptions.find((i) => i.key === item)?.value || "" + } searchFilter={(item, search) => - item.value.toLowerCase().includes(search.toLowerCase()) + item.toLowerCase().includes(search.toLowerCase()) } /> ), diff --git a/components/filters/FilterButton.tsx b/components/filters/FilterButton.tsx index e50e4010..6d976b26 100644 --- a/components/filters/FilterButton.tsx +++ b/components/filters/FilterButton.tsx @@ -23,7 +23,7 @@ export const FilterButton = ({ queryFn, queryKey, set, - values, + values, // selected values title, renderItemLabel, searchFilter, diff --git a/components/filters/FilterSheet.tsx b/components/filters/FilterSheet.tsx index 7f163b72..fe6d9f6a 100644 --- a/components/filters/FilterSheet.tsx +++ b/components/filters/FilterSheet.tsx @@ -186,7 +186,7 @@ export const FilterSheet = ({ className=" bg-neutral-800 px-4 py-3 flex flex-row items-center justify-between" > {renderItemLabel(item)} - {values.includes(item) ? ( + {values.some((i) => i === item) ? ( ) : ( diff --git a/utils/atoms/filters.ts b/utils/atoms/filters.ts index 7324f569..f49b17d4 100644 --- a/utils/atoms/filters.ts +++ b/utils/atoms/filters.ts @@ -1,50 +1,67 @@ -import { - ItemFilter, - ItemSortBy, - NameGuidPair, - SortOrder, -} from "@jellyfin/sdk/lib/generated-client/models"; -import { atom, useAtom } from "jotai"; +import { atom } from "jotai"; + +export enum SortByOption { + Default = "Default", + SortName = "SortName", + CommunityRating = "CommunityRating", + CriticRating = "CriticRating", + DateCreated = "DateCreated", + DatePlayed = "DatePlayed", + PlayCount = "PlayCount", + ProductionYear = "ProductionYear", + Runtime = "Runtime", + OfficialRating = "OfficialRating", + PremiereDate = "PremiereDate", + StartDate = "StartDate", + IsUnplayed = "IsUnplayed", + IsPlayed = "IsPlayed", + AirTime = "AirTime", + Studio = "Studio", + IsFavoriteOrLiked = "IsFavoriteOrLiked", + Random = "Random", +} + +export enum SortOrderOption { + Ascending = "Ascending", + Descending = "Descending", +} export const sortOptions: { - key: ItemSortBy; + key: SortByOption; value: string; }[] = [ - { key: "SortName", value: "Name" }, - { key: "CommunityRating", value: "Community Rating" }, - { key: "CriticRating", value: "Critics Rating" }, - { key: "DateCreated", value: "Date Added" }, - // Only works for shows (last episode added) keeping for future ref. - // { key: "DateLastContentAdded", value: "Content Added" }, - { key: "DatePlayed", value: "Date Played" }, - { key: "PlayCount", value: "Play Count" }, - { key: "ProductionYear", value: "Production Year" }, - { key: "Runtime", value: "Runtime" }, - { key: "OfficialRating", value: "Official Rating" }, - { key: "PremiereDate", value: "Premiere Date" }, - { key: "StartDate", value: "Start Date" }, - { key: "IsUnplayed", value: "Is Unplayed" }, - { key: "IsPlayed", value: "Is Played" }, - // Broken in JF - // { key: "VideoBitRate", value: "Video Bit Rate" }, - { key: "AirTime", value: "Air Time" }, - { key: "Studio", value: "Studio" }, - { key: "IsFavoriteOrLiked", value: "Is Favorite Or Liked" }, - { key: "Random", value: "Random" }, + { key: SortByOption.Default, value: "Default" }, + { key: SortByOption.SortName, value: "Name" }, + { key: SortByOption.CommunityRating, value: "Community Rating" }, + { key: SortByOption.CriticRating, value: "Critics Rating" }, + { key: SortByOption.DateCreated, value: "Date Added" }, + { key: SortByOption.DatePlayed, value: "Date Played" }, + { key: SortByOption.PlayCount, value: "Play Count" }, + { key: SortByOption.ProductionYear, value: "Production Year" }, + { key: SortByOption.Runtime, value: "Runtime" }, + { key: SortByOption.OfficialRating, value: "Official Rating" }, + { key: SortByOption.PremiereDate, value: "Premiere Date" }, + { key: SortByOption.StartDate, value: "Start Date" }, + { key: SortByOption.IsUnplayed, value: "Is Unplayed" }, + { key: SortByOption.IsPlayed, value: "Is Played" }, + { key: SortByOption.AirTime, value: "Air Time" }, + { key: SortByOption.Studio, value: "Studio" }, + { key: SortByOption.IsFavoriteOrLiked, value: "Is Favorite Or Liked" }, + { key: SortByOption.Random, value: "Random" }, ]; export const sortOrderOptions: { - key: SortOrder; + key: SortOrderOption; value: string; }[] = [ - { key: "Ascending", value: "Ascending" }, - { key: "Descending", value: "Descending" }, + { key: SortOrderOption.Ascending, value: "Ascending" }, + { key: SortOrderOption.Descending, value: "Descending" }, ]; export const genreFilterAtom = atom([]); export const tagsFilterAtom = atom([]); export const yearFilterAtom = atom([]); -export const sortByAtom = atom<[typeof sortOptions][number]>([sortOptions[0]]); -export const sortOrderAtom = atom<[typeof sortOrderOptions][number]>([ - sortOrderOptions[0], +export const sortByAtom = atom([SortByOption.Default]); +export const sortOrderAtom = atom([ + SortOrderOption.Ascending, ]);