fullscreen: added delete action
This commit is contained in:
parent
0c8318444b
commit
7d2a27f797
7 changed files with 132 additions and 19 deletions
|
@ -51,6 +51,9 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
case "cancelGetImageBytes":
|
||||
cancelGetImageBytes(call, result);
|
||||
break;
|
||||
case "delete":
|
||||
delete(call, result);
|
||||
break;
|
||||
case "rename":
|
||||
rename(call, result);
|
||||
break;
|
||||
|
@ -64,14 +67,14 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
|
||||
private void getImageBytes(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
Map map = call.argument("entry");
|
||||
Map entryMap = call.argument("entry");
|
||||
Integer width = call.argument("width");
|
||||
Integer height = call.argument("height");
|
||||
if (map == null || width == null || height == null) {
|
||||
if (entryMap == null || width == null || height == null) {
|
||||
result.error("getImageBytes-args", "failed because of missing arguments", null);
|
||||
return;
|
||||
}
|
||||
ImageEntry entry = new ImageEntry(map);
|
||||
ImageEntry entry = new ImageEntry(entryMap);
|
||||
imageDecodeTaskManager.fetch(result, entry, width, height);
|
||||
}
|
||||
|
||||
|
@ -81,16 +84,43 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
result.success(null);
|
||||
}
|
||||
|
||||
private void delete(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
Map entryMap = call.argument("entry");
|
||||
if (entryMap == null) {
|
||||
result.error("delete-args", "failed because of missing arguments", null);
|
||||
return;
|
||||
}
|
||||
Uri uri = Uri.parse((String) entryMap.get("uri"));
|
||||
String path = (String) entryMap.get("path");
|
||||
|
||||
ImageProvider provider = ImageProviderFactory.getProvider(uri);
|
||||
if (provider == null) {
|
||||
result.error("delete-provider", "failed to find provider for uri=" + uri, null);
|
||||
return;
|
||||
}
|
||||
provider.delete(activity, path, uri, new ImageProvider.ImageOpCallback() {
|
||||
@Override
|
||||
public void onSuccess(Map<String, Object> newFields) {
|
||||
new Handler(Looper.getMainLooper()).post(() -> result.success(newFields));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
new Handler(Looper.getMainLooper()).post(() -> result.error("delete-failure", "failed to delete", null));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void rename(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
Map map = call.argument("entry");
|
||||
Map entryMap = call.argument("entry");
|
||||
String newName = call.argument("newName");
|
||||
if (map == null || newName == null) {
|
||||
if (entryMap == null || newName == null) {
|
||||
result.error("rename-args", "failed because of missing arguments", null);
|
||||
return;
|
||||
}
|
||||
Uri uri = Uri.parse((String) map.get("uri"));
|
||||
String path = (String) map.get("path");
|
||||
String mimeType = (String) map.get("mimeType");
|
||||
Uri uri = Uri.parse((String) entryMap.get("uri"));
|
||||
String path = (String) entryMap.get("path");
|
||||
String mimeType = (String) entryMap.get("mimeType");
|
||||
|
||||
ImageProvider provider = ImageProviderFactory.getProvider(uri);
|
||||
if (provider == null) {
|
||||
|
@ -111,15 +141,15 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
|
||||
private void rotate(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
Map map = call.argument("entry");
|
||||
Map entryMap = call.argument("entry");
|
||||
Boolean clockwise = call.argument("clockwise");
|
||||
if (map == null || clockwise == null) {
|
||||
if (entryMap == null || clockwise == null) {
|
||||
result.error("rotate-args", "failed because of missing arguments", null);
|
||||
return;
|
||||
}
|
||||
Uri uri = Uri.parse((String) map.get("uri"));
|
||||
String path = (String) map.get("path");
|
||||
String mimeType = (String) map.get("mimeType");
|
||||
Uri uri = Uri.parse((String) entryMap.get("uri"));
|
||||
String path = (String) entryMap.get("path");
|
||||
String mimeType = (String) entryMap.get("mimeType");
|
||||
|
||||
ImageProvider provider = ImageProviderFactory.getProvider(uri);
|
||||
if (provider == null) {
|
||||
|
@ -151,7 +181,6 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
|
||||
@Override
|
||||
public void onPermissionDenied(PermissionDeniedResponse response) {
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setMessage("This permission is needed for use this features of the app so please, allow it!");
|
||||
builder.setTitle("We need this permission");
|
||||
|
|
|
@ -32,6 +32,10 @@ import deckers.thibault.aves.utils.Utils;
|
|||
public abstract class ImageProvider {
|
||||
private static final String LOG_TAG = Utils.createLogTag(ImageProvider.class);
|
||||
|
||||
public void delete(final Activity activity, final String path, final Uri uri, final ImageOpCallback callback) {
|
||||
callback.onFailure();
|
||||
}
|
||||
|
||||
public void rename(final Activity activity, final String oldPath, final Uri oldUri, final String mimeType, final String newFilename, final ImageOpCallback callback) {
|
||||
if (oldPath == null) {
|
||||
Log.w(LOG_TAG, "entry does not have a path, uri=" + oldUri);
|
||||
|
@ -114,7 +118,7 @@ public abstract class ImageProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private void rotateJpeg(Activity activity, final String path, final Uri uri, final String mimeType, boolean clockwise, final ImageOpCallback callback) {
|
||||
private void rotateJpeg(final Activity activity, final String path, final Uri uri, final String mimeType, boolean clockwise, final ImageOpCallback callback) {
|
||||
String editablePath = path;
|
||||
boolean onSdCard = Env.isOnSdCard(activity, path);
|
||||
if (onSdCard) {
|
||||
|
@ -176,7 +180,7 @@ public abstract class ImageProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private void rotatePng(Activity activity, final String path, final Uri uri, final String mimeType, boolean clockwise, final ImageOpCallback callback) {
|
||||
private void rotatePng(final Activity activity, final String path, final Uri uri, final String mimeType, boolean clockwise, final ImageOpCallback callback) {
|
||||
if (path == null) {
|
||||
callback.onFailure();
|
||||
return;
|
||||
|
|
|
@ -11,6 +11,9 @@ import java.util.ArrayList;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
import deckers.thibault.aves.model.ImageEntry;
|
||||
import deckers.thibault.aves.utils.Env;
|
||||
import deckers.thibault.aves.utils.PermissionManager;
|
||||
import deckers.thibault.aves.utils.StorageUtils;
|
||||
import deckers.thibault.aves.utils.Utils;
|
||||
|
||||
public class MediaStoreImageProvider extends ImageProvider {
|
||||
|
@ -111,4 +114,35 @@ public class MediaStoreImageProvider extends ImageProvider {
|
|||
}
|
||||
return entries.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(final Activity activity, final String path, final Uri uri, final ImageOpCallback callback) {
|
||||
// check write access permission to SD card
|
||||
// Before KitKat, we do whatever we want on the SD card.
|
||||
// From KitKat, we need access permission from the Document Provider, at the file level.
|
||||
// From Lollipop, we can request the permission at the SD card root level.
|
||||
if (Env.isOnSdCard(activity, path)) {
|
||||
Uri sdCardTreeUri = PermissionManager.getSdCardTreeUri(activity);
|
||||
if (sdCardTreeUri == null) {
|
||||
Runnable runnable = () -> delete(activity, path, uri, callback);
|
||||
PermissionManager.showSdCardAccessDialog(activity, runnable);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the file is on SD card, calling the content resolver delete() removes the entry from the MediaStore
|
||||
// but it doesn't delete the file, even if the app has the permission
|
||||
StorageUtils.deleteFromSdCard(activity, sdCardTreeUri, Env.getStorageVolumes(activity), path);
|
||||
Log.d(LOG_TAG, "deleted from SD card at path=" + uri);
|
||||
callback.onSuccess(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (activity.getContentResolver().delete(uri, null, null) > 0) {
|
||||
Log.d(LOG_TAG, "deleted from content resolver uri=" + uri);
|
||||
callback.onSuccess(null);
|
||||
return;
|
||||
}
|
||||
|
||||
callback.onFailure();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -188,6 +188,8 @@ class ImageEntry with ChangeNotifier {
|
|||
return false;
|
||||
}
|
||||
|
||||
Future<bool> delete() => ImageFileService.delete(this);
|
||||
|
||||
Future<bool> rename(String newName) async {
|
||||
if (newName == filename) return true;
|
||||
|
||||
|
|
|
@ -42,6 +42,18 @@ class ImageFileService {
|
|||
}
|
||||
}
|
||||
|
||||
static Future<bool> delete(ImageEntry entry) async {
|
||||
try {
|
||||
await platform.invokeMethod('delete', <String, dynamic>{
|
||||
'entry': entry.toMap(),
|
||||
});
|
||||
return true;
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('delete failed with exception=${e.message}');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static Future<Map> rename(ImageEntry entry, String newName) async {
|
||||
try {
|
||||
// return map with: 'contentId' 'path' 'title' 'uri' (all optional)
|
||||
|
|
|
@ -215,6 +215,9 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
|
||||
onActionSelected(ImageEntry entry, FullscreenAction action) {
|
||||
switch (action) {
|
||||
case FullscreenAction.delete:
|
||||
showDeleteDialog(entry);
|
||||
break;
|
||||
case FullscreenAction.edit:
|
||||
AndroidAppService.edit(entry.uri, entry.mimeType);
|
||||
break;
|
||||
|
@ -263,6 +266,32 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
showFeedback(success ? 'Done!' : 'Failed');
|
||||
}
|
||||
|
||||
showDeleteDialog(ImageEntry entry) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
content: Text('Are you sure?'),
|
||||
actions: [
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('CANCEL'),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text('DELETE'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (confirmed == null || !confirmed) return;
|
||||
if (await entry.delete())
|
||||
entries.remove(entry);
|
||||
else
|
||||
showFeedback('Failed');
|
||||
}
|
||||
|
||||
showRenameDialog(ImageEntry entry) async {
|
||||
final currentName = entry.title;
|
||||
final controller = TextEditingController(text: currentName);
|
||||
|
@ -287,12 +316,11 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
|||
);
|
||||
});
|
||||
if (newName == null || newName.isEmpty) return;
|
||||
final success = await entry.rename(newName);
|
||||
showFeedback(success ? 'Done!' : 'Failed');
|
||||
showFeedback(await entry.rename(newName) ? 'Done!' : 'Failed');
|
||||
}
|
||||
}
|
||||
|
||||
enum FullscreenAction { edit, info, open, openMap, rename, rotateCCW, rotateCW, setAs, share }
|
||||
enum FullscreenAction { delete, edit, info, open, openMap, rename, rotateCCW, rotateCW, setAs, share }
|
||||
|
||||
class ImagePage extends StatefulWidget {
|
||||
final List<ImageEntry> entries;
|
||||
|
|
|
@ -52,6 +52,10 @@ class FullscreenTopOverlay extends StatelessWidget {
|
|||
value: FullscreenAction.info,
|
||||
child: MenuRow(text: 'Info', icon: Icons.info_outline),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: FullscreenAction.delete,
|
||||
child: MenuRow(text: 'Delete', icon: Icons.delete_outline),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: FullscreenAction.rename,
|
||||
child: MenuRow(text: 'Rename', icon: Icons.title),
|
||||
|
|
Loading…
Reference in a new issue