Skip to content

Commit

Permalink
fix: use scrollview for better design
Browse files Browse the repository at this point in the history
  • Loading branch information
fredrikburmester committed Aug 18, 2024
1 parent 30280e8 commit dbb7c6c
Showing 1 changed file with 191 additions and 157 deletions.
348 changes: 191 additions & 157 deletions app/(auth)/(tabs)/library/collections/[collectionId].tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ColumnItem } from "@/components/common/ColumnItem";
import { Text } from "@/components/common/Text";
import { TouchableItemRouter } from "@/components/common/TouchableItemRouter";
import { FilterButton } from "@/components/filters/FilterButton";
import { ResetFiltersButton } from "@/components/filters/ResetFiltersButton";
Expand All @@ -20,19 +20,31 @@ import {
BaseItemKind,
} from "@jellyfin/sdk/lib/generated-client/models";
import { getFilterApi, getItemsApi } from "@jellyfin/sdk/lib/utils/api";
import { FlashList } from "@shopify/flash-list";
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import { useLocalSearchParams } from "expo-router";
import { Stack, useLocalSearchParams, useNavigation } from "expo-router";
import { useAtom } from "jotai";
import React, { useCallback, useMemo } from "react";
import { ScrollView, View } from "react-native";
import React, { useCallback, useEffect, useMemo } from "react";
import { NativeScrollEvent, ScrollView, View } from "react-native";

const isCloseToBottom = ({
layoutMeasurement,
contentOffset,
contentSize,
}: NativeScrollEvent) => {
const paddingToBottom = 200;
return (
layoutMeasurement.height + contentOffset.y >=
contentSize.height - paddingToBottom
);
};

const page: React.FC = () => {
const searchParams = useLocalSearchParams();
const { collectionId } = searchParams as { collectionId: string };

const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const navigation = useNavigation();

const [selectedGenres, setSelectedGenres] = useAtom(genreFilterAtom);
const [selectedYears, setSelectedYears] = useAtom(yearFilterAtom);
Expand Down Expand Up @@ -85,7 +97,7 @@ const page: React.FC = () => {
const response = await getItemsApi(api).getItems({
userId: user?.Id,
parentId: collectionId,
limit: 50,
limit: 66,
startIndex: pageParam,
sortBy: [sortBy[0].key, "SortName", "ProductionYear"],
sortOrder: [sortOrder[0].key],
Expand Down Expand Up @@ -126,9 +138,21 @@ const page: React.FC = () => {
],
queryFn: fetchItems,
getNextPageParam: (lastPage, pages) => {
const totalItems = lastPage?.TotalRecordCount || 0;
if ((lastPage?.Items?.length || 0) < totalItems) {
return lastPage?.Items?.length;
if (
!lastPage?.Items ||
!lastPage?.TotalRecordCount ||
lastPage?.TotalRecordCount === 0
)
return undefined;

const totalItems = lastPage.TotalRecordCount;
const accumulatedItems = pages.reduce(
(acc, curr) => acc + (curr?.Items?.length || 0),
0
);

if (accumulatedItems < totalItems) {
return lastPage?.Items?.length * pages.length;
} else {
return undefined;
}
Expand All @@ -141,160 +165,170 @@ const page: React.FC = () => {
return data?.pages.flatMap((page) => page?.Items)[0]?.Type || null;
}, [data]);

const flatData = useMemo(() => {
return data?.pages.flatMap((p) => p?.Items) || [];
}, [data]);

if (!collection || !collection.CollectionType) return null;

return (
<>
<FlashList
refreshing={isFetching}
data={data?.pages.flatMap((page) => page?.Items) || []}
horizontal={false}
contentInsetAdjustmentBehavior="automatic"
contentContainerStyle={{
paddingTop: 17,
paddingHorizontal: 10,
paddingBottom: 150,
}}
onEndReached={fetchNextPage}
onEndReachedThreshold={0.5}
renderItem={({ item, index }) =>
item ? (
<ColumnItem index={index} numColumns={3} style={{}}>
<TouchableItemRouter
style={{
width: "100%",
padding: 4,
}}
item={item}
>
<MoviePoster item={item} />
<ItemCardText item={item} />
</TouchableItemRouter>
</ColumnItem>
) : null
<ScrollView
contentInsetAdjustmentBehavior="automatic"
onScroll={({ nativeEvent }) => {
if (isCloseToBottom(nativeEvent)) {
fetchNextPage();
}
numColumns={3}
estimatedItemSize={200}
ListHeaderComponent={
<View className="mb-4">
<ScrollView horizontal>
<View className="flex flex-row space-x-1">
<ResetFiltersButton />
<FilterButton
collectionId={collectionId}
queryKey="genreFilter"
queryFn={async () => {
if (!api) return null;
const response = await getFilterApi(
api
).getQueryFiltersLegacy({
userId: user?.Id,
includeItemTypes: type ? [type] : [],
parentId: collectionId,
});
return response.data.Genres || [];
}}
set={setSelectedGenres}
values={selectedGenres}
title="Genres"
renderItemLabel={(item) => item.toString()}
searchFilter={(item, search) =>
item.toLowerCase().includes(search.toLowerCase())
}
/>
<FilterButton
collectionId={collectionId}
queryKey="tagsFilter"
queryFn={async () => {
if (!api) return null;
const response = await getFilterApi(
api
).getQueryFiltersLegacy({
userId: user?.Id,
includeItemTypes: type ? [type] : [],
parentId: collectionId,
});
return response.data.Tags || [];
}}
set={setSelectedTags}
values={selectedTags}
title="Tags"
renderItemLabel={(item) => item.toString()}
searchFilter={(item, search) =>
item.toLowerCase().includes(search.toLowerCase())
}
/>
<FilterButton
collectionId={collectionId}
queryKey="yearFilter"
queryFn={async () => {
if (!api) return null;
const response = await getFilterApi(
api
).getQueryFiltersLegacy({
userId: user?.Id,
includeItemTypes: type ? [type] : [],
parentId: collectionId,
});
return (
response.data.Years?.sort((a, b) => b - a).map((y) =>
y.toString()
) || []
);
}}
set={setSelectedYears}
values={selectedYears}
title="Years"
renderItemLabel={(item) => item.toString()}
searchFilter={(item, search) =>
item.toLowerCase().includes(search.toLowerCase())
}
/>
<FilterButton
collectionId={collectionId}
queryKey="sortByFilter"
queryFn={async () => {
return sortOptions;
}}
set={setSortBy}
values={sortBy}
title="Sort by"
renderItemLabel={(item) => item.value}
searchFilter={(item, search) =>
item.value.toLowerCase().includes(search.toLowerCase()) ||
item.value.toLowerCase().includes(search.toLowerCase())
}
showSearch={false}
/>
<FilterButton
showSearch={false}
collectionId={collectionId}
queryKey="orderByFilter"
queryFn={async () => {
return sortOrderOptions;
}}
set={setSortOrder}
values={sortOrder}
title="Order by"
renderItemLabel={(item) => item.value}
searchFilter={(item, search) =>
item.value.toLowerCase().includes(search.toLowerCase()) ||
item.value.toLowerCase().includes(search.toLowerCase())
}
/>
</View>
</ScrollView>
{!type && isFetching && (
<Loader
style={{
marginTop: 300,
}}
scrollEventThrottle={400}
>
<View className="mt-4 mb-24">
<View className="mb-4">
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<View className="flex flex-row space-x-1 px-3">
<ResetFiltersButton />
<FilterButton
collectionId={collectionId}
queryKey="genreFilter"
queryFn={async () => {
if (!api) return null;
const response = await getFilterApi(
api
).getQueryFiltersLegacy({
userId: user?.Id,
includeItemTypes: type ? [type] : [],
parentId: collectionId,
});
return response.data.Genres || [];
}}
set={setSelectedGenres}
values={selectedGenres}
title="Genres"
renderItemLabel={(item) => item.toString()}
searchFilter={(item, search) =>
item.toLowerCase().includes(search.toLowerCase())
}
/>
)}
</View>
}
/>
</>
<FilterButton
collectionId={collectionId}
queryKey="tagsFilter"
queryFn={async () => {
if (!api) return null;
const response = await getFilterApi(
api
).getQueryFiltersLegacy({
userId: user?.Id,
includeItemTypes: type ? [type] : [],
parentId: collectionId,
});
return response.data.Tags || [];
}}
set={setSelectedTags}
values={selectedTags}
title="Tags"
renderItemLabel={(item) => item.toString()}
searchFilter={(item, search) =>
item.toLowerCase().includes(search.toLowerCase())
}
/>
<FilterButton
collectionId={collectionId}
queryKey="yearFilter"
queryFn={async () => {
if (!api) return null;
const response = await getFilterApi(
api
).getQueryFiltersLegacy({
userId: user?.Id,
includeItemTypes: type ? [type] : [],
parentId: collectionId,
});
return (
response.data.Years?.sort((a, b) => b - a).map((y) =>
y.toString()
) || []
);
}}
set={setSelectedYears}
values={selectedYears}
title="Years"
renderItemLabel={(item) => item.toString()}
searchFilter={(item, search) =>
item.toLowerCase().includes(search.toLowerCase())
}
/>
<FilterButton
icon="sort"
collectionId={collectionId}
queryKey="sortByFilter"
queryFn={async () => {
return sortOptions;
}}
set={setSortBy}
values={sortBy}
title="Sort by"
renderItemLabel={(item) => item.value}
searchFilter={(item, search) =>
item.value.toLowerCase().includes(search.toLowerCase()) ||
item.value.toLowerCase().includes(search.toLowerCase())
}
showSearch={false}
/>
<FilterButton
icon="sort"
showSearch={false}
collectionId={collectionId}
queryKey="orderByFilter"
queryFn={async () => {
return sortOrderOptions;
}}
set={setSortOrder}
values={sortOrder}
title="Order by"
renderItemLabel={(item) => item.value}
searchFilter={(item, search) =>
item.value.toLowerCase().includes(search.toLowerCase()) ||
item.value.toLowerCase().includes(search.toLowerCase())
}
/>
</View>
</ScrollView>
{!type && isFetching && (
<Loader
style={{
marginTop: 300,
}}
/>
)}
</View>
<View className="flex flex-row flex-wrap px-4 justify-between after:content-['']">
{flatData.map(
(item, index) =>
item && (
<TouchableItemRouter
key={`${item.Id}`}
style={{
width: "32%",
marginBottom: 4,
}}
item={item}
className={`
`}
>
<MoviePoster item={item} />
<ItemCardText item={item} />
</TouchableItemRouter>
)
)}
{flatData.length % 3 !== 0 && (
<View
style={{
width: "33%",
}}
></View>
)}
</View>
</View>
</ScrollView>
);
};

Expand Down

0 comments on commit dbb7c6c

Please sign in to comment.