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":
|
case "cancelGetImageBytes":
|
||||||
cancelGetImageBytes(call, result);
|
cancelGetImageBytes(call, result);
|
||||||
break;
|
break;
|
||||||
|
case "delete":
|
||||||
|
delete(call, result);
|
||||||
|
break;
|
||||||
case "rename":
|
case "rename":
|
||||||
rename(call, result);
|
rename(call, result);
|
||||||
break;
|
break;
|
||||||
|
@ -64,14 +67,14 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getImageBytes(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
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 width = call.argument("width");
|
||||||
Integer height = call.argument("height");
|
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);
|
result.error("getImageBytes-args", "failed because of missing arguments", null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ImageEntry entry = new ImageEntry(map);
|
ImageEntry entry = new ImageEntry(entryMap);
|
||||||
imageDecodeTaskManager.fetch(result, entry, width, height);
|
imageDecodeTaskManager.fetch(result, entry, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,16 +84,43 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
||||||
result.success(null);
|
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) {
|
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");
|
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);
|
result.error("rename-args", "failed because of missing arguments", null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Uri uri = Uri.parse((String) map.get("uri"));
|
Uri uri = Uri.parse((String) entryMap.get("uri"));
|
||||||
String path = (String) map.get("path");
|
String path = (String) entryMap.get("path");
|
||||||
String mimeType = (String) map.get("mimeType");
|
String mimeType = (String) entryMap.get("mimeType");
|
||||||
|
|
||||||
ImageProvider provider = ImageProviderFactory.getProvider(uri);
|
ImageProvider provider = ImageProviderFactory.getProvider(uri);
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
|
@ -111,15 +141,15 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rotate(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
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");
|
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);
|
result.error("rotate-args", "failed because of missing arguments", null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Uri uri = Uri.parse((String) map.get("uri"));
|
Uri uri = Uri.parse((String) entryMap.get("uri"));
|
||||||
String path = (String) map.get("path");
|
String path = (String) entryMap.get("path");
|
||||||
String mimeType = (String) map.get("mimeType");
|
String mimeType = (String) entryMap.get("mimeType");
|
||||||
|
|
||||||
ImageProvider provider = ImageProviderFactory.getProvider(uri);
|
ImageProvider provider = ImageProviderFactory.getProvider(uri);
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
|
@ -151,7 +181,6 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPermissionDenied(PermissionDeniedResponse response) {
|
public void onPermissionDenied(PermissionDeniedResponse response) {
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
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.setMessage("This permission is needed for use this features of the app so please, allow it!");
|
||||||
builder.setTitle("We need this permission");
|
builder.setTitle("We need this permission");
|
||||||
|
|
|
@ -32,6 +32,10 @@ import deckers.thibault.aves.utils.Utils;
|
||||||
public abstract class ImageProvider {
|
public abstract class ImageProvider {
|
||||||
private static final String LOG_TAG = Utils.createLogTag(ImageProvider.class);
|
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) {
|
public void rename(final Activity activity, final String oldPath, final Uri oldUri, final String mimeType, final String newFilename, final ImageOpCallback callback) {
|
||||||
if (oldPath == null) {
|
if (oldPath == null) {
|
||||||
Log.w(LOG_TAG, "entry does not have a path, uri=" + oldUri);
|
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;
|
String editablePath = path;
|
||||||
boolean onSdCard = Env.isOnSdCard(activity, path);
|
boolean onSdCard = Env.isOnSdCard(activity, path);
|
||||||
if (onSdCard) {
|
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) {
|
if (path == null) {
|
||||||
callback.onFailure();
|
callback.onFailure();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -11,6 +11,9 @@ import java.util.ArrayList;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import deckers.thibault.aves.model.ImageEntry;
|
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;
|
import deckers.thibault.aves.utils.Utils;
|
||||||
|
|
||||||
public class MediaStoreImageProvider extends ImageProvider {
|
public class MediaStoreImageProvider extends ImageProvider {
|
||||||
|
@ -111,4 +114,35 @@ public class MediaStoreImageProvider extends ImageProvider {
|
||||||
}
|
}
|
||||||
return entries.stream();
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> delete() => ImageFileService.delete(this);
|
||||||
|
|
||||||
Future<bool> rename(String newName) async {
|
Future<bool> rename(String newName) async {
|
||||||
if (newName == filename) return true;
|
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 {
|
static Future<Map> rename(ImageEntry entry, String newName) async {
|
||||||
try {
|
try {
|
||||||
// return map with: 'contentId' 'path' 'title' 'uri' (all optional)
|
// 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) {
|
onActionSelected(ImageEntry entry, FullscreenAction action) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
case FullscreenAction.delete:
|
||||||
|
showDeleteDialog(entry);
|
||||||
|
break;
|
||||||
case FullscreenAction.edit:
|
case FullscreenAction.edit:
|
||||||
AndroidAppService.edit(entry.uri, entry.mimeType);
|
AndroidAppService.edit(entry.uri, entry.mimeType);
|
||||||
break;
|
break;
|
||||||
|
@ -263,6 +266,32 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
showFeedback(success ? 'Done!' : 'Failed');
|
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 {
|
showRenameDialog(ImageEntry entry) async {
|
||||||
final currentName = entry.title;
|
final currentName = entry.title;
|
||||||
final controller = TextEditingController(text: currentName);
|
final controller = TextEditingController(text: currentName);
|
||||||
|
@ -287,12 +316,11 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (newName == null || newName.isEmpty) return;
|
if (newName == null || newName.isEmpty) return;
|
||||||
final success = await entry.rename(newName);
|
showFeedback(await entry.rename(newName) ? 'Done!' : 'Failed');
|
||||||
showFeedback(success ? '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 {
|
class ImagePage extends StatefulWidget {
|
||||||
final List<ImageEntry> entries;
|
final List<ImageEntry> entries;
|
||||||
|
|
|
@ -52,6 +52,10 @@ class FullscreenTopOverlay extends StatelessWidget {
|
||||||
value: FullscreenAction.info,
|
value: FullscreenAction.info,
|
||||||
child: MenuRow(text: 'Info', icon: Icons.info_outline),
|
child: MenuRow(text: 'Info', icon: Icons.info_outline),
|
||||||
),
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: FullscreenAction.delete,
|
||||||
|
child: MenuRow(text: 'Delete', icon: Icons.delete_outline),
|
||||||
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: FullscreenAction.rename,
|
value: FullscreenAction.rename,
|
||||||
child: MenuRow(text: 'Rename', icon: Icons.title),
|
child: MenuRow(text: 'Rename', icon: Icons.title),
|
||||||
|
|
Loading…
Reference in a new issue