refactor: Browse & Browse Settings Screen

This commit is contained in:
Rajarshee Chatterjee
2022-04-09 11:43:54 +05:30
parent b97a3cf16d
commit 54a6ec64bf
19 changed files with 619 additions and 527 deletions
-2
View File
@@ -22,8 +22,6 @@ const strings = fs.readFileSync(
'utf8',
);
console.log(flatten(JSON.parse(strings, '')));
const stringTypes = `/**\n * This file is auto-generated\n */\n\n
export interface StringMap ${JSON.stringify(flatten(JSON.parse(strings)))}
+26
View File
@@ -0,0 +1,26 @@
import React from 'react';
import { StatusBar } from 'react-native';
import { Appbar as PaperAppbar } from 'react-native-paper';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface AppbarProps {
title: string;
handleGoBack: () => void;
theme: ThemeTypeV1;
}
const Appbar: React.FC<AppbarProps> = ({ title, handleGoBack, theme }) => (
<PaperAppbar.Header
style={{ backgroundColor: theme.colorPrimary }}
statusBarHeight={StatusBar.currentHeight}
>
{handleGoBack && <PaperAppbar.BackAction onPress={handleGoBack} />}
<PaperAppbar.Content
title={title}
titleStyle={{ color: theme.textColorPrimary }}
/>
</PaperAppbar.Header>
);
export default Appbar;
+109
View File
@@ -0,0 +1,109 @@
import React, { ReactNode } from 'react';
import { StyleSheet, View } from 'react-native';
import { List as PaperList, Divider as PaperDivider } from 'react-native-paper';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface ListItemProps {
title: string;
description?: string | null;
icon?: string;
onPress?: () => void;
theme: ThemeTypeV1;
}
const Section = ({ children }: { children: ReactNode }) => (
<PaperList.Section style={styles.listSection}>{children}</PaperList.Section>
);
const SubHeader = ({
children,
theme,
}: {
children: ReactNode;
theme: ThemeTypeV1;
}) => (
<PaperList.Subheader style={{ color: theme.colorAccent }}>
{children}
</PaperList.Subheader>
);
const Item: React.FC<ListItemProps> = ({
title,
description,
icon,
onPress,
theme,
}) => (
<PaperList.Item
title={title}
titleStyle={{ color: theme.textColorPrimary }}
description={description}
descriptionStyle={{ color: theme.textColorSecondary }}
left={() => (
<View style={styles.iconContainer}>
{icon && (
<PaperList.Icon
color={theme.colorAccent}
icon={icon}
style={styles.icon}
/>
)}
</View>
)}
onPress={onPress}
rippleColor={theme.rippleColor}
/>
);
const Divider = ({ theme }: { theme: ThemeTypeV1 }) => (
<PaperDivider
style={[styles.divider, { backgroundColor: theme.dividerColor }]}
/>
);
const InfoItem = ({
title,
icon,
theme,
}: {
title: string;
icon: string;
theme: ThemeTypeV1;
}) => (
<PaperList.Item
title={title}
titleStyle={[{ color: theme.textColorSecondary }, styles.infoItem]}
titleNumberOfLines={5}
left={() =>
icon && <PaperList.Icon color={theme.textColorSecondary} icon={icon} />
}
/>
);
export default {
Section,
SubHeader,
Item,
Divider,
InfoItem,
};
const styles = StyleSheet.create({
listSection: {
flex: 1,
marginVertical: 0,
},
divider: {
height: 1,
},
iconContainer: {
justifyContent: 'center',
},
icon: {
marginVertical: 0,
},
infoItem: {
fontSize: 14,
},
});
+63
View File
@@ -0,0 +1,63 @@
import React from 'react';
import { Pressable, StyleSheet, View, Text } from 'react-native';
import { Switch } from 'react-native-paper';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface SwitchItemProps {
value: boolean;
label: string;
description?: string;
onPress: () => void;
theme: ThemeTypeV1;
}
const SwitchItem: React.FC<SwitchItemProps> = ({
label,
description,
onPress,
theme,
value,
}) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={[styles.container]}
onPress={onPress}
>
<View style={styles.labelContainer}>
<Text style={[{ color: theme.textColorPrimary }, styles.label]}>
{label}
</Text>
{description ? (
<Text style={{ color: theme.textColorSecondary }}>{description}</Text>
) : null}
</View>
<Switch
value={value}
onValueChange={onPress}
color={theme.colorAccent}
style={styles.switch}
/>
</Pressable>
);
export default SwitchItem;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
},
labelContainer: {
flex: 1,
},
label: {
fontSize: 16,
textAlignVertical: 'center',
},
switch: {
marginLeft: 8,
},
});
+3
View File
@@ -7,3 +7,6 @@ export { default as EmptyView } from './EmptyView/EmptyView';
export { default as NovelList } from './NovelList/NovelList';
export { default as Chip } from './Chip/Chip';
export { default as Button } from './Button/Button';
export { default as Appbar } from './Appbar/Appbar';
export { default as SwitchItem } from './SwitchItem/SwitchItem';
export { default as List } from './List/List';
+3 -3
View File
@@ -19,7 +19,7 @@ import MoreStack from './MoreStack';
*/
import Novel from '../screens/novel/NovelScreen';
import Reader from '../screens/reader/ReaderScreen';
import Extension from '../screens/source/SourceScreen';
import SourceScreen from '../screens/source/SourceScreen';
import GlobalSearch from '../screens/browse/globalsearch/GlobalSearch';
import Migration from '../screens/browse/migration/Migration';
import SourceNovels from '../screens/browse/SourceNovels';
@@ -27,7 +27,7 @@ import MigrateNovel from '../screens/browse/migration/MigrationNovels';
import MalTopNovels from '../screens/browse/discover/MalTopNovels';
import NewUpdateDialog from '../components/NewUpdateDialog';
import BrowseSettings from '../screens/browse/SettingsBrowseScreen';
import BrowseSettings from '../screens/browse/BrowseSettings';
const Stack = createStackNavigator();
@@ -53,7 +53,7 @@ const MainNavigator = () => {
<Stack.Screen name="Novel" component={Novel} />
<Stack.Screen name="Chapter" component={Reader} />
<Stack.Screen name="MoreStack" component={MoreStack} />
<Stack.Screen name="Extension" component={Extension} />
<Stack.Screen name="SourceScreen" component={SourceScreen} />
<Stack.Screen name="BrowseMal" component={MalTopNovels} />
<Stack.Screen name="BrowseSettings" component={BrowseSettings} />
<Stack.Screen name="GlobalSearch" component={GlobalSearch} />
+12 -3
View File
@@ -15,15 +15,21 @@ export const useThemeV1 = () =>
(state: RootState) => state.settingsReducer.theme,
) as ThemeTypeV1;
export const useSettingsV1 = () =>
useAppSelector((state: RootState) => state.settingsReducer);
export const useSettingsV2 = () =>
useAppSelector((state: RootState) => state.settingsReducerV2);
export const useBrowseSettings = () =>
useAppSelector((state: RootState) => state.settingsReducerV2.browse) || {};
export const useAppearanceSettings = () =>
useAppSelector((state: RootState) => state.settingsReducerV2.appearance);
export const useReaderSettings = () =>
useAppSelector((state: RootState) => state.settingsReducerV2.reader);
export const useSettingsV1 = () =>
useAppSelector((state: RootState) => state.settingsReducer);
export const useReaderSettingsV1 = () =>
useAppSelector((state: RootState) => state.settingsReducer.reader);
@@ -41,3 +47,6 @@ export const useSavedChapterData = (chapterId: number) =>
export const useDownloadQueue = () =>
useAppSelector((state: RootState) => state.downloadsReducer.downloadQueue);
export const useSourcesReducer = () =>
useAppSelector((state: RootState) => state.sourceReducerV2);
+23 -130
View File
@@ -1,8 +1,8 @@
import { StatusBar } from 'react-native';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { ColorScheme, ThemeType } from '../../theme/types';
import { ThemeType } from '../../theme/types';
import { defaultDarkTheme } from '../../theme/dark';
import { BrowseSettingsMap } from './types';
export enum DisplayModes {
Compact = 'compact',
@@ -18,10 +18,10 @@ export enum TextAlignments {
Right = 'right',
}
interface ReaderTheme {
backgroundColor: string;
textColor: string;
}
// interface ReaderTheme {
// backgroundColor: string;
// textColor: string;
// }
interface SettingsState {
appearance: {
@@ -64,6 +64,11 @@ interface SettingsState {
incognitoMode: boolean;
downloadOnlyMode: boolean;
};
browse: {
onlyShowPinnedSources: boolean;
showMyAnimeList: boolean;
searchAllSources: boolean;
};
}
const initialState: SettingsState = {
@@ -107,142 +112,30 @@ const initialState: SettingsState = {
incognitoMode: false,
downloadOnlyMode: false,
},
browse: {
onlyShowPinnedSources: false,
showMyAnimeList: true,
searchAllSources: false,
},
};
export const settingsSlice = createSlice({
name: 'settingsReducerV2',
initialState,
reducers: {
setTheme: (state, action: PayloadAction<ThemeType>) => {
StatusBar.setBarStyle(
action.payload.type === ColorScheme.DARK
? 'light-content'
: 'dark-content',
);
state.appearance.theme = action.payload;
},
setAmoledMode: state => {
state.appearance.theme.background = '#000000';
state.appearance.theme.surface = '#000000';
state.appearance.theme.surfaceVariant = '#000000';
},
setDisplayMode: (state, action: PayloadAction<DisplayModes>) => {
state.appearance.displayMode = action.payload;
},
setGridSizPotrait: (state, action: PayloadAction<number>) => {
state.appearance.novelsPerRowPotrait = action.payload;
},
toggleShowUpdatesTab: state => {
state.appearance.showUpdatesTab = !state.appearance.showUpdatesTab;
},
toggleShowHistoryTab: state => {
state.appearance.showHistoryTab = !state.appearance.showHistoryTab;
},
toggleShowLabelsInNav: state => {
state.appearance.showLabelsInNav = !state.appearance.showLabelsInNav;
},
setReaderTheme: (state, action: PayloadAction<ReaderTheme>) => {
state.reader.backgroundColor = action.payload.backgroundColor;
state.reader.textColor = action.payload.textColor;
},
setReaderFont: (state, action: PayloadAction<{ fontFamily: string }>) => {
state.reader.fontFamily = action.payload.fontFamily;
},
setReaderPadding: (
setBrowseSettings: (
state,
action: PayloadAction<{ paddingHorizontal: number }>,
action: PayloadAction<{ key: keyof BrowseSettingsMap; value: any }>,
) => {
state.reader.paddingHorizontal = action.payload.paddingHorizontal;
},
setReaderFontSize: (state, action: PayloadAction<{ fontSize: number }>) => {
state.reader.fontSize = action.payload.fontSize;
},
setReaderLineHeight: (
state,
action: PayloadAction<{ lineHeight: number }>,
) => {
state.reader.lineHeight = action.payload.lineHeight;
},
setCustomCSS: (state, action: PayloadAction<{ customCSS: string }>) => {
state.reader.customCSS = action.payload.customCSS;
},
toggleReaderFullScreenMode: state => {
state.reader.fullScreenMode = !state.reader.fullScreenMode;
},
toggleWebViewReader: state => {
state.reader.useWebViewReader = !state.reader.useWebViewReader;
},
setLibraryFilter: (state, action: PayloadAction<{ filter: string }>) => {
if (state.library.filters.includes(action.payload.filter)) {
state.library.filters = state.library.filters.filter(
item => item !== action.payload.filter,
);
} else {
state.library.filters = [
...state.library.filters,
action.payload.filter,
];
if (state?.browse === undefined) {
state.browse = initialState.browse;
}
},
setLibrarySortOrder: (state, action: PayloadAction<{ sort: string }>) => {
state.library.sort = action.payload.sort;
},
toggleShowUnreadBadge: state => {
state.library.showUnreadBadge = !state.library.showUnreadBadge;
},
toggleShowDownloadsBadge: state => {
state.library.showDownloadsBadge = !state.library.showDownloadsBadge;
},
toggleShowProgressPercentage: state => {
state.reader.showProgressPercentage =
!state.reader.showProgressPercentage;
},
toggleUpdateLibraryOnLaunch: state => {
state.library.updateLibraryOnLaunch =
!state.library.updateLibraryOnLaunch;
},
toggleUpdateNovelMetadata: state => {
state.updates.updateNovelMetadata = !state.updates.updateNovelMetadata;
},
toggleOnlyUpdateOngoingNovels: state => {
state.updates.onlyUpdateOngoingNovels =
!state.updates.onlyUpdateOngoingNovels;
},
toggleShowLastUpdateTime: state => {
state.updates.showLastUpdateTime = !state.updates.showLastUpdateTime;
},
toggleUseSwipeGestures: state => {
state.reader.useSwipeGestures = !state.reader.useSwipeGestures;
state.browse[action.payload.key] = action.payload.value;
},
},
});
export const {
setTheme,
setDisplayMode,
toggleShowUpdatesTab,
toggleShowHistoryTab,
toggleShowLabelsInNav,
setReaderTheme,
setReaderFont,
setReaderPadding,
setReaderFontSize,
setReaderLineHeight,
setCustomCSS,
toggleReaderFullScreenMode,
setLibraryFilter,
setLibrarySortOrder,
toggleShowUnreadBadge,
toggleShowDownloadsBadge,
setAmoledMode,
toggleWebViewReader,
toggleShowProgressPercentage,
setGridSizPotrait,
toggleUpdateLibraryOnLaunch,
toggleOnlyUpdateOngoingNovels,
toggleUpdateNovelMetadata,
toggleShowLastUpdateTime,
toggleUseSwipeGestures,
} = settingsSlice.actions;
export const { setBrowseSettings } = settingsSlice.actions;
export default settingsSlice.reducer;
+37
View File
@@ -0,0 +1,37 @@
// export interface SettingsMap {
// 'app.downloadOnlyMode': 'string';
// 'app.incognitoMode': 'string';
// 'appearance.displayMode': 'string';
// 'appearance.novelsPerRowLandscape': 'string';
// 'appearance.novelsPerRowPotrait': 'string';
// 'appearance.showHistoryTab': 'string';
// 'appearance.showLabelsInNav': 'string';
// 'appearance.showUpdatesTab': 'string';
// 'appearance.theme': 'string';
// 'library.showDownloadsBadge': 'string';
// 'library.showUnreadBadge': 'string';
// 'library.sort': 'string';
// 'library.updateLibraryOnLaunch': 'string';
// 'reader.backgroundColor': 'string';
// 'reader.customCSS': 'string';
// 'reader.fontFamily': 'string';
// 'reader.fontSize': 'string';
// 'reader.fullScreenMode': 'string';
// 'reader.lineHeight': 'string';
// 'reader.paddingHorizontal': 'string';
// 'reader.showProgressPercentage': 'string';
// 'reader.textAlign': 'string';
// 'reader.textColor': 'string';
// 'reader.useSwipeGestures': 'string';
// 'reader.useWebViewReader': 'string';
// 'updates.lastUpdateTime': 'string';
// 'updates.onlyUpdateOngoingNovels': 'string';
// 'updates.showLastUpdateTime': 'string';
// 'updates.updateNovelMetadata': 'string';
// }
export interface BrowseSettingsMap {
onlyShowPinnedSources: 'string';
searchAllSources: 'string';
showMyAnimeList: 'string';
}
+24 -22
View File
@@ -1,18 +1,18 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import sourceList from '../../sources/sources.json';
import AllSources from '../../sources/sources.json';
import { Source } from '../../sources/types';
interface SourcesState {
allSources: Source[];
pinnedSources: number[];
pinnedSourceIds: number[];
lastUsed: number | null;
languageFilters: string[];
}
const initialState: SourcesState = {
allSources: [],
pinnedSources: [],
allSources: AllSources,
pinnedSourceIds: [],
lastUsed: null,
languageFilters: ['English'],
};
@@ -22,37 +22,39 @@ export const sourcesSlice = createSlice({
initialState,
reducers: {
getSourcesAction: state => {
let sources: Source[] = sourceList;
sources = sources.sort((a, b) =>
a.sourceName.localeCompare(b.sourceName),
);
sources = sources.filter(
const sources = AllSources.filter(
source => state.languageFilters.indexOf(source.lang) !== -1,
);
).sort((a, b) => a.sourceName.localeCompare(b.sourceName));
state.allSources = sources;
},
searchSourcesAction: (state, action: PayloadAction<string>) => {
state.allSources = sourceList.filter(
(source: Source) =>
source.sourceName
.toLowerCase()
.includes(action.payload.toLowerCase()) &&
state.languageFilters.indexOf(source.lang) !== -1,
);
state.allSources = AllSources.filter(
source => state.languageFilters.indexOf(source.lang) !== -1,
)
.sort((a, b) => a.sourceName.localeCompare(b.sourceName))
.filter(
(source: Source) =>
source.sourceName
.toLowerCase()
.includes(action.payload.toLowerCase()) &&
state.languageFilters.indexOf(source.lang) !== -1,
);
},
setLastUsedSource: (state, action: PayloadAction<{ sourceId: number }>) => {
state.lastUsed = action.payload.sourceId;
},
togglePinSource: (state, action: PayloadAction<number>) => {
if (state.pinnedSources.indexOf(action.payload) > -1) {
state.pinnedSources = state.pinnedSources.filter(
if (state?.pinnedSourceIds === undefined) {
state.pinnedSourceIds = [];
}
if (state.pinnedSourceIds?.indexOf(action.payload) > -1) {
state.pinnedSourceIds = state.pinnedSourceIds.filter(
source => source !== action.payload,
);
} else {
state.pinnedSources = [...state.pinnedSources, action.payload];
state.pinnedSourceIds = [...state.pinnedSourceIds, action.payload];
}
},
toggleLanguageFilter: (
+2
View File
@@ -7,6 +7,7 @@ import settingsReducerV2 from './settings/settingsSlice';
import localStorageReducer from './localStorage/localStorageSlice';
import libraryReducer from './library/library.reducer';
import sourceReducer from './source/source.reducers';
import sourceReducerV2 from './source/sourcesSlice';
import novelReducer from './novel/novel.reducer';
import updatesReducer from './updates/updates.reducer';
import trackerReducer from './tracker/tracker.reducer';
@@ -26,6 +27,7 @@ const reducers = combineReducers({
localStorageReducer,
libraryReducer,
sourceReducer,
sourceReducerV2,
novelReducer,
updatesReducer,
trackerReducer,
-196
View File
@@ -1,196 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react';
import {
StyleSheet,
View,
Text,
FlatList,
ActivityIndicator,
RefreshControl,
} from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import {
getSourcesAction,
pinSourceAction,
searchSourcesAction,
} from '../../redux/source/source.actions';
import { useSettings, useTheme } from '../../hooks/reduxHooks';
import { showToast } from '../../hooks/showToast';
import { Searchbar } from '../../components/Searchbar/Searchbar';
import EmptyView from '../../components/EmptyView';
import SourceCard from './SourceCard/SourceCard';
import MalCard from './discover/MalCard/MalCard';
import { getString } from '../../../strings/translations';
const Browse = ({ navigation }) => {
const [refreshing, setRefreshing] = useState(false);
const [searchText, setSearchText] = useState('');
let {
sources,
search,
loading,
pinned,
filters = [],
showMyAnimeList = true,
} = useSelector(state => state.sourceReducer);
const { onlyShowPinnedSources = false } = useSettings();
const theme = useTheme();
const dispatch = useDispatch();
const isPinned = sourceId => (pinned.indexOf(sourceId) === -1 ? false : true);
sources = sources.filter(source => filters.indexOf(source.lang) === -1);
search = search.filter(source => filters.indexOf(source.lang) === -1);
let pinnedSources = sources.filter(source => isPinned(source.sourceId));
useEffect(() => {
dispatch(getSourcesAction());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onRefresh = () => {
showToast('Updating extension list');
setRefreshing(true);
getSourcesAction();
setRefreshing(false);
};
const clearSearchbar = () => {
setSearchText('');
};
const onChangeText = text => {
setSearchText(text);
dispatch(searchSourcesAction(text));
};
const navigateToSource = useCallback(
source =>
navigation.navigate('Extension', {
sourceId: source.sourceId,
sourceName: source.sourceName,
url: source.url,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
const renderItem = ({ item }) => (
<SourceCard
source={item}
isPinned={isPinned(item.sourceId)}
navigateToSource={navigateToSource}
onTogglePinSource={sourceId => dispatch(pinSourceAction(sourceId))}
theme={theme}
/>
);
const Header = ({ title }) => (
<Text style={[styles.header, { color: theme.textColorSecondary }]}>
{title}
</Text>
);
return (
<View
style={[styles.container, { backgroundColor: theme.colorPrimaryDark }]}
>
<Searchbar
theme={theme}
placeholder={getString('browseScreen.searchbar')}
backAction="magnify"
searchText={searchText}
onChangeText={onChangeText}
clearSearchbar={clearSearchbar}
actions={[
{
icon: 'book-search',
onPress: () => navigation.navigate('GlobalSearch'),
},
{
icon: 'swap-vertical-variant',
onPress: () => navigation.navigate('Migration'),
},
{
icon: 'cog-outline',
onPress: () => navigation.navigate('BrowseSettings'),
},
]}
/>
<FlatList
contentContainerStyle={{ flexGrow: 1, paddingBottom: 32 }}
data={onlyShowPinnedSources ? [] : !searchText ? sources : search}
keyExtractor={item => item.sourceId.toString()}
renderItem={renderItem}
extraData={pinnedSources}
ListHeaderComponent={
<View>
{showMyAnimeList && (
<>
<Header title="Discover" />
{showMyAnimeList && <MalCard theme={theme} />}
</>
)}
{pinnedSources.length > 0 && (
<FlatList
contentContainerStyle={{ paddingBottom: 16 }}
data={pinnedSources}
keyExtractor={item => item.sourceId.toString()}
renderItem={renderItem}
extraData={pinnedSources}
ListHeaderComponent={<Header title="Pinned" />}
/>
)}
{sources.length > 0 && !onlyShowPinnedSources && (
<Header title="Sources" />
)}
</View>
}
ListEmptyComponent={
loading ? (
<ActivityIndicator
size="small"
color={theme.colorAccent}
style={{ marginTop: 16 }}
/>
) : (
sources.length === 0 && (
<EmptyView
icon="(・Д・。"
description="Enable languages from settings"
/>
)
)
}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={['white']}
progressBackgroundColor={theme.colorAccent}
/>
}
/>
</View>
);
};
export default Browse;
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
paddingHorizontal: 16,
paddingVertical: 12,
textTransform: 'uppercase',
},
});
+147
View File
@@ -0,0 +1,147 @@
import { SectionList, StyleSheet, Text } from 'react-native';
import React, { useCallback, useEffect } from 'react';
import { EmptyView, SearchbarV2 } from '../../components';
import {
getSourcesAction,
searchSourcesAction,
setLastUsedSource,
togglePinSource,
} from '../../redux/source/sourcesSlice';
import {
useAppDispatch,
useSourcesReducer,
useThemeV1,
} from '../../redux/hooks';
import { useNavigation } from '@react-navigation/native';
import { useSearch } from '../../hooks';
import SourceCard from './components/SourceCard/SourceCard';
import { getString } from '../../../strings/translations';
import { Source } from '../../sources/types';
const BrowseScreen = () => {
const { navigate } = useNavigation();
const theme = useThemeV1();
const dispatch = useAppDispatch();
const { searchText, setSearchText, clearSearchbar } = useSearch();
const onChangeText = (text: string) => {
setSearchText(text);
dispatch(searchSourcesAction(text));
};
const handleClearSearchbar = () => {
clearSearchbar();
dispatch(getSourcesAction());
};
const {
allSources,
pinnedSourceIds = [],
languageFilters = ['English'],
lastUsed,
} = useSourcesReducer();
useEffect(() => {
dispatch(getSourcesAction());
}, [dispatch, languageFilters]);
const isPinned = (sourceId: number) => pinnedSourceIds.indexOf(sourceId) > -1;
const pinnedSources = allSources.filter(source => isPinned(source.sourceId));
const lastUsedSource = lastUsed
? allSources.filter(source => source.sourceId === lastUsed)
: [];
const navigateToSource = useCallback((source: Source) => {
navigate(
'SourceScreen' as never,
{
sourceId: source.sourceId,
sourceName: source.sourceName,
url: source.url,
} as never,
);
dispatch(setLastUsedSource({ sourceId: source.sourceId }));
}, []);
return (
<>
<SearchbarV2
searchText={searchText}
placeholder="Search sources"
leftIcon="magnify"
onChangeText={onChangeText}
clearSearchbar={handleClearSearchbar}
theme={theme}
rightIcons={[
{
iconName: 'book-search',
onPress: () => navigate('GlobalSearch' as never),
},
{
iconName: 'swap-vertical-variant',
onPress: () => navigate('Migration' as never),
},
{
iconName: 'cog-outline',
onPress: () => navigate('BrowseSettings' as never),
},
]}
/>
{allSources.length === 0 ? (
<EmptyView
icon="(・Д・。"
description={getString('browseScreen.listEmpty')}
theme={theme}
/>
) : (
<SectionList
sections={[
{
header: getString('browseScreen.lastUsed'),
data: lastUsedSource,
},
{ header: getString('browseScreen.pinned'), data: pinnedSources },
{ header: getString('browseScreen.all'), data: allSources },
]}
keyExtractor={(_, index) => index.toString()}
renderSectionHeader={({ section: { header, data } }) =>
data.length > 0 ? (
<Text
style={[
styles.sectionHeader,
{ color: theme.textColorSecondary },
]}
>
{header}
</Text>
) : null
}
renderItem={({ item }) => (
<SourceCard
source={item}
isPinned={isPinned(item.sourceId)}
navigateToSource={navigateToSource}
onTogglePinSource={sourceId =>
dispatch(togglePinSource(sourceId))
}
theme={theme}
/>
)}
/>
)}
</>
);
};
export default BrowseScreen;
const styles = StyleSheet.create({
sectionHeader: {
fontSize: 14,
paddingVertical: 8,
paddingHorizontal: 16,
},
});
+126
View File
@@ -0,0 +1,126 @@
import { FlatList, StyleSheet } from 'react-native';
import React from 'react';
import { Appbar, List, SwitchItem } from '../../components';
import {
useAppDispatch,
useBrowseSettings,
useSourcesReducer,
useThemeV1,
} from '../../redux/hooks';
import { useNavigation } from '@react-navigation/native';
import { getString } from '../../../strings/translations';
import { languages } from '../../utils/constants/languages';
import { toggleLanguageFilter } from '../../redux/source/sourcesSlice';
import { setBrowseSettings } from '../../redux/settings/settingsSlice';
const BrowseSettings = () => {
const theme = useThemeV1();
const { goBack } = useNavigation();
const dispatch = useAppDispatch();
const { languageFilters } = useSourcesReducer();
const {
searchAllSources = false,
showMyAnimeList = true,
onlyShowPinnedSources = false,
} = useBrowseSettings();
return (
<>
<Appbar
title={getString('moreScreen.settingsScreen.browseSettings')}
handleGoBack={goBack}
theme={theme}
/>
<FlatList
contentContainerStyle={styles.container}
ListHeaderComponent={
<>
<List.SubHeader theme={theme}>
{getString('browseScreen.globalSearch')}
</List.SubHeader>
<SwitchItem
label={getString(
'moreScreen.settingsScreen.browseSettingsScreen.searchAllSources',
)}
value={searchAllSources}
onPress={() =>
dispatch(
setBrowseSettings({
key: 'searchAllSources',
value: !searchAllSources,
}),
)
}
theme={theme}
/>
<List.InfoItem
title={getString(
'moreScreen.settingsScreen.browseSettingsScreen.searchAllWarning',
)}
icon="information-outline"
theme={theme}
/>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>
{getString('browseScreen.discover')}
</List.SubHeader>
<SwitchItem
label="Show MyAnimeList"
value={showMyAnimeList}
onPress={() =>
dispatch(
setBrowseSettings({
key: 'showMyAnimeList',
value: !showMyAnimeList,
}),
)
}
theme={theme}
/>
<SwitchItem
label={getString(
'moreScreen.settingsScreen.browseSettingsScreen.onlyShowPinnedSources',
)}
value={onlyShowPinnedSources}
onPress={() =>
dispatch(
setBrowseSettings({
key: 'onlyShowPinnedSources',
value: !onlyShowPinnedSources,
}),
)
}
theme={theme}
/>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>
{getString(
'moreScreen.settingsScreen.browseSettingsScreen.languages',
)}
</List.SubHeader>
</>
}
keyExtractor={item => item}
data={languages}
renderItem={({ item }) => (
<SwitchItem
label={item}
value={languageFilters.indexOf(item) !== -1}
onPress={() => dispatch(toggleLanguageFilter({ language: item }))}
theme={theme}
/>
)}
/>
</>
);
};
export default BrowseSettings;
const styles = StyleSheet.create({
container: {
paddingBottom: 40,
},
});
-164
View File
@@ -1,164 +0,0 @@
import React, { useMemo } from 'react';
import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
import { Switch } from 'react-native-paper';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useDispatch, useSelector } from 'react-redux';
import { Appbar } from '../../components/Appbar';
import { List } from '../../components/List';
import { useSettings, useTheme } from '../../hooks/reduxHooks';
import { setAppSettings } from '../../redux/settings/settings.actions';
import {
enableDiscover,
filterLanguage,
} from '../../redux/source/source.actions';
const BrowseSettings = ({ navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
const { bottom } = useSafeAreaInsets();
const { filters = [], showMyAnimeList = true } = useSelector(
state => state.sourceReducer,
);
const languages = useMemo(
() =>
[
'English',
'Spanish',
'Japanese',
'Chinese',
'Arabic',
'Indonesian',
'Russian',
'Turkish',
'French',
'Portuguese (Brazil)',
'Vietnamese',
].sort(),
[],
);
const { searchAllSources = false, onlyShowPinnedSources = false } =
useSettings();
const renderItem = ({ item }) => {
return (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.switch}
onPress={() => dispatch(filterLanguage(item))}
>
<Text style={{ color: theme.textColorPrimary }}>{item}</Text>
<Switch
color={theme.colorAccent}
value={filters.indexOf(item) === -1 ? true : false}
onValueChange={() => dispatch(filterLanguage(item))}
/>
</Pressable>
);
};
return (
<View style={{ flex: 1 }}>
<Appbar onBackAction={navigation.goBack} title="Browse" />
<FlatList
contentContainerStyle={{ paddingBottom: bottom }}
data={languages}
keyExtractor={item => item}
renderItem={renderItem}
ListHeaderComponent={
<View>
<List.SubHeader theme={theme}>Global Search</List.SubHeader>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.switch}
onPress={() =>
dispatch(setAppSettings('searchAllSources', !searchAllSources))
}
>
<Text style={{ color: theme.textColorPrimary }}>
Search all sources
</Text>
<Switch
color={theme.colorAccent}
value={searchAllSources}
onValueChange={() =>
dispatch(
setAppSettings('searchAllSources', !searchAllSources),
)
}
/>
</Pressable>
<List.InfoItem
title="Searching a large number of sources may freeze the app till searching is finished."
icon="information-outline"
theme={theme}
/>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>Browse</List.SubHeader>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.switch}
onPress={() => dispatch(enableDiscover('showMyAnimeList'))}
>
<Text style={{ color: theme.textColorPrimary }}>MyAnimeList</Text>
<Switch
color={theme.colorAccent}
value={showMyAnimeList}
onValueChange={() =>
dispatch(enableDiscover('showMyAnimeList'))
}
/>
</Pressable>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.switch}
onPress={() =>
dispatch(
setAppSettings(
'onlyShowPinnedSources',
!onlyShowPinnedSources,
),
)
}
>
<Text style={{ color: theme.textColorPrimary }}>
Only show pinned sources
</Text>
<Switch
color={theme.colorAccent}
value={onlyShowPinnedSources}
onValueChange={() =>
dispatch(
setAppSettings(
'onlyShowPinnedSources',
!onlyShowPinnedSources,
),
)
}
/>
</Pressable>
<List.Divider theme={theme} />
<List.SubHeader theme={theme}>Languages</List.SubHeader>
</View>
}
/>
</View>
);
};
export default BrowseSettings;
const styles = StyleSheet.create({
switch: {
padding: 16,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
});
@@ -1,11 +1,11 @@
import React from 'react';
import { StyleSheet, Text, View, Image, Pressable } from 'react-native';
import { getString } from '../../../../strings/translations';
import { Button, IconButtonV2 } from '../../../components';
import { ButtonVariation } from '../../../components/Button/Button';
import { getString } from '../../../../../strings/translations';
import { Button, IconButtonV2 } from '../../../../components';
import { ButtonVariation } from '../../../../components/Button/Button';
import { Source } from '../../../sources/types';
import { ThemeTypeV1 } from '../../../theme/v1/theme/types';
import { Source } from '../../../../sources/types';
import { ThemeTypeV1 } from '../../../../theme/v1/theme/types';
interface Props {
source: Source;
+13
View File
@@ -0,0 +1,13 @@
export const languages = [
'Arabic',
'Chinese',
'English',
'French',
'Indonesian',
'Japanese',
'Portuguese (Brazil)',
'Russian',
'Spanish',
'Turkish',
'Vietnamese',
];
+15 -2
View File
@@ -38,7 +38,13 @@
},
"browse": "Browse",
"browseScreen": {
"searchbar": "Search sources"
"discover": "Discover",
"searchbar": "Search sources",
"globalSearch": "Global search",
"listEmpty": "Enable languages from settings",
"lastUsed": "Last used",
"pinned": "Pinned",
"all": "All"
},
"novelScreen": {
"addToLibaray": "Add to library",
@@ -59,7 +65,14 @@
"generalSettings": "General",
"generalSettingsScreen": {},
"readerSettings": "Reader",
"readerSettingsScreen": {}
"readerSettingsScreen": {},
"browseSettings": "Browse Settings",
"browseSettingsScreen": {
"onlyShowPinnedSources": "Only show pinned sources",
"languages": "Languages",
"searchAllSources": "Search all sources",
"searchAllWarning": "Searching a large number of sources may freeze the app till searching is finished."
}
}
}
}
+11
View File
@@ -27,7 +27,13 @@ export interface StringMap {
'history': 'string';
'historyScreen.searchbar': 'string';
'browse': 'string';
'browseScreen.discover': 'string';
'browseScreen.searchbar': 'string';
'browseScreen.globalSearch': 'string';
'browseScreen.listEmpty': 'string';
'browseScreen.lastUsed': 'string';
'browseScreen.pinned': 'string';
'browseScreen.all': 'string';
'novelScreen.addToLibaray': 'string';
'novelScreen.inLibaray': 'string';
'novelScreen.continueReading': 'string';
@@ -40,4 +46,9 @@ export interface StringMap {
'moreScreen.settings': 'string';
'moreScreen.settingsScreen.generalSettings': 'string';
'moreScreen.settingsScreen.readerSettings': 'string';
'moreScreen.settingsScreen.browseSettings': 'string';
'moreScreen.settingsScreen.browseSettingsScreen.onlyShowPinnedSources': 'string';
'moreScreen.settingsScreen.browseSettingsScreen.languages': 'string';
'moreScreen.settingsScreen.browseSettingsScreen.searchAllSources': 'string';
'moreScreen.settingsScreen.browseSettingsScreen.searchAllWarning': 'string';
}