Feat: Add features from main and fixes (#972)

* fix: download chapters filter

* fix: deletechapters missing

* fix: tweak chapter

* optimise: chapters preprocess

* fix: bookmark chapter

* fix: not showToast when no network

* fix: preprocess updates|history|download

* fix: ScrollView for morescreen

* feat: exclude download chapter filter

* fix: show labels in nav by default
This commit is contained in:
nyagami
2024-02-25 13:30:59 +07:00
committed by GitHub
parent 21e46d5493
commit 9c05266c3c
16 changed files with 323 additions and 233 deletions
+18 -1
View File
@@ -8,6 +8,8 @@ import {
deleteChapterHistory,
getHistoryFromDb,
} from '@database/queries/HistoryQueries';
import dayjs from 'dayjs';
import { parseChapterNumber } from '@utils/parseChapterNumber';
const useHistory = () => {
const [isLoading, setIsLoading] = useState(true);
@@ -16,7 +18,22 @@ const useHistory = () => {
const getHistory = () =>
getHistoryFromDb()
.then(res => setHistory(res))
.then(res =>
setHistory(
res.map(history => {
const parsedTime = dayjs(history.releaseTime);
return {
...history,
releaseTime: parsedTime.isValid()
? parsedTime.format('LL')
: history.releaseTime,
chapterNumber: history.chapterNumber
? history.chapterNumber
: parseChapterNumber(history.novelName, history.name),
};
}),
),
)
.catch((err: Error) => setError(err.message))
.finally(() => setIsLoading(false));
+24 -2
View File
@@ -29,6 +29,8 @@ import { NovelDownloadFolder } from '@utils/constants/download';
import * as RNFS from 'react-native-fs';
import { getString } from '@strings/translations';
import { ChapterItem } from '@plugins/types';
import dayjs from 'dayjs';
import { parseChapterNumber } from '@utils/parseChapterNumber';
// store key: '<PREFIX>_<novel.pluginId>_<novel.path>',
// store key: '<PREFIX>_<novel.id>',
@@ -110,9 +112,29 @@ export const useTrackedNovel = (novelId: number) => {
export const useNovel = (novelPath: string, pluginId: string) => {
const [loading, setLoading] = useState(true);
const [novel, setNovel] = useState<NovelInfo>();
const [chapters, setChapters] = useState<ChapterInfo[]>([]);
const [chapters, _setChapters] = useState<ChapterInfo[]>([]);
const [pages, setPages] = useState<string[]>([]);
const setChapters = useCallback(
(chapters: ChapterInfo[]) => {
if (novel) {
_setChapters(
chapters.map(chapter => {
const parsedTime = dayjs(chapter.releaseTime);
return {
...chapter,
releaseTime: parsedTime.isValid()
? parsedTime.format('LL')
: chapter.releaseTime,
chapterNumber: chapter.chapterNumber
? chapter.chapterNumber
: parseChapterNumber(novel.name, chapter.name),
};
}),
);
}
},
[novel, _setChapters],
);
const [pageIndex = defaultPageIndex, setPageIndex] = useMMKVNumber(`
${NOVEL_PAGE_INDEX_PREFIX}_${pluginId}_${novelPath}
`);
+21 -24
View File
@@ -10,7 +10,6 @@ import {
} from '@plugins/pluginManager';
import { newer } from '@utils/compareVersion';
import { MMKVStorage, getMMKVObject, setMMKVObject } from '@utils/mmkv/mmkv';
import { showToast } from '@utils/showToast';
import { useCallback } from 'react';
import { getString } from '@strings/translations';
@@ -60,31 +59,29 @@ export default function usePlugins() {
const refreshPlugins = useCallback(() => {
const installedPlugins =
getMMKVObject<PluginItem[]>(INSTALLED_PLUGINS) || [];
return fetchPlugins()
.then(fetchedPluginsMap => {
for (const key in fetchedPluginsMap) {
const lang = key as Language;
fetchedPluginsMap[lang] = fetchedPluginsMap[lang].filter(plg => {
const finded = installedPlugins.find(v => v.id === plg.id);
if (finded) {
if (newer(plg.version, finded.version)) {
finded.hasUpdate = true;
finded.iconUrl = plg.iconUrl;
finded.url = plg.url;
if (finded.id === lastUsedPlugin?.id) {
setLastUsedPlugin(finded);
}
return fetchPlugins().then(fetchedPluginsMap => {
for (const key in fetchedPluginsMap) {
const lang = key as Language;
fetchedPluginsMap[lang] = fetchedPluginsMap[lang].filter(plg => {
const finded = installedPlugins.find(v => v.id === plg.id);
if (finded) {
if (newer(plg.version, finded.version)) {
finded.hasUpdate = true;
finded.iconUrl = plg.iconUrl;
finded.url = plg.url;
if (finded.id === lastUsedPlugin?.id) {
setLastUsedPlugin(finded);
}
return false;
}
return true;
});
setMMKVObject(INSTALLED_PLUGINS, installedPlugins);
setMMKVObject(AVAILABLE_PLUGINS, fetchedPluginsMap);
filterPlugins(languagesFilter);
}
})
.catch((error: Error) => showToast(error.message));
return false;
}
return true;
});
setMMKVObject(INSTALLED_PLUGINS, installedPlugins);
setMMKVObject(AVAILABLE_PLUGINS, fetchedPluginsMap);
filterPlugins(languagesFilter);
}
});
}, [languagesFilter]);
const toggleLanguageFilter = (lang: Language) => {
+1 -1
View File
@@ -115,7 +115,7 @@ const initialAppSettings: AppSettings = {
showHistoryTab: true,
showUpdatesTab: true,
showLabelsInNav: false,
showLabelsInNav: true,
useFabForContinueReading: false,
/**
+15 -1
View File
@@ -4,6 +4,7 @@ import { getUpdatesFromDb } from '@database/queries/ChapterQueries';
import { Update } from '@database/types';
import { useMMKVBoolean, useMMKVString } from 'react-native-mmkv';
import dayjs from 'dayjs';
import { parseChapterNumber } from '@utils/parseChapterNumber';
export const SHOW_LAST_UPDATE_TIME = 'SHOW_LAST_UPDATE_TIME';
export const LAST_UPDATE_TIME = 'LAST_UPDATE_TIME';
@@ -33,7 +34,20 @@ export const useUpdates = () => {
const getUpdates = () =>
getUpdatesFromDb()
.then(res => {
setUpdates(res);
setUpdates(
res.map(update => {
const parsedTime = dayjs(update.releaseTime);
return {
...update,
releaseTime: parsedTime.isValid()
? parsedTime.format('LL')
: update.releaseTime,
chapterNumber: update.chapterNumber
? update.chapterNumber
: parseChapterNumber(update.novelName, update.name),
};
}),
);
if (res.length) {
if (
!lastUpdateTime ||
+2 -3
View File
@@ -123,15 +123,14 @@ const updatePlugin = async (plugin: PluginItem) => {
return installPlugin(plugin.url);
};
const fetchPlugins = async () => {
const fetchPlugins = (): Promise<Record<Language, Array<PluginItem>>> => {
// plugins host
const githubUsername = 'LNReader';
const githubRepository = 'lnreader-sources';
const availablePlugins: Record<Language, Array<PluginItem>> = await fetch(
return fetch(
`https://raw.githubusercontent.com/${githubUsername}/${githubRepository}/beta-dist/.dist/plugins.min.json`,
).then(res => res.json());
return availablePlugins;
};
const getPlugin = (pluginId: string) => plugins[pluginId];
@@ -7,6 +7,7 @@ import PluginCard from './PluginCard';
import { usePlugins } from '@hooks/persisted';
import { BrowseScreenProps } from '@navigators/types';
import TrackerCard from '../discover/TrackerCard';
import { showToast } from '@utils/showToast';
interface PluginSectionProps {
sections: { header: string; data: PluginItem[] }[];
@@ -108,7 +109,11 @@ const PluginSection = ({
refreshing={refreshing}
onRefresh={() => {
setRefreshing(true);
refreshPlugins().finally(() => setRefreshing(false));
refreshPlugins()
.finally(() => setRefreshing(false))
.catch(e => {
showToast(e);
});
}}
colors={[theme.onPrimary]}
progressBackgroundColor={theme.primary}
@@ -1,11 +1,10 @@
import React, { useMemo } from 'react';
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import dayjs from 'dayjs';
import { Image } from 'react-native';
import { IconButtonV2 } from '@components';
import { parseChapterNumber } from '@utils/parseChapterNumber';
import { History, NovelInfo } from '@database/types';
import { ThemeColors } from '@theme/types';
@@ -26,20 +25,6 @@ const HistoryCard: React.FC<HistoryCardProps> = ({
handleRemoveFromHistory,
theme,
}) => {
const chapterNoAndTime = useMemo(
() =>
`${getString('historyScreen.chapter')} ${parseChapterNumber(
history.novelName,
history.name,
)} • ${dayjs(history.readTime).format('LT').toUpperCase()}` +
`${
history.progress && history.progress > 0
? ' • ' + history.progress + '%'
: ''
}`,
[history, history.readTime],
);
return (
<Pressable
style={styles.container}
@@ -75,7 +60,14 @@ const HistoryCard: React.FC<HistoryCardProps> = ({
{history.novelName}
</Text>
<Text style={{ color: theme.onSurfaceVariant }}>
{chapterNoAndTime}
{`${getString('historyScreen.chapter')} ${
history.chapterNumber
} • ${dayjs(history.readTime).format('LT').toUpperCase()}` +
`${
history.progress && history.progress > 0
? ' • ' + history.progress + '%'
: ''
}`}
</Text>
</View>
</View>
+16 -1
View File
@@ -21,6 +21,8 @@ import { getString } from '@strings/translations';
import { DownloadsScreenProps } from '@navigators/types';
import { DownloadedChapter } from '@database/types';
import { showToast } from '@utils/showToast';
import dayjs from 'dayjs';
import { parseChapterNumber } from '@utils/parseChapterNumber';
type DownloadGroup = Record<number, DownloadedChapter[]>;
@@ -53,7 +55,20 @@ const Downloads = ({ navigation }: DownloadsScreenProps) => {
const getChapters = async () => {
const res = await getDownloadedChapters();
setChapters(res);
setChapters(
res.map(download => {
const parsedTime = dayjs(download.releaseTime);
return {
...download,
releaseTime: parsedTime.isValid()
? parsedTime.format('LL')
: download.releaseTime,
chapterNumber: download.chapterNumber
? download.chapterNumber
: parseChapterNumber(download.novelName, download.name),
};
}),
);
setLoading(false);
};
+133 -135
View File
@@ -30,149 +30,147 @@ const MoreScreen = ({ navigation }: MoreStackScreenProps) => {
setLibrarySettings({ incognitoMode: !incognitoMode });
return (
<>
<ScrollView>
<MoreHeader
title={getString('more')}
navigation={navigation}
theme={theme}
/>
<ScrollView style={{ flex: 1 }}>
<List.Section>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
paddingHorizontal: 16,
paddingVertical: 14,
flexDirection: 'row',
justifyContent: 'space-between',
}}
onPress={enableDownloadedOnlyMode}
>
<View style={{ flexDirection: 'row' }}>
<List.Icon theme={theme} icon="cloud-off-outline" />
<View style={{ marginLeft: 16 }}>
<Text
style={{
color: theme.onSurface,
fontSize: 16,
}}
>
{getString('moreScreen.downloadOnly')}
</Text>
<Text style={{ color: theme.onSurfaceVariant }}>
{getString('moreScreen.downloadOnlyDesc')}
</Text>
</View>
<List.Section>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
paddingHorizontal: 16,
paddingVertical: 14,
flexDirection: 'row',
justifyContent: 'space-between',
}}
onPress={enableDownloadedOnlyMode}
>
<View style={{ flexDirection: 'row' }}>
<List.Icon theme={theme} icon="cloud-off-outline" />
<View style={{ marginLeft: 16 }}>
<Text
style={{
color: theme.onSurface,
fontSize: 16,
}}
>
{getString('moreScreen.downloadOnly')}
</Text>
<Text style={{ color: theme.onSurfaceVariant }}>
{getString('moreScreen.downloadOnlyDesc')}
</Text>
</View>
<Switch
value={downloadedOnlyMode}
onValueChange={enableDownloadedOnlyMode}
color={theme.primary}
style={{ marginRight: 8 }}
/>
</Pressable>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
paddingHorizontal: 16,
paddingVertical: 14,
flexDirection: 'row',
justifyContent: 'space-between',
}}
onPress={enableIncognitoMode}
>
<View style={{ flexDirection: 'row' }}>
<List.Icon theme={theme} icon="incognito" />
<View style={{ marginLeft: 16 }}>
<Text
style={{
color: theme.onSurface,
fontSize: 16,
}}
>
{getString('moreScreen.incognitoMode')}
</Text>
<Text style={{ color: theme.onSurfaceVariant }}>
{getString('moreScreen.incognitoModeDesc')}
</Text>
</View>
</View>
<Switch
value={downloadedOnlyMode}
onValueChange={enableDownloadedOnlyMode}
color={theme.primary}
style={{ marginRight: 8 }}
/>
</Pressable>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
paddingHorizontal: 16,
paddingVertical: 14,
flexDirection: 'row',
justifyContent: 'space-between',
}}
onPress={enableIncognitoMode}
>
<View style={{ flexDirection: 'row' }}>
<List.Icon theme={theme} icon="incognito" />
<View style={{ marginLeft: 16 }}>
<Text
style={{
color: theme.onSurface,
fontSize: 16,
}}
>
{getString('moreScreen.incognitoMode')}
</Text>
<Text style={{ color: theme.onSurfaceVariant }}>
{getString('moreScreen.incognitoModeDesc')}
</Text>
</View>
<Switch
value={incognitoMode}
onValueChange={enableIncognitoMode}
color={theme.primary}
style={{ marginRight: 8 }}
/>
</Pressable>
<List.Divider theme={theme} />
<List.Item
title={getString('moreScreen.downloadQueue')}
description={queue.length > 0 ? queue.length + ' remaining' : ''}
icon="progress-download"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'DownloadQueue',
})
}
theme={theme}
</View>
<Switch
value={incognitoMode}
onValueChange={enableIncognitoMode}
color={theme.primary}
style={{ marginRight: 8 }}
/>
<List.Item
title={getString('common.downloads')}
icon="folder-download"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Downloads',
})
}
theme={theme}
/>
<List.Item
title={getString('common.categories')}
icon="label-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Categories',
})
}
theme={theme}
/>
<List.Item
title={getString('statsScreen.title')}
icon="chart-line"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Statistics',
})
}
theme={theme}
/>
<List.Divider theme={theme} />
<List.Item
title={getString('common.settings')}
icon="cog-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'SettingsStack',
params: {
screen: 'Settings',
},
})
}
theme={theme}
/>
<List.Item
title={getString('common.about')}
icon="information-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'About',
})
}
theme={theme}
/>
</List.Section>
</ScrollView>
</>
</Pressable>
<List.Divider theme={theme} />
<List.Item
title={getString('moreScreen.downloadQueue')}
description={queue.length > 0 ? queue.length + ' remaining' : ''}
icon="progress-download"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'DownloadQueue',
})
}
theme={theme}
/>
<List.Item
title={getString('common.downloads')}
icon="folder-download"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Downloads',
})
}
theme={theme}
/>
<List.Item
title={getString('common.categories')}
icon="label-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Categories',
})
}
theme={theme}
/>
<List.Item
title={getString('statsScreen.title')}
icon="chart-line"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Statistics',
})
}
theme={theme}
/>
<List.Divider theme={theme} />
<List.Item
title={getString('common.settings')}
icon="cog-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'SettingsStack',
params: {
screen: 'Settings',
},
})
}
theme={theme}
/>
<List.Item
title={getString('common.about')}
icon="information-outline"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'About',
})
}
theme={theme}
/>
</List.Section>
</ScrollView>
);
};
+13 -3
View File
@@ -158,7 +158,10 @@ const Novel = ({ route, navigation }: NovelScreenProps) => {
icon: 'download-outline',
onPress: () => {
if (novel) {
downloadChapters(novel, selected);
downloadChapters(
novel,
selected.filter(chapter => !chapter.isDownloaded),
);
}
setSelected([]);
},
@@ -168,6 +171,7 @@ const Novel = ({ route, navigation }: NovelScreenProps) => {
list.push({
icon: 'trash-can-outline',
onPress: () => {
deleteChapters(selected.filter(chapter => chapter.isDownloaded));
setSelected([]);
},
});
@@ -454,7 +458,10 @@ const Novel = ({ route, navigation }: NovelScreenProps) => {
if (novel) {
downloadChapters(
novel,
chapters.filter(chapter => chapter.unread),
chapters.filter(
chapter =>
chapter.unread && !chapter.isDownloaded,
),
);
}
}}
@@ -467,7 +474,10 @@ const Novel = ({ route, navigation }: NovelScreenProps) => {
}}
onPress={() => {
if (novel) {
downloadChapters(novel, chapters);
downloadChapters(
novel,
chapters.filter(chapter => !chapter.isDownloaded),
);
}
showDownloadMenu(false);
}}
+20 -26
View File
@@ -1,20 +1,15 @@
import React, { memo, ReactNode, useState } from 'react';
import React, { memo, ReactNode } from 'react';
import { View, Text, StyleSheet, Pressable } from 'react-native';
import color from 'color';
import { Row } from '@components/Common';
import {
ChapterBookmarkButton,
DownloadButton,
} from './Chapter/ChapterDownloadButtons';
import { parseChapterNumber } from '@utils/parseChapterNumber';
import { ThemeColors } from '@theme/types';
import { ChapterInfo } from '@database/types';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import dayjs from 'dayjs';
import { getString } from '@strings/translations';
import { useBoolean } from '@hooks';
interface ChapterItemProps {
isDownloading?: boolean;
@@ -51,14 +46,11 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
}) => {
const { id, name, unread, releaseTime, bookmark, chapterNumber, progress } =
chapter;
const [deleteChapterMenuVisible, setDeleteChapterMenuVisible] =
useState(false);
const showDeleteChapterMenu = () => setDeleteChapterMenuVisible(true);
const hideDeleteChapterMenu = () => setDeleteChapterMenuVisible(false);
const chapNum = chapterNumber
? chapterNumber
: parseChapterNumber(novelName, name);
const parsedTime = dayjs(releaseTime);
const {
value: isMenuVisible,
setTrue: showMenu,
setFalse: hideMenu,
} = useBoolean();
return (
<Pressable
key={'chapterItem' + id}
@@ -74,9 +66,9 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
onLongPress={() => onSelectLongPress?.(chapter)}
android_ripple={{ color: theme.rippleColor }}
>
<Row style={styles.row}>
<View style={styles.row}>
{left}
{!!bookmark && <ChapterBookmarkButton theme={theme} />}
{bookmark ? <ChapterBookmarkButton theme={theme} /> : null}
<View style={{ flex: 1 }}>
{isUpdateCard && (
<Text
@@ -91,7 +83,7 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
{novelName}
</Text>
)}
<Row style={styles.row}>
<View style={styles.row}>
{unread ? (
<MaterialCommunityIcons
name="circle"
@@ -116,9 +108,11 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
>
{showChapterTitles
? name
: getString('novelScreen.chapterChapnum', { num: chapNum })}
: getString('novelScreen.chapterChapnum', {
num: chapterNumber,
})}
</Text>
</Row>
</View>
<View style={styles.textRow}>
{releaseTime && !isUpdateCard ? (
<Text
@@ -134,7 +128,7 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
]}
numberOfLines={1}
>
{parsedTime.isValid() ? parsedTime.format('LL') : releaseTime}
{releaseTime}
</Text>
) : null}
{!isUpdateCard && progress && progress > 0 && chapter.unread ? (
@@ -152,7 +146,7 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
) : null}
</View>
</View>
</Row>
</View>
{!isLocal && (
<DownloadButton
isDownloading={isDownloading}
@@ -160,9 +154,9 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
theme={theme}
deleteChapter={deleteChapter}
downloadChapter={downloadChapter}
hideDeleteChapterMenu={hideDeleteChapterMenu}
showDeleteChapterMenu={showDeleteChapterMenu}
deleteChapterMenuVisible={deleteChapterMenuVisible}
hideDeleteChapterMenu={hideMenu}
showDeleteChapterMenu={showMenu}
deleteChapterMenuVisible={isMenuVisible}
/>
)}
</Pressable>
@@ -187,7 +181,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
marginTop: 5,
},
row: { flex: 1, overflow: 'hidden' },
row: { flex: 1, flexDirection: 'row', alignItems: 'center' },
unreadIcon: {
marginRight: 4,
},
@@ -41,12 +41,24 @@ const ChaptersSettingsSheet = ({
<Checkbox
theme={theme}
label={getString('novelScreen.bottomSheet.filters.downloaded')}
status={!!filter.match('AND isDownloaded = 1')}
onPress={() =>
filter.match('AND isDownloaded = 1')
? filterChapters(filter.replace(' AND isDownloaded = 1', ''))
: filterChapters(filter + ' AND isDownloaded = 1')
status={
filter.match('AND isDownloaded=1')
? true
: filter.match('AND isDownloaded=0')
? 'indeterminate'
: false
}
onPress={() => {
if (filter.match('AND isDownloaded=1')) {
filterChapters(
filter.replace(' AND isDownloaded=1', ' AND isDownloaded=0'),
);
} else if (filter.match('AND isDownloaded=0')) {
filterChapters(filter.replace(' AND isDownloaded=0', ''));
} else {
filterChapters(filter + ' AND isDownloaded=1');
}
}}
/>
<Checkbox
theme={theme}
+2 -1
View File
@@ -359,7 +359,8 @@ export const ChapterContent = ({
<>
<ReaderAppbar
novelName={novel.name}
chapter={chapter}
chapter={sourceChapter}
setChapter={setChapter}
goBack={navigation.goBack}
theme={theme}
/>
@@ -10,7 +10,6 @@ import { getString } from '@strings/translations';
import { ChapterScreenProps } from '@navigators/types';
import { ChapterInfo } from '@database/types';
import { ThemeColors } from '@theme/types';
import dayjs from 'dayjs';
import { NovelSettings } from '@hooks/persisted/useNovel';
type ChapterDrawerProps = ChapterScreenProps & {
@@ -73,7 +72,6 @@ const ChapterDrawer = ({
});
};
const renderItem: FlashListProps<ChapterInfo>['renderItem'] = ({ item }) => {
const parsedTime = dayjs(item.releaseTime);
return (
<View
style={[
@@ -88,12 +86,25 @@ const ChapterDrawer = ({
onPress={() => changeChapter(item)}
style={styles.chapterCtn}
>
<Text numberOfLines={1} style={styles.chapterNameCtn}>
<Text
numberOfLines={1}
style={[
styles.chapterNameCtn,
{ color: item.unread ? theme.onSurface : theme.outline },
]}
>
{item.name}
</Text>
<Text style={styles.releaseDateCtn}>
{parsedTime.isValid() ? parsedTime.format('LL') : item.readTime}
</Text>
{item.releaseTime ? (
<Text
style={[
styles.releaseDateCtn,
{ color: item.unread ? theme.onSurfaceVariant : theme.outline },
]}
>
{item.releaseTime}
</Text>
) : null}
</Pressable>
</View>
);
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import color from 'color';
@@ -11,9 +11,10 @@ import { bookmarkChapter } from '@database/queries/ChapterQueries';
interface ReaderAppbarProps {
novelName: string;
chapter: ChapterInfo;
chapter: ChapterInfo & { chapterText: string };
theme: ThemeColors;
goBack: () => void;
setChapter: (chapter: ChapterInfo & { chapterText: string }) => void;
}
const ReaderAppbar = ({
@@ -21,9 +22,8 @@ const ReaderAppbar = ({
chapter,
goBack,
theme,
setChapter,
}: ReaderAppbarProps) => {
const [bookmarked, setBookmarked] = useState(chapter.bookmark);
return (
<Animated.View
entering={FadeIn.duration(150)}
@@ -59,11 +59,14 @@ const ReaderAppbar = ({
</Text>
</View>
<IconButtonV2
name={bookmarked ? 'bookmark' : 'bookmark-outline'}
name={chapter.bookmark ? 'bookmark' : 'bookmark-outline'}
size={24}
onPress={() => {
bookmarkChapter(chapter.id).then(() =>
setBookmarked(!bookmarked),
setChapter({
...chapter,
bookmark: !chapter.bookmark,
}),
);
}}
color={theme.onSurface}