diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt index 46c289c3b..bc9f217f2 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/StorageUtils.kt @@ -89,69 +89,78 @@ object StorageUtils { } private fun findPrimaryVolumePath(context: Context): String? { - // we want: - // /storage/emulated/0/ - // `Environment.getExternalStorageDirectory()` (deprecated) yields: - // /storage/emulated/0 - // `context.getExternalFilesDir(null)` yields: - // /storage/emulated/0/Android/data/{package_name}/files - return appSpecificVolumePath(context.getExternalFilesDir(null)) + try { + // we want: + // /storage/emulated/0/ + // `Environment.getExternalStorageDirectory()` (deprecated) yields: + // /storage/emulated/0 + // `context.getExternalFilesDir(null)` yields: + // /storage/emulated/0/Android/data/{package_name}/files + return appSpecificVolumePath(context.getExternalFilesDir(null)) + } catch (e: Exception) { + Log.e(LOG_TAG, "failed to find primary volume path", e) + } + return null } private fun findVolumePaths(context: Context): Array { // Final set of paths val paths = HashSet() - // Primary emulated SD-CARD - val rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET") ?: "" - if (TextUtils.isEmpty(rawEmulatedStorageTarget)) { - // fix of empty raw emulated storage on marshmallow - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - lateinit var files: List - var validFiles: Boolean - do { - // `getExternalFilesDirs` sometimes include `null` when called right after getting read access - // (e.g. on API 30 emulator) so we retry until the file system is ready - val externalFilesDirs = context.getExternalFilesDirs(null) - validFiles = !externalFilesDirs.contains(null) - if (validFiles) { - files = externalFilesDirs.filterNotNull() - } else { - try { - Thread.sleep(100) - } catch (e: InterruptedException) { - Log.e(LOG_TAG, "insomnia", e) + try { + // Primary emulated SD-CARD + val rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET") ?: "" + if (TextUtils.isEmpty(rawEmulatedStorageTarget)) { + // fix of empty raw emulated storage on marshmallow + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + lateinit var files: List + var validFiles: Boolean + do { + // `getExternalFilesDirs` sometimes include `null` when called right after getting read access + // (e.g. on API 30 emulator) so we retry until the file system is ready + val externalFilesDirs = context.getExternalFilesDirs(null) + validFiles = !externalFilesDirs.contains(null) + if (validFiles) { + files = externalFilesDirs.filterNotNull() + } else { + try { + Thread.sleep(100) + } catch (e: InterruptedException) { + Log.e(LOG_TAG, "insomnia", e) + } } - } - } while (!validFiles) - paths.addAll(files.mapNotNull(::appSpecificVolumePath)) - } else { - // Primary physical SD-CARD (not emulated) - val rawExternalStorage = System.getenv("EXTERNAL_STORAGE") ?: "" - - // Device has physical external storage; use plain paths. - if (TextUtils.isEmpty(rawExternalStorage)) { - // EXTERNAL_STORAGE undefined; falling back to default. - paths.addAll(physicalPaths) + } while (!validFiles) + paths.addAll(files.mapNotNull(::appSpecificVolumePath)) } else { - paths.add(rawExternalStorage) + // Primary physical SD-CARD (not emulated) + val rawExternalStorage = System.getenv("EXTERNAL_STORAGE") ?: "" + + // Device has physical external storage; use plain paths. + if (TextUtils.isEmpty(rawExternalStorage)) { + // EXTERNAL_STORAGE undefined; falling back to default. + paths.addAll(physicalPaths) + } else { + paths.add(rawExternalStorage) + } + } + } else { + // Device has emulated storage; external storage paths should have userId burned into them. + // /storage/emulated/[0,1,2,...]/ + val path = getPrimaryVolumePath(context) + val rawUserId = path.split(File.separator).lastOrNull(String::isNotEmpty)?.takeIf { TextUtils.isDigitsOnly(it) } ?: "" + if (rawUserId.isEmpty()) { + paths.add(rawEmulatedStorageTarget) + } else { + paths.add(rawEmulatedStorageTarget + File.separator + rawUserId) } } - } else { - // Device has emulated storage; external storage paths should have userId burned into them. - // /storage/emulated/[0,1,2,...]/ - val path = getPrimaryVolumePath(context) - val rawUserId = path.split(File.separator).lastOrNull(String::isNotEmpty)?.takeIf { TextUtils.isDigitsOnly(it) } ?: "" - if (rawUserId.isEmpty()) { - paths.add(rawEmulatedStorageTarget) - } else { - paths.add(rawEmulatedStorageTarget + File.separator + rawUserId) - } - } - // All Secondary SD-CARDs (all exclude primary) separated by ":" - System.getenv("SECONDARY_STORAGE")?.let { secondaryStorages -> - paths.addAll(secondaryStorages.split(File.pathSeparator).filter { it.isNotEmpty() }) + // All Secondary SD-CARDs (all exclude primary) separated by ":" + System.getenv("SECONDARY_STORAGE")?.let { secondaryStorages -> + paths.addAll(secondaryStorages.split(File.pathSeparator).filter { it.isNotEmpty() }) + } + } catch (e: Exception) { + Log.e(LOG_TAG, "failed to find volume paths", e) } return paths.map { ensureTrailingSeparator(it) }.toTypedArray() diff --git a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart index 0c8305314..dba5b396e 100644 --- a/lib/widgets/dialogs/filter_editors/create_album_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/create_album_dialog.dart @@ -23,13 +23,13 @@ class _CreateAlbumDialogState extends State { final ValueNotifier _existsNotifier = ValueNotifier(false); final ValueNotifier _isValidNotifier = ValueNotifier(false); late Set _allVolumes; - late StorageVolume _primaryVolume, _selectedVolume; + late StorageVolume? _primaryVolume, _selectedVolume; @override void initState() { super.initState(); _allVolumes = androidFileUtils.storageVolumes; - _primaryVolume = _allVolumes.firstWhere((volume) => volume.isPrimary, orElse: () => _allVolumes.first); + _primaryVolume = _allVolumes.firstWhereOrNull((volume) => volume.isPrimary) ?? _allVolumes.firstOrNull; _selectedVolume = _primaryVolume; _nameFieldFocusNode.addListener(_onFocus); } @@ -144,8 +144,9 @@ class _CreateAlbumDialogState extends State { } String _buildAlbumPath(String name) { - if (name.isEmpty) return ''; - return pContext.join(_selectedVolume.path, 'Pictures', name); + final selectedVolume = _selectedVolume; + if (selectedVolume == null || name.isEmpty) return ''; + return pContext.join(selectedVolume.path, 'Pictures', name); } Future _validate() async { diff --git a/lib/widgets/settings/privacy/file_picker/file_picker.dart b/lib/widgets/settings/privacy/file_picker/file_picker.dart index ae75cb11b..f8695b482 100644 --- a/lib/widgets/settings/privacy/file_picker/file_picker.dart +++ b/lib/widgets/settings/privacy/file_picker/file_picker.dart @@ -37,8 +37,10 @@ class _FilePickerState extends State { @override void initState() { super.initState(); - final primaryVolume = volumes.firstWhere((v) => v.isPrimary); - _goTo(primaryVolume.path); + final primaryVolume = volumes.firstWhereOrNull((v) => v.isPrimary); + if (primaryVolume != null) { + _goTo(primaryVolume.path); + } } @override