Don't dismiss viewer after some actions

This commit is contained in:
Ming Ming 2022-10-16 23:19:51 +08:00
parent 9c7ac04f25
commit c398dabc03
5 changed files with 214 additions and 92 deletions

View file

@ -20,6 +20,7 @@ class ArchiveSelectionHandler {
Future<int> call({
required Account account,
required List<FileDescriptor> selection,
bool shouldShowProcessingText = true,
}) async {
final selectedFiles = await InflateFileDescriptor(_c)(account, selection);
return NotifiedListAction<File>(
@ -27,8 +28,10 @@ class ArchiveSelectionHandler {
action: (file) async {
await UpdateProperty(_c.fileRepo).updateIsArchived(account, file, true);
},
processingText: L10n.global()
.archiveSelectedProcessingNotification(selectedFiles.length),
processingText: shouldShowProcessingText
? L10n.global()
.archiveSelectedProcessingNotification(selectedFiles.length)
: null,
successText: L10n.global().archiveSelectedSuccessNotification,
getFailureText: (failures) =>
L10n.global().archiveSelectedFailureNotification(failures.length),

View file

@ -0,0 +1,52 @@
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/di_container.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/notified_action.dart';
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
import 'package:nc_photos/use_case/update_property.dart';
class UnarchiveSelectionHandler {
UnarchiveSelectionHandler(this._c)
: assert(require(_c)),
assert(InflateFileDescriptor.require(_c));
static bool require(DiContainer c) => DiContainer.has(c, DiType.fileRepo);
/// Unarchive [selectedFiles] and return the unarchived count
Future<int> call({
required Account account,
required List<FileDescriptor> selection,
bool shouldShowProcessingText = true,
}) async {
final selectedFiles = await InflateFileDescriptor(_c)(account, selection);
return await NotifiedListAction<File>(
list: selectedFiles,
action: (file) async {
await UpdateProperty(_c.fileRepo)
.updateIsArchived(account, file, false);
},
processingText: shouldShowProcessingText
? L10n.global()
.unarchiveSelectedProcessingNotification(selectedFiles.length)
: null,
successText: L10n.global().unarchiveSelectedSuccessNotification,
getFailureText: (failures) =>
L10n.global().unarchiveSelectedFailureNotification(failures.length),
onActionError: (file, e, stackTrace) {
_log.shout(
"[call] Failed while unarchiving file: ${logFilename(file.path)}",
e,
stackTrace);
},
)();
}
final DiContainer _c;
static final _log = Logger(
"widget.handler.unarchive_selection_handler.UnarchiveSelectionHandler");
}

View file

@ -260,6 +260,15 @@ class _HorizontalPageViewerState extends State<HorizontalPageViewer> {
}
class HorizontalPageViewerController {
Future<void> previousPage({
required Duration duration,
required Curve curve,
}) =>
_pageController.previousPage(
duration: duration,
curve: curve,
);
Future<void> nextPage({
required Duration duration,
required Curve curve,
@ -269,6 +278,10 @@ class HorizontalPageViewerController {
curve: curve,
);
void jumpToPage(int page) {
_pageController.jumpToPage(page);
}
int get currentPage => _pageController.hasClients
? _pageController.page!.round()
: _pageController.initialPage;

View file

@ -12,6 +12,8 @@ import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/di_container.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';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/flutter_util.dart';
@ -22,10 +24,13 @@ import 'package:nc_photos/pref.dart';
import 'package:nc_photos/share_handler.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
import 'package:nc_photos/use_case/remove_from_album.dart';
import 'package:nc_photos/use_case/update_property.dart';
import 'package:nc_photos/widget/animated_visibility.dart';
import 'package:nc_photos/widget/disposable.dart';
import 'package:nc_photos/widget/handler/archive_selection_handler.dart';
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
import 'package:nc_photos/widget/handler/unarchive_selection_handler.dart';
import 'package:nc_photos/widget/horizontal_page_viewer.dart';
import 'package:nc_photos/widget/image_editor.dart';
import 'package:nc_photos/widget/image_enhancer.dart';
@ -91,6 +96,12 @@ class Viewer extends StatefulWidget {
class _ViewerState extends State<Viewer>
with DisposableManagerMixin<Viewer>, ViewerControllersMixin<Viewer> {
@override
initState() {
super.initState();
_streamFilesView = widget.streamFiles;
}
@override
build(BuildContext context) {
return AppTheme(
@ -119,7 +130,7 @@ class _ViewerState extends State<Viewer>
child: CircularProgressIndicator(),
),
HorizontalPageViewer(
pageCount: widget.streamFiles.length,
pageCount: _streamFilesView.length,
pageBuilder: _buildPage,
initialPage: widget.startIndex,
controller: _viewerController,
@ -139,7 +150,7 @@ class _ViewerState extends State<Viewer>
Widget _buildAppBar(BuildContext context) {
final index =
_isViewerLoaded ? _viewerController.currentPage : widget.startIndex;
final file = widget.streamFiles[index];
final file = _streamFilesView[index];
return Wrap(
children: [
AnimatedVisibility(
@ -198,7 +209,7 @@ class _ViewerState extends State<Viewer>
Widget _buildBottomAppBar(BuildContext context) {
final index =
_isViewerLoaded ? _viewerController.currentPage : widget.startIndex;
final file = widget.streamFiles[index];
final file = _streamFilesView[index];
return Align(
alignment: Alignment.bottomCenter,
child: Material(
@ -315,8 +326,11 @@ class _ViewerState extends State<Viewer>
visible: _isShowDetailPane,
child: ViewerDetailPane(
account: widget.account,
fd: widget.streamFiles[index],
fd: _streamFilesView[index],
album: widget.album,
onRemoveFromAlbumPressed: _onRemoveFromAlbumPressed,
onArchivePressed: _onArchivePressed,
onUnarchivePressed: _onUnarchivePressed,
onSlideshowPressed: _onSlideshowPressed,
),
),
@ -332,7 +346,7 @@ class _ViewerState extends State<Viewer>
}
Widget _buildItemView(BuildContext context, int index) {
final file = widget.streamFiles[index];
final file = _streamFilesView[index];
if (file_util.isSupportedImageFormat(file)) {
return _buildImageView(context, index);
} else if (file_util.isSupportedVideoFormat(file)) {
@ -347,7 +361,7 @@ class _ViewerState extends State<Viewer>
Widget _buildImageView(BuildContext context, int index) {
return RemoteImageViewer(
account: widget.account,
file: widget.streamFiles[index],
file: _streamFilesView[index],
canZoom: _canZoom(),
onLoaded: () => _onImageLoaded(index),
onHeightChanged: (height) => _updateItemHeight(index, height),
@ -367,7 +381,7 @@ class _ViewerState extends State<Viewer>
Widget _buildVideoView(BuildContext context, int index) {
return VideoViewer(
account: widget.account,
file: widget.streamFiles[index],
file: _streamFilesView[index],
onLoaded: () => _onVideoLoaded(index),
onHeightChanged: (height) => _updateItemHeight(index, height),
onPlay: _onVideoPlay,
@ -443,13 +457,13 @@ class _ViewerState extends State<Viewer>
!_pageStates[index]!.hasLoaded) {
_log.info("[_onImageLoaded] Pre-loading nearby images");
if (index > 0) {
final prevFile = widget.streamFiles[index - 1];
final prevFile = _streamFilesView[index - 1];
if (file_util.isSupportedImageFormat(prevFile)) {
RemoteImageViewer.preloadImage(widget.account, prevFile);
}
}
if (index + 1 < widget.streamFiles.length) {
final nextFile = widget.streamFiles[index + 1];
if (index + 1 < _streamFilesView.length) {
final nextFile = _streamFilesView[index + 1];
if (file_util.isSupportedImageFormat(nextFile)) {
RemoteImageViewer.preloadImage(widget.account, nextFile);
}
@ -517,7 +531,7 @@ class _ViewerState extends State<Viewer>
return;
}
final fd = widget.streamFiles[_viewerController.currentPage];
final fd = _streamFilesView[_viewerController.currentPage];
final c = KiwiContainer().resolve<DiContainer>();
final file = (await InflateFileDescriptor(c)(widget.account, [fd])).first;
setState(() {
@ -551,7 +565,7 @@ class _ViewerState extends State<Viewer>
return;
}
final fd = widget.streamFiles[_viewerController.currentPage];
final fd = _streamFilesView[_viewerController.currentPage];
final c = KiwiContainer().resolve<DiContainer>();
final file = (await InflateFileDescriptor(c)(widget.account, [fd])).first;
setState(() {
@ -589,7 +603,7 @@ class _ViewerState extends State<Viewer>
void _onSharePressed(BuildContext context) {
final c = KiwiContainer().resolve<DiContainer>();
final file = widget.streamFiles[_viewerController.currentPage];
final file = _streamFilesView[_viewerController.currentPage];
ShareHandler(
c,
context: context,
@ -597,7 +611,7 @@ class _ViewerState extends State<Viewer>
}
void _onEditPressed(BuildContext context) {
final file = widget.streamFiles[_viewerController.currentPage];
final file = _streamFilesView[_viewerController.currentPage];
if (!file_util.isSupportedImageFormat(file)) {
_log.shout("[_onEditPressed] Video file not supported");
return;
@ -609,7 +623,7 @@ class _ViewerState extends State<Viewer>
}
void _onEnhancePressed(BuildContext context) {
final file = widget.streamFiles[_viewerController.currentPage];
final file = _streamFilesView[_viewerController.currentPage];
if (!file_util.isSupportedImageFormat(file)) {
_log.shout("[_onEnhancePressed] Video file not supported");
return;
@ -624,23 +638,112 @@ class _ViewerState extends State<Viewer>
void _onDownloadPressed() {
final c = KiwiContainer().resolve<DiContainer>();
final file = widget.streamFiles[_viewerController.currentPage];
final file = _streamFilesView[_viewerController.currentPage];
_log.info("[_onDownloadPressed] Downloading file: ${file.fdPath}");
DownloadHandler(c).downloadFiles(widget.account, [file]);
}
Future<void> _onDeletePressed(BuildContext context) async {
void _onDeletePressed(BuildContext context) {
final index = _viewerController.currentPage;
final c = KiwiContainer().resolve<DiContainer>();
final file = widget.streamFiles[_viewerController.currentPage];
final file = _streamFilesView[index];
_log.info("[_onDeletePressed] Removing file: ${file.fdPath}");
final count = await RemoveSelectionHandler(c)(
unawaited(RemoveSelectionHandler(c)(
account: widget.account,
selection: [file],
isRemoveOpened: true,
isMoveToTrash: true,
);
if (count > 0 && mounted) {
));
_removeCurrentItemFromStream(context, index);
}
void _onArchivePressed(BuildContext context) {
final index = _viewerController.currentPage;
final c = KiwiContainer().resolve<DiContainer>();
final file = _streamFilesView[index];
_log.info("[_onArchivePressed] Archive file: ${file.fdPath}");
unawaited(ArchiveSelectionHandler(c)(
account: widget.account,
selection: [file],
shouldShowProcessingText: false,
));
_removeCurrentItemFromStream(context, index);
}
void _onUnarchivePressed(BuildContext context) {
final index = _viewerController.currentPage;
final c = KiwiContainer().resolve<DiContainer>();
final file = _streamFilesView[index];
_log.info("[_onUnarchivePressed] Unarchive file: ${file.fdPath}");
unawaited(UnarchiveSelectionHandler(c)(
account: widget.account,
selection: [file],
shouldShowProcessingText: false,
));
_removeCurrentItemFromStream(context, index);
}
void _onRemoveFromAlbumPressed(BuildContext context) {
assert(widget.album!.provider is AlbumStaticProvider);
final index = _viewerController.currentPage;
final c = KiwiContainer().resolve<DiContainer>();
final file = _streamFilesView[index];
_log.info("[_onRemoveFromAlbumPressed] Remove file: ${file.fdPath}");
NotifiedAction(
() async {
final selectedFile =
(await InflateFileDescriptor(c)(widget.account, [file])).first;
final thisItem = AlbumStaticProvider.of(widget.album!)
.items
.whereType<AlbumFileItem>()
.firstWhere((e) => e.file.compareServerIdentity(selectedFile));
await RemoveFromAlbum(KiwiContainer().resolve<DiContainer>())(
widget.account, widget.album!, [thisItem]);
},
null,
L10n.global().removeSelectedFromAlbumSuccessNotification(1),
failureText: L10n.global().removeSelectedFromAlbumFailureNotification,
).call().catchError((e, stackTrace) {
_log.shout("[_onRemoveFromAlbumPressed] Failed while updating album", e,
stackTrace);
});
_removeCurrentItemFromStream(context, index);
}
void _removeCurrentItemFromStream(BuildContext context, int index) {
if (_streamFilesView.length == 1) {
Navigator.of(context).pop();
} else {
if (index >= _streamFilesView.length - 1) {
// last item, go back
_viewerController
.previousPage(
duration: k.animationDurationNormal,
curve: Curves.easeInOut,
)
.then((_) {
if (mounted) {
setState(() {
_streamFilesEditable.removeAt(index);
});
}
});
} else {
_viewerController
.nextPage(
duration: k.animationDurationNormal,
curve: Curves.easeInOut,
)
.then((_) {
if (mounted) {
setState(() {
_streamFilesEditable.removeAt(index);
});
// a page is removed, length - 1
_viewerController.jumpToPage(index);
}
});
}
}
}
@ -747,6 +850,14 @@ class _ViewerState extends State<Viewer>
bool _canOpenDetailPane() => !_isZoomed;
bool _canZoom() => !_isDetailPaneActive;
List<FileDescriptor> get _streamFilesEditable {
if (!_isStreamFilesCopy) {
_streamFilesView = List.of(_streamFilesView);
_isStreamFilesCopy = true;
}
return _streamFilesView;
}
var _isShowAppBar = true;
var _isShowDetailPane = false;
@ -762,6 +873,9 @@ class _ViewerState extends State<Viewer>
double? _scrollStartPosition;
var _overscrollSum = 0.0;
late List<FileDescriptor> _streamFilesView;
bool _isStreamFilesCopy = false;
static final _log = Logger("widget.viewer._ViewerState");
static const _viewportFraction = 1.05;

View file

@ -27,14 +27,12 @@ import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
import 'package:nc_photos/use_case/list_file_tag.dart';
import 'package:nc_photos/use_case/remove_from_album.dart';
import 'package:nc_photos/use_case/update_album.dart';
import 'package:nc_photos/use_case/update_property.dart';
import 'package:nc_photos/widget/about_geocoding_dialog.dart';
import 'package:nc_photos/widget/animated_visibility.dart';
import 'package:nc_photos/widget/gps_map.dart';
import 'package:nc_photos/widget/handler/add_selection_to_album_handler.dart';
import 'package:nc_photos/widget/handler/archive_selection_handler.dart';
import 'package:nc_photos/widget/list_tile_center_leading.dart';
import 'package:nc_photos/widget/photo_date_time_edit_dialog.dart';
import 'package:path/path.dart' as path_lib;
@ -46,6 +44,9 @@ class ViewerDetailPane extends StatefulWidget {
required this.account,
required this.fd,
this.album,
required this.onRemoveFromAlbumPressed,
required this.onArchivePressed,
required this.onUnarchivePressed,
this.onSlideshowPressed,
}) : super(key: key);
@ -58,6 +59,9 @@ class ViewerDetailPane extends StatefulWidget {
/// The album this file belongs to, or null
final Album? album;
final void Function(BuildContext context) onRemoveFromAlbumPressed;
final void Function(BuildContext context) onArchivePressed;
final void Function(BuildContext context) onUnarchivePressed;
final VoidCallback? onSlideshowPressed;
}
@ -140,7 +144,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
_DetailPaneButton(
icon: Icons.remove_outlined,
label: L10n.global().removeFromAlbumTooltip,
onPressed: () => _onRemoveFromAlbumPressed(context),
onPressed: () => widget.onRemoveFromAlbumPressed(context),
),
if (widget.album != null &&
widget.album!.albumFile?.isOwned(widget.account.userId) ==
@ -159,13 +163,13 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
_DetailPaneButton(
icon: Icons.unarchive_outlined,
label: L10n.global().unarchiveTooltip,
onPressed: () => _onUnarchivePressed(context),
onPressed: () => widget.onUnarchivePressed(context),
)
else
_DetailPaneButton(
icon: Icons.archive_outlined,
label: L10n.global().archiveTooltip,
onPressed: () => _onArchivePressed(context),
onPressed: () => widget.onArchivePressed(context),
),
_DetailPaneButton(
icon: Icons.slideshow_outlined,
@ -417,31 +421,6 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
return cameraSubStr;
}
Future<void> _onRemoveFromAlbumPressed(BuildContext context) async {
assert(widget.album!.provider is AlbumStaticProvider);
try {
await NotifiedAction(
() async {
final thisItem = AlbumStaticProvider.of(widget.album!)
.items
.whereType<AlbumFileItem>()
.firstWhere((element) => element.file.path == widget.fd.fdPath);
await RemoveFromAlbum(KiwiContainer().resolve<DiContainer>())(
widget.account, widget.album!, [thisItem]);
if (mounted) {
Navigator.of(context).pop();
}
},
null,
L10n.global().removeSelectedFromAlbumSuccessNotification(1),
failureText: L10n.global().removeSelectedFromAlbumFailureNotification,
)();
} catch (e, stackTrace) {
_log.shout("[_onRemoveFromAlbumPressed] Failed while updating album", e,
stackTrace);
}
}
Future<void> _onSetAlbumCoverPressed(BuildContext context) async {
assert(_file != null);
assert(widget.album != null);
@ -479,45 +458,6 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
);
}
Future<void> _onArchivePressed(BuildContext context) async {
assert(_file != null);
_log.info("[_onArchivePressed] Archive file: ${widget.fd.fdPath}");
final c = KiwiContainer().resolve<DiContainer>();
final count = await ArchiveSelectionHandler(c)(
account: widget.account,
selection: [_file!],
);
if (count == 1) {
if (mounted) {
Navigator.of(context).pop();
}
}
}
Future<void> _onUnarchivePressed(BuildContext context) async {
assert(_file != null);
_log.info("[_onUnarchivePressed] Unarchive file: ${widget.fd.fdPath}");
try {
await NotifiedAction(
() async {
await UpdateProperty(_c.fileRepo)
.updateIsArchived(widget.account, _file!, false);
if (mounted) {
Navigator.of(context).pop();
}
},
L10n.global().unarchiveSelectedProcessingNotification(1),
L10n.global().unarchiveSelectedSuccessNotification,
failureText: L10n.global().unarchiveSelectedFailureNotification(1),
)();
} catch (e, stackTrace) {
_log.shout(
"[_onUnarchivePressed] Failed while archiving file: ${logFilename(widget.fd.fdPath)}",
e,
stackTrace);
}
}
void _onMapTap() {
if (platform_k.isAndroid) {
final intent = AndroidIntent(