mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-15 20:34:43 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
023fa591ec | ||
|
|
b401d535ce | ||
|
|
23647f7329 | ||
|
|
67275988bd | ||
|
|
f7ae93bd3d | ||
|
|
f124f7e1e2 | ||
|
|
9bcdeaa44d | ||
|
|
4c93736c5e |
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useCallback, useRef, useState } from "react";
|
import React, { useEffect, useCallback, useRef, useState } from "react";
|
||||||
import { View, StyleSheet, ActivityIndicator, FlatList, Pressable, Animated, StatusBar } from "react-native";
|
import { View, StyleSheet, ActivityIndicator, FlatList, Pressable, Animated, StatusBar, Platform } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { ThemedView } from "@/components/ThemedView";
|
import { ThemedView } from "@/components/ThemedView";
|
||||||
import { ThemedText } from "@/components/ThemedText";
|
import { ThemedText } from "@/components/ThemedText";
|
||||||
@@ -15,6 +15,7 @@ import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
|
|||||||
import { getCommonResponsiveStyles } from "@/utils/ResponsiveStyles";
|
import { getCommonResponsiveStyles } from "@/utils/ResponsiveStyles";
|
||||||
import ResponsiveNavigation from "@/components/navigation/ResponsiveNavigation";
|
import ResponsiveNavigation from "@/components/navigation/ResponsiveNavigation";
|
||||||
import { useApiConfig, getApiConfigErrorMessage } from "@/hooks/useApiConfig";
|
import { useApiConfig, getApiConfigErrorMessage } from "@/hooks/useApiConfig";
|
||||||
|
import { Colors } from "@/constants/Colors";
|
||||||
|
|
||||||
const LOAD_MORE_THRESHOLD = 200;
|
const LOAD_MORE_THRESHOLD = 200;
|
||||||
|
|
||||||
@@ -166,7 +167,7 @@ export default function HomeScreen() {
|
|||||||
<View style={dynamicStyles.headerContainer}>
|
<View style={dynamicStyles.headerContainer}>
|
||||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||||
<ThemedText style={dynamicStyles.headerTitle}>首页</ThemedText>
|
<ThemedText style={dynamicStyles.headerTitle}>首页</ThemedText>
|
||||||
<Pressable style={{ marginLeft: 20 }} onPress={() => router.push("/live")}>
|
<Pressable android_ripple={Platform.isTV || deviceType !== 'tv'? { color: 'transparent' } : { color: Colors.dark.link }} style={{ marginLeft: 20 }} onPress={() => router.push("/live")}>
|
||||||
{({ focused }) => (
|
{({ focused }) => (
|
||||||
<ThemedText style={[dynamicStyles.headerTitle, { color: focused ? "white" : "grey" }]}>直播</ThemedText>
|
<ThemedText style={[dynamicStyles.headerTitle, { color: focused ? "white" : "grey" }]}>直播</ThemedText>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { forwardRef } from "react";
|
import React, { forwardRef } from "react";
|
||||||
import { Animated, Pressable, StyleSheet, StyleProp, ViewStyle, PressableProps, TextStyle, View } from "react-native";
|
import { Animated, Pressable, StyleSheet, StyleProp, ViewStyle, PressableProps, TextStyle, View, Platform } from "react-native";
|
||||||
import { ThemedText } from "./ThemedText";
|
import { ThemedText } from "./ThemedText";
|
||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
import { useButtonAnimation } from "@/hooks/useAnimation";
|
import { useButtonAnimation } from "@/hooks/useAnimation";
|
||||||
|
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
|
||||||
|
|
||||||
interface StyledButtonProps extends PressableProps {
|
interface StyledButtonProps extends PressableProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@@ -19,6 +20,7 @@ export const StyledButton = forwardRef<View, StyledButtonProps>(
|
|||||||
const colors = Colors[colorScheme];
|
const colors = Colors[colorScheme];
|
||||||
const [isFocused, setIsFocused] = React.useState(false);
|
const [isFocused, setIsFocused] = React.useState(false);
|
||||||
const animationStyle = useButtonAnimation(isFocused);
|
const animationStyle = useButtonAnimation(isFocused);
|
||||||
|
const deviceType = useResponsiveLayout().deviceType;
|
||||||
|
|
||||||
const variantStyles = {
|
const variantStyles = {
|
||||||
default: StyleSheet.create({
|
default: StyleSheet.create({
|
||||||
@@ -108,6 +110,7 @@ export const StyledButton = forwardRef<View, StyledButtonProps>(
|
|||||||
return (
|
return (
|
||||||
<Animated.View style={[animationStyle, style]}>
|
<Animated.View style={[animationStyle, style]}>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
android_ripple={Platform.isTV || deviceType !== 'tv'? { color: 'transparent' } : { color: Colors.dark.link }}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useCallback, useRef, forwardRef } from "react";
|
import React, { useState, useEffect, useCallback, useRef, forwardRef } from "react";
|
||||||
import { View, Text, Image, StyleSheet, TouchableOpacity, Alert, Animated } from "react-native";
|
import { View, Text, Image, StyleSheet, Pressable, TouchableOpacity, Alert, Animated, Platform } from "react-native";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import { Star, Play } from "lucide-react-native";
|
import { Star, Play } from "lucide-react-native";
|
||||||
import { PlayRecordManager } from "@/services/storage";
|
import { PlayRecordManager } from "@/services/storage";
|
||||||
@@ -7,6 +7,7 @@ import { API } from "@/services/api";
|
|||||||
import { ThemedText } from "@/components/ThemedText";
|
import { ThemedText } from "@/components/ThemedText";
|
||||||
import { Colors } from "@/constants/Colors";
|
import { Colors } from "@/constants/Colors";
|
||||||
import Logger from '@/utils/Logger';
|
import Logger from '@/utils/Logger';
|
||||||
|
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
|
||||||
|
|
||||||
const logger = Logger.withTag('VideoCardTV');
|
const logger = Logger.withTag('VideoCardTV');
|
||||||
|
|
||||||
@@ -54,6 +55,8 @@ const VideoCard = forwardRef<View, VideoCardProps>(
|
|||||||
|
|
||||||
const scale = useRef(new Animated.Value(1)).current;
|
const scale = useRef(new Animated.Value(1)).current;
|
||||||
|
|
||||||
|
const deviceType = useResponsiveLayout().deviceType;
|
||||||
|
|
||||||
const animatedStyle = {
|
const animatedStyle = {
|
||||||
transform: [{ scale }],
|
transform: [{ scale }],
|
||||||
};
|
};
|
||||||
@@ -147,63 +150,126 @@ const VideoCard = forwardRef<View, VideoCardProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.wrapper, animatedStyle, { opacity: fadeAnim }]}>
|
<Animated.View style={[styles.wrapper, animatedStyle, { opacity: fadeAnim }]}>
|
||||||
<TouchableOpacity
|
{Platform.isTV || deviceType !== 'tv' ? (
|
||||||
onPress={handlePress}
|
<TouchableOpacity
|
||||||
onLongPress={handleLongPress}
|
onPress={handlePress}
|
||||||
onFocus={handleFocus}
|
onLongPress={handleLongPress}
|
||||||
onBlur={handleBlur}
|
onFocus={handleFocus}
|
||||||
style={styles.pressable}
|
onBlur={handleBlur}
|
||||||
activeOpacity={1}
|
style={styles.pressable}
|
||||||
delayLongPress={1000}
|
activeOpacity={1}
|
||||||
>
|
delayLongPress={1000}
|
||||||
<View style={styles.card}>
|
>
|
||||||
<Image source={{ uri: api.getImageProxyUrl(poster) }} style={styles.poster} />
|
<View style={styles.card}>
|
||||||
{isFocused && (
|
<Image source={{ uri: api.getImageProxyUrl(poster) }} style={styles.poster} />
|
||||||
<View style={styles.overlay}>
|
{isFocused && (
|
||||||
{isContinueWatching && (
|
<View style={styles.overlay}>
|
||||||
<View style={styles.continueWatchingBadge}>
|
{isContinueWatching && (
|
||||||
<Play size={16} color="#ffffff" fill="#ffffff" />
|
<View style={styles.continueWatchingBadge}>
|
||||||
<ThemedText style={styles.continueWatchingText}>继续观看</ThemedText>
|
<Play size={16} color="#ffffff" fill="#ffffff" />
|
||||||
</View>
|
<ThemedText style={styles.continueWatchingText}>继续观看</ThemedText>
|
||||||
)}
|
</View>
|
||||||
</View>
|
)}
|
||||||
)}
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 进度条 */}
|
{/* 进度条 */}
|
||||||
{isContinueWatching && (
|
{isContinueWatching && (
|
||||||
<View style={styles.progressContainer}>
|
<View style={styles.progressContainer}>
|
||||||
<View style={[styles.progressBar, { width: `${(progress || 0) * 100}%` }]} />
|
<View style={[styles.progressBar, { width: `${(progress || 0) * 100}%` }]} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{rate && (
|
||||||
|
<View style={styles.ratingContainer}>
|
||||||
|
<Star size={12} color="#FFD700" fill="#FFD700" />
|
||||||
|
<ThemedText style={styles.ratingText}>{rate}</ThemedText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{year && (
|
||||||
|
<View style={styles.yearBadge}>
|
||||||
|
<Text style={styles.badgeText}>{year}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{sourceName && (
|
||||||
|
<View style={styles.sourceNameBadge}>
|
||||||
|
<Text style={styles.badgeText}>{sourceName}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<View style={styles.infoContainer}>
|
||||||
|
<ThemedText numberOfLines={1}>{title}</ThemedText>
|
||||||
|
{isContinueWatching && (
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<ThemedText style={styles.continueLabel}>
|
||||||
|
第{episodeIndex}集 已观看 {Math.round((progress || 0) * 100)}%
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
) : (
|
||||||
|
<Pressable
|
||||||
|
android_ripple={{ color: Colors.dark.link }}
|
||||||
|
onPress={handlePress}
|
||||||
|
onLongPress={handleLongPress}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
style={styles.pressable}
|
||||||
|
// activeOpacity={1}
|
||||||
|
delayLongPress={1000}
|
||||||
|
>
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Image source={{ uri: api.getImageProxyUrl(poster) }} style={styles.poster} />
|
||||||
|
{isFocused && (
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
{isContinueWatching && (
|
||||||
|
<View style={styles.continueWatchingBadge}>
|
||||||
|
<Play size={16} color="#ffffff" fill="#ffffff" />
|
||||||
|
<ThemedText style={styles.continueWatchingText}>继续观看</ThemedText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 进度条 */}
|
||||||
|
{isContinueWatching && (
|
||||||
|
<View style={styles.progressContainer}>
|
||||||
|
<View style={[styles.progressBar, { width: `${(progress || 0) * 100}%` }]} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rate && (
|
||||||
|
<View style={styles.ratingContainer}>
|
||||||
|
<Star size={12} color="#FFD700" fill="#FFD700" />
|
||||||
|
<ThemedText style={styles.ratingText}>{rate}</ThemedText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{year && (
|
||||||
|
<View style={styles.yearBadge}>
|
||||||
|
<Text style={styles.badgeText}>{year}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{sourceName && (
|
||||||
|
<View style={styles.sourceNameBadge}>
|
||||||
|
<Text style={styles.badgeText}>{sourceName}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<View style={styles.infoContainer}>
|
||||||
|
<ThemedText numberOfLines={1}>{title}</ThemedText>
|
||||||
|
{isContinueWatching && (
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<ThemedText style={styles.continueLabel}>
|
||||||
|
第{episodeIndex}集 已观看 {Math.round((progress || 0) * 100)}%
|
||||||
|
</ThemedText>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
{rate && (
|
|
||||||
<View style={styles.ratingContainer}>
|
|
||||||
<Star size={12} color="#FFD700" fill="#FFD700" />
|
|
||||||
<ThemedText style={styles.ratingText}>{rate}</ThemedText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{year && (
|
|
||||||
<View style={styles.yearBadge}>
|
|
||||||
<Text style={styles.badgeText}>{year}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
{sourceName && (
|
|
||||||
<View style={styles.sourceNameBadge}>
|
|
||||||
<Text style={styles.badgeText}>{sourceName}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<View style={styles.infoContainer}>
|
|
||||||
<ThemedText numberOfLines={1}>{title}</ThemedText>
|
|
||||||
{isContinueWatching && (
|
|
||||||
<View style={styles.infoRow}>
|
|
||||||
<ThemedText style={styles.continueLabel}>
|
|
||||||
第{episodeIndex}集 已观看 {Math.round((progress || 0) * 100)}%
|
|
||||||
</ThemedText>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -221,9 +287,14 @@ const styles = StyleSheet.create({
|
|||||||
marginHorizontal: 8,
|
marginHorizontal: 8,
|
||||||
},
|
},
|
||||||
pressable: {
|
pressable: {
|
||||||
|
width: CARD_WIDTH + 20,
|
||||||
|
height: CARD_HEIGHT + 60,
|
||||||
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
overflow: "visible",
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
|
marginTop: 10,
|
||||||
width: CARD_WIDTH,
|
width: CARD_WIDTH,
|
||||||
height: CARD_HEIGHT,
|
height: CARD_HEIGHT,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "OrionTV",
|
"name": "OrionTV",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "expo-router/entry",
|
"main": "expo-router/entry",
|
||||||
"version": "1.3.7",
|
"version": "1.3.8",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
"start": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo start",
|
||||||
"android": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo run:android",
|
"android": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo run:android",
|
||||||
|
|||||||
Reference in New Issue
Block a user