[UI] Overall overhaul

This commit is contained in:
Beef 2023-04-15 02:57:03 +01:00
parent 474aad2a47
commit 32b3a0295c
9 changed files with 81 additions and 78 deletions

View file

@ -0,0 +1,38 @@
import { Indexable } from "@types";
import { ReactNative as RN } from "@metro/common";
import { useProxy } from "@lib/storage";
import { HelpMessage, ErrorBoundary, Search } from "@ui/components";
import { CardWrapper } from "@ui/settings/components/Card";
import settings from "@lib/settings";
interface AddonPageProps<T> {
items: Indexable<T & { id: string }>;
safeModeMessage: string;
safeModeExtras?: JSX.Element | JSX.Element[];
card: React.ComponentType<CardWrapper<T>>;
}
export default function AddonPage<T>({ items, safeModeMessage, safeModeExtras, card: CardComponent }: AddonPageProps<T>) {
useProxy(settings)
useProxy(items);
const [search, setSearch] = React.useState("");
return (
<ErrorBoundary>
<RN.ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
{settings.safeMode?.enabled && <RN.View style={{ marginBottom: 10 }}>
<HelpMessage messageType={0}>{safeModeMessage}</HelpMessage>
{safeModeExtras}
</RN.View>}
<Search
style={{ marginBottom: 10 }}
onChangeText={(v: string) => setSearch(v.toLowerCase())}
placeholder="Search"
/>
{/* TODO: When I am more awake, implement better searching than just by ID */}
{/* TODO: Also when I am more awake, make the search bar not scroll with the cards */}
{Object.values(items).filter(i => i.id?.toLowerCase().includes(search)).map((i, id) => <CardComponent item={i} index={id} />)}
</RN.ScrollView>
</ErrorBoundary>
)
}

View file

