Download selected files

This commit is contained in:
Ming Ming 2021-09-29 04:56:44 +08:00
parent 145761495c
commit e7f510ac4f
6 changed files with 201 additions and 67 deletions

85
lib/download_handler.dart Normal file
View file

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/exception.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/notification.dart';
import 'package:nc_photos/platform/k.dart' as platform_k;
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/use_case/download_file.dart';
import 'package:tuple/tuple.dart';
class DownloadHandler {
Future<void> downloadFiles(Account account, List<File> files) async {
_log.info("[downloadFiles] Downloading ${files.length} file");
var controller = SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().downloadProcessingNotification),
duration: k.snackBarDurationShort,
));
controller?.closed.whenComplete(() {
controller = null;
});
final successes = <Tuple2<File, dynamic>>[];
for (final f in files) {
try {
successes.add(Tuple2(f, await DownloadFile()(account, f)));
} on PermissionException catch (_) {
_log.warning("[downloadFiles] Permission not granted");
controller?.close();
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().downloadFailureNoPermissionNotification),
duration: k.snackBarDurationNormal,
));
break;
} on JobCanceledException catch (_) {
_log.info("[downloadFiles] User canceled");
break;
} catch (e, stackTrace) {
_log.shout("[downloadFiles] Failed while DownloadFile", e, stackTrace);
controller?.close();
SnackBarManager().showSnackBar(SnackBar(
content: Text("${L10n.global().downloadFailureNotification}: "
"${exception_util.toUserString(e)}"),
duration: k.snackBarDurationNormal,
));
}
}
if (successes.isNotEmpty) {
controller?.close();
await _onDownloadSuccessful(successes.map((e) => e.item1).toList(),
successes.map((e) => e.item2).toList());
}
}
Future<void> _onDownloadSuccessful(
List<File> files, List<dynamic> results) async {
dynamic notif;
if (platform_k.isAndroid) {
notif = AndroidItemDownloadSuccessfulNotification(
results.cast<String>(), files.map((e) => e.contentType).toList());
}
if (notif != null) {
try {
await notif.notify();
return;
} catch (e, stacktrace) {
_log.shout(
"[_onDownloadSuccessful] Failed showing platform notification",
e,
stacktrace);
}
}
// fallback
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().downloadSuccessNotification),
duration: k.snackBarDurationShort,
));
}
static final _log = Logger("download_handler.DownloadHandler");
}

View file

