diff --git a/Website/src/activitys/DescriptonActivity.tsx b/Website/src/activitys/DescriptonActivity.tsx index f7c3ff6d..30d83817 100644 --- a/Website/src/activitys/DescriptonActivity.tsx +++ b/Website/src/activitys/DescriptonActivity.tsx @@ -21,7 +21,7 @@ import VerifiedIcon from "@mui/icons-material/Verified"; import CommentIcon from "@mui/icons-material/Comment"; import FileDownloadIcon from "@mui/icons-material/FileDownload"; import DeviceUnknownIcon from "@mui/icons-material/DeviceUnknown"; -import ModuleSpecsActivity from "./ModuleSpecsActivity"; +import ModuleFilterConfActivity from "./ModuleFilterConfActivity"; import { parseAndroidVersion } from "@Util/parseAndroidVersion"; import { useTheme } from "@Hooks/useTheme"; import { StyledIconButtonWithText } from "@Components/StyledIconButton"; @@ -248,7 +248,7 @@ function DescriptonActivity() { { context.pushPage({ - component: ModuleSpecsActivity, + component: ModuleFilterConfActivity, key: "comments_" + prop_url.id, extra: { prop_url: prop_url, diff --git a/Website/src/activitys/ModuleFilterConfActivity.tsx b/Website/src/activitys/ModuleFilterConfActivity.tsx new file mode 100644 index 00000000..9462e0b1 --- /dev/null +++ b/Website/src/activitys/ModuleFilterConfActivity.tsx @@ -0,0 +1,127 @@ +import { List, ListItem } from "@mui/material"; +import { Toolbar } from "@Components/onsenui/Toolbar"; +import { Page } from "@Components/onsenui/Page"; +import { useStrings } from "@Hooks/useStrings"; +import { useActivity } from "@Hooks/useActivity"; +import TextField from "@mui/material/TextField"; +import Card from "@mui/material/Card"; +import Stack from "@mui/material/Stack"; +import Button from "@mui/material/Button"; +import DeleteIcon from "@mui/icons-material/Delete"; +import Alert from "@mui/material/Alert"; +import IconButton from "@mui/material/IconButton"; +import { useSettings } from "@Hooks/useSettings"; +import { StyledListItemText } from "@Components/StyledListItemText"; +import React from "react"; +import { os } from "@Native/Os"; +import { useConfirm } from "material-ui-confirm"; + +interface Extra { + prop_url: ModuleProps; +} + +function ModuleFilterConfActivity() { + const { context, extra } = useActivity(); + const { strings } = useStrings(); + const { settings, setSettings } = useSettings(); + const confirm = useConfirm(); + + const [textInput, setTextInput] = React.useState(""); + + const disallowed = /(lost\+found|lost\\\+found|lost\\\\\+found)/; + + const renderToolbar = () => { + return ( + + + + + Configure + + ); + }; + + const addFilter = () => { + if (!disallowed.test(textInput)) { + if (textInput) { + setSettings( + "mod_filt", + (prev) => { + if (prev.some((elem) => elem !== textInput)) { + return [textInput, ...prev]; + } else { + return prev; + } + }, + (state) => setTextInput("") + ); + } else { + os.toast("Please enter a valid filter", Toast.LENGTH_SHORT); + } + } else { + os.toast("Disallowed filter", Toast.LENGTH_SHORT); + } + }; + + return ( + + + + + + ) => { + setTextInput(event.target.value); + }} + /> + + + + + lost\+found is a KernelSU filter to prevent empty modules + + + + {settings.mod_filt.map((rule) => ( + { + confirm({ + title: "Delete filter?", + confirmationText: "Sure", + description: "Are you sure that you want delete this filter?", + }).then(() => { + setSettings( + "mod_filt", + (prev) => prev.filter((item) => item !== rule), + (state) => os.toast("Filter removed", Toast.LENGTH_SHORT) + ); + }); + }} + edge="end" + aria-label="delete" + > + + + ) + } + > + + + ))} + + + + + + ); +} + +export default ModuleFilterConfActivity; diff --git a/Website/src/activitys/ModuleSpecsActivity.tsx b/Website/src/activitys/ModuleSpecsActivity.tsx deleted file mode 100644 index bcf5383a..00000000 --- a/Website/src/activitys/ModuleSpecsActivity.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Divider, List, ListItem, ListSubheader } from "@mui/material"; -import { Toolbar } from "@Components/onsenui/Toolbar"; -import ArrowBackIcon from "@mui/icons-material/ArrowBack"; -import { Page } from "@Components/onsenui/Page"; -import { Magisk } from "@Native/Magisk"; -import { useStrings } from "@Hooks/useStrings"; -import { useActivity } from "@Hooks/useActivity"; -import { StyledListItemText } from "@Components/StyledListItemText"; -import { parseAndroidVersion } from "@Util/parseAndroidVersion"; - -interface Extra { - prop_url: ModuleProps; -} - -function ModuleSpecsActivity() { - const { context, extra } = useActivity(); - const { strings } = useStrings(); - - const { prop_url } = extra; - - const renderToolbar = () => { - return ( - - - - - Module Requirements - - ); - }; - - return ( - - - ({ bgcolor: theme.palette.background.default })}>Default}> - - - - - - - - - - - - - - - - ({ bgcolor: theme.palette.background.default })}>Minimum}> - - - - - - - - - - - - ({ bgcolor: theme.palette.background.default })}>Recommended}> - - - - - - - - - ); -} - -export default ModuleSpecsActivity; diff --git a/Website/src/activitys/SettingsActivity.tsx b/Website/src/activitys/SettingsActivity.tsx index 66f8e0b9..06d36272 100644 --- a/Website/src/activitys/SettingsActivity.tsx +++ b/Website/src/activitys/SettingsActivity.tsx @@ -15,11 +15,14 @@ import { Android12Switch } from "@Components/Android12Switch"; import { useTheme } from "@Hooks/useTheme"; import { useRepos } from "@Hooks/useRepos"; import { Shell } from "@Native/Shell"; +import ModuleFilterConfActivity from "./ModuleFilterConfActivity"; +import { DialogEditTextListItem } from "@Components/DialogEditTextListItem"; function SettingsActivity() { const { context } = useActivity(); const { strings } = useStrings(); const { setRepos } = useRepos(); + const { patchSettings } = useSettings(); const { theme } = useTheme(); @@ -79,6 +82,33 @@ function SettingsActivity() { }} /> + {/* {os.isAndroid && ( */} + { + context.pushPage({ + component: ModuleFilterConfActivity, + key: "", + extra: {}, + }); + }} + > + + + + { + if (value) { + setSettings("def_mod_path", value); + } + }} + > + + + {/* )} */} {os.isAndroid && ( ({ bgcolor: theme.palette.background.default })}>{"Storage"}}> - {" "} { setRepos([]); }} > - + + {" "} + { + patchSettings(); + }} + > + diff --git a/Website/src/activitys/fragments/DeviceModuleFragment.tsx b/Website/src/activitys/fragments/DeviceModuleFragment.tsx index 9093980c..2787c04f 100644 --- a/Website/src/activitys/fragments/DeviceModuleFragment.tsx +++ b/Website/src/activitys/fragments/DeviceModuleFragment.tsx @@ -1,13 +1,7 @@ import DeviceModule from "@Components/DeviceModule"; import { SuFile } from "@Native/SuFile"; -import { StyledCard } from "@Components/StyledCard"; -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import { os } from "@Native/Os"; import React from "react"; import { useActivity } from "@Hooks/useActivity"; -import TerminalActivity from "@Activitys/TerminalActivity"; import { useSettings } from "@Hooks/useSettings"; import { Page } from "@Components/onsenui/Page"; @@ -17,8 +11,22 @@ const DeviceModuleFragment = () => { const [modules, setModules] = React.useState([]); React.useEffect(() => { - setModules(SuFile.list("/data/adb/modules").split(",")); - }, []); + const dir = SuFile.list(settings.def_mod_path).split(","); + + const regex = settings.mod_filt.map(function (re) { + return new RegExp("\\b" + re + "\\b", "i"); + }); + + setModules( + dir.filter(function (t) { + return ( + regex.filter(function (re) { + return re.test(t); + }).length === 0 + ); + }) + ); + }, [settings.def_mod_path, settings.mod_filt]); return ( diff --git a/Website/src/components/DeviceModule.tsx b/Website/src/components/DeviceModule.tsx index c7b28ab2..424eb55d 100644 --- a/Website/src/components/DeviceModule.tsx +++ b/Website/src/components/DeviceModule.tsx @@ -7,10 +7,10 @@ import { Box, Card, Divider, Stack, Typography } from "@mui/material"; import SettingsIcon from "@mui/icons-material/Settings"; import { useActivity } from "@Hooks/useActivity"; import { ConfigureActivity } from "@Activitys/ConfigureActivity"; -import { StyledCard } from "./StyledCard"; import { StyledIconButton } from "./StyledIconButton"; import { useLog } from "@Hooks/native/useLog"; import { Properties } from "properties-file"; +import { useSettings } from "@Hooks/useSettings"; interface Props { module: string; @@ -18,6 +18,7 @@ interface Props { const DeviceModule = (props: Props) => { const { strings } = useStrings(); + const { settings } = useSettings(); const { context, extra } = useActivity(); const [moduleProps, setModuleProps] = React.useState>({}); const [isEnabled, setIsEnabled] = React.useState(true); @@ -28,18 +29,18 @@ const DeviceModule = (props: Props) => { const module = props.module; React.useEffect(() => { - const readProps = SuFile.read(`/data/adb/modules/${module}/module.prop`); + const readProps = SuFile.read(`${settings.def_mod_path}/${module}/module.prop`); setModuleProps(new Properties(readProps).toObject()); }, []); React.useEffect(() => { - const remove = new SuFile(`/data/adb/modules/${module}/remove`); + const remove = new SuFile(`${settings.def_mod_path}/${module}/remove`); setIsSwitchDisabled(remove.exist()); }, [isSwitchDisabled]); React.useEffect(() => { - const disable = new SuFile(`/data/adb/modules/${module}/disable`); + const disable = new SuFile(`${settings.def_mod_path}/${module}/disable`); setIsEnabled(!disable.exist()); }, [isEnabled]); @@ -74,7 +75,7 @@ const DeviceModule = (props: Props) => { disabled={isSwitchDisabled} onChange={(e) => { const checked = e.target.checked; - const disable = new SuFile(`/data/adb/modules/${module}/disable`); + const disable = new SuFile(`${settings.def_mod_path}/${module}/disable`); if (checked) { if (disable.exist()) { @@ -124,7 +125,7 @@ const DeviceModule = (props: Props) => { { - const remove = new SuFile(`/data/adb/modules/${module}/remove`); + const remove = new SuFile(`${settings.def_mod_path}/${module}/remove`); if (remove.exist()) { if (remove.delete()) { setIsSwitchDisabled(false); @@ -143,7 +144,7 @@ const DeviceModule = (props: Props) => { { - const file = new SuFile(`/data/adb/modules/${module}/remove`); + const file = new SuFile(`${settings.def_mod_path}/${module}/remove`); if (file.create()) { setIsSwitchDisabled(true); } else { diff --git a/Website/src/components/DialogEditTextListItem.tsx b/Website/src/components/DialogEditTextListItem.tsx new file mode 100644 index 00000000..466598b1 --- /dev/null +++ b/Website/src/components/DialogEditTextListItem.tsx @@ -0,0 +1,64 @@ +import { useStrings } from "@Hooks/useStrings"; +import Button from "@mui/material/Button"; +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogTitle from "@mui/material/DialogTitle"; +import ListItemButton from "@mui/material/ListItemButton"; +import TextField from "@mui/material/TextField"; +import React from "react"; + +interface DialogEditTextListItemProps extends React.PropsWithChildren { + inputLabel: string; + title: string; + initialValue: string; + description?: string; + onSuccess: (value: string) => void; +} + +export const DialogEditTextListItem = (props: DialogEditTextListItemProps) => { + const { strings } = useStrings(); + + const [textInput, setTextInput] = React.useState(props.initialValue); + const [open, setOpen] = React.useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const handleRepoLinkChange = (event: React.ChangeEvent) => { + setTextInput(event.target.value); + }; + + return ( + <> + {props.children} + + {props.title} + + {props.description && {props.description}} + + + + + + + + + + ); +}; diff --git a/Website/src/hooks/useSettings.tsx b/Website/src/hooks/useSettings.tsx index 2fd7d38d..4f9744fb 100644 --- a/Website/src/hooks/useSettings.tsx +++ b/Website/src/hooks/useSettings.tsx @@ -1,7 +1,7 @@ import React, { createContext, useContext } from "react"; import { colors as kolors } from "@mui/material"; import { defaultComposer } from "default-composer"; -import { useNativeStorage } from "./useNativeStorage"; +import { SetValue, useNativeStorage } from "./useNativeStorage"; import { languages_map } from "../locales/languages"; import { os } from "@Native/Os"; import { SetStateAction } from "./useStateCallback"; @@ -144,6 +144,10 @@ export interface StorageDeclaration { __experimental_local_install: boolean; repos: StoredRepo[]; test: any; + + // Android only + def_mod_path: string; + mod_filt: string[]; } export const INITIAL_SETTINGS: StorageDeclaration = { @@ -157,9 +161,14 @@ export const INITIAL_SETTINGS: StorageDeclaration = { __experimental_local_install: false, repos: [], test: [], + + // Android only + def_mod_path: "/data/adb/modules", + mod_filt: ["lost\\+found"], }; export interface Context { + patchSettings: () => void; settings: StorageDeclaration; setSettings( key: K, @@ -169,6 +178,7 @@ export interface Context { } export const SettingsContext = createContext({ + patchSettings: () => {}, settings: INITIAL_SETTINGS, setSettings( key: K, @@ -187,6 +197,9 @@ export const SettingsProvider = (props: React.PropsWithChildren) => { return ( { + setSettings(defaultComposer(INITIAL_SETTINGS, settings)); + }, settings: defaultComposer(INITIAL_SETTINGS, settings), setSettings: (name, state, callback) => { setSettings(