mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	Update player's layout for tv
This commit is contained in:
		
							parent
							
								
									1bba1eb02a
								
							
						
					
					
						commit
						0e3d87a9ca
					
				@ -19,8 +19,10 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useRef, useState } from "react";
 | 
					import { useRef, useState } from "react";
 | 
				
			||||||
import { GestureResponderEvent, Platform, View } from "react-native";
 | 
					import { GestureResponderEvent, Platform, Pressable, View } from "react-native";
 | 
				
			||||||
 | 
					import { useTVEventHandler } from "@kyoo/primitives/tv";
 | 
				
			||||||
import { px, percent, Stylable, useYoshiki } from "yoshiki/native";
 | 
					import { px, percent, Stylable, useYoshiki } from "yoshiki/native";
 | 
				
			||||||
 | 
					import { focusReset } from "./utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Slider = ({
 | 
					export const Slider = ({
 | 
				
			||||||
	progress,
 | 
						progress,
 | 
				
			||||||
@ -49,7 +51,6 @@ export const Slider = ({
 | 
				
			|||||||
	const [isHover, setHover] = useState(false);
 | 
						const [isHover, setHover] = useState(false);
 | 
				
			||||||
	const [isFocus, setFocus] = useState(false);
 | 
						const [isFocus, setFocus] = useState(false);
 | 
				
			||||||
	const smallBar = !(isSeeking || isHover || isFocus);
 | 
						const smallBar = !(isSeeking || isHover || isFocus);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	const ts = (value: number) => px(value * size);
 | 
						const ts = (value: number) => px(value * size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const change = (event: GestureResponderEvent) => {
 | 
						const change = (event: GestureResponderEvent) => {
 | 
				
			||||||
@ -61,16 +62,17 @@ export const Slider = ({
 | 
				
			|||||||
		setProgress(Math.max(0, Math.min(locationX / layout.width, 1)) * max);
 | 
							setProgress(Math.max(0, Math.min(locationX / layout.width, 1)) * max);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						useTVEventHandler((e) => {
 | 
				
			||||||
 | 
							if (!isFocus) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (e.eventType === "left" && e.eventKeyAction === 0) setProgress(Math.max(progress - 5, 0));
 | 
				
			||||||
 | 
							if (e.eventType === "right" && e.eventKeyAction === 0) setProgress(Math.max(progress + 5, 0));
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const Container = Platform.isTV ? Pressable : View;
 | 
				
			||||||
	return (
 | 
						return (
 | 
				
			||||||
		<View
 | 
							<Container
 | 
				
			||||||
			ref={ref}
 | 
								ref={ref}
 | 
				
			||||||
			// @ts-ignore Web only
 | 
					 | 
				
			||||||
			onMouseEnter={() => setHover(true)}
 | 
					 | 
				
			||||||
			// @ts-ignore Web only
 | 
					 | 
				
			||||||
			onMouseLeave={() => setHover(false)}
 | 
					 | 
				
			||||||
			focusable
 | 
					 | 
				
			||||||
			onFocus={() => setFocus(true)}
 | 
					 | 
				
			||||||
			onBlur={() => setFocus(false)}
 | 
					 | 
				
			||||||
			onStartShouldSetResponder={() => true}
 | 
								onStartShouldSetResponder={() => true}
 | 
				
			||||||
			onResponderGrant={() => {
 | 
								onResponderGrant={() => {
 | 
				
			||||||
				setSeek(true);
 | 
									setSeek(true);
 | 
				
			||||||
@ -85,6 +87,7 @@ export const Slider = ({
 | 
				
			|||||||
			onLayout={() =>
 | 
								onLayout={() =>
 | 
				
			||||||
				ref.current?.measure((_, __, width, ___, pageX) => setLayout({ width: width, x: pageX }))
 | 
									ref.current?.measure((_, __, width, ___, pageX) => setLayout({ width: width, x: pageX }))
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								// @ts-ignore Web only
 | 
				
			||||||
			onKeyDown={(e: KeyboardEvent) => {
 | 
								onKeyDown={(e: KeyboardEvent) => {
 | 
				
			||||||
				switch (e.code) {
 | 
									switch (e.code) {
 | 
				
			||||||
					case "ArrowLeft":
 | 
										case "ArrowLeft":
 | 
				
			||||||
@ -107,10 +110,16 @@ export const Slider = ({
 | 
				
			|||||||
					// @ts-ignore Web only
 | 
										// @ts-ignore Web only
 | 
				
			||||||
					cursor: "pointer",
 | 
										cursor: "pointer",
 | 
				
			||||||
					focus: {
 | 
										focus: {
 | 
				
			||||||
						shadowRadius: 0,
 | 
											self: focusReset,
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				props,
 | 
									{
 | 
				
			||||||
 | 
										onFocus: () => setFocus(true),
 | 
				
			||||||
 | 
										onBlur: () => setFocus(false),
 | 
				
			||||||
 | 
										onMouseEnter: () => setHover(true),
 | 
				
			||||||
 | 
										onMouseLeave: () => setHover(false),
 | 
				
			||||||
 | 
										...props,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
			)}
 | 
								)}
 | 
				
			||||||
		>
 | 
							>
 | 
				
			||||||
			<View
 | 
								<View
 | 
				
			||||||
@ -174,7 +183,7 @@ export const Slider = ({
 | 
				
			|||||||
							position: "absolute",
 | 
												position: "absolute",
 | 
				
			||||||
							top: 0,
 | 
												top: 0,
 | 
				
			||||||
							bottom: 0,
 | 
												bottom: 0,
 | 
				
			||||||
							marginY: ts(Platform.OS === "android" ? -0.5 : 0.5),
 | 
												marginY: ts(Platform.OS === "android" && !Platform.isTV ? -0.5 : 0.5),
 | 
				
			||||||
							bg: (theme) => theme.accent,
 | 
												bg: (theme) => theme.accent,
 | 
				
			||||||
							width: ts(2),
 | 
												width: ts(2),
 | 
				
			||||||
							height: ts(2),
 | 
												height: ts(2),
 | 
				
			||||||
@ -190,6 +199,6 @@ export const Slider = ({
 | 
				
			|||||||
					},
 | 
										},
 | 
				
			||||||
				)}
 | 
									)}
 | 
				
			||||||
			/>
 | 
								/>
 | 
				
			||||||
		</View>
 | 
							</Container>
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										23
									
								
								front/packages/primitives/tv.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								front/packages/primitives/tv.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Kyoo - A portable and vast media library solution.
 | 
				
			||||||
 | 
					 * Copyright (c) Kyoo.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * See AUTHORS.md and LICENSE file in the project root for full license information.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Kyoo is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					 * any later version.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Kyoo is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
				
			||||||
 | 
					 * GNU General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					 * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./tvos-type.d.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { useTVEventHandler } from "react-native";
 | 
				
			||||||
							
								
								
									
										21
									
								
								front/packages/primitives/tv.web.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								front/packages/primitives/tv.web.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Kyoo - A portable and vast media library solution.
 | 
				
			||||||
 | 
					 * Copyright (c) Kyoo.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * See AUTHORS.md and LICENSE file in the project root for full license information.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Kyoo is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					 * any later version.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Kyoo is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
				
			||||||
 | 
					 * GNU General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					 * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useTVEventHandler = () => {};
 | 
				
			||||||
							
								
								
									
										178
									
								
								front/packages/primitives/tvos-type.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								front/packages/primitives/tvos-type.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,178 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Kyoo - A portable and vast media library solution.
 | 
				
			||||||
 | 
					 * Copyright (c) Kyoo.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * See AUTHORS.md and LICENSE file in the project root for full license information.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Kyoo is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					 * it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					 * any later version.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Kyoo is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
				
			||||||
 | 
					 * GNU General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					 * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { ViewProps, ScrollViewProps } from "react-native";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare module "react-native" {
 | 
				
			||||||
 | 
						interface ViewProps {
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * TV next focus down (see documentation for the View component).
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							nextFocusDown?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * TV next focus forward (see documentation for the View component).
 | 
				
			||||||
 | 
							 *
 | 
				
			||||||
 | 
							 * @platform android
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							nextFocusForward?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * TV next focus left (see documentation for the View component).
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							nextFocusLeft?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * TV next focus right (see documentation for the View component).
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							nextFocusRight?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * TV next focus up (see documentation for the View component).
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							nextFocusUp?: number;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export const useTVEventHandler: (handleEvent: (event: HWEvent) => void) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export const TVEventControl: {
 | 
				
			||||||
 | 
							enableTVMenuKey(): void;
 | 
				
			||||||
 | 
							disableTVMenuKey(): void;
 | 
				
			||||||
 | 
							enableTVPanGesture(): void;
 | 
				
			||||||
 | 
							disableTVPanGesture(): void;
 | 
				
			||||||
 | 
							enableGestureHandlersCancelTouches(): void;
 | 
				
			||||||
 | 
							disableGestureHandlersCancelTouches(): void;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export type HWEvent = {
 | 
				
			||||||
 | 
							eventType:
 | 
				
			||||||
 | 
								| "up"
 | 
				
			||||||
 | 
								| "down"
 | 
				
			||||||
 | 
								| "right"
 | 
				
			||||||
 | 
								| "left"
 | 
				
			||||||
 | 
								| "longUp"
 | 
				
			||||||
 | 
								| "longDown"
 | 
				
			||||||
 | 
								| "longRight"
 | 
				
			||||||
 | 
								| "longLeft"
 | 
				
			||||||
 | 
								| "blur"
 | 
				
			||||||
 | 
								| "focus"
 | 
				
			||||||
 | 
								| "pan"
 | 
				
			||||||
 | 
								| string;
 | 
				
			||||||
 | 
							eventKeyAction?: -1 | 1 | 0 | number;
 | 
				
			||||||
 | 
							tag?: number;
 | 
				
			||||||
 | 
							body?: {
 | 
				
			||||||
 | 
								state: "Began" | "Changed" | "Ended";
 | 
				
			||||||
 | 
								x: number;
 | 
				
			||||||
 | 
								y: number;
 | 
				
			||||||
 | 
								velocityx: number;
 | 
				
			||||||
 | 
								velocityy: number;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export class TVEventHandler {
 | 
				
			||||||
 | 
							enable<T extends React.Component<unknown>>(
 | 
				
			||||||
 | 
								component?: T,
 | 
				
			||||||
 | 
								callback?: (component: T, data: HWEvent) => void,
 | 
				
			||||||
 | 
							): void;
 | 
				
			||||||
 | 
							disable(): void;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export interface FocusGuideProps extends ViewProps {
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * If the view should be "visible". display "flex" if visible, otherwise "none". Defaults to
 | 
				
			||||||
 | 
							 * true
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							enabled?: boolean;
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * Array of `Component`s to register as destinations with `UIFocusGuide`
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							destinations?: (null | number | React.Component<any, any> | React.ComponentClass<any>)[];
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * @deprecated Don't use it, no longer necessary.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							safePadding?: "both" | "vertical" | "horizontal" | null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * This component provides support for Apple's `UIFocusGuide` API, to help ensure that focusable
 | 
				
			||||||
 | 
						 * controls can be navigated to, even if they are not directly in line with other controls. An
 | 
				
			||||||
 | 
						 * example is provided in `RNTester` that shows two different ways of using this component.
 | 
				
			||||||
 | 
						 * https://github.com/react-native-tvos/react-native-tvos/blob/tvos-v0.63.4/RNTester/js/examples/TVFocusGuide/TVFocusGuideExample.js
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						export class TVFocusGuideView extends React.Component<FocusGuideProps> {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export interface TVTextScrollViewProps extends ScrollViewProps {
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * The duration of the scroll animation when a swipe is detected. Default value is 0.3 s
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							scrollDuration?: number;
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * Scrolling distance when a swipe is detected Default value is half the visible height
 | 
				
			||||||
 | 
							 * (vertical scroller) or width (horizontal scroller)
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							pageSize?: number;
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * If true, will scroll to start when focus moves out past the beginning of the scroller
 | 
				
			||||||
 | 
							 * Defaults to true
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							snapToStart?: boolean;
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * If true, will scroll to end when focus moves out past the end of the scroller Defaults to
 | 
				
			||||||
 | 
							 * true
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							snapToEnd?: boolean;
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * Called when the scroller comes into focus (e.g. for highlighting)
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							onFocus?(evt: HWEvent): void;
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * Called when the scroller goes out of focus
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							onBlur?(evt: HWEvent): void;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export class TVTextScrollView extends React.Component<TVTextScrollViewProps> {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export interface PressableStateCallbackType {
 | 
				
			||||||
 | 
							readonly focused: boolean;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						export interface TouchableWithoutFeedbackPropsIOS {
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * _(Apple TV only)_ TV preferred focus (see documentation for the View component).
 | 
				
			||||||
 | 
							 *
 | 
				
			||||||
 | 
							 * @platform ios
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							hasTVPreferredFocus?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * _(Apple TV only)_ Object with properties to control Apple TV parallax effects.
 | 
				
			||||||
 | 
							 *
 | 
				
			||||||
 | 
							 * Enabled: If true, parallax effects are enabled. Defaults to true. shiftDistanceX: Defaults to
 | 
				
			||||||
 | 
							 * 2.0. shiftDistanceY: Defaults to 2.0. tiltAngle: Defaults to 0.05. magnification: Defaults to
 | 
				
			||||||
 | 
							 * 1.0. pressMagnification: Defaults to 1.0. pressDuration: Defaults to 0.3. pressDelay:
 | 
				
			||||||
 | 
							 * Defaults to 0.0.
 | 
				
			||||||
 | 
							 *
 | 
				
			||||||
 | 
							 * @platform ios
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							tvParallaxProperties?: TVParallaxProperties;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -35,7 +35,7 @@ import {
 | 
				
			|||||||
} from "@kyoo/primitives";
 | 
					} from "@kyoo/primitives";
 | 
				
			||||||
import { Chapter, Font, Track } from "@kyoo/models";
 | 
					import { Chapter, Font, Track } from "@kyoo/models";
 | 
				
			||||||
import { useAtomValue, useSetAtom, useAtom } from "jotai";
 | 
					import { useAtomValue, useSetAtom, useAtom } from "jotai";
 | 
				
			||||||
import { View, ViewProps } from "react-native";
 | 
					import { Platform, View, ViewProps } from "react-native";
 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
import { percent, rem, useYoshiki } from "yoshiki/native";
 | 
					import { percent, rem, useYoshiki } from "yoshiki/native";
 | 
				
			||||||
import { useRouter } from "solito/router";
 | 
					import { useRouter } from "solito/router";
 | 
				
			||||||
@ -43,6 +43,7 @@ import ArrowBack from "@material-symbols/svg-400/rounded/arrow_back-fill.svg";
 | 
				
			|||||||
import { LeftButtons } from "./left-buttons";
 | 
					import { LeftButtons } from "./left-buttons";
 | 
				
			||||||
import { RightButtons } from "./right-buttons";
 | 
					import { RightButtons } from "./right-buttons";
 | 
				
			||||||
import { bufferedAtom, durationAtom, loadAtom, playAtom, progressAtom } from "../state";
 | 
					import { bufferedAtom, durationAtom, loadAtom, playAtom, progressAtom } from "../state";
 | 
				
			||||||
 | 
					import { useEffect, useRef } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Hover = ({
 | 
					export const Hover = ({
 | 
				
			||||||
	isLoading,
 | 
						isLoading,
 | 
				
			||||||
@ -74,6 +75,14 @@ export const Hover = ({
 | 
				
			|||||||
	onMenuClose: () => void;
 | 
						onMenuClose: () => void;
 | 
				
			||||||
	show: boolean;
 | 
						show: boolean;
 | 
				
			||||||
} & ViewProps) => {
 | 
					} & ViewProps) => {
 | 
				
			||||||
 | 
						const ref = useRef<View | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						useEffect(() => {
 | 
				
			||||||
 | 
							setTimeout(() => {
 | 
				
			||||||
 | 
								ref.current?.focus();
 | 
				
			||||||
 | 
							}, 100);
 | 
				
			||||||
 | 
						}, [show]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO animate show
 | 
						// TODO animate show
 | 
				
			||||||
	const opacity = !show && { opacity: 0 };
 | 
						const opacity = !show && { opacity: 0 };
 | 
				
			||||||
	return (
 | 
						return (
 | 
				
			||||||
@ -113,7 +122,7 @@ export const Hover = ({
 | 
				
			|||||||
							<View
 | 
												<View
 | 
				
			||||||
								{...css({ flexDirection: "row", flexGrow: 1, justifyContent: "space-between" })}
 | 
													{...css({ flexDirection: "row", flexGrow: 1, justifyContent: "space-between" })}
 | 
				
			||||||
							>
 | 
												>
 | 
				
			||||||
								<LeftButtons previousSlug={previousSlug} nextSlug={nextSlug} />
 | 
													<LeftButtons previousSlug={previousSlug} nextSlug={nextSlug} playRef={ref} />
 | 
				
			||||||
								<RightButtons
 | 
													<RightButtons
 | 
				
			||||||
									subtitles={subtitles}
 | 
														subtitles={subtitles}
 | 
				
			||||||
									fonts={fonts}
 | 
														fonts={fonts}
 | 
				
			||||||
@ -176,14 +185,18 @@ export const Back = ({
 | 
				
			|||||||
				props,
 | 
									props,
 | 
				
			||||||
			)}
 | 
								)}
 | 
				
			||||||
		>
 | 
							>
 | 
				
			||||||
 | 
								{!Platform.isTV && (
 | 
				
			||||||
				<IconButton
 | 
									<IconButton
 | 
				
			||||||
					icon={ArrowBack}
 | 
										icon={ArrowBack}
 | 
				
			||||||
				{...(href ? { as: Link as any, href: href } : { as: PressableFeedback, onPress: router.back })}
 | 
										{...(href
 | 
				
			||||||
 | 
											? { as: Link as any, href: href }
 | 
				
			||||||
 | 
											: { as: PressableFeedback, onPress: router.back })}
 | 
				
			||||||
					{...tooltip(t("player.back"))}
 | 
										{...tooltip(t("player.back"))}
 | 
				
			||||||
				/>
 | 
									/>
 | 
				
			||||||
 | 
								)}
 | 
				
			||||||
			<Skeleton>
 | 
								<Skeleton>
 | 
				
			||||||
				{isLoading ? (
 | 
									{isLoading ? (
 | 
				
			||||||
					<Skeleton {...css({ width: rem(5), })} />
 | 
										<Skeleton {...css({ width: rem(5) })} />
 | 
				
			||||||
				) : (
 | 
									) : (
 | 
				
			||||||
					<H1
 | 
										<H1
 | 
				
			||||||
						{...css({
 | 
											{...css({
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@
 | 
				
			|||||||
import { IconButton, Link, P, Slider, tooltip, ts } from "@kyoo/primitives";
 | 
					import { IconButton, Link, P, Slider, tooltip, ts } from "@kyoo/primitives";
 | 
				
			||||||
import { useAtom, useAtomValue } from "jotai";
 | 
					import { useAtom, useAtomValue } from "jotai";
 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
import { View } from "react-native";
 | 
					import { Platform, View } from "react-native";
 | 
				
			||||||
import SkipPrevious from "@material-symbols/svg-400/rounded/skip_previous-fill.svg";
 | 
					import SkipPrevious from "@material-symbols/svg-400/rounded/skip_previous-fill.svg";
 | 
				
			||||||
import SkipNext from "@material-symbols/svg-400/rounded/skip_next-fill.svg";
 | 
					import SkipNext from "@material-symbols/svg-400/rounded/skip_next-fill.svg";
 | 
				
			||||||
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
 | 
					import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
 | 
				
			||||||
@ -32,13 +32,16 @@ import VolumeDown from "@material-symbols/svg-400/rounded/volume_down-fill.svg";
 | 
				
			|||||||
import VolumeUp from "@material-symbols/svg-400/rounded/volume_up-fill.svg";
 | 
					import VolumeUp from "@material-symbols/svg-400/rounded/volume_up-fill.svg";
 | 
				
			||||||
import { durationAtom, mutedAtom, playAtom, progressAtom, volumeAtom } from "../state";
 | 
					import { durationAtom, mutedAtom, playAtom, progressAtom, volumeAtom } from "../state";
 | 
				
			||||||
import { px, useYoshiki } from "yoshiki/native";
 | 
					import { px, useYoshiki } from "yoshiki/native";
 | 
				
			||||||
 | 
					import { RefObject } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const LeftButtons = ({
 | 
					export const LeftButtons = ({
 | 
				
			||||||
	previousSlug,
 | 
						previousSlug,
 | 
				
			||||||
	nextSlug,
 | 
						nextSlug,
 | 
				
			||||||
 | 
						playRef,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
	previousSlug?: string | null;
 | 
						previousSlug?: string | null;
 | 
				
			||||||
	nextSlug?: string | null;
 | 
						nextSlug?: string | null;
 | 
				
			||||||
 | 
						playRef: RefObject<View>;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
	const { css } = useYoshiki();
 | 
						const { css } = useYoshiki();
 | 
				
			||||||
	const { t } = useTranslation();
 | 
						const { t } = useTranslation();
 | 
				
			||||||
@ -58,7 +61,9 @@ export const LeftButtons = ({
 | 
				
			|||||||
				/>
 | 
									/>
 | 
				
			||||||
			)}
 | 
								)}
 | 
				
			||||||
			<IconButton
 | 
								<IconButton
 | 
				
			||||||
 | 
									ref={playRef}
 | 
				
			||||||
				icon={isPlaying ? Pause : PlayArrow}
 | 
									icon={isPlaying ? Pause : PlayArrow}
 | 
				
			||||||
 | 
									hasTVPreferredFocus
 | 
				
			||||||
				onPress={() => setPlay(!isPlaying)}
 | 
									onPress={() => setPlay(!isPlaying)}
 | 
				
			||||||
				{...tooltip(isPlaying ? t("player.pause") : t("player.play"), true)}
 | 
									{...tooltip(isPlaying ? t("player.pause") : t("player.play"), true)}
 | 
				
			||||||
				{...spacing}
 | 
									{...spacing}
 | 
				
			||||||
@ -72,7 +77,7 @@ export const LeftButtons = ({
 | 
				
			|||||||
					{...spacing}
 | 
										{...spacing}
 | 
				
			||||||
				/>
 | 
									/>
 | 
				
			||||||
			)}
 | 
								)}
 | 
				
			||||||
			<VolumeSlider />
 | 
								{!Platform.isTV && <VolumeSlider />}
 | 
				
			||||||
			<ProgressText />
 | 
								<ProgressText />
 | 
				
			||||||
		</View>
 | 
							</View>
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,8 @@
 | 
				
			|||||||
import { QueryIdentifier, QueryPage, WatchItem, WatchItemP, useFetch } from "@kyoo/models";
 | 
					import { QueryIdentifier, QueryPage, WatchItem, WatchItemP, useFetch } from "@kyoo/models";
 | 
				
			||||||
import { Head } from "@kyoo/primitives";
 | 
					import { Head } from "@kyoo/primitives";
 | 
				
			||||||
import { useState, useEffect, ComponentProps } from "react";
 | 
					import { useState, useEffect, ComponentProps } from "react";
 | 
				
			||||||
import { Platform, Pressable, StyleSheet } from "react-native";
 | 
					import { BackHandler, Platform, Pressable, StyleSheet } from "react-native";
 | 
				
			||||||
 | 
					import { useTVEventHandler } from "@kyoo/primitives/tv";
 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
import { useRouter } from "solito/router";
 | 
					import { useRouter } from "solito/router";
 | 
				
			||||||
import { useAtom } from "jotai";
 | 
					import { useAtom } from "jotai";
 | 
				
			||||||
@ -106,6 +107,19 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
 | 
				
			|||||||
		document.addEventListener("pointermove", handler);
 | 
							document.addEventListener("pointermove", handler);
 | 
				
			||||||
		return () => document.removeEventListener("pointermove", handler);
 | 
							return () => document.removeEventListener("pointermove", handler);
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
						useTVEventHandler((e) => {
 | 
				
			||||||
 | 
							if (e.eventType === "cancel") setMouseMoved(false);
 | 
				
			||||||
 | 
							show();
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						useEffect(() => {
 | 
				
			||||||
 | 
							const handler = BackHandler.addEventListener("hardwareBackPress", () => {
 | 
				
			||||||
 | 
								if (!displayControls) return false;
 | 
				
			||||||
 | 
								setMouseMoved(false);
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							return () => handler.remove();
 | 
				
			||||||
 | 
						}, [displayControls]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	useEffect(() => {
 | 
						useEffect(() => {
 | 
				
			||||||
		if (Platform.OS !== "web" || !/Mobi/i.test(window.navigator.userAgent)) return;
 | 
							if (Platform.OS !== "web" || !/Mobi/i.test(window.navigator.userAgent)) return;
 | 
				
			||||||
@ -116,7 +130,7 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
 | 
				
			|||||||
	if (error || playbackError)
 | 
						if (error || playbackError)
 | 
				
			||||||
		return (
 | 
							return (
 | 
				
			||||||
			<>
 | 
								<>
 | 
				
			||||||
				<Back isLoading={false} {...css({ position: "relative", bg: (theme) => theme.appbar })} />
 | 
									<Back isLoading={false} {...css({ position: "relative", bg: (theme) => theme.accent })} />
 | 
				
			||||||
				<ErrorView error={error ?? { errors: [playbackError!] }} />
 | 
									<ErrorView error={error ?? { errors: [playbackError!] }} />
 | 
				
			||||||
			</>
 | 
								</>
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user