diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index b8f116a9..055b29ff 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -8,10 +8,11 @@
-
+
();
Directory source = profilesWidget.baseDirectory();
- Directory destination = Directory(value);
+ Directory destination = Directory(value);
moveDirectory(source.path, destination.path).then((value) async {
isMovingDirectory.value = false;
update();
if (value == "same") {
return;
- } else if (value == "success") {
+ } else if (value == "success") {
profilesWidget.setBaseDirectory(destination);
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('baseDirectory', destination.path);
baseDirectory.value = destination.path;
- } else {
- showDialog(
- context: context,
- builder: (BuildContext context) {
- return Utils.showAlertDialog(
+ Get.snackbar(
+ 'Success',
+ 'Base directory moved successfully',
+ snackPosition: SnackPosition.BOTTOM,
+ duration: const Duration(seconds: 2),
+ );
+ } else {
+ Get.dialog(
+ Utils.showAlertDialog(
title: Text(
'Error',
style: GoogleFonts.poppins(
@@ -83,7 +87,9 @@ class SettingsController extends GetxController {
? "Cannot move to a nested directory"
: value == "not-empty"
? "Destination directory is not empty"
- : "An error occurred",
+ : value == "not-permitted"
+ ? "Selected folder can't be written to (Android SAF). Please choose a different folder."
+ : "An error occurred",
style: GoogleFonts.poppins(
color: TaskWarriorColors.grey,
fontSize: TaskWarriorFonts.fontSizeSmall,
@@ -92,7 +98,7 @@ class SettingsController extends GetxController {
actions: [
TextButton(
onPressed: () {
- Navigator.pop(context);
+ Get.back();
},
child: Text(
'OK',
@@ -102,10 +108,9 @@ class SettingsController extends GetxController {
),
)
],
- );
- },
- );
- }
+ ),
+ );
+ }
});
}
});
@@ -120,16 +125,48 @@ class SettingsController extends GetxController {
return "nested";
}
- Directory toDir = Directory(toDirectory);
+ Directory toDir = Directory(toDirectory);
+ // Ensure destination exists before checking contents
+ await toDir.create(recursive: true);
final length = await toDir.list().length;
if (length > 0) {
return "not-empty";
}
- await moveDirectoryRecurse(fromDirectory, toDirectory);
- return "success";
+ // Preflight: on Android, check that we can actually write to the chosen directory
+ // to avoid crashing with Operation not permitted when a SAF tree URI was selected.
+ try {
+ final testFile = File(path.join(toDirectory, ".tw_write_test"));
+ await toDir.create(recursive: true);
+ await testFile.writeAsString("ok");
+ await testFile.delete();
+ } on FileSystemException catch (e) {
+ // Map common permission error to a friendly status
+ if (e.osError?.errorCode == 1 ||
+ (e.osError?.message.toLowerCase().contains("operation not permitted") ?? false)) {
+ return "not-permitted";
+ }
+ return "error";
+ } catch (_) {
+ return "error";
+ }
+
+ try {
+ await moveDirectoryRecurse(fromDirectory, toDirectory);
+ return "success";
+ } on FileSystemException catch (e) {
+ if (e.osError?.errorCode == 1 ||
+ (e.osError?.message.toLowerCase().contains("operation not permitted") ?? false)) {
+ return "not-permitted";
+ }
+ return "error";
+ } catch (_) {
+ return "error";
+ }
}
+ // ... no hardcoded SAF path mapping; rely on guard and proper APIs if enabled in future
+
Future moveDirectoryRecurse(
String fromDirectory, String toDirectory) async {
Directory fromDir = Directory(fromDirectory);
@@ -140,18 +177,21 @@ class SettingsController extends GetxController {
// Loop through each file and directory and move it to the toDirectory
await for (final entity in fromDir.list()) {
+ // Skip flutter runtime assets – they should not be moved
+ final relativePath = path.relative(entity.path, from: fromDirectory);
+ if (relativePath.split(path.separator).contains('flutter_assets')) {
+ continue;
+ }
if (entity is File) {
// If it's a file, move it to the toDirectory
File file = entity;
- String newPath = path.join(
- toDirectory, path.relative(file.path, from: fromDirectory));
+ String newPath = path.join(toDirectory, relativePath);
await File(newPath).writeAsBytes(await file.readAsBytes());
await file.delete();
} else if (entity is Directory) {
// If it's a directory, create it in the toDirectory and recursively move its contents
Directory dir = entity;
- String newPath = path.join(
- toDirectory, path.relative(dir.path, from: fromDirectory));
+ String newPath = path.join(toDirectory, relativePath);
Directory newDir = Directory(newPath);
await newDir.create(recursive: true);
await moveDirectoryRecurse(dir.path, newPath);