diff --git a/app/src/main/java/com/dergoogler/plugin/ChooserPlugin.java b/app/src/main/java/com/dergoogler/plugin/ChooserPlugin.java index 377e99b6..f94e0b31 100644 --- a/app/src/main/java/com/dergoogler/plugin/ChooserPlugin.java +++ b/app/src/main/java/com/dergoogler/plugin/ChooserPlugin.java @@ -75,14 +75,14 @@ public static String getDisplayName(ContentResolver contentResolver, Uri uri) { private CallbackContext callback; private Boolean includeData; - public void chooseFile(CallbackContext callbackContext, String accept, Boolean includeData) { + public void chooseFile(CallbackContext callbackContext, String accept, Boolean includeData, boolean allowMulti) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); if (!accept.equals("*/*")) { intent.putExtra(Intent.EXTRA_MIME_TYPES, accept.split(",")); } intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMulti); intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); this.includeData = includeData; @@ -103,7 +103,7 @@ public boolean execute( ) { try { if (action.equals(ChooserPlugin.ACTION_OPEN)) { - this.chooseFile(callbackContext, args.getString(0), args.getBoolean(1)); + this.chooseFile(callbackContext, args.getString(0), args.getBoolean(1),args.getBoolean(2)); return true; } } catch (JSONException err) { diff --git a/docs/ModFS.md b/docs/ModFS.md index 01cfe3ce..761c75ce 100644 --- a/docs/ModFS.md +++ b/docs/ModFS.md @@ -2,6 +2,10 @@ **ModFS** is a core component that provides a flexible and customizable filesystem for managing modules on Android devices. It's designed to streamline module installation, updates, and removal while offering granular control over module structure. + + +## Add a local cover to your module > [!IMPORTANT] > Do not hardcode your cover path diff --git a/package.json b/package.json index c2c767a8..c52d0b69 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "reflect-metadata": "^0.2.2", "underscore": "^1.13.6", "usehooks-ts": "^3.1.0", + "uuid": "^10.0.0", "yaml": "^2.3.4" }, "devDependencies": { diff --git a/src/activitys/InstallTerminalV2Activity.tsx b/src/activitys/InstallTerminalV2Activity.tsx index 0042fb58..a0c5015a 100644 --- a/src/activitys/InstallTerminalV2Activity.tsx +++ b/src/activitys/InstallTerminalV2Activity.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Add, Remove, CodeRounded, ArrowBackIosRounded, RestartAlt } from "@mui/icons-material"; -import { Stack, Box, Slider, Button, Typography, TextField, alpha } from "@mui/material"; +import { Stack, Box, Slider, Button, Typography, TextField, alpha, LinearProgress } from "@mui/material"; import FlatList from "flatlist-react"; import { Ansi } from "@Components/Ansi"; import { useTheme } from "@Hooks/useTheme"; @@ -17,6 +17,8 @@ import { useModFS } from "@Hooks/useModFS"; import { formatString } from "@Util/stringFormat"; import { Terminal } from "@Native/Terminal"; import { Image } from "@Components/dapi/Image"; +import { Download } from "@Native/Download"; +import { v1 as uuidv1 } from "uuid"; type IntrCommand = (args: string[], options: Record, add: any) => void; @@ -82,6 +84,11 @@ const useLines = (cmds: Record) => { } }; + const setLastLine = (text: string, props?: object) => { + setLines((p) => p.slice(0, -1)); + addText(text, props); + }; + const addImage = (data: string, props?: object) => { if (typeof data === "string") { setLines((lines) => [ @@ -206,9 +213,12 @@ const useLines = (cmds: Record) => { setUseInt: setUseInt, addButton: addButton, addText: addText, + setLastLine: setLastLine, }; }; +const TMPDIR = "/data/local/tmp"; + export const InstallTerminalV2Activity = () => { const { context, extra } = useActivity(); const { theme } = useTheme(); @@ -220,7 +230,7 @@ export const InstallTerminalV2Activity = () => { const termEndRef = React.useRef(null); const [active, setActive] = React.useState(true); - const { lines, addText, addButton } = useLines({ + const { lines, addText, addButton, setLastLine } = useLines({ color: (args, _, add) => { add.addText(formatString(args[0], colors)); }, @@ -288,91 +298,128 @@ export const InstallTerminalV2Activity = () => { }); }, []); + const getInstallCLI = React.useCallback((adds?: Record) => { + switch (Shell.getRootManager()) { + case "Magisk": + return modFS("MSUINI", adds); + case "KernelSU": + return modFS("KSUINI", adds); + case "APatchSU": + return modFS("ASUINI", adds); + default: + return `exit ${Shell.M_DWL_FAILURE}`; + } + }, []); + + const [downloadProgress, setDownloadProgress] = React.useState(0); + const install = () => { const { exploreInstall, modSource, id, source, issues } = extra; if (exploreInstall) { const url = modSource[0]; - const urls = modSource; + // const urls = modSource; - const explore_install = new Terminal({ - cwd: "/data/local/tmp", - printError: settings.print_terminal_error, - }); + const modPath = `${TMPDIR}/${uuidv1()}.zip`; - explore_install.env = { - ASH_STANDALONE: "1", - MMRL: "true", - MMRL_INTR: "true", - MMRL_VER: BuildConfig.VERSION_CODE.toString(), - NAME: id, - ROOTMANAGER: Shell.getRootManager(), - ...__modFS, - }; + const dl = new Download(url, modPath); - explore_install.onLine = (line) => { - addText(line); - }; + dl.onChange = (obj) => { + switch (obj.type) { + case "downloading": + setDownloadProgress(obj.state); + setLastLine(`- Downlaoding module progress: ${obj.state}%`); + break; + case "finished": + setDownloadProgress(0); - explore_install.onExit = (code) => { - switch (code) { - case Shell.M_INS_SUCCESS: - addText(" "); - addText( - "\x1b[93mYou can press the \x1b[33;4mbutton\x1b[93;0m\x1b[93m below to \x1b[33;4mreboot\x1b[93;0m\x1b[93m your device\x1b[0m" - ); - addButton("Reboot", { - startIcon: , - onClick: rebootDevice, + const explore_install = new Terminal({ + cwd: TMPDIR, + printError: settings.print_terminal_error, }); - addText( - "\x1b[2mModules that causes issues after installing belog not to \x1b[35;4mMMRL\x1b[0;2m!\nPlease report these issues to thier support page\x1b[2m" - ); - if (issues) { - addText(`> \x1b[32mIssues: \x1b[33m${issues}\x1b[0m`); - } - if (source) { - addText(`> \x1b[32mSource: \x1b[33m${source}\x1b[0m`); - } - setActive(false); - break; - case Shell.M_INS_FAILURE: - addText(" "); - addText( - "\x1b[2mModules that causes issues after installing belog not to \x1b[35;4mMMRL\x1b[0;2m!\nPlease report these issues to thier support page\x1b[2m" + + explore_install.env = { + ASH_STANDALONE: "1", + MMRL: "true", + MMRL_INTR: "true", + MMRL_VER: BuildConfig.VERSION_CODE.toString(), + ROOTMANAGER: Shell.getRootManager(), + }; + + explore_install.onLine = (line) => { + addText(line); + }; + + explore_install.onExit = (code) => { + switch (code) { + case Shell.M_INS_SUCCESS: + addText(" "); + addText( + "\x1b[93mYou can press the \x1b[33;4mbutton\x1b[93;0m\x1b[93m below to \x1b[33;4mreboot\x1b[93;0m\x1b[93m your device\x1b[0m" + ); + addButton("Reboot", { + startIcon: , + onClick: rebootDevice, + }); + addText( + "\x1b[2mModules that causes issues after installing belog not to \x1b[35;4mMMRL\x1b[0;2m!\nPlease report these issues to thier support page\x1b[2m" + ); + if (issues) { + addText(`> \x1b[32mIssues: \x1b[33m${issues}\x1b[0m`); + } + if (source) { + addText(`> \x1b[32mSource: \x1b[33m${source}\x1b[0m`); + } + setActive(false); + break; + case Shell.M_INS_FAILURE: + addText(" "); + addText( + "\x1b[2mModules that causes issues after installing belog not to \x1b[35;4mMMRL\x1b[0;2m!\nPlease report these issues to thier support page\x1b[2m" + ); + if (issues) { + addText(`> \x1b[32mIssues: \x1b[33m${issues}\x1b[0m`); + } + if (source) { + addText(`> \x1b[32mSource: \x1b[33m${source}\x1b[0m`); + } + setActive(false); + break; + case Shell.TERM_INTR_ERR: + addText("! \x1b[31mInternal error!\x1b[0m"); + setActive(false); + break; + default: + addText("? Unknown code returned"); + setActive(false); + break; + } + }; + + explore_install.exec( + getInstallCLI({ + ZIPFILE: modPath, + }) ); - if (issues) { - addText(`> \x1b[32mIssues: \x1b[33m${issues}\x1b[0m`); - } - if (source) { - addText(`> \x1b[32mSource: \x1b[33m${source}\x1b[0m`); - } - setActive(false); - break; - case Shell.TERM_INTR_ERR: - addText("! \x1b[31mInternal error!\x1b[0m"); - setActive(false); - break; - default: - addText("? Unknown code returned"); - setActive(false); + break; } }; - explore_install.exec( - modFS("EXPLORE_INSTALL", { - URL: url, - URLS: urls, - MODID: id, - }) - ); + dl.onError = (err) => { + setDownloadProgress(0); + addText("! \x1b[31mUnable to download the module\x1b[0m"); + addText("! \x1b[31mERR: " + err + "\x1b[0m"); + setActive(false); + }; + + dl.start(); } else { const zipfile = modSource[0]; - const zipfiles = modSource; + // const zipfiles = modSource; const local_install = new Terminal({ - cwd: "/data/local/tmp", + cwd: TMPDIR, printError: settings.print_terminal_error, }); @@ -381,9 +428,7 @@ export const InstallTerminalV2Activity = () => { MMRL: "true", MMRL_INTR: "true", MMRL_VER: BuildConfig.VERSION_CODE.toString(), - NAME: id, ROOTMANAGER: Shell.getRootManager(), - ...__modFS, }; local_install.onLine = (line) => { @@ -434,9 +479,8 @@ export const InstallTerminalV2Activity = () => { }; local_install.exec( - modFS("LOCAL_INSTALL", { + getInstallCLI({ ZIPFILE: zipfile, - ZIPFILES: zipfiles, }) ); } @@ -444,9 +488,21 @@ export const InstallTerminalV2Activity = () => { const renderToolbar = () => { return ( - + {!active && } Install + {downloadProgress !== 0 && ( + + )} ); }; diff --git a/src/activitys/ModFSActivity.tsx b/src/activitys/ModFSActivity.tsx index ed23bca0..f938ab29 100644 --- a/src/activitys/ModFSActivity.tsx +++ b/src/activitys/ModFSActivity.tsx @@ -53,101 +53,35 @@ function ModFSActivity() { sectionText: "Installer", items: [ { - text: "Explore install script", - multiline: true, - maxRows: 10, - dialogDesc: ( - <> - - Check the{" "} - - ModFS documentations - {" "} - for more informations! -
- {""}, {""} and {""} can also be used, shell supported. -
- - ), - confKey: "EXPLORE_INSTALL", - }, - { - text: "Local install script", - multiline: true, - maxRows: 10, - dialogDesc: ( - <> - - Check the{" "} - - ModFS documentations - {" "} - for more informations! -
- {""} and {""} can also be used, shell supported. -
- - ), - confKey: "LOCAL_INSTALL", - }, - ], - }, - { - sectionText: "Command line interfaces", - items: [ - { - text: "Magisk install CLI", - disabled: !Shell.isMagiskSU(), - logoText: "assets/MagiskSULogo.png", - confKey: "MSUCLI", - }, - { - text: "Magisk Busybox CLI", - disabled: !Shell.isMagiskSU(), - logoText: "assets/MagiskSULogo.png", - confKey: "MSUBSU", - }, - { - text: "Magisk ResetProp CLI", + text: "Magisk Install Script", disabled: !Shell.isMagiskSU(), logoText: "assets/MagiskSULogo.png", - confKey: "MSURSP", - }, - { - text: "KernelSU install CLI", - disabled: !Shell.isKernelSU(), - logoText: "assets/KernelSULogo.png", - confKey: "KSUCLI", - }, - { - text: "KernelSU Busybox CLI", - disabled: !Shell.isKernelSU(), - logoText: "assets/KernelSULogo.png", - confKey: "KSUBSU", + // dialogDesc: ( + // <> + // + // Check the{" "} + // + // ModFS documentations + // {" "} + // for more informations! + //
+ // {""}, {""} and {""} can also be used, shell supported. + //
+ // + // ), + confKey: "MSUINI", }, { - text: "KernelSU ResetProp CLI", + text: "KernelSU Install Script", disabled: !Shell.isKernelSU(), logoText: "assets/KernelSULogo.png", - confKey: "KSURSP", + confKey: "KSUINI", }, { - text: "APatch install CLI", + text: "APatch Install Script", disabled: !Shell.isAPatchSU(), logoText: "assets/APatchSULogo.png", - confKey: "ASUCLI", - }, - { - text: "APatch Busybox CLI", - disabled: !Shell.isAPatchSU(), - logoText: "assets/APatchSULogo.png", - confKey: "ASUBSU", - }, - { - text: "APatch ResetProp CLI", - disabled: !Shell.isAPatchSU(), - logoText: "assets/APatchSULogo.png", - confKey: "ASURSP", + confKey: "ASUINI", }, ], }, @@ -262,23 +196,23 @@ function ModFSActivity() { }, ], }, - // { - // sectionText: "ModConf Standalone", - // items: [ - // { - // text: "Standalone root directory", - // confKey: "MCALONE", - // }, - // { - // text: "Standalone working directory", - // confKey: "MCALONECWD", - // }, - // { - // text: "Stadnalone meta file", - // confKey: "MCALONEMTA", - // }, - // ], - // }, + { + sectionText: "ModConf Standalone", + items: [ + { + text: "Standalone root directory", + confKey: "MCALONE", + }, + { + text: "Standalone working directory", + confKey: "MCALONECWD", + }, + { + text: "Stadnalone meta file", + confKey: "MCALONEMETA", + }, + ], + }, ], [] ); diff --git a/src/hooks/useModFS.tsx b/src/hooks/useModFS.tsx index aa14d6c4..7a5f1fd8 100644 --- a/src/hooks/useModFS.tsx +++ b/src/hooks/useModFS.tsx @@ -7,15 +7,9 @@ import { default as PModFS } from "modfs"; export interface ModFS { //cli - MSUCLI: string; - MSUBSU: string; - MSURSP: string; - KSUCLI: string; - KSUBSU: string; - KSURSP: string; - ASUCLI: string; - ASUBSU: string; - ASURSP: string; + MSUINI: string; + KSUINI: string; + ASUINI: string; // default paths ADB: string; @@ -58,15 +52,9 @@ export interface ModFS { export const INITIAL_MOD_CONF: ModFS = { //cli - MSUCLI: "/system/bin/magisk", - MSUBSU: "/magisk/busybox", - MSURSP: "/system/bin/resetprop", - KSUCLI: "/ksu/bin/ksud", - KSUBSU: "/ksu/bin/busybox", - KSURSP: "/ksu/bin/resetprop", - ASUCLI: "/ap/bin/apd", - ASUBSU: "/ap/bin/busybox", - ASURSP: "/ap/bin/resetprop", + MSUINI: '/magisk/magisk32 --install-module ""', + KSUINI: '/ksu/bin/ksud module install ""', + ASUINI: '/ap/bin/apd module install ""', // default paths ADB: "/data/adb", @@ -104,7 +92,7 @@ export const INITIAL_MOD_CONF: ModFS = { // Installer EXPLORE_INSTALL: 'mmrl install -y ""', - LOCAL_INSTALL: "mmrl install local -y ", + LOCAL_INSTALL: 'mmrl install local -y ', }; export interface ModConfContext { @@ -128,7 +116,7 @@ export const useModFS = () => { }; export const ModFSProvider = (props: React.PropsWithChildren) => { - const [modFS, setModFS] = useNativeFileStorage("/data/adb/mmrl/modfs.v3.json", INITIAL_MOD_CONF, { loader: "json" }); + const [modFS, setModFS] = useNativeFileStorage("/data/adb/mmrl/modfs.v4.json", INITIAL_MOD_CONF, { loader: "json" }); const pmodFS = React.useMemo(() => new PModFS(defaultComposer(INITIAL_MOD_CONF, modFS)), [modFS]); diff --git a/src/native/Chooser.ts b/src/native/Chooser.ts index afc9bbd6..675f0537 100644 --- a/src/native/Chooser.ts +++ b/src/native/Chooser.ts @@ -4,13 +4,14 @@ type SuccessCallback = (file: string[] | "RESULT_CANCELED") => void; type ErrorCallback = ((code: number) => void) | null; interface ChooserNative { - getFile(type: string, successCallback: SuccessCallback, errorCallback: ErrorCallback): void; + getFile(type: string, allowMulti: boolean, successCallback: SuccessCallback, errorCallback: ErrorCallback): void; } class Chooser extends Native { public type: string; private _onChose: SuccessCallback | undefined; private _onError: ErrorCallback = null; + private _allowMultiChoose = false; public constructor(type: string) { super(window.__chooser__); @@ -20,6 +21,11 @@ class Chooser extends Native { this.type = type; } + public set allowMultiChoose(value: boolean) { + if (typeof value !== "boolean") return; + this._allowMultiChoose = value; + } + public set onChose(func: SuccessCallback) { this._onChose = func; } @@ -36,7 +42,7 @@ class Chooser extends Native { if (this.isAndroid) { if (typeof this._onChose !== "function") throw new TypeError("Chooser 'onChose' is not a function"); - this.interface.getFile(this.type, this._onChose, this._onError); + this.interface.getFile(this.type, this._allowMultiChoose, this._onChose, this._onError); } } } diff --git a/src/util/licenses.json b/src/util/licenses.json index 5d66e9a1..77710ba7 100644 --- a/src/util/licenses.json +++ b/src/util/licenses.json @@ -356,6 +356,14 @@ "version": "3.1.0", "source": "https://www.npmjs.com/package/usehooks-ts" }, + { + "name": "uuid", + "author": null, + "license": "MIT", + "description": "RFC9562 UUIDs", + "version": "10.0.0", + "source": "https://www.npmjs.com/package/uuid" + }, { "name": "yaml", "author": null, diff --git a/web/cordova/plugins/chooser.js b/web/cordova/plugins/chooser.js index 0c0d8b70..a27cf120 100644 --- a/web/cordova/plugins/chooser.js +++ b/web/cordova/plugins/chooser.js @@ -1,10 +1,18 @@ cordova.define("com.dergoogler.plugin.chooser", function (require, exports, module) { module.exports = { - getFile: function (accept, successCallback, failureCallback) { - cordova.exec(successCallback, failureCallback, "Chooser", "getFile", [(typeof accept === "string" ? accept.toLowerCase().replace(/\s/g, "") : undefined) || "*/*", true]); + getFile: function (accept, allowMulti, successCallback, failureCallback) { + cordova.exec(successCallback, failureCallback, "Chooser", "getFile", [ + (typeof accept === "string" ? accept.toLowerCase().replace(/\s/g, "") : undefined) || "*/*", + true, + allowMulti, + ]); }, - getFileMetadata: function (accept, successCallback, failureCallback) { - cordova.exec(successCallback, failureCallback, "Chooser", "getFile", [(typeof accept === "string" ? accept.toLowerCase().replace(/\s/g, "") : undefined) || "*/*", false]); + getFileMetadata: function (accept, allowMulti, successCallback, failureCallback) { + cordova.exec(successCallback, failureCallback, "Chooser", "getFile", [ + (typeof accept === "string" ? accept.toLowerCase().replace(/\s/g, "") : undefined) || "*/*", + false, + allowMulti, + ]); }, }; });