mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-01 12:44:45 -04:00
Add avatar upload setting
This commit is contained in:
parent
0bd497279d
commit
2ecda09ee4
@ -34,6 +34,7 @@
|
|||||||
"expo-dev-client": "~3.3.7",
|
"expo-dev-client": "~3.3.7",
|
||||||
"expo-file-system": "~16.0.5",
|
"expo-file-system": "~16.0.5",
|
||||||
"expo-font": "~11.10.2",
|
"expo-font": "~11.10.2",
|
||||||
|
"expo-image-picker": "~14.7.1",
|
||||||
"expo-linear-gradient": "~12.7.1",
|
"expo-linear-gradient": "~12.7.1",
|
||||||
"expo-linking": "~6.2.2",
|
"expo-linking": "~6.2.2",
|
||||||
"expo-localization": "~14.8.3",
|
"expo-localization": "~14.8.3",
|
||||||
|
@ -108,6 +108,7 @@ const nextConfig = {
|
|||||||
"expo-av",
|
"expo-av",
|
||||||
"expo-modules-core",
|
"expo-modules-core",
|
||||||
"expo-linear-gradient",
|
"expo-linear-gradient",
|
||||||
|
"expo-image-picker",
|
||||||
],
|
],
|
||||||
experimental: {
|
experimental: {
|
||||||
outputFileTracingRoot: path.join(__dirname, "../../"),
|
outputFileTracingRoot: path.join(__dirname, "../../"),
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"@tanstack/react-query": "^5.17.19",
|
"@tanstack/react-query": "^5.17.19",
|
||||||
"@tanstack/react-query-devtools": "^5.17.21",
|
"@tanstack/react-query-devtools": "^5.17.21",
|
||||||
"array-shuffle": "^3.0.0",
|
"array-shuffle": "^3.0.0",
|
||||||
|
"expo-image-picker": "~14.7.1",
|
||||||
"expo-linear-gradient": "^12.7.1",
|
"expo-linear-gradient": "^12.7.1",
|
||||||
"expo-modules-core": "^1.11.8",
|
"expo-modules-core": "^1.11.8",
|
||||||
"hls.js": "^1.5.2",
|
"hls.js": "^1.5.2",
|
||||||
|
@ -48,6 +48,7 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
|
|||||||
| ({
|
| ({
|
||||||
path: (string | false | undefined | null)[];
|
path: (string | false | undefined | null)[];
|
||||||
body?: object;
|
body?: object;
|
||||||
|
formData?: FormData;
|
||||||
plainText?: boolean;
|
plainText?: boolean;
|
||||||
} & Partial<QueryFunctionContext>)
|
} & Partial<QueryFunctionContext>)
|
||||||
),
|
),
|
||||||
@ -72,7 +73,12 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
|
|||||||
try {
|
try {
|
||||||
resp = await fetch(path, {
|
resp = await fetch(path, {
|
||||||
method: context.method,
|
method: context.method,
|
||||||
body: "body" in context && context.body ? JSON.stringify(context.body) : undefined,
|
body:
|
||||||
|
"body" in context && context.body
|
||||||
|
? JSON.stringify(context.body)
|
||||||
|
: "formData" in context && context.formData
|
||||||
|
? context.formData
|
||||||
|
: undefined,
|
||||||
headers: {
|
headers: {
|
||||||
...(token ? { Authorization: token } : {}),
|
...(token ? { Authorization: token } : {}),
|
||||||
...("body" in context ? { "Content-Type": "application/json" } : {}),
|
...("body" in context ? { "Content-Type": "application/json" } : {}),
|
||||||
|
@ -23,8 +23,7 @@ import { useYoshiki, px, Stylable } from "yoshiki/native";
|
|||||||
import { Icon } from "./icons";
|
import { Icon } from "./icons";
|
||||||
import { P } from "./text";
|
import { P } from "./text";
|
||||||
import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
|
import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
|
||||||
import { YoshikiStyle } from "yoshiki";
|
import { ComponentType, forwardRef, RefAttributes } from "react";
|
||||||
import { ComponentType, forwardRef, RefAttributes, useEffect, useState } from "react";
|
|
||||||
|
|
||||||
const stringToColor = (string: string) => {
|
const stringToColor = (string: string) => {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"@shopify/flash-list": "^1.3.1",
|
"@shopify/flash-list": "^1.3.1",
|
||||||
"@tanstack/react-query": "*",
|
"@tanstack/react-query": "*",
|
||||||
"expo-file-system": "*",
|
"expo-file-system": "*",
|
||||||
|
"expo-image-picker": "~14.7.1",
|
||||||
"expo-linear-gradient": "*",
|
"expo-linear-gradient": "*",
|
||||||
"expo-router": "*",
|
"expo-router": "*",
|
||||||
"i18next": "*",
|
"i18next": "*",
|
||||||
|
@ -19,21 +19,33 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Account, KyooErrors, deleteAccount, logout, queryFn, useAccount } from "@kyoo/models";
|
import { Account, KyooErrors, deleteAccount, logout, queryFn, useAccount } from "@kyoo/models";
|
||||||
import { Alert, Button, H1, Icon, Input, P, Popup, ts, usePopup } from "@kyoo/primitives";
|
import { Alert, Avatar, Button, H1, Icon, Input, P, Popup, ts, usePopup } from "@kyoo/primitives";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { ComponentProps, useState } from "react";
|
import { ComponentProps, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { rem, useYoshiki } from "yoshiki/native";
|
import { rem, useYoshiki } from "yoshiki/native";
|
||||||
|
import * as ImagePicker from "expo-image-picker";
|
||||||
import { PasswordInput } from "../login/password-input";
|
import { PasswordInput } from "../login/password-input";
|
||||||
import { Preference, SettingsContainer } from "./base";
|
import { Preference, SettingsContainer } from "./base";
|
||||||
|
|
||||||
import Username from "@material-symbols/svg-400/outlined/badge.svg";
|
import Username from "@material-symbols/svg-400/outlined/badge.svg";
|
||||||
|
import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
|
||||||
import Mail from "@material-symbols/svg-400/outlined/mail.svg";
|
import Mail from "@material-symbols/svg-400/outlined/mail.svg";
|
||||||
import Password from "@material-symbols/svg-400/outlined/password.svg";
|
import Password from "@material-symbols/svg-400/outlined/password.svg";
|
||||||
import Delete from "@material-symbols/svg-400/rounded/delete.svg";
|
import Delete from "@material-symbols/svg-400/rounded/delete.svg";
|
||||||
import Logout from "@material-symbols/svg-400/rounded/logout.svg";
|
import Logout from "@material-symbols/svg-400/rounded/logout.svg";
|
||||||
|
|
||||||
|
function dataURItoBlob(dataURI: string) {
|
||||||
|
const byteString = atob(dataURI.split(",")[1]);
|
||||||
|
const ab = new ArrayBuffer(byteString.length);
|
||||||
|
const ia = new Uint8Array(ab);
|
||||||
|
for (var i = 0; i < byteString.length; i++) {
|
||||||
|
ia[i] = byteString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return new Blob([ab], { type: "image/jpeg" });
|
||||||
|
}
|
||||||
|
|
||||||
export const AccountSettings = () => {
|
export const AccountSettings = () => {
|
||||||
const account = useAccount()!;
|
const account = useAccount()!;
|
||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
@ -118,6 +130,33 @@ export const AccountSettings = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Preference>
|
</Preference>
|
||||||
|
<Preference
|
||||||
|
icon={AccountCircle}
|
||||||
|
customIcon={<Avatar src={account.logo} />}
|
||||||
|
label={t("settings.account.avatar.label")}
|
||||||
|
description={t("settings.account.avatar.description")}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
text={t("misc.edit")}
|
||||||
|
onPress={async () => {
|
||||||
|
const img = await ImagePicker.launchImageLibraryAsync({
|
||||||
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||||
|
aspect: [1, 1],
|
||||||
|
quality: 1,
|
||||||
|
base64: true,
|
||||||
|
});
|
||||||
|
if (img.canceled || img.assets.length !== 1) return;
|
||||||
|
const data = dataURItoBlob(img.assets[0].uri);
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("picture", data);
|
||||||
|
await queryFn({
|
||||||
|
method: "POST",
|
||||||
|
path: ["auth", "me", "logo"],
|
||||||
|
formData,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Preference>
|
||||||
<Preference icon={Mail} label={t("settings.account.email.label")} description={account.email}>
|
<Preference icon={Mail} label={t("settings.account.email.label")} description={account.email}>
|
||||||
<Button
|
<Button
|
||||||
text={t("misc.edit")}
|
text={t("misc.edit")}
|
||||||
|
@ -36,12 +36,14 @@ import { View } from "react-native";
|
|||||||
import { px, rem, useYoshiki } from "yoshiki/native";
|
import { px, rem, useYoshiki } from "yoshiki/native";
|
||||||
|
|
||||||
export const Preference = ({
|
export const Preference = ({
|
||||||
|
customIcon,
|
||||||
icon,
|
icon,
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
|
customIcon?: ReactElement;
|
||||||
icon: Icon;
|
icon: Icon;
|
||||||
label: string;
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -62,8 +64,16 @@ export const Preference = ({
|
|||||||
props,
|
props,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<View {...css({ flexDirection: "row", alignItems: "center", flexShrink: 1 })}>
|
<View
|
||||||
<Icon icon={icon} {...css({ marginX: ts(2) })} />
|
{...css({
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
flexShrink: 1,
|
||||||
|
marginX: ts(2),
|
||||||
|
gap: ts(2),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{customIcon ?? <Icon icon={icon} />}
|
||||||
<View {...css({ flexShrink: 1 })}>
|
<View {...css({ flexShrink: 1 })}>
|
||||||
<P {...css({ marginBottom: 0 })}>{label}</P>
|
<P {...css({ marginBottom: 0 })}>{label}</P>
|
||||||
<SubP>{description}</SubP>
|
<SubP>{description}</SubP>
|
||||||
|
@ -2619,6 +2619,7 @@ __metadata:
|
|||||||
"@shopify/flash-list": ^1.3.1
|
"@shopify/flash-list": ^1.3.1
|
||||||
"@tanstack/react-query": "*"
|
"@tanstack/react-query": "*"
|
||||||
expo-file-system: "*"
|
expo-file-system: "*"
|
||||||
|
expo-image-picker: ~14.7.1
|
||||||
expo-linear-gradient: "*"
|
expo-linear-gradient: "*"
|
||||||
expo-router: "*"
|
expo-router: "*"
|
||||||
i18next: "*"
|
i18next: "*"
|
||||||
@ -7420,6 +7421,26 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"expo-image-loader@npm:~4.6.0":
|
||||||
|
version: 4.6.0
|
||||||
|
resolution: "expo-image-loader@npm:4.6.0"
|
||||||
|
peerDependencies:
|
||||||
|
expo: "*"
|
||||||
|
checksum: 02981667f03dc429cd9db37e0acc302e4a0c4bb5875dc087b1c26388ef64563481e52dddc1d42dd32794eb7051d1acf5bc0c078413469c29025a02d4d4af1154
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"expo-image-picker@npm:~14.7.1":
|
||||||
|
version: 14.7.1
|
||||||
|
resolution: "expo-image-picker@npm:14.7.1"
|
||||||
|
dependencies:
|
||||||
|
expo-image-loader: ~4.6.0
|
||||||
|
peerDependencies:
|
||||||
|
expo: "*"
|
||||||
|
checksum: f9022cc9162365471b8e979df2c7a2156f43819b4717fb6ba376aedb6b55352a7b60b3a50a279dd58cf2551fbdd8a0719c5c903dcde4703436029e0fdca9c035
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"expo-json-utils@npm:~0.12.0":
|
"expo-json-utils@npm:~0.12.0":
|
||||||
version: 0.12.3
|
version: 0.12.3
|
||||||
resolution: "expo-json-utils@npm:0.12.3"
|
resolution: "expo-json-utils@npm:0.12.3"
|
||||||
@ -10426,6 +10447,7 @@ __metadata:
|
|||||||
expo-dev-client: ~3.3.7
|
expo-dev-client: ~3.3.7
|
||||||
expo-file-system: ~16.0.5
|
expo-file-system: ~16.0.5
|
||||||
expo-font: ~11.10.2
|
expo-font: ~11.10.2
|
||||||
|
expo-image-picker: ~14.7.1
|
||||||
expo-linear-gradient: ~12.7.1
|
expo-linear-gradient: ~12.7.1
|
||||||
expo-linking: ~6.2.2
|
expo-linking: ~6.2.2
|
||||||
expo-localization: ~14.8.3
|
expo-localization: ~14.8.3
|
||||||
@ -13998,6 +14020,7 @@ __metadata:
|
|||||||
copy-webpack-plugin: ^12.0.2
|
copy-webpack-plugin: ^12.0.2
|
||||||
eslint: ^8.56.0
|
eslint: ^8.56.0
|
||||||
eslint-config-next: 14.1.0
|
eslint-config-next: 14.1.0
|
||||||
|
expo-image-picker: ~14.7.1
|
||||||
expo-linear-gradient: ^12.7.1
|
expo-linear-gradient: ^12.7.1
|
||||||
expo-modules-core: ^1.11.8
|
expo-modules-core: ^1.11.8
|
||||||
hls.js: ^1.5.2
|
hls.js: ^1.5.2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user