@ -1,5 +1,5 @@
import { Asset } from "@types"; import { Asset } from "@types";
import { ReactNative as RN, stylesheet, clipboard } from "@metro/common"; import { ReactNative as RN, clipboard } from "@metro/common";
import { showToast } from "@ui/toasts"; import { showToast } from "@ui/toasts";
import { getAssetIDByName } from "@ui/assets"; import { getAssetIDByName } from "@ui/assets";
import { Forms } from "@ui/components"; import { Forms } from "@ui/components";
@ -10,18 +10,11 @@ interface AssetDisplayProps {
const { FormRow } = Forms; const { FormRow } = Forms;
const styles = stylesheet.createThemedStyleSheet({
asset: {
width: 32,
height: 32,
}
});
export default function AssetDisplay({ asset }: AssetDisplayProps) { export default function AssetDisplay({ asset }: AssetDisplayProps) {
return ( return (
<FormRow <FormRow
label={`${asset.name} - ${asset.id}`} label={`${asset.name} - ${asset.id}`}
trailing={<RN.Image source={asset.id} style={styles.asset} />} trailing={<RN.Image source={asset.id} style={{ width: 32, height: 32 }} />}
onPress={() => { onPress={() => {
clipboard.setString(asset.name); clipboard.setString(asset.name);
showToast("Copied asset name to clipboard.", getAssetIDByName("toast_copy_link")); showToast("Copied asset name to clipboard.", getAssetIDByName("toast_copy_link"));

View file

@ -13,8 +13,6 @@ const styles = stylesheet.createThemedStyleSheet({
card: { card: {
backgroundColor: semanticColors?.BACKGROUND_SECONDARY, backgroundColor: semanticColors?.BACKGROUND_SECONDARY,
borderRadius: 5, borderRadius: 5,
marginHorizontal: 10,
marginBottom: 10,
}, },
header: { header: {
padding: 0, padding: 0,
@ -44,6 +42,11 @@ interface OverflowAction extends Action {
isDestructive?: boolean; isDestructive?: boolean;
} }
export interface CardWrapper<T> {
item: T;
index: number;
}
interface CardProps { interface CardProps {
index?: number; index?: number;
headerLabel: string | React.ComponentType; headerLabel: string | React.ComponentType;
@ -61,7 +64,7 @@ export default function Card(props: CardProps) {
let pressableState = props.toggleValue ?? false; let pressableState = props.toggleValue ?? false;
return ( return (
<RN.View style={[styles.card, { marginTop: props.index === 0 ? 10 : 0 }]}> <RN.View style={[styles.card, { marginTop: props.index !== 0 ? 10 : 0 }]}>
<FormRow <FormRow
style={styles.header} style={styles.header}
label={props.headerLabel} label={props.headerLabel}

View file

@ -5,20 +5,15 @@ import { getAssetIDByName } from "@ui/assets";
import { showToast } from "@ui/toasts"; import { showToast } from "@ui/toasts";
import { showConfirmationAlert } from "@ui/alerts"; import { showConfirmationAlert } from "@ui/alerts";
import { removePlugin, startPlugin, stopPlugin, getSettings, fetchPlugin } from "@lib/plugins"; import { removePlugin, startPlugin, stopPlugin, getSettings, fetchPlugin } from "@lib/plugins";
import Card from "@ui/settings/components/Card"; import Card, { CardWrapper } from "@ui/settings/components/Card";
interface PluginCardProps { async function stopThenStart(plugin: Plugin, callback: Function) {
plugin: Plugin;
index: number;
}
async function stopThenStart(plugin: Plugin, func: Function) {
if (plugin.enabled) stopPlugin(plugin.id, false); if (plugin.enabled) stopPlugin(plugin.id, false);
func(); callback();
if (plugin.enabled) await startPlugin(plugin.id); if (plugin.enabled) await startPlugin(plugin.id);
} }
export default function PluginCard({ plugin, index }: PluginCardProps) { export default function PluginCard({ item: plugin, index }: CardWrapper<Plugin>) {
const settings = getSettings(plugin.id); const settings = getSettings(plugin.id);
const navigation = NavigationNative.useNavigation(); const navigation = NavigationNative.useNavigation();
const [removed, setRemoved] = React.useState(false); const [removed, setRemoved] = React.useState(false);

View file

@ -12,7 +12,7 @@ export default function SettingsSection() {
return ( return (
<ErrorBoundary> <ErrorBoundary>
<FormSection key="Vendetta" title="Vendetta"> <FormSection key="Vendetta" title={`Vendetta${settings.safeMode?.enabled ? " (Safe Mode)" : ""}`}>
<FormRow <FormRow
label="General" label="General"
leading={<FormRow.Icon source={getAssetIDByName("settings")} />} leading={<FormRow.Icon source={getAssetIDByName("settings")} />}

View file

@ -7,19 +7,14 @@ import { getAssetIDByName } from "@ui/assets";
import { showConfirmationAlert } from "@ui/alerts"; import { showConfirmationAlert } from "@ui/alerts";
import { showToast } from "@ui/toasts"; import { showToast } from "@ui/toasts";
import settings from "@lib/settings"; import settings from "@lib/settings";
import Card from "@ui/settings/components/Card"; import Card, { CardWrapper } from "@ui/settings/components/Card";
interface ThemeCardProps {
theme: Theme;
index: number;
}
async function selectAndReload(value: boolean, id: string) { async function selectAndReload(value: boolean, id: string) {
await selectTheme(value ? id : "default"); await selectTheme(value ? id : "default");
BundleUpdaterManager.reload(); BundleUpdaterManager.reload();
} }
export default function ThemeCard({ theme, index }: ThemeCardProps) { export default function ThemeCard({ item: theme, index }: CardWrapper<Theme>) {
useProxy(settings); useProxy(settings);
const [removed, setRemoved] = React.useState(false); const [removed, setRemoved] = React.useState(false);

View file

@ -6,7 +6,6 @@ import AssetDisplay from "@ui/settings/components/AssetDisplay";
const { FormDivider } = Forms; const { FormDivider } = Forms;
export default function AssetBrowser() { export default function AssetBrowser() {
const [search, setSearch] = React.useState(""); const [search, setSearch] = React.useState("");
@ -14,17 +13,14 @@ export default function AssetBrowser() {
<ErrorBoundary> <ErrorBoundary>
<RN.View style={{ flex: 1 }}> <RN.View style={{ flex: 1 }}>
<Search <Search
style={{ margin: 10 }}
onChangeText={(v: string) => setSearch(v)} onChangeText={(v: string) => setSearch(v)}
placeholder="Search" placeholder="Search"
/> />
<RN.FlatList <RN.FlatList
data={Object.values(all).filter(a => a.name.includes(search) || a.id.toString() === search)} data={Object.values(all).filter(a => a.name.includes(search) || a.id.toString() === search)}
renderItem={({ item }) => ( renderItem={({ item }) => <AssetDisplay asset={item} />}
<> ItemSeparatorComponent={FormDivider}
<AssetDisplay asset={item} />
<FormDivider />
</>
)}
keyExtractor={item => item.name} keyExtractor={item => item.name}
/> />
</RN.View> </RN.View>

View file

@ -1,27 +1,18 @@
import { ReactNative as RN } from "@metro/common"; import { Plugin } from "@types";
import { useProxy } from "@lib/storage"; import { useProxy } from "@lib/storage";
import { plugins } from "@lib/plugins"; import { plugins } from "@lib/plugins";
import { HelpMessage } from "@ui/components";
import settings from "@lib/settings"; import settings from "@lib/settings";
import AddonPage from "@ui/settings/components/AddonPage";
import PluginCard from "@ui/settings/components/PluginCard"; import PluginCard from "@ui/settings/components/PluginCard";
import ErrorBoundary from "@ui/components/ErrorBoundary";
export default function Plugins() { export default function Plugins() {
useProxy(settings) useProxy(settings)
useProxy(plugins);
return ( return (
<ErrorBoundary> <AddonPage<Plugin>
<RN.View style={{ flex: 1 }}> items={plugins}
{settings.safeMode?.enabled && <RN.View style={{ margin: 10 }}> safeModeMessage="You are in Safe Mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page."
<HelpMessage messageType={0}>You are in Safe Mode, so plugins cannot be loaded. Disable any misbehaving plugins, then return to Normal Mode from the General settings page. </HelpMessage> card={PluginCard}
</RN.View>}
<RN.FlatList
data={Object.values(plugins)}
renderItem={({ item, index }) => <PluginCard plugin={item} index={index} />}
keyExtractor={item => item.id}
/> />
</RN.View>
</ErrorBoundary>
) )
} }

View file

@ -1,21 +1,19 @@
import { ButtonColors } from "@types"; import { Theme, ButtonColors } from "@types";
import { ReactNative as RN } from "@metro/common";
import { themes } from "@lib/themes";
import { useProxy } from "@lib/storage"; import { useProxy } from "@lib/storage";
import { ErrorBoundary, Button, HelpMessage } from "@ui/components"; import { themes } from "@lib/themes";
import { Button } from "@ui/components";
import settings from "@lib/settings"; import settings from "@lib/settings";
import AddonPage from "@ui/settings/components/AddonPage";
import ThemeCard from "@ui/settings/components/ThemeCard"; import ThemeCard from "@ui/settings/components/ThemeCard";
export default function Themes() { export default function Themes() {
useProxy(settings); useProxy(settings);
useProxy(themes);
return ( return (
<ErrorBoundary> <AddonPage<Theme>
<RN.View style={{ flex: 1 }}> items={themes}
{settings.safeMode?.enabled && <RN.View style={{ margin: 10 }}> safeModeMessage={`You are in Safe Mode, meaning themes have been temporarily disabled.${settings.safeMode?.currentThemeId ? " If a theme appears to be causing the issue, you can press below to disable it persistently." : ""}`}
<HelpMessage messageType={0}>You are in Safe Mode, meaning themes have been temporarily disabled.{settings.safeMode?.currentThemeId && " If a theme appears to be causing the issue, you can press below to disable it persistently."}</HelpMessage> safeModeExtras={settings.safeMode?.currentThemeId ? <Button
{settings.safeMode?.currentThemeId && <Button
text="Disable Theme" text="Disable Theme"
color={ButtonColors.BRAND} color={ButtonColors.BRAND}
size="small" size="small"
@ -23,14 +21,8 @@ export default function Themes() {
delete settings.safeMode?.currentThemeId; delete settings.safeMode?.currentThemeId;
}} }}
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
/>} /> : undefined}
</RN.View>} card={ThemeCard}
<RN.FlatList
data={Object.values(themes)}
renderItem={({ item, index }) => <ThemeCard theme={item} index={index} />}
keyExtractor={item => item.id}
/> />
</RN.View>
</ErrorBoundary>
) )
} }