mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-06-11 08:23:09 +08:00
Update
This commit is contained in:
42
app/+html.tsx
Normal file
42
app/+html.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import {ScrollViewStyleReset} from 'expo-router/html';
|
||||
import {type PropsWithChildren} from 'react';
|
||||
|
||||
/**
|
||||
* This file is web-only and used to configure the root HTML for every web page during static rendering.
|
||||
* The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs.
|
||||
*/
|
||||
export default function Root({children}: PropsWithChildren) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
|
||||
{/*
|
||||
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
|
||||
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
|
||||
*/}
|
||||
<ScrollViewStyleReset />
|
||||
|
||||
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
|
||||
<style dangerouslySetInnerHTML={{__html: responsiveBackground}} />
|
||||
{/* Add any additional <head> elements that you want globally available on web... */}
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
const responsiveBackground = `
|
||||
body {
|
||||
background-color: #fff;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
}`;
|
||||
32
app/+not-found.tsx
Normal file
32
app/+not-found.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import {Link, Stack} from 'expo-router';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
import {ThemedText} from '@/components/ThemedText';
|
||||
import {ThemedView} from '@/components/ThemedView';
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{title: 'Oops!'}} />
|
||||
<ThemedView style={styles.container}>
|
||||
<ThemedText type="title">This screen doesn't exist.</ThemedText>
|
||||
<Link href="/" style={styles.link}>
|
||||
<ThemedText type="link">Go to home screen!</ThemedText>
|
||||
</Link>
|
||||
</ThemedView>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
link: {
|
||||
marginTop: 15,
|
||||
paddingVertical: 15,
|
||||
},
|
||||
});
|
||||
43
app/_layout.tsx
Normal file
43
app/_layout.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
DarkTheme,
|
||||
DefaultTheme,
|
||||
ThemeProvider,
|
||||
} from "@react-navigation/native";
|
||||
import { useFonts } from "expo-font";
|
||||
import { Stack } from "expo-router";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { useColorScheme } from "@/hooks/useColorScheme";
|
||||
|
||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
export default function RootLayout() {
|
||||
const colorScheme = useColorScheme();
|
||||
const [loaded, error] = useFonts({
|
||||
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded || error) {
|
||||
SplashScreen.hideAsync();
|
||||
if (error) {
|
||||
console.warn(`Error in loading fonts: ${error}`);
|
||||
}
|
||||
}
|
||||
}, [loaded, error]);
|
||||
|
||||
if (!loaded && !error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
|
||||
<Stack>
|
||||
<Stack.Screen name="index" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="+not-found" />
|
||||
</Stack>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
33
app/detail.tsx
Normal file
33
app/detail.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import { View, Text, StyleSheet } from "react-native";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { ThemedView } from "@/components/ThemedView";
|
||||
import { ThemedText } from "@/components/ThemedText";
|
||||
|
||||
export default function DetailScreen() {
|
||||
const { source, id } = useLocalSearchParams();
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
<ThemedText type="title">Detail Page</ThemedText>
|
||||
<View style={styles.separator} />
|
||||
<ThemedText>Source: {source}</ThemedText>
|
||||
<ThemedText>ID: {id}</ThemedText>
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: 20,
|
||||
},
|
||||
separator: {
|
||||
marginVertical: 30,
|
||||
height: 1,
|
||||
width: "80%",
|
||||
backgroundColor: "#666",
|
||||
},
|
||||
});
|
||||
113
app/index.tsx
Normal file
113
app/index.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { View, StyleSheet, ActivityIndicator, FlatList } from "react-native";
|
||||
import { ThemedView } from "@/components/ThemedView";
|
||||
import { ThemedText } from "@/components/ThemedText";
|
||||
import ScrollableRow from "@/components/ScrollableRow.tv";
|
||||
import { MoonTVAPI, DoubanResponse } from "@/services/api";
|
||||
import { RowItem } from "@/components/ScrollableRow.tv";
|
||||
|
||||
interface ContentRow {
|
||||
title: string;
|
||||
data: RowItem[];
|
||||
}
|
||||
|
||||
const categories = [
|
||||
{ title: "热门电影", type: "movie", tag: "热门" },
|
||||
{ title: "热门剧集", type: "tv", tag: "热门" },
|
||||
{ title: "豆瓣 Top250", type: "movie", tag: "top250" },
|
||||
{ title: "综艺", type: "tv", tag: "综艺" },
|
||||
{ title: "美剧", type: "tv", tag: "美剧" },
|
||||
{ title: "韩剧", type: "tv", tag: "韩剧" },
|
||||
{ title: "日剧", type: "tv", tag: "日剧" },
|
||||
{ title: "日漫", type: "tv", tag: "日本动画" },
|
||||
] as const;
|
||||
|
||||
// --- IMPORTANT ---
|
||||
// Replace with your computer's LAN IP address to test on a real device or emulator.
|
||||
// Find it by running `ifconfig` (macOS/Linux) or `ipconfig` (Windows).
|
||||
const API_BASE_URL = "http://192.168.31.123:3001";
|
||||
const api = new MoonTVAPI(API_BASE_URL);
|
||||
|
||||
export default function HomeScreen() {
|
||||
const [rows, setRows] = useState<ContentRow[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAllData = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const promises = categories.map((category) =>
|
||||
api.getDoubanData(category.type, category.tag, 20)
|
||||
);
|
||||
const results = await Promise.all<DoubanResponse>(promises);
|
||||
|
||||
const newRows: ContentRow[] = results.map((result, index) => {
|
||||
const category = categories[index];
|
||||
return {
|
||||
title: category.title,
|
||||
data: result.list.map((item) => ({
|
||||
...item,
|
||||
id: item.title, // Use title as a temporary unique id
|
||||
source: "douban", // Static source for douban items
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
setRows(newRows);
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch data for home screen:", err);
|
||||
setError("无法加载内容,请稍后重试。");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAllData();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ThemedView style={styles.centerContainer}>
|
||||
<ActivityIndicator size="large" />
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ThemedView style={styles.centerContainer}>
|
||||
<ThemedText type="subtitle">{error}</ThemedText>
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
<FlatList
|
||||
data={rows}
|
||||
renderItem={({ item }) => (
|
||||
<ScrollableRow title={item.title} data={item.data} api={api} />
|
||||
)}
|
||||
keyExtractor={(item) => item.title}
|
||||
contentContainerStyle={styles.listContent}
|
||||
/>
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
centerContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
listContent: {
|
||||
paddingTop: 40,
|
||||
paddingBottom: 40,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user