selection: share

This commit is contained in:
Thibault Deckers 2020-04-22 13:19:32 +09:00
parent ab3140a66f
commit 2f532176ed
6 changed files with 81 additions and 16 deletions

View file

@ -10,6 +10,7 @@ import android.graphics.Bitmap;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import com.bumptech.glide.Glide;
@ -20,9 +21,13 @@ import com.bumptech.glide.signature.ObjectKey;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
@ -81,9 +86,8 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
}
case "share": {
String title = call.argument("title");
Uri uri = Uri.parse(call.argument("uri"));
String mimeType = call.argument("mimeType");
share(title, uri, mimeType);
Map<String, List<String>> urisByMimeType = call.argument("urisByMimeType");
shareMultiple(title, urisByMimeType);
result.success(null);
break;
}
@ -190,7 +194,7 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
context.startActivity(Intent.createChooser(intent, title));
}
private void share(String title, Uri uri, String mimeType) {
private void shareSingle(String title, Uri uri, String mimeType) {
Intent intent = new Intent(Intent.ACTION_SEND);
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) {
String path = uri.getPath();
@ -205,4 +209,35 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
intent.setType(mimeType);
context.startActivity(Intent.createChooser(intent, title));
}
private void shareMultiple(String title, @Nullable Map<String, List<String>> urisByMimeType) {
if (urisByMimeType == null) return;
ArrayList<Uri> uriList = urisByMimeType.values().stream().flatMap(Collection::stream).map(Uri::parse).collect(Collectors.toCollection(ArrayList::new));
String[] mimeTypes = urisByMimeType.keySet().toArray(new String[0]);
// simplify share intent for a single item, as some apps can handle one item but not more
if (uriList.size() == 1) {
shareSingle(title, uriList.get(0), mimeTypes[0]);
return;
}
String mimeType = "*/*";
if (mimeTypes.length == 1) {
// items have the same mime type & subtype
mimeType = mimeTypes[0];
} else {
// items have different subtypes
String[] mimeTypeTypes = Arrays.stream(mimeTypes).map(mt -> mt.split("/")[0]).distinct().toArray(String[]::new);
if (mimeTypeTypes.length == 1) {
// items have the same mime type
mimeType = mimeTypeTypes[0] + "/*";
}
}
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
intent.setType(mimeType);
context.startActivity(Intent.createChooser(intent, title));
}
}

View file

@ -76,12 +76,11 @@ class AndroidAppService {
}
}
static Future<void> share(String uri, String mimeType) async {
static Future<void> share(Map<String, List<String>> urisByMimeType) async {
try {
await platform.invokeMethod('share', <String, dynamic>{
'title': 'Share via:',
'uri': uri,
'mimeType': mimeType,
'urisByMimeType': urisByMimeType,
});
} on PlatformException catch (e) {
debugPrint('share failed with code=${e.code}, exception=${e.message}, details=${e.details}');

View file

@ -1,10 +1,14 @@
import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/album/filter_bar.dart';
import 'package:aves/widgets/album/search/search_delegate.dart';
import 'package:aves/widgets/common/menu_row.dart';
import 'package:aves/widgets/fullscreen/fullscreen_actions.dart';
import 'package:aves/widgets/stats/stats.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
@ -128,7 +132,8 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
animation: collection.selectionChangeNotifier,
builder: (context, child) {
final selection = collection.selection;
return Text(selection.isEmpty ? 'Select items' : '${selection.length} ${Intl.plural(selection.length, one: 'item', other: 'items')}');
final count = selection.length;
return Text(selection.isEmpty ? 'Select items' : '${count} ${Intl.plural(count, one: 'item', other: 'items')}');
},
);
}
@ -142,6 +147,18 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
icon: const Icon(OMIcons.search),
onPressed: _goToSearch,
),
if (collection.isSelecting)
AnimatedBuilder(
animation: collection.selectionChangeNotifier,
builder: (context, child) {
const action = FullscreenAction.share;
return IconButton(
icon: Icon(action.getIcon()),
onPressed: collection.selection.isEmpty ? null : _shareSelection,
tooltip: action.getText(),
);
},
),
Builder(
builder: (context) => PopupMenuButton<CollectionAction>(
itemBuilder: (context) => [
@ -258,6 +275,11 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
);
}
void _shareSelection() {
final urisByMimeType = groupBy<ImageEntry, String>(collection.selection, (e) => e.mimeType).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()));
AndroidAppService.share(urisByMimeType);
}
void _onActivityChange() {
if (collection.isSelecting) {
_browseToSelectAnimation.forward();

View file

@ -8,12 +8,18 @@ import 'package:outline_material_icons/outline_material_icons.dart';
class AIcons {
static const IconData date = OMIcons.calendarToday;
static const IconData favourite = OMIcons.favoriteBorder;
static const IconData favouriteActive = OMIcons.favorite;
static const IconData location = OMIcons.place;
static const IconData tag = OMIcons.localOffer;
static const IconData video = OMIcons.movie;
static const IconData delete = OMIcons.delete;
static const IconData favourite = OMIcons.favoriteBorder;
static const IconData favouriteActive = OMIcons.favorite;
static const IconData print = OMIcons.print;
static const IconData rotateLeft = OMIcons.rotateLeft;
static const IconData rotateRight = OMIcons.rotateRight;
static const IconData share = OMIcons.share;
static const IconData animated = Icons.slideshow;
static const IconData play = Icons.play_circle_outline;
static const IconData selected = Icons.check_circle_outline;

View file

@ -62,7 +62,9 @@ class FullscreenActionDelegate {
AndroidAppService.setAs(entry.uri, entry.mimeType);
break;
case FullscreenAction.share:
AndroidAppService.share(entry.uri, entry.mimeType);
AndroidAppService.share({
entry.mimeType: [entry.uri]
});
break;
case FullscreenAction.debug:
_goToDebug(context, entry);

View file

@ -1,3 +1,4 @@
import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart';
@ -66,19 +67,19 @@ extension ExtraFullscreenAction on FullscreenAction {
// different data depending on toggle state
return null;
case FullscreenAction.delete:
return OMIcons.delete;
return AIcons.delete;
case FullscreenAction.info:
return OMIcons.info;
case FullscreenAction.rename:
return OMIcons.title;
case FullscreenAction.rotateCCW:
return OMIcons.rotateLeft;
return AIcons.rotateLeft;
case FullscreenAction.rotateCW:
return OMIcons.rotateRight;
return AIcons.rotateRight;
case FullscreenAction.print:
return OMIcons.print;
return AIcons.print;
case FullscreenAction.share:
return OMIcons.share;
return AIcons.share;
// external app actions
case FullscreenAction.edit:
case FullscreenAction.open: