diff --git a/src/App.tsx b/src/App.tsx
index 2a50651..f5d4939 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -90,6 +90,7 @@ import { requestQueueGroupJoinRequests } from "./components/Group/GroupJoinReque
import { DrawerComponent } from "./components/Drawer/Drawer";
import { LitecoinQRCode } from "./components/LitecoinQRCode";
import { Settings } from "./components/Group/Settings";
+import { MainAvatar } from "./components/MainAvatar";
type extStates =
| "not-authenticated"
@@ -1263,7 +1264,7 @@ function App() {
>
) : (
<>
-
+
{
return true;
break;
}
+ case "publishOnQDN": {
+ const { data, identifier, service } = request.payload;
+
+ publishOnQDN({
+ data,
+ identifier,
+ service
+ })
+ .then((data) => {
+ sendResponse(data);
+ })
+ .catch((error) => {
+ console.error(error.message);
+ sendResponse({ error: error.message });
+ });
+ return true;
+ break;
+ }
case "handleActiveGroupDataFromSocket": {
const { groups, directs } = request.payload;
handleActiveGroupDataFromSocket({
diff --git a/src/backgroundFunctions/encryption.ts b/src/backgroundFunctions/encryption.ts
index d641644..54640f1 100644
--- a/src/backgroundFunctions/encryption.ts
+++ b/src/backgroundFunctions/encryption.ts
@@ -152,6 +152,24 @@ export const publishGroupEncryptedResource = async ({encryptedData, identifier})
throw new Error(error.message);
}
}
+export const publishOnQDN = async ({data, identifier, service}) => {
+ try {
+
+ if(data && identifier && service){
+ const registeredName = await getNameInfo()
+ if(!registeredName) throw new Error('You need a name to publish')
+ const res = await publishData({
+ registeredName, file: data, service, identifier, uploadType: 'file', isBase64: true, withFee: true
+ })
+ return res
+
+ } else {
+ throw new Error('Cannot encrypt content')
+ }
+ } catch (error: any) {
+ throw new Error(error.message);
+ }
+}
export function uint8ArrayToBase64(uint8Array: any) {
const length = uint8Array.length
diff --git a/src/common/ImageUploader.tsx b/src/common/ImageUploader.tsx
new file mode 100644
index 0000000..f21c17f
--- /dev/null
+++ b/src/common/ImageUploader.tsx
@@ -0,0 +1,102 @@
+import React, { useCallback } from 'react'
+import { Box } from '@mui/material'
+import { useDropzone, DropzoneRootProps, DropzoneInputProps } from 'react-dropzone'
+import Compressor from 'compressorjs'
+
+const toBase64 = (file: File): Promise =>
+ new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.readAsDataURL(file)
+ reader.onload = () => resolve(reader.result)
+ reader.onerror = (error) => {
+ reject(error)
+ }
+ })
+
+interface ImageUploaderProps {
+ children: React.ReactNode
+ onPick: (file: File) => void
+}
+
+const ImageUploader: React.FC = ({ children, onPick }) => {
+ const onDrop = useCallback(
+ async (acceptedFiles: File[]) => {
+ if (acceptedFiles.length > 1) {
+ return
+ }
+
+ const image = acceptedFiles[0]
+ let compressedFile: File | undefined
+
+ try {
+ // Check if the file is a GIF
+ if (image.type === 'image/gif') {
+ // Check if the GIF is larger than 500 KB
+ if (image.size > 500 * 1024) {
+ console.error('GIF file size exceeds 500KB limit.')
+ return
+ }
+
+ // No compression for GIF, pass the original file
+ compressedFile = image
+ } else {
+ // For non-GIF files, compress them
+ await new Promise((resolve) => {
+ new Compressor(image, {
+ quality: 0.6,
+ maxWidth: 1200,
+ mimeType: 'image/webp',
+ success(result) {
+ const file = new File([result], image.name, {
+ type: 'image/webp'
+ })
+ compressedFile = file
+ resolve()
+ },
+ error(err) {
+ console.error('Compression error:', err)
+ resolve() // Proceed even if there's an error
+ }
+ })
+ })
+ }
+
+ if (!compressedFile) return
+
+ onPick(compressedFile)
+ } catch (error) {
+ console.error('File processing error:', error)
+ }
+ },
+ [onPick]
+ )
+
+ const {
+ getRootProps,
+ getInputProps,
+ isDragActive
+ }: {
+ getRootProps: () => DropzoneRootProps
+ getInputProps: () => DropzoneInputProps
+ isDragActive: boolean
+ } = useDropzone({
+ onDrop,
+ accept: {
+ 'image/*': []
+ }
+ })
+
+ return (
+
+
+ {children}
+
+ )
+}
+
+export default ImageUploader
diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx
new file mode 100644
index 0000000..71aed3b
--- /dev/null
+++ b/src/components/MainAvatar.tsx
@@ -0,0 +1,206 @@
+import React, { useContext, useEffect, useState } from "react";
+import Logo2 from "../assets/svgs/Logo2.svg";
+import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from "../App";
+import { Avatar, Box, Button, ButtonBase, Popover, Typography } from "@mui/material";
+import { Spacer } from "../common/Spacer";
+import ImageUploader from "../common/ImageUploader";
+import { getFee } from "../background";
+import { fileToBase64 } from "../utils/fileReading";
+import { LoadingButton } from "@mui/lab";
+
+export const MainAvatar = ({ myName }) => {
+ const [hasAvatar, setHasAvatar] = useState(false);
+ const [avatarFile, setAvatarFile] = useState(null);
+ const [tempAvatar, setTempAvatar] = useState(null)
+ const { show } = useContext(MyContext);
+
+ const [anchorEl, setAnchorEl] = useState(null);
+const [isLoading, setIsLoading] = useState(false)
+ // Handle child element click to open Popover
+ const handleChildClick = (event) => {
+ event.stopPropagation(); // Prevent parent onClick from firing
+ setAnchorEl(event.currentTarget);
+ };
+
+ // Handle closing the Popover
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ // Determine if the popover is open
+ const open = Boolean(anchorEl);
+ const id = open ? 'avatar-img' : undefined;
+
+ const checkIfAvatarExists = async () => {
+ try {
+ const identifier = `qortal_avatar`;
+ const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=THUMBNAIL&identifier=${identifier}&limit=1&name=${myName}&includemetadata=false&prefix=true`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const responseData = await response.json();
+ if (responseData?.length > 0) {
+ setHasAvatar(true);
+ }
+ } catch (error) {}
+ };
+ useEffect(() => {
+ if (!myName) return;
+ checkIfAvatarExists();
+ }, [myName]);
+
+ const publishAvatar = async ()=> {
+ try {
+ const fee = await getFee('ARBITRARY')
+
+ await show({
+ message: "Would you like to publish an avatar?" ,
+ publishFee: fee.fee + ' QORT'
+ })
+ setIsLoading(true);
+ const avatarBase64 = await fileToBase64(avatarFile)
+ await new Promise((res, rej) => {
+ chrome?.runtime?.sendMessage(
+ {
+ action: "publishOnQDN",
+ payload: {
+ data: avatarBase64,
+ identifier: "qortal_avatar",
+ service: 'THUMBNAIL'
+ },
+ },
+ (response) => {
+
+ if (!response?.error) {
+ res(response);
+ return
+ }
+ rej(response.error);
+ }
+ );
+ });
+ setAvatarFile(null);
+ setTempAvatar(`data:image/webp;base64,${avatarBase64}`)
+ handleClose()
+ } catch (error) {
+
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
+ if(tempAvatar){
+ return (
+ <>
+
+ {myName?.charAt(0)}
+
+
+
+ change avatar
+
+
+
+ >
+ );
+ }
+
+ if (hasAvatar) {
+ return (
+ <>
+
+ {myName?.charAt(0)}
+
+
+
+ change avatar
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+
+ set avatar
+
+
+
+ >
+ );
+};
+
+
+const PopoverComp = ({avatarFile, setAvatarFile, id, open, anchorEl, handleClose, publishAvatar, isLoading}) => {
+ return (
+
+
+
+ (500 KB max. for GIFS){" "}
+
+ setAvatarFile(file)}>
+
+
+ {avatarFile?.name}
+
+
+
+ Publish avatar
+
+
+
+ )
+ };
\ No newline at end of file
diff --git a/src/components/Mobile/MobileHeader.tsx b/src/components/Mobile/MobileHeader.tsx
index f49291e..5856259 100644
--- a/src/components/Mobile/MobileHeader.tsx
+++ b/src/components/Mobile/MobileHeader.tsx
@@ -321,7 +321,7 @@ const Header = ({
display: "flex",
justifyContent: "center",
alignItems: "center",
- zIndex: 500,
+ zIndex: 6,
width: "30px", // Adjust as needed
height: "30px", // Adjust as needed
backgroundColor: "#232428", // Circle background
diff --git a/src/utils/fileReading/index.ts b/src/utils/fileReading/index.ts
new file mode 100644
index 0000000..c96dd74
--- /dev/null
+++ b/src/utils/fileReading/index.ts
@@ -0,0 +1,57 @@
+// @ts-nocheck
+
+class Semaphore {
+ constructor(count) {
+ this.count = count
+ this.waiting = []
+ }
+ acquire() {
+ return new Promise(resolve => {
+ if (this.count > 0) {
+ this.count--
+ resolve()
+ } else {
+ this.waiting.push(resolve)
+ }
+ })
+ }
+ release() {
+ if (this.waiting.length > 0) {
+ const resolve = this.waiting.shift()
+ resolve()
+ } else {
+ this.count++
+ }
+ }
+}
+
+let semaphore = new Semaphore(1)
+let reader = new FileReader()
+
+export const fileToBase64 = (file) => new Promise(async (resolve, reject) => {
+ if (!reader) {
+ reader = new FileReader()
+ }
+ await semaphore.acquire()
+ reader.readAsDataURL(file)
+ reader.onload = () => {
+ const dataUrl = reader.result
+ if (typeof dataUrl === "string") {
+ const base64String = dataUrl.split(',')[1]
+ reader.onload = null
+ reader.onerror = null
+ resolve(base64String)
+ } else {
+ reader.onload = null
+ reader.onerror = null
+ reject(new Error('Invalid data URL'))
+ }
+ semaphore.release()
+ }
+ reader.onerror = (error) => {
+ reader.onload = null
+ reader.onerror = null
+ reject(error)
+ semaphore.release()
+ }
+})
\ No newline at end of file