From ac5dea602a7f5be68fd2d96b2cc7509c272b7d44 Mon Sep 17 00:00:00 2001
From: Der_Googler <54764558+DerGoogler@users.noreply.github.com>
Date: Sun, 18 Aug 2024 15:23:00 +0200
Subject: [PATCH] module download improvements

---
 src/activitys/ModuleViewActivity/index.tsx    | 58 +++-----------
 .../ModuleViewActivity/tabs/VersionsTab.tsx   | 22 ++++--
 src/components/AvatarWithProgress.tsx         | 75 +++++++++++++++++++
 src/hooks/useDownloadModule.ts                | 47 ++++++++++++
 src/native/Download.ts                        |  3 +
 src/native/Environment.ts                     | 20 ++++-
 src/native/IsolatedEval/index.ts              |  4 +
 7 files changed, 170 insertions(+), 59 deletions(-)
 create mode 100644 src/components/AvatarWithProgress.tsx
 create mode 100644 src/hooks/useDownloadModule.ts

diff --git a/src/activitys/ModuleViewActivity/index.tsx b/src/activitys/ModuleViewActivity/index.tsx
index 91251361..d7e17525 100644
--- a/src/activitys/ModuleViewActivity/index.tsx
+++ b/src/activitys/ModuleViewActivity/index.tsx
@@ -34,6 +34,8 @@ import { useFormatBytes } from "@Hooks/useFormatBytes";
 import LinearProgress from "@mui/material/LinearProgress";
 import { Download } from "@Native/Download";
 import { Environment } from "@Native/Environment";
