fullscreen: added delete action

This commit is contained in:
Thibault Deckers 2019-08-16 16:13:35 +09:00
parent 0c8318444b
commit 7d2a27f797
7 changed files with 132 additions and 19 deletions

View file

@ -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");

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -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),