Add a multiaccount menu on mobile

This commit is contained in:
Zoe Roux 2023-07-13 17:57:11 +09:00
parent 12f685010b
commit fee59833f2
7 changed files with 60 additions and 14 deletions

View File

@ -24,6 +24,7 @@ import { zdate } from "./utils";
import { queryFn } from "./query";
import { KyooErrors } from "./kyoo-errors";
import { Platform } from "react-native";
import { useEffect, useState } from "react";
const TokenP = z.object({
token_type: z.literal("Bearer"),
@ -38,8 +39,21 @@ type Result<A, B> =
| { ok: true; value: A; error?: undefined }
| { ok: false; value?: undefined; error: B };
export type Account = Token & { apiUrl: string; username: string | null };
export type Account = Token & { apiUrl: string; username: string };
export const useAccounts = () => {
const [accounts, setAccounts] = useState<Account[]>([]);
const [selected, setSelected] = useState(0);
useEffect(() => {
async function run() {
setAccounts(JSON.parse(await getSecureItem("accounts") ?? "[]"));
}
run();
}, []);
return {accounts, selected, setSelected};
}
const addAccount = async (token: Token, apiUrl: string, username: string | null): Promise<void> => {
const accounts: Account[] = JSON.parse(await getSecureItem("accounts") ?? "[]");

View File

@ -72,7 +72,8 @@ export const Avatar = forwardRef<
[
{
borderRadius: 999999,
p: ts(1),
height: size,
width: size,
},
fill && {
bg: col,
@ -92,8 +93,6 @@ export const Avatar = forwardRef<
<P
{...css({
marginVertical: 0,
height: size,
width: size,
lineHeight: size,
textAlign: "center",
})}

View File

@ -20,7 +20,7 @@
import { Portal } from "@gorhom/portal";
import { ScrollView } from "moti";
import { ComponentType, createContext, ReactNode, useContext, useEffect, useState } from "react";
import { ComponentType, createContext, ReactElement, ReactNode, useContext, useEffect, useState } from "react";
import { 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";
@ -114,6 +114,7 @@ const Menu = <AsProps,>({
const MenuItem = ({
label,
selected,
left,
onSelect,
href,
icon,
@ -121,12 +122,15 @@ const MenuItem = ({
}: {
label: string;
selected?: boolean;
left?: ReactElement;
icon?: ComponentType<SvgProps>;
} & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined })) => {
const { css, theme } = useYoshiki();
const setOpen = useContext(MenuContext);
const router = useRouter();
const icn = (icon || selected) && <Icon icon={icon ?? Check} color={theme.paragraph} size={24} {...css({ paddingLeft: icon ? ts(2) : 0 })}/>;
return (
<PressableFeedback
onPress={() => {
@ -145,8 +149,10 @@ const MenuItem = ({
props as any,
)}
>
{(icon || selected) && <Icon icon={icon ?? Check} color={theme.paragraph} size={24} {...css({ paddingLeft: icon ? ts(2) : 0 })}/>}
<P {...css({ paddingLeft: ts(2) + +!(icon || selected) * px(24) })}>{label}</P>
{left && left}
{!left && icn && icn}
<P {...css({ paddingLeft: ts(2) + +!(icon || selected || left) * px(24), flexGrow: 1 })}>{label}</P>
{left && icn && icn}
</PressableFeedback>
);
};

View File

@ -19,7 +19,7 @@
*/
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { ComponentProps, ComponentType, forwardRef, ReactNode } from "react";
import { ComponentProps, ComponentType, forwardRef, ReactElement, ReactNode } from "react";
import Link from "next/link";
import { PressableProps } from "react-native";
import { useYoshiki } from "yoshiki/web";
@ -119,6 +119,7 @@ const Item = forwardRef<
const MenuItem = ({
label,
icon,
left,
selected,
onSelect,
href,
@ -126,6 +127,7 @@ const MenuItem = ({
}: {
label: string;
icon?: ComponentType<SvgProps>;
left?: ReactElement;
selected?: boolean;
} & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined })) => {
const { css: nCss } = useNativeYoshiki();
@ -152,6 +154,7 @@ const MenuItem = ({
props as any,
)}
>
{left && left}
{(icon || selected) && (
<Icon
icon={icon ?? Dot}
@ -160,7 +163,7 @@ const MenuItem = ({
{...nCss({ paddingRight: ts(1) })}
/>
)}
{<P {...nCss(!selected && { paddingLeft: ts(1 + (icon ? 2 : 1)) })}>{label}</P>}
{<P {...nCss(!(icon || selected || left) && { paddingLeft: ts(1) })}>{label}</P>}
</Item>
</>
);

View File

@ -26,6 +26,7 @@ import {
Page,
Paged,
QueryIdentifier,
useAccounts,
User,
UserP,
} from "@kyoo/models";
@ -41,6 +42,7 @@ import {
ts,
Menu,
PressableFeedback,
HR,
} from "@kyoo/primitives";
import { Platform, TextInput, View, ViewProps } from "react-native";
import { useTranslation } from "react-i18next";
@ -49,6 +51,8 @@ import { useRouter } from "solito/router";
import { px, rem, Stylable, useYoshiki } from "yoshiki/native";
import MenuIcon from "@material-symbols/svg-400/rounded/menu-fill.svg";
import Search from "@material-symbols/svg-400/rounded/search-fill.svg";
import Login from "@material-symbols/svg-400/rounded/login.svg";
import Register from "@material-symbols/svg-400/rounded/app_registration.svg";
import Logout from "@material-symbols/svg-400/rounded/logout.svg";
import Delete from "@material-symbols/svg-400/rounded/delete.svg";
import { Fetch, FetchNE } from "../fetch";
@ -102,10 +106,17 @@ export const MeQuery: QueryIdentifier<User> = {
parser: UserP,
};
const getDisplayUrl = (url: string) => {
url = url.replace(/\/api$/, "");
url = url.replace(/https?:\/\//, "");
return url;
};
export const NavbarProfile = () => {
const { css, theme } = useYoshiki();
const { t } = useTranslation();
const queryClient = useQueryClient();
const { accounts, selected, setSelected } = useAccounts();
return (
<FetchNE query={MeQuery}>
@ -120,13 +131,24 @@ export const NavbarProfile = () => {
{...css({ marginLeft: ts(1), justifyContent: "center" })}
{...tooltip(username ?? t("navbar.login"))}
>
{accounts.map((x, i) => (
<Menu.Item
key={x.refresh_token}
label={`${x.username} - ${getDisplayUrl(x.apiUrl)}`}
left={<Avatar placeholder={x.username} />}
selected={selected === i}
onSelect={() => setSelected(i)}
/>
))}
{accounts.length > 0 && <HR />}
{isGuest ? (
<>
<Menu.Item label={t("login.login")} href="/login" />
<Menu.Item label={t("login.register")} href="/register" />
<Menu.Item label={t("login.login")} icon={Login} href="/login" />
<Menu.Item label={t("login.register")} icon={Register} href="/register" />
</>
) : (
<>
<Menu.Item label={t("login.add-account")} icon={Login} href="/login" />
<Menu.Item
label={t("login.logout")}
icon={Logout}
@ -197,9 +219,9 @@ export const NavbarRight = () => {
onPress={
Platform.OS === "web"
? () => {
setSearch(true);
setTimeout(() => ref.current?.focus(), 0);
}
setSearch(true);
setTimeout(() => ref.current?.focus(), 0);
}
: () => push("/search")
}
{...tooltip(t("navbar.search"))}

View File

@ -60,6 +60,7 @@
"login": {
"login": "Login",
"register": "Register",
"add-account": "Add account",
"logout": "Logout",
"server": "Server Address",
"email": "Email",

View File

@ -60,6 +60,7 @@
"login": {
"login": "Connexion",
"register": "Créer un compte",
"add-account": "Ajouter un compte",
"logout": "Déconnexion",
"server": "Addresse du serveur",
"email": "Email",