selection: share
This commit is contained in:
parent
ab3140a66f
commit
2f532176ed
6 changed files with 81 additions and 16 deletions
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}');
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue