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 android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
@ -20,9 +21,13 @@ import com.bumptech.glide.signature.ObjectKey;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
@ -81,9 +86,8 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
} }
case "share": { case "share": {
String title = call.argument("title"); String title = call.argument("title");
Uri uri = Uri.parse(call.argument("uri")); Map<String, List<String>> urisByMimeType = call.argument("urisByMimeType");
String mimeType = call.argument("mimeType"); shareMultiple(title, urisByMimeType);
share(title, uri, mimeType);
result.success(null); result.success(null);
break; break;
} }
@ -190,7 +194,7 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
context.startActivity(Intent.createChooser(intent, title)); 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); Intent intent = new Intent(Intent.ACTION_SEND);
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) { if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) {
String path = uri.getPath(); String path = uri.getPath();
@ -205,4 +209,35 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
intent.setType(mimeType); intent.setType(mimeType);
context.startActivity(Intent.createChooser(intent, title)); 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 { try {
await platform.invokeMethod('share', <String, dynamic>{ await platform.invokeMethod('share', <String, dynamic>{
'title': 'Share via:', 'title': 'Share via:',
'uri': uri, 'urisByMimeType': urisByMimeType,
'mimeType': mimeType,
}); });
} on PlatformException catch (e) { } on PlatformException catch (e) {
debugPrint('share failed with code=${e.code}, exception=${e.message}, details=${e.details}'); 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/collection_lens.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/album/filter_bar.dart'; import 'package:aves/widgets/album/filter_bar.dart';
import 'package:aves/widgets/album/search/search_delegate.dart'; import 'package:aves/widgets/album/search/search_delegate.dart';
import 'package:aves/widgets/common/menu_row.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:aves/widgets/stats/stats.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -128,7 +132,8 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
animation: collection.selectionChangeNotifier, animation: collection.selectionChangeNotifier,
builder: (context, child) { builder: (context, child) {
final selection = collection.selection; 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), icon: const Icon(OMIcons.search),
onPressed: _goToSearch, 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(
builder: (context) => PopupMenuButton<CollectionAction>( builder: (context) => PopupMenuButton<CollectionAction>(
itemBuilder: (context) => [ 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() { void _onActivityChange() {
if (collection.isSelecting) { if (collection.isSelecting) {
_browseToSelectAnimation.forward(); _browseToSelectAnimation.forward();

View file

@ -8,12 +8,18 @@ import 'package:outline_material_icons/outline_material_icons.dart';
class AIcons { class AIcons {
static const IconData date = OMIcons.calendarToday; 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 location = OMIcons.place;
static const IconData tag = OMIcons.localOffer; static const IconData tag = OMIcons.localOffer;
static const IconData video = OMIcons.movie; 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 animated = Icons.slideshow;
static const IconData play = Icons.play_circle_outline; static const IconData play = Icons.play_circle_outline;
static const IconData selected = Icons.check_circle_outline; static const IconData selected = Icons.check_circle_outline;

View file

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

View file

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