+import { useDownloadModule } from "@Hooks/useDownloadModule";
+import { AvatarWithProgress } from "@Components/AvatarWithProgress";
 
 function a11yProps(index: number) {
   return {
@@ -137,7 +139,7 @@ const ModuleViewActivity = () => {
 
   const cconfirm = useConfirm();
 
-  const [downloadProgress, setDownloadProgress] = React.useState(0);
+  const [startDL, progress] = useDownloadModule();
 
   return (
     <Page
@@ -217,7 +219,8 @@ const ModuleViewActivity = () => {
               width: "100%",
             }}
           >
-            <Avatar
+            <AvatarWithProgress
+              value={progress}
               alt={name}
               sx={(theme) => ({
                 bgcolor: theme.palette.primary.dark,
@@ -225,15 +228,15 @@ const ModuleViewActivity = () => {
                 height: 100,
                 boxShadow: "0 -1px 5px rgba(0,0,0,.09), 0 3px 5px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.3), 0 1px 3px rgba(0,0,0,.15)",
                 borderRadius: "20%",
-                mr: 1.5,
                 fontSize: 50,
               })}
               src={icon}
+              progressTextVariant="body2"
             >
               {name.charAt(0).toUpperCase()}
-            </Avatar>
+            </AvatarWithProgress>
 
-            <Box sx={{ alignSelf: "center", ml: 0.5, mr: 0.5, width: "100%" }}>
+            <Box sx={{ alignSelf: "center", ml: 2, mr: 0.5, width: "100%" }}>
               <Stack direction="row" justifyContent="flex-start" alignItems="center" spacing={0.5}>
                 <Disappear as={Typography} variant="body1" fontWeight="bold" onDisappear={(visible) => setIsNameVisible(!visible)}>
                   {name}
@@ -344,17 +347,6 @@ const ModuleViewActivity = () => {
               </Stack>
 
               <Stack direction="column" justifyContent="center" alignItems="stretch" spacing={1}>
-                {downloadProgress !== 0 && (
-                  <Box sx={{ display: "flex", alignItems: "center" }}>
-                    <Box sx={{ width: "100%", mr: 1 }}>
-                      <LinearProgress variant="determinate" value={downloadProgress} />
-                    </Box>
-                    <Box sx={{ minWidth: 35 }}>
-                      <Typography variant="body2" color="text.secondary">{`${Math.round(downloadProgress)}%`}</Typography>
-                    </Box>
-                  </Box>
-                )}
-
                 <DropdownButton
                   sx={{
                     width: "100%",
@@ -372,39 +364,7 @@ const ModuleViewActivity = () => {
                       onClick: () => {
                         const lasSeg = new URL(latestVersion.zipUrl).pathname.split("/").pop();
                         const dlPath = Environment.getPublicDir(Environment.DIRECTORY_DOWNLOADS) + "/" + lasSeg;
-                        const dl = new Download(latestVersion.zipUrl, dlPath);
-
-                        dl.onChange = (obj) => {
-                          switch (obj.type) {
-                            case "downloading":
-                              setDownloadProgress(obj.state);
-                              break;
-                            case "finished":
-                              setDownloadProgress(0);
-                              cconfirm({
-                                title: strings("download"),
-                                description: strings("file_downloaded", { path: dlPath }),
-                              })
-                                .then(() => {})
-                                .catch(() => {});
-
-                              break;
-                          }
-                        };
-
-                        dl.onError = (err) => {
-                          setDownloadProgress(0);
-                          os.toast("finsish: " + err, Toast.LENGTH_SHORT);
-                        };
-
-                        dl.start();
-
-                        // os.open(latestVersion.zipUrl, {
-                        //   target: "_blank",
-                        //   features: {
-                        //     color: theme.palette.primary.main,
-                        //   },
-                        // });
+                        startDL(latestVersion.zipUrl, dlPath);
                       },
                     },
                     {
diff --git a/src/activitys/ModuleViewActivity/tabs/VersionsTab.tsx b/src/activitys/ModuleViewActivity/tabs/VersionsTab.tsx
index bd3d51c1..90e60772 100644
--- a/src/activitys/ModuleViewActivity/tabs/VersionsTab.tsx
+++ b/src/activitys/ModuleViewActivity/tabs/VersionsTab.tsx
@@ -2,9 +2,11 @@ import FetchTextActivity from "@Activitys/FetchTextActivity";
 import InstallTerminalActivity from "@Activitys/InstallTerminalActivity";
 import InstallTerminalV2Activity from "@Activitys/InstallTerminalV2Activity";
 import { useActivity } from "@Hooks/useActivity";
+import { useDownloadModule } from "@Hooks/useDownloadModule";
 import { useFormatDate } from "@Hooks/useFormatDate";
 import { useStrings } from "@Hooks/useStrings";
 import { useTheme } from "@Hooks/useTheme";
+import { Environment } from "@Native/Environment";
 import { os } from "@Native/Os";
 import { Shell } from "@Native/Shell";
 import DownloadIcon from "@mui/icons-material/Download";
@@ -12,6 +14,7 @@ import InstallMobileIcon from "@mui/icons-material/InstallMobile";
 import ManageHistoryIcon from "@mui/icons-material/ManageHistory";
 import Chip from "@mui/material/Chip";
 import IconButton from "@mui/material/IconButton";
+import LinearProgress from "@mui/material/LinearProgress";
 import List from "@mui/material/List";
 import ListItem from "@mui/material/ListItem";
 import ListItemText from "@mui/material/ListItemText";
@@ -46,6 +49,8 @@ const VersionItem = React.memo<VersionItemProps>(({ id, version, index }) => {
   const { strings } = useStrings();
   const { theme } = useTheme();
 
+  const [startDL, progress] = useDownloadModule();
+
   const { track, support } = extra;
 
   const versionName = `${version.version} (${version.versionCode})`;
@@ -71,6 +76,11 @@ const VersionItem = React.memo<VersionItemProps>(({ id, version, index }) => {
 
   return (
     <ListItem
+      sx={{
+        "& .MuiListItem-root": {
+          position: "relative",
+        },
+      }}
       secondaryAction={
         <Stack direction="row" justifyContent="center" alignItems="center" spacing={0.5}>
           {version.changelog && (
@@ -102,12 +112,9 @@ const VersionItem = React.memo<VersionItemProps>(({ id, version, index }) => {
           <IconButton
             disabled={!version.zipUrl}
             onClick={() => {
-              os.open(version.zipUrl, {
-                target: "_blank",
-                features: {
-                  color: theme.palette.primary.main,
-                },
-              });
+              const lasSeg = new URL(version.zipUrl).pathname.split("/").pop();
+              const dlPath = Environment.getPublicDir(Environment.DIRECTORY_DOWNLOADS) + "/" + lasSeg;
+              startDL(version.zipUrl, dlPath);
             }}
             edge="end"
             aria-label="download"
@@ -126,6 +133,9 @@ const VersionItem = React.memo<VersionItemProps>(({ id, version, index }) => {
         }
         secondary={ts}
       />
+      {progress !== 0 && (
+        <LinearProgress sx={{ position: "absolute", bottom: 0, left: 0, right: 0, width: "100%" }} variant="determinate" value={progress} />
+      )}
     </ListItem>
   );
 });
diff --git a/src/components/AvatarWithProgress.tsx b/src/components/AvatarWithProgress.tsx
new file mode 100644
index 00000000..15a551cc
--- /dev/null
+++ b/src/components/AvatarWithProgress.tsx
@@ -0,0 +1,75 @@
+import { Avatar, Box, CircularProgress, SxProps, Typography, TypographyProps } from "@mui/material";
+import React from "react";
+
+interface AvatarWithProgressProps extends React.PropsWithChildren {
+  value: number;
+  src?: string;
+  sx?: SxProps<MMRLTheme>;
+  alt?: string;
+  progressTextVariant?: TypographyProps["variant"];
+}
+
+const AvatarWithProgress = (props: AvatarWithProgressProps) => {
+  const isActive = React.useMemo(() => props.value > 0, [props.value]);
+
+  return (
+    <Box
+      sx={{
+        position: "relative",
+        display: "inline-flex",
+        borderRadius: "20%",
+        // overflow: "hidden",
+        // @ts-ignore
+        ...props.sx["& container"],
+      }}
+    >
+      <Avatar src={props.src} sx={props.sx} alt={props.alt} children={props.children} />
+      {isActive && (
+        <>
+          <Box
+            sx={(theme) => ({
+              position: "absolute",
+              top: 0,
+              left: 0,
+              right: 0,
+              bottom: 0,
+              backgroundColor: theme.palette.background.paper,
+              opacity: isActive ? 0.6 : 0,
+              zIndex: 0,
+              // @ts-ignore
+              borderRadius: props.sx.borderRadius,
+            })}
+          />
+
+          <CircularProgress
+            sx={{
+              position: "absolute",
+              top: 0,
+              left: 0,
+              right: 0,
+              bottom: 0,
+              margin: "auto",
+              zIndex: 1,
+            }}
+            variant="determinate"
+            value={props.value}
+            size={87}
+            thickness={1.8}
+          />
+          <Typography
+            sx={{
+              position: "absolute",
+              top: "50%",
+              left: "50%",
+              transform: "translate(-50%, -50%)",
+              zIndex: 2,
+            }}
+            variant={props.progressTextVariant || "caption"}
+          >{`${Math.round(props.value)}%`}</Typography>
+        </>
+      )}
+    </Box>
+  );
+};
+
+export { AvatarWithProgress };
diff --git a/src/hooks/useDownloadModule.ts b/src/hooks/useDownloadModule.ts
new file mode 100644
index 00000000..1f35e805
--- /dev/null
+++ b/src/hooks/useDownloadModule.ts
@@ -0,0 +1,47 @@
+import React from "react";
+import { useStrings } from "./useStrings";
+import { useConfirm } from "material-ui-confirm";
+import { os } from "@Native/Os";
+import { Download } from "@Native/Download";
+
+const useDownloadModule = (): [(url?: string, dest?: string) => void, number] => {
+  const { strings } = useStrings();
+  const konfirm = useConfirm();
+
+  const [progress, setProgress] = React.useState(0);
+
+  const start = (url?: string, dest?: string) => {
+    if (!url || !dest) return;
+
+    const dl = new Download(url, dest);
+
+    dl.onChange = (obj) => {
+      switch (obj.type) {
+        case "downloading":
+          setProgress(obj.state);
+          break;
+        case "finished":
+          setProgress(0);
+          konfirm({
+            title: strings("download"),
+            description: strings("file_downloaded", { path: dest }),
+          })
+            .then(() => {})
+            .catch(() => {});
+
+          break;
+      }
+    };
+
+    dl.onError = (err) => {
+      setProgress(0);
+      os.toast(err, Toast.LENGTH_SHORT);
+    };
+
+    dl.start();
+  };
+
+  return [start, progress];
+};
+
+export { useDownloadModule };
diff --git a/src/native/Download.ts b/src/native/Download.ts
index c4aa4454..14de8205 100644
--- a/src/native/Download.ts
+++ b/src/native/Download.ts
@@ -1,4 +1,5 @@
 import { Native } from "./Native";
+import { os } from "./Os";
 
 type DownloadState = { type: "downloading"; state: number } | { type: "finished"; state: null };
 
@@ -43,6 +44,8 @@ class Download extends Native<DownloadNative> {
         onChange: this._onChange,
         onError: this._onError,
       });
+    } else {
+      os.openURL(this._url, "_blank");
     }
   }
 }
diff --git a/src/native/Environment.ts b/src/native/Environment.ts
index 611e61d9..0622e970 100644
--- a/src/native/Environment.ts
+++ b/src/native/Environment.ts
@@ -30,19 +30,31 @@ class EnvironmentClass extends Native<NativeEnvironment> {
   public readonly DIRECTORY_RECORDINGS: string = "Recordings";
 
   public getExternalStorageDir(): string {
-    return this.interface.getExternalStorageDir();
+    if (this.isAndroid) {
+      return this.interface.getExternalStorageDir();
+    }
+    return "";
   }
 
   public getPackageDataDir(): string {
-    return this.interface.getPackageDataDir();
+    if (this.isAndroid) {
+      return this.interface.getPackageDataDir();
+    }
+    return "";
   }
 
   public getPublicDir(type: string): string {
-    return this.interface.getPublicDir(type);
+    if (this.isAndroid) {
+      return this.interface.getPublicDir(type);
+    }
+    return "";
   }
 
   public getDataDir(): string {
-    return this.interface.getDataDir();
+    if (this.isAndroid) {
+      return this.interface.getDataDir();
+    }
+    return "";
   }
 }
 
diff --git a/src/native/IsolatedEval/index.ts b/src/native/IsolatedEval/index.ts
index 259bd0b7..0e0f8f3b 100644
--- a/src/native/IsolatedEval/index.ts
+++ b/src/native/IsolatedEval/index.ts
@@ -72,6 +72,10 @@ class IsolatedEval<T = any> {
     Build: Build,
     Native: Native,
     React: React,
+    setInterval: setInterval,
+    clearInterval: clearInterval,
+    clearTimeout: clearTimeout,
+    setTimeout: setTimeout,
     eval() {
       throw new IsolatedFunctionBlockError("eval()");
     },