custom js, css modal
This commit is contained in:
@@ -13,3 +13,4 @@ export { default as ColorPreferenceItem } from './ColorPreferenceItem/ColorPrefe
|
||||
export { default as LoadingMoreIndicator } from './LoadingMoreIndicator/LoadingMoreIndicator';
|
||||
export { Checkbox } from './Checkbox/Checkbox';
|
||||
export { RadioButton } from './RadioButton/RadioButton';
|
||||
export { default as ConfirmationDialog } from './ConfirmationDialog/ConfirmationDialog';
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import React, { useState } from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { Modal, overlay, TextInput } from 'react-native-paper';
|
||||
import { StorageAccessFramework } from 'expo-file-system';
|
||||
import * as DocumentPicker from 'expo-document-picker';
|
||||
|
||||
import { Button } from '@components/index';
|
||||
|
||||
import { showToast } from '@utils/showToast';
|
||||
import { useTheme } from '@hooks/persisted';
|
||||
import { getString } from '@strings/translations';
|
||||
|
||||
interface CustomFileModal {
|
||||
visible: boolean;
|
||||
onDismiss: () => void;
|
||||
defaultValue: string;
|
||||
onSave: (val: string) => void;
|
||||
title: string;
|
||||
mimeType: 'text/css' | 'application/javascript';
|
||||
openFileLabel: string;
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const CustomFileModal: React.FC<CustomFileModal> = ({
|
||||
onDismiss,
|
||||
visible,
|
||||
defaultValue,
|
||||
onSave,
|
||||
title,
|
||||
mimeType,
|
||||
openFileLabel,
|
||||
placeholder,
|
||||
description,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [text, setText] = useState('');
|
||||
|
||||
const openDocumentPicker = async () => {
|
||||
try {
|
||||
const file = await DocumentPicker.getDocumentAsync({
|
||||
copyToCacheDirectory: false,
|
||||
type: mimeType,
|
||||
});
|
||||
|
||||
if (file.type === 'success') {
|
||||
const content = await StorageAccessFramework.readAsStringAsync(
|
||||
file.uri,
|
||||
);
|
||||
|
||||
onSave(content.trim());
|
||||
onDismiss();
|
||||
}
|
||||
} catch (error: any) {
|
||||
showToast(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
<TextInput
|
||||
multiline
|
||||
mode="outlined"
|
||||
defaultValue={defaultValue}
|
||||
onChangeText={setText}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={theme.onSurfaceVariant}
|
||||
underlineColor={theme.outline}
|
||||
style={[{ color: theme.onSurface }, styles.textInput]}
|
||||
theme={{ colors: { ...theme } }}
|
||||
/>
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
onPress={() => {
|
||||
onSave(text.trim());
|
||||
onDismiss();
|
||||
}}
|
||||
style={styles.button}
|
||||
title={getString('common.save')}
|
||||
mode="contained"
|
||||
/>
|
||||
<Button
|
||||
style={styles.button}
|
||||
onPress={openDocumentPicker}
|
||||
title={openFileLabel}
|
||||
/>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomFileModal;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalContainer: {
|
||||
margin: 30,
|
||||
padding: 24,
|
||||
borderRadius: 28,
|
||||
},
|
||||
textInput: {
|
||||
height: 220,
|
||||
borderRadius: 14,
|
||||
marginTop: 16,
|
||||
marginBottom: 8,
|
||||
fontSize: 16,
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 24,
|
||||
marginBottom: 16,
|
||||
},
|
||||
customCSSButtons: {
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
button: {
|
||||
marginTop: 16,
|
||||
flex: 1,
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, View, Text } from 'react-native';
|
||||
import { Portal } from 'react-native-paper';
|
||||
|
||||
import { Button, List, ConfirmationDialog } from '@components';
|
||||
|
||||
import { useBoolean } from '@hooks';
|
||||
import { useTheme, useChapterReaderSettings } from '@hooks/persisted';
|
||||
import { getString } from '@strings/translations';
|
||||
|
||||
import CustomFileModal from '../Modals/CustomFileModal';
|
||||
|
||||
const CustomCSSSettings = () => {
|
||||
const theme = useTheme();
|
||||
const { customCSS, setChapterReaderSettings } = useChapterReaderSettings();
|
||||
|
||||
const cssModal = useBoolean();
|
||||
const clearCSSModal = useBoolean();
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={styles.header}>
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('moreScreen.settingsScreen.readerSettings.customCSS')}
|
||||
</List.SubHeader>
|
||||
</View>
|
||||
<View style={styles.customCSSContainer}>
|
||||
<Text numberOfLines={3} style={[{ color: theme.onSurface }]}>
|
||||
{customCSS || 'Example: body {margin: 10px;}'}
|
||||
</Text>
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
onPress={cssModal.setTrue}
|
||||
style={styles.marginLeftS}
|
||||
title={getString('common.edit')}
|
||||
/>
|
||||
<Button
|
||||
onPress={clearCSSModal.setTrue}
|
||||
title={getString('common.clear')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Modals */}
|
||||
<Portal>
|
||||
<CustomFileModal
|
||||
visible={cssModal.value}
|
||||
onDismiss={cssModal.setFalse}
|
||||
defaultValue={customCSS}
|
||||
mimeType="text/css"
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.customCSS',
|
||||
)}
|
||||
description={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.cssHint',
|
||||
)}
|
||||
placeholder="Example: body {margin: 10px;}"
|
||||
openFileLabel={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.openCSSFile',
|
||||
)}
|
||||
onSave={text => setChapterReaderSettings({ customCSS: text })}
|
||||
/>
|
||||
<ConfirmationDialog
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.clearCustomCSS',
|
||||
)}
|
||||
visible={clearCSSModal.value}
|
||||
onSubmit={() => {
|
||||
setChapterReaderSettings({ customCSS: '' });
|
||||
}}
|
||||
onDismiss={clearCSSModal.setFalse}
|
||||
theme={theme}
|
||||
/>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default CustomCSSSettings;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
bottomInset: {
|
||||
paddingBottom: 40,
|
||||
},
|
||||
fontSizeL: {
|
||||
fontSize: 16,
|
||||
},
|
||||
customCSSContainer: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
buttons: {
|
||||
flex: 1,
|
||||
},
|
||||
marginLeftS: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
customCSSButtons: {
|
||||
marginVertical: 8,
|
||||
flex: 1,
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
customThemeButton: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
},
|
||||
header: { flexDirection: 'row', justifyContent: 'space-between' },
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { Portal } from 'react-native-paper';
|
||||
|
||||
import { Button, List, ConfirmationDialog } from '@components/index';
|
||||
|
||||
import { useBoolean } from '@hooks';
|
||||
import { useTheme, useChapterReaderSettings } from '@hooks/persisted';
|
||||
import { getString } from '@strings/translations';
|
||||
|
||||
import CustomFileModal from '../Modals/CustomFileModal';
|
||||
|
||||
const CustomJSSettings = () => {
|
||||
const theme = useTheme();
|
||||
const { customJS, setChapterReaderSettings } = useChapterReaderSettings();
|
||||
const jsModal = useBoolean();
|
||||
const clearJSModal = useBoolean();
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={styles.header}>
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('moreScreen.settingsScreen.readerSettings.customJS')}
|
||||
</List.SubHeader>
|
||||
</View>
|
||||
<View style={styles.customJSContainer}>
|
||||
<Text numberOfLines={3} style={[{ color: theme.onSurface }]}>
|
||||
{customJS || 'Example: body {margin: 10px;}'}
|
||||
</Text>
|
||||
<View style={styles.customJSButtons}>
|
||||
<Button
|
||||
onPress={jsModal.setTrue}
|
||||
style={styles.marginLeftS}
|
||||
title={getString('common.edit')}
|
||||
/>
|
||||
<Button
|
||||
onPress={clearJSModal.setTrue}
|
||||
title={getString('common.clear')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Modals */}
|
||||
<Portal>
|
||||
<CustomFileModal
|
||||
visible={jsModal.value}
|
||||
onDismiss={jsModal.setFalse}
|
||||
defaultValue={customJS}
|
||||
mimeType="application/javascript"
|
||||
title={getString('moreScreen.settingsScreen.readerSettings.customJS')}
|
||||
description={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.jsHint',
|
||||
)}
|
||||
placeholder="Example: document.getElementById('example');"
|
||||
openFileLabel={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.openJSFile',
|
||||
)}
|
||||
onSave={text => setChapterReaderSettings({ customJS: text })}
|
||||
/>
|
||||
<ConfirmationDialog
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.clearCustomJS',
|
||||
)}
|
||||
visible={clearJSModal.value}
|
||||
onSubmit={() => {
|
||||
setChapterReaderSettings({ customJS: '' });
|
||||
}}
|
||||
onDismiss={clearJSModal.setFalse}
|
||||
theme={theme}
|
||||
/>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default CustomJSSettings;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fontSizeL: {
|
||||
fontSize: 16,
|
||||
},
|
||||
customJSContainer: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
marginLeftS: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
customJSButtons: {
|
||||
flex: 1,
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
header: { flexDirection: 'row', justifyContent: 'space-between' },
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
|
||||
import { List, SwitchItem } from '@components/index';
|
||||
|
||||
import { useChapterGeneralSettings, useTheme } from '@hooks/persisted';
|
||||
import { getString } from '@strings/translations';
|
||||
|
||||
const DisplaySettings: React.FC = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
fullScreenMode = true,
|
||||
showScrollPercentage = true,
|
||||
showBatteryAndTime = false,
|
||||
setChapterGeneralSettings,
|
||||
} = useChapterGeneralSettings();
|
||||
|
||||
return (
|
||||
<>
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('novelScreen.bottomSheet.display')}
|
||||
</List.SubHeader>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.fullscreen')}
|
||||
value={fullScreenMode}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({ fullScreenMode: !fullScreenMode })
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.showProgressPercentage')}
|
||||
value={showScrollPercentage}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({
|
||||
showScrollPercentage: !showScrollPercentage,
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.showBatteryAndTime')}
|
||||
value={showBatteryAndTime}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({ showBatteryAndTime: !showBatteryAndTime })
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DisplaySettings;
|
||||
@@ -0,0 +1,177 @@
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
useWindowDimensions,
|
||||
} from 'react-native';
|
||||
import React from 'react';
|
||||
|
||||
import { defaultTo } from 'lodash-es';
|
||||
|
||||
import { Button, List, SwitchItem } from '@components/index';
|
||||
|
||||
import { useTheme, useChapterGeneralSettings } from '@hooks/persisted';
|
||||
import { getString } from '@strings/translations';
|
||||
|
||||
const GeneralSettings: React.FC = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
useVolumeButtons = false,
|
||||
verticalSeekbar = true,
|
||||
swipeGestures = false,
|
||||
autoScroll = false,
|
||||
autoScrollInterval = 10,
|
||||
autoScrollOffset = null,
|
||||
setChapterGeneralSettings,
|
||||
} = useChapterGeneralSettings();
|
||||
|
||||
const areAutoScrollSettingsDefault =
|
||||
autoScrollInterval === 10 && autoScrollOffset === null;
|
||||
|
||||
const { height: screenHeight } = useWindowDimensions();
|
||||
|
||||
const labelStyle = [styles.fontSizeL, { color: theme.onSurface }];
|
||||
return (
|
||||
<>
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('moreScreen.settingsScreen.generalSettings')}
|
||||
</List.SubHeader>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.verticalSeekbar')}
|
||||
description={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.verticalSeekbarDesc',
|
||||
)}
|
||||
value={verticalSeekbar}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({ verticalSeekbar: !verticalSeekbar })
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.volumeButtonsScroll')}
|
||||
value={useVolumeButtons}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({ useVolumeButtons: !useVolumeButtons })
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.swipeGestures')}
|
||||
value={swipeGestures}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({ swipeGestures: !swipeGestures })
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.autoscroll')}
|
||||
value={autoScroll}
|
||||
onPress={() => setChapterGeneralSettings({ autoScroll: !autoScroll })}
|
||||
theme={theme}
|
||||
/>
|
||||
{autoScroll ? (
|
||||
<>
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('readerScreen.bottomSheet.autoscroll')}
|
||||
</List.SubHeader>
|
||||
<View style={styles.autoScrollInterval}>
|
||||
<Text style={[labelStyle, styles.paddingRightM]} numberOfLines={2}>
|
||||
{getString(
|
||||
'moreScreen.settingsScreen.readerSettings.autoScrollInterval',
|
||||
)}
|
||||
</Text>
|
||||
<TextInput
|
||||
style={labelStyle}
|
||||
defaultValue={defaultTo(autoScrollInterval, 10).toString()}
|
||||
keyboardType="numeric"
|
||||
onChangeText={text => {
|
||||
if (text) {
|
||||
setChapterGeneralSettings({
|
||||
autoScrollInterval: Number(text),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.autoScrollInterval}>
|
||||
<Text style={[labelStyle, styles.paddingRightM]} numberOfLines={2}>
|
||||
{getString(
|
||||
'moreScreen.settingsScreen.readerSettings.autoScrollOffset',
|
||||
)}
|
||||
</Text>
|
||||
<TextInput
|
||||
style={labelStyle}
|
||||
defaultValue={defaultTo(
|
||||
autoScrollOffset,
|
||||
Math.round(screenHeight),
|
||||
).toString()}
|
||||
keyboardType="numeric"
|
||||
onChangeText={text => {
|
||||
if (text) {
|
||||
setChapterGeneralSettings({ autoScrollOffset: Number(text) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{!areAutoScrollSettingsDefault ? (
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
style={styles.customThemeButton}
|
||||
title={getString('common.reset')}
|
||||
onPress={() => {
|
||||
setChapterGeneralSettings({ autoScrollInterval: 10 });
|
||||
setChapterGeneralSettings({ autoScrollOffset: null });
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default GeneralSettings;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
bottomInset: {
|
||||
paddingBottom: 40,
|
||||
},
|
||||
fontSizeL: {
|
||||
fontSize: 16,
|
||||
},
|
||||
autoScrollInterval: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
customThemeButton: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
},
|
||||
customCSSContainer: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
marginLeftS: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
customCSSButtons: {
|
||||
flex: 1,
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
paddingRightM: {
|
||||
flex: 1,
|
||||
paddingRight: 16,
|
||||
},
|
||||
label: {
|
||||
fontSize: 16,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,174 @@
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import React from 'react';
|
||||
|
||||
import { Button, ColorPreferenceItem, List } from '@components/index';
|
||||
|
||||
import { useChapterReaderSettings, useTheme } from '@hooks/persisted';
|
||||
import { getString } from '@strings/translations';
|
||||
import ReaderTextAlignSelector from '@screens/reader/components/ReaderBottomSheet/ReaderTextAlignSelector';
|
||||
import ReaderTextSize from '../ReaderTextSize';
|
||||
import ReaderLineHeight from '@screens/reader/components/ReaderBottomSheet/ReaderLineHeight';
|
||||
import ReaderThemeSelector from '@screens/reader/components/ReaderBottomSheet/ReaderThemeSelector';
|
||||
import {
|
||||
presetReaderThemes,
|
||||
readerFonts,
|
||||
} from '@utils/constants/readerConstants';
|
||||
import { useBoolean } from '@hooks';
|
||||
import { Portal } from 'react-native-paper';
|
||||
import ColorPickerModal from '@components/ColorPickerModal/ColorPickerModal';
|
||||
import FontPickerModal from '../Modals/FontPickerModal';
|
||||
|
||||
const ReaderThemeSettings = () => {
|
||||
const theme = useTheme();
|
||||
const readerSettings = useChapterReaderSettings();
|
||||
|
||||
const labelStyle = [styles.fontSizeL, { color: theme.onSurface }];
|
||||
const readerFontPickerModal = useBoolean();
|
||||
const readerBackgroundModal = useBoolean();
|
||||
const readerTextColorModal = useBoolean();
|
||||
|
||||
const isCurrentThemeCustom = readerSettings.customThemes.some(
|
||||
item =>
|
||||
item.backgroundColor === readerSettings.theme &&
|
||||
item.textColor === readerSettings.textColor,
|
||||
);
|
||||
|
||||
const isCurrentThemePreset = presetReaderThemes.some(
|
||||
item =>
|
||||
item.backgroundColor === readerSettings.theme &&
|
||||
item.textColor === readerSettings.textColor,
|
||||
);
|
||||
|
||||
const currentFontName = readerFonts.find(
|
||||
item => item.fontFamily === readerSettings.fontFamily,
|
||||
)?.name;
|
||||
|
||||
return (
|
||||
<>
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('moreScreen.settingsScreen.readerSettings.readerTheme')}
|
||||
</List.SubHeader>
|
||||
<ReaderThemeSelector
|
||||
label={getString('moreScreen.settingsScreen.readerSettings.preset')}
|
||||
labelStyle={labelStyle}
|
||||
/>
|
||||
<ColorPreferenceItem
|
||||
label={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.backgroundColor',
|
||||
)}
|
||||
description={readerSettings.theme}
|
||||
onPress={readerBackgroundModal.setTrue}
|
||||
theme={theme}
|
||||
/>
|
||||
<ColorPreferenceItem
|
||||
label={getString('moreScreen.settingsScreen.readerSettings.textColor')}
|
||||
description={readerSettings.textColor}
|
||||
onPress={readerTextColorModal.setTrue}
|
||||
theme={theme}
|
||||
/>
|
||||
{isCurrentThemeCustom ? (
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
style={styles.customThemeButton}
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.deleteCustomTheme',
|
||||
)}
|
||||
onPress={() =>
|
||||
readerSettings.deleteCustomReaderTheme({
|
||||
backgroundColor: readerSettings.theme,
|
||||
textColor: readerSettings.textColor,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
) : !isCurrentThemePreset ? (
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
style={styles.customThemeButton}
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.saveCustomTheme',
|
||||
)}
|
||||
onPress={() =>
|
||||
readerSettings.saveCustomReaderTheme({
|
||||
backgroundColor: readerSettings.theme,
|
||||
textColor: readerSettings.textColor,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
<ReaderTextAlignSelector labelStyle={labelStyle} />
|
||||
<ReaderTextSize labelStyle={labelStyle} />
|
||||
<ReaderLineHeight labelStyle={labelStyle} />
|
||||
<List.Item
|
||||
title={getString('readerScreen.bottomSheet.fontStyle')}
|
||||
description={currentFontName}
|
||||
onPress={readerFontPickerModal.setTrue}
|
||||
theme={theme}
|
||||
/>
|
||||
{/*
|
||||
Modals
|
||||
*/}
|
||||
<Portal>
|
||||
<ColorPickerModal
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.backgroundColor',
|
||||
)}
|
||||
visible={readerBackgroundModal.value}
|
||||
color={readerSettings.theme}
|
||||
closeModal={readerBackgroundModal.setFalse}
|
||||
theme={theme}
|
||||
onSubmit={color =>
|
||||
readerSettings.setChapterReaderSettings({ theme: color })
|
||||
}
|
||||
/>
|
||||
<ColorPickerModal
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.textColor',
|
||||
)}
|
||||
visible={readerTextColorModal.value}
|
||||
color={readerSettings.textColor}
|
||||
closeModal={readerTextColorModal.setFalse}
|
||||
theme={theme}
|
||||
onSubmit={color =>
|
||||
readerSettings.setChapterReaderSettings({ textColor: color })
|
||||
}
|
||||
/>
|
||||
<FontPickerModal
|
||||
currentFont={readerSettings.fontFamily}
|
||||
visible={readerFontPickerModal.value}
|
||||
onDismiss={readerFontPickerModal.setFalse}
|
||||
/>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ReaderThemeSettings;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
bottomInset: {
|
||||
paddingBottom: 40,
|
||||
},
|
||||
fontSizeL: {
|
||||
fontSize: 16,
|
||||
},
|
||||
customCSSContainer: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
buttons: {
|
||||
flex: 1,
|
||||
},
|
||||
customCSSButtons: {
|
||||
marginVertical: 8,
|
||||
flex: 1,
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
customThemeButton: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
},
|
||||
});
|
||||
@@ -1,47 +1,21 @@
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
ScrollView,
|
||||
useWindowDimensions,
|
||||
} from 'react-native';
|
||||
import React, { useState } from 'react';
|
||||
import { View, ScrollView } from 'react-native';
|
||||
import React from 'react';
|
||||
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { defaultTo } from 'lodash-es';
|
||||
import WebView from 'react-native-webview';
|
||||
import { Portal } from 'react-native-paper';
|
||||
import { dummyHTML } from './utils';
|
||||
|
||||
import {
|
||||
Appbar,
|
||||
Button,
|
||||
ColorPreferenceItem,
|
||||
List,
|
||||
SwitchItem,
|
||||
} from '@components/index';
|
||||
import ReaderThemeSelector from '@screens/reader/components/ReaderBottomSheet/ReaderThemeSelector';
|
||||
import ReaderTextAlignSelector from '@screens/reader/components/ReaderBottomSheet/ReaderTextAlignSelector';
|
||||
import ColorPickerModal from '@components/ColorPickerModal/ColorPickerModal';
|
||||
import FontPickerModal from './FontPickerModal';
|
||||
import ReaderLineHeight from '@screens/reader/components/ReaderBottomSheet/ReaderLineHeight';
|
||||
import ReaderTextSize from './ReaderTextSize';
|
||||
import { Appbar, List } from '@components/index';
|
||||
|
||||
import {
|
||||
useChapterGeneralSettings,
|
||||
useChapterReaderSettings,
|
||||
useTheme,
|
||||
} from '@hooks/persisted';
|
||||
import { useChapterReaderSettings, useTheme } from '@hooks/persisted';
|
||||
import { getString } from '@strings/translations';
|
||||
|
||||
import { dummyHTML } from './utils';
|
||||
import { useBoolean } from '@hooks';
|
||||
import {
|
||||
presetReaderThemes,
|
||||
readerFonts,
|
||||
} from '@utils/constants/readerConstants';
|
||||
|
||||
const READER_HEIGHT = 360;
|
||||
import GeneralSettings from './Settings/GeneralSettings';
|
||||
import CustomCSSSettings from './Settings/CustomCSSSettings';
|
||||
import CustomJSSettings from './Settings/CustomJSSettings';
|
||||
import DisplaySettings from './Settings/DisplaySettings';
|
||||
import ReaderThemeSettings from './Settings/ReaderThemeSettings';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
export type TextAlignments =
|
||||
| 'left'
|
||||
@@ -54,21 +28,17 @@ export type TextAlignments =
|
||||
const SettingsReaderScreen = () => {
|
||||
const theme = useTheme();
|
||||
const navigation = useNavigation();
|
||||
const { bottom } = useSafeAreaInsets();
|
||||
|
||||
const readerSettings = useChapterReaderSettings();
|
||||
const {
|
||||
useVolumeButtons,
|
||||
verticalSeekbar,
|
||||
swipeGestures,
|
||||
autoScroll,
|
||||
autoScrollInterval,
|
||||
autoScrollOffset,
|
||||
fullScreenMode,
|
||||
showScrollPercentage,
|
||||
showBatteryAndTime,
|
||||
setChapterGeneralSettings,
|
||||
} = useChapterGeneralSettings();
|
||||
|
||||
const READER_HEIGHT = 280;
|
||||
const dummyChapterInfo = {
|
||||
sourceId: 11,
|
||||
chapterId: 99,
|
||||
novelId: 20,
|
||||
novelName: 'novel name',
|
||||
chapterName: "chapter' name",
|
||||
};
|
||||
const webViewCSS = `
|
||||
<style>
|
||||
body {
|
||||
@@ -94,36 +64,6 @@ const SettingsReaderScreen = () => {
|
||||
|
||||
const readerBackgroundColor = readerSettings.theme;
|
||||
|
||||
const readerBackgroundModal = useBoolean();
|
||||
const readerTextColorModal = useBoolean();
|
||||
const readerFontPickerModal = useBoolean();
|
||||
|
||||
const isCurrentThemeCustom = readerSettings.customThemes.some(
|
||||
item =>
|
||||
item.backgroundColor === readerSettings.theme &&
|
||||
item.textColor === readerSettings.textColor,
|
||||
);
|
||||
|
||||
const isCurrentThemePreset = presetReaderThemes.some(
|
||||
item =>
|
||||
item.backgroundColor === readerSettings.theme &&
|
||||
item.textColor === readerSettings.textColor,
|
||||
);
|
||||
|
||||
const currentFontName = readerFonts.find(
|
||||
item => item.fontFamily === readerSettings.fontFamily,
|
||||
)?.name;
|
||||
|
||||
const areAutoScrollSettingsDefault =
|
||||
autoScrollInterval === 10 && autoScrollOffset === null;
|
||||
|
||||
const [customCSS, setcustomCSS] = useState(readerSettings.customCSS);
|
||||
const [customJS, setcustomJS] = useState(readerSettings.customJS);
|
||||
|
||||
const { height: screenHeight } = useWindowDimensions();
|
||||
|
||||
const labelStyle = [styles.fontSizeL, { color: theme.onSurface }];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar
|
||||
@@ -146,13 +86,24 @@ const SettingsReaderScreen = () => {
|
||||
${webViewCSS}
|
||||
</head>
|
||||
<body>
|
||||
<chapter>
|
||||
<chapter
|
||||
data-novel-id='${dummyChapterInfo.novelId}'
|
||||
data-chapter-id='${dummyChapterInfo.chapterId}'
|
||||
>
|
||||
${dummyHTML}
|
||||
<script>
|
||||
async function fn(){${readerSettings.customJS}}
|
||||
document.addEventListener("DOMContentLoaded", fn);
|
||||
</script>
|
||||
</chapter>
|
||||
<script>
|
||||
async function fn(){
|
||||
let novelName = "${dummyChapterInfo.novelName}";
|
||||
let chapterName = "${dummyChapterInfo.chapterName}";
|
||||
let sourceId =${dummyChapterInfo.sourceId};
|
||||
let chapterId =${dummyChapterInfo.chapterId};
|
||||
let novelId =${dummyChapterInfo.novelId};
|
||||
let html = document.getElementsByTagName("chapter")[0].innerHTML;
|
||||
${readerSettings.customJS}
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", fn);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
@@ -160,346 +111,21 @@ const SettingsReaderScreen = () => {
|
||||
/>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
style={styles.flex}
|
||||
contentContainerStyle={styles.bottomInset}
|
||||
>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.verticalSeekbar')}
|
||||
description={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.verticalSeekbarDesc',
|
||||
)}
|
||||
value={verticalSeekbar}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({ verticalSeekbar: !verticalSeekbar })
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.volumeButtonsScroll')}
|
||||
value={useVolumeButtons}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({ useVolumeButtons: !useVolumeButtons })
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.swipeGestures')}
|
||||
value={swipeGestures}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({ swipeGestures: !swipeGestures })
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.autoscroll')}
|
||||
value={autoScroll}
|
||||
onPress={() => setChapterGeneralSettings({ autoScroll: !autoScroll })}
|
||||
theme={theme}
|
||||
/>
|
||||
{autoScroll ? (
|
||||
<>
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('readerScreen.bottomSheet.autoscroll')}
|
||||
</List.SubHeader>
|
||||
<View style={styles.autoScrollInterval}>
|
||||
<Text
|
||||
style={[labelStyle, styles.paddingRightM]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{getString(
|
||||
'moreScreen.settingsScreen.readerSettings.autoScrollInterval',
|
||||
)}
|
||||
</Text>
|
||||
<TextInput
|
||||
style={labelStyle}
|
||||
defaultValue={defaultTo(autoScrollInterval, 10).toString()}
|
||||
keyboardType="numeric"
|
||||
onChangeText={text => {
|
||||
if (text) {
|
||||
setChapterGeneralSettings({
|
||||
autoScrollInterval: Number(text),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.autoScrollInterval}>
|
||||
<Text
|
||||
style={[labelStyle, styles.paddingRightM]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{getString(
|
||||
'moreScreen.settingsScreen.readerSettings.autoScrollOffset',
|
||||
)}
|
||||
</Text>
|
||||
<TextInput
|
||||
style={labelStyle}
|
||||
defaultValue={defaultTo(
|
||||
autoScrollOffset,
|
||||
Math.round(screenHeight),
|
||||
).toString()}
|
||||
keyboardType="numeric"
|
||||
onChangeText={text => {
|
||||
if (text) {
|
||||
setChapterGeneralSettings({
|
||||
autoScrollOffset: Number(text),
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
{!areAutoScrollSettingsDefault ? (
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
style={styles.customThemeButton}
|
||||
title={getString('common.reset')}
|
||||
onPress={() => {
|
||||
setChapterGeneralSettings({
|
||||
autoScrollInterval: 10,
|
||||
autoScrollOffset: null,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
<>
|
||||
<ScrollView>
|
||||
<View style={{ paddingBottom: bottom }}>
|
||||
<GeneralSettings />
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('moreScreen.settingsScreen.readerSettings.customCSS')}
|
||||
</List.SubHeader>
|
||||
<View style={styles.customCSSContainer}>
|
||||
<TextInput
|
||||
style={[{ color: theme.onSurface }, styles.fontSizeL]}
|
||||
value={customCSS}
|
||||
onChangeText={text => setcustomCSS(text)}
|
||||
placeholderTextColor={theme.onSurfaceVariant}
|
||||
placeholder="Example: body { color: red; }"
|
||||
multiline={true}
|
||||
/>
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
onPress={() =>
|
||||
readerSettings.setChapterReaderSettings({
|
||||
customCSS: customCSS,
|
||||
})
|
||||
}
|
||||
style={styles.marginLeftS}
|
||||
title={getString('common.save')}
|
||||
/>
|
||||
<Button
|
||||
onPress={() =>
|
||||
readerSettings.setChapterReaderSettings({ customCSS: '' })
|
||||
}
|
||||
title={getString('common.clear')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<CustomCSSSettings />
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>
|
||||
{/* {getString('moreScreen.settingsScreen.readerSettings.customJS')} */}
|
||||
{'Custom JS'}
|
||||
</List.SubHeader>
|
||||
<View style={styles.customCSSContainer}>
|
||||
<TextInput
|
||||
style={[{ color: theme.onSurface }, styles.fontSizeL]}
|
||||
value={customJS}
|
||||
onChangeText={text => setcustomJS(text)}
|
||||
placeholderTextColor={theme.onSurfaceVariant}
|
||||
placeholder="Example: document.getElementById('example');"
|
||||
multiline={true}
|
||||
/>
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
onPress={() =>
|
||||
readerSettings.setChapterReaderSettings({
|
||||
customJS: customJS,
|
||||
})
|
||||
}
|
||||
style={styles.marginLeftS}
|
||||
title={getString('common.save')}
|
||||
/>
|
||||
<Button
|
||||
onPress={() =>
|
||||
readerSettings.setChapterReaderSettings({ customJS: '' })
|
||||
}
|
||||
title={getString('common.clear')}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('novelScreen.bottomSheet.display')}
|
||||
</List.SubHeader>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.fullscreen')}
|
||||
value={fullScreenMode}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({ fullScreenMode: !fullScreenMode })
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.showProgressPercentage')}
|
||||
value={showScrollPercentage}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({
|
||||
showScrollPercentage: !showScrollPercentage,
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<SwitchItem
|
||||
label={getString('readerScreen.bottomSheet.showBatteryAndTime')}
|
||||
value={showBatteryAndTime}
|
||||
onPress={() =>
|
||||
setChapterGeneralSettings({
|
||||
showBatteryAndTime: !showBatteryAndTime,
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>
|
||||
{getString('moreScreen.settingsScreen.readerSettings.readerTheme')}
|
||||
</List.SubHeader>
|
||||
<ReaderThemeSelector
|
||||
label={getString('moreScreen.settingsScreen.readerSettings.preset')}
|
||||
labelStyle={labelStyle}
|
||||
/>
|
||||
<ColorPreferenceItem
|
||||
label={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.backgroundColor',
|
||||
)}
|
||||
description={readerSettings.theme}
|
||||
onPress={readerBackgroundModal.setTrue}
|
||||
theme={theme}
|
||||
/>
|
||||
<ColorPreferenceItem
|
||||
label={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.textColor',
|
||||
)}
|
||||
description={readerSettings.textColor}
|
||||
onPress={readerTextColorModal.setTrue}
|
||||
theme={theme}
|
||||
/>
|
||||
{isCurrentThemeCustom ? (
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
style={styles.customThemeButton}
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.deleteCustomTheme',
|
||||
)}
|
||||
onPress={() =>
|
||||
readerSettings.deleteCustomReaderTheme({
|
||||
backgroundColor: readerSettings.theme,
|
||||
textColor: readerSettings.textColor,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
) : !isCurrentThemePreset ? (
|
||||
<View style={styles.customCSSButtons}>
|
||||
<Button
|
||||
style={styles.customThemeButton}
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.saveCustomTheme',
|
||||
)}
|
||||
onPress={() =>
|
||||
readerSettings.saveCustomReaderTheme({
|
||||
backgroundColor: readerSettings.theme,
|
||||
textColor: readerSettings.textColor,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
<ReaderTextAlignSelector labelStyle={labelStyle} />
|
||||
<ReaderTextSize labelStyle={labelStyle} />
|
||||
<ReaderLineHeight labelStyle={labelStyle} />
|
||||
<List.Item
|
||||
title={getString('readerScreen.bottomSheet.fontStyle')}
|
||||
description={currentFontName}
|
||||
onPress={readerFontPickerModal.setTrue}
|
||||
theme={theme}
|
||||
/>
|
||||
<CustomJSSettings />
|
||||
<List.Divider theme={theme} />
|
||||
<DisplaySettings />
|
||||
<List.Divider theme={theme} />
|
||||
<ReaderThemeSettings />
|
||||
</View>
|
||||
</ScrollView>
|
||||
<Portal>
|
||||
<ColorPickerModal
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.backgroundColor',
|
||||
)}
|
||||
visible={readerBackgroundModal.value}
|
||||
color={readerSettings.theme}
|
||||
closeModal={readerBackgroundModal.setFalse}
|
||||
theme={theme}
|
||||
onSubmit={color =>
|
||||
readerSettings.setChapterReaderSettings({ theme: color })
|
||||
}
|
||||
/>
|
||||
<ColorPickerModal
|
||||
title={getString(
|
||||
'moreScreen.settingsScreen.readerSettings.textColor',
|
||||
)}
|
||||
visible={readerTextColorModal.value}
|
||||
color={readerSettings.textColor}
|
||||
closeModal={readerTextColorModal.setFalse}
|
||||
theme={theme}
|
||||
onSubmit={color =>
|
||||
readerSettings.setChapterReaderSettings({ textColor: color })
|
||||
}
|
||||
/>
|
||||
<FontPickerModal
|
||||
currentFont={readerSettings.fontFamily}
|
||||
visible={readerFontPickerModal.value}
|
||||
onDismiss={readerFontPickerModal.setFalse}
|
||||
/>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsReaderScreen;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
bottomInset: {
|
||||
paddingBottom: 40,
|
||||
},
|
||||
fontSizeL: {
|
||||
fontSize: 16,
|
||||
},
|
||||
autoScrollInterval: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
customThemeButton: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 16,
|
||||
},
|
||||
customCSSContainer: {
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
marginLeftS: {
|
||||
marginLeft: 8,
|
||||
},
|
||||
customCSSButtons: {
|
||||
flex: 1,
|
||||
flexDirection: 'row-reverse',
|
||||
},
|
||||
paddingRightM: {
|
||||
flex: 1,
|
||||
paddingRight: 16,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user