Refactor (#82)
This commit is contained in:
committed by
GitHub
parent
3daa11f642
commit
7c4da7f761
Generated
+3
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "lnreader",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "lnreader",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.5",
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^1.13.0",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
@@ -28,6 +28,7 @@
|
||||
"expo-updates": "~0.5.4",
|
||||
"expo-web-browser": "~9.1.0",
|
||||
"moment": "^2.29.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"qs": "^6.10.1",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"expo-updates": "~0.5.4",
|
||||
"expo-web-browser": "~9.1.0",
|
||||
"moment": "^2.29.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"qs": "^6.10.1",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {Appbar as MaterialAppbar} from 'react-native-paper';
|
||||
|
||||
import {useTheme} from '../hooks/reduxHooks';
|
||||
import {ViewPropTypes} from 'react-native';
|
||||
|
||||
export const Appbar = ({title, onBackAction, style, children}) => {
|
||||
const theme = useTheme();
|
||||
@@ -20,3 +22,9 @@ export const Appbar = ({title, onBackAction, style, children}) => {
|
||||
</MaterialAppbar.Header>
|
||||
);
|
||||
};
|
||||
|
||||
Appbar.propTypes = {
|
||||
title: PropTypes.string,
|
||||
onBackAction: PropTypes.func,
|
||||
style: ViewPropTypes.style,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import {View, Text, Pressable, StyleSheet} from 'react-native';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const Chip = ({label, onPress, theme}) => {
|
||||
return (
|
||||
<View style={[styles.container, {backgroundColor: theme.colorPrimary}]}>
|
||||
<Pressable
|
||||
style={styles.content}
|
||||
onPress={onPress}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
>
|
||||
<Text style={[styles.label, {color: theme.textColorSecondary}]}>
|
||||
{label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
Chip.propTypes = {
|
||||
label: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
alignSelf: 'flex-start',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: 32,
|
||||
marginHorizontal: 4,
|
||||
elevation: 1,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
},
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback} from 'react';
|
||||
import React, {memo, useCallback} from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
@@ -139,7 +139,7 @@ const NovelCover = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelCover;
|
||||
export default memo(NovelCover);
|
||||
|
||||
const ComfortableTitle = ({theme, novelName}) => (
|
||||
<Text
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {useRef} from 'react';
|
||||
import {StyleSheet, View, TextInput, Text, StatusBar} from 'react-native';
|
||||
import {StyleSheet, View, TextInput, StatusBar, Pressable} from 'react-native';
|
||||
|
||||
import {TouchableRipple, IconButton} from 'react-native-paper';
|
||||
import {IconButton} from 'react-native-paper';
|
||||
import {Row} from '../Common';
|
||||
|
||||
export const Searchbar = ({
|
||||
@@ -18,16 +18,12 @@ export const Searchbar = ({
|
||||
const searchRef = useRef(null);
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
borderless
|
||||
onPress={() => searchRef.current.focus()}
|
||||
style={[
|
||||
styles.searchbarContainer,
|
||||
{backgroundColor: theme.searchBarColor},
|
||||
]}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.searchBar}>
|
||||
<Pressable
|
||||
onPress={() => searchRef.current.focus()}
|
||||
style={[styles.pressable, {backgroundColor: theme.searchBarColor}]}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
>
|
||||
<Row style={{flex: 1}}>
|
||||
{backAction && (
|
||||
<IconButton
|
||||
@@ -71,26 +67,24 @@ export const Searchbar = ({
|
||||
onPress={action.onPress}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
searchbarContainer: {
|
||||
searchBar: {
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 8,
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 16,
|
||||
borderRadius: 50,
|
||||
overflow: 'hidden',
|
||||
elevation: 1,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
pressable: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Text as NativeText, TextPropTypes} from 'react-native';
|
||||
|
||||
const Text = ({type, children, style, theme}) => {
|
||||
const color =
|
||||
type === 'primary'
|
||||
? theme.textColorPrimary
|
||||
: type === 'secondary'
|
||||
? theme.textColorSecondary
|
||||
: theme.textColorHint;
|
||||
|
||||
return <NativeText style={[style, {color}]}>{children}</NativeText>;
|
||||
};
|
||||
|
||||
Text.propType = {
|
||||
style: TextPropTypes.style,
|
||||
type: PropTypes.oneOf(['primary', 'secondary', 'hint']),
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
@@ -35,7 +35,9 @@ export const createDB = () => {
|
||||
tx.executeSql(createDownloadTableQuery);
|
||||
tx.executeSql(createUpdatesTableQuery);
|
||||
|
||||
//Db indexes
|
||||
/**
|
||||
* Indexes
|
||||
*/
|
||||
tx.executeSql(createUrlIndexQuery);
|
||||
tx.executeSql(createLibraryIndexQuery);
|
||||
tx.executeSql(createNovelIdIndexQuery);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as SQLite from 'expo-sqlite';
|
||||
const db = SQLite.openDatabase('lnreader.db');
|
||||
|
||||
import {showToast} from '../../hooks/showToast';
|
||||
|
||||
const getHistoryQuery = `
|
||||
SELECT history.*, chapters.*, novels.*
|
||||
FROM history
|
||||
@@ -57,4 +59,5 @@ export const deleteAllHistory = async () => {
|
||||
db.transaction(tx => {
|
||||
tx.executeSql('DELETE FROM history; VACCUM;');
|
||||
});
|
||||
showToast('History deleted.');
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ const getUpdatesQuery = `
|
||||
ON updates.novelId = novels.novelId
|
||||
WHERE date(updates.updateTime) > date('now','-3 months')
|
||||
ORDER BY updates.updateTime DESC
|
||||
LIMIT 100
|
||||
`;
|
||||
|
||||
export const getUpdates = async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const createChapterTableQuery = `
|
||||
const createChapterTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS chapters(
|
||||
chapterId INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chapterUrl TEXT,
|
||||
@@ -13,6 +13,18 @@ export const createChapterTableQuery = `
|
||||
ON DELETE CASCADE
|
||||
)`;
|
||||
|
||||
export const createNovelIdIndexQuery = `CREATE INDEX IF NOT EXISTS chapterNovelIdIndex ON chapters(novelId)`;
|
||||
const createNovelIdIndexQuery = `
|
||||
CREATE INDEX
|
||||
IF NOT EXISTS
|
||||
chapterNovelIdIndex ON chapters(novelId)`;
|
||||
|
||||
export const createUnreadChaptersIndexQuery = `CREATE INDEX IF NOT EXISTS chapterUnreadByNovelIndex ON chapters(novelId, \`read\`) WHERE \`read\`=0`;
|
||||
const createUnreadChaptersIndexQuery = `
|
||||
CREATE INDEX
|
||||
IF NOT EXISTS
|
||||
chapterUnreadByNovelIndex ON chapters(novelId, \`read\`) WHERE \`read\`=0`;
|
||||
|
||||
export {
|
||||
createChapterTableQuery,
|
||||
createNovelIdIndexQuery,
|
||||
createUnreadChaptersIndexQuery,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import {useState} from 'react';
|
||||
|
||||
export const useModal = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const showModal = () => setVisible(true);
|
||||
const hideModal = () => setVisible(false);
|
||||
|
||||
return {visible, showModal, hideModal};
|
||||
};
|
||||
@@ -4,11 +4,11 @@ import {createMaterialBottomTabNavigator} from '@react-navigation/material-botto
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import Library from '../screens/Library/Library';
|
||||
import Updates from '../screens/Updates/Updates';
|
||||
import History from '../screens/History/History';
|
||||
import Browse from '../screens/Browse/Browse';
|
||||
import More from '../screens/More/More';
|
||||
import Library from '../screens/library/Library';
|
||||
import Updates from '../screens/updates/Updates';
|
||||
import History from '../screens/history/History';
|
||||
import Browse from '../screens/browse/Browse';
|
||||
import More from '../screens/more/More';
|
||||
|
||||
import {useSettings, useTheme} from '../hooks/reduxHooks';
|
||||
|
||||
|
||||
+18
-13
@@ -9,21 +9,26 @@ import {useTheme} from '../hooks/reduxHooks';
|
||||
import {githubUpdateChecker} from '../hooks/githubUpdateChecker';
|
||||
import * as SplashScreen from 'expo-splash-screen';
|
||||
|
||||
// Navigators
|
||||
/**
|
||||
* Navigators
|
||||
*/
|
||||
import BottomNavigator from './BottomNavigator';
|
||||
|
||||
// Screens
|
||||
import Novel from '../screens/Novel/Novel';
|
||||
import Chapter from '../screens/Chapter/Chapter';
|
||||
import Extension from '../screens/Extension/Extension';
|
||||
import GlobalSearch from '../screens/Browse/globalsearch/GlobalSearch';
|
||||
import Migration from '../screens/Browse/migration/Migration';
|
||||
import SourceNovels from '../screens/Browse/SourceNovels';
|
||||
import MigrateNovel from '../screens/Browse/migration/MigrationNovels';
|
||||
import MoreStack from './More';
|
||||
import MalTopNovels from '../screens/Browse/discover/MalTopNovels';
|
||||
|
||||
/**
|
||||
* Screens
|
||||
*/
|
||||
import Novel from '../screens/novel/Novel';
|
||||
import Reader from '../screens/reader/Reader';
|
||||
import Extension from '../screens/source/Source';
|
||||
import GlobalSearch from '../screens/browse/globalsearch/GlobalSearch';
|
||||
import Migration from '../screens/browse/migration/Migration';
|
||||
import SourceNovels from '../screens/browse/SourceNovels';
|
||||
import MigrateNovel from '../screens/browse/migration/MigrationNovels';
|
||||
|
||||
import MalTopNovels from '../screens/browse/discover/MalTopNovels';
|
||||
import NewUpdateDialog from '../components/NewUpdateDialog';
|
||||
import BrowseSettings from '../screens/Browse/BrowseSettings';
|
||||
import BrowseSettings from '../screens/browse/BrowseSettings';
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
@@ -46,7 +51,7 @@ const MainNavigator = () => {
|
||||
<Stack.Navigator screenOptions={{headerShown: false}}>
|
||||
<Stack.Screen name="BottomNavigator" component={BottomNavigator} />
|
||||
<Stack.Screen name="Novel" component={Novel} />
|
||||
<Stack.Screen name="Chapter" component={Chapter} />
|
||||
<Stack.Screen name="Chapter" component={Reader} />
|
||||
<Stack.Screen name="MoreStack" component={MoreStack} />
|
||||
<Stack.Screen name="Extension" component={Extension} />
|
||||
<Stack.Screen name="BrowseMal" component={MalTopNovels} />
|
||||
|
||||
+10
-10
@@ -3,16 +3,16 @@ import React from 'react';
|
||||
import {createStackNavigator} from '@react-navigation/stack';
|
||||
|
||||
// Screens
|
||||
import About from '../screens/More/About';
|
||||
import Settings from '../screens/More/Settings';
|
||||
import TrackerSettings from '../screens/More/TrackerSettings';
|
||||
import ReaderSettings from '../screens/More/ReaderSettings';
|
||||
import BackupSettings from '../screens/More/BackupSettings';
|
||||
import AdvancedSettings from '../screens/More/AdvancedSettings';
|
||||
import GeneralSettings from '../screens/More/GeneralSettings';
|
||||
import DownloadQueue from '../screens/More/DownloadQueue';
|
||||
import Downloads from '../screens/More/Downloads';
|
||||
import AppearanceSettings from '../screens/More/AppearanceSettings';
|
||||
import About from '../screens/more/About';
|
||||
import Settings from '../screens/setting/Settings';
|
||||
import TrackerSettings from '../screens/setting/SettingsTracker';
|
||||
import ReaderSettings from '../screens/setting/SettingsReader';
|
||||
import BackupSettings from '../screens/setting/SettingsBackup';
|
||||
import AdvancedSettings from '../screens/setting/SettingsAdvanced';
|
||||
import GeneralSettings from '../screens/setting/SettingsGeneral';
|
||||
import DownloadQueue from '../screens/more/DownloadQueue';
|
||||
import Downloads from '../screens/more/Downloads';
|
||||
import AppearanceSettings from '../screens/setting/SettingsAppearance';
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import {useTheme} from '../../hooks/reduxHooks';
|
||||
import {showToast} from '../../hooks/showToast';
|
||||
|
||||
import {Searchbar} from '../../components/Searchbar/Searchbar';
|
||||
import SourceCard from './components/SourceCard';
|
||||
import SourceItem from './components/SourceItem';
|
||||
import EmptyView from '../../components/EmptyView';
|
||||
import DiscoverCard from './discover/DiscoverCard';
|
||||
|
||||
@@ -65,7 +65,7 @@ const Browse = ({navigation}) => {
|
||||
};
|
||||
|
||||
const renderItem = ({item}) => (
|
||||
<SourceCard
|
||||
<SourceItem
|
||||
item={item}
|
||||
isPinned={isPinned(item.sourceId)}
|
||||
dispatch={dispatch}
|
||||
|
||||
+18
-15
@@ -1,27 +1,30 @@
|
||||
import React from 'react';
|
||||
import React, {useCallback} from 'react';
|
||||
import {View, Text, Image, StyleSheet, Pressable} from 'react-native';
|
||||
|
||||
import {Button, IconButton} from 'react-native-paper';
|
||||
|
||||
import {pinSourceAction} from '../../../redux/source/source.actions';
|
||||
|
||||
const ExtensionCard = ({item, theme, isPinned, dispatch, navigation}) => {
|
||||
const SourceItem = ({item, theme, isPinned, dispatch, navigation}) => {
|
||||
const {sourceId, sourceName, icon, lang} = item;
|
||||
|
||||
const navigateToExtension = () =>
|
||||
navigation.navigate('Extension', {
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
url: item.url,
|
||||
});
|
||||
const navigateToSource = useCallback(
|
||||
() =>
|
||||
navigation.navigate('Extension', {
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
url: item.url,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.extensionCard}
|
||||
onPress={navigateToExtension}
|
||||
style={styles.container}
|
||||
onPress={navigateToSource}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
>
|
||||
<Image source={{uri: icon}} style={styles.extensionIcon} />
|
||||
<Image source={{uri: icon}} style={styles.icon} />
|
||||
<View style={styles.extensionDetails}>
|
||||
<View>
|
||||
<Text
|
||||
@@ -46,7 +49,7 @@ const ExtensionCard = ({item, theme, isPinned, dispatch, navigation}) => {
|
||||
labelStyle={{letterSpacing: 0}}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={navigateToExtension}
|
||||
onPress={navigateToSource}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
@@ -64,17 +67,17 @@ const ExtensionCard = ({item, theme, isPinned, dispatch, navigation}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtensionCard;
|
||||
export default SourceItem;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
extensionCard: {
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
extensionIcon: {
|
||||
icon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
@@ -4,7 +4,7 @@ import {StyleSheet, View, FlatList, Text} from 'react-native';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {Appbar} from '../../../components/Appbar';
|
||||
import MigrationSourceCard from './MigrationSourceCard';
|
||||
import MigrationSourceItem from './MigrationSourceItem';
|
||||
|
||||
import {useLibrary, useTheme} from '../../../hooks/reduxHooks';
|
||||
|
||||
@@ -23,11 +23,10 @@ const GlobalSearch = ({navigation}) => {
|
||||
);
|
||||
|
||||
const renderItem = ({item}) => (
|
||||
<MigrationSourceCard
|
||||
<MigrationSourceItem
|
||||
item={item}
|
||||
theme={theme}
|
||||
noOfNovels={novelsPerSource(item.sourceId)}
|
||||
buttonLabel="All"
|
||||
onPress={() => navigation.navigate('SourceNovels', item.sourceId)}
|
||||
/>
|
||||
);
|
||||
@@ -43,6 +42,7 @@ const GlobalSearch = ({navigation}) => {
|
||||
<Appbar title="Select Source" onBackAction={navigation.goBack} />
|
||||
<FlatList
|
||||
data={sources}
|
||||
contentContainerStyle={{paddingBottom: 48}}
|
||||
keyExtractor={item => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
@@ -57,11 +57,7 @@ const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
containerStyle: {
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
},
|
||||
|
||||
listHeader: {
|
||||
padding: 20,
|
||||
paddingBottom: 10,
|
||||
|
||||
@@ -15,11 +15,8 @@ const MigrationNovelList = ({data, theme, library, navigation}) => {
|
||||
const showMigrateNovelDialog = () => setMigrateNovelDialog(true);
|
||||
const hideMigrateNovelDialog = () => setMigrateNovelDialog(false);
|
||||
|
||||
const inLibrary = (sourceId, novelUrl) => {
|
||||
return library.some(
|
||||
obj => obj.novelUrl === novelUrl && obj.sourceId === sourceId,
|
||||
);
|
||||
};
|
||||
const inLibrary = (sourceId, novelUrl) =>
|
||||
library.some(obj => obj.novelUrl === novelUrl && obj.sourceId === sourceId);
|
||||
|
||||
const renderItem = ({item}) => (
|
||||
<GlobalSearchNovelCover
|
||||
|
||||
@@ -15,7 +15,6 @@ import MigrationNovelList from './MigrationNovelList';
|
||||
import {Appbar} from '../../../components/Appbar';
|
||||
|
||||
import {showToast} from '../../../hooks/showToast';
|
||||
import {migrateNovel} from '../../../database/queries/NovelQueries';
|
||||
import {ScreenContainer} from '../../../components/Common';
|
||||
import {getSource} from '../../../sources/sources';
|
||||
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Dimensions, View, Text, StyleSheet} from 'react-native';
|
||||
|
||||
import Slider from '@react-native-community/slider';
|
||||
import {getDeviceOrientation} from '../../../services/utils/helpers';
|
||||
|
||||
const ReaderSeekbar = ({
|
||||
theme,
|
||||
hide,
|
||||
contentSize,
|
||||
scrollPercentage,
|
||||
setLoading,
|
||||
scrollViewRef,
|
||||
}) => {
|
||||
const onSlidingComplete = value => {
|
||||
setLoading(true);
|
||||
scrollViewRef.current.scrollTo({
|
||||
x: 0,
|
||||
y: Math.round((value * contentSize) / 100),
|
||||
animated: false,
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
} else if (getDeviceOrientation() === 'potrait') {
|
||||
return (
|
||||
<View style={styles.verticalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginLeft: 16,
|
||||
transform: [{rotate: '-90deg'}],
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginRight: 16,
|
||||
transform: [{rotate: '-90deg'}],
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.horizontalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginLeft: 16,
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ReaderSeekbar;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
verticalSliderContainer: {
|
||||
position: 'absolute',
|
||||
zIndex: 2,
|
||||
right: -(Dimensions.get('window').width / 2) + 40,
|
||||
bottom: 300,
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
transform: [{rotate: '90deg'}],
|
||||
},
|
||||
horizontalSliderContainer: {
|
||||
position: 'absolute',
|
||||
zIndex: 2,
|
||||
bottom: 100,
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import WebView from 'react-native-webview';
|
||||
|
||||
export const WebViewReader = ({style, css, text, onScroll}) => (
|
||||
<WebView
|
||||
style={style}
|
||||
originWhitelist={['*']}
|
||||
scalesPageToFit={true}
|
||||
showsVerticalScrollIndicator={false}
|
||||
onScroll={onScroll}
|
||||
source={{
|
||||
html: `
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||
<style>
|
||||
${css}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${text}
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -12,18 +12,20 @@ import {useFocusEffect} from '@react-navigation/native';
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import EmptyView from '../../components/EmptyView';
|
||||
import HistoryItem from './HistoryItem';
|
||||
import RemoveHistoryDialog from './RemoveHistoryDialog';
|
||||
import {ScreenContainer} from '../../components/Common';
|
||||
import HistoryItem from './components/HistoryItem';
|
||||
import RemoveHistoryDialog from './components/RemoveHistoryDialog';
|
||||
import {Searchbar} from '../../components/Searchbar/Searchbar';
|
||||
|
||||
import {dateFormat} from '../../services/utils/constants';
|
||||
import {useTheme} from '../../hooks/reduxHooks';
|
||||
import {
|
||||
deleteAllHistory,
|
||||
deleteHistory,
|
||||
getHistoryFromDb,
|
||||
} from '../../database/queries/HistoryQueries';
|
||||
|
||||
import {useModal} from '../../hooks/useModal';
|
||||
|
||||
const History = ({navigation}) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
@@ -35,50 +37,26 @@ const History = ({navigation}) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const getHistory = async () => {
|
||||
const history = await getHistoryFromDb();
|
||||
const rawHistory = await getHistoryFromDb();
|
||||
|
||||
const groups = history.reduce((groups, update) => {
|
||||
var dateParts = update.historyTimeRead.split('-');
|
||||
var jsDate = new Date(
|
||||
dateParts[0],
|
||||
dateParts[1] - 1,
|
||||
dateParts[2].substr(0, 2),
|
||||
);
|
||||
const date = jsDate.toISOString();
|
||||
if (!groups[date]) {
|
||||
groups[date] = [];
|
||||
}
|
||||
groups[date].push(update);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
const groupedHistory = Object.keys(groups).map(date => {
|
||||
return {
|
||||
date,
|
||||
novels: groups[date],
|
||||
};
|
||||
});
|
||||
|
||||
setHistory(groupedHistory);
|
||||
setHistory(rawHistory);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Confirm Clear History Dialog
|
||||
*/
|
||||
const [visible, setVisible] = useState(false);
|
||||
const showDialog = () => setVisible(true);
|
||||
const hideDialog = () => setVisible(false);
|
||||
const removeHistoryModal = useModal();
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
getHistory();
|
||||
}, [visible]),
|
||||
}, []),
|
||||
);
|
||||
|
||||
const deleteChapterHistory = historyId => {
|
||||
deleteHistory(historyId);
|
||||
getHistory();
|
||||
setHistory(history.filter(item => item.historyId !== historyId));
|
||||
};
|
||||
|
||||
const renderHistoryCard = ({item}) => (
|
||||
@@ -100,22 +78,39 @@ const History = ({navigation}) => {
|
||||
setSearchText(text);
|
||||
let results = [];
|
||||
|
||||
text !== '' &&
|
||||
history.map(item => {
|
||||
const date = item.date;
|
||||
const novels = item.novels.filter(novel =>
|
||||
novel.novelName.toLowerCase().includes(text.toLowerCase()),
|
||||
);
|
||||
if (text !== '') {
|
||||
results = history.filter(novel =>
|
||||
novel.novelName.toLowerCase().includes(text.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
if (novels.length > 0) {
|
||||
results.push({
|
||||
date,
|
||||
novels,
|
||||
});
|
||||
}
|
||||
});
|
||||
setSearchResults(groupByDate(results));
|
||||
};
|
||||
|
||||
setSearchResults(results);
|
||||
const groupByDate = rawHistory => {
|
||||
const groups = rawHistory.reduce((groups, update) => {
|
||||
var dateParts = update.historyTimeRead.split('-');
|
||||
var jsDate = new Date(
|
||||
dateParts[0],
|
||||
dateParts[1] - 1,
|
||||
dateParts[2].substr(0, 2),
|
||||
);
|
||||
const date = jsDate.toISOString();
|
||||
if (!groups[date]) {
|
||||
groups[date] = [];
|
||||
}
|
||||
groups[date].push(update);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
const groupedHistory = Object.keys(groups).map(date => {
|
||||
return {
|
||||
date,
|
||||
novels: groups[date],
|
||||
};
|
||||
});
|
||||
|
||||
return groupedHistory;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -130,13 +125,13 @@ const History = ({navigation}) => {
|
||||
actions={[
|
||||
{
|
||||
icon: 'delete-sweep',
|
||||
onPress: showDialog,
|
||||
onPress: removeHistoryModal.showModal,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<FlatList
|
||||
contentContainerStyle={{paddingBottom: 40}}
|
||||
data={searchText ? searchResults : history}
|
||||
contentContainerStyle={styles.container}
|
||||
data={searchText ? searchResults : groupByDate(history)}
|
||||
keyExtractor={item => item.date}
|
||||
renderItem={({item}) => (
|
||||
<FlatList
|
||||
@@ -145,12 +140,7 @@ const History = ({navigation}) => {
|
||||
renderItem={renderHistoryCard}
|
||||
ListHeaderComponent={
|
||||
<Text
|
||||
style={{
|
||||
textTransform: 'uppercase',
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 16,
|
||||
color: theme.textColorSecondary,
|
||||
}}
|
||||
style={[styles.dateHeader, {color: theme.textColorSecondary}]}
|
||||
>
|
||||
{moment(item.date).calendar(null, dateFormat)}
|
||||
</Text>
|
||||
@@ -173,9 +163,14 @@ const History = ({navigation}) => {
|
||||
}
|
||||
/>
|
||||
<RemoveHistoryDialog
|
||||
dialogVisible={visible}
|
||||
hideDialog={hideDialog}
|
||||
dialogVisible={removeHistoryModal.visible}
|
||||
hideDialog={removeHistoryModal.hideModal}
|
||||
theme={theme}
|
||||
onPress={() => {
|
||||
deleteAllHistory();
|
||||
setHistory([]);
|
||||
removeHistoryModal.hideModal();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -183,4 +178,14 @@ const History = ({navigation}) => {
|
||||
|
||||
export default History;
|
||||
|
||||
const styles = StyleSheet.create({});
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
paddingBottom: 40,
|
||||
},
|
||||
dateHeader: {
|
||||
textTransform: 'uppercase',
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
});
|
||||
|
||||
+43
-46
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, {useCallback} from 'react';
|
||||
import {Pressable, StyleSheet, View, Image, Text} from 'react-native';
|
||||
|
||||
import moment from 'moment';
|
||||
import {IconButton} from 'react-native-paper';
|
||||
|
||||
import {parseChapterNumber} from '../../services/utils/helpers';
|
||||
import {setNovel} from '../../redux/novel/novel.actions';
|
||||
import {parseChapterNumber} from '../../../services/utils/helpers';
|
||||
import {setNovel} from '../../../redux/novel/novel.actions';
|
||||
|
||||
const HistoryItem = ({history, theme, dispatch, navigation, deleteHistory}) => {
|
||||
const {
|
||||
@@ -22,10 +22,13 @@ const HistoryItem = ({history, theme, dispatch, navigation, deleteHistory}) => {
|
||||
bookmark,
|
||||
} = history;
|
||||
|
||||
const getChapterNumber = () =>
|
||||
`Chapter ${parseChapterNumber(chapterName)} • ${moment(historyTimeRead)
|
||||
.format('h:mm a')
|
||||
.toUpperCase()}`;
|
||||
const getChapterNumber = useCallback(
|
||||
() =>
|
||||
`Chapter ${parseChapterNumber(chapterName)} • ${moment(historyTimeRead)
|
||||
.format('h:mm a')
|
||||
.toUpperCase()}`,
|
||||
[],
|
||||
);
|
||||
|
||||
const navigateToNovel = () => {
|
||||
navigation.navigate('Novel', history);
|
||||
@@ -44,59 +47,43 @@ const HistoryItem = ({history, theme, dispatch, navigation, deleteHistory}) => {
|
||||
bookmark,
|
||||
});
|
||||
|
||||
const getNovelCover = useCallback(
|
||||
() =>
|
||||
novelCover && !novelCover.startsWith('/')
|
||||
? novelCover
|
||||
: 'https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true',
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={navigateToNovel}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
style={styles.pressable}
|
||||
>
|
||||
<View style={styles.historyItemContainer}>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<View style={styles.content}>
|
||||
<View style={styles.container}>
|
||||
<View>
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
novelCover && !novelCover.startsWith('/')
|
||||
? novelCover
|
||||
: 'https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true',
|
||||
}}
|
||||
style={styles.historyItemCover}
|
||||
/>
|
||||
<Image source={{uri: getNovelCover()}} style={styles.novelCover} />
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
marginLeft: 16,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<View style={styles.textContainer}>
|
||||
<Text
|
||||
style={[{color: theme.textColorPrimary}, styles.historyItemTitle]}
|
||||
style={[{color: theme.textColorPrimary}, styles.chapterTitle]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{novelName}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginTop: 2,
|
||||
}}
|
||||
style={[{color: theme.textColorSecondary}, styles.chapterNumber]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{getChapterNumber()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.historyActionsContainer}>
|
||||
<View style={styles.buttons}>
|
||||
<IconButton
|
||||
icon="delete-outline"
|
||||
size={22}
|
||||
size={24}
|
||||
color={theme.textColorPrimary}
|
||||
onPress={() => deleteHistory(historyId)}
|
||||
/>
|
||||
@@ -115,27 +102,37 @@ const HistoryItem = ({history, theme, dispatch, navigation, deleteHistory}) => {
|
||||
export default HistoryItem;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
historyItemContainer: {
|
||||
content: {
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
pressable: {
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
historyItemCover: {
|
||||
novelCover: {
|
||||
height: 80,
|
||||
width: 57,
|
||||
width: 56,
|
||||
borderRadius: 4,
|
||||
},
|
||||
historyItemTitle: {
|
||||
fontSize: 16,
|
||||
textContainer: {
|
||||
flexShrink: 1,
|
||||
marginLeft: 16,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
chapterTitle: {
|
||||
fontSize: 15,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
historyActionsContainer: {
|
||||
buttons: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
chapterNumber: {
|
||||
marginTop: 4,
|
||||
},
|
||||
});
|
||||
+2
-10
@@ -1,23 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
import {Button, Dialog, Portal} from 'react-native-paper';
|
||||
import {deleteAllHistory} from '../../database/queries/HistoryQueries';
|
||||
import {showToast} from '../../hooks/showToast';
|
||||
|
||||
const RemoveHistoryDialog = ({dialogVisible, hideDialog, theme}) => {
|
||||
const onPress = () => {
|
||||
deleteAllHistory();
|
||||
showToast('History deleted.');
|
||||
hideDialog();
|
||||
};
|
||||
|
||||
const RemoveHistoryDialog = ({dialogVisible, hideDialog, theme, onPress}) => {
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog
|
||||
visible={dialogVisible}
|
||||
onDismiss={hideDialog}
|
||||
style={{
|
||||
borderRadius: 6,
|
||||
borderRadius: 8,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
}}
|
||||
>
|
||||
@@ -26,7 +26,7 @@ import {updateLibraryAction} from '../../redux/updates/updates.actions';
|
||||
import {useSettings, useTheme} from '../../hooks/reduxHooks';
|
||||
import {setNovel} from '../../redux/novel/novel.actions';
|
||||
import {Portal} from 'react-native-paper';
|
||||
import LibraryFilterSheet from './components/LibraryFilterSheet';
|
||||
import LibraryBottomSheet from './components/LibraryBottomSheet';
|
||||
import {Actionbar} from '../../components/Actionbar/Actionbar';
|
||||
import {
|
||||
markAllChaptersRead,
|
||||
@@ -45,7 +45,7 @@ const LibraryScreen = ({navigation}) => {
|
||||
downloadedOnlyMode = false,
|
||||
} = settings;
|
||||
|
||||
const libraryFilterSheetRef = useRef(null);
|
||||
const libraryBottomSheetRef = useRef(null);
|
||||
|
||||
const {
|
||||
loading,
|
||||
@@ -152,7 +152,7 @@ const LibraryScreen = ({navigation}) => {
|
||||
actions={[
|
||||
{
|
||||
icon: 'filter-variant',
|
||||
onPress: () => libraryFilterSheetRef.current.show(),
|
||||
onPress: () => libraryBottomSheetRef.current.show(),
|
||||
color:
|
||||
filter || downloadedOnlyMode
|
||||
? theme.filterColor
|
||||
@@ -271,8 +271,8 @@ const LibraryScreen = ({navigation}) => {
|
||||
]}
|
||||
/>
|
||||
<Portal>
|
||||
<LibraryFilterSheet
|
||||
bottomSheetRef={libraryFilterSheetRef}
|
||||
<LibraryBottomSheet
|
||||
bottomSheetRef={libraryBottomSheetRef}
|
||||
theme={theme}
|
||||
dispatch={dispatch}
|
||||
sort={sort}
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
downloadChapterAction,
|
||||
} from '../../redux/novel/novel.actions';
|
||||
|
||||
import UpdatesItem from '../Updates/components/UpdatesItem';
|
||||
import UpdatesItem from '../updates/components/UpdatesItem';
|
||||
import RemoveDownloadsDialog from './components/RemoveDownloadsDialog';
|
||||
|
||||
const Downloads = ({navigation}) => {
|
||||
|
||||
@@ -46,7 +46,7 @@ import {showToast} from '../../hooks/showToast';
|
||||
|
||||
import ChapterItem from './components/ChapterItem';
|
||||
import NovelInfoHeader from './components/Info/NovelInfoHeader';
|
||||
import ChaptersSettingsSheet from './components/ChaptersSettingsSheet';
|
||||
import NovelBottomSheet from './components/NovelBottomSheet';
|
||||
import TrackSheet from './components/Tracker/TrackSheet';
|
||||
import {Row} from '../../components/Common';
|
||||
import JumpToChapterModal from './components/JumpToChapterModal';
|
||||
@@ -69,7 +69,7 @@ const Novel = ({route, navigation}) => {
|
||||
const [extraMenu, showExtraMenu] = useState(false);
|
||||
|
||||
let flatlistRef = useRef(null);
|
||||
let chaptersSettingsSheetRef = useRef(null);
|
||||
let novelBottomSheetRef = useRef(null);
|
||||
let trackerSheetRef = useRef(null);
|
||||
|
||||
const {
|
||||
@@ -448,7 +448,7 @@ const Novel = ({route, navigation}) => {
|
||||
chapters={chapters}
|
||||
navigation={navigation}
|
||||
trackerSheetRef={trackerSheetRef}
|
||||
chaptersSettingsSheetRef={chaptersSettingsSheetRef}
|
||||
novelBottomSheetRef={novelBottomSheetRef}
|
||||
/>
|
||||
}
|
||||
refreshControl={refreshControl()}
|
||||
@@ -559,9 +559,9 @@ const Novel = ({route, navigation}) => {
|
||||
theme={theme}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<ChaptersSettingsSheet
|
||||
<NovelBottomSheet
|
||||
novelUrl={novelUrl}
|
||||
bottomSheetRef={chaptersSettingsSheetRef}
|
||||
bottomSheetRef={novelBottomSheetRef}
|
||||
dispatch={dispatch}
|
||||
sortAndFilterChapters={sortAndFilterChapters}
|
||||
novelId={novel.novelId}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {LinearGradient} from 'expo-linear-gradient';
|
||||
import easeGradient from 'react-native-easing-gradient';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import {TouchableWithoutFeedback} from 'react-native-gesture-handler';
|
||||
import {Chip} from '../../../../components/Chip/Chip';
|
||||
|
||||
const NovelInfoContainer = ({children}) => (
|
||||
<View style={styles.novelInfoContainer}>{children}</View>
|
||||
@@ -199,28 +200,14 @@ const TrackerButton = ({theme, isTracked, onPress}) => (
|
||||
</View>
|
||||
);
|
||||
|
||||
const GenreChip = ({children, theme}) => (
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
backgroundColor: theme.dividerColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const NovelGenres = ({theme, genre}) => {
|
||||
const data = genre.split(',');
|
||||
|
||||
const renderItem = ({item}) => <GenreChip theme={theme}>{item}</GenreChip>;
|
||||
const renderItem = ({item}) => <Chip label={item} theme={theme} />;
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.novelGenres}
|
||||
contentContainerStyle={styles.genreContainer}
|
||||
horizontal
|
||||
data={data}
|
||||
keyExtractor={item => item}
|
||||
@@ -282,9 +269,9 @@ const styles = StyleSheet.create({
|
||||
borderWidth: 0,
|
||||
elevation: 0,
|
||||
},
|
||||
novelGenres: {
|
||||
genreContainer: {
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
paddingBottom: 4,
|
||||
},
|
||||
genreChip: {
|
||||
flex: 1,
|
||||
|
||||
@@ -24,14 +24,14 @@ import {Row} from '../../../../components/Common';
|
||||
import NovelSummary from './NovelSummary';
|
||||
import ReadButton from './ReadButton';
|
||||
|
||||
// const getStatusIcon = (status) => {
|
||||
// const icons = {
|
||||
// Ongoing: "clock-outline",
|
||||
// Completed: "check-all",
|
||||
// Unknown: "help",
|
||||
// };
|
||||
// const getStatusIcon = status => {
|
||||
// const icons = {
|
||||
// Ongoing: 'clock-outline',
|
||||
// Completed: 'check-all',
|
||||
// Unknown: 'help',
|
||||
// };
|
||||
|
||||
// return icons[status] || icons.Unknown;
|
||||
// return icons[status] || icons.Unknown;
|
||||
// };
|
||||
|
||||
const NovelInfoHeader = ({
|
||||
@@ -46,7 +46,7 @@ const NovelInfoHeader = ({
|
||||
navigation,
|
||||
trackerSheetRef,
|
||||
setCustomNovelCover,
|
||||
chaptersSettingsSheetRef,
|
||||
novelBottomSheetRef,
|
||||
}) => {
|
||||
const {tracker, trackedNovels} = useTrackingStatus();
|
||||
|
||||
@@ -111,15 +111,11 @@ const NovelInfoHeader = ({
|
||||
</NovelAuthor>
|
||||
<Row>
|
||||
{/* <MaterialCommunityIcons
|
||||
name={
|
||||
loading
|
||||
? "help"
|
||||
: getStatusIcon(novel.status)
|
||||
}
|
||||
size={16}
|
||||
color={theme.textColorSecondary}
|
||||
style={{ marginRight: 4 }}
|
||||
/> */}
|
||||
name={loading ? 'help' : getStatusIcon(novel.status)}
|
||||
size={16}
|
||||
color={theme.textColorSecondary}
|
||||
style={{marginRight: 4}}
|
||||
/> */}
|
||||
<NovelInfo theme={theme}>
|
||||
{!loading
|
||||
? (novel.status || 'Unknown status') + ' • ' + novel.source
|
||||
@@ -245,7 +241,7 @@ const NovelInfoHeader = ({
|
||||
/>
|
||||
<Pressable
|
||||
style={styles.bottomsheet}
|
||||
onPress={() => chaptersSettingsSheetRef.current.show()}
|
||||
onPress={() => novelBottomSheetRef.current.show()}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
>
|
||||
<>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import {View, Text, StyleSheet} from 'react-native';
|
||||
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
|
||||
@@ -60,5 +60,6 @@ const styles = StyleSheet.create({
|
||||
marginTop: 16,
|
||||
marginBottom: 8,
|
||||
marginHorizontal: 16,
|
||||
borderRadius: 50,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ const Updates = ({navigation}) => {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
dispatch(getUpdatesAction());
|
||||
}, [getUpdatesAction]),
|
||||
}, []),
|
||||
);
|
||||
|
||||
const onRefresh = async () => {
|
||||
@@ -125,9 +125,7 @@ const Updates = ({navigation}) => {
|
||||
/>
|
||||
);
|
||||
|
||||
const clearSearchbar = () => {
|
||||
setSearchText('');
|
||||
};
|
||||
const clearSearchbar = () => setSearchText('');
|
||||
|
||||
const onChangeText = text => {
|
||||
setSearchText(text);
|
||||
@@ -171,13 +169,7 @@ const Updates = ({navigation}) => {
|
||||
/>
|
||||
{showLastUpdateTime && lastUpdateTime && (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 4,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
style={[styles.lastUpdateTime, {color: theme.textColorSecondary}]}
|
||||
>
|
||||
Library last updated: {moment(lastUpdateTime).fromNow()}
|
||||
</Text>
|
||||
@@ -216,6 +208,17 @@ const Updates = ({navigation}) => {
|
||||
export default Updates;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {flex: 1},
|
||||
flatList: {flexGrow: 1, paddingBottom: 8},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
flatList: {
|
||||
flexGrow: 1,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
lastUpdateTime: {
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 4,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ChapterDownloadingButton,
|
||||
DeleteChapterButton,
|
||||
DownloadChapterButton,
|
||||
} from '../../Novel/components/Chapter/ChapterDownloadButtons';
|
||||
} from '../../novel/components/Chapter/ChapterDownloadButtons';
|
||||
|
||||
export const DownloadButton = ({
|
||||
downloadQueue,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useState} from 'react';
|
||||
import React, {memo, useState} from 'react';
|
||||
import {View, Text, StyleSheet, Pressable} from 'react-native';
|
||||
|
||||
import FastImage from 'react-native-fast-image';
|
||||
@@ -83,7 +83,7 @@ const UpdateCard = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateCard;
|
||||
export default memo(UpdateCard);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
updateCard: {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
ScrollView,
|
||||
TouchableWithoutFeedback,
|
||||
Dimensions,
|
||||
Pressable,
|
||||
} from 'react-native';
|
||||
|
||||
import {useDispatch} from 'react-redux';
|
||||
@@ -17,7 +16,6 @@ import changeNavigationBarColor, {
|
||||
hideNavigationBar,
|
||||
showNavigationBar,
|
||||
} from 'react-native-navigation-bar-color';
|
||||
import WebView from 'react-native-webview';
|
||||
|
||||
import {
|
||||
getChapterFromDB,
|
||||
@@ -34,31 +32,29 @@ import {
|
||||
useTrackingStatus,
|
||||
} from '../../hooks/reduxHooks';
|
||||
import {updateChaptersRead} from '../../redux/tracker/tracker.actions';
|
||||
import {
|
||||
readerBackground,
|
||||
readerLineHeight,
|
||||
readerTextColor,
|
||||
} from './readerStyleController';
|
||||
import {readerBackground, readerTextColor} from './utils/readerStyles';
|
||||
import {markChapterReadAction} from '../../redux/novel/novel.actions';
|
||||
import {saveScrollPosition} from '../../redux/preferences/preference.actions';
|
||||
import {parseChapterNumber} from '../../services/updates';
|
||||
|
||||
import ChapterAppbar from './components/ChapterAppbar';
|
||||
import ReaderSheet from './components/ReaderSheet';
|
||||
import ReaderAppbar from './components/ReaderAppbar';
|
||||
import ReaderBottomSheet from './components/ReaderBottomSheet/ReaderBottomSheet';
|
||||
import ReaderFooter from './components/ReaderFooter';
|
||||
import ReaderSeekBar from './components/ReaderSeekBar';
|
||||
|
||||
import EmptyView from '../../components/EmptyView';
|
||||
|
||||
import ChapterFooter from './components/ChapterFooter';
|
||||
import VerticalScrollbar from './components/VerticalScrollbar';
|
||||
import GestureRecognizer from 'react-native-swipe-gestures';
|
||||
import {LoadingScreen} from '../../components/LoadingScreen/LoadingScreen';
|
||||
import {insertHistory} from '../../database/queries/HistoryQueries';
|
||||
import {SET_LAST_READ} from '../../redux/preferences/preference.types';
|
||||
import {htmlToText} from '../../sources/helpers/htmlToText';
|
||||
import {setAppSettings} from '../../redux/settings/settings.actions';
|
||||
import {cleanHtml} from '../../sources/helpers/cleanHtml';
|
||||
import {useBatteryLevel} from 'react-native-device-info';
|
||||
import moment from 'moment';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import WebViewReader from './components/WebViewReader';
|
||||
import TextReader from './components/TextReader';
|
||||
|
||||
const Chapter = ({route, navigation}) => {
|
||||
const {
|
||||
@@ -373,18 +369,6 @@ const Chapter = ({route, navigation}) => {
|
||||
setHidden(!hidden);
|
||||
};
|
||||
|
||||
const readerStyles = [
|
||||
{
|
||||
fontSize: reader.textSize,
|
||||
color: reader.textColor || readerTextColor(reader.theme),
|
||||
lineHeight: readerLineHeight(reader.textSize, reader.lineHeight),
|
||||
textAlign: reader.textAlign,
|
||||
},
|
||||
reader.fontFamily && {
|
||||
fontFamily: reader.fontFamily,
|
||||
},
|
||||
];
|
||||
|
||||
const config = {
|
||||
velocityThreshold: 0.3,
|
||||
directionalOffsetThreshold: 80,
|
||||
@@ -431,10 +415,19 @@ const Chapter = ({route, navigation}) => {
|
||||
const enableWebView = () =>
|
||||
dispatch(setAppSettings('useWebViewForChapter', !useWebViewForChapter));
|
||||
|
||||
const onWebViewNavigationStateChange = async ({url}) => {
|
||||
if ((sourceId === 50 || sourceId == 62) && url !== 'about:blank') {
|
||||
setLoading(true);
|
||||
const res = await fetchChapter(sourceId, novelUrl, url);
|
||||
setChapter(res);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
<ChapterAppbar
|
||||
<ReaderAppbar
|
||||
novelName={novelName}
|
||||
chapterName={chapterName}
|
||||
chapterId={chapterId}
|
||||
@@ -502,160 +495,36 @@ const Chapter = ({route, navigation}) => {
|
||||
onPress={hideHeader}
|
||||
onLayout={scrollToSavedProgress}
|
||||
>
|
||||
{useWebViewForChapter ? (
|
||||
<WebView
|
||||
style={{
|
||||
backgroundColor: readerBackground(reader.theme),
|
||||
}}
|
||||
originWhitelist={['*']}
|
||||
scalesPageToFit={true}
|
||||
showsVerticalScrollIndicator={false}
|
||||
onScroll={onScroll}
|
||||
onNavigationStateChange={async ({url}) => {
|
||||
if (
|
||||
(sourceId === 50 || sourceId == 62) &&
|
||||
url !== 'about:blank'
|
||||
) {
|
||||
setLoading(true);
|
||||
const res = await fetchChapter(sourceId, novelUrl, url);
|
||||
setChapter(res);
|
||||
setLoading(false);
|
||||
<View style={{flex: 1}}>
|
||||
{useWebViewForChapter ? (
|
||||
<WebViewReader
|
||||
html={chapter.chapterText}
|
||||
reader={reader}
|
||||
theme={theme}
|
||||
onScroll={onScroll}
|
||||
onWebViewNavigationStateChange={
|
||||
onWebViewNavigationStateChange
|
||||
}
|
||||
}}
|
||||
source={{
|
||||
html: `
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||
<style>
|
||||
html {
|
||||
overflow-x: hidden;
|
||||
padding-top: ${StatusBar.currentHeight};
|
||||
word-wrap: break-word;
|
||||
}
|
||||
body {
|
||||
padding-left: ${reader.padding}%;
|
||||
padding-right: ${reader.padding}%;
|
||||
padding-bottom: 30px;
|
||||
font-size: ${reader.textSize}px;
|
||||
color: ${reader.textColor};
|
||||
|
||||
text-align: ${reader.textAlign};
|
||||
line-height: ${reader.lineHeight};
|
||||
font-family: ${reader.fontFamily};
|
||||
}
|
||||
hr {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
a {
|
||||
color: ${theme.colorAccent};
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
${reader.customCSS}
|
||||
@font-face {
|
||||
font-family: ${reader.fontFamily};
|
||||
src: url("file:///android_asset/fonts/${
|
||||
reader.fontFamily
|
||||
}.ttf");
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${cleanHtml(chapter.chapterText)}
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
}}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: `${reader.padding}%`,
|
||||
paddingTop: StatusBar.currentHeight,
|
||||
}}
|
||||
>
|
||||
<Text style={readerStyles} selectable={textSelectable}>
|
||||
{htmlToText(chapter.chapterText)}
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={{
|
||||
marginTop: 32,
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: reader.textColor,
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Finished: {chapterName}
|
||||
</Text>
|
||||
{nextChapter ? (
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
marginVertical: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
android_ripple={{
|
||||
color: theme.rippleColor,
|
||||
}}
|
||||
onPress={navigateToNextChapter}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: reader.textColor,
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Next: {nextChapter.chapterName}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : (
|
||||
<Text
|
||||
style={{
|
||||
color: reader.textColor,
|
||||
fontSize: 16,
|
||||
paddingVertical: 8,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
There's no next chapter
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<TextReader
|
||||
text={chapter.chapterText}
|
||||
reader={reader}
|
||||
chapterName={chapterName}
|
||||
textSelectable={textSelectable}
|
||||
theme={theme}
|
||||
nextChapter={nextChapter}
|
||||
navigateToNextChapter={navigateToNextChapter}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
)}
|
||||
</ScrollView>
|
||||
</GestureRecognizer>
|
||||
|
||||
<Portal>
|
||||
<ReaderSheet
|
||||
<ReaderBottomSheet
|
||||
theme={theme}
|
||||
reader={reader}
|
||||
dispatch={dispatch}
|
||||
@@ -672,7 +541,7 @@ const Chapter = ({route, navigation}) => {
|
||||
/>
|
||||
</Portal>
|
||||
{!useWebViewForChapter && (
|
||||
<VerticalScrollbar
|
||||
<ReaderSeekBar
|
||||
theme={theme}
|
||||
hide={hidden}
|
||||
setLoading={setLoading}
|
||||
@@ -682,7 +551,7 @@ const Chapter = ({route, navigation}) => {
|
||||
setScrollPercentage={setScrollPercentage}
|
||||
/>
|
||||
)}
|
||||
<ChapterFooter
|
||||
<ReaderFooter
|
||||
theme={theme}
|
||||
swipeGestures={swipeGestures}
|
||||
enableSwipeGestures={enableSwipeGestures}
|
||||
+45
-209
@@ -1,37 +1,32 @@
|
||||
import React, {memo, useCallback, useState} from 'react';
|
||||
import React, {memo, useState} from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
Dimensions,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
useWindowDimensions,
|
||||
Animated,
|
||||
} from 'react-native';
|
||||
|
||||
import {TabView, SceneMap, TabBar} from 'react-native-tab-view';
|
||||
import Slider from '@react-native-community/slider';
|
||||
import {IconButton, Menu, Switch} from 'react-native-paper';
|
||||
import {IconButton, Switch} from 'react-native-paper';
|
||||
import Bottomsheet from 'rn-sliding-up-panel';
|
||||
|
||||
import BottomSheetHandle from '../../../components/BottomSheetHandle';
|
||||
import {Row} from '../../../components/Common';
|
||||
import BottomSheetHandle from '../../../../components/BottomSheetHandle';
|
||||
import {Row} from '../../../../components/Common';
|
||||
|
||||
import {fonts} from '../../../services/utils/constants';
|
||||
import {fonts} from '../../../../services/utils/constants';
|
||||
|
||||
import {
|
||||
setAppSettings,
|
||||
setReaderSettings,
|
||||
} from '../../../redux/settings/settings.actions';
|
||||
} from '../../../../redux/settings/settings.actions';
|
||||
import {
|
||||
ToggleButton,
|
||||
ToggleColorButton,
|
||||
} from '../../../components/Common/ToggleButton';
|
||||
|
||||
const ReaderSettingTitle = ({title}) => (
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
);
|
||||
} from '../../../../components/Common/ToggleButton';
|
||||
import {ReaderBottomSheetSwitch} from './components/ReaderBottomSheetSwitch';
|
||||
import {ReaderBottomSheetFontPicker} from './components/ReaderBottomSheetFontPicker';
|
||||
|
||||
const ReaderSheet = ({
|
||||
theme,
|
||||
@@ -250,230 +245,71 @@ const ReaderSheet = ({
|
||||
/>
|
||||
</Row>
|
||||
</Row>
|
||||
<Row
|
||||
style={{
|
||||
justifyContent: 'space-between',
|
||||
marginVertical: 6,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
Font style
|
||||
</Text>
|
||||
<ScrollView
|
||||
horizontal={true}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{alignItems: 'center'}}
|
||||
>
|
||||
{fonts.map(font => (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
marginHorizontal: 6,
|
||||
},
|
||||
reader.fontFamily === font.fontFamily && {
|
||||
backgroundColor: theme.rippleColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
}}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setReaderSettings('fontFamily', font.fontFamily),
|
||||
)
|
||||
}
|
||||
android_ripple={{
|
||||
color: theme.rippleColor,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontFamily: font.fontFamily,
|
||||
color:
|
||||
reader.fontFamily === font.fontFamily
|
||||
? theme.colorAccent
|
||||
: theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{font.name}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</Row>
|
||||
<ReaderBottomSheetFontPicker reader={reader} theme={theme} />
|
||||
</View>
|
||||
<Pressable
|
||||
style={styles.switchStyle}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
<ReaderBottomSheetSwitch
|
||||
label="Use WebView"
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings('useWebViewForChapter', !useWebViewForChapter),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
}}
|
||||
>
|
||||
Use WebView
|
||||
</Text>
|
||||
<Switch
|
||||
value={useWebViewForChapter}
|
||||
onValueChange={() =>
|
||||
dispatch(
|
||||
setAppSettings('useWebViewForChapter', !useWebViewForChapter),
|
||||
)
|
||||
}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={styles.switchStyle}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
value={useWebViewForChapter}
|
||||
theme={theme}
|
||||
/>
|
||||
<ReaderBottomSheetSwitch
|
||||
label="Fullscreen"
|
||||
onPress={() =>
|
||||
dispatch(setAppSettings('fullScreenMode', !fullScreenMode))
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
}}
|
||||
>
|
||||
Fullscreen
|
||||
</Text>
|
||||
<Switch
|
||||
value={fullScreenMode}
|
||||
onValueChange={() =>
|
||||
dispatch(setAppSettings('fullScreenMode', !fullScreenMode))
|
||||
}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</Pressable>
|
||||
value={fullScreenMode}
|
||||
theme={theme}
|
||||
/>
|
||||
{!useWebViewForChapter && (
|
||||
<Pressable
|
||||
style={styles.switchStyle}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
<ReaderBottomSheetSwitch
|
||||
label="AutoScroll"
|
||||
onPress={() =>
|
||||
dispatch(setAppSettings('autoScroll', !autoScroll))
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
}}
|
||||
>
|
||||
AutoScroll
|
||||
</Text>
|
||||
<Switch
|
||||
value={autoScroll}
|
||||
onValueChange={() =>
|
||||
dispatch(setAppSettings('autoScroll', !autoScroll))
|
||||
}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</Pressable>
|
||||
value={autoScroll}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
<Pressable
|
||||
style={styles.switchStyle}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
<ReaderBottomSheetSwitch
|
||||
label="Show battery and time"
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings('showBatteryAndTime', !showBatteryAndTime),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
}}
|
||||
>
|
||||
Show battery and time
|
||||
</Text>
|
||||
<Switch
|
||||
value={showBatteryAndTime}
|
||||
onValueChange={() =>
|
||||
dispatch(
|
||||
setAppSettings('showBatteryAndTime', !showBatteryAndTime),
|
||||
)
|
||||
}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={styles.switchStyle}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
value={showBatteryAndTime}
|
||||
theme={theme}
|
||||
/>
|
||||
<ReaderBottomSheetSwitch
|
||||
label="Show progress percentage"
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings('showScrollPercentage', !showScrollPercentage),
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
}}
|
||||
>
|
||||
Show progress percentage
|
||||
</Text>
|
||||
<Switch
|
||||
value={showScrollPercentage}
|
||||
onValueChange={() =>
|
||||
dispatch(
|
||||
setAppSettings('showScrollPercentage', !showScrollPercentage),
|
||||
)
|
||||
}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={styles.switchStyle}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
value={showScrollPercentage}
|
||||
theme={theme}
|
||||
/>
|
||||
<ReaderBottomSheetSwitch
|
||||
label="Swipe gestures"
|
||||
onPress={enableSwipeGestures}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
}}
|
||||
>
|
||||
Swipe gestures
|
||||
</Text>
|
||||
<Switch
|
||||
value={swipeGestures}
|
||||
onValueChange={enableSwipeGestures}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</Pressable>
|
||||
value={swipeGestures}
|
||||
theme={theme}
|
||||
/>
|
||||
{!useWebViewForChapter && (
|
||||
<Pressable
|
||||
style={styles.switchStyle}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
<ReaderBottomSheetSwitch
|
||||
label="Select text"
|
||||
onPress={() =>
|
||||
dispatch(setAppSettings('textSelectable', !selectText))
|
||||
}
|
||||
>
|
||||
<Text style={{color: theme.textColorSecondary}}>Select text</Text>
|
||||
<Switch
|
||||
value={selectText}
|
||||
onValueChange={() =>
|
||||
dispatch(setAppSettings('textSelectable', !selectText))
|
||||
}
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</Pressable>
|
||||
value={selectText}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Pressable, ScrollView, StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import {setReaderSettings} from '../../../../../redux/settings/settings.actions';
|
||||
|
||||
import {fonts} from '../../../../../services/utils/constants';
|
||||
|
||||
export const ReaderBottomSheetFontPicker = ({reader, theme}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isSelected = font => reader.fontFamily === font.fontFamily;
|
||||
|
||||
return (
|
||||
<View style={styles.row}>
|
||||
<Text style={[{color: theme.textColorSecondary}, styles.title]}>
|
||||
Font style
|
||||
</Text>
|
||||
<ScrollView
|
||||
horizontal={true}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
contentContainerStyle={{alignItems: 'center'}}
|
||||
>
|
||||
{fonts.map(item => (
|
||||
<View
|
||||
key={item.fontFamily}
|
||||
style={[
|
||||
styles.container,
|
||||
isSelected(item) && {backgroundColor: theme.rippleColor},
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.content}
|
||||
onPress={() =>
|
||||
dispatch(setReaderSettings('fontFamily', item.fontFamily))
|
||||
}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
fontFamily: item.fontFamily,
|
||||
color: isSelected(item)
|
||||
? theme.colorAccent
|
||||
: theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
marginHorizontal: 6,
|
||||
},
|
||||
content: {
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 6,
|
||||
},
|
||||
title: {
|
||||
marginRight: 16,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Pressable, StyleSheet, Text} from 'react-native';
|
||||
|
||||
import {Switch} from 'react-native-paper';
|
||||
|
||||
export const ReaderBottomSheetSwitch = ({label, value, onPress, theme}) => {
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.container}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
onPress={onPress}
|
||||
>
|
||||
<Text style={{color: theme.textColorSecondary}}>{label}</Text>
|
||||
<Switch value={value} onValueChange={onPress} color={theme.colorAccent} />
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
ReaderBottomSheetSwitch.propTypes = {
|
||||
label: PropTypes.string,
|
||||
value: PropTypes.bool,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
});
|
||||
-1
@@ -3,7 +3,6 @@ import {Dimensions, View, Text, StyleSheet} from 'react-native';
|
||||
|
||||
import Slider from '@react-native-community/slider';
|
||||
import {getDeviceOrientation} from '../../../services/utils/helpers';
|
||||
import FadeView from '../../../components/Common/CrossFadeView';
|
||||
|
||||
const VerticalScrollbar = ({
|
||||
theme,
|
||||
@@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Pressable, StatusBar, StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
import {readerLineHeight, readerTextColor} from '../utils/readerStyles';
|
||||
import {htmlToText} from '../../../sources/helpers/htmlToText';
|
||||
|
||||
const TextReader = ({
|
||||
text,
|
||||
theme,
|
||||
reader,
|
||||
chapterName,
|
||||
textSelectable,
|
||||
nextChapter,
|
||||
navigateToNextChapter,
|
||||
}) => {
|
||||
const textStyle = {
|
||||
fontSize: reader.textSize,
|
||||
color: reader.textColor || readerTextColor(reader.theme),
|
||||
lineHeight: readerLineHeight(reader.textSize, reader.lineHeight),
|
||||
textAlign: reader.textAlign,
|
||||
};
|
||||
|
||||
const fontStyle = reader.fontFamily && {
|
||||
fontFamily: reader.fontFamily,
|
||||
};
|
||||
|
||||
const chapterText = htmlToText(text);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
paddingHorizontal: `${reader.padding}%`,
|
||||
paddingTop: StatusBar.currentHeight,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={[textStyle, fontStyle]} selectable={textSelectable}>
|
||||
{chapterText}
|
||||
</Text>
|
||||
|
||||
<View style={styles.navigationContainer}>
|
||||
<Text style={[styles.finishedChapterText, {color: reader.textColor}]}>
|
||||
Finished: {chapterName}
|
||||
</Text>
|
||||
{nextChapter ? (
|
||||
<View style={styles.nextChapButtonContainer}>
|
||||
<Pressable
|
||||
style={styles.nextChapterButton}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
onPress={navigateToNextChapter}
|
||||
>
|
||||
<Text
|
||||
style={[styles.nextChapButtonLabel, {color: reader.textColor}]}
|
||||
>
|
||||
Next: {nextChapter.chapterName}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={[{color: reader.textColor}, styles.noNextChapterText]}>
|
||||
There's no next chapter
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextReader;
|
||||
|
||||
TextReader.propTypes = {
|
||||
text: PropTypes.string,
|
||||
reader: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
chapterName: PropTypes.string,
|
||||
textSelectable: PropTypes.bool,
|
||||
nextChapter: PropTypes.object,
|
||||
navigateToNextChapter: PropTypes.func,
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
navigationContainer: {
|
||||
marginTop: 32,
|
||||
marginBottom: 16,
|
||||
},
|
||||
finishedChapterText: {
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
nextChapButtonContainer: {
|
||||
borderRadius: 8,
|
||||
marginVertical: 4,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
nextChapterButton: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
nextChapButtonLabel: {
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
noNextChapterText: {
|
||||
fontSize: 16,
|
||||
paddingVertical: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {StatusBar, StyleSheet, View} from 'react-native';
|
||||
|
||||
import WebView from 'react-native-webview';
|
||||
|
||||
import {readerBackground} from '../utils/readerStyles';
|
||||
import {cleanHtml} from '../../../sources/helpers/cleanHtml';
|
||||
|
||||
const WebViewReader = ({
|
||||
html,
|
||||
theme,
|
||||
reader,
|
||||
onScroll,
|
||||
onWebViewNavigationStateChange,
|
||||
}) => {
|
||||
const backgroundColor = readerBackground(reader.theme);
|
||||
|
||||
const chapterText = cleanHtml(html);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<WebView
|
||||
style={{backgroundColor}}
|
||||
originWhitelist={['*']}
|
||||
scalesPageToFit={true}
|
||||
showsVerticalScrollIndicator={false}
|
||||
onScroll={onScroll}
|
||||
onNavigationStateChange={onWebViewNavigationStateChange}
|
||||
source={{
|
||||
html: `
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||
<style>
|
||||
html {
|
||||
overflow-x: hidden;
|
||||
padding-top: ${StatusBar.currentHeight};
|
||||
word-wrap: break-word;
|
||||
}
|
||||
body {
|
||||
padding-left: ${reader.padding}%;
|
||||
padding-right: ${reader.padding}%;
|
||||
padding-bottom: 30px;
|
||||
|
||||
font-size: ${reader.textSize}px;
|
||||
color: ${reader.textColor};
|
||||
text-align: ${reader.textAlign};
|
||||
line-height: ${reader.lineHeight};
|
||||
font-family: ${reader.fontFamily};
|
||||
}
|
||||
hr {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
a {
|
||||
color: ${theme.colorAccent};
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
${reader.customCSS}
|
||||
|
||||
@font-face {
|
||||
font-family: ${reader.fontFamily};
|
||||
src: url("file:///android_asset/fonts/${reader.fontFamily}.ttf");
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${chapterText}
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
WebViewReader.propTypes = {
|
||||
html: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onScroll: PropTypes.func,
|
||||
onWebViewNavigationStateChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export default WebViewReader;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
+14
@@ -1,3 +1,15 @@
|
||||
const presetThemes = [
|
||||
{
|
||||
value: 1,
|
||||
backgroundColor: '#000000',
|
||||
textColor: 'rgba(255,255,255,0.7)',
|
||||
},
|
||||
{value: 2, backgroundColor: '#FFFFFF', textColor: '#111111'},
|
||||
{value: 3, backgroundColor: '#F7DFC6', textColor: '#593100'},
|
||||
{value: 4, backgroundColor: '#292832', textColor: '#CCCCCC'},
|
||||
{value: 5, backgroundColor: '#2B2C30', textColor: '#CCCCCC'},
|
||||
];
|
||||
|
||||
export const readerBackground = val => {
|
||||
const backgroundColor = {
|
||||
1: '#000000',
|
||||
@@ -31,3 +43,5 @@ export const readerLineHeight = (fontSize, lineHeightMultiplier) => {
|
||||
|
||||
return lineHeight;
|
||||
};
|
||||
|
||||
export {presetThemes};
|
||||
@@ -6,7 +6,6 @@ import {List} from '../../components/List';
|
||||
import {ScreenContainer} from '../../components/Common';
|
||||
|
||||
import {useTheme} from '../../hooks/reduxHooks';
|
||||
import {restoreLibraryAction} from '../../redux/library/library.actions';
|
||||
import {
|
||||
createFullBackup,
|
||||
restoreFullBackup,
|
||||
@@ -14,7 +13,6 @@ import {
|
||||
|
||||
const BackupSettings = ({navigation}) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -35,12 +33,12 @@ const BackupSettings = ({navigation}) => {
|
||||
theme={theme}
|
||||
/>
|
||||
{/* <List.Divider theme={theme} />
|
||||
<List.Item
|
||||
title="Restore old backup"
|
||||
description="Restore library from backup file of version v1.1.2 or lower"
|
||||
onPress={() => dispatch(restoreLibraryAction())}
|
||||
theme={theme}
|
||||
/> */}
|
||||
<List.Item
|
||||
title="Restore old backup"
|
||||
description="Restore library from backup file of version v1.1.2 or lower"
|
||||
onPress={() => dispatch(restoreLibraryAction())}
|
||||
theme={theme}
|
||||
/> */}
|
||||
<List.InfoItem
|
||||
title="Create backup may not work on devices with Android 9 or lower."
|
||||
icon="information-outline"
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useState} from 'react';
|
||||
import React from 'react';
|
||||
import {ScrollView} from 'react-native';
|
||||
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
@@ -7,12 +7,13 @@ import {Appbar} from '../../components/Appbar';
|
||||
import {List} from '../../components/List';
|
||||
import {ScreenContainer} from '../../components/Common';
|
||||
import SwitchSetting from '../../components/Switch/Switch';
|
||||
import DisplayModeModal from './components/DisplayModeModal';
|
||||
import GridSizeModal from './components/GridSizeModal';
|
||||
import DisplayModeModal from '../more/components/DisplayModeModal';
|
||||
import GridSizeModal from '../more/components/GridSizeModal';
|
||||
|
||||
import {useSettings, useTheme} from '../../hooks/reduxHooks';
|
||||
import {setAppSettings} from '../../redux/settings/settings.actions';
|
||||
import {SHOW_LAST_UPDATE_TIME} from '../../redux/updates/updates.types';
|
||||
import {useModal} from '../../hooks/useModal';
|
||||
|
||||
const GenralSettings = ({navigation}) => {
|
||||
const theme = useTheme();
|
||||
@@ -32,7 +33,7 @@ const GenralSettings = ({navigation}) => {
|
||||
state => state.updatesReducer,
|
||||
);
|
||||
|
||||
const displayModeLabel = displayMode => {
|
||||
const displayModeLabel = () => {
|
||||
const label = {
|
||||
0: 'Compact Grid',
|
||||
1: 'Comfortable Grid',
|
||||
@@ -40,22 +41,18 @@ const GenralSettings = ({navigation}) => {
|
||||
3: 'No Title Grid',
|
||||
};
|
||||
|
||||
return label[displayMode] ?? 'Compact Grid';
|
||||
return label[displayMode] || label[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Display Mode Modal
|
||||
*/
|
||||
const [displayModalVisible, setDisplayModalVisible] = useState(false);
|
||||
const showDisplayModal = () => setDisplayModalVisible(true);
|
||||
const hideDisplayModal = () => setDisplayModalVisible(false);
|
||||
const displayModalRef = useModal();
|
||||
|
||||
/**
|
||||
* Grid Size Modal
|
||||
*/
|
||||
const [gridSizeModalVisible, setGridSizeModalVisible] = useState(false);
|
||||
const showGridSizeModal = () => setGridSizeModalVisible(true);
|
||||
const hideGridSizeModal = () => setGridSizeModalVisible(false);
|
||||
const gridSizeModalRef = useModal();
|
||||
|
||||
return (
|
||||
<ScreenContainer theme={theme}>
|
||||
@@ -66,14 +63,14 @@ const GenralSettings = ({navigation}) => {
|
||||
<List.SubHeader theme={theme}>Display</List.SubHeader>
|
||||
<List.Item
|
||||
title="Display Mode"
|
||||
description={displayModeLabel(displayMode)}
|
||||
onPress={showDisplayModal}
|
||||
description={displayModeLabel()}
|
||||
onPress={displayModalRef.showModal}
|
||||
theme={theme}
|
||||
/>
|
||||
<List.Item
|
||||
title="Items per row in library"
|
||||
description={`${novelsPerRow} items per row`}
|
||||
onPress={showGridSizeModal}
|
||||
onPress={gridSizeModalRef.showModal}
|
||||
theme={theme}
|
||||
/>
|
||||
<List.Divider theme={theme} />
|
||||
@@ -130,16 +127,16 @@ const GenralSettings = ({navigation}) => {
|
||||
</ScrollView>
|
||||
<DisplayModeModal
|
||||
displayMode={displayMode}
|
||||
displayModalVisible={displayModalVisible}
|
||||
hideDisplayModal={hideDisplayModal}
|
||||
displayModalVisible={displayModalRef.visible}
|
||||
hideDisplayModal={displayModalRef.hideModal}
|
||||
dispatch={dispatch}
|
||||
theme={theme}
|
||||
/>
|
||||
<GridSizeModal
|
||||
dispatch={dispatch}
|
||||
novelsPerRow={novelsPerRow}
|
||||
gridSizeModalVisible={gridSizeModalVisible}
|
||||
hideGridSizeModal={hideGridSizeModal}
|
||||
gridSizeModalVisible={gridSizeModalRef.visible}
|
||||
hideGridSizeModal={gridSizeModalRef.hideModal}
|
||||
theme={theme}
|
||||
/>
|
||||
</ScreenContainer>
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import React, {useState} from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
@@ -12,7 +11,7 @@ import {
|
||||
import {Button} from 'react-native-paper';
|
||||
import WebView from 'react-native-webview';
|
||||
import {useDispatch} from 'react-redux';
|
||||
import {TabView, SceneMap, TabBar} from 'react-native-tab-view';
|
||||
import {TabView, TabBar} from 'react-native-tab-view';
|
||||
|
||||
import {Appbar} from '../../components/Appbar';
|
||||
import ColorPickerModal from '../../components/ColorPickerModal';
|
||||
@@ -28,11 +27,8 @@ import {
|
||||
setAppSettings,
|
||||
setReaderSettings,
|
||||
} from '../../redux/settings/settings.actions';
|
||||
import {
|
||||
readerBackground,
|
||||
readerLineHeight,
|
||||
readerTextColor,
|
||||
} from '../Chapter/readerStyleController';
|
||||
import {readerBackground, readerTextColor} from '../reader/utils/readerStyles';
|
||||
import {useModal} from '../../hooks/useModal';
|
||||
|
||||
const FirstRoute = React.memo(({theme, dispatch, reader}) => {
|
||||
const presetThemes = [
|
||||
@@ -59,12 +55,7 @@ const FirstRoute = React.memo(({theme, dispatch, reader}) => {
|
||||
/**
|
||||
* Reader Background Color Modal
|
||||
*/
|
||||
const [readerBackgroundColorModal, setReaderBackgroundtColorModal] =
|
||||
useState(false);
|
||||
const showReaderBackgroundtColorModal = () =>
|
||||
setReaderBackgroundtColorModal(true);
|
||||
const hideReaderBackgroundtColorModal = () =>
|
||||
setReaderBackgroundtColorModal(false);
|
||||
const readerBackgroundModal = useModal();
|
||||
|
||||
/**
|
||||
* Reader Text Color Modal
|
||||
@@ -78,7 +69,7 @@ const FirstRoute = React.memo(({theme, dispatch, reader}) => {
|
||||
};
|
||||
|
||||
const setReaderTextColor = val => {
|
||||
dispatch(setReaderSettings('textColor', val ?? reader.textColor));
|
||||
dispatch(setReaderSettings('textColor', val || reader.textColor));
|
||||
};
|
||||
|
||||
const [customHtmlCSS, setcustomHtmlCSS] = useState(reader.customCSS);
|
||||
@@ -103,17 +94,17 @@ const FirstRoute = React.memo(({theme, dispatch, reader}) => {
|
||||
showsHorizontalScrollIndicator={false}
|
||||
>
|
||||
<Row>
|
||||
{presetThemes.map(theme => (
|
||||
{presetThemes.map(item => (
|
||||
<ToggleColorButton
|
||||
key={theme.value}
|
||||
selected={reader.theme === theme.backgroundColor}
|
||||
backgroundColor={theme.backgroundColor}
|
||||
textColor={theme.textColor}
|
||||
key={item.value}
|
||||
selected={reader.theme === item.backgroundColor}
|
||||
backgroundColor={item.backgroundColor}
|
||||
textColor={item.textColor}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
setReaderSettings('theme', theme.backgroundColor),
|
||||
setReaderSettings('theme', item.backgroundColor),
|
||||
);
|
||||
dispatch(setReaderSettings('textColor', theme.textColor));
|
||||
dispatch(setReaderSettings('textColor', item.textColor));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -125,7 +116,7 @@ const FirstRoute = React.memo(({theme, dispatch, reader}) => {
|
||||
<List.ColorItem
|
||||
title="Background Color"
|
||||
description={readerBackground(reader.theme).toUpperCase()}
|
||||
onPress={showReaderBackgroundtColorModal}
|
||||
onPress={readerBackgroundModal.showModal}
|
||||
theme={theme}
|
||||
/>
|
||||
<List.ColorItem
|
||||
@@ -230,9 +221,9 @@ const FirstRoute = React.memo(({theme, dispatch, reader}) => {
|
||||
</ScrollView>
|
||||
<ColorPickerModal
|
||||
title="Reader background color"
|
||||
modalVisible={readerBackgroundColorModal}
|
||||
modalVisible={readerBackgroundModal.visible}
|
||||
color={readerBackground(reader.theme)}
|
||||
hideModal={hideReaderBackgroundtColorModal}
|
||||
hideModal={readerBackgroundModal.hideModal}
|
||||
theme={theme}
|
||||
onSubmit={setReaderBackground}
|
||||
/>
|
||||
@@ -34,7 +34,7 @@ const Extension = ({navigation, route}) => {
|
||||
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const incrementPage = () => setPage(page => page + 1);
|
||||
const incrementPage = () => setPage(prevState => prevState + 1);
|
||||
|
||||
const clearSearchbar = () => {
|
||||
setNovels([]);
|
||||
@@ -48,7 +48,7 @@ const Extension = ({navigation, route}) => {
|
||||
if (page <= totalPages) {
|
||||
const source = getSource(sourceId);
|
||||
const res = await source.popularNovels(page);
|
||||
setNovels(novels => [...novels, ...res.novels]);
|
||||
setNovels(prevState => [...prevState, ...res.novels]);
|
||||
setTotalPages(res.totalPages);
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -92,20 +92,6 @@ const Extension = ({navigation, route}) => {
|
||||
};
|
||||
}, [page]);
|
||||
|
||||
const isCloseToBottom = ({layoutMeasurement, contentOffset, contentSize}) => {
|
||||
const paddingToBottom = 20;
|
||||
return (
|
||||
layoutMeasurement.height + contentOffset.y >=
|
||||
contentSize.height - paddingToBottom
|
||||
);
|
||||
};
|
||||
|
||||
// const onScroll = ({ nativeEvent }) => {
|
||||
// if (!searchText && isCloseToBottom(nativeEvent)) {
|
||||
// incrementPage();
|
||||
// }
|
||||
// };
|
||||
|
||||
const getNextPage = () => {
|
||||
if (!searchText) {
|
||||
incrementPage();
|
||||
@@ -190,7 +176,6 @@ const Extension = ({navigation, route}) => {
|
||||
data={novels}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={listEmptyComponent()}
|
||||
// onScroll={onScroll}
|
||||
onEndReached={getNextPage}
|
||||
ListFooterComponent={
|
||||
!searchText &&
|
||||
@@ -12,7 +12,7 @@ export const parseChapterNumber = chapterName => {
|
||||
} else if (occurrence) {
|
||||
return occurrence[0];
|
||||
} else {
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ const popularNovels = async page => {
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
let novels = [];
|
||||
|
||||
$('.page-item-detail').each(function (result) {
|
||||
const novelName = $(this).find('h5 > a').text();
|
||||
const novelName = $(this).find('h3 > a').text();
|
||||
const novelCover = $(this).find('img').attr('src');
|
||||
|
||||
let novelUrl = $(this).find('h5 > a').attr('href');
|
||||
let novelUrl = $(this).find('h3 > a').attr('href');
|
||||
novelUrl = novelUrl.replace(`${baseUrl}/`, '');
|
||||
|
||||
const novel = {
|
||||
@@ -39,12 +39,10 @@ const popularNovels = async page => {
|
||||
const parseNovelAndChapters = async novelUrl => {
|
||||
const url = `${baseUrl}/${novelUrl}`;
|
||||
|
||||
console.log(url);
|
||||
|
||||
const result = await fetch(url);
|
||||
const body = await result.text();
|
||||
|
||||
$ = cheerio.load(body);
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
let novel = {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user