feat: request manage external storage permission when changing data directory to sdcard root on Android (#2142)

This commit is contained in:
Huang Xin
2025-09-30 14:59:16 +08:00
committed by GitHub
parent 0a1e0212e2
commit 1d4541e353
20 changed files with 357 additions and 30 deletions
+36 -11
View File
@@ -24,7 +24,8 @@ echo "🔢 Computed versionCode: $VERSION_CODE"
PROPERTIES_FILE="./src-tauri/gen/android/app/tauri.properties"
MANIFEST="./src-tauri/gen/android/app/src/main/AndroidManifest.xml"
PERMISSION_LINE='<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>'
INSTALL_PERMISSION_LINE='<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>'
STORAGE_PERMISSION_LINE='<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>'
if [[ ! -f "$PROPERTIES_FILE" ]]; then
echo "❌ File not found: $PROPERTIES_FILE"
@@ -38,13 +39,34 @@ mv "$tmpfile" "$PROPERTIES_FILE"
echo "✅ Updated $PROPERTIES_FILE"
ised() {
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
return $?
}
# --- REMOVE PERMISSION BEFORE BUILD ---
if grep -q 'REQUEST_INSTALL_PACKAGES' "$MANIFEST"; then
echo "🧹 Removing REQUEST_INSTALL_PACKAGES from AndroidManifest.xml"
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "/REQUEST_INSTALL_PACKAGES/d" "$MANIFEST"
if ised "/REQUEST_INSTALL_PACKAGES/d" "$MANIFEST"; then
echo "✅ Successfully removed REQUEST_INSTALL_PACKAGES"
else
sed -i "/REQUEST_INSTALL_PACKAGES/d" "$MANIFEST"
echo "❌ Failed to remove REQUEST_INSTALL_PACKAGES" >&2
exit 1
fi
fi
if grep -q 'MANAGE_EXTERNAL_STORAGE' "$MANIFEST"; then
echo "🧹 Removing MANAGE_EXTERNAL_STORAGE from AndroidManifest.xml"
if ised "/MANAGE_EXTERNAL_STORAGE/d" "$MANIFEST"; then
echo "✅ Successfully removed MANAGE_EXTERNAL_STORAGE"
else
echo "❌ Failed to remove MANAGE_EXTERNAL_STORAGE" >&2
exit 1
fi
fi
@@ -54,13 +76,16 @@ pnpm tauri android build
# --- ADD PERMISSION BACK AFTER BUILD ---
if ! grep -q 'REQUEST_INSTALL_PACKAGES' "$MANIFEST"; then
echo "♻️ Restoring REQUEST_INSTALL_PACKAGES in AndroidManifest.xml"
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "/android.permission.INTERNET/a\\
$PERMISSION_LINE
" "$MANIFEST"
else
sed -i "/android.permission.INTERNET/a \ $PERMISSION_LINE" "$MANIFEST"
fi
ised "/android.permission.INTERNET/a\\
$INSTALL_PERMISSION_LINE
" "$MANIFEST"
fi
if ! grep -q 'MANAGE_EXTERNAL_STORAGE' "$MANIFEST"; then
echo "♻️ Restoring MANAGE_EXTERNAL_STORAGE in AndroidManifest.xml"
ised "/android.permission.WRITE_EXTERNAL_STORAGE/a\\
$STORAGE_PERMISSION_LINE
" "$MANIFEST"
fi
source .env.google-play.local
@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<!-- AndroidTV support -->
<uses-feature android:name="android.software.leanback" android:required="false" />
@@ -114,6 +115,12 @@
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
</activity>
<service
android:name="com.readest.native_tts.MediaForegroundService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
@@ -1,10 +1,13 @@
package com.readest.native_bridge
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.os.Build
import android.os.Environment
import android.provider.Settings
import android.view.View
import android.view.KeyEvent
import android.view.WindowInsets
@@ -13,15 +16,19 @@ import android.view.WindowInsetsController
import android.graphics.Color
import android.webkit.WebView
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.graphics.fonts.SystemFonts
import android.graphics.fonts.Font
import androidx.core.view.WindowCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.FileProvider
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.browser.customtabs.CustomTabsIntent
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.Permission
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
@@ -67,7 +74,11 @@ interface KeyDownInterceptor {
fun interceptBackKey(enabled: Boolean)
}
@TauriPlugin
@TauriPlugin(
permissions = [
Permission(strings = [Manifest.permission.MANAGE_EXTERNAL_STORAGE], alias = "manageStorage")
]
)
class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
private val implementation = NativeBridge()
private var redirectScheme = "readest"
@@ -75,6 +86,7 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
companion object {
var pendingInvoke: Invoke? = null
private const val REQUEST_MANAGE_STORAGE = 1001
}
override fun load(webView: WebView) {
@@ -402,4 +414,53 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) {
}
invoke.resolve(ret)
}
@Command
fun request_manage_storage_permission(invoke: Invoke) {
val ret = JSObject()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
try {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.data = Uri.parse("package:${activity.packageName}")
activity.startActivityForResult(intent, REQUEST_MANAGE_STORAGE)
ret.put("manageStorage", "denied")
invoke.resolve(ret)
} catch (e: Exception) {
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
activity.startActivity(intent)
ret.put("manageStorage", "denied")
invoke.resolve(ret)
}
} else {
ret.put("manageStorage", "granted")
invoke.resolve(ret)
}
} else {
val readPermission = ContextCompat.checkSelfPermission(
activity,
Manifest.permission.READ_EXTERNAL_STORAGE
)
val writePermission = ContextCompat.checkSelfPermission(
activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
if (readPermission == PackageManager.PERMISSION_GRANTED &&
writePermission == PackageManager.PERMISSION_GRANTED) {
ret.put("manageStorage", "granted")
invoke.resolve(ret)
} else {
ActivityCompat.requestPermissions(
activity,
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
REQUEST_MANAGE_STORAGE
)
ret.put("manageStorage", "prompt")
invoke.resolve(ret)
}
}
}
}
@@ -15,6 +15,9 @@ const COMMANDS: &[&str] = &[
"iap_restore_purchases",
"get_system_color_scheme",
"get_safe_area_insets",
"request_manage_storage_permission",
"checkPermissions",
"requestPermissions",
];
fn main() {
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-checkPermissions"
description = "Enables the checkPermissions command without any pre-configured scope."
commands.allow = ["checkPermissions"]
[[permission]]
identifier = "deny-checkPermissions"
description = "Denies the checkPermissions command without any pre-configured scope."
commands.deny = ["checkPermissions"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-requestPermissions"
description = "Enables the requestPermissions command without any pre-configured scope."
commands.allow = ["requestPermissions"]
[[permission]]
identifier = "deny-requestPermissions"
description = "Denies the requestPermissions command without any pre-configured scope."
commands.deny = ["requestPermissions"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-request-manage-storage-permission"
description = "Enables the request_manage_storage_permission command without any pre-configured scope."
commands.allow = ["request_manage_storage_permission"]
[[permission]]
identifier = "deny-request-manage-storage-permission"
description = "Denies the request_manage_storage_permission command without any pre-configured scope."
commands.deny = ["request_manage_storage_permission"]
@@ -20,6 +20,9 @@ Default permissions for the plugin
- `allow-iap-restore-purchases`
- `allow-get-system-color-scheme`
- `allow-get-safe-area-insets`
- `allow-request-manage-storage-permission`
- `allow-checkPermissions`
- `allow-requestPermissions`
## Permission Table
@@ -85,6 +88,32 @@ Denies the auth_with_safari command without any pre-configured scope.
<tr>
<td>
`native-bridge:allow-checkPermissions`
</td>
<td>
Enables the checkPermissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-checkPermissions`
</td>
<td>
Denies the checkPermissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-copy-uri-to-path`
</td>
@@ -397,6 +426,58 @@ Denies the lock_screen_orientation command without any pre-configured scope.
<tr>
<td>
`native-bridge:allow-requestPermissions`
</td>
<td>
Enables the requestPermissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-requestPermissions`
</td>
<td>
Denies the requestPermissions command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-request-manage-storage-permission`
</td>
<td>
Enables the request_manage_storage_permission command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:deny-request-manage-storage-permission`
</td>
<td>
Denies the request_manage_storage_permission command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`native-bridge:allow-set-system-ui-visibility`
</td>
@@ -17,4 +17,7 @@ permissions = [
"allow-iap-restore-purchases",
"allow-get-system-color-scheme",
"allow-get-safe-area-insets",
"allow-request-manage-storage-permission",
"allow-checkPermissions",
"allow-requestPermissions",
]
@@ -318,6 +318,18 @@
"const": "deny-auth-with-safari",
"markdownDescription": "Denies the auth_with_safari command without any pre-configured scope."
},
{
"description": "Enables the checkPermissions command without any pre-configured scope.",
"type": "string",
"const": "allow-checkPermissions",
"markdownDescription": "Enables the checkPermissions command without any pre-configured scope."
},
{
"description": "Denies the checkPermissions command without any pre-configured scope.",
"type": "string",
"const": "deny-checkPermissions",
"markdownDescription": "Denies the checkPermissions command without any pre-configured scope."
},
{
"description": "Enables the copy_uri_to_path command without any pre-configured scope.",
"type": "string",
@@ -462,6 +474,30 @@
"const": "deny-lock-screen-orientation",
"markdownDescription": "Denies the lock_screen_orientation command without any pre-configured scope."
},
{
"description": "Enables the requestPermissions command without any pre-configured scope.",
"type": "string",
"const": "allow-requestPermissions",
"markdownDescription": "Enables the requestPermissions command without any pre-configured scope."
},
{
"description": "Denies the requestPermissions command without any pre-configured scope.",
"type": "string",
"const": "deny-requestPermissions",
"markdownDescription": "Denies the requestPermissions command without any pre-configured scope."
},
{
"description": "Enables the request_manage_storage_permission command without any pre-configured scope.",
"type": "string",
"const": "allow-request-manage-storage-permission",
"markdownDescription": "Enables the request_manage_storage_permission command without any pre-configured scope."
},
{
"description": "Denies the request_manage_storage_permission command without any pre-configured scope.",
"type": "string",
"const": "deny-request-manage-storage-permission",
"markdownDescription": "Denies the request_manage_storage_permission command without any pre-configured scope."
},
{
"description": "Enables the set_system_ui_visibility command without any pre-configured scope.",
"type": "string",
@@ -487,10 +523,10 @@
"markdownDescription": "Denies the use_background_audio command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`",
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-request-manage-storage-permission`\n- `allow-checkPermissions`\n- `allow-requestPermissions`",
"type": "string",
"const": "default",
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`"
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`\n- `allow-get-sys-fonts-list`\n- `allow-intercept-keys`\n- `allow-lock-screen-orientation`\n- `allow-iap-initialize`\n- `allow-iap-fetch-products`\n- `allow-iap-purchase-product`\n- `allow-iap-restore-purchases`\n- `allow-get-system-color-scheme`\n- `allow-get-safe-area-insets`\n- `allow-request-manage-storage-permission`\n- `allow-checkPermissions`\n- `allow-requestPermissions`"
}
]
}
@@ -126,3 +126,10 @@ pub(crate) async fn get_safe_area_insets<R: Runtime>(
) -> Result<GetSafeAreaInsetsResponse> {
app.native_bridge().get_safe_area_insets()
}
#[command]
pub(crate) async fn request_manage_storage_permission<R: Runtime>(
app: AppHandle<R>,
) -> Result<RequestManageStoragePermissionResponse> {
app.native_bridge().request_manage_storage_permission()
}
@@ -106,4 +106,10 @@ impl<R: Runtime> NativeBridge<R> {
pub fn get_safe_area_insets(&self) -> crate::Result<GetSafeAreaInsetsResponse> {
Err(crate::Error::UnsupportedPlatformError)
}
pub fn request_manage_storage_permission(
&self,
) -> crate::Result<RequestManageStoragePermissionResponse> {
Err(crate::Error::UnsupportedPlatformError)
}
}
@@ -53,6 +53,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
commands::iap_restore_purchases,
commands::get_system_color_scheme,
commands::get_safe_area_insets,
commands::request_manage_storage_permission,
])
.setup(|app, api| {
#[cfg(mobile)]
@@ -169,3 +169,13 @@ impl<R: Runtime> NativeBridge<R> {
.map_err(Into::into)
}
}
impl<R: Runtime> NativeBridge<R> {
pub fn request_manage_storage_permission(
&self,
) -> crate::Result<RequestManageStoragePermissionResponse> {
self.0
.run_mobile_plugin("request_manage_storage_permission", ())
.map_err(Into::into)
}
}
@@ -167,3 +167,9 @@ pub struct GetSafeAreaInsetsResponse {
pub left: f64,
pub right: f64,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RequestManageStoragePermissionResponse {
pub manage_storage: String, // "granted", "denied", or "prompt"
}
@@ -150,10 +150,10 @@ class MediaForegroundService : Service() {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("TTS Media Session")
.setContentText("Text-to-speech playback active")
.setSmallIcon(android.R.drawable.ic_media_play) // Replace with your icon
.setSmallIcon(android.R.drawable.ic_media_play)
.setOngoing(true)
.setSilent(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.build()
@@ -6,7 +6,8 @@ import {
RiErrorWarningFill,
RiLoader2Line,
} from 'react-icons/ri';
import { join } from '@tauri-apps/api/path';
import { documentDir, join } from '@tauri-apps/api/path';
import { invoke, PermissionState } from '@tauri-apps/api/core';
import { relaunch } from '@tauri-apps/plugin-process';
import { useEnv } from '@/context/EnvContext';
import { useTranslation } from '@/hooks/useTranslation';
@@ -40,6 +41,10 @@ interface MigrationProgress {
currentFile?: string;
}
interface Permissions {
manageStorage: PermissionState;
}
export const MigrateDataWindow = () => {
const _ = useTranslation();
const { appService, envConfig } = useEnv();
@@ -56,12 +61,14 @@ export const MigrateDataWindow = () => {
const [filesToMigrate, setFilesToMigrate] = useState<FileItem[]>([]);
const [currentDirFileCount, setCurrentDirFileCount] = useState('');
const [currentDirFileSize, setCurrentDirFileSize] = useState(0);
const [androidNewDirs, setAndroidNewDirs] = useState<{ path: string; label: string }[]>([]);
useEffect(() => {
const handleCustomEvent = (event: CustomEvent) => {
setIsOpen(event.detail.visible);
if (event.detail.visible) {
loadCurrentDataDir();
loadAndroidDirs();
}
};
@@ -93,6 +100,27 @@ export const MigrateDataWindow = () => {
}
};
const loadAndroidDirs = async () => {
const sdcardDirs = [
{ path: '/storage/emulated/0', label: '/sdcard' },
{ path: '/storage/emulated/0/Books', label: '/sdcard/Books' },
{ path: '/storage/emulated/0/Documents', label: '/sdcard/Documents' },
{ path: '/storage/emulated/0/Download', label: '/sdcard/Download' },
];
try {
if (appService?.isAndroidApp) {
const localDocumentDir = await documentDir();
setAndroidNewDirs([
// For Google Play version we won't request permission to access root of /sdcard
...(appService?.distChannel === 'playstore' ? [] : sdcardDirs),
{ path: localDocumentDir, label: '/sdcard/APPDATA/Documents' },
]);
}
} catch (error) {
console.error('Error loading app local data directory:', error);
}
};
const handleSelectNewDir = async () => {
setMigrationStatus('selecting');
setErrorMessage('');
@@ -117,6 +145,16 @@ export const MigrateDataWindow = () => {
const handleSelectedNewDir = async (dir: string) => {
setErrorMessage('');
if (!dir.includes('Android/data')) {
let permission = await invoke<Permissions>('plugin:native-bridge|checkPermissions');
if (permission.manageStorage !== 'granted') {
permission = await invoke<Permissions>(
'plugin:native-bridge|request_manage_storage_permission',
);
}
if (permission.manageStorage !== 'granted') return;
}
try {
const newDataDir = await join(dir, DATA_SUBDIR);
await appService?.createDir(newDataDir, 'None', true);
@@ -221,11 +259,6 @@ 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'
@@ -299,9 +332,9 @@ export const MigrateDataWindow = () => {
{androidNewDirs.map((dir) => (
<MenuItem
key={dir.path}
toggled={newDataDir === dir.path}
toggled={newDataDir.split(`/${DATA_SUBDIR}`)[0] === dir.path}
transient
label={dir.path}
label={dir.label}
onClick={() => handleSelectedNewDir(dir.path)}
/>
))}
@@ -387,7 +420,7 @@ export const MigrateDataWindow = () => {
<span className='font-medium'>{_('Migration failed')}</span>
</div>
<div className='bg-error/10 border-error/20 rounded-lg border p-3'>
<p className='text-error/80 text-sm'>{errorMessage}</p>
<p className='text-error/80 break-all text-sm'>{errorMessage}</p>
</div>
</div>
)}
+2 -1
View File
@@ -3,6 +3,7 @@ import { SystemSettings } from '@/types/settings';
import {
AppPlatform,
AppService,
DistChannel,
FileItem,
OsPlatform,
ResolvedPath,
@@ -86,7 +87,7 @@ export abstract class BaseAppService implements AppService {
hasUpdater = false;
hasOrientationLock = false;
canCustomizeRootDir = false;
distChannel = 'readest';
distChannel = 'readest' as DistChannel;
protected abstract fs: FileSystem;
protected abstract resolvePath(fp: string, base: BaseDir): ResolvedPath;
@@ -26,7 +26,14 @@ import {
} from '@tauri-apps/api/path';
import { type as osType } from '@tauri-apps/plugin-os';
import { FileSystem, BaseDir, AppPlatform, ResolvedPath, FileItem } from '@/types/system';
import {
FileSystem,
BaseDir,
AppPlatform,
ResolvedPath,
FileItem,
DistChannel,
} from '@/types/system';
import { getOSPlatform, isContentURI, isValidURL } from '@/utils/misc';
import { getDirPath, getFilename } from '@/utils/path';
import { NativeFile, RemoteFile } from '@/utils/file';
@@ -342,7 +349,7 @@ export class NativeAppService extends BaseAppService {
override hasOrientationLock =
(OS_TYPE === 'ios' && getOSPlatform() === 'ios') || OS_TYPE === 'android';
override canCustomizeRootDir = OS_TYPE !== 'ios';
override distChannel = process.env['NEXT_PUBLIC_DIST_CHANNEL'] || 'readest';
override distChannel = (process.env['NEXT_PUBLIC_DIST_CHANNEL'] || 'readest') as DistChannel;
private execDir?: string = undefined;
+2 -1
View File
@@ -9,6 +9,7 @@ export type OsPlatform = 'android' | 'ios' | 'macos' | 'windows' | 'linux' | 'un
export type BaseDir = 'Books' | 'Settings' | 'Data' | 'Fonts' | 'Log' | 'Cache' | 'Temp' | 'None';
export type DeleteAction = 'cloud' | 'local' | 'both';
export type SelectDirectoryMode = 'read' | 'write';
export type DistChannel = 'readest' | 'playstore' | 'appstore' | 'unknown';
export type ResolvedPath = {
baseDir: number;
@@ -60,7 +61,7 @@ export interface AppService {
isPortableApp: boolean;
isDesktopApp: boolean;
canCustomizeRootDir: boolean;
distChannel: string;
distChannel: DistChannel;
init(): Promise<void>;
openFile(path: string, base: BaseDir): Promise<File>;