prep to delete multiple entries
This commit is contained in:
parent
a69a7ea436
commit
1751b7b3d7
10 changed files with 175 additions and 79 deletions
|
@ -11,8 +11,9 @@ import java.util.Map;
|
|||
import app.loup.streams_channel.StreamsChannel;
|
||||
import deckers.thibault.aves.channelhandlers.AppAdapterHandler;
|
||||
import deckers.thibault.aves.channelhandlers.FileAdapterHandler;
|
||||
import deckers.thibault.aves.channelhandlers.ImageByteStreamHandler;
|
||||
import deckers.thibault.aves.channelhandlers.ImageFileHandler;
|
||||
import deckers.thibault.aves.channelhandlers.ImageStreamHandler;
|
||||
import deckers.thibault.aves.channelhandlers.ImageOpStreamHandler;
|
||||
import deckers.thibault.aves.channelhandlers.MediaStoreStreamHandler;
|
||||
import deckers.thibault.aves.channelhandlers.MetadataHandler;
|
||||
import deckers.thibault.aves.utils.Constants;
|
||||
|
@ -48,8 +49,11 @@ public class MainActivity extends FlutterActivity {
|
|||
new MethodChannel(messenger, MetadataHandler.CHANNEL).setMethodCallHandler(new MetadataHandler(this));
|
||||
new EventChannel(messenger, MediaStoreStreamHandler.CHANNEL).setStreamHandler(mediaStoreStreamHandler);
|
||||
|
||||
final StreamsChannel imageStreamChannel = new StreamsChannel(messenger, ImageStreamHandler.CHANNEL);
|
||||
imageStreamChannel.setStreamHandlerFactory(arguments -> new ImageStreamHandler(this, arguments));
|
||||
final StreamsChannel imageByteStreamChannel = new StreamsChannel(messenger, ImageByteStreamHandler.CHANNEL);
|
||||
imageByteStreamChannel.setStreamHandlerFactory(arguments -> new ImageByteStreamHandler(this, arguments));
|
||||
|
||||
final StreamsChannel imageOpStreamChannel = new StreamsChannel(messenger, ImageOpStreamHandler.CHANNEL);
|
||||
imageOpStreamChannel.setStreamHandlerFactory(arguments -> new ImageOpStreamHandler(this, arguments));
|
||||
|
||||
new MethodChannel(messenger, VIEWER_CHANNEL).setMethodCallHandler(
|
||||
(call, result) -> {
|
||||
|
|
|
@ -24,8 +24,8 @@ import deckers.thibault.aves.decoder.VideoThumbnail;
|
|||
import deckers.thibault.aves.utils.MimeTypes;
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
|
||||
public class ImageStreamHandler implements EventChannel.StreamHandler {
|
||||
public static final String CHANNEL = "deckers.thibault/aves/imagestream";
|
||||
public class ImageByteStreamHandler implements EventChannel.StreamHandler {
|
||||
public static final String CHANNEL = "deckers.thibault/aves/imagebytestream";
|
||||
|
||||
private Activity activity;
|
||||
private Uri uri;
|
||||
|
@ -33,7 +33,7 @@ public class ImageStreamHandler implements EventChannel.StreamHandler {
|
|||
private EventChannel.EventSink eventSink;
|
||||
private Handler handler;
|
||||
|
||||
public ImageStreamHandler(Activity activity, Object arguments) {
|
||||
public ImageByteStreamHandler(Activity activity, Object arguments) {
|
||||
this.activity = activity;
|
||||
if (arguments instanceof Map) {
|
||||
Map argMap = (Map) arguments;
|
||||
|
@ -87,7 +87,6 @@ public class ImageStreamHandler implements EventChannel.StreamHandler {
|
|||
}
|
||||
} catch (Exception e) {
|
||||
error("getImage-video-exception", "failed to get image from uri=" + uri, e.getMessage());
|
||||
return;
|
||||
}
|
||||
Glide.with(activity).clear(target);
|
||||
} else {
|
|
@ -7,7 +7,6 @@ import android.os.Looper;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import deckers.thibault.aves.model.ImageEntry;
|
||||
|
@ -40,9 +39,6 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
case "getThumbnail":
|
||||
new Thread(() -> getThumbnail(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
case "delete":
|
||||
new Thread(() -> delete(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
case "rename":
|
||||
new Thread(() -> rename(call, new MethodResultWrapper(result))).start();
|
||||
break;
|
||||
|
@ -95,47 +91,6 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
});
|
||||
}
|
||||
|
||||
private void delete(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
List<Map> entryMapList = call.argument("entries");
|
||||
if (entryMapList == null) {
|
||||
result.error("delete-args", "failed because of missing arguments", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entryMapList.size() == 0) {
|
||||
result.success(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// assume same provider for all entries
|
||||
Map firstEntry = entryMapList.get(0);
|
||||
Uri firstUri = Uri.parse((String) firstEntry.get("uri"));
|
||||
ImageProvider provider = ImageProviderFactory.getProvider(firstUri);
|
||||
if (provider == null) {
|
||||
result.error("delete-provider", "failed to find provider for uri=" + firstUri, null);
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map entryMap : entryMapList) {
|
||||
Uri uri = Uri.parse((String) entryMap.get("uri"));
|
||||
String path = (String) entryMap.get("path");
|
||||
provider.delete(activity, path, uri, new ImageProvider.ImageOpCallback() {
|
||||
|
||||
// TODO TLAD this will fail for more than 1 entry. stream results back instead
|
||||
|
||||
@Override
|
||||
public void onSuccess(Map<String, Object> newFields) {
|
||||
new Handler(Looper.getMainLooper()).post(() -> result.success(1));
|
||||
}
|
||||
|
||||
@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 entryMap = call.argument("entry");
|
||||
String newName = call.argument("newName");
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
package deckers.thibault.aves.channelhandlers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import deckers.thibault.aves.model.provider.ImageProvider;
|
||||
import deckers.thibault.aves.model.provider.ImageProviderFactory;
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
|
||||
public class ImageOpStreamHandler implements EventChannel.StreamHandler {
|
||||
public static final String CHANNEL = "deckers.thibault/aves/imageopstream";
|
||||
|
||||
private Activity activity;
|
||||
private EventChannel.EventSink eventSink;
|
||||
private Handler handler;
|
||||
private List<Map> entryMapList;
|
||||
private String op;
|
||||
|
||||
public ImageOpStreamHandler(Activity activity, Object arguments) {
|
||||
this.activity = activity;
|
||||
if (arguments instanceof Map) {
|
||||
Map argMap = (Map) arguments;
|
||||
this.op = (String) argMap.get("op");
|
||||
this.entryMapList = new ArrayList<>();
|
||||
List rawEntries = (List) argMap.get("entries");
|
||||
if (rawEntries != null) {
|
||||
for (Object entry : rawEntries) {
|
||||
entryMapList.add((Map) entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListen(Object o, final EventChannel.EventSink eventSink) {
|
||||
this.eventSink = eventSink;
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
if ("delete".equals(op)) {
|
||||
new Thread(this::delete).start();
|
||||
} else {
|
||||
endOfStream();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object o) {
|
||||
}
|
||||
|
||||
// {String uri, bool success}
|
||||
private void success(final Map<String, Object> result) {
|
||||
handler.post(() -> eventSink.success(result));
|
||||
}
|
||||
|
||||
private void error(final String errorCode, final String errorMessage, final Object errorDetails) {
|
||||
handler.post(() -> eventSink.error(errorCode, errorMessage, errorDetails));
|
||||
}
|
||||
|
||||
private void endOfStream() {
|
||||
handler.post(() -> eventSink.endOfStream());
|
||||
}
|
||||
|
||||
private void delete() {
|
||||
if (entryMapList.size() == 0) {
|
||||
endOfStream();
|
||||
return;
|
||||
}
|
||||
|
||||
// assume same provider for all entries
|
||||
Map firstEntry = entryMapList.get(0);
|
||||
Uri firstUri = Uri.parse((String) firstEntry.get("uri"));
|
||||
ImageProvider provider = ImageProviderFactory.getProvider(firstUri);
|
||||
if (provider == null) {
|
||||
error("delete-provider", "failed to find provider for uri=" + firstUri, null);
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map entryMap : entryMapList) {
|
||||
String uriString = (String) entryMap.get("uri");
|
||||
Uri uri = Uri.parse(uriString);
|
||||
String path = (String) entryMap.get("path");
|
||||
provider.delete(activity, path, uri, new ImageProvider.ImageOpCallback() {
|
||||
@Override
|
||||
public void onSuccess(Map<String, Object> newFields) {
|
||||
Map<String, Object> result = new HashMap<String, Object>() {{
|
||||
put("uri", uriString);
|
||||
put("success", true);
|
||||
}};
|
||||
success(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
Map<String, Object> result = new HashMap<String, Object>() {{
|
||||
put("uri", uriString);
|
||||
put("success", false);
|
||||
}};
|
||||
success(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
endOfStream();
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package deckers.thibault.aves.channelhandlers;
|
|||
import android.app.Activity;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import deckers.thibault.aves.model.provider.MediaStoreImageProvider;
|
||||
import deckers.thibault.aves.utils.Utils;
|
||||
|
|
|
@ -307,9 +307,7 @@ public abstract class ImageProvider {
|
|||
values.put(MediaStore.MediaColumns.HEIGHT, rotatedHeight);
|
||||
int updatedRowCount = contentResolver.update(uri, values, null, null);
|
||||
if (updatedRowCount > 0) {
|
||||
MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> {
|
||||
callback.onSuccess(newFields);
|
||||
});
|
||||
MediaScannerConnection.scanFile(activity, new String[]{path}, new String[]{mimeType}, (p, u) -> callback.onSuccess(newFields));
|
||||
} else {
|
||||
Log.w(LOG_TAG, "failed to update fields in Media Store for uri=" + uri);
|
||||
callback.onSuccess(newFields);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/favourite_repo.dart';
|
||||
import 'package:aves/model/image_metadata.dart';
|
||||
import 'package:aves/services/image_file_service.dart';
|
||||
|
@ -295,7 +297,19 @@ class ImageEntry {
|
|||
return true;
|
||||
}
|
||||
|
||||
Future<bool> delete() async => (await ImageFileService.delete([this])) == 1;
|
||||
Future<bool> delete() {
|
||||
Completer completer = Completer<bool>();
|
||||
ImageFileService.delete([this]).listen(
|
||||
(event) => completer.complete(event.success),
|
||||
onError: completer.completeError,
|
||||
onDone: () {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void toggleFavourite() {
|
||||
if (isFavourite) {
|
||||
|
|
|
@ -10,7 +10,8 @@ import 'package:streams_channel/streams_channel.dart';
|
|||
|
||||
class ImageFileService {
|
||||
static const platform = MethodChannel('deckers.thibault/aves/image');
|
||||
static final StreamsChannel streamsChannel = StreamsChannel('deckers.thibault/aves/imagestream');
|
||||
static final StreamsChannel byteChannel = StreamsChannel('deckers.thibault/aves/imagebytestream');
|
||||
static final StreamsChannel opChannel = StreamsChannel('deckers.thibault/aves/imageopstream');
|
||||
|
||||
static Future<void> getImageEntries() async {
|
||||
try {
|
||||
|
@ -38,7 +39,7 @@ class ImageFileService {
|
|||
try {
|
||||
final completer = Completer<Uint8List>();
|
||||
final bytesBuilder = BytesBuilder(copy: false);
|
||||
streamsChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
byteChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
}).listen(
|
||||
|
@ -77,16 +78,16 @@ class ImageFileService {
|
|||
);
|
||||
}
|
||||
|
||||
static Future<int> delete(List<ImageEntry> entries) async {
|
||||
static Stream<ImageOpEvent> delete(List<ImageEntry> entries) {
|
||||
try {
|
||||
await platform.invokeMethod('delete', <String, dynamic>{
|
||||
return opChannel.receiveBroadcastStream(<String, dynamic>{
|
||||
'op': 'delete',
|
||||
'entries': entries.map((e) => e.toMap()).toList(),
|
||||
});
|
||||
return 1;
|
||||
}).map((event) => ImageOpEvent.fromMap(event));
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('delete failed with code=${e.code}, exception=${e.message}, details=${e.details}');
|
||||
return Stream.error(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Future<Map> rename(ImageEntry entry, String newName) async {
|
||||
|
@ -117,3 +118,17 @@ class ImageFileService {
|
|||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
class ImageOpEvent {
|
||||
final String uri;
|
||||
final bool success;
|
||||
|
||||
ImageOpEvent({this.uri, this.success});
|
||||
|
||||
factory ImageOpEvent.fromMap(Map map) {
|
||||
return ImageOpEvent(
|
||||
uri: map['uri'],
|
||||
success: map['success'] ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,22 +45,24 @@ class DecoratedThumbnail extends StatelessWidget {
|
|||
extent: extent,
|
||||
heroTag: heroTag,
|
||||
),
|
||||
if (showOverlay) Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
child: ThumbnailEntryOverlay(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
if (showOverlay)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
child: ThumbnailEntryOverlay(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showOverlay) Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: ThumbnailSelectionOverlay(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
if (showOverlay)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: ThumbnailSelectionOverlay(
|
||||
entry: entry,
|
||||
extent: extent,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:aves/model/collection_lens.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/widgets/fullscreen/image_view.dart';
|
||||
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
|
|
Loading…
Reference in a new issue