@@ -54,22 +54,26 @@ class SettingsController extends GetxController {
54
54
// InheritedProfiles profilesWidget = ProfilesWidget.of(context);
55
55
var profilesWidget = Get .find <SplashController >();
56
56
Directory source = profilesWidget.baseDirectory ();
57
- Directory destination = Directory (value);
57
+ Directory destination = Directory (value);
58
58
moveDirectory (source.path, destination.path).then ((value) async {
59
59
isMovingDirectory.value = false ;
60
60
update ();
61
61
if (value == "same" ) {
62
62
return ;
63
- } else if (value == "success" ) {
63
+ } else if (value == "success" ) {
64
64
profilesWidget.setBaseDirectory (destination);
65
65
SharedPreferences prefs = await SharedPreferences .getInstance ();
66
66
prefs.setString ('baseDirectory' , destination.path);
67
67
baseDirectory.value = destination.path;
68
- } else {
69
- showDialog (
70
- context: context,
71
- builder: (BuildContext context) {
72
- return Utils .showAlertDialog (
68
+ Get .snackbar (
69
+ 'Success' ,
70
+ 'Base directory moved successfully' ,
71
+ snackPosition: SnackPosition .BOTTOM ,
72
+ duration: const Duration (seconds: 2 ),
73
+ );
74
+ } else {
75
+ Get .dialog (
76
+ Utils .showAlertDialog (
73
77
title: Text (
74
78
'Error' ,
75
79
style: GoogleFonts .poppins (
@@ -83,7 +87,9 @@ class SettingsController extends GetxController {
83
87
? "Cannot move to a nested directory"
84
88
: value == "not-empty"
85
89
? "Destination directory is not empty"
86
- : "An error occurred" ,
90
+ : value == "not-permitted"
91
+ ? "Selected folder can't be written to (Android SAF). Please choose a different folder."
92
+ : "An error occurred" ,
87
93
style: GoogleFonts .poppins (
88
94
color: TaskWarriorColors .grey,
89
95
fontSize: TaskWarriorFonts .fontSizeSmall,
@@ -92,7 +98,7 @@ class SettingsController extends GetxController {
92
98
actions: [
93
99
TextButton (
94
100
onPressed: () {
95
- Navigator . pop (context );
101
+ Get . back ( );
96
102
},
97
103
child: Text (
98
104
'OK' ,
@@ -102,10 +108,9 @@ class SettingsController extends GetxController {
102
108
),
103
109
)
104
110
],
105
- );
106
- },
107
- );
108
- }
111
+ ),
112
+ );
113
+ }
109
114
});
110
115
}
111
116
});
@@ -120,16 +125,48 @@ class SettingsController extends GetxController {
120
125
return "nested" ;
121
126
}
122
127
123
- Directory toDir = Directory (toDirectory);
128
+ Directory toDir = Directory (toDirectory);
129
+ // Ensure destination exists before checking contents
130
+ await toDir.create (recursive: true );
124
131
final length = await toDir.list ().length;
125
132
if (length > 0 ) {
126
133
return "not-empty" ;
127
134
}
128
135
129
- await moveDirectoryRecurse (fromDirectory, toDirectory);
130
- return "success" ;
136
+ // Preflight: on Android, check that we can actually write to the chosen directory
137
+ // to avoid crashing with Operation not permitted when a SAF tree URI was selected.
138
+ try {
139
+ final testFile = File (path.join (toDirectory, ".tw_write_test" ));
140
+ await toDir.create (recursive: true );
141
+ await testFile.writeAsString ("ok" );
142
+ await testFile.delete ();
143
+ } on FileSystemException catch (e) {
144
+ // Map common permission error to a friendly status
145
+ if (e.osError? .errorCode == 1 ||
146
+ (e.osError? .message.toLowerCase ().contains ("operation not permitted" ) ?? false )) {
147
+ return "not-permitted" ;
148
+ }
149
+ return "error" ;
150
+ } catch (_) {
151
+ return "error" ;
152
+ }
153
+
154
+ try {
155
+ await moveDirectoryRecurse (fromDirectory, toDirectory);
156
+ return "success" ;
157
+ } on FileSystemException catch (e) {
158
+ if (e.osError? .errorCode == 1 ||
159
+ (e.osError? .message.toLowerCase ().contains ("operation not permitted" ) ?? false )) {
160
+ return "not-permitted" ;
161
+ }
162
+ return "error" ;
163
+ } catch (_) {
164
+ return "error" ;
165
+ }
131
166
}
132
167
168
+ // ... no hardcoded SAF path mapping; rely on guard and proper APIs if enabled in future
169
+
133
170
Future <void > moveDirectoryRecurse (
134
171
String fromDirectory, String toDirectory) async {
135
172
Directory fromDir = Directory (fromDirectory);
@@ -140,18 +177,21 @@ class SettingsController extends GetxController {
140
177
141
178
// Loop through each file and directory and move it to the toDirectory
142
179
await for (final entity in fromDir.list ()) {
180
+ // Skip flutter runtime assets – they should not be moved
181
+ final relativePath = path.relative (entity.path, from: fromDirectory);
182
+ if (relativePath.split (path.separator).contains ('flutter_assets' )) {
183
+ continue ;
184
+ }
143
185
if (entity is File ) {
144
186
// If it's a file, move it to the toDirectory
145
187
File file = entity;
146
- String newPath = path.join (
147
- toDirectory, path.relative (file.path, from: fromDirectory));
188
+ String newPath = path.join (toDirectory, relativePath);
148
189
await File (newPath).writeAsBytes (await file.readAsBytes ());
149
190
await file.delete ();
150
191
} else if (entity is Directory ) {
151
192
// If it's a directory, create it in the toDirectory and recursively move its contents
152
193
Directory dir = entity;
153
- String newPath = path.join (
154
- toDirectory, path.relative (dir.path, from: fromDirectory));
194
+ String newPath = path.join (toDirectory, relativePath);
155
195
Directory newDir = Directory (newPath);
156
196
await newDir.create (recursive: true );
157
197
await moveDirectoryRecurse (dir.path, newPath);
0 commit comments