mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-31 14:33:50 -04:00
Create a usePopup to simplify popup creation
This commit is contained in:
parent
d9f4a6ff8d
commit
53ac4a2050
@ -18,10 +18,10 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactElement, ReactNode, useCallback, useEffect, useState } from "react";
|
||||||
import { Container } from "./container";
|
import { Container } from "./container";
|
||||||
import { Portal } from "@gorhom/portal";
|
import { Portal, usePortal } from "@gorhom/portal";
|
||||||
import { SwitchVariant, YoshikiFunc } from "./themes";
|
import { ContrastArea, SwitchVariant, YoshikiFunc } from "./themes";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { imageBorderRadius } from "./constants";
|
import { imageBorderRadius } from "./constants";
|
||||||
import { px } from "yoshiki/native";
|
import { px } from "yoshiki/native";
|
||||||
@ -29,7 +29,7 @@ import { ts } from "./utils";
|
|||||||
|
|
||||||
export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFunc<ReactNode> }) => {
|
export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFunc<ReactNode> }) => {
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<ContrastArea mode="user">
|
||||||
<SwitchVariant>
|
<SwitchVariant>
|
||||||
{({ css, theme }) => (
|
{({ css, theme }) => (
|
||||||
<View
|
<View
|
||||||
@ -60,6 +60,19 @@ export const Popup = ({ children, ...props }: { children: ReactNode | YoshikiFun
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</SwitchVariant>
|
</SwitchVariant>
|
||||||
</Portal>
|
</ContrastArea>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const usePopup = () => {
|
||||||
|
const { addPortal, removePortal } = usePortal();
|
||||||
|
const [current, setPopup] = useState<ReactNode>();
|
||||||
|
const close = useCallback(() => setPopup(undefined), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addPortal("popup", current);
|
||||||
|
return () => removePortal("popup");
|
||||||
|
}, [current, addPortal, removePortal]);
|
||||||
|
|
||||||
|
return [setPopup, close];
|
||||||
|
};
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IconButton, Menu, tooltip } from "@kyoo/primitives";
|
import { IconButton, Menu, tooltip, usePopup } from "@kyoo/primitives";
|
||||||
import { ComponentProps, ReactElement, useEffect, useState } from "react";
|
import { ComponentProps, ReactElement, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
|
import MoreVert from "@material-symbols/svg-400/rounded/more_vert.svg";
|
||||||
@ -51,7 +51,7 @@ export const EpisodesContext = ({
|
|||||||
const downloader = useDownloader();
|
const downloader = useDownloader();
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [popup, setPopup] = useState<ReactElement | undefined>(undefined);
|
const [setPopup, close] = usePopup();
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
@ -109,19 +109,12 @@ export const EpisodesContext = ({
|
|||||||
label={t("home.episodeMore.mediainfo")}
|
label={t("home.episodeMore.mediainfo")}
|
||||||
icon={MovieInfo}
|
icon={MovieInfo}
|
||||||
onSelect={() =>
|
onSelect={() =>
|
||||||
setPopup(
|
setPopup(<MediaInfoPopup mediaType={type} mediaSlug={slug} close={close} />)
|
||||||
<MediaInfoPopup
|
|
||||||
mediaType={type}
|
|
||||||
mediaSlug={slug}
|
|
||||||
close={() => setPopup(undefined)}
|
|
||||||
/>,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
{popup}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -47,9 +47,9 @@ import {
|
|||||||
ts,
|
ts,
|
||||||
Chip,
|
Chip,
|
||||||
DottedSeparator,
|
DottedSeparator,
|
||||||
focusReset,
|
usePopup,
|
||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
import { Fragment, ReactElement, useState } from "react";
|
import { Fragment } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
|
import MovieInfo from "@material-symbols/svg-400/rounded/movie_info.svg";
|
||||||
import { ImageStyle, Platform, View } from "react-native";
|
import { ImageStyle, Platform, View } from "react-native";
|
||||||
@ -112,7 +112,7 @@ export const TitleLine = ({
|
|||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const downloader = useDownloader();
|
const downloader = useDownloader();
|
||||||
const [popup, setPopup] = useState<ReactElement | undefined>(undefined);
|
const [setPopup, close] = usePopup();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
@ -246,30 +246,22 @@ export const TitleLine = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{type === "movie" && slug && (
|
{type === "movie" && slug && (
|
||||||
<IconButton
|
<>
|
||||||
icon={Download}
|
<IconButton
|
||||||
onPress={() => downloader(type, slug)}
|
icon={Download}
|
||||||
color={{ xs: theme.user.contrast, md: theme.colors.white }}
|
onPress={() => downloader(type, slug)}
|
||||||
{...tooltip(t("home.episodeMore.download"))}
|
color={{ xs: theme.user.contrast, md: theme.colors.white }}
|
||||||
/>
|
{...tooltip(t("home.episodeMore.download"))}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon={MovieInfo}
|
||||||
|
color={{ xs: theme.user.contrast, md: theme.colors.white }}
|
||||||
|
onPress={() =>
|
||||||
|
setPopup(<MediaInfoPopup mediaType={"movie"} mediaSlug={slug!} close={close} />)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{type === "movie" && (
|
|
||||||
<IconButton
|
|
||||||
icon={MovieInfo}
|
|
||||||
color={{ xs: theme.user.contrast, md: theme.colors.white }}
|
|
||||||
onPress={() =>
|
|
||||||
slug &&
|
|
||||||
setPopup(
|
|
||||||
<MediaInfoPopup
|
|
||||||
mediaType={type}
|
|
||||||
mediaSlug={slug}
|
|
||||||
close={() => setPopup(undefined)}
|
|
||||||
/>,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{popup}
|
|
||||||
{rating !== null && (
|
{rating !== null && (
|
||||||
<>
|
<>
|
||||||
<DottedSeparator
|
<DottedSeparator
|
||||||
|
@ -45,6 +45,7 @@ import {
|
|||||||
SwitchVariant,
|
SwitchVariant,
|
||||||
imageBorderRadius,
|
imageBorderRadius,
|
||||||
ts,
|
ts,
|
||||||
|
usePopup,
|
||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
import { DefaultLayout } from "../layout";
|
import { DefaultLayout } from "../layout";
|
||||||
import { Children, ComponentProps, ReactElement, ReactNode, useState } from "react";
|
import { Children, ComponentProps, ReactElement, ReactNode, useState } from "react";
|
||||||
@ -257,10 +258,11 @@ const ChangePasswordPopup = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void }) => {
|
const AccountSettings = () => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [setPopup, close] = usePopup();
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { mutateAsync } = useMutation({
|
const { mutateAsync } = useMutation({
|
||||||
@ -335,7 +337,7 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
|
|||||||
label={t("settings.account.username.label")}
|
label={t("settings.account.username.label")}
|
||||||
inital={account.username}
|
inital={account.username}
|
||||||
apply={async (v) => await mutateAsync({ username: v })}
|
apply={async (v) => await mutateAsync({ username: v })}
|
||||||
close={() => setPopup(undefined)}
|
close={close}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -356,7 +358,7 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
|
|||||||
label={t("settings.account.email.label")}
|
label={t("settings.account.email.label")}
|
||||||
inital={account.email}
|
inital={account.email}
|
||||||
apply={async (v) => await mutateAsync({ email: v })}
|
apply={async (v) => await mutateAsync({ email: v })}
|
||||||
close={() => setPopup(undefined)}
|
close={close}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -375,7 +377,7 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
|
|||||||
icon={Password}
|
icon={Password}
|
||||||
label={t("settings.account.password.label")}
|
label={t("settings.account.password.label")}
|
||||||
apply={async (op, np) => await editPassword({ oldPassword: op, newPassword: np })}
|
apply={async (op, np) => await editPassword({ oldPassword: op, newPassword: np })}
|
||||||
close={() => setPopup(undefined)}
|
close={close}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -389,67 +391,63 @@ const AccountSettings = ({ setPopup }: { setPopup: (e?: ReactElement) => void })
|
|||||||
export const SettingsPage: QueryPage = () => {
|
export const SettingsPage: QueryPage = () => {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const languages = new Intl.DisplayNames([i18n.language ?? "en"], { type: "language" });
|
const languages = new Intl.DisplayNames([i18n.language ?? "en"], { type: "language" });
|
||||||
const [popup, setPopup] = useState<ReactElement | undefined>(undefined);
|
|
||||||
|
|
||||||
const theme = useUserTheme("auto");
|
const theme = useUserTheme("auto");
|
||||||
return (
|
return (
|
||||||
<>
|
<ScrollView contentContainerStyle={{ gap: ts(4), paddingBottom: ts(4) }}>
|
||||||
<ScrollView contentContainerStyle={{ gap: ts(4), paddingBottom: ts(4) }}>
|
<SettingsContainer title={t("settings.general.label")}>
|
||||||
<SettingsContainer title={t("settings.general.label")}>
|
<Preference
|
||||||
<Preference
|
icon={Theme}
|
||||||
icon={Theme}
|
label={t("settings.general.theme.label")}
|
||||||
|
description={t("settings.general.theme.description")}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
label={t("settings.general.theme.label")}
|
label={t("settings.general.theme.label")}
|
||||||
description={t("settings.general.theme.description")}
|
value={theme}
|
||||||
>
|
onValueChange={(value) => setUserTheme(value)}
|
||||||
<Select
|
values={["auto", "light", "dark"]}
|
||||||
label={t("settings.general.theme.label")}
|
getLabel={(key) => t(`settings.general.theme.${key}`)}
|
||||||
value={theme}
|
/>
|
||||||
onValueChange={(value) => setUserTheme(value)}
|
</Preference>
|
||||||
values={["auto", "light", "dark"]}
|
<Preference
|
||||||
getLabel={(key) => t(`settings.general.theme.${key}`)}
|
icon={Language}
|
||||||
/>
|
label={t("settings.general.language.label")}
|
||||||
</Preference>
|
description={t("settings.general.language.description")}
|
||||||
<Preference
|
>
|
||||||
icon={Language}
|
<Select
|
||||||
label={t("settings.general.language.label")}
|
label={t("settings.general.language.label")}
|
||||||
description={t("settings.general.language.description")}
|
value={i18n.resolvedLanguage!}
|
||||||
>
|
onValueChange={(value) =>
|
||||||
<Select
|
i18n.changeLanguage(value !== "system" ? value : (i18n.options.lng as string))
|
||||||
label={t("settings.general.language.label")}
|
}
|
||||||
value={i18n.resolvedLanguage!}
|
values={["system", ...Object.keys(i18n.options.resources!)]}
|
||||||
onValueChange={(value) =>
|
getLabel={(key) =>
|
||||||
i18n.changeLanguage(value !== "system" ? value : (i18n.options.lng as string))
|
key === "system" ? t("settings.general.language.system") : languages.of(key) ?? key
|
||||||
}
|
}
|
||||||
values={["system", ...Object.keys(i18n.options.resources!)]}
|
/>
|
||||||
getLabel={(key) =>
|
</Preference>
|
||||||
key === "system" ? t("settings.general.language.system") : languages.of(key) ?? key
|
</SettingsContainer>
|
||||||
}
|
<AccountSettings />
|
||||||
/>
|
<SettingsContainer title={t("settings.about.label")}>
|
||||||
</Preference>
|
<Link
|
||||||
</SettingsContainer>
|
href="https://github.com/zoriya/kyoo/releases/latest/download/kyoo.apk"
|
||||||
<AccountSettings setPopup={setPopup} />
|
target="_blank"
|
||||||
<SettingsContainer title={t("settings.about.label")}>
|
>
|
||||||
<Link
|
<Preference
|
||||||
href="https://github.com/zoriya/kyoo/releases/latest/download/kyoo.apk"
|
icon={Android}
|
||||||
target="_blank"
|
label={t("settings.about.android-app.label")}
|
||||||
>
|
description={t("settings.about.android-app.description")}
|
||||||
<Preference
|
/>
|
||||||
icon={Android}
|
</Link>
|
||||||
label={t("settings.about.android-app.label")}
|
<Link href="https://github.com/zoriya/kyoo" target="_blank">
|
||||||
description={t("settings.about.android-app.description")}
|
<Preference
|
||||||
/>
|
icon={Public}
|
||||||
</Link>
|
label={t("settings.about.git.label")}
|
||||||
<Link href="https://github.com/zoriya/kyoo" target="_blank">
|
description={t("settings.about.git.description")}
|
||||||
<Preference
|
/>
|
||||||
icon={Public}
|
</Link>
|
||||||
label={t("settings.about.git.label")}
|
</SettingsContainer>
|
||||||
description={t("settings.about.git.description")}
|
</ScrollView>
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</SettingsContainer>
|
|
||||||
</ScrollView>
|
|
||||||
{popup}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user