mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-12-07 05:35:07 -05:00
Implement bottom controls
This commit is contained in:
parent
bcff909c21
commit
d93ce325e9
@ -1,10 +1,4 @@
|
||||
import type React from "react";
|
||||
import {
|
||||
type ComponentProps,
|
||||
type ComponentType,
|
||||
type ForwardedRef,
|
||||
forwardRef,
|
||||
} from "react";
|
||||
import type { ComponentProps, ComponentType } from "react";
|
||||
import { Platform, type PressableProps } from "react-native";
|
||||
import type { SvgProps } from "react-native-svg";
|
||||
import type { YoshikiStyle } from "yoshiki";
|
||||
@ -13,12 +7,6 @@ import { PressableFeedback } from "./links";
|
||||
import { P } from "./text";
|
||||
import { type Breakpoint, focusReset, ts } from "./utils";
|
||||
|
||||
declare module "react" {
|
||||
function forwardRef<T, P = {}>(
|
||||
render: (props: P, ref: React.ForwardedRef<T>) => React.ReactElement | null,
|
||||
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
|
||||
}
|
||||
|
||||
export type Icon = ComponentType<SvgProps>;
|
||||
|
||||
type IconProps = {
|
||||
@ -54,27 +42,21 @@ export const Icon = ({ icon: Icon, color, size = 24, ...props }: IconProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const IconButton = forwardRef(function IconButton<
|
||||
AsProps = PressableProps,
|
||||
>(
|
||||
{
|
||||
icon,
|
||||
size,
|
||||
color,
|
||||
as,
|
||||
...asProps
|
||||
}: IconProps & {
|
||||
as?: ComponentType<AsProps>;
|
||||
} & AsProps,
|
||||
ref: ForwardedRef<unknown>,
|
||||
) {
|
||||
export const IconButton = <AsProps = PressableProps>({
|
||||
icon,
|
||||
size,
|
||||
color,
|
||||
as,
|
||||
...asProps
|
||||
}: IconProps & {
|
||||
as?: ComponentType<AsProps>;
|
||||
} & AsProps) => {
|
||||
const { css, theme } = useYoshiki();
|
||||
|
||||
const Container = as ?? PressableFeedback;
|
||||
|
||||
return (
|
||||
<Container
|
||||
ref={ref as any}
|
||||
focusRipple
|
||||
{...(css(
|
||||
{
|
||||
@ -102,7 +84,7 @@ export const IconButton = forwardRef(function IconButton<
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const IconFab = <AsProps = PressableProps>(
|
||||
props: ComponentProps<typeof IconButton<AsProps>>,
|
||||
|
||||
@ -20,11 +20,6 @@ export const Back = ({ name, ...props }: { name: string } & ViewProps) => {
|
||||
<View
|
||||
{...css(
|
||||
{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bg: (theme) => theme.darkOverlay,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
|
||||
140
front/src/ui/player/controls/bottom-controls.tsx
Normal file
140
front/src/ui/player/controls/bottom-controls.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
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 { useTranslation } from "react-i18next";
|
||||
import { Platform, View, type ViewProps } from "react-native";
|
||||
import type { VideoPlayer } from "react-native-video";
|
||||
import { percent, useYoshiki } from "yoshiki/native";
|
||||
import type { Chapter, KImage } from "~/models";
|
||||
import {
|
||||
H2,
|
||||
IconButton,
|
||||
Link,
|
||||
noTouch,
|
||||
Poster,
|
||||
tooltip,
|
||||
ts,
|
||||
} from "~/primitives";
|
||||
import { FullscreenButton, PlayButton, VolumeSlider } from "./misc";
|
||||
import { ProgressBar, ProgressText } from "./progress";
|
||||
import { AudioMenu, QualityMenu, SubtitleMenu } from "./tracks-menu";
|
||||
|
||||
export const BottomControls = ({
|
||||
player,
|
||||
poster,
|
||||
name,
|
||||
chapters,
|
||||
...props
|
||||
}: {
|
||||
player: VideoPlayer;
|
||||
poster: KImage;
|
||||
name: string;
|
||||
chapters: Chapter[];
|
||||
} & ViewProps) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
<View
|
||||
{...css(
|
||||
{
|
||||
flexDirection: "row",
|
||||
padding: ts(1),
|
||||
},
|
||||
props,
|
||||
)}
|
||||
>
|
||||
<View
|
||||
{...css({
|
||||
width: "15%",
|
||||
display: { xs: "none", sm: "flex" },
|
||||
position: "relative",
|
||||
})}
|
||||
>
|
||||
<Poster
|
||||
src={poster}
|
||||
quality="low"
|
||||
layout={{ width: percent(100) }}
|
||||
{...(css({ position: "absolute", bottom: 0 }) as any)}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
{...css({
|
||||
marginLeft: { xs: ts(0.5), sm: ts(3) },
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
maxWidth: percent(100),
|
||||
})}
|
||||
>
|
||||
<H2 numberOfLines={1} {...css({ paddingBottom: ts(1) })}>
|
||||
name
|
||||
</H2>
|
||||
<ProgressBar player={player} chapters={chapters} />
|
||||
<ControlButtons player={player} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const ControlButtons = ({
|
||||
player,
|
||||
previous,
|
||||
next,
|
||||
...props
|
||||
}: {
|
||||
player: VideoPlayer;
|
||||
previous: string;
|
||||
next: string;
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const spacing = css({ marginHorizontal: ts(1) });
|
||||
|
||||
return (
|
||||
<View
|
||||
{...css(
|
||||
{
|
||||
flexDirection: "row",
|
||||
flexGrow: 1,
|
||||
justifyContent: "space-between",
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
props,
|
||||
)}
|
||||
>
|
||||
<View {...css({ flexDirection: "row" })}>
|
||||
<View {...css({ flexDirection: "row" }, noTouch)}>
|
||||
{previous && (
|
||||
<IconButton
|
||||
icon={SkipPrevious}
|
||||
as={Link}
|
||||
href={previous}
|
||||
replace
|
||||
{...tooltip(t("player.previous"), true)}
|
||||
{...spacing}
|
||||
/>
|
||||
)}
|
||||
<PlayButton player={player} {...spacing} />
|
||||
{next && (
|
||||
<IconButton
|
||||
icon={SkipNext}
|
||||
as={Link}
|
||||
href={next}
|
||||
replace
|
||||
{...tooltip(t("player.next"), true)}
|
||||
{...spacing}
|
||||
/>
|
||||
)}
|
||||
{Platform.OS === "web" && <VolumeSlider player={player} />}
|
||||
</View>
|
||||
<ProgressText player={player} {...spacing} />
|
||||
</View>
|
||||
<View {...css({ flexDirection: "row" })}>
|
||||
<SubtitleMenu {...(spacing as any)} />
|
||||
<AudioMenu {...(spacing as any)} />
|
||||
<QualityMenu {...(spacing as any)} />
|
||||
{Platform.OS === "web" && <FullscreenButton {...spacing} />}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@ -1,45 +1,14 @@
|
||||
import ArrowBack from "@material-symbols/svg-400/rounded/arrow_back-fill.svg";
|
||||
import { useRouter } from "expo-router";
|
||||
import {
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
type ImageStyle,
|
||||
Platform,
|
||||
Pressable,
|
||||
View,
|
||||
type ViewProps,
|
||||
} from "react-native";
|
||||
import { useEvent, type VideoPlayer } from "react-native-video";
|
||||
import { percent, rem, useYoshiki } from "yoshiki/native";
|
||||
import type { AudioTrack, Chapter, KImage, Subtitle } from "~/models";
|
||||
import {
|
||||
alpha,
|
||||
CircularProgress,
|
||||
H1,
|
||||
H2,
|
||||
IconButton,
|
||||
Poster,
|
||||
PressableFeedback,
|
||||
Skeleton,
|
||||
Slider,
|
||||
Tooltip,
|
||||
tooltip,
|
||||
ts,
|
||||
useIsTouch,
|
||||
} from "~/primitives";
|
||||
import { LeftButtons } from "./components/left-buttons";
|
||||
import { RightButtons } from "./components/right-buttons";
|
||||
import { BottomScrubber, ScrubberTooltip } from "./scrubber";
|
||||
import type { VideoPlayer } from "react-native-video";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
import type { KImage } from "~/models";
|
||||
import { Back } from "./back";
|
||||
import { BottomControls } from "./bottom-controls";
|
||||
import { TouchControls } from "./touch";
|
||||
|
||||
export const Controls = ({
|
||||
player,
|
||||
title,
|
||||
poster,
|
||||
}: {
|
||||
player: VideoPlayer;
|
||||
title: string;
|
||||
@ -47,16 +16,11 @@ export const Controls = ({
|
||||
poster: KImage | null;
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
// const show = useAtomValue(hoverAtom);
|
||||
// const setHover = useSetAtom(hoverReasonAtom);
|
||||
// const isSeeking = useAtomValue(seekingAtom);
|
||||
// const isTouch = useIsTouch();
|
||||
|
||||
// const showBottomSeeker = isSeeking && isTouch;
|
||||
|
||||
// <TouchControls previousSlug={previousSlug} nextSlug={nextSlug} />
|
||||
return (
|
||||
<View
|
||||
<TouchControls
|
||||
player={player}
|
||||
// onPointerEnter={(e) => {
|
||||
// if (e.nativeEvent.pointerType === "mouse")
|
||||
// setHover((x) => ({ ...x, mouseHover: true }));
|
||||
@ -65,118 +29,33 @@ export const Controls = ({
|
||||
// if (e.nativeEvent.pointerType === "mouse")
|
||||
// setHover((x) => ({ ...x, mouseHover: false }));
|
||||
// }}
|
||||
{...css({
|
||||
// TODO: animate show
|
||||
//display: !show ? "none" : "flex",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
// box-none does not work on the web while none does not work on android
|
||||
pointerEvents: Platform.OS === "web" ? "none" : "box-none",
|
||||
})}
|
||||
>
|
||||
<Back
|
||||
name={title}
|
||||
{...css({
|
||||
pointerEvents: "auto",
|
||||
// pointerEvents: "auto",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bg: (theme) => theme.darkOverlay,
|
||||
})}
|
||||
/>
|
||||
<View
|
||||
<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: Platform.OS === "web" ? ("fixed" as any) : "absolute",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bg: (theme) => theme.darkOverlay,
|
||||
flexDirection: "row",
|
||||
pointerEvents: "auto",
|
||||
padding: percent(1),
|
||||
})}
|
||||
>
|
||||
<VideoPoster poster={poster} alt={showName} isLoading={isLoading} />
|
||||
<View
|
||||
{...css({
|
||||
marginLeft: { xs: ts(0.5), sm: ts(3) },
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
maxWidth: percent(100),
|
||||
})}
|
||||
>
|
||||
{!showBottomSeeker && (
|
||||
<H2 numberOfLines={1} {...css({ paddingBottom: ts(1) })}>
|
||||
{isLoading ? (
|
||||
<Skeleton {...css({ width: rem(15), height: rem(2) })} />
|
||||
) : (
|
||||
name
|
||||
)}
|
||||
</H2>
|
||||
)}
|
||||
<ProgressBar chapters={chapters} url={url} />
|
||||
{showBottomSeeker ? (
|
||||
<BottomScrubber url={url} chapters={chapters} />
|
||||
) : (
|
||||
<View
|
||||
{...css({
|
||||
flexDirection: "row",
|
||||
flexGrow: 1,
|
||||
justifyContent: "space-between",
|
||||
flexWrap: "wrap",
|
||||
})}
|
||||
>
|
||||
<LeftButtons previousSlug={previousSlug} nextSlug={nextSlug} />
|
||||
<RightButtons
|
||||
subtitles={subtitles}
|
||||
audios={audios}
|
||||
fonts={fonts}
|
||||
onMenuOpen={() => setHover((x) => ({ ...x, menuOpened: true }))}
|
||||
onMenuClose={() => {
|
||||
// Disable hover since the menu overlay makes the mouseout unreliable.
|
||||
setHover((x) => ({
|
||||
...x,
|
||||
menuOpened: false,
|
||||
mouseHover: false,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const VideoPoster = ({
|
||||
poster,
|
||||
alt,
|
||||
isLoading,
|
||||
}: {
|
||||
poster?: KyooImage | null;
|
||||
alt?: string;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
<View
|
||||
{...css({
|
||||
width: "15%",
|
||||
display: { xs: "none", sm: "flex" },
|
||||
position: "relative",
|
||||
})}
|
||||
>
|
||||
<Poster
|
||||
src={poster}
|
||||
quality="low"
|
||||
alt={alt}
|
||||
forcedLoading={isLoading}
|
||||
layout={{ width: percent(100) }}
|
||||
{...(css({ position: "absolute", bottom: 0 }) as { style: ImageStyle })}
|
||||
/>
|
||||
</View>
|
||||
</TouchControls>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import FullscreenExit from "@material-symbols/svg-400/rounded/fullscreen_exit-fill.svg";
|
||||
import Fullscreen from "@material-symbols/svg-400/rounded/fullscreen-fill.svg";
|
||||
import Pause from "@material-symbols/svg-400/rounded/pause-fill.svg";
|
||||
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
|
||||
import VolumeDown from "@material-symbols/svg-400/rounded/volume_down-fill.svg";
|
||||
import VolumeMute from "@material-symbols/svg-400/rounded/volume_mute-fill.svg";
|
||||
import VolumeOff from "@material-symbols/svg-400/rounded/volume_off-fill.svg";
|
||||
import VolumeUp from "@material-symbols/svg-400/rounded/volume_up-fill.svg";
|
||||
import { useState } from "react";
|
||||
import { type ComponentProps, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View } from "react-native";
|
||||
import { type PressableProps, View } from "react-native";
|
||||
import { useEvent, type VideoPlayer } from "react-native-video";
|
||||
import { px, useYoshiki } from "yoshiki/native";
|
||||
import {
|
||||
@ -18,7 +20,12 @@ import {
|
||||
ts,
|
||||
} from "~/primitives";
|
||||
|
||||
export const PlayButton = ({ player, ...props }: { player: VideoPlayer }) => {
|
||||
export const PlayButton = ({
|
||||
player,
|
||||
...props
|
||||
}: { player: VideoPlayer } & Partial<
|
||||
ComponentProps<typeof IconButton<PressableProps>>
|
||||
>) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [playing, setPlay] = useState(player.isPlaying);
|
||||
@ -39,6 +46,24 @@ export const PlayButton = ({ player, ...props }: { player: VideoPlayer }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const FullscreenButton = (
|
||||
props: Partial<ComponentProps<typeof IconButton<PressableProps>>>,
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// TODO: actually implement that
|
||||
const [fullscreen, setFullscreen] = useState(true);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
icon={fullscreen ? FullscreenExit : Fullscreen}
|
||||
onPress={() => console.log("lol")}
|
||||
{...tooltip(t("player.fullscreen"), true)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const VolumeSlider = ({ player, ...props }: { player: VideoPlayer }) => {
|
||||
const { css } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -45,6 +45,7 @@ export const TouchControls = ({
|
||||
|
||||
return (
|
||||
<DoublePressable
|
||||
tabIndex={-1}
|
||||
onPress={() => {
|
||||
if (isTouch) {
|
||||
show(!shouldShow);
|
||||
|
||||
68
front/src/ui/player/controls/tracks-menu.tsx
Normal file
68
front/src/ui/player/controls/tracks-menu.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import ClosedCaption from "@material-symbols/svg-400/rounded/closed_caption-fill.svg";
|
||||
import MusicNote from "@material-symbols/svg-400/rounded/music_note-fill.svg";
|
||||
import SettingsIcon from "@material-symbols/svg-400/rounded/settings-fill.svg";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconButton, Menu, tooltip } from "~/primitives";
|
||||
|
||||
type MenuProps = ComponentProps<typeof Menu<ComponentProps<typeof IconButton>>>;
|
||||
|
||||
export const SubtitleMenu = (props: Partial<MenuProps>) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// {subtitles && subtitles.length > 0 && (
|
||||
// )}
|
||||
return (
|
||||
<Menu
|
||||
Trigger={IconButton}
|
||||
icon={ClosedCaption}
|
||||
{...tooltip(t("player.subtitles"), true)}
|
||||
{...props}
|
||||
>
|
||||
{/* <Menu.Item */}
|
||||
{/* label={t("player.subtitle-none")} */}
|
||||
{/* selected={!selectedSubtitle} */}
|
||||
{/* onSelect={() => setSubtitle(null)} */}
|
||||
{/* /> */}
|
||||
{/* {subtitles */}
|
||||
{/* .filter((x) => !!x.link) */}
|
||||
{/* .map((x, i) => ( */}
|
||||
{/* <Menu.Item */}
|
||||
{/* key={x.index ?? i} */}
|
||||
{/* label={ */}
|
||||
{/* x.link ? getSubtitleName(x) : `${getSubtitleName(x)} (${x.codec})` */}
|
||||
{/* } */}
|
||||
{/* selected={selectedSubtitle === x} */}
|
||||
{/* disabled={!x.link} */}
|
||||
{/* onSelect={() => setSubtitle(x)} */}
|
||||
{/* /> */}
|
||||
{/* ))} */}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export const AudioMenu = (props: Partial<MenuProps>) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Menu
|
||||
Trigger={IconButton}
|
||||
icon={MusicNote}
|
||||
{...tooltip(t("player.audios"), true)}
|
||||
{...props}
|
||||
></Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export const QualityMenu = (props: Partial<MenuProps>) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Menu
|
||||
Trigger={IconButton}
|
||||
icon={SettingsIcon}
|
||||
{...tooltip(t("player.quality"), true)}
|
||||
{...props}
|
||||
></Menu>
|
||||
);
|
||||
};
|
||||
@ -6,10 +6,6 @@ import {
|
||||
touchOnly,
|
||||
ts,
|
||||
} from "@kyoo/primitives";
|
||||
import Pause from "@material-symbols/svg-400/rounded/pause-fill.svg";
|
||||
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-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 { useAtom, useAtomValue } from "jotai";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Platform, View } from "react-native";
|
||||
@ -17,55 +13,6 @@ import { px, type Stylable, useYoshiki } from "yoshiki/native";
|
||||
import { HoverTouch, hoverAtom } from "../controls";
|
||||
import { playAtom } from "./state";
|
||||
|
||||
export const LeftButtons = ({
|
||||
previousSlug,
|
||||
nextSlug,
|
||||
}: {
|
||||
previousSlug?: string | null;
|
||||
nextSlug?: string | null;
|
||||
}) => {
|
||||
const { css } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
const [isPlaying, setPlay] = useAtom(playAtom);
|
||||
|
||||
const spacing = css({ marginHorizontal: ts(1) });
|
||||
|
||||
return (
|
||||
<View {...css({ flexDirection: "row" })}>
|
||||
<View {...css({ flexDirection: "row" }, noTouch)}>
|
||||
{previousSlug && (
|
||||
<IconButton
|
||||
icon={SkipPrevious}
|
||||
as={Link}
|
||||
href={previousSlug}
|
||||
replace
|
||||
{...tooltip(t("player.previous"), true)}
|
||||
{...spacing}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
icon={isPlaying ? Pause : PlayArrow}
|
||||
onPress={() => setPlay(!isPlaying)}
|
||||
{...tooltip(isPlaying ? t("player.pause") : t("player.play"), true)}
|
||||
{...spacing}
|
||||
/>
|
||||
{nextSlug && (
|
||||
<IconButton
|
||||
icon={SkipNext}
|
||||
as={Link}
|
||||
href={nextSlug}
|
||||
replace
|
||||
{...tooltip(t("player.next"), true)}
|
||||
{...spacing}
|
||||
/>
|
||||
)}
|
||||
{Platform.OS === "web" && <VolumeSlider />}
|
||||
</View>
|
||||
<ProgressText {...css({ marginLeft: ts(1) })} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const TouchControls = ({
|
||||
previousSlug,
|
||||
nextSlug,
|
||||
|
||||
@ -1,114 +0,0 @@
|
||||
/*
|
||||
* 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 type { Audio, Subtitle } from "@kyoo/models";
|
||||
import { IconButton, Menu, tooltip, ts } from "@kyoo/primitives";
|
||||
import ClosedCaption from "@material-symbols/svg-400/rounded/closed_caption-fill.svg";
|
||||
import Fullscreen from "@material-symbols/svg-400/rounded/fullscreen-fill.svg";
|
||||
import FullscreenExit from "@material-symbols/svg-400/rounded/fullscreen_exit-fill.svg";
|
||||
import MusicNote from "@material-symbols/svg-400/rounded/music_note-fill.svg";
|
||||
import SettingsIcon from "@material-symbols/svg-400/rounded/settings-fill.svg";
|
||||
import { useAtom } from "jotai";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Platform, View } from "react-native";
|
||||
import { type Stylable, useYoshiki } from "yoshiki/native";
|
||||
import { useSubtitleName } from "../../../../packages/ui/src/utils";
|
||||
import { fullscreenAtom, subtitleAtom } from "./state";
|
||||
import { AudiosMenu, QualitiesMenu } from "./video";
|
||||
|
||||
export const RightButtons = ({
|
||||
audios,
|
||||
subtitles,
|
||||
fonts,
|
||||
onMenuOpen,
|
||||
onMenuClose,
|
||||
...props
|
||||
}: {
|
||||
audios?: Audio[];
|
||||
subtitles?: Subtitle[];
|
||||
fonts?: string[];
|
||||
onMenuOpen: () => void;
|
||||
onMenuClose: () => void;
|
||||
} & Stylable) => {
|
||||
const { css } = useYoshiki();
|
||||
const { t } = useTranslation();
|
||||
const getSubtitleName = useSubtitleName();
|
||||
const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom);
|
||||
const [selectedSubtitle, setSubtitle] = useAtom(subtitleAtom);
|
||||
|
||||
const spacing = css({ marginHorizontal: ts(1) });
|
||||
|
||||
return (
|
||||
<View {...css({ flexDirection: "row" }, props)}>
|
||||
{subtitles && subtitles.length > 0 && (
|
||||
<Menu
|
||||
Trigger={IconButton}
|
||||
icon={ClosedCaption}
|
||||
onMenuOpen={onMenuOpen}
|
||||
onMenuClose={onMenuClose}
|
||||
{...tooltip(t("player.subtitles"), true)}
|
||||
{...spacing}
|
||||
>
|
||||
<Menu.Item
|
||||
label={t("player.subtitle-none")}
|
||||
selected={!selectedSubtitle}
|
||||
onSelect={() => setSubtitle(null)}
|
||||
/>
|
||||
{subtitles
|
||||
.filter((x) => !!x.link)
|
||||
.map((x, i) => (
|
||||
<Menu.Item
|
||||
key={x.index ?? i}
|
||||
label={x.link ? getSubtitleName(x) : `${getSubtitleName(x)} (${x.codec})`}
|
||||
selected={selectedSubtitle === x}
|
||||
disabled={!x.link}
|
||||
onSelect={() => setSubtitle(x)}
|
||||
/>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
<AudiosMenu
|
||||
Trigger={IconButton}
|
||||
icon={MusicNote}
|
||||
onMenuOpen={onMenuOpen}
|
||||
onMenuClose={onMenuClose}
|
||||
audios={audios}
|
||||
{...tooltip(t("player.audios"), true)}
|
||||
{...spacing}
|
||||
/>
|
||||
<QualitiesMenu
|
||||
Trigger={IconButton}
|
||||
icon={SettingsIcon}
|
||||
onMenuOpen={onMenuOpen}
|
||||
onMenuClose={onMenuClose}
|
||||
{...tooltip(t("player.quality"), true)}
|
||||
{...spacing}
|
||||
/>
|
||||
{Platform.OS === "web" && (
|
||||
<IconButton
|
||||
icon={isFullscreen ? FullscreenExit : Fullscreen}
|
||||
onPress={() => setFullscreen(!isFullscreen)}
|
||||
{...tooltip(t("player.fullscreen"), true)}
|
||||
{...spacing}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user