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 SkipNext from "@material-symbols/svg-400/rounded/skip_next-fill.svg";
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 type { ComponentProps } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Platform, View, type ViewProps } from "react-native"; import { Platform, View, type ViewProps } from "react-native";
import type { VideoPlayer } from "react-native-video"; import type { VideoPlayer } from "react-native-video";
@ -9,6 +10,7 @@ import {
H2, H2,
IconButton, IconButton,
Link, Link,
type Menu,
noTouch, noTouch,
Poster, Poster,
tooltip, tooltip,
@ -23,12 +25,18 @@ export const BottomControls = ({
poster, poster,
name, name,
chapters, chapters,
previous,
next,
setMenu,
...props ...props
}: { }: {
player: VideoPlayer; player: VideoPlayer;
poster: KImage; poster: KImage;
name: string; name: string;
chapters: Chapter[]; chapters: Chapter[];
previous: string | null;
next: string | null;
setMenu: (isOpen: boolean) => void;
} & ViewProps) => { } & ViewProps) => {
const { css } = useYoshiki(); const { css } = useYoshiki();
@ -69,7 +77,12 @@ export const BottomControls = ({
name name
</H2> </H2>
<ProgressBar player={player} chapters={chapters} /> <ProgressBar player={player} chapters={chapters} />
<ControlButtons player={player} /> <ControlButtons
player={player}
previous={previous}
next={next}
setMenu={setMenu}
/>
</View> </View>
</View> </View>
); );
@ -79,16 +92,23 @@ const ControlButtons = ({
player, player,
previous, previous,
next, next,
setMenu,
...props ...props
}: { }: {
player: VideoPlayer; player: VideoPlayer;
previous: string; previous: string | null;
next: string; next: string | null;
setMenu: (isOpen: boolean) => void;
}) => { }) => {
const { css } = useYoshiki(); const { css } = useYoshiki();
const { t } = useTranslation(); const { t } = useTranslation();
const spacing = css({ marginHorizontal: ts(1) }); const spacing = css({ marginHorizontal: ts(1) });
const menuProps = {
onMenuOpen: () => setMenu(true),
onMenuClose: () => setMenu(false),
...spacing,
} satisfies Partial<ComponentProps<typeof Menu>>;
return ( return (
<View <View
@ -130,9 +150,9 @@ const ControlButtons = ({
<ProgressText player={player} {...spacing} /> <ProgressText player={player} {...spacing} />
</View> </View>
<View {...css({ flexDirection: "row" })}> <View {...css({ flexDirection: "row" })}>
<SubtitleMenu {...(spacing as any)} /> <SubtitleMenu {...menuProps} />
<AudioMenu {...(spacing as any)} /> <AudioMenu {...menuProps} />
<QualityMenu {...(spacing as any)} /> <QualityMenu {...menuProps} />
{Platform.OS === "web" && <FullscreenButton {...spacing} />} {Platform.OS === "web" && <FullscreenButton {...spacing} />}
</View> </View>
</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 type { VideoPlayer } from "react-native-video";
import { useYoshiki } from "yoshiki/native"; import { useYoshiki } from "yoshiki/native";
import type { KImage } from "~/models"; import type { Chapter, KImage } from "~/models";
import { Back } from "./back"; import { Back } from "./back";
import { BottomControls } from "./bottom-controls"; import { BottomControls } from "./bottom-controls";
import { TouchControls } from "./touch"; import { TouchControls } from "./touch";
@ -8,54 +10,75 @@ import { TouchControls } from "./touch";
export const Controls = ({ export const Controls = ({
player, player,
title, title,
subTitle,
poster, poster,
chapters,
previous,
next,
}: { }: {
player: VideoPlayer; player: VideoPlayer;
title: string; title: string;
description: string | null; subTitle: string;
poster: KImage | null; poster: KImage;
chapters: Chapter[];
previous: string | null;
next: string | null;
}) => { }) => {
const { css } = useYoshiki(); const { css } = useYoshiki();
const [hover, setHover] = useState(false);
const [menuOpenned, setMenu] = useState(false);
// <TouchControls previousSlug={previousSlug} nextSlug={nextSlug} /> // <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 ( return (
<TouchControls <TouchControls player={player} forceShow={hover || menuOpenned}>
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 }));
// }}
>
<Back <Back
name={title} name={title}
{...css({ {...css(
// pointerEvents: "auto", {
position: "absolute", // pointerEvents: "auto",
top: 0, position: "absolute",
left: 0, top: 0,
right: 0, left: 0,
bg: (theme) => theme.darkOverlay, right: 0,
})} bg: (theme) => theme.darkOverlay,
},
hoverControls,
)}
/> />
<BottomControls <BottomControls
player={player} player={player}
name={title} name={subTitle}
poster={poster!} poster={poster}
chapters={[]} chapters={chapters}
{...css({ previous={previous}
// Fixed is used because firefox android make the hover disapear under the navigation bar in absolute next={next}
// position: Platform.OS === "web" ? ("fixed" as any) : "absolute", setMenu={setMenu}
position: "absolute", {...css(
bottom: 0, {
left: 0, // Fixed is used because firefox android make the hover disapear under the navigation bar in absolute
right: 0, // position: Platform.OS === "web" ? ("fixed" as any) : "absolute",
bg: (theme) => theme.darkOverlay, position: "absolute",
})} bottom: 0,
left: 0,
right: 0,
bg: (theme) => theme.darkOverlay,
},
hoverControls,
)}
/> />
</TouchControls> </TouchControls>
); );
}; };
export { LoadingIndicator } from "./misc";

View File

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

View File

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