mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-27 17:18:29 +01:00
first commit
This commit is contained in:
46
app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx
Normal file
46
app/(auth)/(tabs)/(home,libraries,search)/livetv/_layout.tsx
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
11
app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx
Normal file
11
app/(auth)/(tabs)/(home,libraries,search)/livetv/guide.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
154
app/(auth)/(tabs)/(home,libraries,search)/livetv/programs.tsx
Normal file
154
app/(auth)/(tabs)/(home,libraries,search)/livetv/programs.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
11
app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx
Normal file
11
app/(auth)/(tabs)/(home,libraries,search)/livetv/series.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user