Enhance responsive design and update dependencies

- Added expo-screen-orientation package to manage screen orientation.
- Updated react-native-gesture-handler to version 2.27.1 for improved gesture handling.
- Implemented responsive design features across multiple screens using the new useResponsive hook.
- Refactored DetailScreen, HomeScreen, SearchScreen, and PlayScreen to adapt layouts based on screen size.
- Introduced PlayerControlsMobile for optimized playback controls on mobile devices.
- Adjusted button styles in StyledButton for better responsiveness.
This commit is contained in:
zimplexing
2025-07-09 17:23:46 +08:00
parent d42a3e014e
commit 803fc87fd9
10 changed files with 428 additions and 97 deletions

View File

@@ -1,10 +1,13 @@
import React, { useEffect, useRef } from "react";
import { View, StyleSheet, TouchableOpacity, ActivityIndicator, BackHandler } from "react-native";
import { View, StyleSheet, ActivityIndicator, BackHandler, Platform, SafeAreaView, Dimensions } from "react-native";
import { useLocalSearchParams, useRouter } from "expo-router";
import { Video, ResizeMode } from "expo-av";
import * as ScreenOrientation from "expo-screen-orientation";
import { useKeepAwake } from "expo-keep-awake";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { ThemedView } from "@/components/ThemedView";
import { PlayerControls } from "@/components/PlayerControls";
import { PlayerControlsMobile } from "@/components/PlayerControls.mobile";
import { EpisodeSelectionModal } from "@/components/EpisodeSelectionModal";
import { SourceSelectionModal } from "@/components/SourceSelectionModal";
import { SeekingBar } from "@/components/SeekingBar";
@@ -12,11 +15,13 @@ import { NextEpisodeOverlay } from "@/components/NextEpisodeOverlay";
import { LoadingOverlay } from "@/components/LoadingOverlay";
import usePlayerStore from "@/stores/playerStore";
import { useTVRemoteHandler } from "@/hooks/useTVRemoteHandler";
import { useResponsive } from "@/hooks/useResponsive";
export default function PlayScreen() {
const videoRef = useRef<Video>(null);
const router = useRouter();
useKeepAwake();
const { isMobile } = useResponsive();
const { source, id, episodeIndex, position } = useLocalSearchParams<{
source: string;
id: string;
@@ -48,6 +53,21 @@ export default function PlayScreen() {
reset,
} = usePlayerStore();
useEffect(() => {
async function lockOrientation() {
if (isMobile) {
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT);
}
}
lockOrientation();
return () => {
if (isMobile) {
ScreenOrientation.unlockAsync();
}
};
}, [isMobile]);
useEffect(() => {
setVideoRef(videoRef);
if (source && id) {
@@ -58,7 +78,37 @@ export default function PlayScreen() {
};
}, [source, id, episodeIndex, position, setVideoRef, loadVideo, reset]);
const { onScreenPress } = useTVRemoteHandler();
const { onScreenPress: onTVScreenPress } = useTVRemoteHandler();
const handleScreenPress = () => {
if (isMobile) {
setShowControls(!showControls);
} else {
onTVScreenPress();
}
};
const singleTap = Gesture.Tap()
.onEnd(() => {
handleScreenPress();
})
.runOnJS(true);
const doubleTap = Gesture.Tap()
.numberOfTaps(2)
.onEnd((e) => {
if (!isMobile) return;
const tapPositionX = e.x;
const screenWidth = Dimensions.get("window").width;
if (tapPositionX < screenWidth / 2) {
seek(-10); // Seek back
} else {
seek(10); // Seek forward
}
})
.runOnJS(true);
const composedGesture = Gesture.Exclusive(doubleTap, singleTap);
useEffect(() => {
const backAction = () => {
@@ -93,39 +143,45 @@ export default function PlayScreen() {
const currentEpisode = episodes[currentEpisodeIndex];
const PlayerComponent = isMobile ? PlayerControlsMobile : PlayerControls;
return (
<ThemedView focusable style={styles.container}>
<TouchableOpacity activeOpacity={1} style={styles.videoContainer} onPress={onScreenPress}>
<Video
ref={videoRef}
style={styles.videoPlayer}
source={{ uri: currentEpisode?.url }}
resizeMode={ResizeMode.CONTAIN}
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
onLoad={() => {
const jumpPosition = introEndTime || initialPosition;
if (jumpPosition > 0) {
videoRef.current?.setPositionAsync(jumpPosition);
}
usePlayerStore.setState({ isLoading: false });
}}
onLoadStart={() => usePlayerStore.setState({ isLoading: true })}
useNativeControls={false}
shouldPlay
/>
<SafeAreaView style={styles.container}>
<ThemedView focusable style={styles.container}>
<GestureDetector gesture={composedGesture}>
<View style={styles.videoContainer}>
<Video
ref={videoRef}
style={styles.videoPlayer}
source={{ uri: currentEpisode?.url }}
resizeMode={ResizeMode.CONTAIN}
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
onLoad={() => {
const jumpPosition = introEndTime || initialPosition;
if (jumpPosition > 0) {
videoRef.current?.setPositionAsync(jumpPosition);
}
usePlayerStore.setState({ isLoading: false });
}}
onLoadStart={() => usePlayerStore.setState({ isLoading: true })}
useNativeControls={false}
shouldPlay
/>
{showControls && <PlayerControls showControls={showControls} setShowControls={setShowControls} />}
{showControls && <PlayerComponent showControls={showControls} setShowControls={setShowControls} />}
<SeekingBar />
<SeekingBar />
<LoadingOverlay visible={isLoading} />
<LoadingOverlay visible={isLoading} />
<NextEpisodeOverlay visible={showNextEpisodeOverlay} onCancel={() => setShowNextEpisodeOverlay(false)} />
</TouchableOpacity>
<NextEpisodeOverlay visible={showNextEpisodeOverlay} onCancel={() => setShowNextEpisodeOverlay(false)} />
</View>
</GestureDetector>
<EpisodeSelectionModal />
<SourceSelectionModal />
</ThemedView>
<EpisodeSelectionModal />
<SourceSelectionModal />
</ThemedView>
</SafeAreaView>
);
}