prep to delete multiple entries

This commit is contained in:
Thibault Deckers 2020-04-24 10:15:29 +09:00
parent a69a7ea436
commit 1751b7b3d7
10 changed files with 175 additions and 79 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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