feat: Add Change Default Category (Closes #383)

This commit is contained in:
Rajarshee Chatterjee
2022-08-18 21:16:35 +05:30
parent 0d8a5d6172
commit 934edbb84c
18 changed files with 259 additions and 27 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ import color from 'color';
interface Props {
label: string;
status: boolean;
onPress: () => void;
onPress?: () => void;
style?: StyleProp<ViewStyle>;
labelStyle?: StyleProp<TextStyle>;
theme: MD3ThemeType;
+2
View File
@@ -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';
+3 -4
View File
@@ -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,
),
),
);
+5 -2
View File
@@ -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 => {
+3
View File
@@ -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';
+35
View File
@@ -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<Category[]>([]);
const [error, setError] = useState<string>();
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;
+14 -2
View File
@@ -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>('CategorySettings');
const setCategorySettings = (value: Partial<CategorySettings>) =>
setSettings({ ...categorySettings, ...value });
return {
...categorySettings,
setCategorySettings,
};
};
+2
View File
@@ -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 = () => (
<Stack.Screen name="BackupSettings" component={BackupSettings} />
<Stack.Screen name="AppearanceSettings" component={AppearanceSettings} />
<Stack.Screen name="AdvancedSettings" component={AdvancedSettings} />
<Stack.Screen name="LibrarySettings" component={LibrarySettings} />
</Stack.Navigator>
);
+6 -2
View File
@@ -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);
/**
@@ -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<BrowseSourceScreenProps> = ({ route }) => {
clearFilters,
} = useBrowseSource(sourceId, showLatestNovels);
const { defaultCategoryId = 1 } = useCategorySettings();
const {
isSearching,
searchResults,
@@ -155,7 +156,12 @@ const BrowseSourceScreen: React.FC<BrowseSourceScreenProps> = ({ route }) => {
];
}
});
insertNovelInLibrary(sourceId, item.novelUrl, inLibrary);
insertNovelInLibrary(
sourceId,
item.novelUrl,
inLibrary,
defaultCategoryId,
);
}}
/>
);
+12 -1
View File
@@ -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]);
@@ -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<DefaultCategoryDialogProps> = ({
categories,
defaultCategoryId,
hideDialog,
visible,
setDefaultCategory,
}) => {
const theme = useTheme();
return (
<Dialog
visible={visible}
onDismiss={hideDialog}
style={{ backgroundColor: getDialogBackground(theme) }}
>
<Dialog.Title style={{ color: theme.textColorPrimary }}>
{getString('categories.defaultCategory')}
</Dialog.Title>
<FlatList
style={styles.scrollArea}
initialNumToRender={10}
data={categories}
renderItem={({ item }) => (
<RadioButton
status={item.id === defaultCategoryId}
label={item.name}
onPress={() => setDefaultCategory(item.id)}
theme={theme}
/>
)}
/>
<Dialog.Actions>
<Button
theme={{ colors: { primary: theme.primary } }}
onPress={hideDialog}
>
{getString('common.cancel')}
</Button>
</Dialog.Actions>
</Dialog>
);
};
export default DefaultCategoryDialog;
const styles = StyleSheet.create({
scrollArea: {
maxHeight: 480,
},
});
@@ -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 (
<>
<Appbar
title={getString('library')}
handleGoBack={goBack}
theme={theme}
/>
<List.Section>
<List.Item
title={getString('categories.header')}
description={`${categories.length} ${getString(
'common.categories',
).toLowerCase()}`}
onPress={() =>
navigate('MoreStack' as never, { screen: 'Categories' } as never)
}
theme={theme}
/>
<List.Item
title={getString('categories.defaultCategory')}
description={
categories.find(category => category.id === defaultCategoryId)?.name
}
onPress={defaultCategoryDialog.setTrue}
theme={theme}
/>
</List.Section>
<Portal>
<DefaultCategoryDialog
categories={categories}
defaultCategoryId={defaultCategoryId || 1}
visible={defaultCategoryDialog.value}
hideDialog={defaultCategoryDialog.setFalse}
setDefaultCategory={setDefaultCategoryId}
/>
</Portal>
</>
);
};
export default SettingsLibraryScreen;
+20 -10
View File
@@ -24,6 +24,26 @@ const SettingsScreen = ({ navigation }) => {
}
theme={theme}
/>
<List.Item
title="Appearance"
icon="palette-outline"
onPress={() =>
navigation.navigate('SettingsStack', {
screen: 'AppearanceSettings',
})
}
theme={theme}
/>
<List.Item
title={getString('library')}
icon="book-variant-multiple"
onPress={() =>
navigation.navigate('SettingsStack', {
screen: 'LibrarySettings',
})
}
theme={theme}
/>
<List.Item
title={getString('moreScreen.settingsScreen.readerSettings.title')}
icon="book-open-outline"
@@ -54,16 +74,6 @@ const SettingsScreen = ({ navigation }) => {
}
theme={theme}
/>
<List.Item
title="Appearance"
icon="palette-outline"
onPress={() =>
navigation.navigate('SettingsStack', {
screen: 'AppearanceSettings',
})
}
theme={theme}
/>
<List.Item
title="Advanced"
icon="code-tags"
+6
View File
@@ -15,3 +15,9 @@ export interface LibrarySettings {
incognitoMode?: boolean;
downloadedOnlyMode?: boolean;
}
export interface CategorySettings {
defaultCategoryId?: number;
includedCategoriesInUpdate?: number[];
excludedCategoriesInUpdate?: number[];
}
+3 -2
View File
@@ -156,14 +156,15 @@
"addCategories": "Add category",
"editCategories": "Rename category",
"setCategories": "Set categories",
"header": "Edit Categories",
"header": "Edit categories",
"emptyMsg": "You have no categories. Tap the plus button to create one for organizing your library",
"setModalEmptyMsg": "You have no categories. Tap the Edit button to create one for organizing your library",
"deleteModal": {
"header": "Delete category",
"desc": "Do you wish to delete category"
},
"duplicateError": "A category with this name already exists!"
"duplicateError": "A category with this name already exists!",
"defaultCategory": "Default category"
},
"settings": {
"icognitoMode": "Incognito mode",
+1
View File
@@ -120,6 +120,7 @@ export interface StringMap {
'categories.deleteModal.header': 'string';
'categories.deleteModal.desc': 'string';
'categories.duplicateError': 'string';
'categories.defaultCategory': 'string';
'settings.icognitoMode': 'string';
'settings.downloadedOnly': 'string';
}
+3 -1
View File
@@ -18,10 +18,12 @@
"@components/*": ["src/components/*"],
"@database/*": ["src/database/*"],
"@hooks/*": ["src/hooks/*"],
"@hooks": ["src/hooks/index"],
"@screens/*": ["src/screens/*"],
"@strings/*": ["strings/*"],
"@redux/*": ["src/redux/*"],
"@theme/*": ["src/theme/*"]
"@theme/*": ["src/theme/*"],
"@utils/*": ["src/utils/*"]
}
},
"exclude": [