mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-03 05:34:23 -04:00
Create menu (bottom sheet or side sheet)
This commit is contained in:
parent
b0eb8c3b42
commit
1139a726c9
@ -28,6 +28,7 @@ import { createQueryClient } from "@kyoo/models";
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { initReactI18next } from "react-i18next";
|
import { initReactI18next } from "react-i18next";
|
||||||
import { getLocales } from "expo-localization";
|
import { getLocales } from "expo-localization";
|
||||||
|
import { PortalProvider } from "@gorhom/portal";
|
||||||
import "intl-pluralrules";
|
import "intl-pluralrules";
|
||||||
|
|
||||||
// TODO: use a backend to load jsons.
|
// TODO: use a backend to load jsons.
|
||||||
@ -72,7 +73,9 @@ export default function Root() {
|
|||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<ThemeSelector>
|
<ThemeSelector>
|
||||||
<ThemedStack />
|
<PortalProvider>
|
||||||
|
<ThemedStack />
|
||||||
|
</PortalProvider>
|
||||||
</ThemeSelector>
|
</ThemeSelector>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"web": "expo start --web"
|
"web": "expo start --web"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@gorhom/portal": "^1.0.14",
|
||||||
"@kyoo/ui": "workspace:^",
|
"@kyoo/ui": "workspace:^",
|
||||||
"@material-symbols/svg-400": "^0.4.2",
|
"@material-symbols/svg-400": "^0.4.2",
|
||||||
"@shopify/flash-list": "1.3.1",
|
"@shopify/flash-list": "1.3.1",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.9.3",
|
"@emotion/react": "^11.9.3",
|
||||||
"@emotion/styled": "^11.9.3",
|
"@emotion/styled": "^11.9.3",
|
||||||
|
"@gorhom/portal": "^1.0.14",
|
||||||
"@kyoo/models": "workspace:^",
|
"@kyoo/models": "workspace:^",
|
||||||
"@kyoo/primitives": "workspace:^",
|
"@kyoo/primitives": "workspace:^",
|
||||||
"@kyoo/ui": "workspace:^",
|
"@kyoo/ui": "workspace:^",
|
||||||
|
@ -20,10 +20,11 @@
|
|||||||
|
|
||||||
import "../polyfill";
|
import "../polyfill";
|
||||||
|
|
||||||
import { ReactNode, useState } from "react";
|
import { PortalProvider } from "@gorhom/portal";
|
||||||
import NextApp, { AppContext, type AppProps } from "next/app";
|
|
||||||
import { createTheme, ThemeProvider as MTheme } from "@mui/material";
|
import { createTheme, ThemeProvider as MTheme } from "@mui/material";
|
||||||
import { Hydrate, QueryClientProvider } from "@tanstack/react-query";
|
import { Hydrate, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { ReactNode, useState } from "react";
|
||||||
|
import NextApp, { AppContext, type AppProps } from "next/app";
|
||||||
import {
|
import {
|
||||||
HiddenIfNoJs,
|
HiddenIfNoJs,
|
||||||
SkeletonCss,
|
SkeletonCss,
|
||||||
@ -32,7 +33,6 @@ import {
|
|||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
import { createQueryClient, fetchQuery, QueryIdentifier, QueryPage } from "@kyoo/models";
|
import { createQueryClient, fetchQuery, QueryIdentifier, QueryPage } from "@kyoo/models";
|
||||||
import { useTheme, useMobileHover } from "yoshiki/web";
|
import { useTheme, useMobileHover } from "yoshiki/web";
|
||||||
import { useYoshiki } from "yoshiki/native";
|
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { withTranslations } from "../i18n";
|
import { withTranslations } from "../i18n";
|
||||||
@ -105,7 +105,9 @@ const App = ({ Component, pageProps }: AppProps) => {
|
|||||||
<Hydrate state={queryState}>
|
<Hydrate state={queryState}>
|
||||||
<ThemeSelector>
|
<ThemeSelector>
|
||||||
<GlobalCssTheme />
|
<GlobalCssTheme />
|
||||||
<Layout page={<Component {...props} />} />
|
<PortalProvider>
|
||||||
|
<Layout page={<Component {...props} />} />
|
||||||
|
</PortalProvider>
|
||||||
</ThemeSelector>
|
</ThemeSelector>
|
||||||
</Hydrate>
|
</Hydrate>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"typescript": "^4.9.3"
|
"typescript": "^4.9.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"@gorhom/portal": "*",
|
||||||
"@material-symbols/svg-400": "*",
|
"@material-symbols/svg-400": "*",
|
||||||
"expo-linear-gradient": "*",
|
"expo-linear-gradient": "*",
|
||||||
"moti": "*",
|
"moti": "*",
|
||||||
|
@ -31,6 +31,7 @@ export * from "./container";
|
|||||||
export * from "./divider";
|
export * from "./divider";
|
||||||
export * from "./progress";
|
export * from "./progress";
|
||||||
export * from "./slider";
|
export * from "./slider";
|
||||||
|
export * from "./menu";
|
||||||
|
|
||||||
export * from "./animated";
|
export * from "./animated";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
|
@ -18,18 +18,20 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentType, Fragment, ReactNode } from "react";
|
import { ComponentProps, ComponentType, Fragment, ReactNode } from "react";
|
||||||
import {
|
import {
|
||||||
Platform,
|
Platform,
|
||||||
|
Pressable,
|
||||||
TextProps,
|
TextProps,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
TouchableNativeFeedback,
|
TouchableNativeFeedback,
|
||||||
View,
|
View,
|
||||||
ViewProps,
|
ViewProps,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
PressableProps,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { LinkCore, TextLink } from "solito/link";
|
import { LinkCore, TextLink } from "solito/link";
|
||||||
import { useYoshiki, Pressable } from "yoshiki/native";
|
import { useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
export const A = ({
|
export const A = ({
|
||||||
href,
|
href,
|
||||||
@ -58,19 +60,20 @@ export const A = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Link = ({
|
export const PressableFeedback = ({
|
||||||
href,
|
|
||||||
children,
|
children,
|
||||||
|
WebComponent,
|
||||||
...props
|
...props
|
||||||
}: ViewProps & {
|
}: ViewProps & {
|
||||||
href: string;
|
onFocus?: PressableProps["onFocus"];
|
||||||
onFocus?: () => void;
|
onBlur?: PressableProps["onBlur"];
|
||||||
onBlur?: () => void;
|
onPressIn?: PressableProps["onPressIn"];
|
||||||
onPressIn?: () => void;
|
onPressOut?: PressableProps["onPressOut"];
|
||||||
onPressOut?: () => void;
|
onPress?: PressableProps["onPress"];
|
||||||
|
WebComponent?: ComponentType;
|
||||||
}) => {
|
}) => {
|
||||||
const { onBlur, onFocus, onPressIn, onPressOut, ...noFocusProps } = props;
|
const { onBlur, onFocus, onPressIn, onPressOut, onPress, ...noPressProps } = props;
|
||||||
const focusProps = { onBlur, onFocus, onPressIn, onPressOut };
|
const pressProps = { onBlur, onFocus, onPressIn, onPressOut, onPress };
|
||||||
const radiusStyle = Platform.select<ViewProps>({
|
const radiusStyle = Platform.select<ViewProps>({
|
||||||
android: {
|
android: {
|
||||||
style: { borderRadius: StyleSheet.flatten(props?.style)?.borderRadius, overflow: "hidden" },
|
style: { borderRadius: StyleSheet.flatten(props?.style)?.borderRadius, overflow: "hidden" },
|
||||||
@ -78,28 +81,43 @@ export const Link = ({
|
|||||||
default: {},
|
default: {},
|
||||||
});
|
});
|
||||||
const Wrapper = radiusStyle.style ? View : Fragment;
|
const Wrapper = radiusStyle.style ? View : Fragment;
|
||||||
|
const InnerPressable = Platform.select<ComponentType<{ children?: any }>>({
|
||||||
|
web: WebComponent ?? Pressable,
|
||||||
|
android: TouchableNativeFeedback,
|
||||||
|
ios: TouchableOpacity,
|
||||||
|
default: Pressable,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper {...radiusStyle}>
|
<Wrapper {...radiusStyle}>
|
||||||
<LinkCore
|
<InnerPressable
|
||||||
href={href}
|
{...Platform.select<object>({
|
||||||
Component={Platform.select<ComponentType>({
|
android: { useForeground: true, ...pressProps },
|
||||||
web: View,
|
|
||||||
android: TouchableNativeFeedback,
|
|
||||||
ios: TouchableOpacity,
|
|
||||||
default: Pressable,
|
|
||||||
})}
|
|
||||||
componentProps={Platform.select<object>({
|
|
||||||
android: { useForeground: true, ...focusProps },
|
|
||||||
default: props,
|
default: props,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{Platform.select<ReactNode>({
|
{Platform.select<ReactNode>({
|
||||||
android: <View {...noFocusProps}>{children}</View>,
|
android: <View {...noPressProps}>{children}</View>,
|
||||||
ios: <View {...noFocusProps}>{children}</View>,
|
ios: <View {...noPressProps}>{children}</View>,
|
||||||
default: children,
|
default: children,
|
||||||
})}
|
})}
|
||||||
</LinkCore>
|
</InnerPressable>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Link = ({
|
||||||
|
href,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: { href: string } & Omit<ComponentProps<typeof PressableFeedback>, "WebComponent">) => {
|
||||||
|
return (
|
||||||
|
<LinkCore
|
||||||
|
href={href}
|
||||||
|
Component={PressableFeedback}
|
||||||
|
componentProps={{ WebComponent: View, ...props }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</LinkCore>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
145
front/packages/primitives/src/menu.tsx
Normal file
145
front/packages/primitives/src/menu.tsx
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* 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 { Portal } from "@gorhom/portal";
|
||||||
|
import { ScrollView } from "moti";
|
||||||
|
import { ComponentType, createContext, ReactNode, useContext, useEffect, useState } from "react";
|
||||||
|
import { PressableProps, StyleSheet, Pressable } from "react-native";
|
||||||
|
import { percent, px, sm, useYoshiki, xl } from "yoshiki/native";
|
||||||
|
import Close from "@material-symbols/svg-400/rounded/close-fill.svg";
|
||||||
|
import { IconButton } from "./icons";
|
||||||
|
import { PressableFeedback } from "./links";
|
||||||
|
import { P } from "./text";
|
||||||
|
import { ContrastArea } from "./themes";
|
||||||
|
import { ts } from "./utils";
|
||||||
|
|
||||||
|
const MenuContext = createContext<((open: boolean) => void) | undefined>(undefined);
|
||||||
|
|
||||||
|
const Menu = <AsProps extends { onPress: PressableProps["onPress"] }>({
|
||||||
|
Triger,
|
||||||
|
onMenuOpen,
|
||||||
|
onMenuClose,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
Triger: ComponentType<AsProps>;
|
||||||
|
children: ReactNode | ReactNode[] | null;
|
||||||
|
onMenuOpen: () => void;
|
||||||
|
onMenuClose: () => void;
|
||||||
|
} & Omit<AsProps, "onPress">) => {
|
||||||
|
const [isOpen, setOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) onMenuOpen?.call(null);
|
||||||
|
else onMenuClose?.call(null);
|
||||||
|
}, [isOpen, onMenuClose, onMenuOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* @ts-ignore */}
|
||||||
|
<Triger onPress={() => setOpen(true)} {...props} />
|
||||||
|
{isOpen && (
|
||||||
|
<Portal>
|
||||||
|
<ContrastArea mode="user">
|
||||||
|
{({ css, theme }) => (
|
||||||
|
<MenuContext.Provider value={setOpen}>
|
||||||
|
<Pressable
|
||||||
|
onPress={() => setOpen(false)}
|
||||||
|
focusable={false}
|
||||||
|
{...css({ ...StyleSheet.absoluteFillObject, flexGrow: 1, bg: "transparent" })}
|
||||||
|
/>
|
||||||
|
<ScrollView
|
||||||
|
{...css([
|
||||||
|
{
|
||||||
|
bg: (theme) => theme.background,
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 0,
|
||||||
|
width: percent(100),
|
||||||
|
alignSelf: "center",
|
||||||
|
borderTopLeftRadius: px(26),
|
||||||
|
borderTopRightRadius: { xs: px(26), xl: 0 },
|
||||||
|
paddingTop: { xs: px(26), xl: 0 },
|
||||||
|
marginTop: { xs: px(72), xl: 0 },
|
||||||
|
},
|
||||||
|
sm({
|
||||||
|
maxWidth: px(640),
|
||||||
|
marginHorizontal: px(56),
|
||||||
|
}),
|
||||||
|
xl({
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
marginRight: 0,
|
||||||
|
borderBottomLeftRadius: px(26),
|
||||||
|
}),
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
icon={Close}
|
||||||
|
color={theme.colors.black}
|
||||||
|
onPress={() => setOpen(false)}
|
||||||
|
{...css({ alignSelf: "flex-end", display: { xs: "none", xl: "flex" } })}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</ScrollView>
|
||||||
|
</MenuContext.Provider>
|
||||||
|
)}
|
||||||
|
</ContrastArea>
|
||||||
|
</Portal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MenuItem = <AsProps extends PressableProps>({
|
||||||
|
label,
|
||||||
|
icon,
|
||||||
|
selected,
|
||||||
|
as,
|
||||||
|
onPress,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
icon?: JSX.Element;
|
||||||
|
selected?: boolean;
|
||||||
|
as?: ComponentType<AsProps>;
|
||||||
|
} & AsProps) => {
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
const setOpen = useContext(MenuContext);
|
||||||
|
|
||||||
|
const As: ComponentType<any> = as ?? PressableFeedback;
|
||||||
|
return (
|
||||||
|
<As
|
||||||
|
onPress={(e: any) => {
|
||||||
|
setOpen?.call(null, false);
|
||||||
|
onPress?.call(null, e);
|
||||||
|
}}
|
||||||
|
{...css(
|
||||||
|
{ paddingHorizontal: ts(2), width: percent(100), height: ts(5), justifyContent: "center" },
|
||||||
|
props as any,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{icon ?? null}
|
||||||
|
<P>{label}</P>
|
||||||
|
</As>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Menu.Item = MenuItem;
|
||||||
|
|
||||||
|
export { Menu };
|
@ -135,7 +135,7 @@ export const ContrastArea = ({
|
|||||||
contrastText,
|
contrastText,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode | YoshikiFunc<ReactNode>;
|
children: ReactNode | YoshikiFunc<ReactNode>;
|
||||||
mode?: "light" | "dark";
|
mode?: "light" | "dark" | "user";
|
||||||
contrastText?: boolean;
|
contrastText?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const oldTheme = useTheme();
|
const oldTheme = useTheme();
|
||||||
|
@ -25,10 +25,5 @@
|
|||||||
"react-native-reanimated": "*",
|
"react-native-reanimated": "*",
|
||||||
"react-native-svg": "*",
|
"react-native-svg": "*",
|
||||||
"yoshiki": "*"
|
"yoshiki": "*"
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"react-native": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,64 +19,75 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Font, Track } from "@kyoo/models";
|
import { Font, Track } from "@kyoo/models";
|
||||||
import { IconButton, tooltip } from "@kyoo/primitives";
|
import { IconButton, tooltip, Menu, ts, A } from "@kyoo/primitives";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useRouter } from "solito/router";
|
import { useRouter } from "solito/router";
|
||||||
import { useState } from "react";
|
|
||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import ClosedCaption from "@material-symbols/svg-400/rounded/closed_caption-fill.svg";
|
||||||
import Fullscreen from "@material-symbols/svg-400/rounded/fullscreen-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 FullscreenExit from "@material-symbols/svg-400/rounded/fullscreen_exit-fill.svg";
|
||||||
|
import { Stylable, useYoshiki } from "yoshiki/native";
|
||||||
|
import { createParam } from "solito";
|
||||||
import { fullscreenAtom, subtitleAtom } from "../state";
|
import { fullscreenAtom, subtitleAtom } from "../state";
|
||||||
|
|
||||||
|
const { useParam } = createParam<{ subtitle?: string }>();
|
||||||
|
|
||||||
export const RightButtons = ({
|
export const RightButtons = ({
|
||||||
subtitles,
|
subtitles,
|
||||||
fonts,
|
fonts,
|
||||||
onMenuOpen,
|
onMenuOpen,
|
||||||
onMenuClose,
|
onMenuClose,
|
||||||
|
...props
|
||||||
}: {
|
}: {
|
||||||
subtitles?: Track[];
|
subtitles?: Track[];
|
||||||
fonts?: Font[];
|
fonts?: Font[];
|
||||||
onMenuOpen: () => void;
|
onMenuOpen: () => void;
|
||||||
onMenuClose: () => void;
|
onMenuClose: () => void;
|
||||||
}) => {
|
} & Stylable) => {
|
||||||
|
const { css } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [subtitleAnchor, setSubtitleAnchor] = useState<HTMLButtonElement | null>(null);
|
|
||||||
const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom);
|
const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom);
|
||||||
|
const [selectedSubtitle, setSubtitle] = useParam("subtitle");
|
||||||
|
|
||||||
|
const spacing = css({ marginHorizontal: ts(1) });
|
||||||
|
|
||||||
|
subtitles = [{ id: 1, slug: "oto", displayName: "toto" }];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View {...css({ flexDirection: "row" }, props)}>
|
||||||
// sx={{
|
{subtitles && (
|
||||||
// display: "flex",
|
<Menu
|
||||||
// "> *": {
|
Triger={IconButton}
|
||||||
// m: { xs: "4px !important", sm: "8px !important" },
|
icon={ClosedCaption}
|
||||||
// p: { xs: "4px !important", sm: "8px !important" },
|
{...tooltip(t("player.subtitles"), true)}
|
||||||
// },
|
{...spacing}
|
||||||
// }}
|
// id="sortby"
|
||||||
>
|
// aria-controls={subtitleAnchor ? "subtitle-menu" : undefined}
|
||||||
{/* {subtitles && ( */}
|
// aria-haspopup="true"
|
||||||
{/* <Tooltip title={t("subtitles")}> */}
|
// aria-expanded={subtitleAnchor ? "true" : undefined}
|
||||||
{/* <IconButton */}
|
>
|
||||||
{/* id="sortby" */}
|
<Menu.Item
|
||||||
{/* aria-label={t("subtitles")} */}
|
label={t("player.subtitle-none")}
|
||||||
{/* aria-controls={subtitleAnchor ? "subtitle-menu" : undefined} */}
|
selected={!selectedSubtitle}
|
||||||
{/* aria-haspopup="true" */}
|
onPress={() => setSubtitle(undefined)}
|
||||||
{/* aria-expanded={subtitleAnchor ? "true" : undefined} */}
|
/>
|
||||||
{/* onClick={(event) => { */}
|
{subtitles.map((x) => (
|
||||||
{/* setSubtitleAnchor(event.currentTarget); */}
|
<Menu.Item
|
||||||
{/* onMenuOpen(); */}
|
key={x.id}
|
||||||
{/* }} */}
|
label={x.displayName}
|
||||||
{/* sx={{ color: "white" }} */}
|
selected={selectedSubtitle === x.slug}
|
||||||
{/* > */}
|
onPress={() => setSubtitle(x.slug)}
|
||||||
{/* <ClosedCaption /> */}
|
/>
|
||||||
{/* </IconButton> */}
|
))}
|
||||||
{/* </Tooltip> */}
|
</Menu>
|
||||||
{/* )} */}
|
)}
|
||||||
{Platform.OS === "web" && (
|
{Platform.OS === "web" && (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={isFullscreen ? FullscreenExit : Fullscreen}
|
icon={isFullscreen ? FullscreenExit : Fullscreen}
|
||||||
onPress={() => setFullscreen(!isFullscreen)}
|
onPress={() => setFullscreen(!isFullscreen)}
|
||||||
{...tooltip(t("player.fullscreen"), true)}
|
{...tooltip(t("player.fullscreen"), true)}
|
||||||
|
{...spacing}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* {subtitleAnchor && ( */}
|
{/* {subtitleAnchor && ( */}
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
subtitleAtom,
|
subtitleAtom,
|
||||||
volumeAtom,
|
volumeAtom,
|
||||||
} from "./state";
|
} from "./state";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
| { type: "play" }
|
| { type: "play" }
|
||||||
@ -93,6 +94,7 @@ export const useVideoKeyboard = (
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (Platform.OS !== "web") return;
|
||||||
const handler = (event: KeyboardEvent) => {
|
const handler = (event: KeyboardEvent) => {
|
||||||
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import { ResizeMode, Video as NativeVideo, VideoProps } from "expo-av";
|
|||||||
import SubtitleOctopus from "libass-wasm";
|
import SubtitleOctopus from "libass-wasm";
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
import { bakedAtom } from "../jotai-utils";
|
import { bakedAtom } from "../jotai-utils";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
enum PlayMode {
|
enum PlayMode {
|
||||||
Direct,
|
Direct,
|
||||||
@ -100,6 +101,7 @@ export const Video = ({
|
|||||||
|
|
||||||
const setFullscreen = useSetAtom(privateFullscreen);
|
const setFullscreen = useSetAtom(privateFullscreen);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (Platform.OS !== "web") return;
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
setFullscreen(document.fullscreenElement != null);
|
setFullscreen(document.fullscreenElement != null);
|
||||||
};
|
};
|
||||||
|
@ -2142,6 +2142,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@gorhom/portal@npm:^1.0.14":
|
||||||
|
version: 1.0.14
|
||||||
|
resolution: "@gorhom/portal@npm:1.0.14"
|
||||||
|
dependencies:
|
||||||
|
nanoid: ^3.3.1
|
||||||
|
peerDependencies:
|
||||||
|
react: "*"
|
||||||
|
react-native: "*"
|
||||||
|
checksum: 227bb96a2db854ab29bb9da8d4f3823c7f7448358de459709dd1b78522110da564c9a8734c6bc7d7153ed7c99320e0fb5d60b420c2ebb75ecaf2f0d757f410f9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@graphql-typed-document-node/core@npm:^3.1.0":
|
"@graphql-typed-document-node/core@npm:^3.1.0":
|
||||||
version: 3.1.1
|
version: 3.1.1
|
||||||
resolution: "@graphql-typed-document-node/core@npm:3.1.1"
|
resolution: "@graphql-typed-document-node/core@npm:3.1.1"
|
||||||
@ -2339,6 +2351,7 @@ __metadata:
|
|||||||
solito: ^2.0.5
|
solito: ^2.0.5
|
||||||
typescript: ^4.9.3
|
typescript: ^4.9.3
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
"@gorhom/portal": "*"
|
||||||
"@material-symbols/svg-400": "*"
|
"@material-symbols/svg-400": "*"
|
||||||
expo-linear-gradient: "*"
|
expo-linear-gradient: "*"
|
||||||
moti: "*"
|
moti: "*"
|
||||||
@ -2375,9 +2388,6 @@ __metadata:
|
|||||||
react-native-reanimated: "*"
|
react-native-reanimated: "*"
|
||||||
react-native-svg: "*"
|
react-native-svg: "*"
|
||||||
yoshiki: "*"
|
yoshiki: "*"
|
||||||
peerDependenciesMeta:
|
|
||||||
react-native:
|
|
||||||
optional: true
|
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
@ -6545,19 +6555,19 @@ __metadata:
|
|||||||
|
|
||||||
"expo-av@file:///home/anonymus-raccoon/projects/expo/packages/expo-av/::locator=mobile%40workspace%3Aapps%2Fmobile":
|
"expo-av@file:///home/anonymus-raccoon/projects/expo/packages/expo-av/::locator=mobile%40workspace%3Aapps%2Fmobile":
|
||||||
version: 13.0.1
|
version: 13.0.1
|
||||||
resolution: "expo-av@file:///home/anonymus-raccoon/projects/expo/packages/expo-av/#///home/anonymus-raccoon/projects/expo/packages/expo-av/::hash=5981bc&locator=mobile%40workspace%3Aapps%2Fmobile"
|
resolution: "expo-av@file:///home/anonymus-raccoon/projects/expo/packages/expo-av/#///home/anonymus-raccoon/projects/expo/packages/expo-av/::hash=6acf81&locator=mobile%40workspace%3Aapps%2Fmobile"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
expo: "*"
|
expo: "*"
|
||||||
checksum: 3313e891f708f423f29c417372701fa6ddd5bd30028a1ebcd9634d61e5ed0475dbbcc51384fc88e82b4c0dd482dc6af38f9b1d4d103fb1a81ea05d2a4c967586
|
checksum: 89c23346fcf8cc3ed50b523969dad76c14e9bb3f526c7cc0032783fca9aeacc1c14a1aca86b84cb5ebd51d4ee2142f7fb507ed47d7430e12b71733858a70acc9
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"expo-av@file:///home/anonymus-raccoon/projects/expo/packages/expo-av/::locator=web%40workspace%3Aapps%2Fweb":
|
"expo-av@file:///home/anonymus-raccoon/projects/expo/packages/expo-av/::locator=web%40workspace%3Aapps%2Fweb":
|
||||||
version: 13.0.1
|
version: 13.0.1
|
||||||
resolution: "expo-av@file:///home/anonymus-raccoon/projects/expo/packages/expo-av/#///home/anonymus-raccoon/projects/expo/packages/expo-av/::hash=5981bc&locator=web%40workspace%3Aapps%2Fweb"
|
resolution: "expo-av@file:///home/anonymus-raccoon/projects/expo/packages/expo-av/#///home/anonymus-raccoon/projects/expo/packages/expo-av/::hash=6acf81&locator=web%40workspace%3Aapps%2Fweb"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
expo: "*"
|
expo: "*"
|
||||||
checksum: 3313e891f708f423f29c417372701fa6ddd5bd30028a1ebcd9634d61e5ed0475dbbcc51384fc88e82b4c0dd482dc6af38f9b1d4d103fb1a81ea05d2a4c967586
|
checksum: 89c23346fcf8cc3ed50b523969dad76c14e9bb3f526c7cc0032783fca9aeacc1c14a1aca86b84cb5ebd51d4ee2142f7fb507ed47d7430e12b71733858a70acc9
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -9962,6 +9972,7 @@ __metadata:
|
|||||||
resolution: "mobile@workspace:apps/mobile"
|
resolution: "mobile@workspace:apps/mobile"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core": ^7.19.3
|
"@babel/core": ^7.19.3
|
||||||
|
"@gorhom/portal": ^1.0.14
|
||||||
"@kyoo/ui": "workspace:^"
|
"@kyoo/ui": "workspace:^"
|
||||||
"@material-symbols/svg-400": ^0.4.2
|
"@material-symbols/svg-400": ^0.4.2
|
||||||
"@shopify/flash-list": 1.3.1
|
"@shopify/flash-list": 1.3.1
|
||||||
@ -10057,7 +10068,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"nanoid@npm:^3.1.23, nanoid@npm:^3.3.4":
|
"nanoid@npm:^3.1.23, nanoid@npm:^3.3.1, nanoid@npm:^3.3.4":
|
||||||
version: 3.3.4
|
version: 3.3.4
|
||||||
resolution: "nanoid@npm:3.3.4"
|
resolution: "nanoid@npm:3.3.4"
|
||||||
bin:
|
bin:
|
||||||
@ -13639,6 +13650,7 @@ __metadata:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@emotion/react": ^11.9.3
|
"@emotion/react": ^11.9.3
|
||||||
"@emotion/styled": ^11.9.3
|
"@emotion/styled": ^11.9.3
|
||||||
|
"@gorhom/portal": ^1.0.14
|
||||||
"@kyoo/models": "workspace:^"
|
"@kyoo/models": "workspace:^"
|
||||||
"@kyoo/primitives": "workspace:^"
|
"@kyoo/primitives": "workspace:^"
|
||||||
"@kyoo/ui": "workspace:^"
|
"@kyoo/ui": "workspace:^"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user