mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-26 07:40:31 +01:00
fix(library): reset (X) also clears sort & order, via a shared hook
The mobile ResetFiltersButton and the TV filter header each had their own active-state and reset logic, and both ignored sort & order — so the reset chip never appeared for a changed sort and reset never restored it. Centralise both in useFilterReset(libraryId): the chip now reflects a non-default sort, and reset restores SortName/Ascending and clears the per-library persisted sort/order/filter preferences so the reset sticks.
This commit is contained in:
@@ -326,7 +326,7 @@ const page: React.FC = () => {
|
|||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
key: "reset",
|
key: "reset",
|
||||||
component: <ResetFiltersButton />,
|
component: <ResetFiltersButton libraryId={collectionId} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "genre",
|
key: "genre",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import { TVPosterCard } from "@/components/tv/TVPosterCard";
|
|||||||
import { useScaledTVPosterSizes } from "@/constants/TVPosterSizes";
|
import { useScaledTVPosterSizes } from "@/constants/TVPosterSizes";
|
||||||
import { useScaledTVTypography } from "@/constants/TVTypography";
|
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
|
import { useFilterReset } from "@/hooks/useFilterReset";
|
||||||
import { useOrientation } from "@/hooks/useOrientation";
|
import { useOrientation } from "@/hooks/useOrientation";
|
||||||
import { useRefreshLibraryOnFocus } from "@/hooks/useRefreshLibraryOnFocus";
|
import { useRefreshLibraryOnFocus } from "@/hooks/useRefreshLibraryOnFocus";
|
||||||
import { useTVItemActionModal } from "@/hooks/useTVItemActionModal";
|
import { useTVItemActionModal } from "@/hooks/useTVItemActionModal";
|
||||||
@@ -521,7 +522,7 @@ const Page = () => {
|
|||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
key: "reset",
|
key: "reset",
|
||||||
component: <ResetFiltersButton />,
|
component: <ResetFiltersButton libraryId={libraryId} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "genre",
|
key: "genre",
|
||||||
@@ -688,19 +689,9 @@ const Page = () => {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// TV Filter bar header
|
// Filter bar reset + visibility, shared with the mobile ResetFiltersButton so
|
||||||
const hasActiveFilters =
|
// sort/order can't be forgotten on one path (it used to be reset on neither).
|
||||||
selectedGenres.length > 0 ||
|
const { hasActiveFilters, resetAllFilters } = useFilterReset(libraryId);
|
||||||
selectedYears.length > 0 ||
|
|
||||||
selectedTags.length > 0 ||
|
|
||||||
filterBy.length > 0;
|
|
||||||
|
|
||||||
const resetAllFilters = useCallback(() => {
|
|
||||||
setSelectedGenres([]);
|
|
||||||
setSelectedYears([]);
|
|
||||||
setSelectedTags([]);
|
|
||||||
_setFilterBy([]);
|
|
||||||
}, [setSelectedGenres, setSelectedYears, setSelectedTags, _setFilterBy]);
|
|
||||||
|
|
||||||
// TV Filter options - with "All" option for clearable filters
|
// TV Filter options - with "All" option for clearable filters
|
||||||
const tvGenreFilterOptions = useMemo(
|
const tvGenreFilterOptions = useMemo(
|
||||||
|
|||||||
@@ -1,38 +1,24 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { TouchableOpacity, type TouchableOpacityProps } from "react-native";
|
import { TouchableOpacity, type TouchableOpacityProps } from "react-native";
|
||||||
import {
|
import { useFilterReset } from "@/hooks/useFilterReset";
|
||||||
filterByAtom,
|
|
||||||
genreFilterAtom,
|
|
||||||
tagsFilterAtom,
|
|
||||||
yearFilterAtom,
|
|
||||||
} from "@/utils/atoms/filters";
|
|
||||||
|
|
||||||
interface Props extends TouchableOpacityProps {}
|
interface Props extends TouchableOpacityProps {
|
||||||
|
libraryId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const ResetFiltersButton: React.FC<Props> = ({ ...props }) => {
|
export const ResetFiltersButton: React.FC<Props> = ({
|
||||||
const [selectedGenres, setSelectedGenres] = useAtom(genreFilterAtom);
|
libraryId,
|
||||||
const [selectedTags, setSelectedTags] = useAtom(tagsFilterAtom);
|
...props
|
||||||
const [selectedYears, setSelectedYears] = useAtom(yearFilterAtom);
|
}) => {
|
||||||
const [selectedFilters, setSelectedFilters] = useAtom(filterByAtom);
|
const { hasActiveFilters, resetAllFilters } = useFilterReset(libraryId);
|
||||||
|
|
||||||
if (
|
if (!hasActiveFilters) {
|
||||||
selectedGenres.length === 0 &&
|
|
||||||
selectedTags.length === 0 &&
|
|
||||||
selectedYears.length === 0 &&
|
|
||||||
selectedFilters.length === 0
|
|
||||||
) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={resetAllFilters}
|
||||||
setSelectedGenres([]);
|
|
||||||
setSelectedTags([]);
|
|
||||||
setSelectedYears([]);
|
|
||||||
setSelectedFilters([]);
|
|
||||||
}}
|
|
||||||
className='bg-purple-600 rounded-full w-[30px] h-[30px] flex items-center justify-center mr-1'
|
className='bg-purple-600 rounded-full w-[30px] h-[30px] flex items-center justify-center mr-1'
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|||||||
84
hooks/useFilterReset.ts
Normal file
84
hooks/useFilterReset.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import {
|
||||||
|
FilterByPreferenceAtom,
|
||||||
|
filterByAtom,
|
||||||
|
genreFilterAtom,
|
||||||
|
SortByOption,
|
||||||
|
SortOrderOption,
|
||||||
|
sortByAtom,
|
||||||
|
sortByPreferenceAtom,
|
||||||
|
sortOrderAtom,
|
||||||
|
sortOrderPreferenceAtom,
|
||||||
|
tagsFilterAtom,
|
||||||
|
yearFilterAtom,
|
||||||
|
} from "@/utils/atoms/filters";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single source of truth for the library filter bar's "reset" action and its
|
||||||
|
* visibility. The mobile ResetFiltersButton and the TV filter header both use
|
||||||
|
* this so they can't drift — sort/order used to be reset on neither path, so
|
||||||
|
* the reset (X) never reflected a changed sort.
|
||||||
|
*
|
||||||
|
* A reset clears the session filters AND the per-library persisted preferences
|
||||||
|
* (sort, order, filterBy); otherwise the saved preference resurfaces when the
|
||||||
|
* library's mount effect re-applies it on the next entry.
|
||||||
|
*/
|
||||||
|
export const useFilterReset = (libraryId: string) => {
|
||||||
|
const [selectedGenres, setSelectedGenres] = useAtom(genreFilterAtom);
|
||||||
|
const [selectedYears, setSelectedYears] = useAtom(yearFilterAtom);
|
||||||
|
const [selectedTags, setSelectedTags] = useAtom(tagsFilterAtom);
|
||||||
|
const [filterBy, setFilterBy] = useAtom(filterByAtom);
|
||||||
|
const [sortBy, setSortBy] = useAtom(sortByAtom);
|
||||||
|
const [sortOrder, setSortOrder] = useAtom(sortOrderAtom);
|
||||||
|
const [, setSortByPreference] = useAtom(sortByPreferenceAtom);
|
||||||
|
const [, setSortOrderPreference] = useAtom(sortOrderPreferenceAtom);
|
||||||
|
const [, setFilterByPreference] = useAtom(FilterByPreferenceAtom);
|
||||||
|
|
||||||
|
// SortName / Ascending is the baseline a library opens with (mount-effect
|
||||||
|
// fallback), so any other value counts as an active, resettable sort.
|
||||||
|
const hasActiveFilters =
|
||||||
|
selectedGenres.length > 0 ||
|
||||||
|
selectedYears.length > 0 ||
|
||||||
|
selectedTags.length > 0 ||
|
||||||
|
filterBy.length > 0 ||
|
||||||
|
sortBy[0] !== SortByOption.SortName ||
|
||||||
|
sortOrder[0] !== SortOrderOption.Ascending;
|
||||||
|
|
||||||
|
const resetAllFilters = useCallback(() => {
|
||||||
|
setSelectedGenres([]);
|
||||||
|
setSelectedYears([]);
|
||||||
|
setSelectedTags([]);
|
||||||
|
setFilterBy([]);
|
||||||
|
setSortBy([SortByOption.SortName]);
|
||||||
|
setSortOrder([SortOrderOption.Ascending]);
|
||||||
|
setSortByPreference((prev) => {
|
||||||
|
const next = { ...prev };
|
||||||
|
delete next[libraryId];
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
setSortOrderPreference((prev) => {
|
||||||
|
const next = { ...prev };
|
||||||
|
delete next[libraryId];
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
setFilterByPreference((prev) => {
|
||||||
|
const next = { ...prev };
|
||||||
|
delete next[libraryId];
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
libraryId,
|
||||||
|
setSelectedGenres,
|
||||||
|
setSelectedYears,
|
||||||
|
setSelectedTags,
|
||||||
|
setFilterBy,
|
||||||
|
setSortBy,
|
||||||
|
setSortOrder,
|
||||||
|
setSortByPreference,
|
||||||
|
setSortOrderPreference,
|
||||||
|
setFilterByPreference,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { hasActiveFilters, resetAllFilters };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user