diff --git a/src/database/queries/LibraryQueries.ts b/src/database/queries/LibraryQueries.ts
index 92f36aaa..b92555bf 100644
--- a/src/database/queries/LibraryQueries.ts
+++ b/src/database/queries/LibraryQueries.ts
@@ -8,11 +8,28 @@ import {
inArray,
exists,
notExists,
+ isNotNull,
+ ne,
} from 'drizzle-orm';
import { dbManager } from '@database/db';
import { novelSchema, novelCategorySchema } from '@database/schema';
import { castInt } from '@database/manager/manager';
-import type { GlobalUpdateCategoryFilters } from '@hooks/persisted/useSettings';
+import type {
+ GlobalUpdateCategoryFilters,
+ SmartUpdateFilters,
+} from '@hooks/persisted/useSettings';
+
+const smartUpdateConditions = ({
+ skipCompleted,
+ skipUnstarted,
+ skipWithUnread,
+}: SmartUpdateFilters) => [
+ skipWithUnread
+ ? sql`COALESCE(${novelSchema.chaptersUnread}, 0) = 0`
+ : undefined,
+ skipUnstarted ? isNotNull(novelSchema.lastReadAt) : undefined,
+ skipCompleted ? ne(novelSchema.status, 'Completed') : undefined,
+];
/**
* Get library novels with optional filtering and sorting using Drizzle ORM
@@ -55,8 +72,12 @@ export const getLibraryNovelsFromDb = (
* An empty include list means all categories, while exclusions always win.
*/
export const getLibraryNovelsForGlobalUpdate = (
- onlyUpdateOngoingNovels: boolean,
{ includedCategoryIds, excludedCategoryIds }: GlobalUpdateCategoryFilters,
+ smartUpdateFilters: SmartUpdateFilters = {
+ skipCompleted: false,
+ skipUnstarted: false,
+ skipWithUnread: false,
+ },
) => {
const includedNovel = includedCategoryIds.length
? dbManager
@@ -88,9 +109,9 @@ export const getLibraryNovelsForGlobalUpdate = (
and(
eq(novelSchema.inLibrary, true),
eq(novelSchema.isLocal, false),
- onlyUpdateOngoingNovels ? eq(novelSchema.status, 'Ongoing') : undefined,
includedNovel ? exists(includedNovel) : undefined,
excludedNovel ? notExists(excludedNovel) : undefined,
+ ...smartUpdateConditions(smartUpdateFilters),
),
)
.all();
@@ -101,7 +122,6 @@ export const getLibraryNovelsForGlobalUpdate = (
*/
export const getLibraryWithCategory = async (
categoryId?: number | null,
- onlyUpdateOngoingNovels?: boolean,
excludeLocalNovels?: boolean,
) => {
// First, get novel IDs associated with the specified category
@@ -131,7 +151,6 @@ export const getLibraryWithCategory = async (
eq(novelSchema.inLibrary, true),
inArray(novelSchema.id, novelIds),
excludeLocalNovels ? eq(novelSchema.isLocal, false) : undefined,
- onlyUpdateOngoingNovels ? eq(novelSchema.status, 'Ongoing') : undefined,
),
)
.all();
diff --git a/src/database/queries/__tests__/LibraryQueries.test.ts b/src/database/queries/__tests__/LibraryQueries.test.ts
index 91c75274..69f3006f 100644
--- a/src/database/queries/__tests__/LibraryQueries.test.ts
+++ b/src/database/queries/__tests__/LibraryQueries.test.ts
@@ -158,7 +158,7 @@ describe('LibraryQueries', () => {
await insertTestNovelCategory(testDb, novelId1, categoryId1);
await insertTestNovelCategory(testDb, novelId2, categoryId1);
- const novels = await getLibraryWithCategory(categoryId1, undefined, true);
+ const novels = await getLibraryWithCategory(categoryId1, true);
expect(novels).toHaveLength(1);
expect(novels[0].name).toBe('Novel 2');
});
@@ -180,11 +180,7 @@ describe('LibraryQueries', () => {
await insertTestNovelCategory(testDb, novelId1, categoryId1);
await insertTestNovelCategory(testDb, novelId2, categoryId1);
- const novels = await getLibraryWithCategory(
- categoryId1,
- undefined,
- false,
- );
+ const novels = await getLibraryWithCategory(categoryId1, false);
expect(novels).toHaveLength(2);
expect(novels.map(n => n.name).sort()).toEqual(['Novel 1', 'Novel 2']);
});
@@ -309,7 +305,7 @@ describe('LibraryQueries', () => {
);
await insertTestNovelCategory(testDb, otherNovelId, otherCategoryId);
- const novels = await getLibraryNovelsForGlobalUpdate(false, {
+ const novels = await getLibraryNovelsForGlobalUpdate({
includedCategoryIds: [includedCategoryId],
excludedCategoryIds: [],
});
@@ -348,7 +344,7 @@ describe('LibraryQueries', () => {
includedCategoryId,
);
- const novels = await getLibraryNovelsForGlobalUpdate(false, {
+ const novels = await getLibraryNovelsForGlobalUpdate({
includedCategoryIds: [includedCategoryId],
excludedCategoryIds: [excludedCategoryId],
});
@@ -356,7 +352,7 @@ describe('LibraryQueries', () => {
expect(novels.map(novel => novel.name)).toEqual(['Included Novel']);
});
- it('applies the ongoing and local novel filters', async () => {
+ it('excludes local novels without filtering by status', async () => {
await insertTestNovel(testDb, {
inLibrary: true,
isLocal: false,
@@ -376,12 +372,64 @@ describe('LibraryQueries', () => {
status: 'Ongoing',
});
- const novels = await getLibraryNovelsForGlobalUpdate(true, {
+ const novels = await getLibraryNovelsForGlobalUpdate({
includedCategoryIds: [],
excludedCategoryIds: [],
});
- expect(novels.map(novel => novel.name)).toEqual(['Ongoing Novel']);
+ expect(novels.map(novel => novel.name)).toEqual([
+ 'Ongoing Novel',
+ 'Completed Novel',
+ ]);
+ });
+
+ it('applies all smart update skip filters', async () => {
+ await insertTestNovel(testDb, {
+ chaptersUnread: 0,
+ inLibrary: true,
+ isLocal: false,
+ lastReadAt: '2026-07-25',
+ name: 'Eligible Novel',
+ status: 'Ongoing',
+ });
+ await insertTestNovel(testDb, {
+ chaptersUnread: 2,
+ inLibrary: true,
+ isLocal: false,
+ lastReadAt: '2026-07-25',
+ name: 'Unread Chapters',
+ status: 'Ongoing',
+ });
+ await insertTestNovel(testDb, {
+ chaptersUnread: 0,
+ inLibrary: true,
+ isLocal: false,
+ lastReadAt: null,
+ name: 'Unstarted Novel',
+ status: 'Ongoing',
+ });
+ await insertTestNovel(testDb, {
+ chaptersUnread: 0,
+ inLibrary: true,
+ isLocal: false,
+ lastReadAt: '2026-07-25',
+ name: 'Completed Novel',
+ status: 'Completed',
+ });
+
+ const novels = await getLibraryNovelsForGlobalUpdate(
+ {
+ includedCategoryIds: [],
+ excludedCategoryIds: [],
+ },
+ {
+ skipCompleted: true,
+ skipUnstarted: true,
+ skipWithUnread: true,
+ },
+ );
+
+ expect(novels.map(novel => novel.name)).toEqual(['Eligible Novel']);
});
});
@@ -477,50 +525,6 @@ describe('LibraryQueries', () => {
expect(novels).toHaveLength(0);
});
- it('should filter by onlyUpdateOngoingNovels = true', async () => {
- const novelId1 = await insertTestNovel(testDb, {
- inLibrary: true,
- name: 'Novel 1',
- status: 'Ongoing',
- });
- const novelId2 = await insertTestNovel(testDb, {
- inLibrary: true,
- name: 'Novel 2',
- status: 'Completed',
- });
- const categoryId1 = await insertTestCategory(testDb, {
- name: 'Category A',
- });
- await insertTestNovelCategory(testDb, novelId1, categoryId1);
- await insertTestNovelCategory(testDb, novelId2, categoryId1);
-
- const novels = await getLibraryWithCategory(categoryId1, true);
- expect(novels).toHaveLength(1);
- expect(novels[0].name).toBe('Novel 1');
- });
-
- it('should not filter by onlyUpdateOngoingNovels = false', async () => {
- const novelId1 = await insertTestNovel(testDb, {
- inLibrary: true,
- name: 'Novel 1',
- status: 'Ongoing',
- });
- const novelId2 = await insertTestNovel(testDb, {
- inLibrary: true,
- name: 'Novel 2',
- status: 'Completed',
- });
- const categoryId1 = await insertTestCategory(testDb, {
- name: 'Category A',
- });
- await insertTestNovelCategory(testDb, novelId1, categoryId1);
- await insertTestNovelCategory(testDb, novelId2, categoryId1);
-
- const novels = await getLibraryWithCategory(categoryId1, false);
- expect(novels).toHaveLength(2);
- expect(novels.map(n => n.name).sort()).toEqual(['Novel 1', 'Novel 2']);
- });
-
it('should filter by excludeLocalNovels = true', async () => {
const novelId1 = await insertTestNovel(testDb, {
inLibrary: true,
@@ -538,7 +542,7 @@ describe('LibraryQueries', () => {
await insertTestNovelCategory(testDb, novelId1, categoryId1);
await insertTestNovelCategory(testDb, novelId2, categoryId1);
- const novels = await getLibraryWithCategory(categoryId1, undefined, true);
+ const novels = await getLibraryWithCategory(categoryId1, true);
expect(novels).toHaveLength(1);
expect(novels[0].name).toBe('Novel 2');
});
@@ -560,11 +564,7 @@ describe('LibraryQueries', () => {
await insertTestNovelCategory(testDb, novelId1, categoryId1);
await insertTestNovelCategory(testDb, novelId2, categoryId1);
- const novels = await getLibraryWithCategory(
- categoryId1,
- undefined,
- false,
- );
+ const novels = await getLibraryWithCategory(categoryId1, false);
expect(novels).toHaveLength(2);
expect(novels.map(n => n.name).sort()).toEqual(['Novel 1', 'Novel 2']);
});
diff --git a/src/hooks/persisted/__mocks__/useSettings.ts b/src/hooks/persisted/__mocks__/useSettings.ts
index a491cb34..8588187a 100644
--- a/src/hooks/persisted/__mocks__/useSettings.ts
+++ b/src/hooks/persisted/__mocks__/useSettings.ts
@@ -16,7 +16,9 @@ export const initialAppSettings = {
relativeTimestamps: true,
downloadedOnlyMode: false,
useLibraryFAB: false,
- onlyUpdateOngoingNovels: false,
+ smartUpdateSkipCompleted: false,
+ smartUpdateSkipUnstarted: false,
+ smartUpdateSkipWithUnread: false,
updateLibraryOnLaunch: false,
downloadNewChapters: false,
refreshNovelMetadata: false,
diff --git a/src/hooks/persisted/useSettings.ts b/src/hooks/persisted/useSettings.ts
index f88a0308..abeed85e 100644
--- a/src/hooks/persisted/useSettings.ts
+++ b/src/hooks/persisted/useSettings.ts
@@ -66,7 +66,9 @@ export interface AppSettings {
* Update settings
*/
- onlyUpdateOngoingNovels: boolean;
+ smartUpdateSkipCompleted: boolean;
+ smartUpdateSkipUnstarted: boolean;
+ smartUpdateSkipWithUnread: boolean;
updateLibraryOnLaunch: boolean;
downloadNewChapters: boolean;
refreshNovelMetadata: boolean;
@@ -123,6 +125,12 @@ export interface GlobalUpdateCategoryFilters {
includedCategoryIds: number[];
}
+export interface SmartUpdateFilters {
+ skipCompleted: boolean;
+ skipUnstarted: boolean;
+ skipWithUnread: boolean;
+}
+
const normalizeCategoryIds = (categoryIds?: number[]): number[] =>
Array.from(
new Set(
@@ -238,7 +246,9 @@ const initialAppSettings: AppSettings = {
* Update settings
*/
- onlyUpdateOngoingNovels: false,
+ smartUpdateSkipCompleted: false,
+ smartUpdateSkipUnstarted: false,
+ smartUpdateSkipWithUnread: false,
updateLibraryOnLaunch: false,
downloadNewChapters: false,
refreshNovelMetadata: false,
diff --git a/src/i18n/languages/af_ZA/strings.json b/src/i18n/languages/af_ZA/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/af_ZA/strings.json
+++ b/src/i18n/languages/af_ZA/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/ar_SA/strings.json b/src/i18n/languages/ar_SA/strings.json
index 120d3e26..e070a1bd 100644
--- a/src/i18n/languages/ar_SA/strings.json
+++ b/src/i18n/languages/ar_SA/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "ترتيب الفرز",
"updateLibrary": "تحديث المكتبة عند التشغيل",
"updateLibraryDesc": "لا ينصح به للأجهزة الضعيفة",
- "updateOngoing": "تحديث الروايات المستمرة فقط",
"updateTime": "إظهار وقت آخر تحديث",
"useFAB": "استخدام الزر العائم (FAB) في المكتبة"
},
diff --git a/src/i18n/languages/as_IN/strings.json b/src/i18n/languages/as_IN/strings.json
index 03216e19..635a2cf5 100644
--- a/src/i18n/languages/as_IN/strings.json
+++ b/src/i18n/languages/as_IN/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/ca_ES/strings.json b/src/i18n/languages/ca_ES/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/ca_ES/strings.json
+++ b/src/i18n/languages/ca_ES/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/cs_CZ/strings.json b/src/i18n/languages/cs_CZ/strings.json
index 147e2f1f..e2fb978c 100644
--- a/src/i18n/languages/cs_CZ/strings.json
+++ b/src/i18n/languages/cs_CZ/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/da_DK/strings.json b/src/i18n/languages/da_DK/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/da_DK/strings.json
+++ b/src/i18n/languages/da_DK/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/de_DE/strings.json b/src/i18n/languages/de_DE/strings.json
index 6cd9b263..a5ca7d4e 100644
--- a/src/i18n/languages/de_DE/strings.json
+++ b/src/i18n/languages/de_DE/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Bibliothek beim Starten aktualisieren",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Nur laufende Bücher aktualisieren",
"updateTime": "Zeige den Zeitpunkt der letzten Aktualisierung",
"useFAB": "FAB in der Bibliothek verwenden"
},
diff --git a/src/i18n/languages/el_GR/strings.json b/src/i18n/languages/el_GR/strings.json
index 78524afc..0e81c344 100644
--- a/src/i18n/languages/el_GR/strings.json
+++ b/src/i18n/languages/el_GR/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/en/strings.json b/src/i18n/languages/en/strings.json
index d250f531..703f2a0b 100644
--- a/src/i18n/languages/en/strings.json
+++ b/src/i18n/languages/en/strings.json
@@ -332,10 +332,13 @@
"novelSort": "Novel Sort",
"refreshMetadata": "Automatically refresh metadata",
"refreshMetadataDescription": "Check for new cover and details when updating library",
+ "smartUpdate": "Smart update",
+ "smartUpdateSkipCompleted": "Skip entries with \"Completed\" status",
+ "smartUpdateSkipUnstarted": "Skip unstarted entries",
+ "smartUpdateSkipWithUnread": "Skip entries with unread chapter(s)",
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library",
"timeTracking": "Time Tracking",
diff --git a/src/i18n/languages/es_ES/strings.json b/src/i18n/languages/es_ES/strings.json
index 4229ca3f..5b9b9a1f 100644
--- a/src/i18n/languages/es_ES/strings.json
+++ b/src/i18n/languages/es_ES/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Actualizar biblioteca al iniciar",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Solo actualizar novelas en curso",
"updateTime": "Mostrar la última actualización",
"useFAB": "Usar FAB en la biblioteca"
},
diff --git a/src/i18n/languages/fi_FI/strings.json b/src/i18n/languages/fi_FI/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/fi_FI/strings.json
+++ b/src/i18n/languages/fi_FI/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/fr_FR/strings.json b/src/i18n/languages/fr_FR/strings.json
index e36d6a1f..44f136d9 100644
--- a/src/i18n/languages/fr_FR/strings.json
+++ b/src/i18n/languages/fr_FR/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Ordre de tri",
"updateLibrary": "Mettre à jour la bibliothèque au lancement",
"updateLibraryDesc": "Non recommandé pour les appareils ????",
- "updateOngoing": "Ne mettre à jour que les romans en cours",
"updateTime": "Afficher la date de dernière mise à jour",
"useFAB": "Utiliser FAB dans la bibliothèque"
},
diff --git a/src/i18n/languages/he_IL/strings.json b/src/i18n/languages/he_IL/strings.json
index 7f255a7a..4b170333 100644
--- a/src/i18n/languages/he_IL/strings.json
+++ b/src/i18n/languages/he_IL/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/hi_IN/strings.json b/src/i18n/languages/hi_IN/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/hi_IN/strings.json
+++ b/src/i18n/languages/hi_IN/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/hu_HU/strings.json b/src/i18n/languages/hu_HU/strings.json
index 1cf41b81..b491518a 100644
--- a/src/i18n/languages/hu_HU/strings.json
+++ b/src/i18n/languages/hu_HU/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Könyvtár frissítése indításkor",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Utolsó frissítés idejének mutatása",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/id_ID/strings.json b/src/i18n/languages/id_ID/strings.json
index 27c96657..be1d5381 100644
--- a/src/i18n/languages/id_ID/strings.json
+++ b/src/i18n/languages/id_ID/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Perbarui perpustakaan saat diluncurkan\n",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Hanya memperbarui novel yang sedang berlangsung\n",
"updateTime": "Perlihatkan waktu pembaruan terakhir\n",
"useFAB": "Gunakan FAB di perpustakaan\n"
},
diff --git a/src/i18n/languages/it_IT/strings.json b/src/i18n/languages/it_IT/strings.json
index 605b044a..78ae43ce 100644
--- a/src/i18n/languages/it_IT/strings.json
+++ b/src/i18n/languages/it_IT/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Aggiorna la libreria all'avvio",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Aggiorna solo le novel in corso",
"updateTime": "Mostra l'orario dell'ultimo aggiornamento",
"useFAB": "Usa FAB nella libreria"
},
diff --git a/src/i18n/languages/ja_JP/strings.json b/src/i18n/languages/ja_JP/strings.json
index ea618eea..5d297ba7 100644
--- a/src/i18n/languages/ja_JP/strings.json
+++ b/src/i18n/languages/ja_JP/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/ko_KR/strings.json b/src/i18n/languages/ko_KR/strings.json
index 447217f1..26bffdac 100644
--- a/src/i18n/languages/ko_KR/strings.json
+++ b/src/i18n/languages/ko_KR/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/nl_NL/strings.json b/src/i18n/languages/nl_NL/strings.json
index d422423e..1a6102b6 100644
--- a/src/i18n/languages/nl_NL/strings.json
+++ b/src/i18n/languages/nl_NL/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/no_NO/strings.json b/src/i18n/languages/no_NO/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/no_NO/strings.json
+++ b/src/i18n/languages/no_NO/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/or_IN/strings.json b/src/i18n/languages/or_IN/strings.json
index 47e76912..920c9e34 100644
--- a/src/i18n/languages/or_IN/strings.json
+++ b/src/i18n/languages/or_IN/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/pl_PL/strings.json b/src/i18n/languages/pl_PL/strings.json
index 46b38b8b..d6ebee02 100644
--- a/src/i18n/languages/pl_PL/strings.json
+++ b/src/i18n/languages/pl_PL/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/pt_BR/strings.json b/src/i18n/languages/pt_BR/strings.json
index b5a68c54..5ee503ba 100644
--- a/src/i18n/languages/pt_BR/strings.json
+++ b/src/i18n/languages/pt_BR/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Ordenar",
"updateLibrary": "Atualizar biblioteca ao iniciar",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Atualizar somente novels em andamento",
"updateTime": "Mostrar horário da última atualização",
"useFAB": "Usar Botões flutuantes na Biblioteca"
},
diff --git a/src/i18n/languages/pt_PT/strings.json b/src/i18n/languages/pt_PT/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/pt_PT/strings.json
+++ b/src/i18n/languages/pt_PT/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/ro_RO/strings.json b/src/i18n/languages/ro_RO/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/ro_RO/strings.json
+++ b/src/i18n/languages/ro_RO/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/ru_RU/strings.json b/src/i18n/languages/ru_RU/strings.json
index bd4cfa87..276e027a 100644
--- a/src/i18n/languages/ru_RU/strings.json
+++ b/src/i18n/languages/ru_RU/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Порядок сортировки",
"updateLibrary": "Обновлять библиотеку при запуске",
"updateLibraryDesc": "Не рекомендуется для слабых устройств",
- "updateOngoing": "Обновлять только онгоинги",
"updateTime": "Показать время последнего обновления",
"useFAB": "Использовать FAB в библиотеке"
},
diff --git a/src/i18n/languages/sq_AL/strings.json b/src/i18n/languages/sq_AL/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/sq_AL/strings.json
+++ b/src/i18n/languages/sq_AL/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/sr_SP/strings.json b/src/i18n/languages/sr_SP/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/sr_SP/strings.json
+++ b/src/i18n/languages/sr_SP/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/sv_SE/strings.json b/src/i18n/languages/sv_SE/strings.json
index 1896da6b..4402c28d 100644
--- a/src/i18n/languages/sv_SE/strings.json
+++ b/src/i18n/languages/sv_SE/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/languages/tr_TR/strings.json b/src/i18n/languages/tr_TR/strings.json
index 95d3a416..69beefd1 100644
--- a/src/i18n/languages/tr_TR/strings.json
+++ b/src/i18n/languages/tr_TR/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sıralama Düzeni",
"updateLibrary": "Açılışta kitaplığı güncelle",
"updateLibraryDesc": "Eski cihazlarda önerilmez",
- "updateOngoing": "Yalnızca devam eden novel'leri güncelle",
"updateTime": "Son güncelleme süresini göster",
"useFAB": "Kitaplıkta FAB kullan"
},
diff --git a/src/i18n/languages/uk_UA/strings.json b/src/i18n/languages/uk_UA/strings.json
index 2166c4c1..8fe060eb 100644
--- a/src/i18n/languages/uk_UA/strings.json
+++ b/src/i18n/languages/uk_UA/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Порядок сортування",
"updateLibrary": "Оновлювати бібліотеку при запуску",
"updateLibraryDesc": "Не рекомендується для слабких пристроїв",
- "updateOngoing": "Оновлювати лише новели, що оновлюються",
"updateTime": "Показувати час останнього оновлення",
"useFAB": "Використовувати FAB в бібліотеці"
},
diff --git a/src/i18n/languages/vi_VN/strings.json b/src/i18n/languages/vi_VN/strings.json
index a2e2e075..946312ad 100644
--- a/src/i18n/languages/vi_VN/strings.json
+++ b/src/i18n/languages/vi_VN/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Thứ tự sắp xếp",
"updateLibrary": "Cập nhật thư viện khi khởi động",
"updateLibraryDesc": "Không khuyến khích cho thiết bị yếu",
- "updateOngoing": "Chỉ cập nhật truyện đang thực hiện",
"updateTime": "Hiện lần cập nhật trước",
"useFAB": "Sử dụng FAB trong thư viện"
},
diff --git a/src/i18n/languages/zh_CN/strings.json b/src/i18n/languages/zh_CN/strings.json
index 467c9a82..a00af4a8 100644
--- a/src/i18n/languages/zh_CN/strings.json
+++ b/src/i18n/languages/zh_CN/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "排序",
"updateLibrary": "打开应用时更新书库",
"updateLibraryDesc": "不推荐在低端设备上启用",
- "updateOngoing": "仅更新连载小说",
"updateTime": "显示上次更新时间",
"useFAB": "在书库中使用浮动操作按钮"
},
diff --git a/src/i18n/languages/zh_TW/strings.json b/src/i18n/languages/zh_TW/strings.json
index 1c43df47..150ef16f 100644
--- a/src/i18n/languages/zh_TW/strings.json
+++ b/src/i18n/languages/zh_TW/strings.json
@@ -298,7 +298,6 @@
"sortOrder": "Sort Order",
"updateLibrary": "Update library on launch",
"updateLibraryDesc": "Not recommended for low devices",
- "updateOngoing": "Only update ongoing novels",
"updateTime": "Show last update time",
"useFAB": "Use FAB in Library"
},
diff --git a/src/i18n/types/index.ts b/src/i18n/types/index.ts
index d3408751..5fbad791 100644
--- a/src/i18n/types/index.ts
+++ b/src/i18n/types/index.ts
@@ -295,10 +295,13 @@ export interface StringMap {
'generalSettingsScreen.novelSort': 'string';
'generalSettingsScreen.refreshMetadata': 'string';
'generalSettingsScreen.refreshMetadataDescription': 'string';
+ 'generalSettingsScreen.smartUpdate': 'string';
+ 'generalSettingsScreen.smartUpdateSkipCompleted': 'string';
+ 'generalSettingsScreen.smartUpdateSkipUnstarted': 'string';
+ 'generalSettingsScreen.smartUpdateSkipWithUnread': 'string';
'generalSettingsScreen.sortOrder': 'string';
'generalSettingsScreen.updateLibrary': 'string';
'generalSettingsScreen.updateLibraryDesc': 'string';
- 'generalSettingsScreen.updateOngoing': 'string';
'generalSettingsScreen.updateTime': 'string';
'generalSettingsScreen.useFAB': 'string';
'generalSettingsScreen.timeTracking': 'string';
diff --git a/src/screens/settings/SettingsLibraryScreen/SettingsLibraryScreen.tsx b/src/screens/settings/SettingsLibraryScreen/SettingsLibraryScreen.tsx
index 9655db66..95a2920c 100644
--- a/src/screens/settings/SettingsLibraryScreen/SettingsLibraryScreen.tsx
+++ b/src/screens/settings/SettingsLibraryScreen/SettingsLibraryScreen.tsx
@@ -24,6 +24,7 @@ import DefaultChapterSortModal from '../components/DefaultChapterSortModal';
import SettingSwitch from '../components/SettingSwitch';
import DefaultCategoryDialog from './DefaultCategoryDialog';
import GlobalUpdateCategoriesDialog from './GlobalUpdateCategoriesDialog';
+import SmartUpdateDialog from './SmartUpdateDialog';
import DisplayModeModal from './modals/DisplayModeModal';
import GridSizeModal from './modals/GridSizeModal';
import NovelBadgesModal from './modals/NovelBadgesModal';
@@ -60,9 +61,11 @@ const SettingsLibraryScreen = ({ navigation }: LibrarySettingsScreenProps) => {
const {
defaultChapterSort,
downloadNewChapters,
- onlyUpdateOngoingNovels,
refreshNovelMetadata,
setAppSettings,
+ smartUpdateSkipCompleted = false,
+ smartUpdateSkipUnstarted = false,
+ smartUpdateSkipWithUnread = false,
updateLibraryOnLaunch,
useLibraryFAB,
} = useAppSettings();
@@ -75,6 +78,7 @@ const SettingsLibraryScreen = ({ navigation }: LibrarySettingsScreenProps) => {
const gridSizeModal = useBoolean();
const novelBadgesModal = useBoolean();
const novelSortModal = useBoolean();
+ const smartUpdateDialog = useBoolean();
const defaultChapterSortModal = useBoolean();
const appDefaultCategory = categories.find(category => category.id === 1);
const selectedDefaultCategory = categories.find(
@@ -92,6 +96,11 @@ const SettingsLibraryScreen = ({ navigation }: LibrarySettingsScreenProps) => {
const [draftExcludedCategoryIds, setDraftExcludedCategoryIds] = useState<
number[]
>([]);
+ const [draftSmartUpdateFilters, setDraftSmartUpdateFilters] = useState({
+ skipCompleted: false,
+ skipUnstarted: false,
+ skipWithUnread: false,
+ });
const setDefaultCategory = (categoryId: number) => {
setLibrarySettings({
@@ -122,6 +131,24 @@ const SettingsLibraryScreen = ({ navigation }: LibrarySettingsScreenProps) => {
globalUpdateCategoriesDialog.setFalse();
};
+ const showSmartUpdateDialog = () => {
+ setDraftSmartUpdateFilters({
+ skipCompleted: smartUpdateSkipCompleted,
+ skipUnstarted: smartUpdateSkipUnstarted,
+ skipWithUnread: smartUpdateSkipWithUnread,
+ });
+ smartUpdateDialog.setTrue();
+ };
+
+ const saveSmartUpdateFilters = () => {
+ setAppSettings({
+ smartUpdateSkipCompleted: draftSmartUpdateFilters.skipCompleted,
+ smartUpdateSkipUnstarted: draftSmartUpdateFilters.skipUnstarted,
+ smartUpdateSkipWithUnread: draftSmartUpdateFilters.skipWithUnread,
+ });
+ smartUpdateDialog.setFalse();
+ };
+
const getCategoryFilterDescription = (
categoryIds: number[],
emptyLabel: string,
@@ -144,6 +171,19 @@ const SettingsLibraryScreen = ({ navigation }: LibrarySettingsScreenProps) => {
globalUpdateExcludeCategoryIds,
getString('common.none'),
);
+ const smartUpdateDescription = [
+ smartUpdateSkipWithUnread
+ ? getString('generalSettingsScreen.smartUpdateSkipWithUnread')
+ : null,
+ smartUpdateSkipUnstarted
+ ? getString('generalSettingsScreen.smartUpdateSkipUnstarted')
+ : null,
+ smartUpdateSkipCompleted
+ ? getString('generalSettingsScreen.smartUpdateSkipCompleted')
+ : null,
+ ]
+ .filter(Boolean)
+ .join(', ');
const sortOrderParts = sortOrder.split(' ');
const sortOrderLabel =
@@ -247,16 +287,6 @@ const SettingsLibraryScreen = ({ navigation }: LibrarySettingsScreenProps) => {
{getString('generalSettingsScreen.globalUpdate')}
-
- setAppSettings({
- onlyUpdateOngoingNovels: !onlyUpdateOngoingNovels,
- })
- }
- theme={theme}
- />
{
onPress={showGlobalUpdateCategoriesDialog}
theme={theme}
/>
+
{
onChange={updateDraftCategoryFilters}
onSave={saveGlobalUpdateCategories}
/>
+
);
diff --git a/src/screens/settings/SettingsLibraryScreen/SmartUpdateDialog.tsx b/src/screens/settings/SettingsLibraryScreen/SmartUpdateDialog.tsx
new file mode 100644
index 00000000..3e3c61a9
--- /dev/null
+++ b/src/screens/settings/SettingsLibraryScreen/SmartUpdateDialog.tsx
@@ -0,0 +1,73 @@
+import { Checkbox, Dialog } from '@components';
+import type { SmartUpdateFilters } from '@hooks/persisted/useSettings';
+import { useTheme } from '@hooks/persisted';
+import { getString } from '@i18n/translations';
+
+interface SmartUpdateDialogProps {
+ filters: SmartUpdateFilters;
+ visible: boolean;
+ onCancel: () => void;
+ onChange: (filters: SmartUpdateFilters) => void;
+ onSave: () => void;
+}
+
+const SmartUpdateDialog = ({
+ filters,
+ visible,
+ onCancel,
+ onChange,
+ onSave,
+}: SmartUpdateDialogProps) => {
+ const theme = useTheme();
+
+ return (
+
+
+ {getString('generalSettingsScreen.smartUpdate')}
+
+
+
+ onChange({
+ ...filters,
+ skipWithUnread: !filters.skipWithUnread,
+ })
+ }
+ theme={theme}
+ />
+
+ onChange({
+ ...filters,
+ skipUnstarted: !filters.skipUnstarted,
+ })
+ }
+ theme={theme}
+ />
+
+ onChange({
+ ...filters,
+ skipCompleted: !filters.skipCompleted,
+ })
+ }
+ theme={theme}
+ />
+
+
+
+ {getString('common.cancel')}
+
+ {getString('common.ok')}
+
+
+ );
+};
+
+export default SmartUpdateDialog;
diff --git a/src/services/updates/__tests__/index.test.ts b/src/services/updates/__tests__/index.test.ts
index 0e9bf7ab..2286bb73 100644
--- a/src/services/updates/__tests__/index.test.ts
+++ b/src/services/updates/__tests__/index.test.ts
@@ -3,6 +3,7 @@ import {
getLibraryWithCategory,
} from '@database/queries/LibraryQueries';
import type { BackgroundTaskMetadata } from '@services/backgroundTasks/contracts';
+import { getMMKVObject } from '@utils/mmkv/mmkv';
import { updateNovel } from '../LibraryUpdateQueries';
import { updateLibrary } from '../index';
@@ -37,6 +38,7 @@ jest.mock('@hooks/persisted/useSettings', () => ({
const mockedGetLibraryNovels = jest.mocked(getLibraryNovelsForGlobalUpdate);
const mockedGetLibraryWithCategory = jest.mocked(getLibraryWithCategory);
const mockedUpdateNovel = jest.mocked(updateNovel);
+const mockedGetMMKVObject = jest.mocked(getMMKVObject);
type LibraryNovel = Awaited<
ReturnType
@@ -55,6 +57,7 @@ const flushPromises = () => new Promise(resolve => setImmediate(resolve));
describe('updateLibrary', () => {
beforeEach(() => {
jest.clearAllMocks();
+ mockedGetMMKVObject.mockReturnValue({});
});
it('updates at most three different sources while keeping each source sequential', async () => {
@@ -126,11 +129,30 @@ describe('updateLibrary', () => {
await updateLibrary({ categoryId: 4 }, jest.fn(), jest.fn());
- expect(mockedGetLibraryWithCategory).toHaveBeenCalledWith(
- 4,
- undefined,
- true,
- );
+ expect(mockedGetLibraryWithCategory).toHaveBeenCalledWith(4, true);
expect(mockedGetLibraryNovels).not.toHaveBeenCalled();
});
+
+ it('passes smart update preferences to the global update query', async () => {
+ mockedGetMMKVObject.mockReturnValue({
+ smartUpdateSkipCompleted: true,
+ smartUpdateSkipUnstarted: true,
+ smartUpdateSkipWithUnread: true,
+ });
+ mockedGetLibraryNovels.mockResolvedValue([]);
+
+ await updateLibrary({}, jest.fn(), jest.fn());
+
+ expect(mockedGetLibraryNovels).toHaveBeenCalledWith(
+ {
+ excludedCategoryIds: [],
+ includedCategoryIds: [],
+ },
+ {
+ skipCompleted: true,
+ skipUnstarted: true,
+ skipWithUnread: true,
+ },
+ );
+ });
});
diff --git a/src/services/updates/index.ts b/src/services/updates/index.ts
index f5af65da..37a338fb 100644
--- a/src/services/updates/index.ts
+++ b/src/services/updates/index.ts
@@ -52,8 +52,18 @@ const updateLibrary = async (
progress: 0,
}));
- const { downloadNewChapters, refreshNovelMetadata, onlyUpdateOngoingNovels } =
- getMMKVObject(APP_SETTINGS) || {};
+ const {
+ downloadNewChapters,
+ refreshNovelMetadata,
+ smartUpdateSkipCompleted,
+ smartUpdateSkipUnstarted,
+ smartUpdateSkipWithUnread,
+ } = getMMKVObject(APP_SETTINGS) || {};
+ const smartUpdateFilters = {
+ skipCompleted: Boolean(smartUpdateSkipCompleted),
+ skipUnstarted: Boolean(smartUpdateSkipUnstarted),
+ skipWithUnread: Boolean(smartUpdateSkipWithUnread),
+ };
const options: UpdateNovelOptions = {
downloadNewChapters: downloadNewChapters || false,
refreshNovelMetadata: refreshNovelMetadata || false,
@@ -62,15 +72,11 @@ const updateLibrary = async (
let libraryNovels: DBNovelInfo[] = [];
if (categoryId) {
- libraryNovels = await getLibraryWithCategory(
- categoryId,
- onlyUpdateOngoingNovels,
- true,
- );
+ libraryNovels = await getLibraryWithCategory(categoryId, true);
} else {
libraryNovels = await getLibraryNovelsForGlobalUpdate(
- Boolean(onlyUpdateOngoingNovels),
getGlobalUpdateCategoryFilters(),
+ smartUpdateFilters,
);
}