mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-02 14:56:20 +01:00
Share enhanced photos
This commit is contained in:
parent
65d8825b6b
commit
e088b1dbaa
6 changed files with 151 additions and 13 deletions
|
@ -102,6 +102,13 @@ class LocalFileRepo {
|
||||||
}) =>
|
}) =>
|
||||||
dataSrc.deleteFiles(files, onFailure: onFailure);
|
dataSrc.deleteFiles(files, onFailure: onFailure);
|
||||||
|
|
||||||
|
/// See [LocalFileDataSource.shareFiles]
|
||||||
|
Future<void> shareFiles(
|
||||||
|
List<LocalFile> files, {
|
||||||
|
LocalFileOnFailureListener? onFailure,
|
||||||
|
}) =>
|
||||||
|
dataSrc.shareFiles(files, onFailure: onFailure);
|
||||||
|
|
||||||
final LocalFileDataSource dataSrc;
|
final LocalFileDataSource dataSrc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,4 +121,10 @@ abstract class LocalFileDataSource {
|
||||||
List<LocalFile> files, {
|
List<LocalFile> files, {
|
||||||
LocalFileOnFailureListener? onFailure,
|
LocalFileOnFailureListener? onFailure,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Share files
|
||||||
|
Future<void> shareFiles(
|
||||||
|
List<LocalFile> files, {
|
||||||
|
LocalFileOnFailureListener? onFailure,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:nc_photos/entity/local_file.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/mobile/android/android_info.dart';
|
import 'package:nc_photos/mobile/android/android_info.dart';
|
||||||
import 'package:nc_photos/mobile/android/k.dart' as android;
|
import 'package:nc_photos/mobile/android/k.dart' as android;
|
||||||
|
import 'package:nc_photos/mobile/share.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/stream_extension.dart';
|
import 'package:nc_photos/stream_extension.dart';
|
||||||
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
|
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
|
||||||
|
@ -28,19 +29,9 @@ class LocalFileMediaStoreDataSource implements LocalFileDataSource {
|
||||||
LocalFileOnFailureListener? onFailure,
|
LocalFileOnFailureListener? onFailure,
|
||||||
}) async {
|
}) async {
|
||||||
_log.info("[deleteFiles] ${files.map((f) => f.logTag).toReadableString()}");
|
_log.info("[deleteFiles] ${files.map((f) => f.logTag).toReadableString()}");
|
||||||
final uriFiles = files
|
final uriFiles = _filterUriFiles(files, (f) {
|
||||||
.where((f) {
|
|
||||||
if (f is! LocalUriFile) {
|
|
||||||
_log.warning(
|
|
||||||
"[deleteFiles] Can't remove file not returned by this data source: $f");
|
|
||||||
onFailure?.call(f, ArgumentError("File not supported"), null);
|
onFailure?.call(f, ArgumentError("File not supported"), null);
|
||||||
return false;
|
});
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.cast<LocalUriFile>()
|
|
||||||
.toList();
|
|
||||||
if (AndroidInfo().sdkInt >= AndroidVersion.R) {
|
if (AndroidInfo().sdkInt >= AndroidVersion.R) {
|
||||||
await _deleteFiles30(uriFiles, onFailure);
|
await _deleteFiles30(uriFiles, onFailure);
|
||||||
} else {
|
} else {
|
||||||
|
@ -48,6 +39,27 @@ class LocalFileMediaStoreDataSource implements LocalFileDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
shareFiles(
|
||||||
|
List<LocalFile> files, {
|
||||||
|
LocalFileOnFailureListener? onFailure,
|
||||||
|
}) async {
|
||||||
|
_log.info("[shareFiles] ${files.map((f) => f.logTag).toReadableString()}");
|
||||||
|
final uriFiles = _filterUriFiles(files, (f) {
|
||||||
|
onFailure?.call(f, ArgumentError("File not supported"), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
final share = AndroidFileShare(uriFiles.map((e) => e.uri).toList(),
|
||||||
|
uriFiles.map((e) => e.mime).toList());
|
||||||
|
try {
|
||||||
|
await share.share();
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
for (final f in uriFiles) {
|
||||||
|
onFailure?.call(f, e, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _deleteFiles30(
|
Future<void> _deleteFiles30(
|
||||||
List<LocalUriFile> files, LocalFileOnFailureListener? onFailure) async {
|
List<LocalUriFile> files, LocalFileOnFailureListener? onFailure) async {
|
||||||
assert(AndroidInfo().sdkInt >= AndroidVersion.R);
|
assert(AndroidInfo().sdkInt >= AndroidVersion.R);
|
||||||
|
@ -79,6 +91,25 @@ class LocalFileMediaStoreDataSource implements LocalFileDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<LocalUriFile> _filterUriFiles(
|
||||||
|
List<LocalFile> files, [
|
||||||
|
void Function(LocalFile)? nonUriFileCallback,
|
||||||
|
]) {
|
||||||
|
return files
|
||||||
|
.where((f) {
|
||||||
|
if (f is! LocalUriFile) {
|
||||||
|
_log.warning(
|
||||||
|
"[deleteFiles] Can't remove file not returned by this data source: $f");
|
||||||
|
nonUriFileCallback?.call(f);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cast<LocalUriFile>()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
static LocalFile _toLocalFile(MediaStoreQueryResult r) => LocalUriFile(
|
static LocalFile _toLocalFile(MediaStoreQueryResult r) => LocalUriFile(
|
||||||
uri: r.uri,
|
uri: r.uri,
|
||||||
displayName: r.displayName,
|
displayName: r.displayName,
|
||||||
|
|
|
@ -3,12 +3,16 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_db.dart';
|
import 'package:nc_photos/app_db.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/debug_util.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/entity/share/data_source.dart';
|
import 'package:nc_photos/entity/share/data_source.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
@ -22,6 +26,7 @@ import 'package:nc_photos/use_case/copy.dart';
|
||||||
import 'package:nc_photos/use_case/create_dir.dart';
|
import 'package:nc_photos/use_case/create_dir.dart';
|
||||||
import 'package:nc_photos/use_case/create_share.dart';
|
import 'package:nc_photos/use_case/create_share.dart';
|
||||||
import 'package:nc_photos/use_case/download_file.dart';
|
import 'package:nc_photos/use_case/download_file.dart';
|
||||||
|
import 'package:nc_photos/use_case/share_local.dart';
|
||||||
import 'package:nc_photos/widget/processing_dialog.dart';
|
import 'package:nc_photos/widget/processing_dialog.dart';
|
||||||
import 'package:nc_photos/widget/share_link_multiple_files_dialog.dart';
|
import 'package:nc_photos/widget/share_link_multiple_files_dialog.dart';
|
||||||
import 'package:nc_photos/widget/share_method_dialog.dart';
|
import 'package:nc_photos/widget/share_method_dialog.dart';
|
||||||
|
@ -36,6 +41,32 @@ class ShareHandler {
|
||||||
this.clearSelection,
|
this.clearSelection,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Future<void> shareLocalFiles(List<LocalFile> files) async {
|
||||||
|
if (!isSelectionCleared) {
|
||||||
|
clearSelection?.call();
|
||||||
|
}
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
var hasShownError = false;
|
||||||
|
await ShareLocal(c)(
|
||||||
|
files,
|
||||||
|
onFailure: (f, e, stackTrace) {
|
||||||
|
if (e != null) {
|
||||||
|
_log.shout(
|
||||||
|
"[shareLocalFiles] Failed while sharing file: ${logFilename(f.logTag)}",
|
||||||
|
e,
|
||||||
|
stackTrace);
|
||||||
|
if (!hasShownError) {
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(exception_util.toUserString(e)),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
hasShownError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> shareFiles(Account account, List<File> files) async {
|
Future<void> shareFiles(Account account, List<File> files) async {
|
||||||
try {
|
try {
|
||||||
final method = await _askShareMethod();
|
final method = await _askShareMethod();
|
||||||
|
|
26
app/lib/use_case/share_local.dart
Normal file
26
app/lib/use_case/share_local.dart
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
|
|
||||||
|
class ShareLocal {
|
||||||
|
ShareLocal(this._c) : assert(require(_c));
|
||||||
|
|
||||||
|
static bool require(DiContainer c) =>
|
||||||
|
DiContainer.has(c, DiType.localFileRepo);
|
||||||
|
|
||||||
|
Future<void> call(
|
||||||
|
List<LocalFile> files, {
|
||||||
|
LocalFileOnFailureListener? onFailure,
|
||||||
|
}) async {
|
||||||
|
var count = files.length;
|
||||||
|
await _c.localFileRepo.shareFiles(files, onFailure: (f, e, stackTrace) {
|
||||||
|
--count;
|
||||||
|
onFailure?.call(f, e, stackTrace);
|
||||||
|
});
|
||||||
|
_log.info("[call] Shared $count files successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
final DiContainer _c;
|
||||||
|
|
||||||
|
static final _log = Logger("use_case.share_local.ShareLocal");
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
|
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/pref.dart';
|
||||||
|
import 'package:nc_photos/share_handler.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/widget/empty_list_indicator.dart';
|
import 'package:nc_photos/widget/empty_list_indicator.dart';
|
||||||
|
@ -160,6 +161,13 @@ class _EnhancedPhotoBrowserState extends State<EnhancedPhotoBrowser>
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.share),
|
||||||
|
tooltip: L10n.global().shareTooltip,
|
||||||
|
onPressed: () {
|
||||||
|
_onSelectionSharePressed(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
PopupMenuButton<_SelectionMenuOption>(
|
PopupMenuButton<_SelectionMenuOption>(
|
||||||
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
|
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
|
@ -198,6 +206,21 @@ class _EnhancedPhotoBrowserState extends State<EnhancedPhotoBrowser>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onSelectionSharePressed(BuildContext context) async {
|
||||||
|
final selected = selectedListItems
|
||||||
|
.whereType<_FileListItem>()
|
||||||
|
.map((e) => e.file)
|
||||||
|
.toList();
|
||||||
|
await ShareHandler(
|
||||||
|
context: context,
|
||||||
|
clearSelection: () {
|
||||||
|
setState(() {
|
||||||
|
clearSelectedItems();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
).shareLocalFiles(selected);
|
||||||
|
}
|
||||||
|
|
||||||
void _onSelectionMenuSelected(
|
void _onSelectionMenuSelected(
|
||||||
BuildContext context, _SelectionMenuOption option) {
|
BuildContext context, _SelectionMenuOption option) {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/local_file.dart';
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
|
import 'package:nc_photos/share_handler.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/widget/handler/delete_local_selection_handler.dart';
|
import 'package:nc_photos/widget/handler/delete_local_selection_handler.dart';
|
||||||
import 'package:nc_photos/widget/horizontal_page_viewer.dart';
|
import 'package:nc_photos/widget/horizontal_page_viewer.dart';
|
||||||
|
@ -108,6 +109,13 @@ class _LocalFileViewerState extends State<LocalFileViewer> {
|
||||||
shadowColor: Colors.transparent,
|
shadowColor: Colors.transparent,
|
||||||
foregroundColor: Colors.white.withOpacity(.87),
|
foregroundColor: Colors.white.withOpacity(.87),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.share),
|
||||||
|
tooltip: L10n.global().shareTooltip,
|
||||||
|
onPressed: () {
|
||||||
|
_onSharePressed(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
PopupMenuButton<_AppBarMenuOption>(
|
PopupMenuButton<_AppBarMenuOption>(
|
||||||
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
|
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
|
@ -126,6 +134,12 @@ class _LocalFileViewerState extends State<LocalFileViewer> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onSharePressed(BuildContext context) async {
|
||||||
|
final file = widget.streamFiles[_viewerController.currentPage];
|
||||||
|
_log.info("[_onSharePressed] Sharing file: ${file.logTag}");
|
||||||
|
await ShareHandler(context: context).shareLocalFiles([file]);
|
||||||
|
}
|
||||||
|
|
||||||
void _onMenuSelected(BuildContext context, _AppBarMenuOption option) {
|
void _onMenuSelected(BuildContext context, _AppBarMenuOption option) {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case _AppBarMenuOption.delete:
|
case _AppBarMenuOption.delete:
|
||||||
|
|
Loading…
Reference in a new issue