first commit

This commit is contained in:
Fredrik Burmester
2024-10-05 09:17:54 +02:00
parent d064622055
commit 387add4c83
14 changed files with 384 additions and 24 deletions

View File

@@ -0,0 +1,46 @@
import { createMaterialTopTabNavigator } from "@react-navigation/material-top-tabs";
import type {
MaterialTopTabNavigationOptions,
MaterialTopTabNavigationEventMap,
} from "@react-navigation/material-top-tabs";
import { ParamListBase, TabNavigationState } from "@react-navigation/native";
import { withLayoutContext } from "expo-router";
const { Navigator } = createMaterialTopTabNavigator();
export const Tab = withLayoutContext<
MaterialTopTabNavigationOptions,
typeof Navigator,
TabNavigationState<ParamListBase>,
MaterialTopTabNavigationEventMap
>(Navigator);
const Layout = () => {
return (
<Tab
initialRouteName="programs"
keyboardDismissMode="none"
screenOptions={{
tabBarLabelStyle: { fontSize: 10 },
tabBarItemStyle: {
width: 100,
},
tabBarStyle: { backgroundColor: "black" },
animationEnabled: true,
lazy: true,
swipeEnabled: true,
tabBarIndicatorStyle: { backgroundColor: "#9334E9" },
tabBarScrollEnabled: true,
}}
>
<Tab.Screen name="programs" />
<Tab.Screen name="guide" />
<Tab.Screen name="channels" />
<Tab.Screen name="recordings" />
<Tab.Screen name="schedule" />
<Tab.Screen name="series" />
</Tab>
);
};
export default Layout;

View File

