mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Adapt staff list to react native (first pass)
This commit is contained in:
parent
8c28df9517
commit
26f9cf646b
@ -57,7 +57,7 @@ const ThemedStack = () => {
|
||||
headerStyle: {
|
||||
backgroundColor: theme.appbar,
|
||||
},
|
||||
headerTintColor: "#fff",
|
||||
headerTintColor: theme.colors.white,
|
||||
headerTitleStyle: {
|
||||
fontWeight: "bold",
|
||||
},
|
||||
|
@ -1,23 +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 { MovieDetails } from "@kyoo/ui";
|
||||
|
||||
export default MovieDetails;
|
@ -1,59 +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 { Avatar, Box, Skeleton, SxProps, Typography } from "@mui/material";
|
||||
import { Person } from "~/models/resources/person";
|
||||
import { Link } from "~/utils/link";
|
||||
|
||||
export const PersonAvatar = ({ person, sx }: { person?: Person; sx?: SxProps }) => {
|
||||
if (!person) {
|
||||
return (
|
||||
<Box sx={sx}>
|
||||
<Skeleton variant="circular" sx={{ width: "100%", aspectRatio: "1/1", height: "unset" }} />
|
||||
<Typography align="center">
|
||||
<Skeleton />
|
||||
</Typography>
|
||||
<Typography variant="body2" align="center">
|
||||
<Skeleton />
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link href={`/person/${person.slug}`} color="inherit" sx={sx}>
|
||||
<Avatar
|
||||
src={person.poster!}
|
||||
alt={person.name}
|
||||
sx={{ width: "100%", height: "unset", aspectRatio: "1/1" }}
|
||||
/>
|
||||
<Typography align="center">{person.name}</Typography>
|
||||
{person.role && person.type && (
|
||||
<Typography variant="body2" align="center">
|
||||
{person.type} ({person.role})
|
||||
</Typography>
|
||||
)}
|
||||
{person.role && !person.type && (
|
||||
<Typography variant="body2" align="center">
|
||||
{person.role}
|
||||
</Typography>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
};
|
@ -18,26 +18,31 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { View, ViewStyle } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { Image } from "./image";
|
||||
import { useYoshiki, px } from "yoshiki/native";
|
||||
import { useYoshiki, px, Stylable } from "yoshiki/native";
|
||||
import { Icon } from "./icons";
|
||||
import { Skeleton } from "./skeleton";
|
||||
|
||||
export const Avatar = ({
|
||||
src,
|
||||
alt,
|
||||
size = px(24),
|
||||
isLoading = false,
|
||||
...props
|
||||
}: {
|
||||
src?: string;
|
||||
alt: string;
|
||||
src?: string | null;
|
||||
alt?: string;
|
||||
size?: number;
|
||||
}) => {
|
||||
isLoading?: boolean;
|
||||
} & Stylable) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
if (isLoading) return <Skeleton variant="round" {...css({ width: size, height: size })} />;
|
||||
return (
|
||||
<View {...css({ borderRadius: size / 2, width: size, height: size })}>
|
||||
<View {...css({ borderRadius: size / 2, width: size, height: size }, props)}>
|
||||
{src ? (
|
||||
<Image src={src} alt={alt} width={size} height={size} />
|
||||
<Image src={src} alt={alt} layout={{ width: size, height: size }} />
|
||||
) : (
|
||||
<Icon icon="account-circle" size={size} />
|
||||
)}
|
||||
|
@ -18,15 +18,20 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentType } from "react";
|
||||
import { View, ViewProps } from "react-native";
|
||||
import { percent, px, useYoshiki } from "yoshiki/native";
|
||||
|
||||
export const Container = (props: ViewProps) => {
|
||||
export const Container = <AsProps = ViewProps,>({
|
||||
as,
|
||||
...props
|
||||
}: { as?: ComponentType<AsProps> } & AsProps) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
const As = as ?? View;
|
||||
return (
|
||||
<View
|
||||
{...css(
|
||||
<As
|
||||
{...(css(
|
||||
{
|
||||
display: "flex",
|
||||
paddingHorizontal: px(15),
|
||||
@ -39,7 +44,7 @@ export const Container = (props: ViewProps) => {
|
||||
},
|
||||
},
|
||||
props,
|
||||
)}
|
||||
) as any)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -19,8 +19,9 @@
|
||||
*/
|
||||
|
||||
import { HR as EHR } from "@expo/html-elements";
|
||||
import { percent, px, Stylable, useYoshiki } from "yoshiki/native";
|
||||
import { alpha, ts } from ".";
|
||||
import { px, Stylable, useYoshiki } from "yoshiki/native";
|
||||
import { alpha } from "./themes";
|
||||
import { ts } from "./utils";
|
||||
|
||||
export const HR = ({
|
||||
orientation,
|
||||
|
@ -22,7 +22,7 @@ import MIcon from "@expo/vector-icons/MaterialIcons";
|
||||
import { ComponentProps, ComponentType } from "react";
|
||||
import { PressableProps } from "react-native";
|
||||
import { Pressable, px, useYoshiki } from "yoshiki/native";
|
||||
import { Breakpoint, ts } from ".";
|
||||
import { Breakpoint, ts } from "./utils";
|
||||
|
||||
export type IconProps = {
|
||||
icon: ComponentProps<typeof MIcon>["name"];
|
||||
|
@ -31,13 +31,4 @@ export * from "./container";
|
||||
export * from "./divider";
|
||||
|
||||
export * from "./animated";
|
||||
|
||||
export * from "./utils/breakpoints";
|
||||
export * from "./utils/nojs";
|
||||
export * from "./utils/head";
|
||||
|
||||
import { px } from "yoshiki/native";
|
||||
|
||||
export const ts = (spacing: number) => {
|
||||
return px(spacing * 8);
|
||||
};
|
||||
export * from "./utils";
|
||||
|
@ -18,7 +18,8 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ComponentType, ReactNode } from "react";
|
||||
import { ViewStyle } from "@expo/html-elements/build/primitives/View";
|
||||
import { ComponentType, Fragment, ReactNode } from "react";
|
||||
import {
|
||||
Platform,
|
||||
TextProps,
|
||||
@ -26,6 +27,7 @@ import {
|
||||
TouchableNativeFeedback,
|
||||
View,
|
||||
ViewProps,
|
||||
StyleSheet,
|
||||
} from "react-native";
|
||||
import { LinkCore, TextLink } from "solito/link";
|
||||
import { useYoshiki, Pressable } from "yoshiki/native";
|
||||
@ -70,26 +72,35 @@ export const Link = ({
|
||||
}) => {
|
||||
const { onBlur, onFocus, onPressIn, onPressOut, ...noFocusProps } = props;
|
||||
const focusProps = { onBlur, onFocus, onPressIn, onPressOut };
|
||||
const radiusStyle = Platform.select<ViewProps>({
|
||||
android: {
|
||||
style: { borderRadius: StyleSheet.flatten(props?.style).borderRadius, overflow: "hidden" },
|
||||
},
|
||||
default: {},
|
||||
});
|
||||
const Wrapper = radiusStyle ? View : Fragment;
|
||||
|
||||
return (
|
||||
<LinkCore
|
||||
href={href}
|
||||
Component={Platform.select<ComponentType>({
|
||||
web: View,
|
||||
android: TouchableNativeFeedback,
|
||||
ios: TouchableOpacity,
|
||||
default: Pressable,
|
||||
})}
|
||||
componentProps={Platform.select<object>({
|
||||
android: { useForeground: true, ...focusProps },
|
||||
default: props,
|
||||
})}
|
||||
>
|
||||
{Platform.select<ReactNode>({
|
||||
android: <View {...noFocusProps}>{children}</View>,
|
||||
ios: <View {...noFocusProps}>{children}</View>,
|
||||
default: children,
|
||||
})}
|
||||
</LinkCore>
|
||||
<Wrapper {...radiusStyle}>
|
||||
<LinkCore
|
||||
href={href}
|
||||
Component={Platform.select<ComponentType>({
|
||||
web: View,
|
||||
android: TouchableNativeFeedback,
|
||||
ios: TouchableOpacity,
|
||||
default: Pressable,
|
||||
})}
|
||||
componentProps={Platform.select<object>({
|
||||
android: { useForeground: true, ...focusProps },
|
||||
default: props,
|
||||
})}
|
||||
>
|
||||
{Platform.select<ReactNode>({
|
||||
android: <View {...noFocusProps}>{children}</View>,
|
||||
ios: <View {...noFocusProps}>{children}</View>,
|
||||
default: children,
|
||||
})}
|
||||
</LinkCore>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
@ -31,7 +31,7 @@ import {
|
||||
P as EP,
|
||||
LI as ELI,
|
||||
} from "@expo/html-elements";
|
||||
import { ts } from ".";
|
||||
import { ts } from "./utils/spacing";
|
||||
|
||||
const styleText = (
|
||||
Component: ComponentType<ComponentProps<typeof EP>>,
|
||||
@ -76,7 +76,7 @@ export const LI = ({ children, ...props }: TextProps) => {
|
||||
const { css } = useYoshiki();
|
||||
|
||||
return (
|
||||
<P accessibilityRole="listitem" {...props}>
|
||||
<P accessibilityRole={Platform.OS === "web" ? "listitem" : props.accessibilityRole} {...props}>
|
||||
<Text
|
||||
{...css({
|
||||
height: percent(100),
|
||||
|
@ -39,11 +39,12 @@ import {
|
||||
A,
|
||||
ts,
|
||||
} from "@kyoo/primitives";
|
||||
import { ScrollView } from "moti";
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import {
|
||||
Theme,
|
||||
sm,
|
||||
md,
|
||||
px,
|
||||
min,
|
||||
@ -229,16 +230,14 @@ const Description = ({
|
||||
>
|
||||
{t("show.genre")}:{" "}
|
||||
{(isLoading ? [...Array(3)] : genres!).map((genre, i) => (
|
||||
<>
|
||||
{i !== 0 && ", "}
|
||||
<Fragment key={genre?.slug ?? i.toString()}>
|
||||
<P>{i !== 0 && ", "}</P>
|
||||
{isLoading ? (
|
||||
<Skeleton key={i} {...css({ width: rem(5) })} />
|
||||
<Skeleton {...css({ width: rem(5) })} />
|
||||
) : (
|
||||
<A key={genre.slug} href={`/genres/${genre.slug}`}>
|
||||
{genre.name}
|
||||
</A>
|
||||
<A href={`/genres/${genre.slug}`}>{genre.name}</A>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
))}
|
||||
</P>
|
||||
|
||||
@ -278,65 +277,52 @@ const Description = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const ShowHeader = ({
|
||||
query,
|
||||
slug,
|
||||
}: {
|
||||
query: QueryIdentifier<Show | Movie>;
|
||||
slug: string;
|
||||
}) => {
|
||||
/* const scroll = useScroll(); */
|
||||
export const Header = ({ query, slug }: { query: QueryIdentifier<Show | Movie>; slug: string }) => {
|
||||
const { css } = useYoshiki();
|
||||
// TODO: tweek the navbar color with the theme.
|
||||
|
||||
return (
|
||||
<>
|
||||
<Navbar {...css({ bg: "transparent" })} />
|
||||
<Fetch query={query}>
|
||||
{({ isLoading, ...data }) => (
|
||||
<Main {...css(StyleSheet.absoluteFillObject)}>
|
||||
<Head title={data?.name} description={data?.overview} />
|
||||
{/* TODO: Add a shadow on navbar items */}
|
||||
{/* TODO: Put the navbar outside of the scrollbox */}
|
||||
<ImageBackground
|
||||
src={data?.thumbnail}
|
||||
alt=""
|
||||
containerStyle={{
|
||||
height: {
|
||||
xs: vh(40),
|
||||
sm: min(vh(60), px(750)),
|
||||
md: min(vh(60), px(680)),
|
||||
lg: vh(70),
|
||||
},
|
||||
minHeight: { xs: px(350), sm: px(300), md: px(400), lg: px(600) },
|
||||
}}
|
||||
>
|
||||
<TitleLine
|
||||
isLoading={isLoading}
|
||||
slug={slug}
|
||||
name={data?.name}
|
||||
date={data ? getDisplayDate(data as any) : undefined}
|
||||
poster={data?.poster}
|
||||
studio={data?.studio}
|
||||
{...css({
|
||||
marginTop: {
|
||||
xs: max(vh(20), px(200)),
|
||||
sm: vh(45),
|
||||
md: max(vh(30), px(150)),
|
||||
lg: max(vh(35), px(200)),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</ImageBackground>
|
||||
<Description
|
||||
<Fetch query={query}>
|
||||
{({ isLoading, ...data }) => (
|
||||
<ScrollView>
|
||||
<Head title={data?.name} description={data?.overview} />
|
||||
<ImageBackground
|
||||
src={data?.thumbnail}
|
||||
alt=""
|
||||
containerStyle={{
|
||||
height: {
|
||||
xs: vh(40),
|
||||
sm: min(vh(60), px(750)),
|
||||
md: min(vh(60), px(680)),
|
||||
lg: vh(70),
|
||||
},
|
||||
minHeight: { xs: px(350), sm: px(300), md: px(400), lg: px(600) },
|
||||
}}
|
||||
>
|
||||
<TitleLine
|
||||
isLoading={isLoading}
|
||||
overview={data?.overview}
|
||||
genres={data?.genres}
|
||||
{...css({ paddingTop: { xs: 0, md: ts(2) } })}
|
||||
slug={slug}
|
||||
name={data?.name}
|
||||
date={data ? getDisplayDate(data as any) : undefined}
|
||||
poster={data?.poster}
|
||||
studio={data?.studio}
|
||||
{...css({
|
||||
marginTop: {
|
||||
xs: max(vh(20), px(200)),
|
||||
sm: vh(45),
|
||||
md: max(vh(30), px(150)),
|
||||
lg: max(vh(35), px(200)),
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Main>
|
||||
)}
|
||||
</Fetch>
|
||||
</>
|
||||
</ImageBackground>
|
||||
<Description
|
||||
isLoading={isLoading}
|
||||
overview={data?.overview}
|
||||
genres={data?.genres}
|
||||
{...css({ paddingTop: { xs: 0, md: ts(2) } })}
|
||||
/>
|
||||
</ScrollView>
|
||||
)}
|
||||
</Fetch>
|
||||
);
|
||||
};
|
||||
|
@ -19,7 +19,10 @@
|
||||
*/
|
||||
|
||||
import { Movie, MovieP, QueryIdentifier, QueryPage } from "@kyoo/models";
|
||||
import { ShowHeader } from "./header";
|
||||
import { Navbar } from "../navbar";
|
||||
import { DefaultLayout } from "../layout";
|
||||
import { Header } from "./header";
|
||||
import { Staff } from "./staff";
|
||||
|
||||
const query = (slug: string): QueryIdentifier<Movie> => ({
|
||||
parser: MovieP,
|
||||
@ -32,16 +35,16 @@ const query = (slug: string): QueryIdentifier<Movie> => ({
|
||||
export const MovieDetails: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||
return (
|
||||
<>
|
||||
{/* <Head> */}
|
||||
{/* <title>{makeTitle(data?.name)}</title> */}
|
||||
{/* <meta name="description" content={data?.overview!} /> */}
|
||||
{/* </Head> */}
|
||||
<ShowHeader slug={slug} query={query(slug)} />
|
||||
{/* <ShowStaff slug={slug} /> */}
|
||||
<Header slug={slug} query={query(slug)} />
|
||||
{/* <Staff slug={slug} /> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
MovieDetails.getFetchUrls = ({ slug }) => [//query(slug),
|
||||
// ShowStaff.query(slug), Navbar.query()
|
||||
MovieDetails.getFetchUrls = ({ slug }) => [
|
||||
query(slug),
|
||||
// ShowStaff.query(slug),
|
||||
Navbar.query(),
|
||||
];
|
||||
|
||||
MovieDetails.getLayout = DefaultLayout;
|
||||
|
@ -18,30 +18,40 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export {}
|
||||
// export const ShowStaff = ({ slug }: { slug: string }) => {
|
||||
// const { items, isError, error } = useInfiniteFetch(ShowStaff.query(slug));
|
||||
// const { t } = useTranslation("browse");
|
||||
import { Person, PersonP, QueryIdentifier } from "@kyoo/models";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { InfiniteFetch } from "../fetch-infinite";
|
||||
import { PersonAvatar } from "./person";
|
||||
|
||||
// // TODO: handle infinite scroll
|
||||
export const Staff = ({ slug }: { slug: string }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// if (isError) return <ErrorComponent {...error} />;
|
||||
// TODO: handle infinite scroll
|
||||
|
||||
// return (
|
||||
// <HorizontalList title={t("show.staff")} noContent={t("show.staff-none")}>
|
||||
// {(items ?? [...Array(20)]).map((x, i) => (
|
||||
// <PersonAvatar
|
||||
// key={x ? x.id : i}
|
||||
// person={x}
|
||||
// sx={{ width: { xs: "7rem", lg: "10rem" }, flexShrink: 0, px: 2 }}
|
||||
// />
|
||||
// ))}
|
||||
// </HorizontalList>
|
||||
// );
|
||||
// };
|
||||
return (
|
||||
<InfiniteFetch
|
||||
query={Staff.query(slug)}
|
||||
layout={{ numColumns: 0, size: PersonAvatar.width }}
|
||||
placeholderCount={20}
|
||||
>
|
||||
{/* <HorizontalList title={t("show.staff")} noContent={t("show.staff-none")}> */}
|
||||
{(item, key) => (
|
||||
<PersonAvatar
|
||||
key={key}
|
||||
isLoading={item.isLoading}
|
||||
slug={item?.slug}
|
||||
name={item?.name}
|
||||
role={item?.type ? `${item?.type} (${item?.role})` : item?.role}
|
||||
poster={item?.poster}
|
||||
// sx={{ width: { xs: "7rem", lg: "10rem" }, flexShrink: 0, px: 2 }}
|
||||
/>
|
||||
)}
|
||||
</InfiniteFetch>
|
||||
);
|
||||
};
|
||||
|
||||
// ShowStaff.query = (slug: string): QueryIdentifier<Person> => ({
|
||||
// parser: PersonP,
|
||||
// path: ["shows", slug, "people"],
|
||||
// infinite: true,
|
||||
// });
|
||||
Staff.query = (slug: string): QueryIdentifier<Person> => ({
|
||||
parser: PersonP,
|
||||
path: ["shows", slug, "people"],
|
||||
infinite: true,
|
||||
});
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
import { Page, QueryIdentifier, useFetch, KyooErrors } from "@kyoo/models";
|
||||
import { Breakpoint, P } from "@kyoo/primitives";
|
||||
import { Fragment } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useYoshiki } from "yoshiki/native";
|
||||
|
||||
@ -47,14 +48,12 @@ export const Fetch = <Data,>({
|
||||
const { data, error } = useFetch(query);
|
||||
|
||||
if (error) return <ErrorView error={error} />;
|
||||
if (placeholderCount === 1 || !isPage<object>(data))
|
||||
return children(data ? { ...data, isLoading: false } : ({ isLoading: true } as any), 0);
|
||||
if (!data)
|
||||
return (
|
||||
<>{[...Array(placeholderCount)].map((_, i) => children({ isLoading: true } as any, i))}</>
|
||||
);
|
||||
if (!isPage<object>(data))
|
||||
return (
|
||||
<> {children(data ? { ...data, isLoading: false } : ({ isLoading: true } as any), 0)} </>
|
||||
);
|
||||
return <>{data.items.map((item, i) => children({ ...item, isLoading: false } as any, i))}</>;
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user