Skip to content

Commit

Permalink
feat: Add User Input Support for Plugin Configurations (#1273)
Browse files Browse the repository at this point in the history
* Added plugin settings

* Get plugin settings from plugin.
  • Loading branch information
NilSilva authored Oct 30, 2024
1 parent 0a170f1 commit 0fad9b9
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/hooks/persisted/usePlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export default function usePlugins() {
const actualPlugin: PluginItem = {
...plugin,
version: _plg.version,
hasSettings: !!_plg.pluginSettings,
};
// safe
if (!installedPlugins.some(plg => plg.id === plugin.id)) {
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface PluginItem {
customJS?: string;
customCSS?: string;
hasUpdate?: boolean;
hasSettings?: boolean;
}

export interface ImageRequestInit {
Expand All @@ -68,6 +69,7 @@ export interface ImageRequestInit {
export interface Plugin extends PluginItem {
imageRequestInit?: ImageRequestInit;
filters?: Filters;
pluginSettings: any;
popularNovels: (
pageNo: number,
options?: PopularNovelsOptions<Filters>,
Expand Down
32 changes: 32 additions & 0 deletions src/screens/browse/components/BrowseTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import Animated, {
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import { Portal } from 'react-native-paper';
import SourceSettingsModal from './Modals/SourceSettings';
import { useBoolean } from '@hooks';
import { getPlugin } from '@plugins/pluginManager';

interface AvailableTabProps {
searchText: string;
Expand All @@ -50,6 +54,11 @@ export const InstalledTab = memo(
updatePlugin,
} = usePlugins();
const { showMyAnimeList, showAniList } = useBrowseSettings();
const settingsModal = useBoolean();
const [selectedPluginId, setSelectedPluginId] = useState<string>('');

const pluginSettings = getPlugin(selectedPluginId)?.pluginSettings;

const navigateToSource = useCallback(
(plugin: PluginItem, showLatestNovels?: boolean) => {
navigation.navigate('SourceScreen', {
Expand Down Expand Up @@ -159,6 +168,18 @@ export const InstalledTab = memo(
</View>
</View>
<View style={{ flex: 1 }} />
{item.hasSettings ? (
<IconButtonV2
name="cog-outline"
size={22}
color={theme.primary}
onPress={() => {
setSelectedPluginId(item.id);
settingsModal.setTrue();
}}
theme={theme}
/>
) : null}
{item.hasUpdate || __DEV__ ? (
<IconButtonV2
name="download-outline"
Expand Down Expand Up @@ -242,6 +263,17 @@ export const InstalledTab = memo(
>
{getString('browseScreen.installedPlugins')}
</Text>

<Portal>
<SourceSettingsModal
visible={settingsModal.value}
onDismiss={settingsModal.setFalse}
title={getString('browseScreen.settings.title')}
description={getString('browseScreen.settings.description')}
pluginId={selectedPluginId}
pluginSettings={pluginSettings}
/>
</Portal>
</>
}
/>
Expand Down
169 changes: 169 additions & 0 deletions src/screens/browse/components/Modals/SourceSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Modal, overlay, TextInput } from 'react-native-paper';
import { Button } from '@components/index';
import { useTheme } from '@hooks/persisted';
import { getString } from '@strings/translations';
import { Storage } from '@plugins/helpers/storage';

interface PluginSetting {
value: string;
label: string;
}

interface PluginSettings {
[key: string]: PluginSetting;
}

interface SourceSettingsModal {
visible: boolean;
onDismiss: () => void;
title: string;
description?: string;
pluginId: string;
pluginSettings?: PluginSettings;
}

const SourceSettingsModal: React.FC<SourceSettingsModal> = ({
onDismiss,
visible,
title,
description,
pluginId,
pluginSettings,
}) => {
const theme = useTheme();

const [formValues, setFormValues] = useState<{ [key: string]: string }>({});

useEffect(() => {
if (pluginSettings) {
const storage = new Storage(pluginId);

const loadFormValues = async () => {
const loadedValues = await Promise.all(
Object.keys(pluginSettings).map(async key => {
const storedValue = await storage.get(key);
return {
key,
value:
storedValue !== null ? storedValue : pluginSettings[key].value,
};
}),
);

const initialFormValues = Object.fromEntries(
loadedValues.map(({ key, value }) => [key, value]),
);

setFormValues(initialFormValues);
};

loadFormValues();
}
}, [pluginSettings, pluginId]);

const handleChange = (key: string, value: string) => {
setFormValues(prevValues => ({
...prevValues,
[key]: value,
}));
};

const handleSave = () => {
const storage = new Storage(pluginId);
Object.entries(formValues).forEach(([key, value]) => {
storage.set(key, value);
});
onDismiss();
};

if (!pluginSettings || Object.keys(pluginSettings).length === 0) {
return (
<Modal
visible={visible}
onDismiss={onDismiss}
contentContainerStyle={[
styles.modalContainer,
{ backgroundColor: overlay(2, theme.surface) },
]}
>
<Text style={[styles.modalTitle, { color: theme.onSurface }]}>
{title}
</Text>
<Text style={[{ color: theme.onSurfaceVariant }]}>
{description || 'No settings available.'}
</Text>
</Modal>
);
}

return (
<Modal
visible={visible}
onDismiss={onDismiss}
contentContainerStyle={[
styles.modalContainer,
{ backgroundColor: overlay(2, theme.surface) },
]}
>
<Text style={[styles.modalTitle, { color: theme.onSurface }]}>
{title}
</Text>
<Text style={[{ color: theme.onSurfaceVariant }]}>{description}</Text>

{Object.entries(pluginSettings).map(([key, setting]) => (
<TextInput
key={key}
mode="outlined"
label={setting.label}
value={formValues[key] || ''}
onChangeText={value => handleChange(key, value)}
placeholder={`Enter ${setting.label}`}
placeholderTextColor={theme.onSurfaceDisabled}
underlineColor={theme.outline}
style={[{ color: theme.onSurface }, styles.textInput]}
theme={{ colors: { ...theme } }}
/>
))}

<View style={styles.customCSSButtons}>
<Button
onPress={handleSave}
style={styles.button}
title={getString('common.save')}
mode="contained"
/>
</View>
</Modal>
);
};

export default SourceSettingsModal;

const styles = StyleSheet.create({
modalContainer: {
margin: 30,
padding: 24,
borderRadius: 28,
},
textInput: {
height: 50,
borderRadius: 14,
marginTop: 16,
marginBottom: 8,
fontSize: 16,
},
modalTitle: {
fontSize: 24,
marginBottom: 16,
},
customCSSButtons: {
flexDirection: 'row',
},
button: {
marginTop: 16,
flex: 1,
marginHorizontal: 8,
},
});
6 changes: 5 additions & 1 deletion strings/languages/en/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@
"tryAgain": "Try again in a moment",
"uninstalledPlugin": "Uninstalled %{name}",
"updateFailed": "Update failed",
"updatedTo": "Updated to %{version}"
"updatedTo": "Updated to %{version}",
"settings": {
"title": "Plugin Settings",
"description": "Fill in the plugin settings. Restart app to apply the settings."
}
},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
Expand Down

0 comments on commit 0fad9b9

Please sign in to comment.