From 7c4da7f761ce34e468feab24813dcd570bdb6c88 Mon Sep 17 00:00:00 2001 From: Rajarshee Chatterjee Date: Sat, 2 Oct 2021 15:21:56 +0530 Subject: [PATCH] Refactor (#82) --- package-lock.json | 5 +- package.json | 1 + src/components/Appbar.js | 8 + src/components/Chip/Chip.js | 49 ++++ src/components/NovelCover.js | 4 +- src/components/Searchbar/Searchbar.js | 36 ++- src/components/Typography/Typography.js | 20 ++ src/database/DBHelper.js | 4 +- src/database/queries/HistoryQueries.js | 3 + src/database/queries/UpdateQueries.js | 1 + src/database/tables/ChapterTable.js | 18 +- src/hooks/useModal.js | 10 + src/navigators/BottomNavigator.js | 10 +- src/navigators/Main.js | 31 ++- src/navigators/More.js | 20 +- src/screens/Browse/Browse.js | 4 +- .../{SourceCard.js => SourceItem.js} | 33 +-- src/screens/Browse/migration/Migration.js | 12 +- .../Browse/migration/MigrationNovelList.js | 7 +- .../Browse/migration/MigrationNovels.js | 1 - ...onSourceCard.js => MigrationSourceItem.js} | 0 src/screens/Chapter/ReaderSeekbar.js | 129 --------- .../Chapter/components/WebViewReader.js | 27 -- src/screens/History/History.js | 121 +++++---- .../History/{ => components}/HistoryItem.js | 89 +++--- .../{ => components}/RemoveHistoryDialog.js | 12 +- src/screens/Library/Library.js | 10 +- ...ryFilterSheet.js => LibraryBottomSheet.js} | 0 src/screens/More/Downloads.js | 2 +- src/screens/Novel/Novel.js | 10 +- .../components/Info/NovelInfoComponents.js | 23 +- .../Novel/components/Info/NovelInfoHeader.js | 32 +-- .../Novel/components/Info/NovelSummary.js | 2 +- .../Novel/components/Info/ReadButton.js | 1 + ...rsSettingsSheet.js => NovelBottomSheet.js} | 0 src/screens/Updates/Updates.js | 29 +- .../Updates/components/DownloadButton.js | 2 +- src/screens/Updates/components/UpdatesItem.js | 4 +- .../{Chapter/Chapter.js => reader/Reader.js} | 217 +++------------ .../components/ReaderAppbar.js} | 0 .../ReaderBottomSheet/ReaderBottomSheet.js} | 254 ++++-------------- .../components/ReaderBottomSheetFontPicker.js | 80 ++++++ .../components/ReaderBottomSheetSwitch.js | 36 +++ .../components/ReaderFooter.js} | 0 .../components/ReaderSeekBar.js} | 1 - src/screens/reader/components/TextReader.js | 115 ++++++++ .../reader/components/WebViewReader.js | 100 +++++++ .../utils/readerStyles.js} | 14 + src/screens/{More => setting}/Settings.js | 0 .../SettingsAdvanced.js} | 0 .../SettingsAppearance.js} | 0 .../SettingsBackup.js} | 14 +- .../SettingsGeneral.js} | 33 ++- .../SettingsReader.js} | 41 ++- .../SettingsTracker.js} | 0 .../Extension.js => source/Source.js} | 19 +- src/services/utils/helpers.js | 2 +- src/sources/en/boxnovel.js | 10 +- 58 files changed, 825 insertions(+), 881 deletions(-) create mode 100644 src/components/Chip/Chip.js create mode 100644 src/components/Typography/Typography.js create mode 100644 src/hooks/useModal.js rename src/screens/Browse/components/{SourceCard.js => SourceItem.js} (77%) rename src/screens/Browse/migration/{MigrationSourceCard.js => MigrationSourceItem.js} (100%) delete mode 100644 src/screens/Chapter/ReaderSeekbar.js delete mode 100644 src/screens/Chapter/components/WebViewReader.js rename src/screens/History/{ => components}/HistoryItem.js (56%) rename src/screens/History/{ => components}/RemoveHistoryDialog.js (77%) rename src/screens/Library/components/{LibraryFilterSheet.js => LibraryBottomSheet.js} (100%) rename src/screens/Novel/components/{ChaptersSettingsSheet.js => NovelBottomSheet.js} (100%) rename src/screens/{Chapter/Chapter.js => reader/Reader.js} (70%) rename src/screens/{Chapter/components/ChapterAppbar.js => reader/components/ReaderAppbar.js} (100%) rename src/screens/{Chapter/components/ReaderSheet.js => reader/components/ReaderBottomSheet/ReaderBottomSheet.js} (57%) create mode 100644 src/screens/reader/components/ReaderBottomSheet/components/ReaderBottomSheetFontPicker.js create mode 100644 src/screens/reader/components/ReaderBottomSheet/components/ReaderBottomSheetSwitch.js rename src/screens/{Chapter/components/ChapterFooter.js => reader/components/ReaderFooter.js} (100%) rename src/screens/{Chapter/components/VerticalScrollbar.js => reader/components/ReaderSeekBar.js} (98%) create mode 100644 src/screens/reader/components/TextReader.js create mode 100644 src/screens/reader/components/WebViewReader.js rename src/screens/{Chapter/readerStyleController.js => reader/utils/readerStyles.js} (61%) rename src/screens/{More => setting}/Settings.js (100%) rename src/screens/{More/AdvancedSettings.js => setting/SettingsAdvanced.js} (100%) rename src/screens/{More/AppearanceSettings.js => setting/SettingsAppearance.js} (100%) rename src/screens/{More/BackupSettings.js => setting/SettingsBackup.js} (77%) rename src/screens/{More/GeneralSettings.js => setting/SettingsGeneral.js} (78%) rename src/screens/{More/ReaderSettings.js => setting/SettingsReader.js} (92%) rename src/screens/{More/TrackerSettings.js => setting/SettingsTracker.js} (100%) rename src/screens/{Extension/Extension.js => source/Source.js} (90%) diff --git a/package-lock.json b/package-lock.json index a708cca7..ab1b3f44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 1b386340..b48b6665 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/Appbar.js b/src/components/Appbar.js index 7b825877..b4993b00 100644 --- a/src/components/Appbar.js +++ b/src/components/Appbar.js @@ -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}) => { ); }; + +Appbar.propTypes = { + title: PropTypes.string, + onBackAction: PropTypes.func, + style: ViewPropTypes.style, +}; diff --git a/src/components/Chip/Chip.js b/src/components/Chip/Chip.js new file mode 100644 index 00000000..be57cc9b --- /dev/null +++ b/src/components/Chip/Chip.js @@ -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 ( + + + + {label} + + + + ); +}; + +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, + }, +}); diff --git a/src/components/NovelCover.js b/src/components/NovelCover.js index 1efca1eb..568e9b8a 100644 --- a/src/components/NovelCover.js +++ b/src/components/NovelCover.js @@ -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}) => ( searchRef.current.focus()} - style={[ - styles.searchbarContainer, - {backgroundColor: theme.searchBarColor}, - ]} - rippleColor={theme.rippleColor} - > - + + searchRef.current.focus()} + style={[styles.pressable, {backgroundColor: theme.searchBarColor}]} + android_ripple={{color: theme.rippleColor}} + > {backAction && ( ))} - - + + ); }; 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, }, }); diff --git a/src/components/Typography/Typography.js b/src/components/Typography/Typography.js new file mode 100644 index 00000000..b9d66569 --- /dev/null +++ b/src/components/Typography/Typography.js @@ -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 {children}; +}; + +Text.propType = { + style: TextPropTypes.style, + type: PropTypes.oneOf(['primary', 'secondary', 'hint']), + theme: PropTypes.object.isRequired, +}; diff --git a/src/database/DBHelper.js b/src/database/DBHelper.js index 85c58983..7bf92e82 100644 --- a/src/database/DBHelper.js +++ b/src/database/DBHelper.js @@ -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); diff --git a/src/database/queries/HistoryQueries.js b/src/database/queries/HistoryQueries.js index 7c37a99f..6f55750d 100644 --- a/src/database/queries/HistoryQueries.js +++ b/src/database/queries/HistoryQueries.js @@ -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.'); }; diff --git a/src/database/queries/UpdateQueries.js b/src/database/queries/UpdateQueries.js index 296d239d..e814d8f0 100644 --- a/src/database/queries/UpdateQueries.js +++ b/src/database/queries/UpdateQueries.js @@ -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 () => { diff --git a/src/database/tables/ChapterTable.js b/src/database/tables/ChapterTable.js index 14edcbf9..76305f8c 100644 --- a/src/database/tables/ChapterTable.js +++ b/src/database/tables/ChapterTable.js @@ -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, +}; diff --git a/src/hooks/useModal.js b/src/hooks/useModal.js new file mode 100644 index 00000000..e50124b7 --- /dev/null +++ b/src/hooks/useModal.js @@ -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}; +}; diff --git a/src/navigators/BottomNavigator.js b/src/navigators/BottomNavigator.js index 1c9b9eb6..2d06840f 100644 --- a/src/navigators/BottomNavigator.js +++ b/src/navigators/BottomNavigator.js @@ -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'; diff --git a/src/navigators/Main.js b/src/navigators/Main.js index 3dbc451d..48952084 100644 --- a/src/navigators/Main.js +++ b/src/navigators/Main.js @@ -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 = () => { - + diff --git a/src/navigators/More.js b/src/navigators/More.js index c9cc0437..00a337e4 100644 --- a/src/navigators/More.js +++ b/src/navigators/More.js @@ -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(); diff --git a/src/screens/Browse/Browse.js b/src/screens/Browse/Browse.js index c02f8f63..7f7b26e6 100644 --- a/src/screens/Browse/Browse.js +++ b/src/screens/Browse/Browse.js @@ -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}) => ( - { +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 ( - + { labelStyle={{letterSpacing: 0}} uppercase={false} color={theme.colorAccent} - onPress={navigateToExtension} + onPress={navigateToSource} > Browse @@ -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, diff --git a/src/screens/Browse/migration/Migration.js b/src/screens/Browse/migration/Migration.js index 2fdfb99e..d407badd 100644 --- a/src/screens/Browse/migration/Migration.js +++ b/src/screens/Browse/migration/Migration.js @@ -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}) => ( - navigation.navigate('SourceNovels', item.sourceId)} /> ); @@ -43,6 +42,7 @@ const GlobalSearch = ({navigation}) => { 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, diff --git a/src/screens/Browse/migration/MigrationNovelList.js b/src/screens/Browse/migration/MigrationNovelList.js index 3c984f80..527ff376 100644 --- a/src/screens/Browse/migration/MigrationNovelList.js +++ b/src/screens/Browse/migration/MigrationNovelList.js @@ -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}) => ( { - 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 ( - - - {scrollPercentage} - - - - 100 - - - ); - } else { - return ( - - - {scrollPercentage} - - - - 100 - - - ); - } -}; - -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, - }, -}); diff --git a/src/screens/Chapter/components/WebViewReader.js b/src/screens/Chapter/components/WebViewReader.js deleted file mode 100644 index 704d7250..00000000 --- a/src/screens/Chapter/components/WebViewReader.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import WebView from 'react-native-webview'; - -export const WebViewReader = ({style, css, text, onScroll}) => ( - - - - - - - ${text} - - -`, - }} - /> -); diff --git a/src/screens/History/History.js b/src/screens/History/History.js index 2f75a94b..c4bcad76 100644 --- a/src/screens/History/History.js +++ b/src/screens/History/History.js @@ -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, }, ]} /> item.date} renderItem={({item}) => ( { renderItem={renderHistoryCard} ListHeaderComponent={ {moment(item.date).calendar(null, dateFormat)} @@ -173,9 +163,14 @@ const History = ({navigation}) => { } /> { + deleteAllHistory(); + setHistory([]); + removeHistoryModal.hideModal(); + }} /> ); @@ -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, + }, +}); diff --git a/src/screens/History/HistoryItem.js b/src/screens/History/components/HistoryItem.js similarity index 56% rename from src/screens/History/HistoryItem.js rename to src/screens/History/components/HistoryItem.js index 6458e021..466ad14d 100644 --- a/src/screens/History/HistoryItem.js +++ b/src/screens/History/components/HistoryItem.js @@ -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 ( - - + + - + - + {novelName} {getChapterNumber()} - + 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, + }, }); diff --git a/src/screens/History/RemoveHistoryDialog.js b/src/screens/History/components/RemoveHistoryDialog.js similarity index 77% rename from src/screens/History/RemoveHistoryDialog.js rename to src/screens/History/components/RemoveHistoryDialog.js index 5d9f7277..1022f827 100644 --- a/src/screens/History/RemoveHistoryDialog.js +++ b/src/screens/History/components/RemoveHistoryDialog.js @@ -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 ( diff --git a/src/screens/Library/Library.js b/src/screens/Library/Library.js index 7980e5cb..e283fa17 100644 --- a/src/screens/Library/Library.js +++ b/src/screens/Library/Library.js @@ -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}) => { ]} /> - { diff --git a/src/screens/Novel/Novel.js b/src/screens/Novel/Novel.js index 4503a986..9bf4f99b 100644 --- a/src/screens/Novel/Novel.js +++ b/src/screens/Novel/Novel.js @@ -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} /> - ( {children} @@ -199,28 +200,14 @@ const TrackerButton = ({theme, isTracked, onPress}) => ( ); -const GenreChip = ({children, theme}) => ( - - {children} - -); - const NovelGenres = ({theme, genre}) => { const data = genre.split(','); - const renderItem = ({item}) => {item}; + const renderItem = ({item}) => ; return ( item} @@ -282,9 +269,9 @@ const styles = StyleSheet.create({ borderWidth: 0, elevation: 0, }, - novelGenres: { + genreContainer: { paddingHorizontal: 16, - marginBottom: 8, + paddingBottom: 4, }, genreChip: { flex: 1, diff --git a/src/screens/Novel/components/Info/NovelInfoHeader.js b/src/screens/Novel/components/Info/NovelInfoHeader.js index f5a59d71..bb08f47e 100644 --- a/src/screens/Novel/components/Info/NovelInfoHeader.js +++ b/src/screens/Novel/components/Info/NovelInfoHeader.js @@ -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 = ({ {/* */} + name={loading ? 'help' : getStatusIcon(novel.status)} + size={16} + color={theme.textColorSecondary} + style={{marginRight: 4}} + /> */} {!loading ? (novel.status || 'Unknown status') + ' • ' + novel.source @@ -245,7 +241,7 @@ const NovelInfoHeader = ({ /> chaptersSettingsSheetRef.current.show()} + onPress={() => novelBottomSheetRef.current.show()} android_ripple={{color: theme.rippleColor}} > <> diff --git a/src/screens/Novel/components/Info/NovelSummary.js b/src/screens/Novel/components/Info/NovelSummary.js index 5e042ae4..04066c6c 100644 --- a/src/screens/Novel/components/Info/NovelSummary.js +++ b/src/screens/Novel/components/Info/NovelSummary.js @@ -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'; diff --git a/src/screens/Novel/components/Info/ReadButton.js b/src/screens/Novel/components/Info/ReadButton.js index a1460443..c454c873 100644 --- a/src/screens/Novel/components/Info/ReadButton.js +++ b/src/screens/Novel/components/Info/ReadButton.js @@ -60,5 +60,6 @@ const styles = StyleSheet.create({ marginTop: 16, marginBottom: 8, marginHorizontal: 16, + borderRadius: 50, }, }); diff --git a/src/screens/Novel/components/ChaptersSettingsSheet.js b/src/screens/Novel/components/NovelBottomSheet.js similarity index 100% rename from src/screens/Novel/components/ChaptersSettingsSheet.js rename to src/screens/Novel/components/NovelBottomSheet.js diff --git a/src/screens/Updates/Updates.js b/src/screens/Updates/Updates.js index 28687a78..3a585a85 100644 --- a/src/screens/Updates/Updates.js +++ b/src/screens/Updates/Updates.js @@ -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 && ( Library last updated: {moment(lastUpdateTime).fromNow()} @@ -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', + }, }); diff --git a/src/screens/Updates/components/DownloadButton.js b/src/screens/Updates/components/DownloadButton.js index cf689379..3a776759 100644 --- a/src/screens/Updates/components/DownloadButton.js +++ b/src/screens/Updates/components/DownloadButton.js @@ -5,7 +5,7 @@ import { ChapterDownloadingButton, DeleteChapterButton, DownloadChapterButton, -} from '../../Novel/components/Chapter/ChapterDownloadButtons'; +} from '../../novel/components/Chapter/ChapterDownloadButtons'; export const DownloadButton = ({ downloadQueue, diff --git a/src/screens/Updates/components/UpdatesItem.js b/src/screens/Updates/components/UpdatesItem.js index d4505c1b..2d42c298 100644 --- a/src/screens/Updates/components/UpdatesItem.js +++ b/src/screens/Updates/components/UpdatesItem.js @@ -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: { diff --git a/src/screens/Chapter/Chapter.js b/src/screens/reader/Reader.js similarity index 70% rename from src/screens/Chapter/Chapter.js rename to src/screens/reader/Reader.js index 625addce..1574b6a5 100644 --- a/src/screens/Chapter/Chapter.js +++ b/src/screens/reader/Reader.js @@ -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 ( <> <> - { onPress={hideHeader} onLayout={scrollToSavedProgress} > - {useWebViewForChapter ? ( - { - if ( - (sourceId === 50 || sourceId == 62) && - url !== 'about:blank' - ) { - setLoading(true); - const res = await fetchChapter(sourceId, novelUrl, url); - setChapter(res); - setLoading(false); + + {useWebViewForChapter ? ( + - - - - - - - ${cleanHtml(chapter.chapterText)} - - - `, - }} - onScroll={onScroll} - /> - ) : ( - - - {htmlToText(chapter.chapterText)} - - - - - Finished: {chapterName} - - {nextChapter ? ( - - - - Next: {nextChapter.chapterName} - - - - ) : ( - - There's no next chapter - - )} - - - )} + /> + ) : ( + + )} + )} - { /> {!useWebViewForChapter && ( - { setScrollPercentage={setScrollPercentage} /> )} - ( - {title} -); +} from '../../../../components/Common/ToggleButton'; +import {ReaderBottomSheetSwitch} from './components/ReaderBottomSheetSwitch'; +import {ReaderBottomSheetFontPicker} from './components/ReaderBottomSheetFontPicker'; const ReaderSheet = ({ theme, @@ -250,230 +245,71 @@ const ReaderSheet = ({ /> - - - Font style - - - {fonts.map(font => ( - - - dispatch( - setReaderSettings('fontFamily', font.fontFamily), - ) - } - android_ripple={{ - color: theme.rippleColor, - }} - > - - {font.name} - - - - ))} - - + - dispatch( setAppSettings('useWebViewForChapter', !useWebViewForChapter), ) } - > - - Use WebView - - - dispatch( - setAppSettings('useWebViewForChapter', !useWebViewForChapter), - ) - } - color={theme.colorAccent} - /> - - + dispatch(setAppSettings('fullScreenMode', !fullScreenMode)) } - > - - Fullscreen - - - dispatch(setAppSettings('fullScreenMode', !fullScreenMode)) - } - color={theme.colorAccent} - /> - + value={fullScreenMode} + theme={theme} + /> {!useWebViewForChapter && ( - dispatch(setAppSettings('autoScroll', !autoScroll)) } - > - - AutoScroll - - - dispatch(setAppSettings('autoScroll', !autoScroll)) - } - color={theme.colorAccent} - /> - + value={autoScroll} + theme={theme} + /> )} - dispatch( setAppSettings('showBatteryAndTime', !showBatteryAndTime), ) } - > - - Show battery and time - - - dispatch( - setAppSettings('showBatteryAndTime', !showBatteryAndTime), - ) - } - color={theme.colorAccent} - /> - - + dispatch( setAppSettings('showScrollPercentage', !showScrollPercentage), ) } - > - - Show progress percentage - - - dispatch( - setAppSettings('showScrollPercentage', !showScrollPercentage), - ) - } - color={theme.colorAccent} - /> - - + - - Swipe gestures - - - + value={swipeGestures} + theme={theme} + /> {!useWebViewForChapter && ( - dispatch(setAppSettings('textSelectable', !selectText)) } - > - Select text - - dispatch(setAppSettings('textSelectable', !selectText)) - } - color={theme.colorAccent} - /> - + value={selectText} + theme={theme} + /> )} diff --git a/src/screens/reader/components/ReaderBottomSheet/components/ReaderBottomSheetFontPicker.js b/src/screens/reader/components/ReaderBottomSheet/components/ReaderBottomSheetFontPicker.js new file mode 100644 index 00000000..0500eacd --- /dev/null +++ b/src/screens/reader/components/ReaderBottomSheet/components/ReaderBottomSheetFontPicker.js @@ -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 ( + + + Font style + + + {fonts.map(item => ( + + + dispatch(setReaderSettings('fontFamily', item.fontFamily)) + } + android_ripple={{color: theme.rippleColor}} + > + + {item.name} + + + + ))} + + + ); +}; + +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, + }, +}); diff --git a/src/screens/reader/components/ReaderBottomSheet/components/ReaderBottomSheetSwitch.js b/src/screens/reader/components/ReaderBottomSheet/components/ReaderBottomSheetSwitch.js new file mode 100644 index 00000000..ea2cc64e --- /dev/null +++ b/src/screens/reader/components/ReaderBottomSheet/components/ReaderBottomSheetSwitch.js @@ -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 ( + + {label} + + + ); +}; + +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, + }, +}); diff --git a/src/screens/Chapter/components/ChapterFooter.js b/src/screens/reader/components/ReaderFooter.js similarity index 100% rename from src/screens/Chapter/components/ChapterFooter.js rename to src/screens/reader/components/ReaderFooter.js diff --git a/src/screens/Chapter/components/VerticalScrollbar.js b/src/screens/reader/components/ReaderSeekBar.js similarity index 98% rename from src/screens/Chapter/components/VerticalScrollbar.js rename to src/screens/reader/components/ReaderSeekBar.js index 81b39e29..ea946007 100644 --- a/src/screens/Chapter/components/VerticalScrollbar.js +++ b/src/screens/reader/components/ReaderSeekBar.js @@ -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, diff --git a/src/screens/reader/components/TextReader.js b/src/screens/reader/components/TextReader.js new file mode 100644 index 00000000..670589b7 --- /dev/null +++ b/src/screens/reader/components/TextReader.js @@ -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 ( + + + {chapterText} + + + + + Finished: {chapterName} + + {nextChapter ? ( + + + + Next: {nextChapter.chapterName} + + + + ) : ( + + There's no next chapter + + )} + + + ); +}; + +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', + }, +}); diff --git a/src/screens/reader/components/WebViewReader.js b/src/screens/reader/components/WebViewReader.js new file mode 100644 index 00000000..1ebdeb91 --- /dev/null +++ b/src/screens/reader/components/WebViewReader.js @@ -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 ( + + + + + + + + + + ${chapterText} + + + `, + }} + /> + + ); +}; + +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, + }, +}); diff --git a/src/screens/Chapter/readerStyleController.js b/src/screens/reader/utils/readerStyles.js similarity index 61% rename from src/screens/Chapter/readerStyleController.js rename to src/screens/reader/utils/readerStyles.js index 8ee9f191..54b0d7af 100644 --- a/src/screens/Chapter/readerStyleController.js +++ b/src/screens/reader/utils/readerStyles.js @@ -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}; diff --git a/src/screens/More/Settings.js b/src/screens/setting/Settings.js similarity index 100% rename from src/screens/More/Settings.js rename to src/screens/setting/Settings.js diff --git a/src/screens/More/AdvancedSettings.js b/src/screens/setting/SettingsAdvanced.js similarity index 100% rename from src/screens/More/AdvancedSettings.js rename to src/screens/setting/SettingsAdvanced.js diff --git a/src/screens/More/AppearanceSettings.js b/src/screens/setting/SettingsAppearance.js similarity index 100% rename from src/screens/More/AppearanceSettings.js rename to src/screens/setting/SettingsAppearance.js diff --git a/src/screens/More/BackupSettings.js b/src/screens/setting/SettingsBackup.js similarity index 77% rename from src/screens/More/BackupSettings.js rename to src/screens/setting/SettingsBackup.js index 2bc80071..5e1f8279 100644 --- a/src/screens/More/BackupSettings.js +++ b/src/screens/setting/SettingsBackup.js @@ -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} /> {/* - dispatch(restoreLibraryAction())} - theme={theme} - /> */} + dispatch(restoreLibraryAction())} + theme={theme} + /> */} { 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 ( @@ -66,14 +63,14 @@ const GenralSettings = ({navigation}) => { Display @@ -130,16 +127,16 @@ const GenralSettings = ({navigation}) => { diff --git a/src/screens/More/ReaderSettings.js b/src/screens/setting/SettingsReader.js similarity index 92% rename from src/screens/More/ReaderSettings.js rename to src/screens/setting/SettingsReader.js index 63c6fb73..d9894e7a 100644 --- a/src/screens/More/ReaderSettings.js +++ b/src/screens/setting/SettingsReader.js @@ -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} > - {presetThemes.map(theme => ( + {presetThemes.map(item => ( { 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}) => { { diff --git a/src/screens/More/TrackerSettings.js b/src/screens/setting/SettingsTracker.js similarity index 100% rename from src/screens/More/TrackerSettings.js rename to src/screens/setting/SettingsTracker.js diff --git a/src/screens/Extension/Extension.js b/src/screens/source/Source.js similarity index 90% rename from src/screens/Extension/Extension.js rename to src/screens/source/Source.js index 87025068..c315ccda 100644 --- a/src/screens/Extension/Extension.js +++ b/src/screens/source/Source.js @@ -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 && diff --git a/src/services/utils/helpers.js b/src/services/utils/helpers.js index 35dd0d9f..8011aa9d 100644 --- a/src/services/utils/helpers.js +++ b/src/services/utils/helpers.js @@ -12,7 +12,7 @@ export const parseChapterNumber = chapterName => { } else if (occurrence) { return occurrence[0]; } else { - return 0; + return -1; } }; diff --git a/src/sources/en/boxnovel.js b/src/sources/en/boxnovel.js index 1815e57b..68e373a0 100644 --- a/src/sources/en/boxnovel.js +++ b/src/sources/en/boxnovel.js @@ -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 = {};