@ -6,6 +6,7 @@ import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/download_handler.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
@ -249,7 +250,17 @@ class _AlbumBrowserState extends State<AlbumBrowser>
icon: const Icon(Icons.remove),
tooltip: L10n.global().removeSelectedFromAlbumTooltip,
onPressed: _onSelectionAppBarRemovePressed,
)
),
PopupMenuButton<_SelectionMenuOption>(
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
itemBuilder: (context) => [
PopupMenuItem(
value: _SelectionMenuOption.download,
child: Text(L10n.global().downloadTooltip),
),
],
onSelected: (option) => _onSelectionMenuSelected(context, option),
),
]);
}
@ -331,6 +342,29 @@ class _AlbumBrowserState extends State<AlbumBrowser>
}
}
void _onSelectionMenuSelected(
BuildContext context, _SelectionMenuOption option) {
switch (option) {
case _SelectionMenuOption.download:
_onSelectionDownloadPressed();
break;
default:
_log.shout("[_onSelectionMenuSelected] Unknown option: $option");
break;
}
}
void _onSelectionDownloadPressed() {
final selected = selectedListItems
.whereType<_FileListItem>()
.map((e) => e.file)
.toList();
DownloadHandler().downloadFiles(widget.account, selected);
setState(() {
clearSelectedItems();
});
}
void _onEditAppBarSortPressed() {
final sortProvider = _editAlbum!.sortProvider;
showDialog(
@ -634,6 +668,10 @@ class _AlbumBrowserState extends State<AlbumBrowser>
static final _log = Logger("widget.album_browser._AlbumBrowserState");
}
enum _SelectionMenuOption {
download,
}
abstract class _ListItem implements SelectableItem, DraggableItem {
_ListItem({
required this.index,

View file

@ -7,6 +7,7 @@ import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/debug_util.dart';
import 'package:nc_photos/download_handler.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/cover_provider.dart';
import 'package:nc_photos/entity/album/item.dart';
@ -258,19 +259,19 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
_onSelectionAppBarSharePressed(context);
},
),
PopupMenuButton(
PopupMenuButton<_SelectionMenuOption>(
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
itemBuilder: (context) => [
PopupMenuItem(
value: _SelectionMenuOption.download,
child: Text(L10n.global().downloadTooltip),
),
PopupMenuItem(
value: _SelectionMenuOption.delete,
child: Text(L10n.global().deleteTooltip),
),
],
onSelected: (option) {
if (option == _SelectionMenuOption.delete) {
_onSelectionAppBarDeletePressed();
}
},
onSelected: (option) => _onSelectionMenuSelected(context, option),
),
]);
}
@ -370,6 +371,21 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
});
}
void _onSelectionMenuSelected(
BuildContext context, _SelectionMenuOption option) {
switch (option) {
case _SelectionMenuOption.delete:
_onSelectionAppBarDeletePressed();
break;
case _SelectionMenuOption.download:
_onSelectionDownloadPressed();
break;
default:
_log.shout("[_onSelectionMenuSelected] Unknown option: $option");
break;
}
}
void _onSelectionAppBarDeletePressed() async {
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global()
@ -423,6 +439,17 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
}
}
void _onSelectionDownloadPressed() {
final selected = selectedListItems
.whereType<_FileListItem>()
.map((e) => e.file)
.toList();
DownloadHandler().downloadFiles(widget.account, selected);
setState(() {
clearSelectedItems();
});
}
void _onEditAppBarSortPressed() {
final sortProvider = _editAlbum!.sortProvider;
showDialog(
@ -553,6 +580,7 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
enum _SelectionMenuOption {
delete,
download,
}
abstract class _ListItem implements SelectableItem {

View file

@ -14,6 +14,7 @@ import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/scan_dir.dart';
import 'package:nc_photos/debug_util.dart';
import 'package:nc_photos/download_handler.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
@ -197,6 +198,10 @@ class _HomePhotosState extends State<HomePhotos>
PopupMenuButton<_SelectionMenuOption>(
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
itemBuilder: (context) => [
PopupMenuItem(
value: _SelectionMenuOption.download,
child: Text(L10n.global().downloadTooltip),
),
PopupMenuItem(
value: _SelectionMenuOption.archive,
child: Text(L10n.global().archiveTooltip),
@ -401,6 +406,17 @@ class _HomePhotosState extends State<HomePhotos>
}
}
void _onDownloadPressed() {
final selected = selectedListItems
.whereType<_FileListItem>()
.map((e) => e.file)
.toList();
DownloadHandler().downloadFiles(widget.account, selected);
setState(() {
clearSelectedItems();
});
}
Future<void> _onArchivePressed(BuildContext context) async {
final selectedFiles = selectedListItems
.whereType<_FileListItem>()
@ -472,6 +488,10 @@ class _HomePhotosState extends State<HomePhotos>
_onDeletePressed(context);
break;
case _SelectionMenuOption.download:
_onDownloadPressed();
break;
default:
_log.shout("[_onSelectionAppBarMenuSelected] Unknown option: $option");
break;
@ -859,4 +879,5 @@ class _MetadataTaskLoadingIcon extends AnimatedWidget {
enum _SelectionMenuOption {
archive,
delete,
download,
}

View file

@ -11,6 +11,7 @@ import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/list_face.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/debug_util.dart';
import 'package:nc_photos/download_handler.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
@ -279,6 +280,10 @@ class _PersonBrowserState extends State<PersonBrowser>
PopupMenuButton<_SelectionMenuOption>(
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
itemBuilder: (context) => [
PopupMenuItem(
value: _SelectionMenuOption.download,
child: Text(L10n.global().downloadTooltip),
),
PopupMenuItem(
value: _SelectionMenuOption.archive,
child: Text(L10n.global().archiveTooltip),
@ -362,6 +367,15 @@ class _PersonBrowserState extends State<PersonBrowser>
}
}
void _onDownloadPressed() {
final selected =
selectedListItems.whereType<_ListItem>().map((e) => e.file).toList();
DownloadHandler().downloadFiles(widget.account, selected);
setState(() {
clearSelectedItems();
});
}
Future<void> _onArchivePressed(BuildContext context) async {
final selectedFiles =
selectedListItems.whereType<_ListItem>().map((e) => e.file).toList();
@ -429,6 +443,10 @@ class _PersonBrowserState extends State<PersonBrowser>
_onDeletePressed(context);
break;
case _SelectionMenuOption.download:
_onDownloadPressed();
break;
default:
_log.shout("[_onOptionMenuSelected] Unknown option: $option");
break;
@ -561,4 +579,5 @@ class _ListItem implements SelectableItem {
enum _SelectionMenuOption {
archive,
delete,
download,
}

View file

@ -9,20 +9,18 @@ import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/debug_util.dart';
import 'package:nc_photos/download_handler.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file/data_source.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/exception.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/notification.dart';
import 'package:nc_photos/platform/k.dart' as platform_k;
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/theme.dart';
import 'package:nc_photos/use_case/download_file.dart';
import 'package:nc_photos/use_case/remove.dart';
import 'package:nc_photos/widget/animated_visibility.dart';
import 'package:nc_photos/widget/disposable.dart';
@ -440,65 +438,10 @@ class _ViewerState extends State<Viewer>
ShareHandler().shareFiles(context, widget.account, [file]);
}
void _onDownloadPressed() async {
void _onDownloadPressed() {
final file = widget.streamFiles[_viewerController.currentPage];
_log.info("[_onDownloadPressed] Downloading file: ${file.path}");
var controller = SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().downloadProcessingNotification),
duration: k.snackBarDurationShort,
));
controller?.closed.whenComplete(() {
controller = null;
});
dynamic result;
try {
result = await DownloadFile()(widget.account, file);
controller?.close();
await _onDownloadSuccessful(file, result);
} on PermissionException catch (_) {
_log.warning("[_onDownloadPressed] Permission not granted");
controller?.close();
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().downloadFailureNoPermissionNotification),
duration: k.snackBarDurationNormal,
));
} on JobCanceledException catch (_) {
_log.info("[_onDownloadPressed] Canceled");
} catch (e, stacktrace) {
_log.shout(
"[_onDownloadPressed] Failed while downloadFile", e, stacktrace);
controller?.close();
SnackBarManager().showSnackBar(SnackBar(
content: Text("${L10n.global().downloadFailureNotification}: "
"${exception_util.toUserString(e)}"),
duration: k.snackBarDurationNormal,
));
}
}
Future<void> _onDownloadSuccessful(File file, dynamic result) async {
dynamic notif;
if (platform_k.isAndroid) {
notif = AndroidItemDownloadSuccessfulNotification(
[result], [file.contentType]);
}
if (notif != null) {
try {
await notif.notify();
return;
} catch (e, stacktrace) {
_log.shout(
"[_onDownloadSuccessful] Failed showing platform notification",
e,
stacktrace);
}
}
// fallback
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().downloadSuccessNotification),
duration: k.snackBarDurationShort,
));
DownloadHandler().downloadFiles(widget.account, [file]);
}
void _onDeletePressed(BuildContext context) async {