Refactor (#82)

This commit is contained in:
Rajarshee Chatterjee
2021-10-02 15:21:56 +05:30
committed by GitHub
parent 3daa11f642
commit 7c4da7f761
58 changed files with 825 additions and 881 deletions
+3 -2
View File
@@ -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",
+1
View File
@@ -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",
+8
View File
@@ -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,
};
+49
View File
@@ -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,
},
});
+2 -2
View File
@@ -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
+15 -21
View File
@@ -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,
},
});
+20
View File
@@ -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,
};
+3 -1
View File
@@ -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);
+3
View File
@@ -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.');
};
+1
View File
@@ -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 () => {
+15 -3
View File
@@ -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,
};
+10
View File
@@ -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};
};
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+2 -2
View File
@@ -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}
@@ -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 -8
View File
@@ -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';
-129
View File
@@ -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>
`,
}}
/>
);
+63 -58
View File
@@ -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,
},
});
@@ -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,
},
});
@@ -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,
}}
>
+5 -5
View File
@@ -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}
+1 -1
View File
@@ -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}) => {
+5 -5
View File
@@ -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,
},
});
+16 -13
View File
@@ -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}
@@ -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>
@@ -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,
},
});
@@ -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,
+115
View File
@@ -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,
},
});
@@ -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 &&
+1 -1
View File
@@ -12,7 +12,7 @@ export const parseChapterNumber = chapterName => {
} else if (occurrence) {
return occurrence[0];
} else {
return 0;
return -1;
}
};
+4 -6
View File
@@ -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 = {};