diff --git a/src/components/RadioButton/RadioButton.tsx b/src/components/RadioButton/RadioButton.tsx index 490e9330..b329c9f1 100644 --- a/src/components/RadioButton/RadioButton.tsx +++ b/src/components/RadioButton/RadioButton.tsx @@ -14,7 +14,7 @@ import color from 'color'; interface Props { label: string; status: boolean; - onPress: () => void; + onPress?: () => void; style?: StyleProp; labelStyle?: StyleProp; theme: MD3ThemeType; diff --git a/src/components/index.ts b/src/components/index.ts index 3354fb5d..063eff7c 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -11,3 +11,5 @@ export { default as SwitchItem } from './SwitchItem/SwitchItem'; export { default as List } from './List/List'; export { default as ColorPreferenceItem } from './ColorPreferenceItem/ColorPreferenceItem'; export { default as LoadingMoreIndicator } from './LoadingMoreIndicator/LoadingMoreIndicator'; +export { Checkbox } from './Checkbox/Checkbox'; +export { RadioButton } from './RadioButton/RadioButton'; diff --git a/src/database/queries/NovelQueries.js b/src/database/queries/NovelQueries.js index c097c17c..8a225279 100644 --- a/src/database/queries/NovelQueries.js +++ b/src/database/queries/NovelQueries.js @@ -12,7 +12,7 @@ import { txnErrorCallback } from '../utils/helpers'; import { noop } from 'lodash'; const insertNovelQuery = - 'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + 'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, categoryIds) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; export const insertNovel = async novel => { return new Promise((resolve, reject) => @@ -31,11 +31,10 @@ export const insertNovel = async novel => { novel.artist, novel.status, novel.genre, + JSON.stringify(novel.categoryIds), ], (txObj, { insertId }) => resolve(insertId), - (txObj, error) => { - // console.log('Error ', error) - }, + txnErrorCallback, ), ), ); diff --git a/src/database/queries/NovelQueriesV2.ts b/src/database/queries/NovelQueriesV2.ts index db0ec60e..e7e7e702 100644 --- a/src/database/queries/NovelQueriesV2.ts +++ b/src/database/queries/NovelQueriesV2.ts @@ -69,6 +69,7 @@ export const insertNovelInLibrary = async ( sourceId: number, novelUrl: string, inLibrary: boolean, + defaultCategoryId: number, ) => { if (inLibrary) { showToast(getString('browseScreen.removeFromLibrary')); @@ -131,10 +132,11 @@ export const insertNovelInLibrary = async ( artist, status, genre, - followed + followed, + categoryIds ) VALUES - (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`, + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)`, [ novel.novelUrl, novel.sourceUrl, @@ -147,6 +149,7 @@ export const insertNovelInLibrary = async ( novel.artist, novel.status, novel.genre, + JSON.stringify([defaultCategoryId]), ], (txObj, { insertId }) => { novel.chapters?.map(chapter => { diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 373b1f5b..3c2ce206 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -6,3 +6,6 @@ export { default as useChapter } from './useChapter'; export { default as useNextAndPrevChapter } from './useNextAndPrevChapter'; export { default as useNovelTrackerInfo } from './useNovelTrackerInfo'; export { default as useBoolean } from './useBoolean'; +export { useTheme } from './useTheme'; +export { default as useCategories } from './useCategories'; +export { useCategorySettings, useLibrarySettings } from './useSettings'; diff --git a/src/hooks/useCategories.ts b/src/hooks/useCategories.ts new file mode 100644 index 00000000..4b23e3ff --- /dev/null +++ b/src/hooks/useCategories.ts @@ -0,0 +1,35 @@ +import { useEffect, useState } from 'react'; + +import { getCategoriesFromDb } from '@database/queries/CategoryQueries'; +import { Category } from '@database/types'; + +const useCategories = () => { + const [isLoading, setIsLoading] = useState(true); + const [categories, setCategories] = useState([]); + const [error, setError] = useState(); + + const getCategories = async () => { + try { + const res = await getCategoriesFromDb(); + setCategories(res); + } catch (err) { + if (err instanceof Error) { + setError(err.message); + } + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + getCategories(); + }, []); + + return { + isLoading, + categories, + error, + }; +}; + +export default useCategories; diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index ba1db463..61b7d840 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -1,6 +1,5 @@ import { useMMKVObject } from 'react-native-mmkv'; - -import { LibrarySettings } from '../utils/constants/settings'; +import { CategorySettings, LibrarySettings } from '../utils/constants/settings'; export const useLibrarySettings = () => { const [librarySettings, setSettings] = @@ -14,3 +13,16 @@ export const useLibrarySettings = () => { setLibrarySettings, }; }; + +export const useCategorySettings = () => { + const [categorySettings, setSettings] = + useMMKVObject('CategorySettings'); + + const setCategorySettings = (value: Partial) => + setSettings({ ...categorySettings, ...value }); + + return { + ...categorySettings, + setCategorySettings, + }; +}; diff --git a/src/navigators/MoreStack.js b/src/navigators/MoreStack.js index f3317575..e933a6b0 100644 --- a/src/navigators/MoreStack.js +++ b/src/navigators/MoreStack.js @@ -14,6 +14,7 @@ import DownloadQueue from '../screens/more/DownloadQueueScreen'; import Downloads from '../screens/more/DownloadsScreen'; import AppearanceSettings from '../screens/settings/SettingsAppearanceScreen'; import CategoriesScreen from '@screens/Categories/CategoriesScreen'; +import LibrarySettings from '@screens/settings/SettingsLibraryScreen/SettingsLibraryScreen'; const Stack = createStackNavigator(); @@ -28,6 +29,7 @@ const SettingsStack = () => ( + ); diff --git a/src/redux/novel/novel.actions.js b/src/redux/novel/novel.actions.js index fd4106da..157d4362 100644 --- a/src/redux/novel/novel.actions.js +++ b/src/redux/novel/novel.actions.js @@ -64,7 +64,8 @@ export const setNovel = novel => async dispatch => { }; export const getNovelAction = - (followed, sourceId, novelUrl, novelId, sort, filter) => async dispatch => { + (followed, sourceId, novelUrl, novelId, sort, filter, defaultCategoryId) => + async dispatch => { try { dispatch({ type: LOADING_NOVEL }); @@ -104,7 +105,10 @@ export const getNovelAction = /** * Insert novel in db. */ - const fetchedNovelId = await insertNovel(fetchedNovel); + const fetchedNovelId = await insertNovel({ + ...fetchedNovel, + categoryIds: [defaultCategoryId], + }); await insertChapters(fetchedNovelId, fetchedNovel.chapters); /** diff --git a/src/screens/BrowseSourceScreen/BrowseSourceScreen.tsx b/src/screens/BrowseSourceScreen/BrowseSourceScreen.tsx index 4cfa5234..8b2c7a85 100644 --- a/src/screens/BrowseSourceScreen/BrowseSourceScreen.tsx +++ b/src/screens/BrowseSourceScreen/BrowseSourceScreen.tsx @@ -15,7 +15,7 @@ import NovelList from '@components/NovelList'; import NovelCover from '@components/NovelCover'; import FilterBottomSheet from './components/BottomSheet'; -import { useSearch } from '../../hooks'; +import { useCategorySettings, useSearch } from '../../hooks'; import { useTheme } from '@hooks/useTheme'; import { useBrowseSource, useSearchSource } from './useBrowseSource'; @@ -59,6 +59,7 @@ const BrowseSourceScreen: React.FC = ({ route }) => { clearFilters, } = useBrowseSource(sourceId, showLatestNovels); + const { defaultCategoryId = 1 } = useCategorySettings(); const { isSearching, searchResults, @@ -155,7 +156,12 @@ const BrowseSourceScreen: React.FC = ({ route }) => { ]; } }); - insertNovelInLibrary(sourceId, item.novelUrl, inLibrary); + insertNovelInLibrary( + sourceId, + item.novelUrl, + inLibrary, + defaultCategoryId, + ); }} /> ); diff --git a/src/screens/novel/NovelScreen.js b/src/screens/novel/NovelScreen.js index 291f9174..cc7a8432 100644 --- a/src/screens/novel/NovelScreen.js +++ b/src/screens/novel/NovelScreen.js @@ -64,6 +64,7 @@ import FadeView from '../../components/Common/CrossFadeView'; import DownloadCustomChapterModal from './components/DownloadCustomChapterModal'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import useBoolean from '@hooks/useBoolean'; +import { useCategorySettings } from '@hooks/useSettings'; const Novel = ({ route, navigation }) => { const item = route.params; @@ -104,9 +105,19 @@ const Novel = ({ route, navigation }) => { novel.novelId, ); + const { defaultCategoryId = 1 } = useCategorySettings(); + useEffect(() => { dispatch( - getNovelAction(followed, sourceId, novelUrl, novelId, sort, filter), + getNovelAction( + followed, + sourceId, + novelUrl, + novelId, + sort, + filter, + defaultCategoryId, + ), ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [getNovelAction]); diff --git a/src/screens/settings/SettingsLibraryScreen/DefaultCategoryDialog.tsx b/src/screens/settings/SettingsLibraryScreen/DefaultCategoryDialog.tsx new file mode 100644 index 00000000..971ccfaf --- /dev/null +++ b/src/screens/settings/SettingsLibraryScreen/DefaultCategoryDialog.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { FlatList, StyleSheet } from 'react-native'; +import { Button, Dialog } from 'react-native-paper'; + +import { RadioButton } from '@components'; + +import { getString } from '@strings/translations'; +import { useTheme } from '@hooks'; + +import { Category } from '@database/types'; +import { getDialogBackground } from '@theme/colors'; + +interface DefaultCategoryDialogProps { + visible: boolean; + hideDialog: () => void; + categories: Category[]; + defaultCategoryId: number; + setDefaultCategory: (categoryId: number) => void; +} + +const DefaultCategoryDialog: React.FC = ({ + categories, + defaultCategoryId, + hideDialog, + visible, + setDefaultCategory, +}) => { + const theme = useTheme(); + + return ( + + + {getString('categories.defaultCategory')} + + ( + setDefaultCategory(item.id)} + theme={theme} + /> + )} + /> + + + + + ); +}; + +export default DefaultCategoryDialog; + +const styles = StyleSheet.create({ + scrollArea: { + maxHeight: 480, + }, +}); diff --git a/src/screens/settings/SettingsLibraryScreen/SettingsLibraryScreen.tsx b/src/screens/settings/SettingsLibraryScreen/SettingsLibraryScreen.tsx new file mode 100644 index 00000000..a37bce12 --- /dev/null +++ b/src/screens/settings/SettingsLibraryScreen/SettingsLibraryScreen.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Appbar, List } from '@components'; +import { getString } from '@strings/translations'; +import { + useBoolean, + useCategories, + useCategorySettings, + useTheme, +} from '@hooks'; +import { useNavigation } from '@react-navigation/native'; +import { Portal } from 'react-native-paper'; +import DefaultCategoryDialog from './DefaultCategoryDialog'; + +const SettingsLibraryScreen = () => { + const theme = useTheme(); + const { goBack, navigate } = useNavigation(); + const { defaultCategoryId, setCategorySettings } = useCategorySettings(); + const { categories } = useCategories(); + + const defaultCategoryDialog = useBoolean(); + + const setDefaultCategoryId = (categoryId: number) => + setCategorySettings({ defaultCategoryId: categoryId }); + + return ( + <> + + + + navigate('MoreStack' as never, { screen: 'Categories' } as never) + } + theme={theme} + /> + category.id === defaultCategoryId)?.name + } + onPress={defaultCategoryDialog.setTrue} + theme={theme} + /> + + + + + + ); +}; + +export default SettingsLibraryScreen; diff --git a/src/screens/settings/SettingsScreen.js b/src/screens/settings/SettingsScreen.js index 5517c957..0ebab00f 100644 --- a/src/screens/settings/SettingsScreen.js +++ b/src/screens/settings/SettingsScreen.js @@ -24,6 +24,26 @@ const SettingsScreen = ({ navigation }) => { } theme={theme} /> + + navigation.navigate('SettingsStack', { + screen: 'AppearanceSettings', + }) + } + theme={theme} + /> + + navigation.navigate('SettingsStack', { + screen: 'LibrarySettings', + }) + } + theme={theme} + /> { } theme={theme} /> - - navigation.navigate('SettingsStack', { - screen: 'AppearanceSettings', - }) - } - theme={theme} - />