diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/collections/[collectionId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/collections/[collectionId].tsx index 5fd125c9..80144416 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/collections/[collectionId].tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/collections/[collectionId].tsx @@ -326,7 +326,7 @@ const page: React.FC = () => { data={[ { key: "reset", - component: , + component: , }, { key: "genre", diff --git a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx index b8b9d132..0d04e136 100644 --- a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx +++ b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx @@ -39,6 +39,7 @@ import { TVPosterCard } from "@/components/tv/TVPosterCard"; import { useScaledTVPosterSizes } from "@/constants/TVPosterSizes"; import { useScaledTVTypography } from "@/constants/TVTypography"; import useRouter from "@/hooks/useAppRouter"; +import { useFilterReset } from "@/hooks/useFilterReset"; import { useOrientation } from "@/hooks/useOrientation"; import { useRefreshLibraryOnFocus } from "@/hooks/useRefreshLibraryOnFocus"; import { useTVItemActionModal } from "@/hooks/useTVItemActionModal"; @@ -521,7 +522,7 @@ const Page = () => { data={[ { key: "reset", - component: , + component: , }, { key: "genre", @@ -688,19 +689,9 @@ const Page = () => { ], ); - // TV Filter bar header - const hasActiveFilters = - selectedGenres.length > 0 || - selectedYears.length > 0 || - selectedTags.length > 0 || - filterBy.length > 0; - - const resetAllFilters = useCallback(() => { - setSelectedGenres([]); - setSelectedYears([]); - setSelectedTags([]); - _setFilterBy([]); - }, [setSelectedGenres, setSelectedYears, setSelectedTags, _setFilterBy]); + // Filter bar reset + visibility, shared with the mobile ResetFiltersButton so + // sort/order can't be forgotten on one path (it used to be reset on neither). + const { hasActiveFilters, resetAllFilters } = useFilterReset(libraryId); // TV Filter options - with "All" option for clearable filters const tvGenreFilterOptions = useMemo( diff --git a/components/filters/ResetFiltersButton.tsx b/components/filters/ResetFiltersButton.tsx index 856ccd3b..1635f4dd 100644 --- a/components/filters/ResetFiltersButton.tsx +++ b/components/filters/ResetFiltersButton.tsx @@ -1,38 +1,24 @@ import { Ionicons } from "@expo/vector-icons"; -import { useAtom } from "jotai"; import { TouchableOpacity, type TouchableOpacityProps } from "react-native"; -import { - filterByAtom, - genreFilterAtom, - tagsFilterAtom, - yearFilterAtom, -} from "@/utils/atoms/filters"; +import { useFilterReset } from "@/hooks/useFilterReset"; -interface Props extends TouchableOpacityProps {} +interface Props extends TouchableOpacityProps { + libraryId: string; +} -export const ResetFiltersButton: React.FC = ({ ...props }) => { - const [selectedGenres, setSelectedGenres] = useAtom(genreFilterAtom); - const [selectedTags, setSelectedTags] = useAtom(tagsFilterAtom); - const [selectedYears, setSelectedYears] = useAtom(yearFilterAtom); - const [selectedFilters, setSelectedFilters] = useAtom(filterByAtom); +export const ResetFiltersButton: React.FC = ({ + libraryId, + ...props +}) => { + const { hasActiveFilters, resetAllFilters } = useFilterReset(libraryId); - if ( - selectedGenres.length === 0 && - selectedTags.length === 0 && - selectedYears.length === 0 && - selectedFilters.length === 0 - ) { + if (!hasActiveFilters) { return null; } return ( { - setSelectedGenres([]); - setSelectedTags([]); - setSelectedYears([]); - setSelectedFilters([]); - }} + onPress={resetAllFilters} className='bg-purple-600 rounded-full w-[30px] h-[30px] flex items-center justify-center mr-1' {...props} > diff --git a/hooks/useFilterReset.ts b/hooks/useFilterReset.ts new file mode 100644 index 00000000..aad25f85 --- /dev/null +++ b/hooks/useFilterReset.ts @@ -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 }; +};