forked from akai/readest
feat: supported changing data location on Android (#2131)
This commit is contained in:
@@ -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 (
|
||||
<Dialog
|
||||
id='migrate_data_dir_window'
|
||||
@@ -258,17 +281,44 @@ export const MigrateDataWindow = () => {
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
className='btn btn-outline btn-sm w-full'
|
||||
onClick={handleSelectNewDir}
|
||||
disabled={migrationStatus === 'migrating' || migrationStatus === 'selecting'}
|
||||
>
|
||||
{migrationStatus === 'selecting' && (
|
||||
<RiLoader2Line className='h-4 w-4 animate-spin' />
|
||||
)}
|
||||
{newDataDir ? _('Choose Different Folder') : _('Choose New Folder')}
|
||||
</button>
|
||||
{appService?.isAndroidApp ? (
|
||||
<Dropdown
|
||||
label={_('Choose New Folder')}
|
||||
className='dropdown-bottom flex w-full justify-center'
|
||||
buttonClassName='btn btn-ghost btn-outline w-full'
|
||||
toggleButton={
|
||||
<div>{newDataDir ? _('Choose Different Folder') : _('Choose New Folder')}</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'folder-menu dropdown-content dropdown-center no-triangle',
|
||||
'border-base-300 !bg-base-200 z-20 mt-1 max-w-[90vw] shadow-2xl',
|
||||
)}
|
||||
>
|
||||
{androidNewDirs.map((dir) => (
|
||||
<MenuItem
|
||||
key={dir.path}
|
||||
toggled={newDataDir === dir.path}
|
||||
transient
|
||||
label={dir.path}
|
||||
onClick={() => handleSelectedNewDir(dir.path)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Dropdown>
|
||||
) : (
|
||||
<button
|
||||
className='btn btn-outline btn-sm w-full'
|
||||
onClick={handleSelectNewDir}
|
||||
disabled={migrationStatus === 'migrating' || migrationStatus === 'selecting'}
|
||||
>
|
||||
{migrationStatus === 'selecting' && (
|
||||
<RiLoader2Line className='h-4 w-4 animate-spin' />
|
||||
)}
|
||||
{newDataDir ? _('Choose Different Folder') : _('Choose New Folder')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Migration Progress */}
|
||||
|
||||
@@ -268,7 +268,7 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
|
||||
Icon={themeMode === 'dark' ? BiMoon : themeMode === 'light' ? BiSun : TbSunMoon}
|
||||
onClick={cycleThemeMode}
|
||||
/>
|
||||
{appService?.isDesktopApp && !appService?.isPortableApp && (
|
||||
{appService?.canCustomizeRootDir && (
|
||||
<>
|
||||
<hr aria-hidden='true' className='border-base-200 my-1' />
|
||||
<MenuItem label={_('Advanced Settings')}>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface AppService {
|
||||
isLinuxApp: boolean;
|
||||
isPortableApp: boolean;
|
||||
isDesktopApp: boolean;
|
||||
canCustomizeRootDir: boolean;
|
||||
distChannel: string;
|
||||
|
||||
init(): Promise<void>;
|
||||
|
||||
Reference in New Issue
Block a user