mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 14:33:26 +00:00
add back button and header layout to player
This commit is contained in:
BIN
apps/expo/assets/images/icon-transparent.png
Normal file
BIN
apps/expo/assets/images/icon-transparent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
@@ -42,7 +42,7 @@ export default function Searchbar({
|
||||
ref={inputRef}
|
||||
placeholder="What are you looking for?"
|
||||
placeholderTextColor={Colors.secondary[200]}
|
||||
className="w-full rounded-3xl py-3 pr-5 text-white focus-visible:outline-none"
|
||||
className="w-full rounded-3xl py-3 pr-5 text-white"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
@@ -17,14 +17,19 @@ import {
|
||||
import { fetchMediaDetails } from "@movie-web/tmdb";
|
||||
|
||||
import type { ItemData } from "~/components/item/item";
|
||||
import { usePlayer } from "../hooks/usePlayer";
|
||||
import { Header } from "~/components/player/Header";
|
||||
import { PlayerProvider, usePlayer } from "~/context/player.context";
|
||||
|
||||
export default function VideoPlayerWrapper() {
|
||||
const params = useLocalSearchParams();
|
||||
const data = params.data
|
||||
? (JSON.parse(params.data as string) as ItemData)
|
||||
: null;
|
||||
return <VideoPlayer data={data} />;
|
||||
return (
|
||||
<PlayerProvider>
|
||||
<VideoPlayer data={data} />
|
||||
</PlayerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
interface VideoPlayerProps {
|
||||
@@ -37,7 +42,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ data }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
const {
|
||||
videoRef,
|
||||
setVideoRef,
|
||||
unlockOrientation,
|
||||
presentFullscreenPlayer,
|
||||
dismissFullscreenPlayer,
|
||||
@@ -99,6 +104,8 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ data }) => {
|
||||
: [],
|
||||
);
|
||||
|
||||
console.log("stream", url);
|
||||
|
||||
setVideoSrc({
|
||||
uri: url,
|
||||
headers: {
|
||||
@@ -143,17 +150,19 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({ data }) => {
|
||||
return (
|
||||
<View className="flex-1 items-center justify-center bg-black">
|
||||
<Video
|
||||
ref={videoRef}
|
||||
ref={setVideoRef}
|
||||
source={videoSrc}
|
||||
// textTracks={textTracks} // breaks playback
|
||||
className="absolute inset-0"
|
||||
fullscreen={true}
|
||||
fullscreen
|
||||
paused={false}
|
||||
controls={true}
|
||||
controls
|
||||
useSecureView
|
||||
onLoadStart={onVideoLoadStart}
|
||||
onReadyForDisplay={onReadyForDisplay}
|
||||
/>
|
||||
{isLoading && <ActivityIndicator size="large" color="#0000ff" />}
|
||||
{!isLoading && <Header title="S8 E11 Rocky 8" />}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
29
apps/expo/src/components/player/BackButton.tsx
Normal file
29
apps/expo/src/components/player/BackButton.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useRouter } from "expo-router";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
|
||||
import { usePlayer } from "~/context/player.context";
|
||||
|
||||
export const BackButton = ({
|
||||
className,
|
||||
}: Partial<React.ComponentProps<typeof Ionicons>>) => {
|
||||
const { unlockOrientation } = usePlayer();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Ionicons
|
||||
name="arrow-back"
|
||||
onPress={() => {
|
||||
unlockOrientation()
|
||||
.then(() => {
|
||||
return router.back();
|
||||
})
|
||||
.catch(() => {
|
||||
return router.back();
|
||||
});
|
||||
}}
|
||||
size={36}
|
||||
color="white"
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
};
|
22
apps/expo/src/components/player/Header.tsx
Normal file
22
apps/expo/src/components/player/Header.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Image, View } from "react-native";
|
||||
|
||||
import Icon from "../../../assets/images/icon-transparent.png";
|
||||
import { Text } from "../ui/Text";
|
||||
import { BackButton } from "./BackButton";
|
||||
|
||||
interface HeaderProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const Header = ({ title }: HeaderProps) => {
|
||||
return (
|
||||
<View className="absolute top-0 flex w-full flex-row items-center justify-between px-6 pt-6">
|
||||
<BackButton className="w-36" />
|
||||
<Text className="font-bold">{title}</Text>
|
||||
<View className="flex w-36 flex-row items-center justify-center gap-2 space-x-2 rounded-full bg-secondary-300 px-4 py-2 opacity-50">
|
||||
<Image source={Icon} className="h-6 w-6" />
|
||||
<Text className="font-bold">movie-web</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
77
apps/expo/src/context/player.context.tsx
Normal file
77
apps/expo/src/context/player.context.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { VideoRef } from "react-native-video";
|
||||
import React, { createContext, useCallback, useContext, useState } from "react";
|
||||
import * as ScreenOrientation from "expo-screen-orientation";
|
||||
|
||||
interface PlayerContextProps {
|
||||
videoRef: VideoRef | null;
|
||||
setVideoRef: (ref: VideoRef | null) => void;
|
||||
lockOrientation: () => Promise<void>;
|
||||
unlockOrientation: () => Promise<void>;
|
||||
presentFullscreenPlayer: () => Promise<void>;
|
||||
dismissFullscreenPlayer: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface PlayerProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PlayerContext = createContext<PlayerContextProps | undefined>(undefined);
|
||||
|
||||
export const PlayerProvider = ({ children }: PlayerProviderProps) => {
|
||||
const [internalVideoRef, setInternalVideoRef] = useState<VideoRef | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const setVideoRef = useCallback((ref: VideoRef | null) => {
|
||||
setInternalVideoRef(ref);
|
||||
}, []);
|
||||
|
||||
const lockOrientation = useCallback(async () => {
|
||||
await ScreenOrientation.lockAsync(
|
||||
ScreenOrientation.OrientationLock.LANDSCAPE,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const unlockOrientation = useCallback(async () => {
|
||||
await ScreenOrientation.lockAsync(
|
||||
ScreenOrientation.OrientationLock.PORTRAIT_UP,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const presentFullscreenPlayer = useCallback(async () => {
|
||||
if (internalVideoRef) {
|
||||
internalVideoRef.presentFullscreenPlayer();
|
||||
await lockOrientation();
|
||||
}
|
||||
}, [internalVideoRef, lockOrientation]);
|
||||
|
||||
const dismissFullscreenPlayer = useCallback(async () => {
|
||||
if (internalVideoRef) {
|
||||
internalVideoRef.dismissFullscreenPlayer();
|
||||
await unlockOrientation();
|
||||
}
|
||||
}, [internalVideoRef, unlockOrientation]);
|
||||
|
||||
const contextValue: PlayerContextProps = {
|
||||
videoRef: internalVideoRef,
|
||||
setVideoRef,
|
||||
lockOrientation,
|
||||
unlockOrientation,
|
||||
presentFullscreenPlayer,
|
||||
dismissFullscreenPlayer,
|
||||
};
|
||||
|
||||
return (
|
||||
<PlayerContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</PlayerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePlayer = (): PlayerContextProps => {
|
||||
const context = useContext(PlayerContext);
|
||||
if (!context) {
|
||||
throw new Error("usePlayer must be used within a PlayerProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
@@ -1,41 +0,0 @@
|
||||
import type { VideoRef } from "react-native-video";
|
||||
import { useCallback, useRef } from "react";
|
||||
import * as ScreenOrientation from "expo-screen-orientation";
|
||||
|
||||
export const usePlayer = () => {
|
||||
const ref = useRef<VideoRef>(null);
|
||||
|
||||
const lockOrientation = useCallback(async () => {
|
||||
await ScreenOrientation.lockAsync(
|
||||
ScreenOrientation.OrientationLock.LANDSCAPE,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const unlockOrientation = useCallback(async () => {
|
||||
await ScreenOrientation.lockAsync(
|
||||
ScreenOrientation.OrientationLock.PORTRAIT_UP,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const presentFullscreenPlayer = useCallback(async () => {
|
||||
if (ref.current) {
|
||||
ref.current.presentFullscreenPlayer();
|
||||
await lockOrientation();
|
||||
}
|
||||
}, [lockOrientation]);
|
||||
|
||||
const dismissFullscreenPlayer = useCallback(async () => {
|
||||
if (ref.current) {
|
||||
ref.current.dismissFullscreenPlayer();
|
||||
await unlockOrientation();
|
||||
}
|
||||
}, [unlockOrientation]);
|
||||
|
||||
return {
|
||||
videoRef: ref,
|
||||
lockOrientation,
|
||||
unlockOrientation,
|
||||
presentFullscreenPlayer,
|
||||
dismissFullscreenPlayer,
|
||||
} as const;
|
||||
};
|
11
apps/expo/src/types/globals.d.ts
vendored
Normal file
11
apps/expo/src/types/globals.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
declare module "*.svg" {
|
||||
import type { ImageSourcePropType } from "react-native";
|
||||
const content: ImageSourcePropType;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module "*.png" {
|
||||
import type { ImageSourcePropType } from "react-native";
|
||||
const content: ImageSourcePropType;
|
||||
export default content;
|
||||
}
|
@@ -9,8 +9,6 @@
|
||||
"build": "turbo build",
|
||||
"clean": "git clean -xdf node_modules",
|
||||
"clean:workspaces": "turbo clean",
|
||||
"db:push": "pnpm -F db push",
|
||||
"db:studio": "pnpm -F db studio",
|
||||
"dev": "turbo dev --parallel",
|
||||
"format": "turbo format --continue -- --cache --cache-location node_modules/.cache/.prettiercache",
|
||||
"format:fix": "turbo format --continue -- --write --cache --cache-location node_modules/.cache/.prettiercache",
|
||||
|
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"prettier": "@movie-web/prettier-config",
|
||||
"dependencies": {
|
||||
"@movie-web/providers": "^2.1.1",
|
||||
"@movie-web/providers": "^2.2.0",
|
||||
"tmdb-ts": "^1.6.1"
|
||||
}
|
||||
}
|
||||
|
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -168,8 +168,8 @@ importers:
|
||||
packages/provider-utils:
|
||||
dependencies:
|
||||
'@movie-web/providers':
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
tmdb-ts:
|
||||
specifier: ^1.6.1
|
||||
version: 1.6.1
|
||||
@@ -2348,15 +2348,17 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: true
|
||||
|
||||
/@movie-web/providers@2.1.1:
|
||||
resolution: {integrity: sha512-g2CA/w3YlGw3b3v6yDSgUIUdym4rFs4CzZOo/OlyL4HtsFH9mk182ukt7HYSxgddCEJRjl81LZZc3/pLRIGcMA==}
|
||||
/@movie-web/providers@2.2.0:
|
||||
resolution: {integrity: sha512-7rKUpLPklwOtS5P2CAeh0P3sPIuYvtkKIgm0kVMp+OsSpKd9IcuYm79bbDrA0MDi3IMGik1W6la9Mzy91+8uYQ==}
|
||||
dependencies:
|
||||
cheerio: 1.0.0-rc.12
|
||||
cookie: 0.6.0
|
||||
crypto-js: 4.2.0
|
||||
form-data: 4.0.0
|
||||
iso-639-1: 3.1.0
|
||||
nanoid: 3.3.7
|
||||
node-fetch: 2.7.0
|
||||
set-cookie-parser: 2.6.0
|
||||
unpacker: 1.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
@@ -4543,6 +4545,11 @@ packages:
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/cookie@0.6.0:
|
||||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/core-js-compat@3.35.1:
|
||||
resolution: {integrity: sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==}
|
||||
dependencies:
|
||||
|
@@ -10,5 +10,9 @@ export default {
|
||||
300: "#32324F",
|
||||
700: "#131322",
|
||||
},
|
||||
iconBackground: "#14141c",
|
||||
icon: {
|
||||
background: "#14141c",
|
||||
},
|
||||
background: "#0a0a12",
|
||||
};
|
||||
|
Reference in New Issue
Block a user