From cf2beb829905de53ebb46477fb588ce318b3079b Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sun, 5 Jan 2025 15:46:44 +0100 Subject: [PATCH] feat: hide libraries --- app/(auth)/(tabs)/(home)/_layout.tsx | 6 + .../(home)/settings/hide-libraries/page.tsx | 60 +++++++++ app/(auth)/(tabs)/(libraries)/index.tsx | 22 ++-- components/common/TouchableItemRouter.tsx | 115 ++++++------------ components/settings/OtherSettings.tsx | 9 +- utils/atoms/settings.ts | 2 + 6 files changed, 125 insertions(+), 89 deletions(-) create mode 100644 app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx index 3509be51..98ee5c0d 100644 --- a/app/(auth)/(tabs)/(home)/_layout.tsx +++ b/app/(auth)/(tabs)/(home)/_layout.tsx @@ -77,6 +77,12 @@ export default function IndexLayout() { title: "", }} /> + {Object.entries(nestedTabPageScreenOptions).map(([name, options]) => ( ))} diff --git a/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx b/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx new file mode 100644 index 00000000..bddf62b8 --- /dev/null +++ b/app/(auth)/(tabs)/(home)/settings/hide-libraries/page.tsx @@ -0,0 +1,60 @@ +import { Text } from "@/components/common/Text"; +import { ListGroup } from "@/components/list/ListGroup"; +import { ListItem } from "@/components/list/ListItem"; +import { Loader } from "@/components/Loader"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { useSettings } from "@/utils/atoms/settings"; +import { getUserViewsApi } from "@jellyfin/sdk/lib/utils/api"; +import { useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; +import { Switch, View } from "react-native"; + +export default function page() { + const [settings, updateSettings] = useSettings(); + const user = useAtomValue(userAtom); + const api = useAtomValue(apiAtom); + + const { data, isLoading: isLoading } = useQuery({ + queryKey: ["user-views", user?.Id], + queryFn: async () => { + const response = await getUserViewsApi(api!).getUserViews({ + userId: user?.Id, + }); + + return response.data.Items || null; + }, + }); + + if (!settings) return null; + + if (isLoading) + return ( + + + + ); + + return ( + + + {data?.map((view) => ( + {}}> + { + updateSettings({ + hiddenLibraries: value + ? [...(settings.hiddenLibraries || []), view.Id!] + : settings.hiddenLibraries?.filter((id) => id !== view.Id), + }); + }} + /> + + ))} + + + Select the libraries you want to hide from the Library tab. + + + ); +} diff --git a/app/(auth)/(tabs)/(libraries)/index.tsx b/app/(auth)/(tabs)/(libraries)/index.tsx index ef729254..e8c3f766 100644 --- a/app/(auth)/(tabs)/(libraries)/index.tsx +++ b/app/(auth)/(tabs)/(libraries)/index.tsx @@ -10,7 +10,7 @@ import { import { FlashList } from "@shopify/flash-list"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAtom } from "jotai"; -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; import { StyleSheet, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; @@ -23,20 +23,20 @@ export default function index() { const { data, isLoading: isLoading } = useQuery({ queryKey: ["user-views", user?.Id], queryFn: async () => { - if (!api || !user?.Id) { - return null; - } - - const response = await getUserViewsApi(api).getUserViews({ - userId: user.Id, + const response = await getUserViewsApi(api!).getUserViews({ + userId: user?.Id, }); return response.data.Items || null; }, - enabled: !!api && !!user?.Id, - staleTime: 60 * 1000 * 60, + staleTime: 60, }); + const libraries = useMemo( + () => data?.filter((l) => !settings?.hiddenLibraries?.includes(l.Id!)), + [data, settings?.hiddenLibraries] + ); + useEffect(() => { for (const item of data || []) { queryClient.prefetchQuery({ @@ -63,7 +63,7 @@ export default function index() { ); - if (!data) + if (!libraries) return ( No libraries found @@ -81,7 +81,7 @@ export default function index() { paddingLeft: insets.left, paddingRight: insets.right, }} - data={data} + data={libraries} renderItem={({ item }) => } keyExtractor={(item) => item.Id || ""} ItemSeparatorComponent={() => diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx index c13e9821..04d36dc7 100644 --- a/components/common/TouchableItemRouter.tsx +++ b/components/common/TouchableItemRouter.tsx @@ -4,9 +4,11 @@ import { BaseItemPerson, } from "@jellyfin/sdk/lib/generated-client/models"; import { useRouter, useSegments } from "expo-router"; -import { PropsWithChildren } from "react"; +import { PropsWithChildren, useCallback } from "react"; import { TouchableOpacity, TouchableOpacityProps } from "react-native"; import * as ContextMenu from "zeego/context-menu"; +import { useActionSheet } from "@expo/react-native-action-sheet"; +import * as Haptics from "expo-haptics"; interface Props extends TouchableOpacityProps { item: BaseItemDto; @@ -16,8 +18,6 @@ export const itemRouter = ( item: BaseItemDto | BaseItemPerson, from: string ) => { - console.log(item.Type, item?.CollectionType); - if ("CollectionType" in item && item.CollectionType === "livetv") { return `/(auth)/(tabs)/${from}/livetv`; } @@ -68,10 +68,33 @@ export const TouchableItemRouter: React.FC> = ({ }) => { const router = useRouter(); const segments = useSegments(); + const { showActionSheetWithOptions } = useActionSheet(); + const markAsPlayedStatus = useMarkAsPlayed(item); const from = segments[2]; - const markAsPlayedStatus = useMarkAsPlayed(item); + const showActionSheet = useCallback(() => { + if (!(item.Type === "Movie" || item.Type === "Episode")) return; + + const options = ["Mark as Played", "Mark as Not Played", "Cancel"]; + const cancelButtonIndex = 2; + + showActionSheetWithOptions( + { + options, + cancelButtonIndex, + }, + async (selectedIndex) => { + if (selectedIndex === 0) { + await markAsPlayedStatus(true); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } else if (selectedIndex === 1) { + await markAsPlayedStatus(false); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + } + } + ); + }, [showActionSheetWithOptions, markAsPlayedStatus]); if ( from === "(home)" || @@ -80,78 +103,16 @@ export const TouchableItemRouter: React.FC> = ({ from === "(favorites)" ) return ( - - - { - const url = itemRouter(item, from); - // @ts-ignore - router.push(url); - }} - {...props} - > - {children} - - - - Actions - { - markAsPlayedStatus(true); - }} - shouldDismissMenuOnSelect - > - - Mark as watched - - - - { - markAsPlayedStatus(false); - }} - shouldDismissMenuOnSelect - destructive - > - - Mark as not watched - - - - - + { + const url = itemRouter(item, from); + // @ts-expect-error + router.push(url); + }} + {...props} + > + {children} + ); }; diff --git a/components/settings/OtherSettings.tsx b/components/settings/OtherSettings.tsx index d280a167..8987379a 100644 --- a/components/settings/OtherSettings.tsx +++ b/components/settings/OtherSettings.tsx @@ -15,10 +15,12 @@ import * as DropdownMenu from "zeego/dropdown-menu"; import { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; +import { useRouter } from "expo-router"; interface Props extends ViewProps {} export const OtherSettings: React.FC = () => { + const router = useRouter(); const [settings, updateSettings] = useSettings(); /******************** @@ -54,7 +56,7 @@ export const OtherSettings: React.FC = () => { if (!settings) return null; return ( - + { } /> + router.push("/settings/hide-libraries/page")} + title="Hide Libraries" + showArrow + /> ); }; diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index c37dd4eb..fa201610 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -88,6 +88,7 @@ export type Settings = { remuxConcurrentLimit: 1 | 2 | 3 | 4; safeAreaInControlsEnabled: boolean; jellyseerrServerUrl?: string; + hiddenLibraries?: string[]; }; const loadSettings = (): Settings => { @@ -126,6 +127,7 @@ const loadSettings = (): Settings => { remuxConcurrentLimit: 1, safeAreaInControlsEnabled: true, jellyseerrServerUrl: undefined, + hiddenLibraries: [], }; try {