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 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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}');
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue