diff --git a/apps/readest-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/apps/readest-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml index ebe40fb0..446eebf5 100644 --- a/apps/readest-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml +++ b/apps/readest-app/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -30,27 +30,55 @@ - + + + + + - + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -59,6 +87,7 @@ + diff --git a/apps/readest-app/src-tauri/tauri.conf.json b/apps/readest-app/src-tauri/tauri.conf.json index 941d0e0b..8ff15c37 100644 --- a/apps/readest-app/src-tauri/tauri.conf.json +++ b/apps/readest-app/src-tauri/tauri.conf.json @@ -86,11 +86,18 @@ }, { "name": "azw", - "ext": ["azw", "azw3"], + "ext": ["azw"], "description": "AZW file", "mimeType": "application/vnd.amazon.ebook", "role": "Viewer" }, + { + "name": "azw3", + "ext": ["azw3"], + "description": "AZW3 file", + "mimeType": "application/vnd.amazon.mobi8-ebook", + "role": "Viewer" + }, { "name": "fb2", "ext": ["fb2"], diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 77ea4b50..9dc9919c 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -35,7 +35,7 @@ import { useDemoBooks } from './hooks/useDemoBooks'; import { useBooksSync } from './hooks/useBooksSync'; import { useScreenWakeLock } from '@/hooks/useScreenWakeLock'; import { useOpenWithBooks } from '@/hooks/useOpenWithBooks'; -import { FILE_SELECTION_PRESETS, SelectedFile, useFileSelector } from '@/hooks/useFileSelector'; +import { SelectedFile, useFileSelector } from '@/hooks/useFileSelector'; import { lockScreenOrientation } from '@/utils/bridge'; import { tauriHandleSetAlwaysOnTop, @@ -589,7 +589,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP const handleImportBooks = async () => { setIsSelectMode(false); console.log('Importing books...'); - selectFiles({ ...FILE_SELECTION_PRESETS.books, multiple: true }).then((result) => { + selectFiles({ type: 'books', multiple: true }).then((result) => { if (result.files.length === 0 || result.error) return; importBooks(result.files); }); @@ -662,6 +662,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP { diff --git a/apps/readest-app/src/app/reader/components/settings/CustomFonts.tsx b/apps/readest-app/src/app/reader/components/settings/CustomFonts.tsx index 064afdb5..c6d5e7a0 100644 --- a/apps/readest-app/src/app/reader/components/settings/CustomFonts.tsx +++ b/apps/readest-app/src/app/reader/components/settings/CustomFonts.tsx @@ -6,7 +6,7 @@ import { useEnv } from '@/context/EnvContext'; import { useReaderStore } from '@/store/readerStore'; import { useTranslation } from '@/hooks/useTranslation'; import { useCustomFontStore } from '@/store/customFontStore'; -import { FILE_SELECTION_PRESETS, useFileSelector } from '@/hooks/useFileSelector'; +import { useFileSelector } from '@/hooks/useFileSelector'; import { mountCustomFont } from '@/styles/fonts'; import { parseFontName } from '@/utils/font'; import { getFilename } from '@/utils/path'; @@ -41,7 +41,7 @@ const CustomFonts: React.FC = ({ bookKey, onBack }) => { currentDefaultFont === 'serif' ? viewSettings.serifFont : viewSettings.sansSerifFont; const handleImportFont = () => { - selectFiles({ ...FILE_SELECTION_PRESETS.fonts, multiple: true }).then(async (result) => { + selectFiles({ type: 'fonts', multiple: true }).then(async (result) => { if (result.error || result.files.length === 0) return; if (!(await appService!.fs.exists('', 'Fonts'))) { await appService!.fs.createDir('', 'Fonts'); @@ -164,9 +164,9 @@ const CustomFonts: React.FC = ({ bookKey, onBack }) => { 'card h-12 border shadow-sm', currentFontFamily === font.name ? 'border-primary/50 bg-primary/50' - : 'border-base-200 bg-base-200 cursor-pointer', + : `border-base-200 bg-base-200 ${isDeleteMode ? '' : 'cursor-pointer'}`, )} - onClick={() => handleSelectFont(font.id)} + onClick={!isDeleteMode ? () => handleSelectFont(font.id) : undefined} title={font.name} >
diff --git a/apps/readest-app/src/components/metadata/BookDetailEdit.tsx b/apps/readest-app/src/components/metadata/BookDetailEdit.tsx index c10dcdef..838cbdd6 100644 --- a/apps/readest-app/src/components/metadata/BookDetailEdit.tsx +++ b/apps/readest-app/src/components/metadata/BookDetailEdit.tsx @@ -7,8 +7,8 @@ import { BookMetadata } from '@/libs/document'; import { useEnv } from '@/context/EnvContext'; import { useTranslation } from '@/hooks/useTranslation'; import { flattenContributors, formatAuthors, formatPublisher, formatTitle } from '@/utils/book'; +import { useFileSelector } from '@/hooks/useFileSelector'; import { FormField } from './FormField'; -import { FILE_SELECTION_PRESETS, useFileSelector } from '@/hooks/useFileSelector'; import BookCover from '@/components/BookCover'; interface BookDetailEditProps { @@ -149,7 +149,7 @@ const BookDetailEdit: React.FC = ({ ]; const handleSelectLocalImage = async () => { - selectFiles({ ...FILE_SELECTION_PRESETS.covers, multiple: false }).then(async (result) => { + selectFiles({ type: 'covers', multiple: false }).then(async (result) => { if (result.error || result.files.length === 0) return; const selectedFile = result.files[0]!; if (selectedFile.path && appService) { diff --git a/apps/readest-app/src/hooks/useFileSelector.ts b/apps/readest-app/src/hooks/useFileSelector.ts index 40294dcd..3fc90dc9 100644 --- a/apps/readest-app/src/hooks/useFileSelector.ts +++ b/apps/readest-app/src/hooks/useFileSelector.ts @@ -1,9 +1,11 @@ import { AppService } from '@/types/system'; import { isTauriAppPlatform } from '@/services/environment'; +import { basename } from '@tauri-apps/api/path'; import { stubTranslation as _ } from '@/utils/misc'; import { BOOK_ACCEPT_FORMATS, SUPPORTED_BOOK_EXTS } from '@/services/constants'; export interface FileSelectorOptions { + type: SelectionType; accept?: string; multiple?: boolean; extensions?: string[]; @@ -42,15 +44,24 @@ const selectFileTauri = async ( appService: AppService, _: (key: string) => string, ): Promise => { - const exts = appService?.isIOSApp ? [] : options.extensions || []; + const noFilter = appService?.isIOSApp || (appService?.isAndroidApp && options.type === 'books'); + const exts = noFilter ? [] : options.extensions || []; const title = options.dialogTitle || _('Select Files'); - const files = (await appService?.selectFiles(_(title), exts)) || []; + let files = (await appService?.selectFiles(_(title), exts)) || []; - if (appService?.isIOSApp && options.extensions) { - return files.filter((file: string) => { - const fileExt = file.split('.').pop()?.toLowerCase() || 'unknown'; - return options.extensions!.includes(fileExt); - }); + if (noFilter && options.extensions) { + files = await Promise.all( + files.map(async (file: string) => { + let processedFile = file; + if (appService?.isAndroidApp && file.startsWith('content://')) { + processedFile = await basename(file); + } + const fileExt = processedFile.split('.').pop()?.toLowerCase() || 'unknown'; + const extensions = options.extensions!; + const shouldInclude = extensions.includes(fileExt) || extensions.includes('*'); + return shouldInclude ? file : null; + }), + ).then((results) => results.filter((file) => file !== null)); } return files; @@ -69,7 +80,8 @@ const processTauriFiles = (files: string[]): SelectedFile[] => { }; export const useFileSelector = (appService: AppService | null, _: (key: string) => string) => { - const selectFiles = async (options: FileSelectorOptions = {}) => { + const selectFiles = async (options: FileSelectorOptions = { type: 'generic' }) => { + options = { ...FILE_SELECTION_PRESETS[options.type], ...options }; if (!appService) { return { files: [] as SelectedFile[], error: 'App service is not available' }; } @@ -96,6 +108,11 @@ export const useFileSelector = (appService: AppService | null, _: (key: string) }; export const FILE_SELECTION_PRESETS = { + generic: { + accept: '*/*', + extensions: ['*'], + dialogTitle: _('Select Files'), + }, images: { accept: 'image/*', extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'], @@ -127,3 +144,5 @@ export const FILE_SELECTION_PRESETS = { dialogTitle: _('Select Image'), }, }; + +export type SelectionType = keyof typeof FILE_SELECTION_PRESETS; diff --git a/apps/readest-app/src/hooks/useSync.ts b/apps/readest-app/src/hooks/useSync.ts index afdae854..d2357836 100644 --- a/apps/readest-app/src/hooks/useSync.ts +++ b/apps/readest-app/src/hooks/useSync.ts @@ -176,7 +176,7 @@ export function useSync(bookKey?: string) { const syncConfigs = useCallback( async (bookConfigs?: BookConfig[], bookId?: string, op: SyncOp = 'both') => { - if (!lastSyncedAtInited) return; + if (!bookId && !lastSyncedAtInited) return; if ((op === 'push' || op === 'both') && bookConfigs?.length) { await pushChanges({ configs: bookConfigs }); }