diff --git a/scripts/stringTypes.cjs b/scripts/stringTypes.cjs index 920e7d77..ea0a1890 100644 --- a/scripts/stringTypes.cjs +++ b/scripts/stringTypes.cjs @@ -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)))} diff --git a/src/components/Appbar/Appbar.tsx b/src/components/Appbar/Appbar.tsx new file mode 100644 index 00000000..f8a95842 --- /dev/null +++ b/src/components/Appbar/Appbar.tsx @@ -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 = ({ title, handleGoBack, theme }) => ( + + {handleGoBack && } + + +); + +export default Appbar; diff --git a/src/components/List/List.tsx b/src/components/List/List.tsx new file mode 100644 index 00000000..b7ec70a0 --- /dev/null +++ b/src/components/List/List.tsx @@ -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 }) => ( + {children} +); + +const SubHeader = ({ + children, + theme, +}: { + children: ReactNode; + theme: ThemeTypeV1; +}) => ( + + {children} + +); + +const Item: React.FC = ({ + title, + description, + icon, + onPress, + theme, +}) => ( + ( + + {icon && ( + + )} + + )} + onPress={onPress} + rippleColor={theme.rippleColor} + /> +); + +const Divider = ({ theme }: { theme: ThemeTypeV1 }) => ( + +); + +const InfoItem = ({ + title, + icon, + theme, +}: { + title: string; + icon: string; + theme: ThemeTypeV1; +}) => ( + + 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, + }, +}); diff --git a/src/components/SwitchItem/SwitchItem.tsx b/src/components/SwitchItem/SwitchItem.tsx new file mode 100644 index 00000000..a37a2d8a --- /dev/null +++ b/src/components/SwitchItem/SwitchItem.tsx @@ -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 = ({ + label, + description, + onPress, + theme, + value, +}) => ( + + + + {label} + + {description ? ( + {description} + ) : null} + + + +); + +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, + }, +}); diff --git a/src/components/index.ts b/src/components/index.ts index 59bbef22..55fc96e7 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -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'; diff --git a/src/navigators/Main.js b/src/navigators/Main.js index 326c6d76..ffc3c386 100644 --- a/src/navigators/Main.js +++ b/src/navigators/Main.js @@ -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 = () => { - + diff --git a/src/redux/hooks/index.ts b/src/redux/hooks/index.ts index 53e943f3..ed392eab 100644 --- a/src/redux/hooks/index.ts +++ b/src/redux/hooks/index.ts @@ -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); diff --git a/src/redux/settings/settingsSlice.ts b/src/redux/settings/settingsSlice.ts index b0374c32..ea02cb12 100644 --- a/src/redux/settings/settingsSlice.ts +++ b/src/redux/settings/settingsSlice.ts @@ -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) => { - 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) => { - state.appearance.displayMode = action.payload; - }, - setGridSizPotrait: (state, action: PayloadAction) => { - 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) => { - 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; diff --git a/src/redux/settings/types/index.ts b/src/redux/settings/types/index.ts new file mode 100644 index 00000000..f0a134e5 --- /dev/null +++ b/src/redux/settings/types/index.ts @@ -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'; +} diff --git a/src/redux/source/sourcesSlice.ts b/src/redux/source/sourcesSlice.ts index 113a6690..145e2e70 100644 --- a/src/redux/source/sourcesSlice.ts +++ b/src/redux/source/sourcesSlice.ts @@ -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) => { - 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) => { - 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: ( diff --git a/src/redux/store.ts b/src/redux/store.ts index 51a10639..e7fcdae5 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -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, diff --git a/src/screens/browse/BrowseScreen.js b/src/screens/browse/BrowseScreen.js deleted file mode 100644 index ce4e7cb3..00000000 --- a/src/screens/browse/BrowseScreen.js +++ /dev/null @@ -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 }) => ( - dispatch(pinSourceAction(sourceId))} - theme={theme} - /> - ); - - const Header = ({ title }) => ( - - {title} - - ); - - return ( - - navigation.navigate('GlobalSearch'), - }, - { - icon: 'swap-vertical-variant', - onPress: () => navigation.navigate('Migration'), - }, - { - icon: 'cog-outline', - onPress: () => navigation.navigate('BrowseSettings'), - }, - ]} - /> - - item.sourceId.toString()} - renderItem={renderItem} - extraData={pinnedSources} - ListHeaderComponent={ - - {showMyAnimeList && ( - <> -
- {showMyAnimeList && } - - )} - {pinnedSources.length > 0 && ( - item.sourceId.toString()} - renderItem={renderItem} - extraData={pinnedSources} - ListHeaderComponent={
} - /> - )} - {sources.length > 0 && !onlyShowPinnedSources && ( -
- )} - - } - ListEmptyComponent={ - loading ? ( - - ) : ( - sources.length === 0 && ( - - ) - ) - } - refreshControl={ - - } - /> - - ); -}; - -export default Browse; - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - header: { - paddingHorizontal: 16, - paddingVertical: 12, - textTransform: 'uppercase', - }, -}); diff --git a/src/screens/browse/BrowseScreen.tsx b/src/screens/browse/BrowseScreen.tsx new file mode 100644 index 00000000..ef53ccd9 --- /dev/null +++ b/src/screens/browse/BrowseScreen.tsx @@ -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 ( + <> + navigate('GlobalSearch' as never), + }, + { + iconName: 'swap-vertical-variant', + onPress: () => navigate('Migration' as never), + }, + { + iconName: 'cog-outline', + onPress: () => navigate('BrowseSettings' as never), + }, + ]} + /> + {allSources.length === 0 ? ( + + ) : ( + index.toString()} + renderSectionHeader={({ section: { header, data } }) => + data.length > 0 ? ( + + {header} + + ) : null + } + renderItem={({ item }) => ( + + dispatch(togglePinSource(sourceId)) + } + theme={theme} + /> + )} + /> + )} + + ); +}; + +export default BrowseScreen; + +const styles = StyleSheet.create({ + sectionHeader: { + fontSize: 14, + paddingVertical: 8, + paddingHorizontal: 16, + }, +}); diff --git a/src/screens/browse/BrowseSettings.tsx b/src/screens/browse/BrowseSettings.tsx new file mode 100644 index 00000000..55ff8480 --- /dev/null +++ b/src/screens/browse/BrowseSettings.tsx @@ -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 ( + <> + + + + {getString('browseScreen.globalSearch')} + + + dispatch( + setBrowseSettings({ + key: 'searchAllSources', + value: !searchAllSources, + }), + ) + } + theme={theme} + /> + + + + {getString('browseScreen.discover')} + + + dispatch( + setBrowseSettings({ + key: 'showMyAnimeList', + value: !showMyAnimeList, + }), + ) + } + theme={theme} + /> + + dispatch( + setBrowseSettings({ + key: 'onlyShowPinnedSources', + value: !onlyShowPinnedSources, + }), + ) + } + theme={theme} + /> + + + {getString( + 'moreScreen.settingsScreen.browseSettingsScreen.languages', + )} + + + } + keyExtractor={item => item} + data={languages} + renderItem={({ item }) => ( + dispatch(toggleLanguageFilter({ language: item }))} + theme={theme} + /> + )} + /> + + ); +}; + +export default BrowseSettings; + +const styles = StyleSheet.create({ + container: { + paddingBottom: 40, + }, +}); diff --git a/src/screens/browse/SettingsBrowseScreen.js b/src/screens/browse/SettingsBrowseScreen.js deleted file mode 100644 index 81e4aed5..00000000 --- a/src/screens/browse/SettingsBrowseScreen.js +++ /dev/null @@ -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 ( - dispatch(filterLanguage(item))} - > - {item} - dispatch(filterLanguage(item))} - /> - - ); - }; - - return ( - - - item} - renderItem={renderItem} - ListHeaderComponent={ - - Global Search - - dispatch(setAppSettings('searchAllSources', !searchAllSources)) - } - > - - Search all sources - - - dispatch( - setAppSettings('searchAllSources', !searchAllSources), - ) - } - /> - - - - Browse - dispatch(enableDiscover('showMyAnimeList'))} - > - MyAnimeList - - dispatch(enableDiscover('showMyAnimeList')) - } - /> - - - dispatch( - setAppSettings( - 'onlyShowPinnedSources', - !onlyShowPinnedSources, - ), - ) - } - > - - Only show pinned sources - - - dispatch( - setAppSettings( - 'onlyShowPinnedSources', - !onlyShowPinnedSources, - ), - ) - } - /> - - - Languages - - } - /> - - ); -}; - -export default BrowseSettings; - -const styles = StyleSheet.create({ - switch: { - padding: 16, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, -}); diff --git a/src/screens/browse/SourceCard/SourceCard.tsx b/src/screens/browse/components/SourceCard/SourceCard.tsx similarity index 85% rename from src/screens/browse/SourceCard/SourceCard.tsx rename to src/screens/browse/components/SourceCard/SourceCard.tsx index 37fc5d68..f56c31b4 100644 --- a/src/screens/browse/SourceCard/SourceCard.tsx +++ b/src/screens/browse/components/SourceCard/SourceCard.tsx @@ -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; diff --git a/src/utils/constants/languages.ts b/src/utils/constants/languages.ts new file mode 100644 index 00000000..f7e839a7 --- /dev/null +++ b/src/utils/constants/languages.ts @@ -0,0 +1,13 @@ +export const languages = [ + 'Arabic', + 'Chinese', + 'English', + 'French', + 'Indonesian', + 'Japanese', + 'Portuguese (Brazil)', + 'Russian', + 'Spanish', + 'Turkish', + 'Vietnamese', +]; diff --git a/strings/languages/en/strings.json b/strings/languages/en/strings.json index 022bcb96..5893b92c 100644 --- a/strings/languages/en/strings.json +++ b/strings/languages/en/strings.json @@ -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." + } } } } diff --git a/strings/types/index.ts b/strings/types/index.ts index c7b74442..fab69b1c 100644 --- a/strings/types/index.ts +++ b/strings/types/index.ts @@ -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'; }