From e1deff341dbcddc004974e95bf71e17bbf21fefb Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Fri, 26 Sep 2025 23:32:16 +0800 Subject: [PATCH] feat: supported changing data location on Android (#2131) --- .../library/components/MigrateDataWindow.tsx | 76 +++++++++++++++---- .../app/library/components/SettingsMenu.tsx | 2 +- apps/readest-app/src/services/appService.ts | 2 +- .../src/services/nativeAppService.ts | 3 +- apps/readest-app/src/types/system.ts | 1 + 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/apps/readest-app/src/app/library/components/MigrateDataWindow.tsx b/apps/readest-app/src/app/library/components/MigrateDataWindow.tsx index fea95150..9453fa90 100644 --- a/apps/readest-app/src/app/library/components/MigrateDataWindow.tsx +++ b/apps/readest-app/src/app/library/components/MigrateDataWindow.tsx @@ -1,3 +1,4 @@ +import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; import { RiFolderOpenLine, @@ -14,10 +15,12 @@ import { revealItemInDir } from '@tauri-apps/plugin-opener'; import { DATA_SUBDIR } from '@/services/constants'; import { FileItem } from '@/types/system'; import { getDirPath } from '@/utils/path'; +import { formatBytes } from '@/utils/book'; import { getOSPlatform } from '@/utils/misc'; import { FILE_REVEAL_LABELS, FILE_REVEAL_PLATFORMS } from '@/utils/os'; import Dialog from '@/components/Dialog'; -import { formatBytes } from '@/utils/book'; +import Dropdown from '@/components/Dropdown'; +import MenuItem from '@/components/MenuItem'; export const setMigrateDataDirDialogVisible = (visible: boolean) => { const dialog = document.getElementById('migrate_data_dir_window'); @@ -111,6 +114,21 @@ export const MigrateDataWindow = () => { } }; + const handleSelectedNewDir = async (dir: string) => { + setErrorMessage(''); + + try { + const newDataDir = await join(dir, DATA_SUBDIR); + await appService?.createDir(newDataDir, 'None', true); + setNewDataDir(newDataDir); + setMigrationStatus('idle'); + } catch (error) { + console.error('Error selecting directory:', error); + setErrorMessage(_('Failed to select directory')); + setMigrationStatus('error'); + } + }; + const handleStartMigration = async () => { if (!appService || !currentDataDir || !newDataDir || !filesToMigrate.length) return; @@ -186,7 +204,7 @@ export const MigrateDataWindow = () => { }; const handleRevealDir = (dataDir: string) => { - if (dataDir) { + if (dataDir && appService?.isDesktopApp) { revealItemInDir(dataDir); } }; @@ -203,6 +221,11 @@ export const MigrateDataWindow = () => { const fileRevealLabel = FILE_REVEAL_LABELS[osPlatform as FILE_REVEAL_PLATFORMS] || FILE_REVEAL_LABELS.default; + const androidNewDirs = [ + { path: '/storage/emulated/0/Documents' }, + { path: '/storage/emulated/0/Download' }, + ]; + return ( { )} - - + {appService?.isAndroidApp ? ( + {newDataDir ? _('Choose Different Folder') : _('Choose New Folder')} + } + > +
+ {androidNewDirs.map((dir) => ( + handleSelectedNewDir(dir.path)} + /> + ))} +
+
+ ) : ( + + )} {/* Migration Progress */} diff --git a/apps/readest-app/src/app/library/components/SettingsMenu.tsx b/apps/readest-app/src/app/library/components/SettingsMenu.tsx index 4863a2d8..5f375fb9 100644 --- a/apps/readest-app/src/app/library/components/SettingsMenu.tsx +++ b/apps/readest-app/src/app/library/components/SettingsMenu.tsx @@ -268,7 +268,7 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { Icon={themeMode === 'dark' ? BiMoon : themeMode === 'light' ? BiSun : TbSunMoon} onClick={cycleThemeMode} /> - {appService?.isDesktopApp && !appService?.isPortableApp && ( + {appService?.canCustomizeRootDir && ( <> diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 0c880214..0a1e4713 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -85,7 +85,7 @@ export abstract class BaseAppService implements AppService { hasHaptics = false; hasUpdater = false; hasOrientationLock = false; - canCustomRootDir = false; + canCustomizeRootDir = false; distChannel = 'readest'; protected abstract fs: FileSystem; diff --git a/apps/readest-app/src/services/nativeAppService.ts b/apps/readest-app/src/services/nativeAppService.ts index 5f89b921..35b2fa5e 100644 --- a/apps/readest-app/src/services/nativeAppService.ts +++ b/apps/readest-app/src/services/nativeAppService.ts @@ -341,8 +341,9 @@ export class NativeAppService extends BaseAppService { // orientation lock is not supported on iPad override hasOrientationLock = (OS_TYPE === 'ios' && getOSPlatform() === 'ios') || OS_TYPE === 'android'; - override canCustomRootDir = OS_TYPE !== 'ios'; + override canCustomizeRootDir = OS_TYPE !== 'ios'; override distChannel = process.env['NEXT_PUBLIC_DIST_CHANNEL'] || 'readest'; + private execDir?: string = undefined; override async init() { diff --git a/apps/readest-app/src/types/system.ts b/apps/readest-app/src/types/system.ts index 6e5a5cc4..d1180f4f 100644 --- a/apps/readest-app/src/types/system.ts +++ b/apps/readest-app/src/types/system.ts @@ -59,6 +59,7 @@ export interface AppService { isLinuxApp: boolean; isPortableApp: boolean; isDesktopApp: boolean; + canCustomizeRootDir: boolean; distChannel: string; init(): Promise;