Fix: pre-alpha-patch-1 (#905)

* fix: wrong version checker, update lastused

* fix: only show update dialog if newer version

* fix: remove chapter id in chapter item

* fix: date in chapter drawer

* fix: decode uri for local epub

* fix: simpler SetCategoriesModal

* fix: not allow local as default

* fix: no delay on selfhost backup/restore
This commit is contained in:
nyagami
2024-01-22 23:08:27 +07:00
committed by GitHub
parent b823c913b5
commit 0cf75fd6f9
14 changed files with 130 additions and 117 deletions
+34 -2
View File
@@ -1,6 +1,6 @@
import * as SQLite from 'expo-sqlite';
import { noop } from 'lodash-es';
import { BackupCategory, Category, NovelCategory } from '../types';
import { BackupCategory, Category, NovelCategory, CCategory } from '../types';
import { showToast } from '@utils/showToast';
import { txnErrorCallback } from '../utils/helpers';
const db = SQLite.openDatabase('lnreader.db');
@@ -22,6 +22,33 @@ export const getCategoriesFromDb = async (): Promise<Category[]> => {
);
};
export const getCategoriesWithCount = async (
novelIds: number[],
): Promise<CCategory[]> => {
const getCategoriesWithCountQuery = `
SELECT *, novelsCount
FROM Category LEFT JOIN
(
SELECT categoryId, COUNT(novelId) as novelsCount
FROM NovelCategory WHERE novelId in (${novelIds.join(
',',
)}) GROUP BY categoryId
) as NC ON Category.id = NC.categoryId
ORDER BY sort
`;
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
getCategoriesWithCountQuery,
[],
(txObj, { rows }) => resolve((rows as any)._array),
txnErrorCallback,
);
}),
);
};
const createCategoryQuery = 'INSERT INTO Category (name) VALUES (?)';
export const createCategory = (categoryName: string): void =>
@@ -94,7 +121,11 @@ export const isCategoryNameDuplicate = (
const updateCategoryOrderQuery = 'UPDATE Category SET sort = ? WHERE id = ?';
export const updateCategoryOrderInDb = (categories: Category[]): void =>
export const updateCategoryOrderInDb = (categories: Category[]): void => {
// Do not set local as default one
if (categories.length && categories[0].id == 2) {
return;
}
db.transaction(tx => {
categories.map(category => {
tx.executeSql(
@@ -105,6 +136,7 @@ export const updateCategoryOrderInDb = (categories: Category[]): void =>
);
});
});
};
export const getAllNovelCategories = (): Promise<NovelCategory[]> => {
return new Promise(resolve =>
+15 -38
View File
@@ -100,9 +100,6 @@ export const switchNovelToLibrary = async (
) => {
const novel = await getNovel(novelUrl);
if (novel) {
if (novel.isLocal) {
return;
}
db.transaction(tx => {
tx.executeSql(
'UPDATE Novel SET inLibrary = ? WHERE id = ?',
@@ -288,45 +285,25 @@ export const updateNovelCategoryById = async (
export const updateNovelCategories = async (
novelIds: number[],
categoryIds: number[],
option: string,
): Promise<void> => {
let queries: string[] = [];
// allow local novels have other categories, but not the revesre
// not allow others have local id;
categoryIds = categoryIds.filter(id => id !== 2);
if (option === 'KEEP_OLD') {
novelIds.forEach(novelId => {
categoryIds.forEach(categoryId =>
queries.push(
`INSERT OR REPLACE INTO NovelCategory (novelId, categoryId) VALUES (${novelId}, ${categoryId})`,
),
);
});
} else {
novelIds.forEach(novelId =>
queries.push(`DELETE FROM NovelCategory WHERE novelId = ${novelId}`),
queries.push(
`DELETE FROM NovelCategory WHERE novelId IN (${novelIds.join(
',',
)}) AND categoryId != 2`,
);
novelIds.forEach(novelId => {
categoryIds.forEach(categoryId =>
queries.push(
`INSERT INTO NovelCategory (novelId, categoryId) VALUES (${novelId}, ${categoryId})`,
),
);
novelIds.forEach(novelId => {
categoryIds.forEach(categoryId =>
queries.push(
`INSERT INTO NovelCategory (novelId, categoryId) VALUES (${novelId}, ${categoryId})`,
),
);
});
}
return new Promise(resolve => {
db.transaction(tx => {
queries.forEach((query, index) => {
tx.executeSql(
query,
[],
() => {
if (index === queries.length - 1) {
resolve();
}
},
txnErrorCallback,
);
});
});
db.transaction(tx => {
queries.forEach(query => {
tx.executeSql(query);
});
});
};
+4
View File
@@ -73,6 +73,10 @@ export interface NovelCategory {
categoryId: number;
}
export interface CCategory extends Category {
novelsCount: number;
}
export interface LibraryStats {
novelsCount?: number;
chaptersCount?: number;
+2 -2
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { appVersion } from '../../utils/versionUtils';
import { newer } from '@utils/compareVersion';
interface GithubUpdate {
isNewVersion: boolean;
@@ -32,9 +33,8 @@ export const useGithubUpdateChecker = (): GithubUpdate => {
const regex = /[^\\d.]/;
let newVersion = versionTag.replace(regex, '');
currentVersion = currentVersion;
return newVersion !== currentVersion;
return newer(newVersion, currentVersion);
};
useEffect(() => {
+10 -5
View File
@@ -73,6 +73,10 @@ export default function usePlugins() {
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;
}
@@ -161,16 +165,17 @@ export default function usePlugins() {
if (plugin.id !== plg.id) {
return plg;
}
return {
id: plugin.id,
url: plugin.url,
lang: plugin.lang,
iconUrl: plugin.iconUrl,
const newPlugin: PluginItem = {
...plugin,
site: _plg.site,
name: _plg.name,
version: _plg.version,
hasUpdate: false,
};
if (newPlugin.id === lastUsedPlugin?.id) {
setLastUsedPlugin(newPlugin);
}
return newPlugin;
}),
);
filterPlugins(languagesFilter);
+1 -2
View File
@@ -70,8 +70,7 @@ const installPlugin = async (url: string): Promise<Plugin | undefined> => {
const oldPlugin = plugins[plugin.id];
if (oldPlugin) {
if (newer(plugin.version, oldPlugin.version)) {
delete plugins[oldPlugin.id];
plugins[plugin.id] = plugin;
plugins[oldPlugin.id] = plugin;
await RNFS.writeFile(plugin.path, rawCode, 'utf8');
return plugin;
} else {
+13 -1
View File
@@ -43,10 +43,22 @@ const CategoriesScreen = () => {
getCategories();
}, []);
const updateCategorySort = (currentIndex: number, newIndex: number) => {
// Do not set local as default one
if (
(newIndex === 0 &&
currentIndex == 1 &&
categories?.[currentIndex].id === 2) ||
(newIndex === 1 && currentIndex == 0 && categories?.[newIndex].id === 2)
) {
return;
}
const updatedOrderCategories = orderBy(
categories?.map((category, index) => {
// + 1 because in db, sort start from 1
// swap adjacent cards
if (index === currentIndex) {
return { ...category, sort: newIndex + 1 }; // + 1 because in db, sort start from 1
return { ...category, sort: newIndex + 1 };
}
if (index === newIndex) {
return { ...category, sort: currentIndex + 1 };
@@ -44,11 +44,14 @@ const CategoryCard: React.FC<CategoryCardProps> = ({
style={[
styles.cardCtn,
{
backgroundColor: theme.isDark
? overlay(2, category.sort === 1 ? theme.surface2 : theme.surface)
: category.sort === 1
? theme.primaryContainer
: theme.secondaryContainer,
backgroundColor:
category.sort === 1
? theme.isDark
? overlay(2, theme.surface2)
: theme.primaryContainer
: category.id === 2
? theme.tertiaryContainer
: theme.secondaryContainer,
},
]}
>
+1 -1
View File
@@ -114,7 +114,7 @@ const ChapterItem: React.FC<ChapterItemProps> = ({
]}
numberOfLines={1}
>
{showChapterTitles ? name : `Chapter ${chapNum} • ID: ${id}`}
{showChapterTitles ? name : `Chapter ${chapNum}`}
</Text>
</Row>
<View style={styles.textRow}>
@@ -1,18 +1,19 @@
import React, { useEffect, useState } from 'react';
import { Dimensions, FlatList, StyleSheet, Text, View } from 'react-native';
import { Divider, Modal, overlay, Portal } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { Button } from '@components/index';
import { useTheme } from '@hooks/persisted';
import { getString } from '@strings/translations';
import { getCategoriesFromDb } from '@database/queries/CategoryQueries';
import { getCategoriesWithCount } from '@database/queries/CategoryQueries';
import { updateNovelCategories } from '@database/queries/NovelQueries';
import { Category } from '@database/types';
import { CCategory, Category } from '@database/types';
import { Checkbox } from '@components/Checkbox/Checkbox';
import { xor } from 'lodash-es';
import { RootStackParamList } from '@navigators/types';
interface SetCategoryModalProps {
novelIds: number[];
@@ -30,15 +31,14 @@ const SetCategoryModal: React.FC<SetCategoryModalProps> = ({
onEditCategories,
}) => {
const theme = useTheme();
const { navigate } = useNavigation();
const [option, setOption] = useState<string>('KEEP_OLD');
const { navigate } = useNavigation<NavigationProp<RootStackParamList>>();
const [selectedCategories, setSelectedCategories] = useState<Category[]>([]);
const [categories = [], setCategories] = useState<Category[]>();
const [categories = [], setCategories] = useState<CCategory[]>();
const getCategories = async () => {
const res = await getCategoriesFromDb();
const res = await getCategoriesWithCount(novelIds);
setCategories(res);
setSelectedCategories(res.filter(c => c.novelsCount));
};
useEffect(() => {
@@ -95,35 +95,13 @@ const SetCategoryModal: React.FC<SetCategoryModalProps> = ({
backgroundColor: theme.onSurfaceDisabled,
}}
/>
<View>
<Text style={[styles.modelOption, { color: theme.onSurface }]}>
{'Option'}
</Text>
<Checkbox
status={option === 'KEEP_OLD'}
label={'Keep old categories'}
onPress={() => setOption('KEEP_OLD')}
viewStyle={styles.checkboxView}
theme={theme}
/>
<Checkbox
status={option === 'REMOVE_OLD'}
label={'Remove old categories (not selected)'}
onPress={() => setOption('REMOVE_OLD')}
viewStyle={styles.checkboxView}
theme={theme}
/>
</View>
<View style={styles.btnContainer}>
<Button
title={getString('common.edit')}
onPress={() => {
navigate(
'MoreStack' as never,
{
screen: 'Categories',
} as never,
);
navigate('MoreStack', {
screen: 'Categories',
});
closeModal();
onEditCategories?.();
}}
@@ -142,7 +120,6 @@ const SetCategoryModal: React.FC<SetCategoryModalProps> = ({
await updateNovelCategories(
novelIds,
selectedCategories.map(category => category.id),
option,
);
closeModal();
setSelectedCategories([]);
+26 -22
View File
@@ -10,6 +10,7 @@ import { getString } from '@strings/translations';
import { ChapterScreenProps } from '@navigators/types';
import { ChapterInfo } from '@database/types';
import { ThemeColors } from '@theme/types';
import dayjs from 'dayjs';
const ChapterDrawer = ({ route, navigation }: ChapterScreenProps) => {
const theme = useTheme();
@@ -60,29 +61,32 @@ const ChapterDrawer = ({ route, navigation }: ChapterScreenProps) => {
chapter: item,
});
};
const renderItem: FlashListProps<ChapterInfo>['renderItem'] = ({ item }) => (
<View
style={[
styles.drawerElementContainer,
item.id === chapter.id && {
backgroundColor: color(theme.primary).alpha(0.12).string(),
},
]}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
onPress={() => changeChapter(item)}
style={styles.chapterCtn}
const renderItem: FlashListProps<ChapterInfo>['renderItem'] = ({ item }) => {
const parsedTime = dayjs(item.releaseTime);
return (
<View
style={[
styles.drawerElementContainer,
item.id === chapter.id && {
backgroundColor: color(theme.primary).alpha(0.12).string(),
},
]}
>
<Text numberOfLines={1} style={styles.chapterNameCtn}>
{item.name}
</Text>
{item.readTime ? (
<Text style={styles.releaseDateCtn}>{item.releaseTime}</Text>
) : null}
</Pressable>
</View>
);
<Pressable
android_ripple={{ color: theme.rippleColor }}
onPress={() => changeChapter(item)}
style={styles.chapterCtn}
>
<Text numberOfLines={1} style={styles.chapterNameCtn}>
{item.name}
</Text>
<Text style={styles.releaseDateCtn}>
{parsedTime.isValid() ? parsedTime.format('LL') : item.readTime}
</Text>
</Pressable>
</View>
);
};
const ListFooter = () => {
return (
-2
View File
@@ -58,7 +58,6 @@ const remoteBackupAction = async (taskData?: TaskData) => {
})
.then(() => subtasks[j]())
.then(backupPackage => upload(host, backupPackage))
.then(() => sleep(delay))
.catch(error => {
throw error;
});
@@ -144,7 +143,6 @@ const remoteRestoreAction = async (taskData?: TaskData) => {
},
})
.then(() => subtasks[j]())
.then(() => sleep(delay))
.catch(error => {
throw error;
});
+1 -2
View File
@@ -81,7 +81,7 @@ const insertLocalChapter = (
'INSERT INTO Chapter(novelId, name, url, releaseTime) VALUES(?, ?, ?, ?)',
[
novelId,
name,
decodeURI(name),
// use fakeid just for make the url is unique :D
NovelDownloadFolder + '/local/' + novelId + '/' + fakeId,
releaseTime,
@@ -184,7 +184,6 @@ const importEpubAction = async (taskData?: TaskData) => {
throw e;
});
filePaths.forEach(filePath => filePathSet.add(filePath));
await sleep(500);
}
}
await sleep(taskData.delay);
+4 -1
View File
@@ -4,10 +4,13 @@ export const older = (a: string, b: string): boolean => {
const arrA = a.split('.').map(value => Number(value));
const arrB = b.split('.').map(value => Number(value));
try {
for (let i = 0; i < Math.max(arrA.length, arrB.length); i++) {
for (let i = 0; i < Math.min(arrA.length, arrB.length); i++) {
if (arrA[i] < arrB[i]) {
return true;
}
if (arrA[i] > arrB[i]) {
return false;
}
}
} catch (e: any) {
return arrA.length < arrB.length;