Keep controls open in menues or hover

This commit is contained in:
Zoe Roux 2025-07-26 00:38:56 +02:00
parent f7eed87c5c
commit 9cc8ee7490
No known key found for this signature in database
4 changed files with 93 additions and 45 deletions

View File

@ -1,5 +1,6 @@
import SkipNext from "@material-symbols/svg-400/rounded/skip_next-fill.svg";
import SkipPrevious from "@material-symbols/svg-400/rounded/skip_previous-fill.svg";
import type { ComponentProps } from "react";
import { useTranslation } from "react-i18next";
import { Platform, View, type ViewProps } from "react-native";
import type { VideoPlayer } from "react-native-video";
@ -9,6 +10,7 @@ import {
H2,
IconButton,
Link,
type Menu,
noTouch,
Poster,
tooltip,
@ -23,12 +25,18 @@ export const BottomControls = ({
poster,
name,
chapters,
previous,
next,
setMenu,
...props
}: {
player: VideoPlayer;
poster: KImage;
name: string;
chapters: Chapter[];
previous: string | null;
next: string | null;
setMenu: (isOpen: boolean) => void;
} & ViewProps) => {
const { css } = useYoshiki();
@ -69,7 +77,12 @@ export const BottomControls = ({
name
</H2>
<ProgressBar player={player} chapters={chapters} />
<ControlButtons player={player} />
<ControlButtons
player={player}
previous={previous}
next={next}
setMenu={setMenu}
/>
</View>
</View>
);
@ -79,16 +92,23 @@ const ControlButtons = ({
player,
previous,
next,
setMenu,
...props
}: {
player: VideoPlayer;
previous: string;
next: string;
previous: string | null;
next: string | null;
setMenu: (isOpen: boolean) => void;
}) => {
const { css } = useYoshiki();
const { t } = useTranslation();
const spacing = css({ marginHorizontal: ts(1) });
const menuProps = {
onMenuOpen: () => setMenu(true),
onMenuClose: () => setMenu(false),
...spacing,
} satisfies Partial<ComponentProps<typeof Menu>>;
return (
<View
@ -130,9 +150,9 @@ const ControlButtons = ({
<ProgressText player={player} {...spacing} />
</View>
<View {...css({ flexDirection: "row" })}>
<SubtitleMenu {...(spacing as any)} />
<AudioMenu {...(spacing as any)} />
<QualityMenu {...(spacing as any)} />
<SubtitleMenu {...menuProps} />
<AudioMenu {...menuProps} />
<QualityMenu {...menuProps} />
{Platform.OS === "web" && <FullscreenButton {...spacing} />}
</View>
</View>

View File

@ -1,6 +1,8 @@
import { useState } from "react";
import type { ViewProps } from "react-native";
import type { VideoPlayer } from "react-native-video";
import { useYoshiki } from "yoshiki/native";
import type { KImage } from "~/models";
import type { Chapter, KImage } from "~/models";
import { Back } from "./back";
import { BottomControls } from "./bottom-controls";
import { TouchControls } from "./touch";
@ -8,54 +10,75 @@ import { TouchControls } from "./touch";
export const Controls = ({
player,
title,
subTitle,
poster,
chapters,
previous,
next,
}: {
player: VideoPlayer;
title: string;
description: string | null;
poster: KImage | null;
subTitle: string;
poster: KImage;
chapters: Chapter[];
previous: string | null;
next: string | null;
}) => {
const { css } = useYoshiki();
const [hover, setHover] = useState(false);
const [menuOpenned, setMenu] = useState(false);
// <TouchControls previousSlug={previousSlug} nextSlug={nextSlug} />
const hoverControls = {
onPointerEnter: (e) => {
if (e.nativeEvent.pointerType === "mouse") setHover(true);
},
onPointerLeave: (e) => {
if (e.nativeEvent.pointerType === "mouse") setHover(false);
},
} satisfies ViewProps;
return (
<TouchControls
player={player}
// onPointerEnter={(e) => {
// if (e.nativeEvent.pointerType === "mouse")
// setHover((x) => ({ ...x, mouseHover: true }));
// }}
// onPointerLeave={(e) => {
// if (e.nativeEvent.pointerType === "mouse")
// setHover((x) => ({ ...x, mouseHover: false }));
// }}
>
<TouchControls player={player} forceShow={hover || menuOpenned}>
<Back
name={title}
{...css({
// pointerEvents: "auto",
position: "absolute",
top: 0,
left: 0,
right: 0,
bg: (theme) => theme.darkOverlay,
})}
{...css(
{
// pointerEvents: "auto",
position: "absolute",
top: 0,
left: 0,
right: 0,
bg: (theme) => theme.darkOverlay,
},
hoverControls,
)}
/>
<BottomControls
player={player}
name={title}
poster={poster!}
chapters={[]}
{...css({
// Fixed is used because firefox android make the hover disapear under the navigation bar in absolute
// position: Platform.OS === "web" ? ("fixed" as any) : "absolute",
position: "absolute",
bottom: 0,
left: 0,
right: 0,
bg: (theme) => theme.darkOverlay,
})}
name={subTitle}
poster={poster}
chapters={chapters}
previous={previous}
next={next}
setMenu={setMenu}
{...css(
{
// Fixed is used because firefox android make the hover disapear under the navigation bar in absolute
// position: Platform.OS === "web" ? ("fixed" as any) : "absolute",
position: "absolute",
bottom: 0,
left: 0,
right: 0,
bg: (theme) => theme.darkOverlay,
},
hoverControls,
)}
/>
</TouchControls>
);
};
export { LoadingIndicator } from "./misc";

View File

@ -12,13 +12,15 @@ import { useIsTouch } from "~/primitives";
export const TouchControls = ({
player,
children,
forceShow = false,
...props
}: { player: VideoPlayer } & PressableProps) => {
}: { player: VideoPlayer; forceShow?: boolean } & PressableProps) => {
const { css } = useYoshiki();
const isTouch = useIsTouch();
const [shouldShow, setShow] = useState(true);
const [_show, setShow] = useState(true);
const hideTimeout = useRef<NodeJS.Timeout | null>(null);
const shouldShow = forceShow || _show;
const show = useCallback((val: boolean = true) => {
setShow(val);
if (hideTimeout.current) clearTimeout(hideTimeout.current);
@ -27,7 +29,6 @@ export const TouchControls = ({
setShow(false);
}, 2500);
}, []);
// TODO: handle mouse hover & seek
// On mouse move
useEffect(() => {

View File

@ -8,7 +8,7 @@ import { useToken } from "~/providers/account-context";
import { useLocalSetting } from "~/providers/settings";
import { type QueryIdentifier, useFetch } from "~/query";
import { useQueryState } from "~/utils";
import { LoadingIndicator } from "./controls";
import { Controls, LoadingIndicator } from "./controls";
const mapMetadata = (item: FullVideo | undefined) => {
if (!item) return null;
@ -134,7 +134,11 @@ export const Player = () => {
/>
<ContrastArea mode="dark">
<LoadingIndicator player={player} />
<Controls player={player} {...metadata} />
<Controls
player={player}
title={metadata?.title}
subTitle={metadata?.subtitle}
/>
</ContrastArea>
</View>
);