diff --git a/apps/readest-app/scripts/release-google-play.sh b/apps/readest-app/scripts/release-google-play.sh index 0ce68f0c..992f33eb 100755 --- a/apps/readest-app/scripts/release-google-play.sh +++ b/apps/readest-app/scripts/release-google-play.sh @@ -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='' +INSTALL_PERMISSION_LINE='' +STORAGE_PERMISSION_LINE='' 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 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 376251a8..a8423bc9 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 @@ -1,12 +1,13 @@ + - + @@ -114,6 +115,12 @@ + + = 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) + } + } + } } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs index 3a42ecbe..30b21d1e 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs @@ -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() { diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/checkPermissions.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/checkPermissions.toml new file mode 100644 index 00000000..dd209449 --- /dev/null +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/checkPermissions.toml @@ -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"] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/requestPermissions.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/requestPermissions.toml new file mode 100644 index 00000000..b3238d63 --- /dev/null +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/requestPermissions.toml @@ -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"] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/request_manage_storage_permission.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/request_manage_storage_permission.toml new file mode 100644 index 00000000..634a2199 --- /dev/null +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/request_manage_storage_permission.toml @@ -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"] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md index fb3332b9..9dd17e8f 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md @@ -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. +`native-bridge:allow-checkPermissions` + + + + +Enables the checkPermissions command without any pre-configured scope. + + + + + + + +`native-bridge:deny-checkPermissions` + + + + +Denies the checkPermissions command without any pre-configured scope. + + + + + + + `native-bridge:allow-copy-uri-to-path` @@ -397,6 +426,58 @@ Denies the lock_screen_orientation command without any pre-configured scope. +`native-bridge:allow-requestPermissions` + + + + +Enables the requestPermissions command without any pre-configured scope. + + + + + + + +`native-bridge:deny-requestPermissions` + + + + +Denies the requestPermissions command without any pre-configured scope. + + + + + + + +`native-bridge:allow-request-manage-storage-permission` + + + + +Enables the request_manage_storage_permission command without any pre-configured scope. + + + + + + + +`native-bridge:deny-request-manage-storage-permission` + + + + +Denies the request_manage_storage_permission command without any pre-configured scope. + + + + + + + `native-bridge:allow-set-system-ui-visibility` diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml index a88c994a..0e3ff07a 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml @@ -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", ] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json index 95dbe93c..d5588e4e 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json @@ -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`" } ] } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs index 8cda521f..021c2130 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs @@ -126,3 +126,10 @@ pub(crate) async fn get_safe_area_insets( ) -> Result { app.native_bridge().get_safe_area_insets() } + +#[command] +pub(crate) async fn request_manage_storage_permission( + app: AppHandle, +) -> Result { + app.native_bridge().request_manage_storage_permission() +} diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs index 77f5cc00..f20f0c87 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs @@ -106,4 +106,10 @@ impl NativeBridge { pub fn get_safe_area_insets(&self) -> crate::Result { Err(crate::Error::UnsupportedPlatformError) } + + pub fn request_manage_storage_permission( + &self, + ) -> crate::Result { + Err(crate::Error::UnsupportedPlatformError) + } } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs index 35cc282f..e8cf47ef 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs @@ -53,6 +53,7 @@ pub fn init() -> TauriPlugin { commands::iap_restore_purchases, commands::get_system_color_scheme, commands::get_safe_area_insets, + commands::request_manage_storage_permission, ]) .setup(|app, api| { #[cfg(mobile)] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs index ecb99986..3f50da39 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs @@ -169,3 +169,13 @@ impl NativeBridge { .map_err(Into::into) } } + +impl NativeBridge { + pub fn request_manage_storage_permission( + &self, + ) -> crate::Result { + self.0 + .run_mobile_plugin("request_manage_storage_permission", ()) + .map_err(Into::into) + } +} diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs index 8254f0bf..d8b446b7 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs @@ -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" +} diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/java/NativeTTSPlugin.kt b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/java/NativeTTSPlugin.kt index ac65d1e5..71c88662 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/java/NativeTTSPlugin.kt +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-tts/android/src/main/java/NativeTTSPlugin.kt @@ -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() diff --git a/apps/readest-app/src/app/library/components/MigrateDataWindow.tsx b/apps/readest-app/src/app/library/components/MigrateDataWindow.tsx index 9453fa90..1eaa741e 100644 --- a/apps/readest-app/src/app/library/components/MigrateDataWindow.tsx +++ b/apps/readest-app/src/app/library/components/MigrateDataWindow.tsx @@ -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([]); 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('plugin:native-bridge|checkPermissions'); + if (permission.manageStorage !== 'granted') { + permission = await invoke( + '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 ( { {androidNewDirs.map((dir) => ( handleSelectedNewDir(dir.path)} /> ))} @@ -387,7 +420,7 @@ export const MigrateDataWindow = () => { {_('Migration failed')}
-

{errorMessage}

+

{errorMessage}

)} diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 0a1e4713..00fb521d 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -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; diff --git a/apps/readest-app/src/services/nativeAppService.ts b/apps/readest-app/src/services/nativeAppService.ts index 35b2fa5e..4c3a6615 100644 --- a/apps/readest-app/src/services/nativeAppService.ts +++ b/apps/readest-app/src/services/nativeAppService.ts @@ -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; diff --git a/apps/readest-app/src/types/system.ts b/apps/readest-app/src/types/system.ts index d1180f4f..8e4ead99 100644 --- a/apps/readest-app/src/types/system.ts +++ b/apps/readest-app/src/types/system.ts @@ -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; openFile(path: string, base: BaseDir): Promise;