@@ -0,0 +1,56 @@
import { ItemImage } from "@/components/common/ItemImage";
import { Text } from "@/components/common/Text";
import { ItemPoster } from "@/components/posters/ItemPoster";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { getLiveTvApi } from "@jellyfin/sdk/lib/utils/api";
import { FlashList } from "@shopify/flash-list";
import { useQuery } from "@tanstack/react-query";
import { Image } from "expo-image";
import { useAtom } from "jotai";
import React from "react";
import { View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function page() {
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const insets = useSafeAreaInsets();
const { data: channels } = useQuery({
queryKey: ["livetv", "channels"],
queryFn: async () => {
if (!api) return [];
const res = await getLiveTvApi(api).getLiveTvChannels({
startIndex: 0,
fields: ["PrimaryImageAspectRatio"],
limit: 100,
userId: user?.Id,
});
return res.data.Items;
},
});
return (
<View className="flex flex-1">
<FlashList
data={channels}
estimatedItemSize={76}
renderItem={({ item }) => (
<View className="flex flex-row items-center px-4 mb-2">
<View className="w-22 mr-4 rounded-lg overflow-hidden">
<ItemImage
style={{
aspectRatio: "1/1",
width: 60,
borderRadius: 8,
}}
item={item}
/>
</View>
<Text className="font-bold">{item.Name}</Text>
</View>
)}
/>
</View>
);
}

View File

@@ -0,0 +1,11 @@
import { Text } from "@/components/common/Text";
import React from "react";
import { View } from "react-native";
export default function page() {
return (
<View>
<Text>Not implemented</Text>
</View>
);
}

View File

@@ -0,0 +1,154 @@
import { Text } from "@/components/common/Text";
import { ScrollingCollectionList } from "@/components/home/ScrollingCollectionList";
import { TAB_HEIGHT } from "@/constants/Values";
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import { getLiveTvApi } from "@jellyfin/sdk/lib/utils/api";
import { useQuery } from "@tanstack/react-query";
import { useAtom } from "jotai";
import React from "react";
import {
RefreshControl,
ScrollView,
SectionListComponent,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function page() {
const [api] = useAtom(apiAtom);
const [user] = useAtom(userAtom);
const insets = useSafeAreaInsets();
return (
<ScrollView
nestedScrollEnabled
contentInsetAdjustmentBehavior="automatic"
key={"home"}
contentContainerStyle={{
paddingLeft: insets.left,
paddingRight: insets.right,
paddingBottom: 16,
paddingTop: 8,
}}
style={{
marginBottom: TAB_HEIGHT,
}}
>
<View className="flex flex-col space-y-2">
<ScrollingCollectionList
queryKey={["livetv", "recommended"]}
title={"On now"}
queryFn={async () => {
if (!api) return [] as BaseItemDto[];
const res = await getLiveTvApi(api).getRecommendedPrograms({
userId: user?.Id,
isAiring: true,
limit: 24,
imageTypeLimit: 1,
enableImageTypes: ["Primary", "Thumb", "Backdrop"],
enableTotalRecordCount: false,
fields: ["ChannelInfo", "PrimaryImageAspectRatio"],
});
return res.data.Items || [];
}}
orientation="horizontal"
/>
<ScrollingCollectionList
queryKey={["livetv", "shows"]}
title={"Shows"}
queryFn={async () => {
if (!api) return [] as BaseItemDto[];
const res = await getLiveTvApi(api).getLiveTvPrograms({
userId: user?.Id,
hasAired: false,
limit: 9,
isMovie: false,
isSeries: true,
isSports: false,
isNews: false,
isKids: false,
enableTotalRecordCount: false,
fields: ["ChannelInfo", "PrimaryImageAspectRatio"],
enableImageTypes: ["Primary", "Thumb", "Backdrop"],
});
return res.data.Items || [];
}}
orientation="horizontal"
/>
<ScrollingCollectionList
queryKey={["livetv", "movies"]}
title={"Movies"}
queryFn={async () => {
if (!api) return [] as BaseItemDto[];
const res = await getLiveTvApi(api).getLiveTvPrograms({
userId: user?.Id,
hasAired: false,
limit: 9,
isMovie: true,
enableTotalRecordCount: false,
fields: ["ChannelInfo"],
enableImageTypes: ["Primary", "Thumb", "Backdrop"],
});
return res.data.Items || [];
}}
orientation="horizontal"
/>
<ScrollingCollectionList
queryKey={["livetv", "sports"]}
title={"Sports"}
queryFn={async () => {
if (!api) return [] as BaseItemDto[];
const res = await getLiveTvApi(api).getLiveTvPrograms({
userId: user?.Id,
hasAired: false,
limit: 9,
isSports: true,
enableTotalRecordCount: false,
fields: ["ChannelInfo"],
enableImageTypes: ["Primary", "Thumb", "Backdrop"],
});
return res.data.Items || [];
}}
orientation="horizontal"
/>
<ScrollingCollectionList
queryKey={["livetv", "kids"]}
title={"For Kids"}
queryFn={async () => {
if (!api) return [] as BaseItemDto[];
const res = await getLiveTvApi(api).getLiveTvPrograms({
userId: user?.Id,
hasAired: false,
limit: 9,
isKids: true,
enableTotalRecordCount: false,
fields: ["ChannelInfo"],
enableImageTypes: ["Primary", "Thumb", "Backdrop"],
});
return res.data.Items || [];
}}
orientation="horizontal"
/>
<ScrollingCollectionList
queryKey={["livetv", "news"]}
title={"News"}
queryFn={async () => {
if (!api) return [] as BaseItemDto[];
const res = await getLiveTvApi(api).getLiveTvPrograms({
userId: user?.Id,
hasAired: false,
limit: 9,
isNews: true,
enableTotalRecordCount: false,
fields: ["ChannelInfo"],
enableImageTypes: ["Primary", "Thumb", "Backdrop"],
});
return res.data.Items || [];
}}
orientation="horizontal"
/>
</View>
</ScrollView>
);
}

View File

@@ -0,0 +1,11 @@
import { Text } from "@/components/common/Text";
import React from "react";
import { View } from "react-native";
export default function page() {
return (
<View>
<Text>Not implemented</Text>
</View>
);
}

View File

@@ -0,0 +1,11 @@
import { Text } from "@/components/common/Text";
import React from "react";
import { View } from "react-native";
export default function page() {
return (
<View>
<Text>Not implemented</Text>
</View>
);
}

View File

@@ -0,0 +1,11 @@
import { Text } from "@/components/common/Text";
import React from "react";
import { View } from "react-native";
export default function page() {
return (
<View>
<Text>Not implemented</Text>
</View>
);
}