feat(eink): add an option to save last book cover on Android, closes #1414 (#2361)

This commit is contained in:
Huang Xin
2025-10-29 23:15:08 +08:00
committed by GitHub
parent 9c5bcb13e2
commit 82922c5053
32 changed files with 215 additions and 28 deletions
@@ -660,5 +660,7 @@
"Outline": "مخطط",
"Save Current Color": "حفظ اللون الحالي",
"Quick Colors": "ألوان سريعة",
"Highlighter": "محدد النص"
"Highlighter": "محدد النص",
"Save Book Cover": "حفظ غلاف الكتاب",
"Auto-save last book cover": "حفظ غلاف الكتاب الأخير تلقائيًا"
}
@@ -644,5 +644,7 @@
"Outline": "বাহিরের রেখা",
"Save Current Color": "বর্তমান রঙ সংরক্ষণ করুন",
"Quick Colors": "দ্রুত রংসমূহ",
"Highlighter": "হাইলাইটার"
"Highlighter": "হাইলাইটার",
"Save Book Cover": "বইয়ের মলাট সংরক্ষণ করুন",
"Auto-save last book cover": "শেষ বইয়ের মলাট স্বয়ংক্রিয়ভাবে সংরক্ষণ করুন"
}
@@ -640,5 +640,7 @@
"Outline": "མཐའ་ཐིག",
"Save Current Color": "མིག་སྔའི་ཚོན་གཏན་འཁེལ་བྱེད།",
"Quick Colors": "མྱུར་ཚོན།",
"Highlighter": "མདོག་ཚད་ཀྱི་བསྡུར་བ།"
"Highlighter": "མདོག་ཚད་ཀྱི་བསྡུར་བ།",
"Save Book Cover": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།",
"Auto-save last book cover": "དེབ་གནས་བཅས་པའི་སྤྱོད་བྱས་མ་ཐུབ།"
}
@@ -644,5 +644,7 @@
"Outline": "Umriss",
"Save Current Color": "Aktuelle Farbe speichern",
"Quick Colors": "Schnellfarben",
"Highlighter": "Textmarker"
"Highlighter": "Textmarker",
"Save Book Cover": "Buchcover speichern",
"Auto-save last book cover": "Letztes Buchcover automatisch speichern"
}
@@ -644,5 +644,7 @@
"Outline": "Περίγραμμα",
"Save Current Color": "Αποθήκευση τρέχουσας χρώματος",
"Quick Colors": "Γρήγορα χρώματα",
"Highlighter": "Υπογραμμιστής"
"Highlighter": "Υπογραμμιστής",
"Save Book Cover": "Αποθήκευση εξωφύλλου βιβλίου",
"Auto-save last book cover": "Αυτόματη αποθήκευση τελευταίου εξωφύλλου βιβλίου"
}
@@ -648,5 +648,7 @@
"Outline": "Contorno",
"Save Current Color": "Guardar Color Actual",
"Quick Colors": "Colores Rápidos",
"Highlighter": "Resaltador"
"Highlighter": "Resaltador",
"Save Book Cover": "Guardar Portada del Libro",
"Auto-save last book cover": "Guardar automáticamente la última portada del libro"
}
@@ -644,5 +644,7 @@
"Outline": "خط‌کشی",
"Save Current Color": "ذخیره رنگ فعلی",
"Quick Colors": "رنگ‌های سریع",
"Highlighter": "ماژیک هایلایت"
"Highlighter": "ماژیک هایلایت",
"Save Book Cover": "ذخیره جلد کتاب",
"Auto-save last book cover": "ذخیره خودکار آخرین جلد کتاب"
}
@@ -648,5 +648,7 @@
"Outline": "Contour",
"Save Current Color": "Enregistrer la Couleur Actuelle",
"Quick Colors": "Couleurs Rapides",
"Highlighter": "Surligneur"
"Highlighter": "Surligneur",
"Save Book Cover": "Enregistrer la Couverture du Livre",
"Auto-save last book cover": "Enregistrer automatiquement la dernière couverture du livre"
}
@@ -644,5 +644,7 @@
"Outline": "आउटलाइन",
"Save Current Color": "वर्तमान रंग सहेजें",
"Quick Colors": "त्वरित रंग",
"Highlighter": "हाइलाइटर"
"Highlighter": "हाइलाइटर",
"Save Book Cover": "बुक कवर सहेजें",
"Auto-save last book cover": "अंतिम पुस्तक कवर को स्वचालित रूप से सहेजें"
}
@@ -640,5 +640,7 @@
"Outline": "Garis Besar",
"Save Current Color": "Simpan Warna Saat Ini",
"Quick Colors": "Warna Cepat",
"Highlighter": "Penyorot"
"Highlighter": "Penyorot",
"Save Book Cover": "Simpan Sampul Buku",
"Auto-save last book cover": "Simpan otomatis sampul buku terakhir"
}
@@ -648,5 +648,7 @@
"Outline": "Contorno",
"Save Current Color": "Salva Colore Corrente",
"Quick Colors": "Colori Veloci",
"Highlighter": "Evidenziatore"
"Highlighter": "Evidenziatore",
"Save Book Cover": "Salva Copertina del Libro",
"Auto-save last book cover": "Salva automaticamente l'ultima copertina del libro"
}
@@ -640,5 +640,7 @@
"Outline": "アウトライン",
"Save Current Color": "現在の色を保存",
"Quick Colors": "クイックカラー",
"Highlighter": "蛍光ペン"
"Highlighter": "蛍光ペン",
"Save Book Cover": "本のカバーを保存",
"Auto-save last book cover": "最後の本のカバーを自動保存"
}
@@ -640,5 +640,7 @@
"Outline": "윤곽선",
"Save Current Color": "현재 색상 저장",
"Quick Colors": "빠른 색상",
"Highlighter": "형광펜"
"Highlighter": "형광펜",
"Save Book Cover": "책 표지 저장",
"Auto-save last book cover": "마지막 책 표지 자동 저장"
}
@@ -644,5 +644,7 @@
"Outline": "Omtrek",
"Save Current Color": "Huidige kleur opslaan",
"Quick Colors": "Snelle kleuren",
"Highlighter": "Marker"
"Highlighter": "Marker",
"Save Book Cover": "Opslaan boekomslag",
"Auto-save last book cover": "Automatisch opslaan laatste boekomslag"
}
@@ -652,5 +652,7 @@
"Outline": "Obrys",
"Save Current Color": "Zapisz bieżący kolor",
"Quick Colors": "Szybkie kolory",
"Highlighter": "Zakreślacz"
"Highlighter": "Zakreślacz",
"Save Book Cover": "Zapisz okładkę książki",
"Auto-save last book cover": "Automatyczne zapisywanie ostatniej okładki książki"
}
@@ -648,5 +648,7 @@
"Outline": "Contorno",
"Save Current Color": "Salvar cor atual",
"Quick Colors": "Cores rápidas",
"Highlighter": "Marcador"
"Highlighter": "Marcador",
"Save Book Cover": "Salvar Capa do Livro",
"Auto-save last book cover": "Salvar automaticamente a última capa do livro"
}
@@ -652,5 +652,7 @@
"Outline": "Контур",
"Save Current Color": "Сохранить текущий цвет",
"Quick Colors": "Быстрые цвета",
"Highlighter": "Маркер"
"Highlighter": "Маркер",
"Save Book Cover": "Сохранить обложку книги",
"Auto-save last book cover": "Автоматически сохранять последнюю обложку книги"
}
@@ -644,5 +644,7 @@
"Outline": "පරිසීමාව",
"Save Current Color": "දැන් ඇති වර්ණය සුරකින්න",
"Quick Colors": "ක්ෂණික වර්ණ",
"Highlighter": "ඉස්මතු කරන්නා"
"Highlighter": "ඉස්මතු කරන්නා",
"Save Book Cover": "පොත් ආවරණය සුරකින්න",
"Auto-save last book cover": "අවසන් පොත් ආවරණය ස්වයංක්‍රීයව සුරකින්න"
}
@@ -644,5 +644,7 @@
"Outline": "Kontur",
"Save Current Color": "Spara aktuell färg",
"Quick Colors": "Snabba färger",
"Highlighter": "Markeringspenna"
"Highlighter": "Markeringspenna",
"Save Book Cover": "Spara bokomslag",
"Auto-save last book cover": "Automatiskt spara senaste bokomslag"
}
@@ -644,5 +644,7 @@
"Outline": "வட்டம்",
"Save Current Color": "தற்போதைய நிறத்தை சேமிக்கவும்",
"Quick Colors": "விரைவு நிறங்கள்",
"Highlighter": "ஹைலைட்டர்"
"Highlighter": "ஹைலைட்டர்",
"Save Book Cover": "புத்தகக் கோப்பைச் சேமிக்கவும்",
"Auto-save last book cover": "அවසන් புத்தகக் கோப்பு காலியாக உள்ளது"
}
@@ -640,5 +640,7 @@
"Outline": "เส้นขอบ",
"Save Current Color": "บันทึกสีปัจจุบัน",
"Quick Colors": "สีด่วน",
"Highlighter": "ปากกาเน้นข้อความ"
"Highlighter": "ปากกาเน้นข้อความ",
"Save Book Cover": "บันทึกปกหนังสือ",
"Auto-save last book cover": "บันทึกปกหนังสือสุดท้ายโดยอัตโนมัติ"
}
@@ -644,5 +644,7 @@
"Outline": "Kontur",
"Save Current Color": "Mevcut Rengi Kaydet",
"Quick Colors": "Hızlı Renkler",
"Highlighter": "Vurgulayıcı"
"Highlighter": "Vurgulayıcı",
"Save Book Cover": "Kitap Kapağını Kaydet",
"Auto-save last book cover": "Son kitap kapağını otomatik olarak kaydet"
}
@@ -652,5 +652,7 @@
"Outline": "Контур",
"Save Current Color": "Зберегти поточний колір",
"Quick Colors": "Швидкі кольори",
"Highlighter": "Маркер"
"Highlighter": "Маркер",
"Save Book Cover": "Зберегти обкладинку книги",
"Auto-save last book cover": "Автоматично зберегти останню обкладинку книги"
}
@@ -640,5 +640,7 @@
"Outline": "Đường viền",
"Save Current Color": "Lưu màu hiện tại",
"Quick Colors": "Màu nhanh",
"Highlighter": "Bút đánh dấu"
"Highlighter": "Bút đánh dấu",
"Save Book Cover": "Lưu bìa sách",
"Auto-save last book cover": "Tự động lưu bìa sách cuối cùng"
}
@@ -640,5 +640,7 @@
"Outline": "轮廓",
"Save Current Color": "保存当前颜色",
"Quick Colors": "快速颜色",
"Highlighter": "荧光笔"
"Highlighter": "荧光笔",
"Save Book Cover": "保存书籍封面",
"Auto-save last book cover": "自动保存最后一本书的封面"
}
@@ -640,5 +640,7 @@
"Outline": "輪廓",
"Save Current Color": "儲存目前顏色",
"Quick Colors": "快速顏色",
"Highlighter": "螢光筆"
"Highlighter": "螢光筆",
"Save Book Cover": "保存書籍封面",
"Auto-save last book cover": "自動保存最後一本書的封面"
}
@@ -53,6 +53,9 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
);
const [isTelemetryEnabled, setIsTelemetryEnabled] = useState(settings.telemetryEnabled);
const [alwaysInForeground, setAlwaysInForeground] = useState(settings.alwaysInForeground);
const [savedBookCoverForLockScreen, setSavedBookCoverForLockScreen] = useState(
settings.savedBookCoverForLockScreen || '',
);
const iconSize = useResponsiveSize(16);
const showAboutReadest = () => {
@@ -169,6 +172,12 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
setSettingsDialogOpen(true);
};
const handleSetSavedBookCoverForLockScreen = () => {
const newValue = settings.savedBookCoverForLockScreen ? '' : 'default';
saveSysSettings(envConfig, 'savedBookCoverForLockScreen', newValue);
setSavedBookCoverForLockScreen(newValue);
};
const toggleAlwaysInForeground = async () => {
const requestAlwaysInForeground = !settings.alwaysInForeground;
@@ -299,7 +308,20 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
<hr aria-hidden='true' className='border-base-200 my-1' />
<MenuItem label={_('Advanced Settings')}>
<ul className='flex flex-col'>
<MenuItem label={_('Change Data Location')} noIcon onClick={handleSetRootDir} />
<MenuItem
label={_('Change Data Location')}
noIcon={!appService?.isAndroidApp}
onClick={handleSetRootDir}
/>
{appService?.isAndroidApp && (
<MenuItem
label={_('Save Book Cover')}
tooltip={_('Auto-save last book cover')}
description={'💾 Images/last-book-cover.png'}
toggled={!!savedBookCoverForLockScreen}
onClick={handleSetSavedBookCoverForLockScreen}
/>
)}
</ul>
</MenuItem>
</>
@@ -50,6 +50,7 @@ import { TransformContext } from '@/services/transformers/types';
import { transformContent } from '@/services/transformService';
import { lockScreenOrientation } from '@/utils/bridge';
import { useTextTranslation } from '../hooks/useTextTranslation';
import { useBookCoverAutoSave } from '../hooks/useAutoSaveBookCover';
import { manageSyntaxHighlighting } from '@/utils/highlightjs';
import { getViewInsets } from '@/utils/insets';
import { removeTabIndex } from '@/utils/a11y';
@@ -101,6 +102,7 @@ const FoliateViewer: React.FC<{
useUICSS(bookKey);
useProgressSync(bookKey);
useProgressAutoSave(bookKey);
useBookCoverAutoSave(bookKey);
const { syncState, conflictDetails, resolveWithLocal, resolveWithRemote } = useKOSync(bookKey);
useTextTranslation(bookKey, viewRef.current);
@@ -0,0 +1,43 @@
import { useCallback, useEffect } from 'react';
import { useEnv } from '@/context/EnvContext';
import { useBookDataStore } from '@/store/bookDataStore';
import { useSettingsStore } from '@/store/settingsStore';
import { throttle } from '@/utils/throttle';
import { getCoverFilename } from '@/utils/book';
export const useBookCoverAutoSave = (bookKey: string) => {
const { envConfig, appService } = useEnv();
// eslint-disable-next-line react-hooks/exhaustive-deps
const saveBookCover = useCallback(
throttle(
() => {
setTimeout(async () => {
const settings = useSettingsStore.getState().settings;
const bookData = useBookDataStore.getState().getBookData(bookKey);
const book = bookData?.book;
const savedBookHash = settings.savedBookCoverForLockScreen;
if (appService && book && savedBookHash && savedBookHash !== book?.hash) {
const coverPath = await appService.resolveFilePath(getCoverFilename(book), 'Books');
try {
await appService.copyFile(coverPath, 'last-book-cover.png', 'Images');
settings.savedBookCoverForLockScreen = book.hash;
useSettingsStore.getState().setSettings(settings);
useSettingsStore.getState().saveSettings(envConfig, settings);
} catch (error) {
console.error('Failed to auto-save book cover for lock screen:', error);
}
}
}, 5000);
},
5000,
{ emitLast: false },
),
[],
);
useEffect(() => {
saveBookCover();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};
@@ -38,6 +38,7 @@ import { getOSPlatform, isContentURI, isValidURL } from '@/utils/misc';
import { getDirPath, getFilename } from '@/utils/path';
import { NativeFile, RemoteFile } from '@/utils/file';
import { copyURIToPath } from '@/utils/bridge';
import { copyFiles } from '@/utils/files';
import { BaseAppService } from './appService';
import {
@@ -78,7 +79,8 @@ const getPathResolver = ({
const getCustomBasePrefixSync = isCustomBaseDir
? (baseDir: BaseDir) => {
return () => {
const leafDir = ['Settings', 'Data', 'Books', 'Fonts'].includes(baseDir) ? '' : baseDir;
const dataDirs = ['Settings', 'Data', 'Books', 'Fonts', 'Images'];
const leafDir = dataDirs.includes(baseDir) ? '' : baseDir;
return leafDir ? `${customRootDir}/${leafDir}` : customRootDir!;
};
}
@@ -392,6 +394,33 @@ export class NativeAppService extends BaseAppService {
});
}
await this.prepareBooksDir();
await this.runMigrations();
}
private CURRENT_MIGRATION_VERSION = 20251029;
private async runMigrations() {
try {
const settings = await this.loadSettings();
const lastMigrationVersion = settings.migrationVersion || 0;
if (lastMigrationVersion < 20251029) {
try {
await this.migrate20251029();
} catch (error) {
console.error('Error migrating to version 20251029:', error);
}
}
if (lastMigrationVersion < this.CURRENT_MIGRATION_VERSION) {
await this.saveSettings({
...settings,
migrationVersion: this.CURRENT_MIGRATION_VERSION,
});
}
} catch (error) {
console.error('Failed to run migrations:', error);
}
}
override resolvePath(fp: string, base: BaseDir): ResolvedPath {
@@ -422,4 +451,16 @@ export class NativeAppService extends BaseAppService {
});
return Array.isArray(selected) ? selected : selected ? [selected] : [];
}
async migrate20251029() {
console.log('Running migration 20251029 to update paths in Images dir...');
const rootPath = await this.resolveFilePath('..', 'Data');
const newDir = await this.fs.getPrefix('Images');
const oldDir = await join(rootPath, 'Images', 'Readest', 'Images');
await copyFiles(this, oldDir, newDir);
const dirToDelete = await join(rootPath, 'Images', 'Readest');
await this.deleteDir(dirToDelete, 'None', true);
}
}
+3
View File
@@ -56,6 +56,7 @@ export interface SystemSettings {
openLastBooks: boolean;
lastOpenBooks: string[];
autoImportBooksOnOpen: boolean;
savedBookCoverForLockScreen: string;
telemetryEnabled: boolean;
libraryViewMode: LibraryViewModeType;
librarySortBy: LibrarySortByType;
@@ -70,6 +71,8 @@ export interface SystemSettings {
lastSyncedAtConfigs: number;
lastSyncedAtNotes: number;
migrationVersion: number;
globalReadSettings: ReadSettings;
globalViewSettings: ViewSettings;
}
+24
View File
@@ -0,0 +1,24 @@
import { AppService, FileItem } from '@/types/system';
export const copyFiles = async (appService: AppService, srcDir: string, dstDir: string) => {
let filesToCopy: FileItem[] = [];
try {
filesToCopy = await appService.readDirectory(srcDir, 'None');
} catch (error) {
throw new Error(`Dir ${srcDir} failed to read.`);
}
for (let i = 0; i < filesToCopy.length; i++) {
const file = filesToCopy[i]!;
const srcPath = `${srcDir}/${file.path}`;
const destPath = `${dstDir}/${file.path}`;
await appService.copyFile(srcPath, destPath, 'None');
}
const filesCopied = await appService.readDirectory(dstDir, 'None');
for (const file of filesToCopy) {
if (!filesCopied.find((f) => f.path === file.path && f.size === file.size)) {
throw new Error(`File ${file.path} failed to copy.`);
}
}
};